// // MoodStatusView.swift // QuickLocation // import UIKit import Lottie final class MoodStatusView: UIView { var onBack: (() -> Void)? var onSave: (() -> Void)? var onSelectMood: ((Int) -> Void)? private let previewImageView = UIImageView(image: UIImage(named: "Mine/mood_preview_bg")) private let previewLottieView: LottieAnimationView = { let view = LottieAnimationView() view.contentMode = .scaleAspectFit view.loopMode = .loop view.backgroundBehavior = .pauseAndRestore return view }() lazy var navView = BaseNavigationView(title: "") private let gridCard = UIView() private let collectionLayout = CollectionHFlowLayout() private lazy var collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionLayout) private let pageControl = UIPageControl() let saveBtn = UIButton(type: .custom) private var items: [MoodStatusItem] = [] private var selectedMoodIndex: Int? private var lastLayoutWidth: CGFloat = 0 private var previewAnimationName: String? override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .white setupUI() updateGridLayout(for: bounds.width) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { super.layoutSubviews() updateGridLayout(for: bounds.width) updatePreviewLottieFrame() } func configure(items: [MoodStatusItem], selectedMoodIndex: Int?) { self.items = items self.selectedMoodIndex = selectedMoodIndex collectionView.reloadData() updatePreview(with: selectedMoodIndex) } func updateSelection(moodIndex: Int?) { selectedMoodIndex = moodIndex collectionView.reloadData() updatePreview(with: moodIndex) } func setSaving(_ saving: Bool) { saveBtn.isEnabled = !saving saveBtn.alpha = saving ? 0.65 : 1 collectionView.isUserInteractionEnabled = !saving } private func setupUI() { addSubview(navView) navView.layoutChain.edges(excludingEdge: .bottom).height(kNaviHeight) previewImageView.contentMode = .scaleAspectFit previewImageView.clipsToBounds = true addSubview(previewImageView) previewImageView.layoutChain .topToBottomOfView(navView, offset: 8) .edgesHorzontal() .heightToWidth(264.0 / 375.0) previewImageView.addSubview(previewLottieView) 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 = 20 saveBtn.clipsToBounds = true saveBtn.addTarget(self, action: #selector(handleSave), for: .touchUpInside) addSubview(saveBtn) saveBtn.layoutChain .edgesHorzontal(30) .bottom(kSafeBottomMargin + 13) .height(56) gridCard.backgroundColor = UIColor(hexStr: "#F5F6F8") gridCard.layer.cornerRadius = 40 addSubview(gridCard) gridCard.layoutChain .topToBottomOfView(previewImageView, offset: 25) .edgesHorzontal(15) .height(273) collectionLayout.rows = 3 collectionLayout.colums = 4 collectionView.backgroundColor = .clear collectionView.isPagingEnabled = true collectionView.bounces = false collectionView.showsHorizontalScrollIndicator = false collectionView.dataSource = self collectionView.delegate = self collectionView.register(MoodStatusCell.self) gridCard.addSubview(collectionView) collectionView.translatesAutoresizingMaskIntoConstraints = false pageControl.numberOfPages = 3 pageControl.currentPage = 0 pageControl.isUserInteractionEnabled = false pageControl.currentPageIndicatorTintColor = UIColor(hexStr: "#16B3FF") pageControl.pageIndicatorTintColor = UIColor(hexStr: "#7AD6FF", alpha: 0.4) gridCard.addSubview(pageControl) pageControl.layoutChain .centerX() .bottom(8) .height(16) collectionView.layoutChain .edges(excludingEdge: .bottom) .bottomToTopOfView(pageControl) } private func updateGridLayout(for width: CGFloat) { let cardSideInset: CGFloat = 15 let cardWidth = gridCard.bounds.width > 1 ? gridCard.bounds.width : max(width - cardSideInset * 2, 0) guard cardWidth > 0, abs(cardWidth - lastLayoutWidth) > 0.5 else { return } lastLayoutWidth = cardWidth let columns: CGFloat = 4 let itemWidth: CGFloat = 60 let hSpacing: CGFloat = 20 let rowWidth = columns * itemWidth + (columns - 1) * hSpacing let sideInset = max((cardWidth - rowWidth) / 2, 0) collectionLayout.itemSize = CGSize(width: itemWidth, height: 60) collectionLayout.hSpacing = hSpacing collectionLayout.vSpacing = 10 collectionLayout.sectionInset = UIEdgeInsets(top: 23, left: sideInset, bottom: 0, right: sideInset) collectionLayout.invalidateLayout() } private func updatePreviewLottieFrame() { let bounds = previewImageView.bounds guard bounds.width > 0, bounds.height > 0 else { return } previewLottieView.frame = CGRect( x: (bounds.width - 80) / 2, y: 30, width: 80, height: 80 ) } private func updatePreview(with moodIndex: Int?) { guard let moodIndex, let item = MoodStatusCatalog.item(moodIndex: moodIndex), let path = item.jsonPath else { previewAnimationName = nil previewLottieView.stop() previewLottieView.animation = nil return } if previewAnimationName == item.jsonName, previewLottieView.animation != nil { if !previewLottieView.isAnimationPlaying { previewLottieView.play() } return } previewAnimationName = item.jsonName previewLottieView.stop() previewLottieView.animation = LottieAnimation.filepath(path) previewLottieView.currentProgress = 0 previewLottieView.play() } @objc private func handleBack() { onBack?() } @objc private func handleSave() { onSave?() } } extension MoodStatusView: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { items.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell: MoodStatusCell = collectionView.dequeueReusableCell(for: indexPath) let item = items[indexPath.item] cell.configure(image: item.pngImage, selected: item.moodIndex == selectedMoodIndex) return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { onSelectMood?(items[indexPath.item].moodIndex) } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { guard scrollView.bounds.width > 0 else { return } pageControl.currentPage = Int(round(scrollView.contentOffset.x / scrollView.bounds.width)) } } final class MoodStatusCell: UICollectionViewCell { private let imageView = UIImageView() override init(frame: CGRect) { super.init(frame: frame) contentView.backgroundColor = .white contentView.layer.cornerRadius = 16 contentView.layer.borderWidth = 2 contentView.layer.borderColor = UIColor.clear.cgColor contentView.clipsToBounds = true imageView.contentMode = .scaleAspectFit contentView.addSubview(imageView) imageView.layoutChain.edges(all: 5) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func configure(image: UIImage?, selected: Bool) { imageView.image = image contentView.layer.borderColor = selected ? UIColor(hexStr: "#16B3FF").cgColor : UIColor.clear.cgColor contentView.backgroundColor = selected ? UIColor(hexStr: "#F3FBFF") : .white } }