jsdw_ios/QuickLocation/Section/Group/CreateGroup/CreateGroupView.swift

878 lines
30 KiB
Swift

//
// CreateGroupView.swift
// QuickLocation
//
// Created by on 2026/6/2.
//
import UIKit
import RxSwift
import RxCocoa
class CreateGroupView: UIView {
var disposeBag = DisposeBag()
private let limitCount = 50
private let tagMaxLength = 10
var onConfirmAddTag: ((String) -> Void)?
func configureGroupTemplates(_ templates: [GroupTemplateModel]) {
guard let first = templates.first else { return }
typeNameLab.text = first.name
iconCarousel.configure(templates: templates)
submitBtn.isEnabled = true
submitBtn.alpha = 1
}
private func setupRx() {
groupNameTF.rx.text.orEmpty
.subscribe(onNext: { [weak self] text in
guard let self = self else { return }
if text.count > 10 {
self.groupNameTF.text = String(text.prefix(10))
}
})
.disposed(by: disposeBag)
Observable.merge(
groupContentTV.rx.didChange.asObservable(),
groupContentTV.rx.text.map { _ in () },
groupContentTV.rx.methodInvoked(#selector(UITextView.paste(_:))).map { _ in () }
)
.throttle(.milliseconds(100), scheduler: MainScheduler.instance)
.subscribe(onNext: { [weak self] in
guard let self = self else { return }
let count = self.groupContentTV.text.count
self.placeholderLab.isHidden = count != 0
if count > self.limitCount {
self.groupContentTV.text = String(self.groupContentTV.text.prefix(self.limitCount))
self.groupContentTV.selectedRange = NSRange(location: self.limitCount, length: 0)
return
}
})
.disposed(by: disposeBag)
addTagTF.rx.text.orEmpty
.subscribe(onNext: { [weak self] text in
guard let self = self else { return }
if text.count > self.tagMaxLength {
self.addTagTF.text = String(text.prefix(self.tagMaxLength))
}
let count = self.addTagTF.text?.count ?? 0
self.addTagCountLab.text = "\(count)/\(self.tagMaxLength)"
})
.disposed(by: disposeBag)
sheetCancelBtn.rx.tap
.subscribe(onNext: { [weak self] in
self?.dismissAddTagSheet()
})
.disposed(by: disposeBag)
sheetConfirmBtn.rx.tap
.subscribe(onNext: { [weak self] in
guard let self = self else { return }
self.onConfirmAddTag?(self.addTagTF.text ?? "")
})
.disposed(by: disposeBag)
addTagClearBtn.rx.tap
.subscribe(onNext: { [weak self] in
self?.addTagTF.text = ""
self?.addTagCountLab.text = "0/\(self?.tagMaxLength ?? 10)"
})
.disposed(by: disposeBag)
}
private func setupUI() {
addSubview(navBgView)
addSubview(navView)
addSubview(scrollView)
addSubview(submitBtn)
scrollView.addSubview(contentView)
contentView.addSubview(iconCarousel)
contentView.addSubview(typeNamePlaceholder)
contentView.addSubview(groupNameTitleLab)
contentView.addSubview(groupNameInputView)
groupNameInputView.addSubview(groupNameTF)
contentView.addSubview(groupContentTitleLab)
contentView.addSubview(groupContentInputView)
groupContentInputView.addSubview(groupContentTV)
groupContentInputView.addSubview(placeholderLab)
contentView.addSubview(tagTitleLab)
contentView.addSubview(tagView)
navBgView.layoutChain
.edges(excludingEdge: .bottom)
.heightToWidth(160/375)
navView.layoutChain
.edges(excludingEdge: .bottom)
.height(kNaviHeight)
submitBtn.layoutChain
.bottom(kSafeBottomMargin + 24)
.centerX()
.edgesHorzontal(30)
.height(56)
scrollView.layoutChain
.topToBottomOfView(navView)
.edgesHorzontal()
.bottomToTopOfView(submitBtn, offset: -12)
contentView.layoutChain
.edges()
.widthToView(scrollView)
iconCarousel.layoutChain
.top(8)
.edgesHorzontal()
.height(130)
typeNamePlaceholder.layoutChain
.topToBottomOfView(iconCarousel, offset: 4)
.centerX()
groupNameTitleLab.layoutChain
.topToBottomOfView(typeNamePlaceholder, offset: 24)
.left(24)
groupNameInputView.layoutChain
.topToBottomOfView(groupNameTitleLab, offset: 10)
.edgesHorzontal(20)
.height(50)
groupNameTF.layoutChain
.edgesVertical()
.left(16)
.right(48)
groupContentTitleLab.layoutChain
.topToBottomOfView(groupNameInputView, offset: 18)
.left(24)
groupContentInputView.layoutChain
.topToBottomOfView(groupContentTitleLab, offset: 10)
.edgesHorzontal(20)
.height(96)
groupContentTV.layoutChain
.top(8)
.left(12)
.right(48)
.bottom(8)
placeholderLab.layoutChain
.topToView(groupContentTV, offset: 8)
.leftToView(groupContentTV, offset: 5)
tagTitleLab.layoutChain
.topToBottomOfView(groupContentInputView, offset: 18)
.left(24)
tagView.layoutChain
.topToBottomOfView(tagTitleLab, offset: 12)
.edgesHorzontal(20)
.height(28)
.bottom(24)
setupSheet()
}
func updateTagViewHeight() {
tagView.collectionViewLayout.invalidateLayout()
tagView.layoutIfNeeded()
let height = max(28, tagView.collectionViewLayout.collectionViewContentSize.height)
tagView.layoutChain.height(height)
}
func showAddTagSheet() {
addTagTF.text = ""
addTagCountLab.text = "0/\(tagMaxLength)"
sheetOverlay.isHidden = false
sheetOverlay.alpha = 0
sheetPanel.transform = CGAffineTransform(translationX: 0, y: 40)
UIView.animate(withDuration: 0.28, delay: 0, options: .curveEaseOut) {
self.sheetOverlay.alpha = 1
self.sheetPanel.transform = .identity
} completion: { _ in
self.addTagTF.becomeFirstResponder()
}
}
func dismissAddTagSheet() {
addTagTF.resignFirstResponder()
UIView.animate(withDuration: 0.22, delay: 0, options: .curveEaseIn) {
self.sheetOverlay.alpha = 0
self.sheetPanel.transform = CGAffineTransform(translationX: 0, y: 40)
} completion: { _ in
self.sheetOverlay.isHidden = true
self.addTagTF.text = ""
self.addTagCountLab.text = "0/\(self.tagMaxLength)"
}
}
// MARK: - Sheet
private func setupSheet() {
sheetOverlay.backgroundColor = UIColor.black.withAlphaComponent(0.45)
sheetOverlay.isHidden = true
addSubview(sheetOverlay)
sheetOverlay.layoutChain.edges()
sheetPanel.backgroundColor = .white
sheetPanel.layer.cornerRadius = 24
sheetPanel.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
sheetPanel.clipsToBounds = true
sheetOverlay.addSubview(sheetPanel)
sheetPanel.layoutChain.edgesHorzontal().bottom()
sheetHeaderView.backgroundColor = UIColor(hexStr: "#E8F7FF")
sheetPanel.addSubview(sheetHeaderView)
sheetHeaderView.layoutChain.edgesHorzontal().top().height(56)
sheetTitleLab.text = "添加标签"
sheetTitleLab.font = .systemFont(ofSize: 16, weight: .medium)
sheetTitleLab.textColor = ThemeManager.shared.color.titleAuxColor
sheetTitleLab.textAlignment = .center
sheetHeaderView.addSubview(sheetTitleLab)
sheetTitleLab.layoutChain.center()
sheetAddIcon.backgroundColor = UIColor(hexStr: "#D6F0FF")
sheetAddIcon.cornerRadius = 8
sheetAddIcon.contentMode = .center
let plusConfig = UIImage.SymbolConfiguration(pointSize: 13, weight: .medium)
sheetAddIcon.image = UIImage(systemName: "plus", withConfiguration: plusConfig)
sheetAddIcon.tintColor = UIColor(hexStr: "#16B3FF")
sheetPanel.addSubview(sheetAddIcon)
sheetAddIcon.layoutChain
.topToBottomOfView(sheetHeaderView, offset: 24)
.left(20)
.width(32)
.height(32)
addTagInputView.backgroundColor = UIColor(hexStr: "#F7F8FA")
addTagInputView.cornerRadius = 10
sheetPanel.addSubview(addTagInputView)
addTagClearBtn.setImage(UIImage(systemName: "xmark.circle.fill"), for: .normal)
addTagClearBtn.tintColor = UIColor(hexStr: "#FF5B5B")
sheetPanel.addSubview(addTagClearBtn)
addTagClearBtn.layoutChain
.centerY(sheetAddIcon)
.right(20)
.width(22)
.height(22)
addTagInputView.layoutChain
.centerY(sheetAddIcon)
.leftToRightOfView(sheetAddIcon, offset: 10)
.rightToLeftOfView(addTagClearBtn, offset: -10)
.height(40)
addTagInputView.addSubview(addTagTF)
addTagInputView.addSubview(addTagCountLab)
addTagCountLab.layoutChain
.centerY()
.right(12)
addTagTF.layoutChain
.edgesVertical()
.left(12)
.rightToLeftOfView(addTagCountLab, offset: -8)
sheetPanel.addSubview(sheetCancelBtn)
sheetPanel.addSubview(sheetConfirmBtn)
sheetCancelBtn.layoutChain
.topToBottomOfView(sheetAddIcon, offset: 28)
.left(24)
.height(44)
.bottom(20 + kSafeBottomMargin)
sheetConfirmBtn.layoutChain
.leftToRightOfView(sheetCancelBtn, offset: 12)
.right(24)
.bottomToView(sheetCancelBtn)
.heightToView(sheetCancelBtn)
.widthToView(sheetCancelBtn)
let tap = UITapGestureRecognizer(target: self, action: #selector(tapSheetMask))
sheetOverlay.addGestureRecognizer(tap)
}
@objc private func tapSheetMask(_ gesture: UITapGestureRecognizer) {
let point = gesture.location(in: sheetPanel)
if sheetPanel.bounds.contains(point) { return }
dismissAddTagSheet()
}
// MARK: - Views
lazy var navBgView: UIImageView = {
let iv = UIImageView()
iv.image = UIImage(named: "Common/navBar_bg_2")
iv.contentMode = .scaleAspectFill
return iv
}()
lazy var navView: BaseNavigationView = {
let nav = BaseNavigationView(title: "创建圈子")
return nav
}()
lazy var scrollView: UIScrollView = {
let view = UIScrollView()
view.backgroundColor = .clear
view.keyboardDismissMode = .onDrag
view.showsVerticalScrollIndicator = false
return view
}()
lazy var contentView: UIView = {
let view = UIView()
view.backgroundColor = .clear
return view
}()
lazy var iconCarousel: CreateGroupIconCarouselView = {
let view = CreateGroupIconCarouselView()
return view
}()
lazy var typeNamePlaceholder: UIView = {
let view = UIView()
view.backgroundColor = .white
view.cornerRadius = 10
view.isUserInteractionEnabled = false
view.addSubview(typeNameLab)
typeNameLab.layoutChain
.edgesVertical(10)
.edgesHorzontal(31)
return view
}()
lazy var typeNameLab: UILabel = {
let label = UILabel()
label.text = "未知"
label.font = .systemFont(ofSize: 16, weight: .bold)
label.textColor = UIColor(hexStr: "#3D3D3D")
return label
}()
lazy var groupNameTitleLab: UILabel = {
let label = UILabel()
label.text = "圈子名称"
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = ThemeManager.shared.color.titleAuxColor
return label
}()
lazy var groupNameInputView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.cornerRadius = 10
let icon = UIImageView()
icon.image = UIImage(named: "Group/edit")
icon.contentMode = .scaleAspectFit
view.addSubview(icon)
icon.layoutChain
.right(14)
.centerY()
.width(22)
.height(22)
return view
}()
lazy var groupNameTF: UITextField = {
let textField = UITextField(frame: .zero)
textField.font = UIFont.systemFont(ofSize: 14, weight: .medium)
textField.textColor = ThemeManager.shared.color.titleAuxColor
textField.placeholderColor(placeholder: "请输入圈子名称",
color: UIColor(hexStr: "#999999", alpha: 1.0))
return textField
}()
lazy var groupContentTitleLab: UILabel = {
let label = UILabel()
label.text = "圈子描述"
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = ThemeManager.shared.color.titleAuxColor
return label
}()
lazy var groupContentInputView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.cornerRadius = 10
let icon = UIImageView()
icon.image = UIImage(named: "Group/edit")
icon.contentMode = .scaleAspectFit
view.addSubview(icon)
icon.layoutChain
.right(14)
.bottom(14)
.width(22)
.height(22)
return view
}()
lazy var groupContentTV: UITextView = {
let textView = UITextView()
textView.backgroundColor = .clear
textView.font = .systemFont(ofSize: 14, weight: .medium)
textView.textColor = ThemeManager.shared.color.titleAuxColor
textView.isScrollEnabled = true
textView.textContainerInset = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
return textView
}()
lazy var placeholderLab: UILabel = {
let label = UILabel()
label.text = "请输入.."
label.textColor = ThemeManager.shared.color.contentColor
label.font = .systemFont(ofSize: 14, weight: .medium)
return label
}()
lazy var tagTitleLab: UILabel = {
let label = UILabel()
label.text = "选择标签"
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = ThemeManager.shared.color.titleAuxColor
return label
}()
lazy var tagView: UICollectionView = {
let layout = CreateGroupTagFlowLayout()
layout.minimumInteritemSpacing = 8
layout.minimumLineSpacing = 8
layout.sectionInset = .zero
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .clear
cv.isScrollEnabled = false
cv.register(TagCell.self)
return cv
}()
lazy var submitBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("创建", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = FontManager.boboBold(18)
btn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
btn.cornerRadius = 20
btn.isEnabled = false
btn.alpha = 0.5
return btn
}()
private let sheetOverlay = UIView()
private let sheetPanel = UIView()
private let sheetHeaderView = UIView()
private let sheetTitleLab = UILabel()
private let sheetAddIcon = UIImageView()
private let addTagInputView = UIView()
lazy var addTagTF: UITextField = {
let textField = UITextField(frame: .zero)
textField.font = .systemFont(ofSize: 14, weight: .medium)
textField.textColor = ThemeManager.shared.color.titleAuxColor
textField.placeholderColor(placeholder: "请输入标签",
color: UIColor(hexStr: "#999999", alpha: 1.0),
font: .systemFont(ofSize: 14, weight: .medium))
return textField
}()
lazy var addTagCountLab: UILabel = {
let label = UILabel()
label.text = "0/10"
label.font = .systemFont(ofSize: 12, weight: .regular)
label.textColor = ThemeManager.shared.color.contentColor
return label
}()
lazy var addTagClearBtn: UIButton = {
let btn = UIButton(type: .custom)
return btn
}()
lazy var sheetCancelBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("取消", for: .normal)
btn.setTitleColor(UIColor(hexStr: "#16B3FF"), for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 15, weight: .medium)
btn.backgroundColor = .white
btn.borderWidth = 1
btn.borderColor = UIColor(hexStr: "#16B3FF")
btn.cornerRadius = 22
return btn
}()
lazy var sheetConfirmBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setTitle("确定", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.titleLabel?.font = .systemFont(ofSize: 15, weight: .medium)
btn.setBackgroundImage(UIImage(named: "Common/gradient_bg"), for: .normal)
btn.cornerRadius = 22
btn.clipsToBounds = true
return btn
}()
override init(frame: CGRect) {
super.init(frame: .zero)
setupUI()
setupRx()
backgroundColor = UIColor(hexStr: "#FAFAFA")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - Icon Carousel
final class CreateGroupIconCarouselView: UIView {
var onSelectTemplate: ((GroupTemplateModel) -> Void)?
private var templates: [GroupTemplateModel] = []
private let itemSize: CGFloat = 80
private var lastWidth: CGFloat = 0
private var selectedItem = 0
func configure(templates: [GroupTemplateModel]) {
self.templates = templates
selectedItem = 0
collectionView.reloadData()
collectionView.layoutIfNeeded()
scrollToItem(selectedItem, animated: false)
if let first = templates.first {
onSelectTemplate?(first)
}
applyTransforms()
}
override init(frame: CGRect) {
super.init(frame: frame)
clipsToBounds = false
addSubview(collectionView)
collectionView.layoutChain.edges()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
guard bounds.width > 0 else { return }
if bounds.width != lastWidth {
lastWidth = bounds.width
let inset = max(0, (bounds.width - itemSize) / 2)
flowLayout.sectionInset = UIEdgeInsets(top: 25, left: inset, bottom: 25, right: inset)
collectionView.layoutIfNeeded()
scrollToItem(selectedItem, animated: false)
}
applyTransforms()
}
private func scrollToItem(_ item: Int, animated: Bool) {
guard templates.indices.contains(item) else { return }
collectionView.scrollToItem(at: IndexPath(item: item, section: 0),
at: .centeredHorizontally,
animated: animated)
}
private func selectItem(_ item: Int) {
guard templates.indices.contains(item) else { return }
selectedItem = item
onSelectTemplate?(templates[item])
}
private func nearestIndex() -> Int {
let centerX = collectionView.contentOffset.x + collectionView.bounds.width / 2
var nearest = 0
var minDist = CGFloat.greatestFiniteMagnitude
for i in templates.indices {
guard let attr = collectionView.layoutAttributesForItem(at: IndexPath(item: i, section: 0)) else { continue }
let dist = abs(attr.center.x - centerX)
if dist < minDist {
minDist = dist
nearest = i
}
}
return nearest
}
private func applyTransforms() {
let midX = collectionView.contentOffset.x + collectionView.bounds.width / 2
for cell in collectionView.visibleCells {
guard let attr = collectionView.layoutAttributesForItem(at: collectionView.indexPath(for: cell) ?? IndexPath()) else { continue }
let distance = abs(attr.center.x - midX)
let scale = max(0.72, 1.25 - distance / 180)
let alpha = max(0.45, 1 - distance / 220)
cell.transform = CGAffineTransform(scaleX: scale, y: scale)
cell.alpha = alpha
}
}
private lazy var flowLayout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
layout.scrollDirection = .horizontal
layout.itemSize = CGSize(width: itemSize, height: itemSize)
layout.minimumLineSpacing = 18
layout.minimumInteritemSpacing = 0
return layout
}()
private lazy var collectionView: UICollectionView = {
let cv = UICollectionView(frame: .zero, collectionViewLayout: flowLayout)
cv.backgroundColor = .clear
cv.showsHorizontalScrollIndicator = false
cv.decelerationRate = .fast
cv.clipsToBounds = false
cv.register(CreateGroupIconCell.self)
cv.dataSource = self
cv.delegate = self
return cv
}()
}
extension CreateGroupIconCarouselView: UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
templates.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(for: indexPath) as CreateGroupIconCell
cell.configure(templates[indexPath.item])
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
applyTransforms()
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectItem(indexPath.item)
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
applyTransforms()
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
let proposedCenter = targetContentOffset.pointee.x + scrollView.bounds.width / 2
var nearest = 0
var minDist = CGFloat.greatestFiniteMagnitude
for i in templates.indices {
guard let attr = collectionView.layoutAttributesForItem(at: IndexPath(item: i, section: 0)) else { continue }
let dist = abs(attr.center.x - proposedCenter)
if dist < minDist {
minDist = dist
nearest = i
}
}
if let attr = collectionView.layoutAttributesForItem(at: IndexPath(item: nearest, section: 0)) {
targetContentOffset.pointee.x = attr.center.x - scrollView.bounds.width / 2
}
selectItem(nearest)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
selectItem(nearestIndex())
applyTransforms()
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
applyTransforms()
}
}
final class CreateGroupIconCell: UICollectionViewCell {
private let imageView: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFill
view.clipsToBounds = true
view.cornerRadius = 18
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(imageView)
imageView.layoutChain.edges()
contentView.clipsToBounds = false
clipsToBounds = false
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func configure(_ template: GroupTemplateModel) {
let fallback = UIImage(named: "GroupIcon/\(template.type)") ?? UIImage(named: "GroupIcon/1")
imageView.setGroupIcon(url: template.icon, fallback: fallback)
}
override func prepareForReuse() {
super.prepareForReuse()
imageView.setGroupIcon(url: "", fallback: nil)
transform = .identity
alpha = 1
}
}
// MARK: - Tag Flow Layout
final class CreateGroupTagFlowLayout: UICollectionViewFlowLayout {
private var alignedAttrs: [IndexPath: UICollectionViewLayoutAttributes] = [:]
override func prepare() {
super.prepare()
alignedAttrs.removeAll()
let rect = CGRect(origin: .zero, size: collectionViewContentSize)
guard let original = super.layoutAttributesForElements(in: rect) else { return }
let attrs = original.map { $0.copy() as! UICollectionViewLayoutAttributes }
leftAlign(attrs)
attrs.forEach { alignedAttrs[$0.indexPath] = $0 }
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
alignedAttrs.values.filter { $0.frame.intersects(rect) }
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
alignedAttrs[indexPath] ?? super.layoutAttributesForItem(at: indexPath)
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
collectionView?.bounds.width != newBounds.width
}
private func leftAlign(_ attrs: [UICollectionViewLayoutAttributes]) {
var x = sectionInset.left
var y = sectionInset.top
var lineHeight: CGFloat = 0
let maxX = (collectionView?.bounds.width ?? 0) - sectionInset.right
for attr in attrs where attr.representedElementCategory == .cell {
if x > sectionInset.left && x + attr.frame.width > maxX {
x = sectionInset.left
y += lineHeight + minimumLineSpacing
lineHeight = 0
}
attr.frame.origin = CGPoint(x: x, y: y)
x += attr.frame.width + minimumInteritemSpacing
lineHeight = max(lineHeight, attr.frame.height)
}
}
}
// MARK: - TagCell
final class TagCell: UICollectionViewCell {
static let reuseId = "TagCell"
private let label: UILabel = {
let l = UILabel()
l.font = .systemFont(ofSize: 12, weight: .medium)
l.textAlignment = .center
return l
}()
private let plusView: UIImageView = {
let view = UIImageView()
let config = UIImage.SymbolConfiguration(pointSize: 13, weight: .medium)
view.image = UIImage(systemName: "plus", withConfiguration: config)
view.tintColor = .white
view.contentMode = .center
view.isHidden = true
return view
}()
private var isTagSelected = false
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(label)
contentView.addSubview(plusView)
label.layoutChain.edges()
plusView.layoutChain.edges()
contentView.layer.cornerRadius = 8
contentView.backgroundColor = UIColor(hexStr: "#F0F2F5")
updateStyle()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepareForReuse() {
super.prepareForReuse()
plusView.isHidden = true
label.isHidden = false
label.text = nil
}
static func preferredSize(for item: String) -> CGSize {
if item == CreateGroupViewModel.addTagToken {
return CGSize(width: 28, height: 28)
}
let font = UIFont.systemFont(ofSize: 12, weight: .medium)
let textW = ceil((item as NSString).size(withAttributes: [.font: font]).width)
return CGSize(width: max(52, textW + 20), height: 28)
}
func configure(_ text: String, isSelected: Bool) {
plusView.isHidden = true
label.isHidden = false
label.text = text
isTagSelected = isSelected
contentView.layer.cornerRadius = 8
updateStyle()
}
func configureAsAdd() {
plusView.isHidden = false
label.isHidden = true
label.text = nil
isTagSelected = false
contentView.backgroundColor = UIColor(hexStr: "#16B3FF")
contentView.layer.borderWidth = 0
contentView.layer.cornerRadius = 8
}
func toggleSelection() {
isTagSelected.toggle()
updateStyle()
}
private func updateStyle() {
if isTagSelected {
label.textColor = UIColor(hexStr: "#16B3FF")
contentView.backgroundColor = UIColor(hexStr: "#E3F6FF")
contentView.layer.borderWidth = 1
contentView.layer.borderColor = UIColor(hexStr: "#16B3FF").cgColor
} else {
label.textColor = ThemeManager.shared.color.titleAuxColor
contentView.backgroundColor = UIColor(hexStr: "#F0F2F5")
contentView.layer.borderWidth = 0
}
}
}