| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832 |
- //
- // CreateRecordSheet.swift
- // VenusKitto
- //
- // Created by Neoa on 2025/8/25.
- //
- import Foundation
- import UIKit
- /// 自定义创建记录弹窗(含半透明遮罩 + 白色面板)
- final class CreateRecordSheet: UIView, UITextViewDelegate {
- // 需要添加一个viewController引用
- weak var viewController: UIViewController?
- // MARK: - UI
- private let overlay = UIControl()
- private let sheet = UIView()
- private let header = UIView()
- private let backBtn = UIButton(type: .system)
- private let titleLabel = UILabel()
- private let closeBtn = UIButton(type: .system)
- private let memoBtn = UIButton(type: .system)
- private let dash = CAShapeLayer()
- private let scroll = UIScrollView()
- private let content = UIStackView()
- // Editor (Quick Memo)
- private var editorView: UIView?
- private var editorBuilt = false
- private let editorTextView = UITextView()
- private let editorPlaceholder = UILabel()
- // Editor title & tag
- private let editorTitleLabel = UILabel()
- private let tagButton = UIButton(type: .system)
- private var selectedEventName: String = "随手小记"
- // 事件标题到 recordTypeId 的映射(按后端给定)
- private let recordTypeMap: [String: Int] = [
- "吃饭": 1, "喝水": 2, "喂奶": 3, "尿便": 4, "铲屎": 5,
- "驱虫": 6, "喂药": 7, "疫苗": 8, "看病": 9, "手术": 10,
- "洗澡": 11, "梳毛": 12, "剪指甲": 13, "洗耳朵": 14,
- // 后端示例为“挤肛门踢”,也兼容“挤肛门腺”
- "挤肛门踢": 15, "挤肛门腺": 15,
- "洗笼子": 16, "洗食盆": 17, "洗水盆": 18, "洗玩具": 19, "换耗材": 20
- ]
- // 新增:保存选择的petId
- private var selectedPetId: Int?
- // Tag picker
- // 添加TagPickerView属性
- private var tagPickerView: TagPickerView!
- // 在 CreateRecordSheet 中添加日期选择的功能
- private var selectedDate: Date?
- // 对外回调(可选)
- var onDismiss: (() -> Void)?
- // rows 属性定义
- private var rows = UIStackView()
- private var dateLabel = UILabel()
-
- private var petAvatar = UIImageView()
- private var petNameLabel = UILabel()
-
- // MARK: - Life
- override init(frame: CGRect) {
- super.init(frame: frame)
- buildUI()
- isHidden = true
- }
- required init?(coder: NSCoder) { fatalError() }
- // MARK: - Public
- func show() {
- isHidden = false
- overlay.alpha = 0
- sheet.transform = CGAffineTransform(translationX: 0, y: 30)
- sheet.alpha = 0
- setNeedsLayout()
- layoutIfNeeded()
- UIView.animate(withDuration: 0.22) {
- self.overlay.alpha = 1
- self.sheet.alpha = 1
- self.sheet.transform = .identity
- }
- }
- @objc func hide() {
- UIView.animate(withDuration: 0.2, animations: {
- self.overlay.alpha = 0
- self.sheet.alpha = 0
- self.sheet.transform = CGAffineTransform(translationX: 0, y: 10)
- }, completion: { _ in
- self.isHidden = true
- self.onDismiss?()
- })
- }
- // MARK: - Build
- private func buildUI() {
- backgroundColor = .clear
- // overlay
- overlay.backgroundColor = UIColor.black.withAlphaComponent(0.5)
- overlay.alpha = 0
- overlay.addTarget(self, action: #selector(hide), for: .touchUpInside)
- addSubview(overlay)
- // sheet
- sheet.backgroundColor = .white
- sheet.layer.cornerRadius = 16
- sheet.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
- sheet.layer.masksToBounds = true
- sheet.alpha = 0
- addSubview(sheet)
- // header
- backBtn.setImage(UIImage(named: "AddPet385")?.withRenderingMode(.alwaysOriginal), for: .normal)
- backBtn.tintColor = UIColor(hex: "#2B2B2B")
- backBtn.addTarget(self, action: #selector(onBackTapped), for: .touchUpInside)
- titleLabel.text = "创建记录"
- titleLabel.font = .systemFont(ofSize: 18, weight: .semibold)
- titleLabel.textColor = UIColor(hex: "#2B2B2B")
- titleLabel.textAlignment = .center
- closeBtn.setImage(UIImage(named: "AddPet384")?.withRenderingMode(.alwaysOriginal), for: .normal)
- closeBtn.tintColor = UIColor(hex: "#2B2B2B")
- closeBtn.addTarget(self, action: #selector(hide), for: .touchUpInside)
- [header, backBtn, titleLabel, closeBtn].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
- sheet.addSubview(header)
- header.addSubview(backBtn)
- header.addSubview(titleLabel)
- header.addSubview(closeBtn)
- // dashed memo button
- memoBtn.setTitle("随手小记", for: .normal)
- memoBtn.setTitleColor(UIColor(hex: "#2B2B2B"), for: .normal)
- memoBtn.titleLabel?.font = .systemFont(ofSize: 15)
- memoBtn.backgroundColor = UIColor(hex: "#FCFCFC")
- memoBtn.layer.cornerRadius = 14
- memoBtn.layer.masksToBounds = true
- memoBtn.translatesAutoresizingMaskIntoConstraints = false
- sheet.addSubview(memoBtn)
- dash.strokeColor = UIColor(hex: "#5B4227").cgColor
- dash.fillColor = UIColor.clear.cgColor
- // 使用 NSNumber,避免某些系统下的桥接问题
- dash.lineDashPattern = [NSNumber(value: 6), NSNumber(value: 4)]
- dash.lineCap = .round
- dash.lineWidth = 1
- dash.zPosition = 1
- memoBtn.layer.addSublayer(dash)
- // scroll + content
- scroll.showsVerticalScrollIndicator = false
- scroll.translatesAutoresizingMaskIntoConstraints = false
- sheet.addSubview(scroll)
- content.axis = .vertical
- content.spacing = 16
- content.translatesAutoresizingMaskIntoConstraints = false
- scroll.addSubview(content)
- overlay.translatesAutoresizingMaskIntoConstraints = false
- sheet.translatesAutoresizingMaskIntoConstraints = false
- // 创建TagPickerView
- tagPickerView = TagPickerView(frame: CGRect.zero)
- tagPickerView.translatesAutoresizingMaskIntoConstraints = false
- addSubview(tagPickerView)
-
- tagPickerView.onTagSelected = { [weak self] selectedTag in
- guard let self = self else { return }
- self.selectedEventName = selectedTag
- self.editorTitleLabel.text = selectedTag
- }
- NSLayoutConstraint.activate([
- overlay.topAnchor.constraint(equalTo: topAnchor),
- overlay.leadingAnchor.constraint(equalTo: leadingAnchor),
- overlay.trailingAnchor.constraint(equalTo: trailingAnchor),
- overlay.bottomAnchor.constraint(equalTo: bottomAnchor),
- sheet.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0),
- sheet.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0),
- sheet.bottomAnchor.constraint(equalTo: bottomAnchor),
- sheet.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.75),
- header.topAnchor.constraint(equalTo: sheet.topAnchor, constant: 8),
- header.leadingAnchor.constraint(equalTo: sheet.leadingAnchor),
- header.trailingAnchor.constraint(equalTo: sheet.trailingAnchor),
- header.heightAnchor.constraint(equalToConstant: 44),
- backBtn.leadingAnchor.constraint(equalTo: header.leadingAnchor, constant: 12),
- backBtn.centerYAnchor.constraint(equalTo: header.centerYAnchor),
- backBtn.widthAnchor.constraint(equalToConstant: 30),
- backBtn.heightAnchor.constraint(equalToConstant: 30),
- closeBtn.trailingAnchor.constraint(equalTo: header.trailingAnchor, constant: -12),
- closeBtn.centerYAnchor.constraint(equalTo: header.centerYAnchor),
- closeBtn.widthAnchor.constraint(equalToConstant: 30),
- closeBtn.heightAnchor.constraint(equalToConstant: 30),
- titleLabel.centerXAnchor.constraint(equalTo: header.centerXAnchor),
- titleLabel.centerYAnchor.constraint(equalTo: header.centerYAnchor),
- memoBtn.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 8),
- memoBtn.leadingAnchor.constraint(equalTo: sheet.leadingAnchor, constant: 16),
- memoBtn.trailingAnchor.constraint(equalTo: sheet.trailingAnchor, constant: -16),
- memoBtn.heightAnchor.constraint(equalToConstant: 48),
- scroll.topAnchor.constraint(equalTo: memoBtn.bottomAnchor, constant: 12),
- scroll.leadingAnchor.constraint(equalTo: sheet.leadingAnchor),
- scroll.trailingAnchor.constraint(equalTo: sheet.trailingAnchor),
- scroll.bottomAnchor.constraint(equalTo: sheet.bottomAnchor, constant: -8),
- content.topAnchor.constraint(equalTo: scroll.topAnchor, constant: 8),
- content.leadingAnchor.constraint(equalTo: scroll.leadingAnchor, constant: 16),
- content.trailingAnchor.constraint(equalTo: scroll.trailingAnchor, constant: -16),
- content.bottomAnchor.constraint(equalTo: scroll.bottomAnchor, constant: -16),
- content.widthAnchor.constraint(equalTo: scroll.widthAnchor, constant: -32),
-
- tagPickerView.topAnchor.constraint(equalTo: sheet.topAnchor),
- tagPickerView.leadingAnchor.constraint(equalTo: sheet.leadingAnchor),
- tagPickerView.trailingAnchor.constraint(equalTo: sheet.trailingAnchor),
- tagPickerView.bottomAnchor.constraint(equalTo: sheet.bottomAnchor)
- ])
- // 隐藏TagPickerView
- tagPickerView.isHidden = true
- // Sections
- let sections: [(String, [String])] = [
- ("日常事项", ["吃饭","喝水","喂奶","尿便","铲屎"]),
- ("健康事项", ["驱虫","喂药","疫苗","看病","手术"]),
- ("清洁事项", ["洗澡","梳毛","剪指甲","洗耳朵","挤肛门腺"]),
- ("打扫事项", ["洗笼子","洗食盆","洗水盆","洗玩具","换耗材"])
- ]
- for (t, items) in sections { content.addArrangedSubview(makeSection(title: t, items: items)) }
- memoBtn.addTarget(self, action: #selector(onMemoTapped), for: .touchUpInside)
- // Pre-build editor container (hidden by default)
- buildQuickMemoEditorIfNeeded()
- }
- override func layoutSubviews() {
- super.layoutSubviews()
- // Recompute dashed border path after Auto Layout has finalized frames
- let bounds = memoBtn.bounds
- guard bounds.width > 0 && bounds.height > 0 else { return }
- dash.contentsScale = UIScreen.main.scale
- dash.frame = bounds
- let inset: CGFloat = dash.lineWidth / 2
- let rect = bounds.insetBy(dx: inset, dy: inset)
- let radius = max(0, 14 - inset)
- dash.path = UIBezierPath(roundedRect: rect, cornerRadius: radius).cgPath
- }
- // MARK: - Helpers
- private func makeSection(title: String, items: [String]) -> UIView {
- let wrap = UIStackView()
- wrap.axis = .vertical
- wrap.spacing = 8
- let titleLabel = UILabel()
- titleLabel.text = title
- titleLabel.font = .systemFont(ofSize: 13, weight: .semibold)
- titleLabel.textColor = UIColor(hex: "#2B2B2B")
- wrap.addArrangedSubview(titleLabel)
- let grid = UIStackView()
- grid.axis = .vertical
- grid.spacing = 12
- wrap.addArrangedSubview(grid)
- var row = UIStackView(); row.axis = .horizontal; row.spacing = 16; row.distribution = .fillEqually
- grid.addArrangedSubview(row)
- var count = 0
- for name in items {
- if count == 5 {
- row = UIStackView(); row.axis = .horizontal; row.spacing = 16; row.distribution = .fillEqually
- grid.addArrangedSubview(row)
- count = 0
- }
- let v = makeIconChip(title: name, imageName: "peihead")
- row.addArrangedSubview(v)
- count += 1
- }
- return wrap
- }
- private func makeIconChip(title: String, imageName: String) -> UIView {
- let holder = UIView()
- let container = UIStackView()
- container.axis = .vertical
- container.alignment = .center
- container.spacing = 6
- holder.addSubview(container)
- container.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- container.topAnchor.constraint(equalTo: holder.topAnchor),
- container.bottomAnchor.constraint(equalTo: holder.bottomAnchor),
- container.leadingAnchor.constraint(equalTo: holder.leadingAnchor),
- container.trailingAnchor.constraint(equalTo: holder.trailingAnchor)
- ])
- let bg = UIView()
- bg.backgroundColor = .white
- bg.layer.cornerRadius = 12
- bg.layer.borderWidth = 1
- bg.layer.borderColor = UIColor(hex: "#E9E5E1").cgColor
- bg.translatesAutoresizingMaskIntoConstraints = false
- let iv = UIImageView(image: UIImage(named: imageName))
- iv.contentMode = .scaleAspectFit
- iv.translatesAutoresizingMaskIntoConstraints = false
- bg.addSubview(iv)
- NSLayoutConstraint.activate([
- bg.widthAnchor.constraint(equalToConstant: 56),
- bg.heightAnchor.constraint(equalToConstant: 56),
- iv.centerXAnchor.constraint(equalTo: bg.centerXAnchor),
- iv.centerYAnchor.constraint(equalTo: bg.centerYAnchor),
- iv.widthAnchor.constraint(equalToConstant: 40),
- iv.heightAnchor.constraint(equalToConstant: 40)
- ])
- let label = UILabel(); label.text = title; label.font = .systemFont(ofSize: 12); label.textColor = UIColor(hex: "#6B6B6B")
- container.addArrangedSubview(bg)
- container.addArrangedSubview(label)
- // Full-size invisible button to capture taps
- let tap = UIButton(type: .system)
- tap.backgroundColor = .clear
- tap.accessibilityLabel = title // 把事件名称带出来
- tap.addTarget(self, action: #selector(onIconTapped(_:)), for: .touchUpInside)
- holder.addSubview(tap)
- tap.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- tap.topAnchor.constraint(equalTo: holder.topAnchor),
- tap.bottomAnchor.constraint(equalTo: holder.bottomAnchor),
- tap.leadingAnchor.constraint(equalTo: holder.leadingAnchor),
- tap.trailingAnchor.constraint(equalTo: holder.trailingAnchor)
- ])
- return holder
- }
- // MARK: - Back/Editor
- @objc private func onBackTapped() {
- if let ed = editorView, !ed.isHidden {
- // Back from editor to grid
- toggleEditor(false, animated: true)
- } else {
- hide()
- }
- }
- @objc private func openQuickMemoEditor() {
- buildQuickMemoEditorIfNeeded()
- toggleEditor(true, animated: true)
- }
- private func buildQuickMemoEditorIfNeeded() {
- guard !editorBuilt else { return }
- editorBuilt = true
-
- //键盘回收功能
- wire()
-
- let ed = UIView()
- ed.backgroundColor = .clear
- ed.isHidden = true
- sheet.addSubview(ed)
- ed.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- ed.leadingAnchor.constraint(equalTo: sheet.leadingAnchor, constant: 0),
- ed.trailingAnchor.constraint(equalTo: sheet.trailingAnchor, constant: 0),
- ed.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 0),
- ed.bottomAnchor.constraint(equalTo: sheet.bottomAnchor, constant: 0)
- ])
- self.editorView = ed
- // Card
- let card = UIView()
- card.backgroundColor = .white
- card.layer.cornerRadius = 14
- card.layer.masksToBounds = false
- ed.addSubview(card)
- card.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- card.leadingAnchor.constraint(equalTo: ed.leadingAnchor, constant: 0),
- card.trailingAnchor.constraint(equalTo: ed.trailingAnchor, constant: -0),
- card.topAnchor.constraint(equalTo: ed.topAnchor, constant: 0)
- ])
- // Shadow to mimic design
- // card.layer.shadowColor = UIColor.black.withAlphaComponent(0.06).cgColor
- // card.layer.shadowOpacity = 1
- // card.layer.shadowRadius = 10
- // card.layer.shadowOffset = CGSize(width: 0, height: 6)
- let vstack = UIStackView(); vstack.axis = .vertical; vstack.alignment = .fill; vstack.spacing = 16
- card.addSubview(vstack)
- vstack.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- vstack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
- vstack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -16),
- vstack.topAnchor.constraint(equalTo: card.topAnchor, constant: 16),
- ])
- // Icon + title
- let icon = UIImageView(image: UIImage(named: "peihead"))
- icon.contentMode = .scaleAspectFit
- icon.translatesAutoresizingMaskIntoConstraints = false
- icon.widthAnchor.constraint(equalToConstant: 88).isActive = true
- icon.heightAnchor.constraint(equalToConstant: 88).isActive = true
- let iconWrap = UIView(); iconWrap.translatesAutoresizingMaskIntoConstraints = false
- iconWrap.addSubview(icon)
- NSLayoutConstraint.activate([
- icon.centerXAnchor.constraint(equalTo: iconWrap.centerXAnchor),
- icon.topAnchor.constraint(equalTo: iconWrap.topAnchor),
- icon.bottomAnchor.constraint(equalTo: iconWrap.bottomAnchor)
- ])
- vstack.addArrangedSubview(iconWrap)
- // 修改标题行的创建代码
- let titleRow = UIStackView()
- titleRow.axis = .horizontal
- titleRow.alignment = .center
- titleRow.distribution = .fill
- editorTitleLabel.text = selectedEventName
- editorTitleLabel.font = .systemFont(ofSize: 16, weight: .semibold)
- editorTitleLabel.textColor = UIColor(hex: "#2B2B2B")
- editorTitleLabel.textAlignment = .center // 添加居中文本对齐
- // 创建左右占位视图以确保标题居中
- let leftSpacer = UIView()
- let rightSpacer = UIView()
- titleRow.addArrangedSubview(leftSpacer)
- titleRow.addArrangedSubview(editorTitleLabel)
- titleRow.addArrangedSubview(rightSpacer)
- // 现在左右占位视图已经添加到同一个父视图(titleRow)中,可以设置宽度相等约束
- leftSpacer.widthAnchor.constraint(equalTo: rightSpacer.widthAnchor).isActive = true
- // 确保占位视图有最小高度
- leftSpacer.heightAnchor.constraint(equalToConstant: 44).isActive = true
- rightSpacer.heightAnchor.constraint(equalToConstant: 44).isActive = true
- // 标签按钮放在右侧占位视图中
- rightSpacer.addSubview(tagButton)
- rightSpacer.isUserInteractionEnabled = true
- tagButton.translatesAutoresizingMaskIntoConstraints = false
- tagButton.setImage(UIImage(named: "AddRecord382")?.withRenderingMode(.alwaysOriginal), for: .normal)
- tagButton.contentEdgeInsets = UIEdgeInsets(top: 4, left: 4, bottom: 4, right: 4)
- tagButton.addTarget(self, action: #selector(openTagPicker), for: .touchUpInside)
- // 修改约束,确保按钮在右侧并居中
- NSLayoutConstraint.activate([
- tagButton.leadingAnchor.constraint(equalTo: rightSpacer.leadingAnchor, constant: 8), // 添加一些右边距
- tagButton.centerYAnchor.constraint(equalTo: rightSpacer.centerYAnchor),
- tagButton.widthAnchor.constraint(equalToConstant: 30), // 明确设置宽度
- tagButton.heightAnchor.constraint(equalToConstant: 30) // 明确设置高度
- ])
- vstack.addArrangedSubview(titleRow)
-
-
-
- // Form rows container
- rows = UIStackView(); rows.axis = .vertical; rows.spacing = 12
- vstack.addArrangedSubview(rows)
- // Row: 选择宠物(右侧小头像)Home372
- let petRow = makeFormRow(left: "选择宠物")
- petAvatar = UIImageView(image: UIImage(named: "Home372"))
- petAvatar.layer.cornerRadius = 16
- petAvatar.clipsToBounds = true
- petAvatar.translatesAutoresizingMaskIntoConstraints = false
- petAvatar.widthAnchor.constraint(equalToConstant: 32).isActive = true
- petAvatar.heightAnchor.constraint(equalToConstant: 32).isActive = true
- // Configure petNameLabel
- petNameLabel.font = .systemFont(ofSize: 14)
- petNameLabel.textColor = UIColor(hex: "#2B2B2B")
- petNameLabel.textAlignment = .right
- petNameLabel.numberOfLines = 1
- petNameLabel.text = ""
- petNameLabel.translatesAutoresizingMaskIntoConstraints = false
- // Set content hugging and compression priorities
- petNameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
- petNameLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)
- petRow.rightContainer.addSubview(petNameLabel)
- petRow.rightContainer.addSubview(petAvatar)
- NSLayoutConstraint.activate([
- petAvatar.trailingAnchor.constraint(equalTo: petRow.rightContainer.trailingAnchor),
- petAvatar.centerYAnchor.constraint(equalTo: petRow.rightContainer.centerYAnchor),
- petNameLabel.trailingAnchor.constraint(equalTo: petAvatar.leadingAnchor, constant: -8),
- petNameLabel.centerYAnchor.constraint(equalTo: petRow.rightContainer.centerYAnchor),
- petNameLabel.leadingAnchor.constraint(greaterThanOrEqualTo: petRow.rightContainer.leadingAnchor)
- ])
- petRow.container.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(openPetPicker)))
- rows.addArrangedSubview(petRow.container)
- // Row: 记录时间(右侧日期 + chevron)
- let timeRow = makeFormRow(left: "记录时间")
- dateLabel = UILabel(); dateLabel.text = DateFormatter.memoDateString(from: Date()); dateLabel.font = .systemFont(ofSize: 14); dateLabel.textColor = UIColor(hex: "#2B2B2B")
-
- let chevron = UIImageView(image: UIImage(systemName: "chevron.right")); chevron.tintColor = UIColor(hex: "#2B2B2B"); chevron.translatesAutoresizingMaskIntoConstraints = false; chevron.widthAnchor.constraint(equalToConstant: 10).isActive = true
- let rightStack = UIStackView(); rightStack.axis = .horizontal; rightStack.alignment = .center; rightStack.spacing = 6
- rightStack.addArrangedSubview(dateLabel)
- rightStack.addArrangedSubview(chevron)
- timeRow.rightContainer.addSubview(rightStack)
- rightStack.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- rightStack.trailingAnchor.constraint(equalTo: timeRow.rightContainer.trailingAnchor),
- rightStack.centerYAnchor.constraint(equalTo: timeRow.rightContainer.centerYAnchor)
- ])
-
- timeRow.container.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(openDatePicker)))
-
- rows.addArrangedSubview(timeRow.container)
- // Note box
- let noteBox = UIView(); noteBox.backgroundColor = UIColor(hex: "#F8F6F4"); noteBox.layer.cornerRadius = 12
- noteBox.translatesAutoresizingMaskIntoConstraints = false
- noteBox.heightAnchor.constraint(equalToConstant: 136).isActive = true
- vstack.addArrangedSubview(noteBox)
- editorTextView.backgroundColor = .clear
- editorTextView.font = .systemFont(ofSize: 14)
- editorTextView.textColor = UIColor(hex: "#2B2B2B")
- editorTextView.delegate = self
- noteBox.addSubview(editorTextView)
- editorTextView.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- editorTextView.leadingAnchor.constraint(equalTo: noteBox.leadingAnchor, constant: 12),
- editorTextView.trailingAnchor.constraint(equalTo: noteBox.trailingAnchor, constant: -12),
- editorTextView.topAnchor.constraint(equalTo: noteBox.topAnchor, constant: 12),
- editorTextView.bottomAnchor.constraint(equalTo: noteBox.bottomAnchor, constant: -12)
- ])
- editorPlaceholder.text = "字里行间,都是我们的故事"
- editorPlaceholder.textColor = UIColor(hex: "#B0B0B0")
- editorPlaceholder.font = .systemFont(ofSize: 14)
- noteBox.addSubview(editorPlaceholder)
- editorPlaceholder.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- editorPlaceholder.leadingAnchor.constraint(equalTo: editorTextView.leadingAnchor),
- editorPlaceholder.topAnchor.constraint(equalTo: editorTextView.topAnchor)
- ])
- // Save button
- let saveBtn = UIButton(type: .system)
- saveBtn.setTitle("保存", for: .normal)
- saveBtn.setTitleColor(.black, for: .normal)
- saveBtn.backgroundColor = UIColor(hex: "#FFE059")
- saveBtn.layer.cornerRadius = 24
- vstack.addArrangedSubview(saveBtn)
- saveBtn.heightAnchor.constraint(equalToConstant: 48).isActive = true
- saveBtn.addTarget(self, action: #selector(onSaveMemo), for: .touchUpInside)
- // Bottom spacing to allow scrolling content not clipped
- let bottom = UIView(); bottom.heightAnchor.constraint(equalToConstant: 16).isActive = true
- vstack.addArrangedSubview(bottom)
- // Lay out mini dash square in next layout pass
- // mini.layoutIfNeeded()
- // let miniInset: CGFloat = 0.5
- // let miniPath = UIBezierPath(roundedRect: mini.bounds.insetBy(dx: miniInset, dy: miniInset), cornerRadius: 3)
- // miniDash.path = miniPath.cgPath
- // Ensure card bottom anchors to its subviews
- let bottomConstraint = vstack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -16)
- bottomConstraint.isActive = true
- }
-
- @objc private func openPetPicker() {
- let petListVC = PetListViewController()
- petListVC.onPetSelected = { [weak self] selectedPet in
- self?.updatePetInfo(pet: selectedPet)
- }
- viewController?.present(petListVC, animated: true)
- }
-
- func updatePetInfo(pet: Pet) {
- petNameLabel.text = pet.name
- // 在加载图片前设置selectedPetId
- self.selectedPetId = Int(pet.id) ?? self.selectedPetId
- print("dddd \(pet.name)")
- if let url = URL(string: pet.avatar) {
- loadImage(from: url)
- }
- // selectedPet = pet
- }
- private func loadImage(from url: URL) {
- // Load the image without using third-party libraries
- URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
- guard let data = data, error == nil else { return }
- DispatchQueue.main.async {
- self?.petAvatar.image = UIImage(data: data)
- }
- }.resume()
- }
-
- @objc private func openDatePicker() {
- let datePickerController = DatePickerSheetController(initial: selectedDate ?? Date())
- datePickerController.onDone = { [weak self] selectedDate in
- self?.selectedDate = selectedDate
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd"
- print("sss \(dateFormatter.string(from: selectedDate))")
- // 更新记录时间显示
- self?.dateLabel.text = dateFormatter.string(from: selectedDate)
- }
- viewController?.present(datePickerController, animated: false)
- }
-
- private func wire() {
- // 关闭键盘的手势
- let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
- tapGesture.cancelsTouchesInView = false
- addGestureRecognizer(tapGesture)
- }
- @objc private func dismissKeyboard() {
- // 结束编辑并收回键盘
- editorTextView.resignFirstResponder()
- }
- private func toggleEditor(_ show: Bool, animated: Bool) {
- guard let ed = editorView else { return }
- let work = {
- ed.isHidden = !show
- self.scroll.isHidden = show
- }
- if animated {
- UIView.transition(with: sheet, duration: 0.2, options: .transitionCrossDissolve, animations: work)
- } else { work() }
- }
- private func makeFormRow(left: String) -> (container: UIView, rightContainer: UIView) {
- let box = UIView(); box.backgroundColor = UIColor.white; box.layer.cornerRadius = 12
- box.layer.shadowColor = UIColor.black.withAlphaComponent(0.05).cgColor
- box.layer.shadowOpacity = 1; box.layer.shadowRadius = 6; box.layer.shadowOffset = CGSize(width: 0, height: 3)
- let leftLabel = UILabel(); leftLabel.text = left; leftLabel.font = .systemFont(ofSize: 14); leftLabel.textColor = UIColor(hex: "#6B6B6B")
- let right = UIView()
- let line = UIView(); line.backgroundColor = UIColor(hex: "#F0EEEC")
- [leftLabel, right, line].forEach { box.addSubview($0); $0.translatesAutoresizingMaskIntoConstraints = false }
- NSLayoutConstraint.activate([
- leftLabel.leadingAnchor.constraint(equalTo: box.leadingAnchor, constant: 12),
- leftLabel.centerYAnchor.constraint(equalTo: box.centerYAnchor),
- right.trailingAnchor.constraint(equalTo: box.trailingAnchor, constant: -12),
- right.centerYAnchor.constraint(equalTo: box.centerYAnchor),
- right.leadingAnchor.constraint(greaterThanOrEqualTo: leftLabel.trailingAnchor, constant: 8),
- line.heightAnchor.constraint(equalToConstant: 1),
- line.leadingAnchor.constraint(equalTo: box.leadingAnchor),
- line.trailingAnchor.constraint(equalTo: box.trailingAnchor),
- line.bottomAnchor.constraint(equalTo: box.bottomAnchor)
- ])
- box.heightAnchor.constraint(equalToConstant: 54).isActive = true
- // 为 container 添加手势识别器
- let container = box
- let rightContainer = right
- container.isUserInteractionEnabled = true // 确保视图可以接受手势
- return (container, rightContainer)
- }
- @objc private func onSaveMemo() {
- // 1. 构建请求body
- let content = editorTextView.text ?? ""
- let title = (editorTitleLabel.text?.isEmpty == false ? editorTitleLabel.text! : selectedEventName)
- let dateFormatter = DateFormatter()
- dateFormatter.dateFormat = "yyyy-MM-dd"
- let recordTypeIdVal = recordTypeMap[title] ?? 0
- let recordDate = dateFormatter.string(from: selectedDate ?? Date())
- let petId = selectedPetId ?? 0
- let params: [String: Any] = [
- "content": content,
- "title": title,
- "recordDate": recordDate,
- "petId": petId,
- // 其他字段可按接口需要补充,暂用默认
- "module": "event",
- "recordTypeId": recordTypeIdVal,
- "recordUrl": "",
- "id": 0,
- "createTime": "",
- "updateTime": ""
- ]
- print("🧩 recordTypeId for title(\(title)) = \(recordTypeIdVal)")
- guard let url = URL(string: "\(baseURL)/petRecordInfo/createRecord") else {
- showToast("无效的请求地址")
- return
- }
- var request = URLRequest(url: url)
- request.httpMethod = "POST"
- request.setValue("application/json", forHTTPHeaderField: "Content-Type")
- if let token = UserDefaults.standard.string(forKey: "userToken") {
- request.setValue(token, forHTTPHeaderField: "Authorization")
- }
- do {
- let body = try JSONSerialization.data(withJSONObject: params, options: [])
- request.httpBody = body
- print("请求参数: \(String(data: body, encoding: .utf8) ?? "")")
- } catch {
- showToast("参数序列化失败")
- return
- }
- let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
- DispatchQueue.main.async {
- if let error = error {
- print("网络错误: \(error)")
- self?.showToast("网络错误")
- return
- }
- guard let httpResp = response as? HTTPURLResponse else {
- self?.showToast("无响应")
- return
- }
- let status = httpResp.statusCode
- let respBody = data.flatMap { String(data: $0, encoding: .utf8) } ?? ""
- print("响应状态: \(status)")
- print("响应内容: \(respBody)")
- // 尝试解析json
- var codeValue: String = ""
- var msgValue: String = ""
- if let d = data {
- if let json = try? JSONSerialization.jsonObject(with: d, options: []) as? [String: Any] {
- codeValue = (json["code"] as? String) ?? (json["code"] as? Int).map { String($0) } ?? ""
- msgValue = (json["msg"] as? String) ?? ""
- }
- }
- if status == 200 && (codeValue == "200" || codeValue == "200") {
- self?.showToast("保存成功")
- // 通知 HomeViewController 刷新数据
- let petIdStr = String(petId)
- NotificationCenter.default.post(name: .eventRecordDidCreate,
- object: nil,
- userInfo: [
- "petId": petIdStr,
- "recordDate": recordDate,
- "title": title
- ])
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) {
- self?.hide()
- }
- } else {
- self?.showToast(msgValue.isEmpty ? "保存失败" : msgValue)
- }
- }
- }
- task.resume()
- }
- // MARK: - Toast Helper
- private func showToast(_ text: String) {
- let lab = PaddingLabel(insets: .init(top: 8, left: 12, bottom: 8, right: 12))
- lab.backgroundColor = UIColor.black.withAlphaComponent(0.85)
- lab.textColor = .white
- lab.font = .systemFont(ofSize: 14)
- lab.layer.cornerRadius = 8
- lab.layer.masksToBounds = true
- lab.text = text
- sheet.addSubview(lab)
- lab.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- lab.centerXAnchor.constraint(equalTo: sheet.centerXAnchor),
- lab.bottomAnchor.constraint(equalTo: sheet.bottomAnchor, constant: -40)
- ])
- lab.alpha = 0
- UIView.animate(withDuration: 0.2, animations: { lab.alpha = 1 }) { _ in
- UIView.animate(withDuration: 0.2, delay: 1.2, options: [], animations: { lab.alpha = 0 }) { _ in
- lab.removeFromSuperview()
- }
- }
- }
- // MARK: - Memo & Icon Tap Handlers
- @objc private func onMemoTapped() {
- selectedEventName = "随手小记"
- editorTitleLabel.text = selectedEventName
- openQuickMemoEditor()
- }
- @objc private func onIconTapped(_ sender: UIButton) {
- let name = sender.accessibilityLabel ?? "随手小记"
- selectedEventName = name
- editorTitleLabel.text = name
- openQuickMemoEditor()
- }
- // MARK: - Tag Picker UI
- @objc private func openTagPicker() {
- tagPickerView.isHidden = false
- tagPickerView.show()
- }
- func textViewDidChange(_ textView: UITextView) {
- editorPlaceholder.isHidden = !textView.text.isEmpty
- }
- }
- extension DateFormatter {
- static func memoDateString(from date: Date) -> String {
- let df = DateFormatter(); df.dateFormat = "yyyy-MM-dd"; return df.string(from: date)
- }
- }
- // MARK: - Notification Keys
- extension Notification.Name {
- static let eventRecordDidCreate = Notification.Name("EventRecordDidCreate")
- }
|