// // CreateScheduleView.swift // QuickLocation // // Created by 八条 on 2026/6/23. // import UIKit import RxSwift import RxCocoa #if !targetEnvironment(simulator) import AMapNaviKit #endif class CreateScheduleView: UIView { var disposeBag = DisposeBag() 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() { backBtn.rx.tap.subscribe(onNext: { _ in AppRouter.shared.popOrDismiss() }).disposed(by: disposeBag) // tableView 到达顶部继续下拉时,改由 PopView 的 pan 手势接管 createSchedulePopView.scrollView.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.createSchedulePopView.scrollView.setContentOffset(.zero, animated: false) } } else if offset.y != 0 { self.createSchedulePopView.scrollView.setContentOffset(.zero, animated: false) } }) .disposed(by: disposeBag) } private func setupUI() { #if !targetEnvironment(simulator) addSubview(mapView) #endif addSubview(navBgView) addSubview(navBarView) navBarView.addSubview(backBtn) navBarView.addSubview(navTitleLabel) addSubview(createSchedulePopView) navBgView.layoutChain .edges(excludingEdge: .bottom) .heightToWidth(160/375) navBarView.layoutChain .edges(excludingEdge: .bottom) .height(kNaviHeight) backBtn.layoutChain .centerY(navTitleLabel) .left(15) .width(24).height(24) navTitleLabel.layoutChain .top(kStatusBarHeight + 12) .centerX() #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 } switch pan.state { case .began: layoutIfNeeded() panStartTop = createSchedulePopView.frame.minY case .changed: let newTop = panStartTop + pan.translation(in: self).y if isSubCanScroll { let tableViewOffset = self.createSchedulePopView.scrollView.contentOffset.y if tableViewOffset > 0, newTop >= popUpLimit { return } isSubCanScroll = false panStartTop = createSchedulePopView.frame.minY } 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 abs(velocity.y) > 200 { target = velocity.y < 0 ? popUpLimit : popDownLimit } else { target = isNearUp ? popUpLimit : popDownLimit } topConstraint.constant = target UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseInOut, .allowUserInteraction]) { self.layoutIfNeeded() } completion: { _ in let atTop = target == self.popUpLimit self.isSubCanScroll = atTop if !atTop { self.createSchedulePopView.scrollView.contentOffset.y = 0 } } default: break } } override func layoutSubviews() { super.layoutSubviews() if !isLimitsSet { isLimitsSet = true popDownLimit = kScreenHeight / 3 * 2 popUpLimit = navBarView.frame.maxY } } // MARK: - Views lazy var navBgView: UIImageView = { let iv = UIImageView() iv.image = UIImage(named: "Common/navBar_bg_2") iv.contentMode = .scaleAspectFill return iv }() lazy var navBarView: UIView = { let v = UIView() v.backgroundColor = .clear return v }() lazy var backBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(UIImage(named: "Common/back"), for: .normal) btn.extendEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 30) return btn }() lazy var navTitleLabel: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 18, weight: .medium) label.textColor = ThemeManager.shared.color.titleAuxColor label.text = "创建行程" return label }() #if !targetEnvironment(simulator) lazy var mapView: MAMapView = { let mv = MAMapView() mv.zoomLevel = 14 mv.showsUserLocation = false mv.showsCompass = false mv.userTrackingMode = .none return mv }() #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: - UIGestureRecognizerDelegate extension CreateScheduleView: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer) -> Bool { return true } }