From a3b1677409acd4ce50a8f3a67c3a84a2dcb4f2e5 Mon Sep 17 00:00:00 2001 From: mh <729263080@qq.com> Date: Fri, 17 Oct 2025 14:13:23 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=92=E8=89=B2=E6=A8=A1=E5=9D=97=E6=A0=87?= =?UTF-8?q?=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UI/BaseTopView/CLTopHeaderView.swift | 5 - .../Components/UI/Buttons/ChipButtons.swift | 55 +++++++++ .../HorizontalScrollTagsView.swift | 7 +- .../Modules/Roles/Model/CLRoleTagsModel.swift | 13 ++ .../Roles/View/CLRoleCollectionCell.swift | 2 +- .../Modules/Roles/View/CLRoleTagsView.swift | 112 ++++++++++++++++++ .../Roles/View/RoleTagCollectionCell.swift | 64 ++++++++++ .../Roles/View/RolesRootPageView.swift | 93 ++++++++++++++- .../Modules/Roles/View/TagFlowLayout.swift | 80 +++++++++++++ 9 files changed, 420 insertions(+), 11 deletions(-) create mode 100644 Visual_Novel_iOS/Src/Modules/Roles/Model/CLRoleTagsModel.swift create mode 100644 Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleTagsView.swift create mode 100644 Visual_Novel_iOS/Src/Modules/Roles/View/RoleTagCollectionCell.swift create mode 100644 Visual_Novel_iOS/Src/Modules/Roles/View/TagFlowLayout.swift diff --git a/Visual_Novel_iOS/Src/Components/UI/BaseTopView/CLTopHeaderView.swift b/Visual_Novel_iOS/Src/Components/UI/BaseTopView/CLTopHeaderView.swift index 3beed59..3a23c8f 100644 --- a/Visual_Novel_iOS/Src/Components/UI/BaseTopView/CLTopHeaderView.swift +++ b/Visual_Novel_iOS/Src/Components/UI/BaseTopView/CLTopHeaderView.swift @@ -121,19 +121,14 @@ class CLTopHeaderView: UIView { // MARK: Action @objc func searchImgViewClicked() { - print("111111") -// UIWindow.getTopViewController()?.navigationController?.pushViewController(TestEntrancesController(), animated: true) - subject.send(.search) } @objc func calendarImgViewClicked() { - print("2222") subject.send(.check) } @objc func discordImgViewClicked() { - print("333") subject.send(.discord) } diff --git a/Visual_Novel_iOS/Src/Components/UI/Buttons/ChipButtons.swift b/Visual_Novel_iOS/Src/Components/UI/Buttons/ChipButtons.swift index 06cd7f7..2d6564c 100644 --- a/Visual_Novel_iOS/Src/Components/UI/Buttons/ChipButtons.swift +++ b/Visual_Novel_iOS/Src/Components/UI/Buttons/ChipButtons.swift @@ -188,6 +188,61 @@ class EPChipFilterButton: EPChipButton { } } +class EPRoleTagsFilterButton: EPChipButton { + override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = UIColor(hex: "#BAC5D2") // EPSystemToken.color(.surfaceElementNormal) + layer.borderWidth = 0 + + addSubview(textLabel) + textLabel.snp.makeConstraints { make in + make.edges.equalTo(self).inset(paddingInsets).priority(.high) + make.height.equalTo(chipButtonHeight) + } + } + + override func setupTheme() { + super.setupTheme() + layer.borderColor = UIColor(hex: "#0065FF").cgColor // EPSystemToken.color(.primaryVariantNormal).cgColor + } + + override var paddingInsets: UIEdgeInsets { + UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + } + + override var intrinsicContentSize: CGSize{ + return CGSize(width: textLabel.size.width + 32, height: chipButtonHeight) + } + + override var isHighlighted: Bool { + didSet { + backgroundColor = isSelected + ? (isHighlighted ? UIColor(hex: "#BAC5D2") : UIColor(hex: "#0065FF")) + : (isHighlighted ? UIColor(hex: "#0065FF") : UIColor(hex: "#BAC5D2")) + } + } + + override var isSelected: Bool { + didSet { + backgroundColor = isSelected + ? UIColor(hex: "#0065FF") + : UIColor(hex: "#BAC5D2") + layer.borderWidth = isSelected ? 1 : 0 + } + } + + override var isEnabled: Bool { + didSet { + textLabel.textColor = isEnabled + ? UIColor.white + : UIColor(hex: "#E5F1FF") + backgroundColor = isEnabled + ? UIColor.c.csen + : UIColor.c.csed + } + } +} + class EPChipCenterIconButton: EPChipButton { override init(frame: CGRect) { super.init(frame: frame) diff --git a/Visual_Novel_iOS/Src/Components/UI/CustomViews/HorizontalScrollTagsView.swift b/Visual_Novel_iOS/Src/Components/UI/CustomViews/HorizontalScrollTagsView.swift index 8b3fbaf..ba76c24 100644 --- a/Visual_Novel_iOS/Src/Components/UI/CustomViews/HorizontalScrollTagsView.swift +++ b/Visual_Novel_iOS/Src/Components/UI/CustomViews/HorizontalScrollTagsView.swift @@ -45,7 +45,7 @@ class HorizontalScrollTagsView: UIView { private var stackView: UIStackView! /// 标签按钮数组 - private var tagButtons: [EPChipFilterButton] = [] + private var tagButtons: [EPRoleTagsFilterButton] = [] /// 标签数据源 private var tags: [String] = [] @@ -163,7 +163,7 @@ class HorizontalScrollTagsView: UIView { // 创建新按钮 for (index, tag) in tags.enumerated() { - let button = EPChipFilterButton() + let button = EPRoleTagsFilterButton() button.text = tag button.tag = index button.addTarget(self, action: #selector(tagButtonTapped(_:)), for: .touchUpInside) @@ -173,7 +173,7 @@ class HorizontalScrollTagsView: UIView { } } - @objc private func tagButtonTapped(_ sender: EPChipFilterButton) { + @objc private func tagButtonTapped(_ sender: EPRoleTagsFilterButton) { let index = sender.tag // 切换选中状态 @@ -184,7 +184,6 @@ class HorizontalScrollTagsView: UIView { selectedIndices.insert(index) sender.isSelected = true } - // 通知选择变更 notifySelectionChanged() } diff --git a/Visual_Novel_iOS/Src/Modules/Roles/Model/CLRoleTagsModel.swift b/Visual_Novel_iOS/Src/Modules/Roles/Model/CLRoleTagsModel.swift new file mode 100644 index 0000000..44e6634 --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Roles/Model/CLRoleTagsModel.swift @@ -0,0 +1,13 @@ +// +// CLRoleTagsModel.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/17. +// + +import Foundation + +struct CLRoleTagsModel: Codable { + var name: String = "" + var isSelected: Bool = false +} diff --git a/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleCollectionCell.swift b/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleCollectionCell.swift index 668f81d..3e09780 100644 --- a/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleCollectionCell.swift +++ b/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleCollectionCell.swift @@ -129,7 +129,7 @@ class CLRoleCollectionCell: UICollectionViewCell { contentView.addSubview(remindLab) coverImgView.snp.makeConstraints { make in - make.width.equalTo(cellWidth) + make.width.equalTo(cellWidth).priority(999) make.height.equalTo(coverImgView.snp.width).multipliedBy(4.0 / 3.0) make.top.left.right.equalToSuperview() } diff --git a/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleTagsView.swift b/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleTagsView.swift new file mode 100644 index 0000000..109e942 --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Roles/View/CLRoleTagsView.swift @@ -0,0 +1,112 @@ +// +// CLRoleTagsView.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/16. +// + +import UIKit + +class CLRoleTagsView: UIView { + +// let tags = ["#浪漫", "#温柔", "#多愁善感", "#深情", "#this is good", "#沙瓦迪", "#科技哈", "#等好a", "#a"] + var tagModels: [CLRoleTagsModel] = [ + CLRoleTagsModel(name: "#浪漫浪漫浪漫浪漫浪漫浪漫浪漫浪漫浪漫漫浪漫浪漫浪漫漫浪漫浪漫浪漫漫浪漫浪漫浪漫", isSelected: false), + CLRoleTagsModel(name: "#温柔", isSelected: false), + CLRoleTagsModel(name: "#多愁善感", isSelected: false), + CLRoleTagsModel(name: "#this is good", isSelected: false), + CLRoleTagsModel(name: "#沙瓦迪", isSelected: false), + CLRoleTagsModel(name: "#科技哈", isSelected: false), + CLRoleTagsModel(name: "#等好a", isSelected: false), + CLRoleTagsModel(name: "#a", isSelected: false), + CLRoleTagsModel(name: "#浪漫", isSelected: false), + ] + + lazy var collectionView: AutoHeightCollectionView = { + let layout = TagFlowLayout() + layout.interItemSpacing = 10 // 横向间距 + layout.lineSpacing = 8 + layout.maxLineWidth = UIScreen.width - 20.0 + layout.delegate = self + + let collectionView = AutoHeightCollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.delegate = self + collectionView.dataSource = self + collectionView.backgroundColor = .clear + collectionView.setContentHuggingPriority(.required, for: .vertical) + collectionView.showsVerticalScrollIndicator = false + collectionView.showsHorizontalScrollIndicator = false + collectionView.register(RoleTagCollectionCell.self, forCellWithReuseIdentifier: "RoleTagCollectionCell") + return collectionView + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + setupSubviews() +// setupDatas() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: Subviews + func setupSubviews() { + + addSubview(collectionView) + + collectionView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + // MARK: data + private func setupDatas() { + + } +} + +extension CLRoleTagsView: UICollectionViewDataSource, UICollectionViewDelegate, TagFlowLayoutDelegate { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + tagModels.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell: RoleTagCollectionCell = collectionView.dequeueReusableCell(withReuseIdentifier: "RoleTagCollectionCell", for: indexPath) as! RoleTagCollectionCell + + cell.setupData(model: tagModels[indexPath.item]) + + return cell + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + print("点击了标签:\(tagModels[indexPath.item].name)") + tagModels[indexPath.item].isSelected.toggle() +// collectionView.reloadItems(at: [indexPath]) + collectionView.reloadData() + + } + + // 根据文字计算 size + func collectionView(_ collectionView: UICollectionView, + layout: TagFlowLayout, + sizeForItemAt indexPath: IndexPath) -> CGSize { + let text = tagModels[indexPath.item].name + let font = UIFont.boldSystemFont(ofSize: 12) + let w = text.size(withAttributes: [.font: font]).width + 20 // 左右 10 pt padding + return CGSize(width: min(w, layout.maxItemWidth), height: 23) + } + +} + + +final class AutoHeightCollectionView: UICollectionView { + override var contentSize: CGSize { + didSet { invalidateIntrinsicContentSize() } + } + override var intrinsicContentSize: CGSize { + CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height) + } +} diff --git a/Visual_Novel_iOS/Src/Modules/Roles/View/RoleTagCollectionCell.swift b/Visual_Novel_iOS/Src/Modules/Roles/View/RoleTagCollectionCell.swift new file mode 100644 index 0000000..e316f33 --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Roles/View/RoleTagCollectionCell.swift @@ -0,0 +1,64 @@ +// +// RoleTagCollectionCell.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/16. +// + +import UIKit + +class RoleTagCollectionCell: UICollectionViewCell { + + lazy var containerView: UIView = { + let view = UIView() + view.backgroundColor = UIColor(hex: "#BAC5D2") + view.cornerRadius = 11.5 + return view + }() + + lazy var titleLab: UILabel = { + let lab = UILabel() + lab.font = UIFont.boldSystemFont(ofSize: 12) + lab.textColor = UIColor(hex: "#E5F1FF") + lab.textAlignment = .center + return lab + }() + + override init(frame: CGRect) { + super.init(frame: frame) + + self.contentView.clipsToBounds = true + setupViews() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupViews() { + + contentView.addSubview(containerView) + containerView.addSubview(titleLab) + + containerView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + titleLab.snp.makeConstraints { make in + make.centerY.equalToSuperview() + make.left.right.equalToSuperview().inset(7) + } + } + + func setupData(model: CLRoleTagsModel) { + titleLab.text = model.name + if model.isSelected { + containerView.backgroundColor = UIColor(hex: "#0065FF") + titleLab.textColor = .white + } else { + containerView.backgroundColor = UIColor(hex: "#BAC5D2") + titleLab.textColor = UIColor(hex: "#E5F1FF") + } + } + +} diff --git a/Visual_Novel_iOS/Src/Modules/Roles/View/RolesRootPageView.swift b/Visual_Novel_iOS/Src/Modules/Roles/View/RolesRootPageView.swift index 553da7e..8a66cf2 100644 --- a/Visual_Novel_iOS/Src/Modules/Roles/View/RolesRootPageView.swift +++ b/Visual_Novel_iOS/Src/Modules/Roles/View/RolesRootPageView.swift @@ -14,6 +14,10 @@ class RolesRootPageView: CLContainer { let itemWidth: CGFloat = (UIScreen.width - 30.0) / 2.0 var jumpPublisher: AnyPublisher { topView.jumpPublisher } + +// private lazy var pagingView = JXPagingListRefreshView(delegate: self) +// lazy var headerView: + let data: [String] = [ "Once a prodigy, Lin Feng had his cultivation shattered and was cast out Once a prodigy, Lin Feng had his cultivation shattered and was cast", @@ -46,6 +50,11 @@ class RolesRootPageView: CLContainer { return view }() + lazy var tagsView: CLRoleTagsView = { + let tagsView = CLRoleTagsView() + return tagsView + }() + lazy var collectionView: UICollectionView = { let layout = CLWaterfallLayout() layout.columnCount = 2 @@ -65,6 +74,7 @@ class RolesRootPageView: CLContainer { super.init(frame: frame) setupViews() + setupDatas() } required init?(coder: NSCoder) { @@ -73,6 +83,8 @@ class RolesRootPageView: CLContainer { private func setupViews() { addSubview(self.topView) +// addSubview(tagsChooseView) + addSubview(tagsView) addSubview(collectionView) topView.snp.makeConstraints { make in @@ -80,11 +92,20 @@ class RolesRootPageView: CLContainer { make.height.equalTo(UIDevice().navHeight) } + tagsView.snp.makeConstraints { make in + make.right.left.equalToSuperview() + make.top.equalTo(topView.snp.bottom).offset(0) + } + collectionView.snp.makeConstraints { make in make.bottom.left.right.equalToSuperview() - make.top.equalTo(topView.snp.bottom).offset(20) + make.top.equalTo(tagsView.snp.bottom).offset(0) } } + + private func setupDatas() { + + } } extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSource, WaterfallLayoutDelegate { @@ -100,6 +121,10 @@ extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSourc return cell } + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + + } + func collectionView(_ collectionView: UICollectionView, layout: CLWaterfallLayout, heightForItemAt indexPath: IndexPath) -> CGFloat { @@ -131,3 +156,69 @@ extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSourc return coverH + 10.0 + textH + 5.0 + remindH } } + +//extension RolesRootPageView: JXPagingViewDelegate { +// func tableHeaderViewHeight(in _: JXPagingView) -> Int { +// return 100 +// } +// +// func tableHeaderView(in _: JXPagingView) -> UIView { +// return headerView +// } +// +// func heightForPinSectionHeader(in _: JXPagingView) -> Int { +// return headerPinHeadHeight +// } +// +// func viewForPinSectionHeader(in _: JXPagingView) -> UIView { +// return pinHeaderView +// } +// +// func numberOfLists(in _: JXPagingView) -> Int { +// return controllers.count +// } +// +// func pagingView(_: JXPagingView, initListAtIndex index: Int) -> JXPagingViewListViewDelegate { +// let vc = controllers[index] +// return vc +// } +// +// func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) { +// if segmentedView == pinHeaderView.segmentedView { +// refreshSubTagsSegmentView() +// } +//// else if segmentedView == pinHeaderView.subSegementedView { +//// let selectTag = tagsNodes[index] +//// if let currentVc = controllers[pinHeaderView.segmentedView.selectedIndex] as? DiscoverRolesGridController { +//// currentVc.tryLoad(code: selectTag.code!) +//// } +//// } +// } +// +// func mainTableViewDidScroll(_ scrollView: UIScrollView) { +// NaviAlphaHandle.changeNaviViewsAlpha(scrollView: scrollView, alphaViews: [bgBdView, navigationView?.bgView], oppositeViews: []) +// } +//} + +//extension DiscoverRootPageView: JXPagingMainTableViewGestureDelegate { +// func mainTableViewGestureRecognizer( +// _ gestureRecognizer: UIGestureRecognizer, +// shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { +// // 如果是 UICollectionView 的手势,先判断滚动方向 +// if let panGesture = otherGestureRecognizer as? UIPanGestureRecognizer, +// let otherView = otherGestureRecognizer.view, +// otherView is UICollectionView { +// let velocity = panGesture.velocity(in: otherView) +// // 横向滚动时禁止 +// if abs(velocity.x) > abs(velocity.y) { +// return false +// } +// } +// // 禁止segmentedView左右滑动的时候,上下和左右都可以滚动 +//// if otherGestureRecognizer == segmentedView.collectionView.panGestureRecognizer { +//// return false +//// } +// return gestureRecognizer.isKind(of: UIPanGestureRecognizer.self) +// && otherGestureRecognizer.isKind(of: UIPanGestureRecognizer.self) +// } +//} diff --git a/Visual_Novel_iOS/Src/Modules/Roles/View/TagFlowLayout.swift b/Visual_Novel_iOS/Src/Modules/Roles/View/TagFlowLayout.swift new file mode 100644 index 0000000..0b2cbb4 --- /dev/null +++ b/Visual_Novel_iOS/Src/Modules/Roles/View/TagFlowLayout.swift @@ -0,0 +1,80 @@ +// +// TagFlowLayout.swift +// Visual_Novel_iOS +// +// Created by mh on 2025/10/16. +// + +import UIKit + +final class TagFlowLayout: UICollectionViewLayout { + // MARK: - 可外部配置 + var maxLineWidth: CGFloat = UIScreen.width - 20.0 + var maxItemWidth: CGFloat = (UIScreen.width - 50.0) / 2.0 + var interItemSpacing: CGFloat = 8 + var lineSpacing: CGFloat = 6 + var sectionInset: UIEdgeInsets = .init(top: 12, left: 20, bottom: 12, right: 10) + var delegate: TagFlowLayoutDelegate? = nil + + // MARK: - 私有缓存 + private var cache: [UICollectionViewLayoutAttributes] = [] + private var contentHeight: CGFloat = 0 + + override var collectionViewContentSize: CGSize { + CGSize(width: maxLineWidth, height: contentHeight) + } + + override func prepare() { + super.prepare() + guard cache.isEmpty, let cv = collectionView else { return } + + contentHeight = sectionInset.top + var x: CGFloat = sectionInset.left + var y: CGFloat = sectionInset.top + var lineHeight: CGFloat = 0 + + for idx in 0.. maxLineWidth && idx > 0 { + x = sectionInset.left + y += lineHeight + lineSpacing + lineHeight = 0 + } + + // 缓存 frame + let attr = UICollectionViewLayoutAttributes(forCellWith: indexPath) + attr.frame = CGRect(origin: CGPoint(x: x, y: y), size: size) + cache.append(attr) + + // 更新游标 + x += size.width + interItemSpacing + lineHeight = max(lineHeight, size.height) + } + + contentHeight = y + lineHeight + sectionInset.bottom + } + + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + cache.filter { $0.frame.intersects(rect) } + } + + override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + cache[safe: indexPath.item] + } + + override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + newBounds.width != maxLineWidth + } +} + +// MARK: - 可选 delegate(动态 size) +protocol TagFlowLayoutDelegate: AnyObject { + func collectionView(_ collectionView: UICollectionView, + layout: TagFlowLayout, + sizeForItemAt indexPath: IndexPath) -> CGSize +}