jsdw_ios/QuickLocation/Section/Mine/MyProfile/AvatarIconListView.swift

337 lines
10 KiB
Swift

//
// AvatarIconListView.swift
// QuickLocation
//
// Created by on 2026/6/11.
//
import UIKit
class AvatarIconListView: UIView {
var selectedIndex: Int = 1 {
didSet {
updatePreview()
iconCollectionView.reloadData()
}
}
private var currentHeadPic: String = ""
var selectedHeadPic: String {
if selectedIndex > 0 {
return "\(selectedIndex)"
}
return currentHeadPic
}
let saveBtn = UIButton(type: .custom)
var onBadgeTap: (() -> Void)?
var onIconSelected: (() -> Void)?
private var iconImgList: [UIImage] = []
private let collectionLayout = CollectionHFlowLayout()
private var lastLayoutWidth: CGFloat = 0
private var collectionHeightConstraint: NSLayoutConstraint?
private var didScrollToInitialPage = false
private let columns = 4
private let rows = 2
private let hSpacing: CGFloat = 12
private let vSpacing: CGFloat = 12
private let cardSideInset: CGFloat = 18
override init(frame: CGRect) {
super.init(frame: .zero)
backgroundColor = .white
for i in 1...16 {
if let img = UIImage(named: "UserIcon/\(i)") {
iconImgList.append(img)
}
}
setupUI()
updatePreview()
updatePageControl()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
updateGridLayout(for: bounds.width)
scrollToSelectedPageIfNeeded()
}
private func setupUI() {
addSubview(navView)
navView.layoutChain.edges(excludingEdge: .bottom).height(kNaviHeight)
addSubview(previewWrap)
previewWrap.addSubview(selectedIconView)
addSubview(badgeView)
previewWrap.isUserInteractionEnabled = false
addSubview(gridCard)
gridCard.addSubview(titleLab)
gridCard.addSubview(iconCollectionView)
gridCard.addSubview(pageControl)
addSubview(saveBtn)
previewWrap.layoutChain
.topToBottomOfView(navView, offset: 36)
.centerX()
.width(120)
.height(120)
selectedIconView.layoutChain.edges(all: 6)
badgeView.layoutChain
.rightToView(previewWrap, offset: 4)
.bottomToView(previewWrap, offset: 4)
.width(40)
.height(40)
saveBtn.setTitle("保存", for: .normal)
saveBtn.setTitleColor(.white, for: .normal)
saveBtn.titleLabel?.font = FontManager.boboBold(18)
saveBtn.setBackgroundImage(UIImage(named: "Common/button_bg_2"), for: .normal)
saveBtn.layer.cornerRadius = 28
saveBtn.clipsToBounds = true
saveBtn.isHidden = true
saveBtn.layoutChain
.edgesHorzontal(30)
.bottom(kSafeBottomMargin + 13)
.height(56)
gridCard.layoutChain
.topToBottomOfView(previewWrap, offset: 48)
.edgesHorzontal(15)
titleLab.layoutChain
.top(20)
.centerX()
iconCollectionView.layoutChain
.topToBottomOfView(titleLab, offset: 16)
.edgesHorzontal()
collectionHeightConstraint = iconCollectionView.heightAnchor.constraint(equalToConstant: 148)
collectionHeightConstraint?.isActive = true
pageControl.layoutChain
.topToBottomOfView(iconCollectionView, offset: 8)
.centerX()
.bottom(10)
.height(16)
collectionLayout.rows = rows
collectionLayout.colums = columns
collectionLayout.hSpacing = hSpacing
collectionLayout.vSpacing = vSpacing
bringSubviewToFront(badgeView)
}
func configure(headPic: String) {
currentHeadPic = headPic
if HeadPic.isRemote(headPic) {
selectedIndex = 0
selectedIconView.setHeadPic(headPic)
} else {
selectedIndex = max(headPic.integer, 1)
}
}
func setPreviewImage(_ image: UIImage) {
selectedIndex = 0
selectedIconView.image = image
}
@objc private func handleBadgeTap() {
onBadgeTap?()
}
private func updatePreview() {
guard selectedIndex > 0 else { return }
selectedIconView.image = UIImage(named: "UserIcon/\(selectedIndex)")
}
private func updatePageControl() {
let pageSize = columns * rows
let pageCount = max(Int(ceil(Double(iconImgList.count) / Double(pageSize))), 1)
pageControl.numberOfPages = pageCount
pageControl.isHidden = pageCount <= 1
}
private func updateGridLayout(for width: CGFloat) {
let cardWidth = gridCard.bounds.width > 1
? gridCard.bounds.width
: max(width - 30, 0)
guard cardWidth > 0, abs(cardWidth - lastLayoutWidth) > 0.5 else { return }
lastLayoutWidth = cardWidth
let available = cardWidth - cardSideInset * 2
let itemW = floor((available - hSpacing * CGFloat(columns - 1)) / CGFloat(columns))
collectionLayout.itemSize = CGSize(width: itemW, height: itemW)
collectionLayout.sectionInset = UIEdgeInsets(top: 0, left: cardSideInset, bottom: 0, right: cardSideInset)
collectionLayout.invalidateLayout()
let collectionHeight = itemW * CGFloat(rows) + vSpacing * CGFloat(rows - 1)
collectionHeightConstraint?.constant = collectionHeight
}
private func scrollToSelectedPageIfNeeded() {
guard !didScrollToInitialPage,
iconCollectionView.bounds.width > 1,
collectionLayout.itemSize.width > 0 else { return }
didScrollToInitialPage = true
let pageSize = columns * rows
let page = max((selectedIndex - 1) / pageSize, 0)
pageControl.currentPage = page
iconCollectionView.setContentOffset(
CGPoint(x: CGFloat(page) * iconCollectionView.bounds.width, y: 0),
animated: false
)
}
private func updateCurrentPage() {
let width = iconCollectionView.bounds.width
guard width > 0 else { return }
pageControl.currentPage = Int(round(iconCollectionView.contentOffset.x / width))
}
lazy var navView: BaseNavigationView = {
BaseNavigationView(title: "")
}()
private lazy var previewWrap: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 32
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOpacity = 0.08
view.layer.shadowOffset = CGSize(width: 0, height: 6)
view.layer.shadowRadius = 14
return view
}()
lazy var selectedIconView: UIImageView = {
let view = UIImageView()
view.contentMode = .scaleAspectFill
view.clipsToBounds = true
view.layer.cornerRadius = 26
return view
}()
private lazy var badgeView: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(UIImage(named: "Mine/avatar_album_badge"), for: .normal)
btn.imageView?.contentMode = .scaleAspectFit
btn.extendEdgeInsets = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
btn.addTarget(self, action: #selector(handleBadgeTap), for: .touchUpInside)
return btn
}()
private lazy var gridCard: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hexStr: "#F5F6F8")
view.layer.cornerRadius = 40
return view
}()
private lazy var titleLab: UILabel = {
let label = UILabel()
label.text = "选择头像"
label.font = .systemFont(ofSize: 16, weight: .bold)
label.textColor = ThemeManager.shared.color.titleAuxColor
return label
}()
private lazy var iconCollectionView: UICollectionView = {
let cv = UICollectionView(frame: .zero, collectionViewLayout: collectionLayout)
cv.backgroundColor = .clear
cv.isPagingEnabled = true
cv.bounces = false
cv.showsHorizontalScrollIndicator = false
cv.contentInsetAdjustmentBehavior = .never
cv.register(AvatarIconCell.self)
cv.delegate = self
cv.dataSource = self
return cv
}()
private lazy var pageControl: UIPageControl = {
let control = UIPageControl()
control.currentPage = 0
control.isUserInteractionEnabled = false
control.currentPageIndicatorTintColor = UIColor(hexStr: "#16B3FF")
control.pageIndicatorTintColor = UIColor(hexStr: "#7AD6FF", alpha: 0.4)
return control
}()
}
extension AvatarIconListView: UICollectionViewDelegate, UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
iconImgList.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(for: indexPath) as AvatarIconCell
cell.configure(img: iconImgList[indexPath.item], isSelected: selectedIndex == indexPath.item + 1)
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
selectedIndex = indexPath.item + 1
onIconSelected?()
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
updateCurrentPage()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
updateCurrentPage()
}
}
}
final class AvatarIconCell: UICollectionViewCell {
func configure(img: UIImage, isSelected: Bool) {
iconImgView.image = img
contentView.layer.borderColor = isSelected
? UIColor(hexStr: "#16B3FF").cgColor
: UIColor.clear.cgColor
}
private func setupUI() {
contentView.backgroundColor = .clear
contentView.layer.cornerRadius = 18
contentView.layer.borderWidth = 2
contentView.clipsToBounds = true
contentView.addSubview(iconImgView)
iconImgView.layoutChain.edges(all: 2)
}
private lazy var iconImgView: UIImageView = {
let view = UIImageView()
view.backgroundColor = .clear
view.contentMode = .scaleAspectFill
view.clipsToBounds = true
view.layer.cornerRadius = 16
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}