jsdw_ios/QuickLocation/Section/Group/MemberInfo/MemberInfoView.swift

389 lines
14 KiB
Swift

//
// 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"))
}
}
}