jsdw_ios/QuickLocation/Section/Home/HomeView.swift

485 lines
13 KiB
Swift

//
// HomeView.swift
// QuickLocation
//
// Created by on 2026/5/27.
//
import UIKit
import RxSwift
import RxCocoa
import Lottie
#if !targetEnvironment(simulator)
import MAMapKit
#endif
class HomeView: UIView {
var disposeBag = DisposeBag()
let groupMemberView = GroupMemberView(frame: .zero)
// MARK: - groupMemberView
private var groupMemberTopConstraint: NSLayoutConstraint?
private var groupMemberUpLimit: CGFloat = 0
private var groupMemberDownLimit: CGFloat = 0
private var isGroupMemberLimitsSet = false
private var panStartTop: CGFloat = 0
// MARK: - Map
#if !targetEnvironment(simulator)
lazy var mapView: MAMapView = {
let mv = MAMapView()
mv.zoomLevel = 16
mv.showsUserLocation = true
mv.userTrackingMode = .none
mv.showsCompass = false
mv.showsScale = false
mv.isRotateEnabled = false
mv.isRotateCameraEnabled = false
return mv
}()
#else
lazy var mapPlaceholderView: UIView = {
let v = UIView()
v.backgroundColor = UIColor(hexStr: "#EDEDED")
let label = UILabel()
label.text = "Map requires a real device"
label.font = UIFont.systemFont(ofSize: 14)
label.textColor = UIColor(hexStr: "#999999")
label.textAlignment = .center
v.addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: v.centerXAnchor),
label.centerYAnchor.constraint(equalTo: v.centerYAnchor)
])
return v
}()
#endif
// MARK: - Setup
private func setupRx() {
}
private func setupUI() {
#if !targetEnvironment(simulator)
addSubview(mapView)
#else
addSubview(mapPlaceholderView)
#endif
addSubview(navBarBg)
addSubview(avatarImgView)
addSubview(groupView)
groupView.addSubview(groupIconView)
groupView.addSubview(groupNameLab)
groupView.addSubview(groupArrowIconView)
addSubview(messageView)
addSubview(toolsView)
addSubview(searchLottieView)
addSubview(locationView)
locationView.addSubview(locationIconView)
#if !targetEnvironment(simulator)
mapView.layoutChain
.top()
.edges(excludingEdge: .top)
sendSubviewToBack(mapView)
#else
mapPlaceholderView.layoutChain
.topToBottomOfView(navBarBg)
.left(0).right(0).bottom(0)
#endif
navBarBg.layoutChain
.edges(excludingEdge: .bottom)
.heightToWidth(160/375)
avatarImgView.layoutChain
.top(59)
.left(15)
.width(36)
.heightToWidth(1)
groupView.layoutChain
.centerY(avatarImgView)
.height(36)
.centerX()
.width(185, relation: .greaterThanOrEqual)
groupIconView.layoutChain
.left(11)
.centerY()
.width(30)
.height(30)
groupArrowIconView.layoutChain
.right(15)
.centerY()
.width(15)
.height(8.5)
groupNameLab.layoutChain
.edgesVertical()
.leftToRightOfView(groupIconView)
.rightToLeftOfView(groupArrowIconView)
messageView.layoutChain
.right(15)
.centerY(groupView)
.width(36)
.height(36)
toolsView.layoutChain
.left(23)
.centerY()
.width(40)
bubbleView.layoutChain
.top(12)
.height(58)
bubbleIcon.layoutChain
.top()
.centerX()
.width(28)
.height(28)
bubbleLab.layoutChain
.topToBottomOfView(bubbleIcon, offset: 4)
.edgesHorzontal()
signInView.layoutChain.height(58)
signInIcon.layoutChain
.top()
.centerX()
.width(28)
.height(28)
signInLab.layoutChain
.topToBottomOfView(signInIcon, offset: 4)
.edgesHorzontal()
sosView.layoutChain
.height(56)
sosIcon.layoutChain
.top()
.centerX()
.width(28)
.height(28)
sosLab.layoutChain
.topToBottomOfView(sosIcon, offset: 4)
.edgesHorzontal()
searchLottieView.layoutChain
.centerY()
.right()
.width(100)
.height(100)
locationView.layoutChain
.topToBottomOfView(searchLottieView, offset: 8)
.right(15)
.bottomToView(toolsView)
.width(40)
.height(40)
locationIconView.layoutChain
.centerX()
.centerY()
// View
addSubview(groupMemberView)
groupMemberView.layoutChain
.topToBottomOfView(toolsView, offset: 10)
.edgesHorzontal()
.bottom()
groupMemberTopConstraint = groupMemberView.jh_constraint(
.top, toAttribute: .bottom, otherView: toolsView, relation: .equal
)
let pan = UIPanGestureRecognizer(target: self, action: #selector(handleGroupMemberPan(_:)))
groupMemberView.addGestureRecognizer(pan)
}
override init(frame: CGRect) {
super.init(frame: .zero)
backgroundColor = .white
setupUI()
setupRx()
searchLottieView.play()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Layout
override func layoutSubviews() {
super.layoutSubviews()
if !isGroupMemberLimitsSet {
isGroupMemberLimitsSet = true
groupMemberDownLimit = groupMemberView.frame.minY
groupMemberUpLimit = groupView.frame.maxY + 20
}
}
// MARK: - Pan Gesture
@objc private func handleGroupMemberPan(_ pan: UIPanGestureRecognizer) {
guard isGroupMemberLimitsSet, let topConstraint = groupMemberTopConstraint else { return }
switch pan.state {
case .began:
panStartTop = pan.location(in: self).y - pan.translation(in: self).y
case .changed:
let newTop = panStartTop + pan.translation(in: self).y
let clamped = max(groupMemberUpLimit, min(groupMemberDownLimit, newTop))
topConstraint.constant = clamped - groupMemberDownLimit + 10
case .ended, .cancelled:
let velocity = pan.velocity(in: self)
let isNearUp = abs(groupMemberView.frame.minY - groupMemberUpLimit) < abs(groupMemberView.frame.minY - groupMemberDownLimit)
let target: CGFloat
if abs(velocity.y) > 200 {
target = velocity.y < 0 ? groupMemberUpLimit : groupMemberDownLimit
} else {
target = isNearUp ? groupMemberUpLimit : groupMemberDownLimit
}
topConstraint.constant = target - groupMemberDownLimit + 10
UIView.animate(withDuration: 0.25, delay: 0,
options: [.curveEaseOut, .allowUserInteraction]) {
self.layoutIfNeeded()
}
default:
break
}
}
// MARK: - UI Components
lazy var navBarBg: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "Home/navBar_bg")
view.contentMode = .scaleAspectFill
return view
}()
lazy var avatarImgView: UIImageView = {
let view = UIImageView()
view.backgroundColor = .lightGray
view.contentMode = .scaleAspectFill
view.cornerRadius = 18
return view
}()
//
lazy var groupView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.cornerRadius = 18
return view
}()
lazy var groupIconView: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "Home/group")
view.backgroundColor = .clear
view.contentMode = .scaleAspectFill
return view
}()
lazy var groupNameLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 16, weight: .medium)
label.textColor = UIColor(hexStr: "#0F2846")
label.textAlignment = .center
return label
}()
lazy var groupArrowIconView: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "Home/arrow_down")
view.backgroundColor = .clear
view.contentMode = .scaleAspectFill
return view
}()
///
lazy var messageView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.cornerRadius = 18
view.clipsToBounds = false
view.addSubview(messageIcon)
view.addSubview(messageDotView)
messageIcon.layoutChain
.centerX().centerY()
messageDotView.layoutChain
.top()
.right()
.width(10)
.height(10)
return view
}()
lazy var messageIcon: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "Home/message")
view.backgroundColor = .clear
view.contentMode = .scaleAspectFill
return view
}()
lazy var messageDotView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hexStr: "#FD5E61")
view.cornerRadius = 5
return view
}()
// MARK: -
lazy var toolsView: UIStackView = {
let view = UIStackView(arrangedSubviews: [bubbleView, signInView, sosView])
view.axis = .vertical
view.distribution = .fillEqually
view.alignment = .center
view.spacing = 0
view.backgroundColor = .black.withAlphaComponent(0.5)
view.cornerRadius = 20
return view
}()
//
lazy var bubbleView: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.addSubview(bubbleIcon)
view.addSubview(bubbleLab)
let lineView = UIView()
lineView.backgroundColor = .white
view.addSubview(lineView)
lineView.layoutChain
.width(12)
.height(2)
.centerX()
.bottom(7)
return view
}()
lazy var bubbleIcon: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "Home/bubble")
view.backgroundColor = .clear
view.contentMode = .scaleAspectFill
return view
}()
lazy var bubbleLab: UILabel = {
let label = UILabel()
label.text = "气泡"
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = .white
label.textAlignment = .center
return label
}()
//
lazy var signInView: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.addSubview(signInIcon)
view.addSubview(signInLab)
let lineView = UIView()
lineView.backgroundColor = .white
view.addSubview(lineView)
lineView.layoutChain
.width(12)
.height(2)
.centerX()
.bottom(7)
return view
}()
lazy var signInIcon: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "Home/signIn")
view.backgroundColor = .clear
view.contentMode = .scaleAspectFill
return view
}()
lazy var signInLab: UILabel = {
let label = UILabel()
label.text = "签到"
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = .white
label.textAlignment = .center
return label
}()
// SOS
lazy var sosView: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.addSubview(sosIcon)
view.addSubview(sosLab)
return view
}()
lazy var sosIcon: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "Home/sos")
view.backgroundColor = .clear
view.contentMode = .scaleAspectFill
return view
}()
lazy var sosLab: UILabel = {
let label = UILabel()
label.text = "SOS"
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = .white
label.textAlignment = .center
return label
}()
// MARK: -
lazy var searchLottieView: LottieAnimationView = {
let view = LottieAnimationView(name: "home_search")
view.loopMode = .loop
return view
}()
// MARK: -
lazy var locationView: UIView = {
let view = UIView()
view.backgroundColor = .black.withAlphaComponent(0.4)
view.cornerRadius = 20
return view
}()
lazy var locationIconView: UIImageView = {
let view = UIImageView()
view.backgroundColor = .clear
view.image = UIImage(named: "Home/location")
return view
}()
}