jsdw_ios/QuickLocation/Section/Home/GroupSchedule/GroupScheduleView.swift

362 lines
10 KiB
Swift

//
// GroupScheduleView.swift
// QuickLocation
//
// Created by on 2026/6/29.
//
import UIKit
import RxSwift
import RxCocoa
import AMapNaviKit
class GroupScheduleView: UIView {
var disposeBag = DisposeBag()
let selectedSchedule = PublishSubject<ScheduleModel>()
// MARK: - PopView
private var popTopConstraint: NSLayoutConstraint?
private var isLimitsSet = false
private let popDownHeight: CGFloat = 250
private var popUpLimit: CGFloat = 0
private var panStartTop: CGFloat = 0
private var isSubCanScroll = false
private func setupRx() {
backBtn.rx.tap.subscribe(onNext: { _ in
AppRouter.shared.popOrDismiss()
}).disposed(by: disposeBag)
}
private func setupUI() {
addSubview(mapView)
addSubview(navBgView)
addSubview(navBarView)
navBarView.addSubview(navTitleLabel)
navBarView.addSubview(backBtn)
addSubview(bottomView)
bottomView.addSubview(lineView)
bottomView.addSubview(tableView)
navBgView.layoutChain
.edges(excludingEdge: .bottom)
.heightToWidth(160/375)
navBarView.layoutChain
.edges(excludingEdge: .bottom)
.height(kNaviHeight)
navTitleLabel.layoutChain
.top(kStatusBarHeight + 12)
.centerY(backBtn)
.centerX()
backBtn.layoutChain
.centerY(navTitleLabel)
.left(15)
.width(24)
.height(24)
mapView.layoutChain
.top()
.edgesHorzontal()
.bottom()
//
bottomView.layoutChain
.edgesHorzontal()
.bottom()
.top(kScreenHeight - popDownHeight)
lineView.layoutChain
.top(8)
.centerX()
.width(36)
.height(4)
tableView.layoutChain
.topToBottomOfView(lineView, offset: 10)
.edgesHorzontal()
.bottom()
popTopConstraint = bottomView.jh_constraint(
.top, toAttribute: .top, otherView: bottomView.superview, relation: .equal
)
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
pan.delegate = self
bottomView.addGestureRecognizer(pan)
}
// MARK: - Pan Gesture
@objc private func handlePan(_ pan: UIPanGestureRecognizer) {
guard isLimitsSet, let topConstraint = popTopConstraint else { return }
switch pan.state {
case .began:
panStartTop = bottomView.frame.minY
case .changed:
let newTop = panStartTop + pan.translation(in: self).y
let scrollOffset = tableView.contentOffset.y
if isSubCanScroll {
if scrollOffset > 0 { return }
if pan.velocity(in: self).y > 0 || bottomView.frame.minY > popUpLimit + 1 {
isSubCanScroll = false
panStartTop = bottomView.frame.minY
}
} else {
if bottomView.frame.minY <= popUpLimit && newTop <= popUpLimit {
isSubCanScroll = true
panStartTop = bottomView.frame.minY
topConstraint.constant = popUpLimit
return
}
}
let clamped = max(popUpLimit, min(kScreenHeight - popDownHeight, newTop))
topConstraint.constant = clamped
case .ended, .cancelled:
let velocity = pan.velocity(in: self)
let frameMinY = bottomView.frame.minY
let isNearUp = abs(frameMinY - popUpLimit) < abs(frameMinY - (kScreenHeight - popDownHeight))
let target: CGFloat
if frameMinY <= popUpLimit + 5 {
target = isNearUp ? popUpLimit : (kScreenHeight - popDownHeight)
} else if abs(velocity.y) > 200 {
target = velocity.y < 0 ? popUpLimit : (kScreenHeight - popDownHeight)
} else {
target = isNearUp ? popUpLimit : (kScreenHeight - popDownHeight)
}
topConstraint.constant = target
isSubCanScroll = target == popUpLimit
UIView.animate(withDuration: 0.3, delay: 0,
usingSpringWithDamping: 0.85,
initialSpringVelocity: abs(velocity.y) / 1000,
options: [.allowUserInteraction]) {
self.layoutIfNeeded()
}
default:
break
}
}
override func layoutSubviews() {
super.layoutSubviews()
if !isLimitsSet {
isLimitsSet = true
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 view = UIView()
view.backgroundColor = .clear
return view
}()
lazy var navTitleLabel: UILabel = {
let label = UILabel()
label.text = "行程路线"
label.font = .systemFont(ofSize: 18, weight: .medium)
label.textColor = ThemeManager.shared.color.titleAuxColor
label.textAlignment = .center
return label
}()
lazy var backBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(UIImage(named: "Common/back"), for: .normal)
btn.extendEdgeInsets = UIEdgeInsets(top: 54, left: 15, bottom: 100, right: 100)
return btn
}()
lazy var mapView: MAMapView! = {
let mv = MAMapView()
mv.zoomLevel = 14
mv.showsUserLocation = false
mv.showsCompass = false
mv.userTrackingMode = .none
return mv
}()
lazy var bottomView: UIView = {
let v = UIView()
v.backgroundColor = .white
v.layer.cornerRadius = 16
v.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
return v
}()
lazy var lineView: UIView = {
let v = UIView()
v.backgroundColor = UIColor(hexStr: "#EBEBEB")
v.cornerRadius = 2
return v
}()
lazy var tableView: UITableView = {
let tv = UITableView(frame: .zero, style: .plain)
tv.backgroundColor = .clear
tv.separatorStyle = .none
tv.estimatedRowHeight = 137
tv.rowHeight = UITableView.automaticDimension
tv.register(GroupScheduleCell.self)
return tv
}()
///
func dismissPanel() {
guard let topConstraint = popTopConstraint else { return }
isSubCanScroll = false
topConstraint.constant = kScreenHeight - popDownHeight
UIView.animate(withDuration: 0.3) { self.layoutIfNeeded() }
}
func cleanupMap() {
#if !targetEnvironment(simulator)
mapView?.delegate = nil
mapView?.removeFromSuperview()
mapView = nil
#endif
}
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: - UIGestureRecognizerDelegate
extension GroupScheduleView: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith other: UIGestureRecognizer) -> Bool {
return true
}
}
// MARK: - GroupScheduleCell
class GroupScheduleCell: UITableViewCell {
func configure(_ model: ScheduleModel, isSelected: Bool) {
dateLab.text = "行程时间:\(getDateInterval2String(date: "\(model.timestamp/1000)", dateFormat: "yyyy年MM月dd日"))"
iconView.image = model.userIcon
nameLab.text = "\(model.nick_name) 的行程路线"
selectedBgView.isHidden = !isSelected
}
private func setupSubviews() {
contentView.addSubview(bgView)
bgView.addSubview(selectedBgView)
bgView.addSubview(dateLab)
bgView.addSubview(detailView)
bgView.layoutChain
.top()
.edgesHorzontal(15)
.height(137)
.bottom(12)
selectedBgView.layoutChain.edges()
dateLab.layoutChain
.top(9)
.left(15)
detailView.layoutChain
.topToBottomOfView(dateLab, offset: 8)
.edges(all: 15, excludingEdge: .top)
}
lazy var bgView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hexStr: "#EEFAFF")
view.cornerRadius = 10
return view
}()
lazy var selectedBgView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hexStr: "#C0EAFF")
view.borderWidth = 1
view.borderColor = UIColor(hexStr: "#16B3FF")
view.cornerRadius = 10
view.isHidden = true
return view
}()
lazy var dateLab: UILabel = {
let label = UILabel()
label.text = " "
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = UIColor(hexStr: "#0F2846")
return label
}()
lazy var detailView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.cornerRadius = 10
view.addSubview(iconView)
iconView.layoutChain
.left(15)
.centerY()
.width(50)
.heightToWidth(1)
view.addSubview(nameLab)
nameLab.layoutChain
.leftToRightOfView(iconView, offset: 14)
.right(15, relation: .greaterThanOrEqual)
.centerY(iconView)
return view
}()
lazy var iconView: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFill
return view
}()
lazy var nameLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = UIColor(hexStr: "#0F2846")
return label
}()
override init(style: CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
setupSubviews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}