490 lines
15 KiB
Swift
490 lines
15 KiB
Swift
//
|
||
// CreateSchedulePopView.swift
|
||
// QuickLocation
|
||
//
|
||
// Created by 八条 on 2026/6/23.
|
||
//
|
||
|
||
import UIKit
|
||
import RxSwift
|
||
import RxCocoa
|
||
import RxDataSources
|
||
import BRPickerView
|
||
import RxGesture
|
||
import CoreLocation
|
||
import TagListView
|
||
|
||
// MARK: - CreateSchedulePopView
|
||
class CreateSchedulePopView: UIView {
|
||
|
||
var disposeBag = DisposeBag()
|
||
|
||
func setupTagData(_ list: [GroupInfoModel]) {
|
||
let nameArr = list.map { $0.name }
|
||
tagListView.removeAllTags()
|
||
tagListView.addTags(nameArr)
|
||
tagListView.tagViews.forEach {
|
||
$0.layer.cornerRadius = 4
|
||
}
|
||
tagListView.invalidateIntrinsicContentSize() // 通知系统重新算高
|
||
}
|
||
|
||
// 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: 30, bottom: 10, right: 15)
|
||
return btn
|
||
}()
|
||
|
||
lazy var scrollView: UIScrollView = {
|
||
let view = UIScrollView()
|
||
view.backgroundColor = .white
|
||
view.showsHorizontalScrollIndicator = false
|
||
view.bounces = false
|
||
view.delaysContentTouches = false
|
||
|
||
let contentView = UIView()
|
||
contentView.backgroundColor = .clear
|
||
view.addSubview(contentView)
|
||
contentView.layoutChain
|
||
.edges().widthToView(view)
|
||
|
||
contentView.addSubview(tableView)
|
||
tableView.layoutChain
|
||
.top()
|
||
.edgesHorzontal()
|
||
|
||
contentView.addSubview(shareGroupView)
|
||
shareGroupView.layoutChain
|
||
.topToBottomOfView(tableView, offset: 5)
|
||
.edgesHorzontal()
|
||
|
||
contentView.addSubview(createBtn)
|
||
createBtn.layoutChain
|
||
.topToBottomOfView(shareGroupView, 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 shareGroupView: 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).top()
|
||
|
||
view.addSubview(tagListView)
|
||
tagListView.layoutChain
|
||
.topToBottomOfView(titleLab, offset: 15)
|
||
.edgesHorzontal(15)
|
||
.bottom(15)
|
||
|
||
return view
|
||
}()
|
||
|
||
lazy var tagListView: TagListView = {
|
||
let view = TagListView()
|
||
view.textFont = UIFont.systemFont(ofSize: 12, weight: .medium)
|
||
view.textColor = UIColor(hexStr: "#999999")
|
||
view.tagBackgroundColor = UIColor(hexStr: "#F2F2F2")
|
||
view.selectedTextColor = UIColor(hexStr: "#16B3FF")
|
||
view.tagSelectedBackgroundColor = UIColor(hexStr: "#E3F6FF")
|
||
view.selectedBorderColor = UIColor(hexStr: "#16B3FF")
|
||
view.paddingX = 15 // 水平内边距
|
||
view.paddingY = 10 // 垂直内边距
|
||
view.alignment = .left // 对齐
|
||
view.translatesAutoresizingMaskIntoConstraints = false
|
||
return view
|
||
}()
|
||
|
||
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
|
||
// 使用原生圆角避免 CAShapeLayer mask 隐式动画导致的弹跳
|
||
layer.cornerRadius = 30
|
||
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||
layer.masksToBounds = true
|
||
setupUI()
|
||
}
|
||
|
||
required init?(coder: NSCoder) {
|
||
fatalError("init(coder:) has not been implemented")
|
||
}
|
||
}
|
||
|
||
|
||
// MARK: - SchedulePointCell
|
||
|
||
class SchedulePointCell: UITableViewCell {
|
||
|
||
var disposeBag = DisposeBag()
|
||
|
||
var onLocationTap: (() -> Void)?
|
||
var onTimeTap: (() -> Void)?
|
||
var onRemarkChanged: ((String) -> Void)?
|
||
|
||
func configure(item: SchedulePointItem, index: Int, total: Int, onDelete: @escaping () -> Void) {
|
||
indexLabel.text = "\(index + 1)"
|
||
locationLabel.text = item.street.isEmpty ? "点击选择地点" : item.street
|
||
remarkTF.text = item.remark
|
||
|
||
locationLabel.rx.tapGesture
|
||
.when(.recognized)
|
||
.subscribe(onNext: { [weak self] _ in
|
||
self?.onLocationTap?()
|
||
})
|
||
.disposed(by: disposeBag)
|
||
|
||
if let expectedTime = item.expectedTime {
|
||
timeLabel.text = formatTime(expectedTime)
|
||
} else {
|
||
timeLabel.text = "请选择到达时间"
|
||
}
|
||
timeLabel.rx.tapGesture
|
||
.when(.recognized)
|
||
.subscribe(onNext: { [weak self] _ in
|
||
self?.onTimeTap?()
|
||
})
|
||
.disposed(by: disposeBag)
|
||
|
||
// 左侧竖线:仅有一条时不显示虚线,首条顶部不画、末条底部不画
|
||
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
|
||
|
||
@objc private func remarkDidChange() {
|
||
onRemarkChanged?(remarkTF.text ?? "")
|
||
}
|
||
|
||
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") }
|
||
|
||
override func prepareForReuse() {
|
||
super.prepareForReuse()
|
||
disposeBag = DisposeBag()
|
||
}
|
||
|
||
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(1)
|
||
|
||
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(1)
|
||
.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)
|
||
.compressionHorizontal(.defaultLow)
|
||
|
||
timeLabel.layoutChain
|
||
.topToView(locationLabel)
|
||
.right(10)
|
||
.compressionHorizontal(.required)
|
||
|
||
locationLabel.layoutChain.rightToLeftOfView(timeLabel, offset: -8)
|
||
|
||
deleteBtn.layoutChain
|
||
.right(15)
|
||
.width(14).height(14)
|
||
|
||
remarkTF.layoutChain
|
||
.topToBottomOfView(locationLabel, offset: 15)
|
||
.leftToView(locationLabel)
|
||
.rightToLeftOfView(deleteBtn, offset: -10)
|
||
.bottom(15)
|
||
|
||
deleteBtn.layoutChain.centerY(remarkTF)
|
||
}
|
||
|
||
// MARK: - Views
|
||
private let topDashView: DashLineView = {
|
||
let v = DashLineView()
|
||
v.isHidden = true
|
||
v.backgroundColor = .clear
|
||
return v
|
||
}()
|
||
|
||
private let pointIcon: UIImageView = {
|
||
let iv = UIImageView(image: UIImage(named: "Schedule/point"))
|
||
iv.contentMode = .scaleAspectFit
|
||
return iv
|
||
}()
|
||
|
||
private let bottomDashView: DashLineView = {
|
||
let v = DashLineView()
|
||
v.isHidden = true
|
||
v.backgroundColor = .clear
|
||
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.numberOfLines = 0
|
||
return l
|
||
}()
|
||
|
||
private let timeLabel: UILabel = {
|
||
let l = UILabel()
|
||
l.font = .systemFont(ofSize: 12, weight: .medium)
|
||
l.textColor = UIColor(hexStr: "#333333")
|
||
l.isUserInteractionEnabled = true
|
||
l.textAlignment = .right
|
||
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))
|
||
}
|
||
}
|
||
|
||
/// 虚线绘制视图(竖线)
|
||
class DashLineView: UIView {
|
||
override func layoutSubviews() {
|
||
super.layoutSubviews()
|
||
layer.sublayers?.forEach { if $0 is CAShapeLayer { $0.removeFromSuperlayer() } }
|
||
|
||
let shapeLayer = CAShapeLayer()
|
||
shapeLayer.strokeColor = UIColor(hexStr: "#E0E0E0").cgColor
|
||
shapeLayer.lineWidth = 1
|
||
shapeLayer.lineDashPattern = [4, 4]
|
||
shapeLayer.fillColor = nil
|
||
|
||
let path = UIBezierPath()
|
||
path.move(to: CGPoint(x: bounds.midX, y: 0))
|
||
path.addLine(to: CGPoint(x: bounds.midX, y: bounds.height))
|
||
shapeLayer.path = path.cgPath
|
||
|
||
layer.addSublayer(shapeLayer)
|
||
}
|
||
}
|
||
|
||
/// 虚线绘制视图(横线)
|
||
class HorizontalDashLineView: UIView {
|
||
override func layoutSubviews() {
|
||
super.layoutSubviews()
|
||
layer.sublayers?.forEach { if $0 is CAShapeLayer { $0.removeFromSuperlayer() } }
|
||
|
||
let shapeLayer = CAShapeLayer()
|
||
shapeLayer.strokeColor = UIColor(hexStr: "#E0E0E0").cgColor
|
||
shapeLayer.lineWidth = 1
|
||
shapeLayer.lineDashPattern = [4, 4]
|
||
shapeLayer.fillColor = nil
|
||
|
||
let path = UIBezierPath()
|
||
path.move(to: CGPoint(x: 0, y: bounds.midY))
|
||
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.midY))
|
||
shapeLayer.path = path.cgPath
|
||
|
||
layer.addSublayer(shapeLayer)
|
||
}
|
||
}
|