// // BookkeepingQuickSheet.swift // VenusKitto // // Created by Neoa on 2025/8/26. // import Foundation import UIKit // MARK: - BookkeepingQuickSheet: 快速记账分类弹窗 final class BookkeepingQuickSheet: UIView { // 外部回调 var onDismiss: (() -> Void)? var onItemSelected: ((String) -> Void)? // 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 scroll = UIScrollView() private let content = UIStackView() override init(frame: CGRect) { super.init(frame: frame) buildUI() isHidden = true } required init?(coder: NSCoder) { fatalError() } // 显示/隐藏 func show() { isHidden = false overlay.alpha = 0 sheet.alpha = 0 sheet.transform = CGAffineTransform(translationX: 0, y: 24) layoutIfNeeded() UIView.animate(withDuration: 0.22) { self.overlay.alpha = 1 self.sheet.alpha = 1 self.sheet.transform = .identity } } @objc private 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?() }) } private func buildUI() { backgroundColor = .clear // 遮罩 overlay.backgroundColor = UIColor.black.withAlphaComponent(0.5) overlay.alpha = 0 overlay.addTarget(self, action: #selector(hide), for: .touchUpInside) addSubview(overlay) overlay.translatesAutoresizingMaskIntoConstraints = false // 面板 sheet.backgroundColor = .white sheet.layer.cornerRadius = 16 sheet.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] sheet.layer.masksToBounds = true addSubview(sheet) sheet.translatesAutoresizingMaskIntoConstraints = false // 头部 header.translatesAutoresizingMaskIntoConstraints = false sheet.addSubview(header) backBtn.setImage(UIImage(named: "AddPet385")?.withRenderingMode(.alwaysOriginal), for: .normal) backBtn.addTarget(self, action: #selector(hide), 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.addTarget(self, action: #selector(hide), for: .touchUpInside) [backBtn, titleLabel, closeBtn].forEach { v in v.translatesAutoresizingMaskIntoConstraints = false header.addSubview(v) } // 列表(注意使用 contentLayoutGuide / frameLayoutGuide,避免滚动布局冲突) scroll.showsVerticalScrollIndicator = false scroll.translatesAutoresizingMaskIntoConstraints = false sheet.addSubview(scroll) content.axis = .vertical content.spacing = 16 content.translatesAutoresizingMaskIntoConstraints = false scroll.addSubview(content) 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), sheet.trailingAnchor.constraint(equalTo: trailingAnchor), sheet.bottomAnchor.constraint(equalTo: bottomAnchor), sheet.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.65), 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), scroll.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 8), scroll.leadingAnchor.constraint(equalTo: sheet.leadingAnchor), scroll.trailingAnchor.constraint(equalTo: sheet.trailingAnchor), scroll.bottomAnchor.constraint(equalTo: sheet.bottomAnchor, constant: -8), content.leadingAnchor.constraint(equalTo: scroll.contentLayoutGuide.leadingAnchor, constant: 16), content.trailingAnchor.constraint(equalTo: scroll.contentLayoutGuide.trailingAnchor, constant: -16), content.topAnchor.constraint(equalTo: scroll.contentLayoutGuide.topAnchor, constant: 8), content.bottomAnchor.constraint(equalTo: scroll.contentLayoutGuide.bottomAnchor, constant: -16), content.widthAnchor.constraint(equalTo: scroll.frameLayoutGuide.widthAnchor, constant: -32) ]) // 数据(分组 + 每行 5 个) let sections: [(String, [String])] = [ ("食物", ["干粮","罐头","零食","冻干","草粮"]), ("生活", ["衣服","玩具","餐具","厕所","牵引绳"]), ("治病", ["驱虫药","保健品","手术","药品","疫苗"]), ("清洁", ["指甲剪","梳子","清洁用品","洗澡美容","洗护用品"]) ] for (t, items) in sections { content.addArrangedSubview(makeSection(title: t, items: items)) } } // 标题行:小黄块 + 文本(近似原型的装饰) private func makeDecorTitle(_ text: String) -> UIView { let h = UIStackView() h.axis = .horizontal h.alignment = .center h.spacing = 6 let flag = UIView() flag.backgroundColor = UIColor(hex: "#FFE059") flag.layer.cornerRadius = 4 flag.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ flag.widthAnchor.constraint(equalToConstant: 2), flag.heightAnchor.constraint(equalToConstant: 20) ]) let lb = UILabel() lb.text = text lb.font = .systemFont(ofSize: 13, weight: .semibold) lb.textColor = UIColor(hex: "#2B2B2B") h.addArrangedSubview(flag) h.addArrangedSubview(lb) return h } private func makeSection(title: String, items: [String]) -> UIView { let wrap = UIStackView() wrap.axis = .vertical wrap.spacing = 8 let titleView = makeDecorTitle(title) wrap.addArrangedSubview(titleView) 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) row.addArrangedSubview(v) count += 1 } return wrap } // 方形图标(56x56)+ 文本(上图下字),与原型一致 private func makeIconChip(title: 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: "peihead")) 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) // 全覆盖按钮用于点击 let tap = UIButton(type: .system) tap.backgroundColor = .clear tap.accessibilityLabel = title tap.addTarget(self, action: #selector(onItemTap(_:)), 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 } @objc private func onItemTap(_ sender: UIButton) { let name = sender.accessibilityLabel ?? "" onItemSelected?(name) // hide() } }