// // CreateSchedulePopView.swift // QuickLocation // // Created by 八条 on 2026/6/23. // import UIKit import RxSwift import RxCocoa import RxDataSources import BRPickerView // MARK: - 行程点编辑模型 struct SchedulePointItem: IdentifiableType, Equatable { let identity: String = UUID().uuidString var locationName: String = "" var address: String = "" var expectedTime: Date? var remark: String = "" } typealias SchedulePointSection = SectionModel // MARK: - CreateSchedulePopView class CreateSchedulePopView: UIView { var disposeBag = DisposeBag() let pointsRelay = BehaviorRelay<[SchedulePointItem]>(value: [SchedulePointItem()]) // MARK: - Setup private func setupRx() { dateLab.rx.tapGesture.subscribe { _ in let picker = BRDatePickerView(pickerMode: .YMD) picker.minDate = Date() picker.maxDate = Calendar.current.date(byAdding: .day, value: 6, to: Date()) let style = BRPickerStyle() style.selectRowTextColor = UIColor(hexStr: "#16B3FF") picker.pickerStyle = style picker.resultBlock = { [weak self] selectDate, value in guard let date = selectDate else { return } let fmt = DateFormatter() fmt.dateFormat = "yyyy年MM月dd日" self?.dateLab.text = fmt.string(from: date) } picker.show() }.disposed(by: disposeBag) // 新增行程点 addBtn.rx.tap .subscribe(onNext: { [weak self] _ in guard let self = self else { return } var list = self.pointsRelay.value list.append(SchedulePointItem( locationName: "", address: "", expectedTime: nil, remark: "" )) self.pointsRelay.accept(list) }) .disposed(by: disposeBag) // 绑定 tableView pointsRelay .observe(on: MainScheduler.asyncInstance) .map { [SchedulePointSection(model: "", items: $0)] } .bind(to: tableView.rx.items(dataSource: dataSource)) .disposed(by: disposeBag) // 动态更新 tableView 高度 pointsRelay .subscribe(onNext: { [weak self] items in guard let self = self else { return } let rowHeight: CGFloat = 122 var h = CGFloat(items.count) * rowHeight h = max(h, rowHeight) self.tableView.layoutChain.height(h) }) .disposed(by: disposeBag) } private lazy var dataSource: RxTableViewSectionedReloadDataSource = { RxTableViewSectionedReloadDataSource( configureCell: { [weak self] _, tv, indexPath, item in let cell: SchedulePointCell = tv.dequeueReusableCell(for: indexPath) cell.configure(item: item, index: indexPath.row, total: self?.pointsRelay.value.count ?? 0, onDelete: { [weak self] in guard let self = self else { return } var list = self.pointsRelay.value guard indexPath.row < list.count else { return } list.remove(at: indexPath.row) self.pointsRelay.accept(list) }) return cell }) }() // MARK: - UI private func setupUI() { addSubview(lineView) addSubview(dateView) addSubview(detailView) addSubview(scrollView) lineView.layoutChain .top(15) .centerX() .width(36) .height(4) dateView.layoutChain .topToBottomOfView(lineView, offset: 19) .edgesHorzontal() .height(22) detailView.layoutChain .topToBottomOfView(dateView, offset: 20) .edgesHorzontal() .height(22) scrollView.layoutChain .topToBottomOfView(detailView, offset: 15) .edges(excludingEdge: .top) } // MARK: - Views lazy var lineView: UIView = { let v = UIView() v.backgroundColor = UIColor(hexStr: "#EBEBEB") v.cornerRadius = 2 return v }() lazy var dateView: UIView = { let view = UIView() view.backgroundColor = .clear let titleLab = UILabel() titleLab.text = "行程日期:" titleLab.font = .systemFont(ofSize: 14, weight: .medium) titleLab.textColor = ThemeManager.shared.color.titleAuxColor view.addSubview(titleLab) titleLab.layoutChain.left(15).centerY() view.addSubview(dateLab) dateLab.layoutChain.leftToRightOfView(titleLab).centerY() return view }() lazy var dateLab: UILabel = { let label = UILabel() label.font = .systemFont(ofSize: 14, weight: .medium) label.textColor = UIColor(hexStr: "#16B3FF") label.isUserInteractionEnabled = true let fmt = DateFormatter() fmt.dateFormat = "yyyy年MM月dd日" label.text = fmt.string(from: Date()) return label }() lazy var detailView: UIView = { let view = UIView() view.backgroundColor = .clear let titleLab = UILabel() titleLab.text = "行程详情" titleLab.font = .systemFont(ofSize: 14, weight: .medium) titleLab.textColor = ThemeManager.shared.color.titleAuxColor view.addSubview(titleLab) titleLab.layoutChain.left(15).centerY() view.addSubview(addBtn) addBtn.layoutChain.right(15).centerY().width(18).height(18) return view }() lazy var addBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(UIImage(named: "Schedule/add"), for: .normal) btn.backgroundColor = .clear btn.extendEdgeInsets = UIEdgeInsets(top: 30, left: 10, bottom: 10, right: 15) return btn }() lazy var scrollView: UIScrollView = { let view = UIScrollView() view.backgroundColor = .white view.showsHorizontalScrollIndicator = false view.bounces = false let contentView = UIView() contentView.backgroundColor = .clear view.addSubview(contentView) contentView.layoutChain .edges().widthToView(view) contentView.addSubview(tableView) tableView.layoutChain .top() .edgesHorzontal() contentView.addSubview(createBtn) createBtn.layoutChain .topToBottomOfView(tableView, offset: 30) .edgesHorzontal(30) .height(50) .bottom(kSafeBottomMargin + 10) return view }() lazy var tableView: UITableView = { let tv = UITableView(frame: .zero, style: .plain) tv.backgroundColor = .clear tv.separatorStyle = .none tv.estimatedRowHeight = 122 tv.isScrollEnabled = false tv.showsVerticalScrollIndicator = false tv.bounces = false tv.register(SchedulePointCell.self) return tv }() lazy var createBtn: UIButton = { let btn = UIButton(type: .custom) btn.setTitle("立即创建", for: .normal) btn.setTitleColor(.white, for: .normal) btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal) btn.cornerRadius = 25 return btn }() override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .white setupUI() setupRx() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() layoutIfNeeded() setCornerRadius(corners: [.topLeft, .topRight], withCornerRadii: CGSize(width: 30, height: 30)) } } // MARK: - SchedulePointCell class SchedulePointCell: UITableViewCell { var disposeBag = DisposeBag() func configure(item: SchedulePointItem, index: Int, total: Int, onDelete: @escaping () -> Void) { disposeBag = DisposeBag() indexLabel.text = "\(index + 1)" locationLabel.text = item.locationName.isEmpty ? "点击选择地点" : item.locationName if let expectedTime = item.expectedTime { timeLabel.text = formatTime(expectedTime) } else { timeLabel.text = "请选择到达时间" } // 左侧竖线:仅有一条时不显示虚线,首条顶部不画、末条底部不画 topDashView.isHidden = total <= 1 || index == 0 bottomDashView.isHidden = total <= 1 || index == total - 1 // 删除 deleteBtn.rx.tap .subscribe(onNext: { onDelete() }) .disposed(by: disposeBag) } private func formatTime(_ date: Date) -> String { let fmt = DateFormatter() fmt.dateFormat = "HH:mm" return fmt.string(from: date) } // MARK: - Init override init(style: CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .none backgroundColor = .clear setupViews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupViews() { contentView.addSubview(topDashView) contentView.addSubview(pointIcon) contentView.addSubview(bottomDashView) contentView.addSubview(cardView) cardView.addSubview(cardCornerView) cardCornerView.addSubview(indexLabel) cardCornerView.addSubview(locationLabel) cardCornerView.addSubview(timeLabel) cardCornerView.addSubview(remarkTF) cardCornerView.addSubview(deleteBtn) topDashView.layoutChain .top().width(2) pointIcon.layoutChain .centerY() .left(22) .width(18).height(18) topDashView.layoutChain .centerX(pointIcon) .bottomToTopOfView(pointIcon, offset: -10) bottomDashView.layoutChain .topToBottomOfView(pointIcon, offset: 10) .centerX(pointIcon) .width(2) .bottom() // 右侧卡片 cardView.layoutChain .top(15) .leftToRightOfView(pointIcon, offset: 20) .right(15) .height(92) .bottom(15) cardCornerView.layoutChain.edges() indexLabel.layoutChain .top().left() .width(30).height(20) locationLabel.layoutChain .topToBottomOfView(indexLabel, offset: 10) .left(10) timeLabel.layoutChain .centerY(locationLabel) .leftToRightOfView(locationLabel, offset: 38) deleteBtn.layoutChain .bottom(15).right(15) .width(14).height(14) remarkTF.layoutChain .topToBottomOfView(locationLabel, offset: 15) .leftToView(locationLabel) .rightToLeftOfView(deleteBtn, offset: -10) } // MARK: - Views private let topDashView: UIView = { let v = UIView() v.backgroundColor = UIColor(hexStr: "#E0E0E0") v.isHidden = true return v }() private let pointIcon: UIImageView = { let iv = UIImageView(image: UIImage(named: "Schedule/point")) iv.contentMode = .scaleAspectFit return iv }() private let bottomDashView: UIView = { let v = UIView() v.backgroundColor = UIColor(hexStr: "#E0E0E0") v.isHidden = true return v }() private let cardView: UIView = { let view = UIView() view.backgroundColor = .clear view.layer.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.05).cgColor view.layer.shadowOffset = CGSize(width: 0, height: 0) view.layer.shadowOpacity = 1 view.layer.shadowRadius = 8 return view }() lazy var cardCornerView: UIView = { let view = UIView() view.backgroundColor = .white view.cornerRadius = 10 return view }() private let indexLabel: UILabel = { let l = UILabel() l.backgroundColor = UIColor(hexStr: "#DCF4FF") l.textColor = UIColor(hexStr: "#176F9B") l.font = .systemFont(ofSize: 12, weight: .medium) l.textAlignment = .center return l }() private let locationLabel: UILabel = { let l = UILabel() l.font = .systemFont(ofSize: 14, weight: .medium) l.textColor = UIColor(hexStr: "#333333") l.lineBreakMode = .byTruncatingTail return l }() private let timeLabel: UILabel = { let l = UILabel() l.font = .systemFont(ofSize: 12, weight: .medium) l.textColor = UIColor(hexStr: "#333333") l.isUserInteractionEnabled = true return l }() lazy var remarkTF: UITextField = { let tf = UITextField() tf.font = .systemFont(ofSize: 12) tf.placeholder = "在此添加备注..." tf.returnKeyType = .done return tf }() private let deleteBtn: UIButton = { let btn = UIButton(type: .custom) btn.setImage(UIImage(named: "Schedule/delete"), for: .normal) btn.extendEdgeInsets = UIEdgeInsets(top: 10, left: 15, bottom: 15, right: 15) return btn }() override func layoutSubviews() { super.layoutSubviews() cardCornerView.layoutIfNeeded() indexLabel.setCornerRadius(corners: [.bottomRight], withCornerRadii: CGSize(width: 10, height: 10)) } }