jsdw_ios/QuickLocation/Section/Schedule/CreateSchedule/CreateScheduleView.swift

246 lines
7.9 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.

//
// CreateScheduleView.swift
// QuickLocation
//
// Created by 八条 on 2026/6/23.
//
import UIKit
import RxSwift
import RxCocoa
#if !targetEnvironment(simulator)
import AMapNaviKit
#endif
class CreateScheduleView: UIView {
let createSchedulePopView = CreateSchedulePopView()
// MARK: - PopView 拖拽
private var popTopConstraint: NSLayoutConstraint?
private var popUpLimit: CGFloat = 0
private var popDownLimit: CGFloat = 0
private var isLimitsSet = false
private var panStartTop: CGFloat = 0
private var isSubCanScroll = false
private func setupRx() {
// 嵌套滑动协调(参考 GroupView 的 PanScrollView 模式)
createSchedulePopView.scrollView.delegate = self
}
private func setupUI() {
#if !targetEnvironment(simulator)
addSubview(mapView)
#endif
addSubview(navBgView)
addSubview(navView)
navView.addRightButton(deleteBtn)
addSubview(createSchedulePopView)
navBgView.layoutChain
.edges(excludingEdge: .bottom)
.heightToWidth(160/375)
navView.layoutChain
.edges(excludingEdge: .bottom)
.height(kNaviHeight)
#if !targetEnvironment(simulator)
mapView.layoutChain
.top()
.edgesHorzontal()
.bottom()
#endif
// PopView: 初始底部 1/3,最大滑到 navBar 底部
createSchedulePopView.layoutChain
.edgesHorzontal()
.bottom()
.top(kScreenHeight / 3 * 2)
popTopConstraint = createSchedulePopView.jh_constraint(
.top, toAttribute: .top, otherView: createSchedulePopView.superview, relation: .equal
)
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePopPan(_:)))
pan.delegate = self
createSchedulePopView.addGestureRecognizer(pan)
}
// MARK: - Pan Gesture
@objc private func handlePopPan(_ pan: UIPanGestureRecognizer) {
guard isLimitsSet, let topConstraint = popTopConstraint else { return }
if createSchedulePopView.isReorderingPoints { return }
switch pan.state {
case .began:
layoutIfNeeded()
panStartTop = createSchedulePopView.frame.minY
case .changed:
let newTop = panStartTop + pan.translation(in: self).y
let scrollOffset = createSchedulePopView.scrollView.contentOffset.y
if isSubCanScroll {
// 内容正在滑动,不移动 PopView
if scrollOffset > 0 { return }
// 内容滑到顶部,切回 Pan 拖拽(用户下拉时 velocity > 0)
if pan.velocity(in: self).y > 0 || createSchedulePopView.frame.minY > popUpLimit + 1 {
isSubCanScroll = false
panStartTop = createSchedulePopView.frame.minY
}
} else {
// PopView 在顶部且继续上滑 → 激活内容滑动
if createSchedulePopView.frame.minY <= popUpLimit && newTop <= popUpLimit {
isSubCanScroll = true
panStartTop = createSchedulePopView.frame.minY
topConstraint.constant = popUpLimit
return
}
}
let clamped = max(popUpLimit, min(popDownLimit, newTop))
topConstraint.constant = clamped
case .ended, .cancelled:
let velocity = pan.velocity(in: self)
let isNearUp = abs(createSchedulePopView.frame.minY - popUpLimit) < abs(createSchedulePopView.frame.minY - popDownLimit)
let target: CGFloat
if createSchedulePopView.frame.minY <= popUpLimit + 5 {
// 顶部附近:由位置决定去向,速度不触发回收(避免 scrollView 松手误回收)
target = isNearUp ? popUpLimit : popDownLimit
} else if abs(velocity.y) > 200 {
target = velocity.y < 0 ? popUpLimit : popDownLimit
} else {
target = isNearUp ? popUpLimit : popDownLimit
}
topConstraint.constant = target
// 动画前设置 scrollView 状态,避免弹跳
let atTop = target == self.popUpLimit
if !atTop {
isSubCanScroll = false
createSchedulePopView.scrollView.isScrollEnabled = false
createSchedulePopView.scrollView.setContentOffset(.zero, animated: false)
}
isSubCanScroll = atTop
UIView.animate(withDuration: 0.35, delay: 0,
usingSpringWithDamping: 0.85,
initialSpringVelocity: abs(velocity.y) / 1000,
options: [.allowUserInteraction]) {
self.layoutIfNeeded()
} completion: { _ in
self.createSchedulePopView.scrollView.isScrollEnabled = atTop
}
default:
break
}
}
override func layoutSubviews() {
super.layoutSubviews()
if !isLimitsSet {
isLimitsSet = true
popDownLimit = kScreenHeight / 3 * 2
popUpLimit = navView.frame.maxY
createSchedulePopView.scrollView.isScrollEnabled = false
}
}
// MARK: - Views
lazy var navBgView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "Common/navBar_bg_2")
iv.contentMode = .scaleAspectFill
return iv
}()
lazy var navView: BaseNavigationView = {
let nav = BaseNavigationView(title: "创建行程")
return nav
}()
lazy var deleteBtn: UIButton = {
let btn = UIButton()
btn.setImage(UIImage(named: "Common/delete"), for: .normal)
btn.extendEdgeInsets = UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 15)
btn.isHidden = true
return btn
}()
#if !targetEnvironment(simulator)
lazy var mapView: MAMapView! = {
let mv = MAMapView()
mv.zoomLevel = 14
mv.showsUserLocation = false
mv.showsCompass = false
mv.userTrackingMode = .none
DispatchQueue.main.async { mv.logoCenter = CGPoint(x: kScreenWidth - 50, y: kScreenHeight / 3 * 2 - 20) }
return mv
}()
#endif
#if !targetEnvironment(simulator)
func cleanupMap() {
mapView?.delegate = nil
mapView?.removeFromSuperview()
mapView = nil
}
#endif
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
setupUI()
setupRx()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - UIScrollViewDelegate(参考 GroupView 嵌套滑动协调)
extension CreateScheduleView: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
// 内容已有偏移时才允许子滚动
if scrollView.contentOffset.y > 0 {
isSubCanScroll = true
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if isSubCanScroll {
if scrollView.contentOffset.y <= 0 {
isSubCanScroll = false
scrollView.contentOffset.y = 0
}
} else {
scrollView.contentOffset.y = 0
}
}
}
// MARK: - UIGestureRecognizerDelegate
extension CreateScheduleView: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer) -> Bool {
if createSchedulePopView.isReorderingPoints { return false }
return true
}
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if createSchedulePopView.isReorderingPoints,
gestureRecognizer is UIPanGestureRecognizer {
return false
}
return true
}
}