jsdw_ios/QuickLocation/Section/Home/HomeView2.swift

390 lines
12 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.

//
// HomeView2.swift
// QuickLocation
//
// Created by on 2026/8/4.
//
import UIKit
import RxSwift
import RxCocoa
import AMapNaviKit
import MarqueeLabel
class HomeView2: UIView {
var disposeBag = DisposeBag()
let groupMemberView = GroupMemberView2(frame: .zero)
///
private let stage1Height: CGFloat = 160 + 49 + kSafeBottomMargin
private let stage2Height: CGFloat = 327 + 49 + kSafeBottomMargin
// MARK: - groupMemberView
private var groupMemberTopConstraint: NSLayoutConstraint?
///
private var stage3Top: CGFloat = 0
///
private var stage2Top: CGFloat = 0
///
private var stage1Top: CGFloat = 0
private var isGroupMemberLimitsSet = false
private var panStartTop: CGFloat = 0
/// scrollView
private var isSubCanScroll = false
private var groupMemberScrollTopOffset: CGFloat {
-groupMemberView.scrollView.adjustedContentInset.top
}
private var stageTops: [CGFloat] {
[stage3Top, stage2Top, stage1Top]
}
var gpsSignalViewList: [UIView] = []
///
func updateGPSSignal(bars: Int) {
let filled = max(0, min(bars, gpsSignalViewList.count))
for (i, view) in gpsSignalViewList.enumerated() {
view.backgroundColor = i < filled ? UIColor(hexStr: "#4ED178") : UIColor(hexStr: "#E0E0E0")
}
}
private func setupRx() {
//
warningView.rx.tapGesture.subscribe { _ in
LocationPermissionPopView.show()
}.disposed(by: disposeBag)
}
private func setupUI() {
addSubview(mapView)
addSubview(gpsSignalView)
addSubview(warningView)
addSubview(inviteBtn)
addSubview(locationView)
addSubview(groupMemberView)
mapView.layoutChain
.edges(excludingEdge: .bottom)
.bottom(49 + kSafeBottomMargin)
gpsSignalView.layoutChain
.top(kStatusBarHeight + 11)
.left(15)
warningView.layoutChain
.topToBottomOfView(gpsSignalView, offset: 11)
.leftToView(gpsSignalView)
groupMemberView.layoutChain
.top(kScreenHeight - stage2Height)
.edgesHorzontal()
.bottom()
inviteBtn.layoutChain
.bottomToTopOfView(groupMemberView, offset: -9)
.edgesHorzontal(15)
.heightToWidth(50/345)
locationView.layoutChain
.bottomToTopOfView(inviteBtn, offset: -8)
.left(15)
.width(32)
.heightToWidth(1)
groupMemberTopConstraint = groupMemberView.jh_constraint(
.top, toAttribute: .top, otherView: self, relation: .equal
)
let pan = UIPanGestureRecognizer(target: self, action: #selector(handleGroupMemberPan(_:)))
pan.delegate = self
groupMemberView.addGestureRecognizer(pan)
groupMemberView.scrollView.delegate = self
groupMemberView.scrollView.isScrollEnabled = false
}
// MARK: - Map
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
DispatchQueue.main.async { mv.logoCenter = CGPoint(x: kScreenWidth - 50, y: kStatusBarHeight) }
return mv
}()
// MARK: -
lazy var gpsSignalView: UIView = {
let view = UIView()
view.cornerRadius = 6
view.backgroundColor = .white
let signalView1 = UIView()
signalView1.backgroundColor = UIColor(hexStr: "#E6E6E6")
view.addSubview(signalView1)
signalView1.layoutChain
.left(7)
.bottom(5)
.width(3)
.height(4)
let signalView2 = UIView()
signalView2.backgroundColor = UIColor(hexStr: "#E6E6E6")
view.addSubview(signalView2)
signalView2.layoutChain
.leftToRightOfView(signalView1, offset: 2)
.bottomToView(signalView1)
.width(3)
.height(6)
let signalView3 = UIView()
signalView3.backgroundColor = UIColor(hexStr: "#E6E6E6")
view.addSubview(signalView3)
signalView3.layoutChain
.leftToRightOfView(signalView2, offset: 2)
.bottomToView(signalView1)
.width(3)
.height(8)
let signalView4 = UIView()
signalView4.backgroundColor = UIColor(hexStr: "#E6E6E6")
view.addSubview(signalView4)
signalView4.layoutChain
.leftToRightOfView(signalView3, offset: 2)
.bottomToView(signalView1)
.width(3)
.height(10)
let label = UILabel()
label.text = "卫星信号"
label.font = .systemFont(ofSize: 10, weight: .medium)
label.textColor = UIColor(hexStr: "#353B4F")
view.addSubview(label)
label.layoutChain
.leftToRightOfView(signalView4, offset: 2)
.edgesVertical(4)
.right(7)
gpsSignalViewList = [signalView1, signalView2, signalView3, signalView4]
return view
}()
// MARK: - warning
lazy var warningView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.cornerRadius = 8
view.isHidden = true
let icon = UIImageView(image: UIImage(named: "Home/warning"))
view.addSubview(icon)
icon.layoutChain
.left(8)
.width(14)
.heightToWidth(1)
.centerY()
let label = UILabel()
label.text = "有权限未开启,数据可能会异常"
label.textColor = UIColor(hexStr: "#353B4F")
label.font = .systemFont(ofSize: 12, weight: .medium)
view.addSubview(label)
label.layoutChain
.leftToRightOfView(icon, offset: 6)
.right(8)
.edgesVertical(10)
return view
}()
// MARK: -
lazy var locationView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.cornerRadius = 10
view.addSubview(locationIconView)
locationIconView.layoutChain.centerX().centerY()
return view
}()
lazy var locationIconView: UIImageView = {
let view = UIImageView()
view.backgroundColor = .clear
view.image = UIImage(named: "Home/location")
return view
}()
// MARK: -
lazy var inviteBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setBackgroundImage(UIImage(named: "Home/invite"), for: .normal)
return btn
}()
override init(frame: CGRect) {
super.init(frame: .zero)
backgroundColor = .white
setupUI()
setupRx()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Layout
override func layoutSubviews() {
super.layoutSubviews()
if !isGroupMemberLimitsSet, bounds.height > 0 {
isGroupMemberLimitsSet = true
stage3Top = 0
stage2Top = bounds.height - stage2Height
stage1Top = bounds.height - stage1Height
groupMemberTopConstraint?.constant = stage2Top
}
}
// MARK: - Pan Gesture
@objc private func handleGroupMemberPan(_ pan: UIPanGestureRecognizer) {
guard isGroupMemberLimitsSet, let topConstraint = groupMemberTopConstraint else { return }
switch pan.state {
case .began:
layoutIfNeeded()
panStartTop = groupMemberView.frame.minY
case .changed:
let newTop = panStartTop + pan.translation(in: self).y
let scrollOffset = groupMemberView.scrollView.contentOffset.y
if isSubCanScroll {
if scrollOffset > groupMemberScrollTopOffset + 0.5 {
return
}
if pan.velocity(in: self).y > 0 || groupMemberView.frame.minY > stage3Top + 1 {
isSubCanScroll = false
panStartTop = groupMemberView.frame.minY
}
} else {
if groupMemberView.frame.minY <= stage3Top && newTop <= stage3Top {
isSubCanScroll = true
panStartTop = groupMemberView.frame.minY
topConstraint.constant = stage3Top
groupMemberView.setNavBarExpanded(true)
return
}
}
let clamped = max(stage3Top, min(stage1Top, newTop))
topConstraint.constant = clamped
case .ended, .cancelled:
let velocity = pan.velocity(in: self)
let currentTop = groupMemberView.frame.minY
let target = snapTarget(currentTop: currentTop, velocityY: velocity.y)
applyStage(top: target, animated: true)
default:
break
}
}
///
private func snapTarget(currentTop: CGFloat, velocityY: CGFloat) -> CGFloat {
if currentTop <= stage3Top + 5 {
// scroll
return nearestStage(to: currentTop)
}
if abs(velocityY) > 200 {
if velocityY < 0 {
// top
return stageTops.filter { $0 < currentTop - 1 }.max() ?? stage3Top
} else {
// top
return stageTops.filter { $0 > currentTop + 1 }.min() ?? stage1Top
}
}
return nearestStage(to: currentTop)
}
private func nearestStage(to top: CGFloat) -> CGFloat {
stageTops.min(by: { abs($0 - top) < abs($1 - top) }) ?? stage2Top
}
private func applyStage(top: CGFloat, animated: Bool) {
guard let topConstraint = groupMemberTopConstraint else { return }
let atTop = abs(top - stage3Top) < 0.5
topConstraint.constant = top
if !atTop {
isSubCanScroll = false
groupMemberView.scrollView.isScrollEnabled = false
groupMemberView.scrollView.setContentOffset(CGPoint(x: 0, y: groupMemberScrollTopOffset), animated: false)
}
isSubCanScroll = atTop
groupMemberView.setNavBarExpanded(atTop)
let animations = {
self.layoutIfNeeded()
}
let completion: (Bool) -> Void = { _ in
self.groupMemberView.scrollView.isScrollEnabled = atTop
}
if animated {
UIView.animate(withDuration: 0.25, delay: 0,
options: [.curveEaseOut, .allowUserInteraction],
animations: animations, completion: completion)
} else {
animations()
completion(true)
}
}
/// GroupMemberView2
func dismissGroupMemberView() {
guard isGroupMemberLimitsSet else { return }
applyStage(top: stage2Top, animated: true)
}
}
// MARK: - UIScrollViewDelegate
extension HomeView2: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
guard scrollView == groupMemberView.scrollView else { return }
if scrollView.contentOffset.y > groupMemberScrollTopOffset {
isSubCanScroll = true
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard scrollView == groupMemberView.scrollView else { return }
if isSubCanScroll {
if scrollView.contentOffset.y <= groupMemberScrollTopOffset {
isSubCanScroll = false
scrollView.contentOffset = CGPoint(x: 0, y: groupMemberScrollTopOffset)
}
} else {
scrollView.contentOffset = CGPoint(x: 0, y: groupMemberScrollTopOffset)
}
}
}
// MARK: - UIGestureRecognizerDelegate
extension HomeView2: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer) -> Bool {
return true
}
}