// // MemberInfoView.swift // QuickLocation // import UIKit import Kingfisher final class MemberInfoView: UIView { lazy var navView = BaseNavigationView(title: "成员信息") lazy var memberCV: UICollectionView = { let layout = UICollectionViewFlowLayout() layout.scrollDirection = .horizontal layout.itemSize = CGSize(width: 61, height: 90) layout.minimumLineSpacing = 12 let cv = UICollectionView(frame: .zero, collectionViewLayout: layout) cv.backgroundColor = .white cv.showsHorizontalScrollIndicator = false cv.contentInset = UIEdgeInsets(top: 0, left: 12, bottom: 0, right: 12) cv.register(GroupMemberListCell.self) cv.layer.cornerRadius = 20 return cv }() let scrollView = UIScrollView() let contentView = UIView() let detailCard = UIView() private let rowsStack = UIStackView() override init(frame: CGRect) { super.init(frame: frame) backgroundColor = UIColor(hexStr: "#F7F7F7") setupUI() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupUI() { addSubview(navView) navView.layoutChain.top().edgesHorzontal().height(kNaviHeight) addSubview(scrollView) scrollView.layoutChain.topToBottomOfView(navView).edgesHorzontal().bottom() scrollView.addSubview(contentView) contentView.layoutChain.edges().widthToView(scrollView) contentView.addSubview(memberCV) memberCV.layoutChain.top(8).edgesHorzontal(15).height(100) detailCard.backgroundColor = .white detailCard.layer.cornerRadius = 16 contentView.addSubview(detailCard) detailCard.layoutChain .topToBottomOfView(memberCV, offset: 12) .edgesHorzontal(15) .bottom(24) rowsStack.axis = .vertical rowsStack.spacing = 0 detailCard.addSubview(rowsStack) rowsStack.layoutChain.edges(UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0)) } func renderRows(_ rows: [MemberInfoRow]) { rowsStack.arrangedSubviews.forEach { rowsStack.removeArrangedSubview($0) $0.removeFromSuperview() } for (i, row) in rows.enumerated() { let view = MemberInfoRowView(row: row) rowsStack.addArrangedSubview(view) view.layoutChain.height(row.kind == .tag ? 56 : 52) if i < rows.count - 1 { let line = UIView() line.backgroundColor = UIColor(hexStr: "#F2F2F2") view.addSubview(line) line.layoutChain.left(15).right(15).bottom(0).height(0.5) } } } } enum MemberInfoRowKind { case text case avatar case tag case disclosure } struct MemberInfoRow { let title: String let value: String let kind: MemberInfoRowKind let action: (() -> Void)? let avatarURL: String? let showsMoreTags: Bool init(title: String, value: String, kind: MemberInfoRowKind = .text, avatarURL: String? = nil, showsMoreTags: Bool = false, action: (() -> Void)? = nil) { self.title = title self.value = value self.kind = kind self.avatarURL = avatarURL self.showsMoreTags = showsMoreTags self.action = action } } final class MemberInfoRowView: UIControl { private let titleLab = UILabel() private let valueLab = UILabel() private let arrow = UIImageView(image: UIImage(named: "Mine/arrow_right")) private let avatar = UIImageView() private let actionHandler: (() -> Void)? init(row: MemberInfoRow) { self.actionHandler = row.action super.init(frame: .zero) titleLab.text = row.title titleLab.font = .systemFont(ofSize: 14, weight: .medium) titleLab.textColor = UIColor(hexStr: "#293445") addSubview(titleLab) titleLab.layoutChain.left(15).centerY() valueLab.text = row.value valueLab.font = .systemFont(ofSize: 14, weight: .medium) valueLab.textColor = row.value == "未设置" || row.value == "未绑定" || row.value == "保密" ? UIColor(hexStr: "#AAAAAA") : UIColor(hexStr: "#293445") valueLab.textAlignment = .right arrow.contentMode = .scaleAspectFit avatar.contentMode = .scaleAspectFill avatar.clipsToBounds = true avatar.layer.cornerRadius = 14 switch row.kind { case .text: addSubview(valueLab) valueLab.layoutChain.right(15).centerY().leftToRightOfView(titleLab, offset: 12) case .disclosure: addSubview(arrow) arrow.layoutChain.right(15).centerY().width(12).height(12) addSubview(valueLab) valueLab.layoutChain.rightToLeftOfView(arrow, offset: -8).centerY().leftToRightOfView(titleLab, offset: 12) case .avatar: addSubview(arrow) arrow.layoutChain.right(15).centerY().width(12).height(12) addSubview(avatar) avatar.layoutChain.rightToLeftOfView(arrow, offset: -8).centerY().width(28).height(28) if let pic = row.avatarURL, !pic.isEmpty, let img = UIImage(named: "UserIcon/\(pic)") { avatar.image = img } else if let urlStr = row.avatarURL, let u = URL(string: urlStr), urlStr.hasPrefix("http") { avatar.kf.setImage(with: u, placeholder: UIImage(named: "Common/default_avatar")) } else { avatar.image = UIImage(named: "Common/default_avatar") } case .tag: setupGroupTags(row: row) } if row.action != nil { isAccessibilityElement = true accessibilityTraits = .button addTarget(self, action: #selector(tap), for: .touchUpInside) } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupGroupTags(row: MemberInfoRow) { let stack = UIStackView() stack.axis = .horizontal stack.alignment = .center stack.spacing = 8 // The whole row owns the action; its display-only tags must not consume touches. stack.isUserInteractionEnabled = false addSubview(stack) if row.showsMoreTags { let moreLabel = UILabel() moreLabel.text = "..." moreLabel.font = .systemFont(ofSize: 14, weight: .medium) moreLabel.textColor = UIColor(hexStr: "#D5923F") moreLabel.backgroundColor = UIColor(hexStr: "#FFF9DD") moreLabel.textAlignment = .center moreLabel.layer.cornerRadius = 7 moreLabel.clipsToBounds = true stack.addArrangedSubview(moreLabel) moreLabel.layoutChain.width(26).height(26) } let nameLabel = MemberInfoTagLabel() nameLabel.text = row.value nameLabel.font = .systemFont(ofSize: 14, weight: .medium) nameLabel.textColor = UIColor(hexStr: "#D5923F") nameLabel.backgroundColor = UIColor(hexStr: "#FFF9DD") nameLabel.textAlignment = .center nameLabel.numberOfLines = 1 nameLabel.lineBreakMode = .byTruncatingTail nameLabel.layer.cornerRadius = 7 nameLabel.clipsToBounds = true nameLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) stack.addArrangedSubview(nameLabel) nameLabel.layoutChain.height(26) nameLabel.widthAnchor.constraint(lessThanOrEqualToConstant: 160).isActive = true if row.showsMoreTags { arrow.contentMode = .scaleAspectFit stack.addArrangedSubview(arrow) arrow.layoutChain.width(12).height(12) } stack.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ stack.centerYAnchor.constraint(equalTo: centerYAnchor), stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -15), stack.leadingAnchor.constraint(greaterThanOrEqualTo: titleLab.trailingAnchor, constant: 12) ]) } @objc private func tap() { actionHandler?() } } private final class MemberInfoTagLabel: UILabel { private let insets = UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 10) override func drawText(in rect: CGRect) { super.drawText(in: rect.inset(by: insets)) } override var intrinsicContentSize: CGSize { let size = super.intrinsicContentSize return CGSize( width: size.width + insets.left + insets.right, height: size.height + insets.top + insets.bottom ) } } final class CommonGroupsPopVC: DLCustomPopVC { private let groups: [CommonGroupModel] private let groupInfos: [String: GroupInfoModel] private let rowHeight: CGFloat = 64 private let maximumVisibleRows = 5 private let titleLabel: UILabel = { let label = UILabel() label.text = "共同圈子" label.font = .systemFont(ofSize: 18, weight: .semibold) label.textColor = UIColor(hexStr: "#293445") label.textAlignment = .center return label }() private lazy var closeButton: UIButton = { let button = UIButton(type: .custom) button.setImage(UIImage(named: "Common/close"), for: .normal) button.addTarget(self, action: #selector(close), for: .touchUpInside) return button }() private lazy var tableView: UITableView = { let tableView = UITableView(frame: .zero, style: .plain) tableView.backgroundColor = .white tableView.separatorStyle = .none tableView.rowHeight = rowHeight tableView.showsVerticalScrollIndicator = false tableView.dataSource = self tableView.register(CommonGroupReadonlyCell.self) return tableView }() init(groups: [CommonGroupModel], groupInfos: [GroupInfoModel]) { self.groups = CommonGroupModel.deduplicated(groups) self.groupInfos = Dictionary(groupInfos.map { ($0.group_key, $0) }, uniquingKeysWith: { first, _ in first }) super.init(nibName: nil, bundle: nil) popStyle = .bottom bottomCornerRadius = 20 dimmingClick = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() contentView.addSubview(titleLabel) contentView.addSubview(closeButton) contentView.addSubview(tableView) titleLabel.layoutChain.top(20).centerX().height(28) closeButton.layoutChain.right(16).centerY(titleLabel).width(32).height(32) tableView.layoutChain .topToBottomOfView(titleLabel, offset: 12) .left(12) .right(12) .bottom(16 + kSafeBottomMargin) let visibleRows = min(groups.count, maximumVisibleRows) let height = 20 + 28 + 12 + CGFloat(visibleRows) * rowHeight + 16 + kSafeBottomMargin // The custom transition lays out contentBgView before its descendants. Give // the outer sheet an explicit height so the panel cannot collapse to zero. contentBgView.layoutChain.height(height) contentView.layoutChain.height(height) tableView.isScrollEnabled = groups.count > maximumVisibleRows view.layoutIfNeeded() } @objc private func close() { dismiss(animated: true) } } extension CommonGroupsPopVC: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { groups.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell: CommonGroupReadonlyCell = tableView.dequeueReusableCell(for: indexPath) let group = groups[indexPath.row] cell.configure(group: group, info: groupInfos[group.group_key]) return cell } } private final class CommonGroupReadonlyCell: UITableViewCell { private let cardView = UIView() private let iconView = UIImageView() private let nameLabel = MemberInfoTagLabel() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .none backgroundColor = .clear cardView.backgroundColor = UIColor(hexStr: "#FAFAFA") cardView.layer.cornerRadius = 10 contentView.addSubview(cardView) cardView.addSubview(iconView) cardView.addSubview(nameLabel) cardView.layoutChain.edges(UIEdgeInsets(top: 4, left: 0, bottom: 4, right: 0)) iconView.layoutChain.left(12).centerY().width(40).height(40) nameLabel.layoutChain .leftToRightOfView(iconView, offset: 12) .right(12, relation: .greaterThanOrEqual) .centerY() .height(26) iconView.contentMode = .scaleAspectFill iconView.layer.cornerRadius = 8 iconView.clipsToBounds = true nameLabel.font = .systemFont(ofSize: 14, weight: .medium) nameLabel.textColor = UIColor(hexStr: "#D5923F") nameLabel.backgroundColor = UIColor(hexStr: "#FFF9DD") nameLabel.numberOfLines = 1 nameLabel.lineBreakMode = .byTruncatingTail nameLabel.layer.cornerRadius = 7 nameLabel.clipsToBounds = true } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func prepareForReuse() { super.prepareForReuse() iconView.setGroupIcon(url: "", fallback: nil) } func configure(group: CommonGroupModel, info: GroupInfoModel?) { nameLabel.text = group.group_name.isEmpty ? "未知圈子" : group.group_name if let info { iconView.setGroupIcon(info) } else { iconView.setGroupIcon(url: "", fallback: UIImage(named: "GroupIcon/1")) } } }