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

708 lines
24 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.

//
// 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
import SwiftDate
// MARK: - CreateSchedulePopView
class CreateSchedulePopView: UIView {
var disposeBag = DisposeBag()
/// Pop pan
var isReorderingPoints = false
func setupTagData(_ list: [GroupInfoModel]) {
let nameArr = list.map { $0.name }
tagListView.removeAllTags()
tagListView.addTags(nameArr)
tagListView.tagViews.forEach {
$0.layer.cornerRadius = 4
$0.layer.borderWidth = 1
$0.layer.borderColor = UIColor.clear.cgColor
}
tagListView.setNeedsLayout()
tagListView.layoutIfNeeded()
}
// MARK: - UI
private func setupUI() {
addSubview(lineView)
addSubview(headerView)
addSubview(scrollView)
lineView.layoutChain
.top(15)
.centerX()
.width(36)
.height(4)
headerView.layoutChain
.topToBottomOfView(lineView, offset: 16)
.edgesHorzontal()
.height(24)
scrollView.layoutChain
.topToBottomOfView(headerView, offset: 16)
.edges(excludingEdge: .top)
}
// MARK: - Views
lazy var lineView: UIView = {
let v = UIView()
v.backgroundColor = UIColor(hexStr: "#EBEBEB")
v.cornerRadius = 2
return v
}()
lazy var headerView: UIView = {
let view = UIView()
view.backgroundColor = .clear
let pin = UIImageView(image: UIImage(named: "Schedule/location")?.withRenderingMode(.alwaysTemplate))
pin.tintColor = UIColor(hexStr: "#16B3FF")
pin.contentMode = .scaleAspectFit
view.addSubview(pin)
pin.layoutChain.left(16).centerY().width(14).height(14)
let titleLab = UILabel()
titleLab.text = "行程详情"
titleLab.font = .systemFont(ofSize: 16, weight: .bold)
titleLab.textColor = UIColor(hexStr: "#293445")
view.addSubview(titleLab)
titleLab.layoutChain.leftToRightOfView(pin, offset: 6).centerY()
let icon = UIImageView(image: UIImage(named: "Common/arrow_right"))
view.addSubview(icon)
icon.layoutChain
.right(15)
.centerY()
.width(10)
.height(10)
view.addSubview(dateLab)
dateLab.layoutChain.rightToLeftOfView(icon, offset: -6).centerY()
return view
}()
lazy var dateLab: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 13, weight: .medium)
label.textColor = UIColor(hexStr: "#293445")
label.isUserInteractionEnabled = true
label.text = Date().toFormat("yyyy年MM月dd日")
return label
}()
/// +
lazy var addBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.isHidden = true
return btn
}()
lazy var scrollView: UIScrollView = {
let view = UIScrollView()
view.backgroundColor = .clear
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: 8)
.edgesHorzontal()
contentView.addSubview(createBtn)
createBtn.layoutChain
.topToBottomOfView(shareGroupView, offset: 24)
.edgesHorzontal(16)
.height(50)
.bottom(kSafeBottomMargin)
return view
}()
lazy var tableView: UITableView = {
let tv = UITableView(frame: .zero, style: .plain)
tv.backgroundColor = .clear
tv.separatorStyle = .none
tv.estimatedRowHeight = ScheduleTimelineMetric.pointRowHeight
tv.rowHeight = UITableView.automaticDimension
tv.isScrollEnabled = false
tv.showsVerticalScrollIndicator = false
tv.bounces = false
tv.clipsToBounds = false
tv.register(SchedulePointCell.self)
tv.register(ScheduleAddPointCell.self)
return tv
}()
lazy var shareGroupView: UIView = {
let view = UIView()
view.backgroundColor = .clear
let pin = UIImageView(image: UIImage(named: "Schedule/location")?.withRenderingMode(.alwaysTemplate))
pin.tintColor = UIColor(hexStr: "#16B3FF")
pin.contentMode = .scaleAspectFit
view.addSubview(pin)
pin.layoutChain.left(16).top().width(14).height(14)
let sectionLab = UILabel()
sectionLab.text = "其他"
sectionLab.font = .systemFont(ofSize: 16, weight: .bold)
sectionLab.textColor = UIColor(hexStr: "#293445")
view.addSubview(sectionLab)
sectionLab.layoutChain.leftToRightOfView(pin, offset: 6).centerY(pin)
let card = UIView()
card.backgroundColor = .white
card.layer.cornerRadius = 12
view.addSubview(card)
card.layoutChain
.topToBottomOfView(sectionLab, offset: 12)
.edgesHorzontal(16)
.bottom()
let titleLab = UILabel()
titleLab.text = "分享的圈子"
titleLab.font = .systemFont(ofSize: 14, weight: .medium)
titleLab.textColor = UIColor(hexStr: "#1A1A1A")
card.addSubview(titleLab)
titleLab.layoutChain.left(16).top(19)
card.addSubview(tagListView)
tagListView.layoutChain
.topToBottomOfView(titleLab, offset: 12)
.edgesHorzontal(16)
.bottom(12)
return view
}()
lazy var tagListView: TagListView = {
let view = TagListView()
view.textFont = UIFont.systemFont(ofSize: 12, weight: .medium)
view.textColor = UIColor(hexStr: "#AAAAAA")
view.tagBackgroundColor = UIColor(hexStr: "#F0F0F0")
view.tagBorderColor = UIColor.clear
view.selectedTextColor = UIColor(hexStr: "#00ADFE")
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 = FontManager.boboBold(18)
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
btn.cornerRadius = 16
return btn
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor(hexStr: "#FAFAFA")
layer.cornerRadius = 30
layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
layer.masksToBounds = true
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - Timeline metrics
enum ScheduleTimelineMetric {
/// marker left 16 + width 22 / 2
static let centerX: CGFloat = 27
static let markerSize: CGFloat = 18
/// 8 + 10+36+8+36+8+36+10 + 8
static let pointRowHeight: CGFloat = 160
static let addRowHeight: CGFloat = 60
}
// MARK: - SchedulePointCell
final class SchedulePointCell: UITableViewCell {
static let reuseId = "SchedulePointCell"
var disposeBag = DisposeBag()
var onLocationTap: (() -> Void)?
var onTimeTap: (() -> Void)?
var onDelete: (() -> Void)?
/// began/changed/ended VC
var onDragGesture: ((UILongPressGestureRecognizer) -> Void)?
private let dashLineView = ScheduleDashLineView()
private let markerView = UIView()
private let markerLab = UILabel()
private let middleDot = UIView()
private let cardView = UIView()
private let locationRow = ScheduleFieldRowView(
icon: UIImage(named: "Schedule/location"),
placeholder: "请选择"
)
private let timeRow = ScheduleFieldRowView(
icon: UIImage(named: "Schedule/clock"),
placeholder: "请选择"
)
private let remarkRow = ScheduleFieldRowView(
icon: UIImage(named: "Schedule/edit"),
placeholder: "在此添加备注",
editable: true
)
private let dragBtn = UIButton(type: .custom)
private let deleteBtn = UIButton(type: .custom)
private var locationRowRightConstraint: NSLayoutConstraint?
private var remarkRowRightConstraint: NSLayoutConstraint?
private var dragPress: UILongPressGestureRecognizer?
private var dashTopToContent: NSLayoutConstraint?
private var dashTopToMarkerCenter: NSLayoutConstraint?
private var dashBottomToContent: NSLayoutConstraint?
private var dashBottomToMarkerCenter: NSLayoutConstraint?
func configure(item: SchedulePointItem, role: SchedulePointRole) {
switch role {
case .start:
markerView.isHidden = false
middleDot.isHidden = true
markerView.backgroundColor = UIColor(hexStr: "#16B3FF")
markerLab.text = ""
markerLab.isHidden = false
dragBtn.isHidden = true
deleteBtn.isHidden = true
locationRowRightConstraint?.constant = -10
remarkRowRightConstraint?.constant = -10
// 线
dashTopToContent?.isActive = false
dashTopToMarkerCenter?.isActive = true
dashBottomToMarkerCenter?.isActive = false
dashBottomToContent?.isActive = true
case .middle:
markerView.isHidden = true
middleDot.isHidden = false
markerLab.isHidden = true
dragBtn.isHidden = false
deleteBtn.isHidden = false
locationRowRightConstraint?.constant = -40
remarkRowRightConstraint?.constant = -40
// 线穿
dashTopToMarkerCenter?.isActive = false
dashTopToContent?.isActive = true
dashBottomToMarkerCenter?.isActive = false
dashBottomToContent?.isActive = true
case .end:
markerView.isHidden = false
middleDot.isHidden = true
markerView.backgroundColor = UIColor(hexStr: "#FF5A5A")
markerLab.text = ""
markerLab.isHidden = false
dragBtn.isHidden = true
deleteBtn.isHidden = true
locationRowRightConstraint?.constant = -10
remarkRowRightConstraint?.constant = -10
// 线
dashTopToMarkerCenter?.isActive = false
dashTopToContent?.isActive = true
dashBottomToContent?.isActive = false
dashBottomToMarkerCenter?.isActive = true
}
let locationText = item.street.isEmpty ? (item.locationName.isEmpty ? nil : item.locationName) : item.street
locationRow.setText(locationText)
if let expectedTime = item.expectedTime {
timeRow.setText(expectedTime.toFormat("HH:mm"))
} else {
timeRow.setText(nil)
}
remarkRow.setText(item.remark.isEmpty ? nil : item.remark)
remarkRow.textField?.text = item.remark
locationRow.onTap = { [weak self] in self?.onLocationTap?() }
timeRow.onTap = { [weak self] in self?.onTimeTap?() }
}
var remarkText: String {
remarkRow.textField?.text ?? ""
}
var remarkTextField: UITextField? { remarkRow.textField }
override init(style: CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
contentView.backgroundColor = .clear
clipsToBounds = false
contentView.clipsToBounds = false
setupViews()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func prepareForReuse() {
super.prepareForReuse()
disposeBag = DisposeBag()
onLocationTap = nil
onTimeTap = nil
onDelete = nil
onDragGesture = nil
}
private func setupViews() {
// 线
contentView.addSubview(dashLineView)
contentView.addSubview(markerView)
contentView.addSubview(middleDot)
contentView.addSubview(cardView)
markerView.addSubview(markerLab)
cardView.addSubview(locationRow)
cardView.addSubview(timeRow)
cardView.addSubview(remarkRow)
cardView.addSubview(dragBtn)
cardView.addSubview(deleteBtn)
markerView.layer.cornerRadius = ScheduleTimelineMetric.markerSize / 2
markerView.clipsToBounds = true
markerLab.textColor = .white
markerLab.font = FontManager.youSheBiaoTiHei(11)
markerLab.textAlignment = .center
middleDot.backgroundColor = UIColor(hexStr: "#293445")
middleDot.layer.cornerRadius = 5
let middleCenter = UIView()
middleCenter.backgroundColor = .white
middleCenter.cornerRadius = 2
middleDot.addSubview(middleCenter)
middleCenter.layoutChain
.centerX()
.centerY()
.width(4)
.heightToWidth(1)
cardView.backgroundColor = .white
cardView.layer.cornerRadius = 16
dragBtn.setImage(UIImage(named: "Schedule/drag")?.withRenderingMode(.alwaysOriginal), for: .normal)
dragBtn.extendEdgeInsets = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
deleteBtn.setImage(UIImage(named: "Schedule/delete")?.withRenderingMode(.alwaysOriginal), for: .normal)
deleteBtn.extendEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
deleteBtn.addTarget(self, action: #selector(tapDelete), for: .touchUpInside)
let press = UILongPressGestureRecognizer(target: self, action: #selector(handleDragPress(_:)))
press.minimumPressDuration = 0.12
dragBtn.addGestureRecognizer(press)
dragPress = press
dashLineView.translatesAutoresizingMaskIntoConstraints = false
markerView.translatesAutoresizingMaskIntoConstraints = false
let topToContent = dashLineView.topAnchor.constraint(equalTo: contentView.topAnchor)
let topToMarker = dashLineView.topAnchor.constraint(equalTo: markerView.centerYAnchor)
let bottomToContent = dashLineView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
let bottomToMarker = dashLineView.bottomAnchor.constraint(equalTo: markerView.centerYAnchor)
dashTopToContent = topToContent
dashTopToMarkerCenter = topToMarker
dashBottomToContent = bottomToContent
dashBottomToMarkerCenter = bottomToMarker
// 穿configure role
topToMarker.isActive = false
bottomToMarker.isActive = false
NSLayoutConstraint.activate([
topToContent,
bottomToContent,
dashLineView.centerXAnchor.constraint(equalTo: contentView.leftAnchor, constant: ScheduleTimelineMetric.centerX),
dashLineView.widthAnchor.constraint(equalToConstant: 1),
markerView.centerXAnchor.constraint(equalTo: contentView.leftAnchor, constant: ScheduleTimelineMetric.centerX),
markerView.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
markerView.widthAnchor.constraint(equalToConstant: ScheduleTimelineMetric.markerSize),
markerView.heightAnchor.constraint(equalToConstant: ScheduleTimelineMetric.markerSize)
])
markerLab.layoutChain.edges()
middleDot.layoutChain
.centerX(markerView)
.centerY(markerView)
.width(10)
.height(10)
cardView.layoutChain
.top(8)
.left(50)
.right(16)
.bottom(8)
//
timeRow.layoutChain
.top(10)
.left(10)
.right(10)
.height(36)
locationRow.translatesAutoresizingMaskIntoConstraints = false
remarkRow.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
locationRow.topAnchor.constraint(equalTo: timeRow.bottomAnchor, constant: 8),
locationRow.leftAnchor.constraint(equalTo: cardView.leftAnchor, constant: 10),
locationRow.heightAnchor.constraint(equalToConstant: 36),
remarkRow.topAnchor.constraint(equalTo: locationRow.bottomAnchor, constant: 8),
remarkRow.leftAnchor.constraint(equalTo: cardView.leftAnchor, constant: 10),
remarkRow.heightAnchor.constraint(equalToConstant: 36),
remarkRow.bottomAnchor.constraint(equalTo: cardView.bottomAnchor, constant: -10)
])
let locationRight = locationRow.rightAnchor.constraint(equalTo: cardView.rightAnchor, constant: -10)
let remarkRight = remarkRow.rightAnchor.constraint(equalTo: cardView.rightAnchor, constant: -10)
locationRight.isActive = true
remarkRight.isActive = true
locationRowRightConstraint = locationRight
remarkRowRightConstraint = remarkRight
dragBtn.layoutChain
.right(10)
.centerY(locationRow)
.width(22)
.height(22)
deleteBtn.layoutChain
.right(12)
.centerY(remarkRow)
.width(18)
.height(18)
}
@objc private func tapDelete() {
onDelete?()
}
@objc private func handleDragPress(_ gesture: UILongPressGestureRecognizer) {
onDragGesture?(gesture)
}
}
// MARK: - ScheduleAddPointCell
final class ScheduleAddPointCell: UITableViewCell {
static let reuseId = "ScheduleAddPointCell"
var onAdd: (() -> Void)?
private let dashLineView = ScheduleDashLineView()
private let addBtn = UIButton(type: .custom)
override init(style: CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
contentView.backgroundColor = .clear
clipsToBounds = false
contentView.clipsToBounds = false
setup()
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
private func setup() {
contentView.addSubview(dashLineView)
contentView.addSubview(addBtn)
addBtn.setTitle(" 添加行程", for: .normal)
addBtn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
addBtn.titleLabel?.font = .systemFont(ofSize: 14, weight: .medium)
addBtn.setImage(UIImage(named: "Schedule/add")?.withRenderingMode(.alwaysOriginal), for: .normal)
addBtn.backgroundColor = UIColor(hexStr: "#F6FBFF")
addBtn.layer.cornerRadius = 12
addBtn.addTarget(self, action: #selector(tapAdd), for: .touchUpInside)
dashLineView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
dashLineView.topAnchor.constraint(equalTo: contentView.topAnchor),
dashLineView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
dashLineView.centerXAnchor.constraint(equalTo: contentView.leftAnchor, constant: ScheduleTimelineMetric.centerX),
dashLineView.widthAnchor.constraint(equalToConstant: 1)
])
addBtn.layoutChain
.top(8)
.left(50)
.right(16)
.height(44)
.bottom(8)
}
private var dashBorder: CAShapeLayer?
override func layoutSubviews() {
super.layoutSubviews()
dashBorder?.removeFromSuperlayer()
let border = CAShapeLayer()
border.strokeColor = UIColor(hexStr: "#16B3FF").cgColor
border.fillColor = nil
border.lineDashPattern = [5, 4]
border.lineWidth = 1
border.frame = addBtn.bounds
border.path = UIBezierPath(roundedRect: addBtn.bounds, cornerRadius: 12).cgPath
addBtn.layer.borderWidth = 0
addBtn.layer.addSublayer(border)
dashBorder = border
}
@objc private func tapAdd() {
onAdd?()
}
}
// MARK: - Field row
private final class ScheduleFieldRowView: UIView {
var onTap: (() -> Void)?
private(set) var textField: UITextField?
private let iconView = UIImageView()
private let label = UILabel()
private let placeholder: String
init(icon: UIImage?, placeholder: String, editable: Bool = false) {
self.placeholder = placeholder
super.init(frame: .zero)
backgroundColor = .white
layer.cornerRadius = 8
iconView.image = icon?.withRenderingMode(.alwaysOriginal)
iconView.contentMode = .scaleAspectFit
addSubview(iconView)
iconView.layoutChain.left(10).centerY().width(14).height(14)
let tfView = UIView()
tfView.backgroundColor = UIColor(hexStr: "#FAFAFA")
tfView.cornerRadius = 8
addSubview(tfView)
tfView.layoutChain.leftToRightOfView(iconView, offset: 8).right(10).centerY().height(34)
if editable {
let tf = UITextField()
tf.font = .systemFont(ofSize: 13, weight: .medium)
tf.textColor = UIColor(hexStr: "#333333")
tf.placeholder = placeholder
tf.returnKeyType = .done
tfView.addSubview(tf)
tf.layoutChain.edgesHorzontal(10).edgesVertical()
textField = tf
} else {
label.font = .systemFont(ofSize: 13, weight: .medium)
label.textColor = UIColor(hexStr: "#999999")
label.text = placeholder
label.isUserInteractionEnabled = true
tfView.addSubview(label)
label.layoutChain.edgesHorzontal(10).centerY()
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
addGestureRecognizer(tap)
}
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
func setText(_ text: String?) {
if let tf = textField {
tf.text = text
return
}
if let text, !text.isEmpty {
label.text = text
label.textColor = UIColor(hexStr: "#333333")
} else {
label.text = placeholder
label.textColor = UIColor(hexStr: "#999999")
}
}
@objc private func handleTap() {
onTap?()
}
}
// MARK: - Dash line
final class ScheduleDashLineView: 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.maxX == 0 ? 0.5 : bounds.midX, y: bounds.height))
shapeLayer.path = path.cgPath
layer.addSublayer(shapeLayer)
}
}
///
typealias DashLineView = ScheduleDashLineView
final 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)
}
}