jsdw_ios/QuickLocation/Section/Home/GroupListPopView.swift

422 lines
13 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.

//
// GroupListPopView.swift
// QuickLocation
//
// Created by on 2026/5/30.
//
import UIKit
class GroupListPopView: UIView {
private static let shared = GroupListPopView(frame: CGRect(origin: .zero, size: kScreenSize))
private static let rowHeight: CGFloat = 72
private static let maxVisibleRows = 3
private static let primaryBlue = UIColor(hexStr: "#16B3FF")
private static let cardBg = UIColor(hexStr: "#F5FBFF")
private static let tagBg = UIColor(hexStr: "#FFF9DD")
private static let tagText = UIColor(hexStr: "#D5923F")
private var groupModel: GroupModel? {
didSet {
guard let model = groupModel else { return }
selectedGroupKey = model.default_group_key
groupList = model.groups
updateTableHeight()
}
}
private var selectedGroupKey: String = ""
private var groupList: [GroupInfoModel] = [] {
didSet {
tableView.reloadData()
}
}
///
private var completion: ((String?) -> Void)?
@objc private func tapMask() {
completion?(nil)
}
@objc private func cancelAction() {
completion?(nil)
}
@objc private func confirmAction() {
completion?(selectedGroupKey.isEmpty ? nil : selectedGroupKey)
}
private lazy var bgView: UIView = {
let view = UIView()
view.backgroundColor = .black.withAlphaComponent(0.5)
view.clipsToBounds = true
return view
}()
lazy var infoView: UIView = {
let view = UIView()
view.backgroundColor = .clear
let bg = UIView()
bg.backgroundColor = UIColor(hexStr: "#FDFDFD")
view.addSubview(bg)
bg.layoutChain
.top(80)
.edges(excludingEdge: .top)
return view
}()
lazy var headerImageView: UIImageView = {
let view = UIImageView()
view.image = UIImage(named: "Home/group_list_header")
view.contentMode = .scaleAspectFill
view.clipsToBounds = true
return view
}()
lazy var tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .plain)
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.rowHeight = Self.rowHeight
tableView.estimatedRowHeight = Self.rowHeight
tableView.showsVerticalScrollIndicator = false
tableView.bounces = false
tableView.register(GroupListCell.self)
tableView.dataSource = self
tableView.delegate = self
return tableView
}()
lazy var cancelBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("取消", for: .normal)
btn.setTitleColor(Self.primaryBlue, for: .normal)
btn.titleLabel?.font = FontManager.boboBold(18)
btn.backgroundColor = .white
btn.cornerRadius = 16
btn.layer.borderWidth = 1
btn.layer.borderColor = Self.primaryBlue.cgColor
btn.addTarget(self, action: #selector(cancelAction), for: .touchUpInside)
return btn
}()
lazy var confirmBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("确定", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = FontManager.boboBold(18)
btn.backgroundColor = Self.primaryBlue
btn.cornerRadius = 16
btn.addTarget(self, action: #selector(confirmAction), for: .touchUpInside)
return btn
}()
// MARK: - Init
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .clear
addSubview(bgView)
bgView.addSubview(infoView)
infoView.addSubview(headerImageView)
infoView.addSubview(tableView)
infoView.addSubview(cancelBtn)
infoView.addSubview(confirmBtn)
bgView.layoutChain.edges()
infoView.layoutChain
.bottom()
.edgesHorzontal()
headerImageView.layoutChain
.top()
.edgesHorzontal()
.heightToWidth(127/375)
cancelBtn.layoutChain
.left(20)
.bottom(20 + kSafeBottomMargin)
.height(44)
.widthToView(confirmBtn)
confirmBtn.layoutChain
.leftToRightOfView(cancelBtn, offset: 12)
.right(20)
.bottomToView(cancelBtn)
.height(44)
tableView.layoutChain
.topToBottomOfView(headerImageView, offset: -20)
.edgesHorzontal(12)
.height(Self.rowHeight)
.bottomToTopOfView(cancelBtn, offset: -16)
let tap = UITapGestureRecognizer(target: self, action: #selector(tapMask))
tap.delegate = self
addGestureRecognizer(tap)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func updateTableHeight() {
let visible = min(groupList.count, Self.maxVisibleRows)
let height = CGFloat(visible) * Self.rowHeight
tableView.layoutChain.height(height)
tableView.isScrollEnabled = groupList.count > Self.maxVisibleRows
tableView.reloadData()
}
private func slideOffTransform() -> CGAffineTransform {
layoutIfNeeded()
let offset = infoView.bounds.height// + 20 + kSafeBottomMargin
return CGAffineTransform(translationX: 0, y: max(offset, kScreenHeight * 0.5))
}
}
// MARK: - Public
extension GroupListPopView {
/// window
static func show(groupModel: GroupModel,
completion: @escaping ((String?) -> Void)) {
guard let superView = kKeyWindow else { return }
let pop = GroupListPopView.shared
if pop.superview != nil {
pop.removeFromSuperview()
}
pop.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight)
pop.groupModel = groupModel
pop.completion = { text in
completion(text)
GroupListPopView.dismiss()
}
pop.bgView.alpha = 0
superView.addSubview(pop)
superView.bringSubviewToFront(pop)
pop.layoutIfNeeded()
pop.infoView.transform = pop.slideOffTransform()
UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseOut) {
pop.bgView.alpha = 1
pop.infoView.transform = .identity
}
}
///
static func dismiss() {
let pop = GroupListPopView.shared
guard pop.superview != nil else { return }
UIView.animate(withDuration: 0.25, delay: 0, options: [.curveEaseIn]) {
pop.bgView.alpha = 0
pop.infoView.transform = pop.slideOffTransform()
} completion: { _ in
pop.infoView.transform = .identity
pop.removeFromSuperview()
}
}
}
// MARK: - UIGestureRecognizerDelegate
extension GroupListPopView: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if let view = touch.view, !(view == self || view == bgView) {
return false
}
return true
}
}
// MARK: - UITableViewDataSource & UITableViewDelegate
extension GroupListPopView: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
groupList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: GroupListCell = tableView.dequeueReusableCell(for: indexPath)
let model = groupList[indexPath.row]
cell.configure(model: model, isSelected: selectedGroupKey == model.group_key)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedGroupKey = groupList[indexPath.row].group_key
tableView.reloadData()
}
}
// MARK: - GroupListCell
class GroupListCell: UITableViewCell {
func configure(model: GroupInfoModel, isSelected: Bool) {
avaterImgView.image = model.groupIcon
nameLab.text = model.name
countLab.text = "\(model.people_no)成员"
tagStack.arrangedSubviews.forEach {
tagStack.removeArrangedSubview($0)
$0.removeFromSuperview()
}
let labels = Array(model.labels.prefix(2))
tagStack.isHidden = labels.isEmpty
for text in labels {
tagStack.addArrangedSubview(Self.makeTagLabel(text))
}
cardView.backgroundColor = isSelected ? GroupListPopView.cardBgColor : .white
cardView.layer.borderWidth = isSelected ? 1 : 0
cardView.layer.borderColor = GroupListPopView.primaryBlueColor.cgColor
checkIcon.image = isSelected
? UIImage(systemName: "checkmark.circle.fill")
: UIImage(systemName: "circle")
checkIcon.tintColor = isSelected
? GroupListPopView.primaryBlueColor
: UIColor(hexStr: "#D0D0D0")
}
private static func makeTagLabel(_ text: String) -> UILabel {
let label = PaddingLabel()
label.text = text
label.textColor = GroupListPopView.tagTextColor
label.font = .systemFont(ofSize: 10, weight: .medium)
label.backgroundColor = GroupListPopView.tagBgColor
label.cornerRadius = 4
label.clipsToBounds = true
label.textAlignment = .center
return label
}
override init(style: CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
selectionStyle = .none
backgroundColor = .clear
contentView.backgroundColor = .clear
setupSubviews()
}
private func setupSubviews() {
contentView.addSubview(cardView)
cardView.addSubview(avaterImgView)
cardView.addSubview(nameLab)
cardView.addSubview(tagStack)
cardView.addSubview(countLab)
cardView.addSubview(checkIcon)
cardView.layoutChain
.edgesVertical(4)
.edgesHorzontal(4)
avaterImgView.layoutChain
.left(12)
.centerY()
.width(44)
.height(44)
checkIcon.layoutChain
.right(12)
.centerY()
.width(22)
.height(22)
nameLab.layoutChain
.topToView(avaterImgView, offset: 2)
.leftToRightOfView(avaterImgView, offset: 10)
.rightToLeftOfView(checkIcon, offset: -8, relation: .lessThanOrEqual)
tagStack.layoutChain
.leftToRightOfView(nameLab, offset: 6)
.centerY(nameLab)
.rightToLeftOfView(checkIcon, offset: -8, relation: .lessThanOrEqual)
countLab.layoutChain
.topToBottomOfView(nameLab, offset: 4)
.leftToView(nameLab)
.rightToLeftOfView(checkIcon, offset: -8)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var cardView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.cornerRadius = 12
view.clipsToBounds = true
return view
}()
lazy var avaterImgView: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFill
view.cornerRadius = 8
view.clipsToBounds = true
return view
}()
lazy var nameLab: UILabel = {
let label = UILabel()
label.textColor = UIColor(hexStr: "#353B4F")
label.font = .systemFont(ofSize: 15, weight: .medium)
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return label
}()
lazy var tagStack: UIStackView = {
let view = UIStackView()
view.axis = .horizontal
view.alignment = .center
view.spacing = 4
view.setContentCompressionResistancePriority(.required, for: .horizontal)
return view
}()
lazy var countLab: UILabel = {
let label = UILabel()
label.textColor = UIColor(hexStr: "#999999")
label.font = .systemFont(ofSize: 12, weight: .regular)
return label
}()
lazy var checkIcon: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFit
view.preferredSymbolConfiguration = UIImage.SymbolConfiguration(pointSize: 20, weight: .regular)
return view
}()
}
// MARK: - Color accessors for cell (file-private via static)
private extension GroupListPopView {
static var primaryBlueColor: UIColor { primaryBlue }
static var cardBgColor: UIColor { cardBg }
static var tagBgColor: UIColor { tagBg }
static var tagTextColor: UIColor { tagText }
}
///
private class PaddingLabel: UILabel {
private let inset = UIEdgeInsets(top: 2, left: 6, bottom: 2, right: 6)
override func drawText(in rect: CGRect) {
super.drawText(in: rect.inset(by: inset))
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width + inset.left + inset.right,
height: size.height + inset.top + inset.bottom)
}
}