jsdw_ios/QuickLocation/Section/Group/GroupSetting/GroupSettingView.swift

897 lines
37 KiB
Swift

//
// GroupSettingView.swift
// QuickLocation
//
// Created by on 2026/6/9.
//
import UIKit
import RxSwift
import RxCocoa
final class GroupSettingView: UIView {
private let pageInset: CGFloat = 15
var onRemoveMemberShortcutTapped: (() -> Void)?
var onMembersTapped: (() -> Void)?
func setupData(_ model: GroupInfoModel,
members: [GroupMemberModel],
groupNickname: String) {
groupNameView.setValue(model.name)
groupTypeView.setValue(model.groupTemplateName)
groupDescView.setValue(model.description.isEmpty ? "暂无描述" : model.description)
groupNicknameView.setValue(groupNickname)
tagView.configure(tags: model.labels, editable: model.is_owner)
switchBtn.isOn = model.review
auditSwitchView.isHidden = !model.is_owner
auditMemberView.isHidden = !model.is_owner
dismissGroupView.isHidden = !model.is_owner
leaveGroupView.isHidden = model.is_owner
groupNameView.setShowsArrow(model.is_owner)
groupTypeView.setShowsArrow(false)
groupDescView.setShowsArrow(model.is_owner)
groupNicknameView.setShowsArrow(true)
auditMemberView.setShowsArrow(model.is_owner)
let total = model.people_no > 0 ? model.people_no : members.count
let online = members.filter(\.is_online).count
memberCountLab.text = "\(total)人/\(online)人在线"
memberGridView.configure(members: members, showsRemoveShortcut: model.is_owner)
setNeedsLayout()
}
func updateGroupNickname(_ nickname: String) {
groupNicknameView.setValue(nickname)
}
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor(hexStr: "#F7F8FA")
memberGridView.onRemoveMemberShortcutTapped = { [weak self] in
self?.onRemoveMemberShortcutTapped?()
}
memberGridView.onMembersTapped = { [weak self] in
self?.onMembersTapped?()
}
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
addSubview(navBgView)
addSubview(navView)
addSubview(scrollView)
scrollView.addSubview(scrollContentView)
scrollContentView.addSubview(contentStackView)
contentStackView.addArrangedSubview(memberCardView)
contentStackView.addArrangedSubview(infoCardView)
contentStackView.addArrangedSubview(utilityCardView)
contentStackView.addArrangedSubview(dangerCardView)
setupMemberCard()
setupInfoCard()
setupUtilityCard()
setupDangerCard()
navBgView.translatesAutoresizingMaskIntoConstraints = false
navView.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollContentView.translatesAutoresizingMaskIntoConstraints = false
contentStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
navBgView.topAnchor.constraint(equalTo: topAnchor),
navBgView.leadingAnchor.constraint(equalTo: leadingAnchor),
navBgView.trailingAnchor.constraint(equalTo: trailingAnchor),
navBgView.heightAnchor.constraint(equalTo: widthAnchor, multiplier: 160.0 / 375.0),
navView.topAnchor.constraint(equalTo: topAnchor),
navView.leadingAnchor.constraint(equalTo: leadingAnchor),
navView.trailingAnchor.constraint(equalTo: trailingAnchor),
navView.heightAnchor.constraint(equalToConstant: kNaviHeight),
scrollView.topAnchor.constraint(equalTo: navView.bottomAnchor),
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
scrollContentView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
scrollContentView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
scrollContentView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
scrollContentView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
scrollContentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor),
contentStackView.topAnchor.constraint(equalTo: scrollContentView.topAnchor, constant: 14),
contentStackView.leadingAnchor.constraint(equalTo: scrollContentView.leadingAnchor, constant: pageInset),
contentStackView.trailingAnchor.constraint(equalTo: scrollContentView.trailingAnchor, constant: -pageInset),
contentStackView.bottomAnchor.constraint(equalTo: scrollContentView.bottomAnchor, constant: -30)
])
}
private func setupMemberCard() {
memberCardView.addSubview(memberHeaderView)
memberCardView.addSubview(memberGridView)
memberCardView.addSubview(memberSeparatorView)
memberCardView.addSubview(memberActionsView)
memberHeaderView.addSubview(memberTitleLab)
memberHeaderView.addSubview(memberCountLab)
memberHeaderView.addSubview(memberHeaderArrowView)
memberActionsView.addSubview(inviteCodeButton)
memberActionsView.addSubview(actionDividerView)
memberActionsView.addSubview(shareButton)
let views = [memberHeaderView, memberGridView, memberSeparatorView, memberActionsView,
memberTitleLab, memberCountLab, memberHeaderArrowView,
inviteCodeButton, actionDividerView, shareButton]
views.forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
NSLayoutConstraint.activate([
memberHeaderView.topAnchor.constraint(equalTo: memberCardView.topAnchor),
memberHeaderView.leadingAnchor.constraint(equalTo: memberCardView.leadingAnchor),
memberHeaderView.trailingAnchor.constraint(equalTo: memberCardView.trailingAnchor),
memberHeaderView.heightAnchor.constraint(equalToConstant: 44),
memberTitleLab.leadingAnchor.constraint(equalTo: memberHeaderView.leadingAnchor, constant: 16),
memberTitleLab.centerYAnchor.constraint(equalTo: memberHeaderView.centerYAnchor),
memberCountLab.trailingAnchor.constraint(equalTo: memberHeaderArrowView.leadingAnchor, constant: -8),
memberCountLab.centerYAnchor.constraint(equalTo: memberHeaderView.centerYAnchor),
memberHeaderArrowView.trailingAnchor.constraint(equalTo: memberHeaderView.trailingAnchor, constant: -16),
memberHeaderArrowView.centerYAnchor.constraint(equalTo: memberHeaderView.centerYAnchor),
memberHeaderArrowView.widthAnchor.constraint(equalToConstant: 16),
memberHeaderArrowView.heightAnchor.constraint(equalToConstant: 16),
memberGridView.topAnchor.constraint(equalTo: memberHeaderView.bottomAnchor),
memberGridView.leadingAnchor.constraint(equalTo: memberCardView.leadingAnchor, constant: 20),
memberGridView.trailingAnchor.constraint(equalTo: memberCardView.trailingAnchor, constant: -20),
memberSeparatorView.topAnchor.constraint(equalTo: memberGridView.bottomAnchor, constant: 6),
memberSeparatorView.leadingAnchor.constraint(equalTo: memberCardView.leadingAnchor, constant: 16),
memberSeparatorView.trailingAnchor.constraint(equalTo: memberCardView.trailingAnchor, constant: -16),
memberSeparatorView.heightAnchor.constraint(equalToConstant: 1),
memberActionsView.topAnchor.constraint(equalTo: memberSeparatorView.bottomAnchor, constant: 4),
memberActionsView.leadingAnchor.constraint(equalTo: memberCardView.leadingAnchor),
memberActionsView.trailingAnchor.constraint(equalTo: memberCardView.trailingAnchor),
memberActionsView.bottomAnchor.constraint(equalTo: memberCardView.bottomAnchor, constant: -6),
memberActionsView.heightAnchor.constraint(equalToConstant: 40),
inviteCodeButton.leadingAnchor.constraint(equalTo: memberActionsView.leadingAnchor),
inviteCodeButton.topAnchor.constraint(equalTo: memberActionsView.topAnchor),
inviteCodeButton.bottomAnchor.constraint(equalTo: memberActionsView.bottomAnchor),
inviteCodeButton.trailingAnchor.constraint(equalTo: actionDividerView.leadingAnchor),
actionDividerView.centerXAnchor.constraint(equalTo: memberActionsView.centerXAnchor),
actionDividerView.centerYAnchor.constraint(equalTo: memberActionsView.centerYAnchor),
actionDividerView.widthAnchor.constraint(equalToConstant: 0.5),
actionDividerView.heightAnchor.constraint(equalToConstant: 20),
shareButton.leadingAnchor.constraint(equalTo: actionDividerView.trailingAnchor),
shareButton.trailingAnchor.constraint(equalTo: memberActionsView.trailingAnchor),
shareButton.topAnchor.constraint(equalTo: memberActionsView.topAnchor),
shareButton.bottomAnchor.constraint(equalTo: memberActionsView.bottomAnchor)
])
}
private func setupInfoCard() {
infoCardView.addSubview(infoStackView)
infoStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
infoStackView.topAnchor.constraint(equalTo: infoCardView.topAnchor),
infoStackView.leadingAnchor.constraint(equalTo: infoCardView.leadingAnchor),
infoStackView.trailingAnchor.constraint(equalTo: infoCardView.trailingAnchor),
infoStackView.bottomAnchor.constraint(equalTo: infoCardView.bottomAnchor)
])
infoStackView.addArrangedSubview(groupNameView)
infoStackView.addArrangedSubview(groupTypeView)
infoStackView.addArrangedSubview(groupDescView)
infoStackView.addArrangedSubview(groupNicknameView)
infoStackView.addArrangedSubview(auditSwitchView)
infoStackView.addArrangedSubview(auditMemberView)
infoStackView.addArrangedSubview(tagView)
}
private func setupUtilityCard() {
utilityCardView.addSubview(clearHistoryView)
clearHistoryView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(clearHistoryView.edgesToSuperview())
}
private func setupDangerCard() {
dangerCardView.addSubview(dangerStackView)
dangerStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate(dangerStackView.edgesToSuperview())
dangerStackView.addArrangedSubview(dismissGroupView)
dangerStackView.addArrangedSubview(leaveGroupView)
}
private func makeMemberActionButton(title: String, imageName: String) -> UIButton {
let button = UIButton(type: .custom)
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = 7
stackView.isUserInteractionEnabled = false
let imageView = UIImageView(image: UIImage(named: imageName))
imageView.contentMode = .scaleAspectFit
imageView.widthAnchor.constraint(equalToConstant: 18).isActive = true
imageView.heightAnchor.constraint(equalToConstant: 18).isActive = true
let titleLab = UILabel()
titleLab.text = title
titleLab.textColor = UIColor(hexStr: "#293445")
titleLab.font = .systemFont(ofSize: 16, weight: .medium)
stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(titleLab)
button.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: button.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: button.centerYAnchor)
])
return button
}
lazy var navBgView: UIImageView = {
let view = UIImageView(image: UIImage(named: "Common/navBar_bg_2"))
view.contentMode = .scaleAspectFill
return view
}()
lazy var navView: BaseNavigationView = BaseNavigationView(title: "圈子详情")
private lazy var scrollView: UIScrollView = {
let view = UIScrollView()
view.backgroundColor = .clear
view.showsVerticalScrollIndicator = false
return view
}()
private lazy var scrollContentView = UIView()
private lazy var contentStackView: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 16
view.alignment = .fill
view.distribution = .fill
return view
}()
// MARK: -
private lazy var memberCardView = makeCardView()
let memberHeaderView = UIView()
private lazy var memberTitleLab: UILabel = {
let label = UILabel()
label.text = "圈子成员"
label.textColor = UIColor(hexStr: "#293445")
label.font = .systemFont(ofSize: 17, weight: .semibold)
return label
}()
private lazy var memberCountLab: UILabel = {
let label = UILabel()
label.textColor = UIColor(hexStr: "#A5A8AE")
label.font = .systemFont(ofSize: 13, weight: .regular)
return label
}()
private lazy var memberHeaderArrowView: UIImageView = makeArrowView()
private let memberGridView = GroupSettingMemberGridView()
private let memberSeparatorView = HorizontalDashLineView()
private let memberActionsView = UIView()
lazy var inviteCodeButton: UIButton = makeMemberActionButton(title: "邀请码", imageName: "GroupSetting/invite_code")
lazy var shareButton: UIButton = makeMemberActionButton(title: "分享", imageName: "GroupSetting/share")
private lazy var actionDividerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hexStr: "#E7E8EA")
return view
}()
// MARK: -
private lazy var infoCardView = makeCardView()
private lazy var infoStackView: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 0
view.alignment = .fill
view.distribution = .fill
return view
}()
let groupNameView = GroupSettingInfoRow(title: "圈子昵称", style: .text)
let groupTypeView = GroupSettingInfoRow(title: "圈子类型", style: .text)
let groupDescView = GroupSettingInfoRow(title: "圈子描述", style: .multilineText)
let groupNicknameView = GroupSettingInfoRow(title: "我在本群昵称", style: .text)
let auditSwitchView = GroupSettingInfoRow(title: "审核开关", style: .toggle)
let auditMemberView = GroupSettingInfoRow(title: "审核成员", style: .text)
let tagView = GroupSettingTagRow()
var switchBtn: UISwitch { auditSwitchView.switchButton }
// MARK: -
private lazy var utilityCardView = makeCardView()
let clearHistoryView = GroupSettingActionRow(title: "清空聊天记录")
private lazy var dangerCardView = makeCardView()
private lazy var dangerStackView: UIStackView = {
let view = UIStackView()
view.axis = .vertical
view.spacing = 0
view.alignment = .fill
view.distribution = .fill
return view
}()
let dismissGroupView = GroupSettingActionRow(title: "解散圈子", titleColor: UIColor(hexStr: "#FF4D53"), hidesDivider: true)
let leaveGroupView = GroupSettingActionRow(title: "退出圈子", titleColor: UIColor(hexStr: "#FF4D53"), hidesDivider: true)
private func makeCardView() -> UIView {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 12
view.clipsToBounds = true
return view
}
private func makeArrowView() -> UIImageView {
let view = UIImageView(image: UIImage(named: "GroupSetting/arrow_right"))
view.contentMode = .scaleAspectFit
return view
}
}
private final class GroupSettingMemberGridView: UIView {
private let itemWidth: CGFloat = 56
private let itemHeight: CGFloat = 62
var onRemoveMemberShortcutTapped: (() -> Void)?
var onMembersTapped: (() -> Void)?
private weak var removeMemberCell: GroupSettingRemoveMemberCell?
private lazy var scrollView: UIScrollView = {
let view = UIScrollView()
view.showsHorizontalScrollIndicator = false
view.showsVerticalScrollIndicator = false
view.alwaysBounceHorizontal = true
view.delaysContentTouches = false
return view
}()
private lazy var itemsStackView: UIStackView = {
let view = UIStackView()
view.axis = .horizontal
view.spacing = 12
view.alignment = .top
view.distribution = .fill
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(scrollView)
scrollView.addSubview(itemsStackView)
let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
tap.cancelsTouchesInView = false
scrollView.addGestureRecognizer(tap)
scrollView.translatesAutoresizingMaskIntoConstraints = false
itemsStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: topAnchor),
scrollView.leadingAnchor.constraint(equalTo: leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: bottomAnchor),
itemsStackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
itemsStackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
itemsStackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
itemsStackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
itemsStackView.heightAnchor.constraint(equalTo: scrollView.frameLayoutGuide.heightAnchor)
])
heightAnchor.constraint(equalToConstant: itemHeight).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(members: [GroupMemberModel], showsRemoveShortcut: Bool) {
itemsStackView.arrangedSubviews.forEach {
itemsStackView.removeArrangedSubview($0)
$0.removeFromSuperview()
}
removeMemberCell = nil
members.forEach { member in
let cell = GroupSettingMemberCell(member: member)
cell.widthAnchor.constraint(equalToConstant: itemWidth).isActive = true
itemsStackView.addArrangedSubview(cell)
}
if showsRemoveShortcut {
let removeCell = GroupSettingRemoveMemberCell()
removeMemberCell = removeCell
removeCell.widthAnchor.constraint(equalToConstant: itemWidth).isActive = true
itemsStackView.addArrangedSubview(removeCell)
}
}
@objc private func handleTap(_ gesture: UITapGestureRecognizer) {
if let removeMemberCell {
let point = gesture.location(in: removeMemberCell)
if removeMemberCell.bounds.contains(point) {
onRemoveMemberShortcutTapped?()
return
}
}
onMembersTapped?()
}
}
private final class GroupSettingMemberCell: UIView {
init(member: GroupMemberModel) {
super.init(frame: .zero)
setupUI()
avatarView.image = member.userIcon
nicknameLab.text = member.nick_name.isEmpty ? "未知昵称" : member.nick_name
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
addSubview(avatarView)
addSubview(nicknameLab)
avatarView.translatesAutoresizingMaskIntoConstraints = false
nicknameLab.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
avatarView.topAnchor.constraint(equalTo: topAnchor),
avatarView.centerXAnchor.constraint(equalTo: centerXAnchor),
avatarView.widthAnchor.constraint(equalToConstant: 40),
avatarView.heightAnchor.constraint(equalToConstant: 40),
nicknameLab.topAnchor.constraint(equalTo: avatarView.bottomAnchor, constant: 5),
nicknameLab.leadingAnchor.constraint(equalTo: leadingAnchor),
nicknameLab.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
private lazy var avatarView: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFill
view.layer.cornerRadius = 11
view.clipsToBounds = true
return view
}()
private lazy var nicknameLab: UILabel = {
let label = UILabel()
label.textColor = UIColor(hexStr: "#81858C")
label.font = .systemFont(ofSize: 11, weight: .regular)
label.textAlignment = .center
label.lineBreakMode = .byTruncatingTail
return label
}()
}
private final class GroupSettingRemoveMemberCell: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(iconContainer)
addSubview(titleLab)
iconContainer.addSubview(iconView)
[iconContainer, titleLab, iconView].forEach { $0.translatesAutoresizingMaskIntoConstraints = false }
NSLayoutConstraint.activate([
iconContainer.topAnchor.constraint(equalTo: topAnchor),
iconContainer.centerXAnchor.constraint(equalTo: centerXAnchor),
iconContainer.widthAnchor.constraint(equalToConstant: 40),
iconContainer.heightAnchor.constraint(equalToConstant: 40),
iconView.centerXAnchor.constraint(equalTo: iconContainer.centerXAnchor),
iconView.centerYAnchor.constraint(equalTo: iconContainer.centerYAnchor),
iconView.widthAnchor.constraint(equalToConstant: 16),
iconView.heightAnchor.constraint(equalToConstant: 16),
titleLab.topAnchor.constraint(equalTo: iconContainer.bottomAnchor, constant: 5),
titleLab.leadingAnchor.constraint(equalTo: leadingAnchor),
titleLab.trailingAnchor.constraint(equalTo: trailingAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private lazy var iconContainer: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hexStr: "#F6F4F5")
view.layer.cornerRadius = 12
return view
}()
private lazy var iconView: UIImageView = {
let view = UIImageView(image: UIImage(named: "GroupSetting/remove_member"))
view.contentMode = .scaleAspectFit
return view
}()
private lazy var titleLab: UILabel = {
let label = UILabel()
label.text = "移除"
label.textColor = UIColor(hexStr: "#81858C")
label.font = .systemFont(ofSize: 11, weight: .regular)
label.textAlignment = .center
return label
}()
}
final class GroupSettingInfoRow: UIView {
enum Style {
case text
case icon
case multilineText
case toggle
}
let switchButton = UISwitch()
private let style: Style
private let titleLab = UILabel()
private let valueLab = UILabel()
private let valueImageView = UIImageView()
private let arrowImageView = UIImageView(image: UIImage(named: "GroupSetting/arrow_right"))
private let dividerView = UIView()
private var valueTrailingToArrow: NSLayoutConstraint?
private var valueTrailingToRow: NSLayoutConstraint?
private var imageTrailingToArrow: NSLayoutConstraint?
private var imageTrailingToRow: NSLayoutConstraint?
init(title: String, style: Style) {
self.style = style
super.init(frame: .zero)
titleLab.text = title
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setValue(_ value: String) {
valueLab.text = value
}
func setImage(_ image: UIImage?) {
valueImageView.image = image
}
func setShowsArrow(_ showsArrow: Bool) {
guard style != .toggle else { return }
arrowImageView.isHidden = !showsArrow
valueTrailingToArrow?.isActive = showsArrow
valueTrailingToRow?.isActive = !showsArrow
imageTrailingToArrow?.isActive = showsArrow
imageTrailingToRow?.isActive = !showsArrow
}
private func setupUI() {
backgroundColor = .clear
titleLab.textColor = UIColor(hexStr: "#293445")
titleLab.font = .systemFont(ofSize: 16, weight: .regular)
titleLab.setContentHuggingPriority(.required, for: .horizontal)
titleLab.setContentCompressionResistancePriority(.required, for: .horizontal)
valueLab.textColor = UIColor(hexStr: "#4B5565")
valueLab.font = .systemFont(ofSize: 16, weight: .regular)
valueLab.textAlignment = .right
valueLab.lineBreakMode = .byTruncatingTail
valueLab.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
valueImageView.contentMode = .scaleAspectFill
valueImageView.layer.cornerRadius = 10
valueImageView.clipsToBounds = true
arrowImageView.contentMode = .scaleAspectFit
dividerView.backgroundColor = UIColor(hexStr: "#EDEEF0")
switchButton.onTintColor = UIColor(hexStr: "#16B3FF")
[titleLab, valueLab, valueImageView, arrowImageView, switchButton, dividerView].forEach {
addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
NSLayoutConstraint.activate([
titleLab.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
arrowImageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
arrowImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
arrowImageView.widthAnchor.constraint(equalToConstant: 16),
arrowImageView.heightAnchor.constraint(equalToConstant: 16),
dividerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
dividerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
dividerView.bottomAnchor.constraint(equalTo: bottomAnchor),
dividerView.heightAnchor.constraint(equalToConstant: 0.5)
])
switch style {
case .text:
setupTextLayout(multiline: false)
case .icon:
setupIconLayout()
case .multilineText:
setupTextLayout(multiline: true)
case .toggle:
setupToggleLayout()
}
}
private func setupTextLayout(multiline: Bool) {
valueImageView.isHidden = true
switchButton.isHidden = true
valueTrailingToArrow = valueLab.trailingAnchor.constraint(equalTo: arrowImageView.leadingAnchor, constant: -10)
valueTrailingToRow = valueLab.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16)
valueTrailingToRow?.isActive = true
arrowImageView.isHidden = true
if multiline {
valueLab.numberOfLines = 2
valueLab.textAlignment = .right
NSLayoutConstraint.activate([
titleLab.centerYAnchor.constraint(equalTo: centerYAnchor),
valueLab.leadingAnchor.constraint(equalTo: titleLab.trailingAnchor, constant: 20),
valueLab.centerYAnchor.constraint(equalTo: centerYAnchor),
valueLab.topAnchor.constraint(greaterThanOrEqualTo: topAnchor, constant: 12),
valueLab.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -12),
heightAnchor.constraint(greaterThanOrEqualToConstant: 72)
])
} else {
NSLayoutConstraint.activate([
titleLab.centerYAnchor.constraint(equalTo: centerYAnchor),
valueLab.leadingAnchor.constraint(greaterThanOrEqualTo: titleLab.trailingAnchor, constant: 20),
valueLab.centerYAnchor.constraint(equalTo: centerYAnchor),
heightAnchor.constraint(equalToConstant: 62)
])
}
}
private func setupIconLayout() {
valueLab.isHidden = true
switchButton.isHidden = true
arrowImageView.isHidden = true
imageTrailingToArrow = valueImageView.trailingAnchor.constraint(equalTo: arrowImageView.leadingAnchor, constant: -12)
imageTrailingToRow = valueImageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16)
imageTrailingToRow?.isActive = true
NSLayoutConstraint.activate([
titleLab.centerYAnchor.constraint(equalTo: centerYAnchor),
valueImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
valueImageView.widthAnchor.constraint(equalToConstant: 40),
valueImageView.heightAnchor.constraint(equalToConstant: 40),
heightAnchor.constraint(equalToConstant: 64)
])
}
private func setupToggleLayout() {
valueLab.isHidden = true
valueImageView.isHidden = true
arrowImageView.isHidden = true
NSLayoutConstraint.activate([
titleLab.centerYAnchor.constraint(equalTo: centerYAnchor),
switchButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
switchButton.centerYAnchor.constraint(equalTo: centerYAnchor),
heightAnchor.constraint(equalToConstant: 62)
])
}
}
final class GroupSettingTagRow: UIView {
private let titleLab = UILabel()
private let arrowImageView = UIImageView(image: UIImage(named: "GroupSetting/arrow_right"))
private let tagStack: UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.alignment = .center
stack.spacing = 6
stack.isUserInteractionEnabled = false
return stack
}()
private let dividerView = UIView()
private var tagTrailingToArrow: NSLayoutConstraint?
private var tagTrailingToRow: NSLayoutConstraint?
init() {
super.init(frame: .zero)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(tags: [String], editable: Bool) {
tagStack.arrangedSubviews.forEach {
tagStack.removeArrangedSubview($0)
$0.removeFromSuperview()
}
if tags.count > 2 {
tagStack.addArrangedSubview(GroupSettingTagChip(text: "···", compact: true))
tags.suffix(2).forEach { tagStack.addArrangedSubview(GroupSettingTagChip(text: $0, compact: false)) }
} else {
tags.forEach { tagStack.addArrangedSubview(GroupSettingTagChip(text: $0, compact: false)) }
}
arrowImageView.isHidden = !editable
tagTrailingToArrow?.isActive = false
tagTrailingToRow?.isActive = false
if editable {
tagTrailingToArrow?.isActive = true
} else {
tagTrailingToRow?.isActive = true
}
}
private func setupUI() {
titleLab.text = "圈子标签"
titleLab.textColor = UIColor(hexStr: "#293445")
titleLab.font = .systemFont(ofSize: 16, weight: .regular)
titleLab.setContentHuggingPriority(.required, for: .horizontal)
titleLab.setContentCompressionResistancePriority(.required, for: .horizontal)
arrowImageView.contentMode = .scaleAspectFit
arrowImageView.isHidden = true
dividerView.backgroundColor = UIColor(hexStr: "#EDEEF0")
tagStack.setContentHuggingPriority(.required, for: .horizontal)
tagStack.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
[titleLab, arrowImageView, tagStack, dividerView].forEach {
addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
tagTrailingToArrow = tagStack.trailingAnchor.constraint(equalTo: arrowImageView.leadingAnchor, constant: -10)
tagTrailingToRow = tagStack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16)
tagTrailingToRow?.isActive = true
NSLayoutConstraint.activate([
titleLab.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
titleLab.centerYAnchor.constraint(equalTo: centerYAnchor),
arrowImageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
arrowImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
arrowImageView.widthAnchor.constraint(equalToConstant: 16),
arrowImageView.heightAnchor.constraint(equalToConstant: 16),
tagStack.leadingAnchor.constraint(greaterThanOrEqualTo: titleLab.trailingAnchor, constant: 12),
tagStack.centerYAnchor.constraint(equalTo: centerYAnchor),
tagStack.heightAnchor.constraint(equalToConstant: 28),
heightAnchor.constraint(equalToConstant: 62),
dividerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
dividerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
dividerView.bottomAnchor.constraint(equalTo: bottomAnchor),
dividerView.heightAnchor.constraint(equalToConstant: 0.5)
])
}
}
private final class GroupSettingTagChip: UIView {
private let label = UILabel()
private let insets: UIEdgeInsets
init(text: String, compact: Bool) {
insets = compact
? UIEdgeInsets(top: 5, left: 8, bottom: 5, right: 8)
: UIEdgeInsets(top: 5, left: 12, bottom: 5, right: 12)
super.init(frame: .zero)
isUserInteractionEnabled = false
backgroundColor = UIColor(hexStr: "#E4F6FF")
layer.cornerRadius = 8
clipsToBounds = true
label.text = text
label.font = .systemFont(ofSize: 14, weight: .regular)
label.textColor = UIColor(hexStr: "#16B3FF")
label.textAlignment = .center
addSubview(label)
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: insets.left),
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -insets.right),
label.topAnchor.constraint(equalTo: topAnchor, constant: insets.top),
label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -insets.bottom)
])
setContentHuggingPriority(.required, for: .horizontal)
setContentCompressionResistancePriority(.required, for: .horizontal)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
final class GroupSettingActionRow: UIView {
init(title: String,
titleColor: UIColor = UIColor(hexStr: "#293445"),
hidesDivider: Bool = false) {
super.init(frame: .zero)
titleLab.text = title
titleLab.textColor = titleColor
dividerView.isHidden = hidesDivider
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
titleLab.font = .systemFont(ofSize: 16, weight: .regular)
arrowImageView.contentMode = .scaleAspectFit
dividerView.backgroundColor = UIColor(hexStr: "#EDEEF0")
[titleLab, arrowImageView, dividerView].forEach {
addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
NSLayoutConstraint.activate([
titleLab.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
titleLab.centerYAnchor.constraint(equalTo: centerYAnchor),
arrowImageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
arrowImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
arrowImageView.widthAnchor.constraint(equalToConstant: 16),
arrowImageView.heightAnchor.constraint(equalToConstant: 16),
dividerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
dividerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
dividerView.bottomAnchor.constraint(equalTo: bottomAnchor),
dividerView.heightAnchor.constraint(equalToConstant: 0.5),
heightAnchor.constraint(equalToConstant: 62)
])
}
private let titleLab = UILabel()
private let arrowImageView = UIImageView(image: UIImage(named: "GroupSetting/arrow_right"))
private let dividerView = UIView()
}
private final class GroupSettingDashedLineView: UIView {
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.setStrokeColor(UIColor(hexStr: "#D8D8D8").cgColor)
context.setLineWidth(1 / UIScreen.main.scale)
context.setLineDash(phase: 0, lengths: [4, 4])
context.move(to: CGPoint(x: 0, y: rect.midY))
context.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
context.strokePath()
}
}
private extension UIView {
func edgesToSuperview() -> [NSLayoutConstraint] {
guard let superview = superview else { return [] }
return [
topAnchor.constraint(equalTo: superview.topAnchor),
leadingAnchor.constraint(equalTo: superview.leadingAnchor),
trailingAnchor.constraint(equalTo: superview.trailingAnchor),
bottomAnchor.constraint(equalTo: superview.bottomAnchor)
]
}
}