jsdw_ios/QuickLocation/Section/Home/HomeView.swift

583 lines
17 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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)
let quickMessageView = QuickMessageView(frame: CGRectMake(0, kScreenHeight, kScreenWidth, 93))
let interactionView = InteractionView(frame: CGRectMake(0, kScreenHeight, kScreenWidth, 384))
// 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
/// tableView
private var isSubCanScroll = false
// 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
///
var onDismissPanel: (() -> Void)?
// MARK: - Setup
private func setupRx() {
quickMessageView.closeBtn.rx.tap
.subscribe(onNext: { [weak self] _ in
self?.dismissMemberPanel()
self?.onDismissPanel?()
})
.disposed(by: disposeBag)
// tableView GroupMemberView pan
groupMemberView.tableView.rx.contentOffset
.subscribe(onNext: { [weak self] offset in
guard let self = self else { return }
if self.isSubCanScroll {
if offset.y <= 0 {
self.isSubCanScroll = false
self.groupMemberView.tableView.setContentOffset(.zero, animated: false)
}
} else if offset.y != 0 {
self.groupMemberView.tableView.setContentOffset(.zero, animated: false)
}
})
.disposed(by: disposeBag)
// view
quickMessageView.closeBtn.rx.tap.subscribe(onNext: { _ in
self.dismissMemberPanel()
}).disposed(by: disposeBag)
}
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(_:)))
pan.delegate = self
groupMemberView.addGestureRecognizer(pan)
// quickMessageView interactionView
addSubview(interactionView)
addSubview(quickMessageView)
}
// MARK: -
func showMemberPanel(member: CircleMember) {
interactionView.configure(member: member)
quickMessageView.isHidden = false
interactionView.isHidden = false
quickMessageView.frame.origin.y = kScreenHeight - 384 - 93
interactionView.frame.origin.y = kScreenHeight - 384
}
func dismissMemberPanel() {
quickMessageView.frame.origin.y = kScreenHeight
interactionView.frame.origin.y = kScreenHeight
quickMessageView.isHidden = true
interactionView.isHidden = true
onDismissPanel?()
}
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 = groupMemberView.frame.minY
case .changed:
let newTop = panStartTop + pan.translation(in: self).y
// tableView
// tableView view
if isSubCanScroll {
let tableViewOffset = self.groupMemberView.tableView.contentOffset.y
if tableViewOffset > 0, newTop >= groupMemberUpLimit {
// tableView
return
}
// view pan
isSubCanScroll = false
panStartTop = groupMemberView.frame.minY
}
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()
} completion: { _ in
let atTop = target == self.groupMemberUpLimit
self.isSubCanScroll = atTop
// tableView
if !atTop {
self.groupMemberView.tableView.contentOffset.y = 0
}
}
default:
break
}
}
/// GroupMemberView
func dismissGroupMemberView() {
guard isGroupMemberLimitsSet, let topConstraint = groupMemberTopConstraint else { return }
isSubCanScroll = false
topConstraint.constant = groupMemberDownLimit - groupMemberDownLimit + 10
groupMemberView.tableView.setContentOffset(.zero, animated: false)
UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseOut) {
self.layoutIfNeeded()
}
}
// 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
}()
}
// MARK: - UIGestureRecognizerDelegate
extension HomeView: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer) -> Bool {
// GroupMemberView pan tableView pan
// isSubCanScroll + contentOffset
return true
}
}