角色list
This commit is contained in:
parent
2c6bb330af
commit
8f61a1e969
|
|
@ -18,42 +18,58 @@ final class TopHeaderManager {
|
||||||
return view
|
return view
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private var hostingView: UIView?
|
// 为传入的视图创建并附加一个新的 CLTopHeaderView。
|
||||||
private var topConstraint: Constraint?
|
// 若视图已拥有 CLTopHeaderView,则直接返回已有实例,避免重复添加。
|
||||||
private var heightConstraint: Constraint?
|
@discardableResult
|
||||||
|
func buildAndAttach(to superView: UIView) -> CLTopHeaderView {
|
||||||
private init() {}
|
if let existing = superView.subviews.first(where: { $0 is CLTopHeaderView }) as? CLTopHeaderView {
|
||||||
|
return existing
|
||||||
var jumpPublisher: AnyPublisher<JumpTarget, Never> {
|
}
|
||||||
headerView.jumpPublisher
|
let header = CLTopHeaderView()
|
||||||
|
superView.addSubview(header)
|
||||||
|
header.snp.makeConstraints { make in
|
||||||
|
make.left.right.top.equalToSuperview()
|
||||||
|
make.height.equalTo(UIDevice().navHeight)
|
||||||
|
}
|
||||||
|
return header
|
||||||
}
|
}
|
||||||
|
|
||||||
func attachIfNeeded(to viewController: UIViewController) {
|
// private var hostingView: UIView?
|
||||||
guard hostingView !== viewController.view else { return }
|
// private var topConstraint: Constraint?
|
||||||
detach()
|
// private var heightConstraint: Constraint?
|
||||||
let container = viewController.view!
|
//
|
||||||
container.addSubview(headerView)
|
// private init() {}
|
||||||
headerView.snp.makeConstraints { make in
|
//
|
||||||
topConstraint = make.top.equalToSuperview().constraint
|
// var jumpPublisher: AnyPublisher<JumpTarget, Never> {
|
||||||
make.leading.trailing.equalToSuperview()
|
// headerView.jumpPublisher
|
||||||
heightConstraint = make.height.equalTo(UIDevice().navHeight).constraint
|
// }
|
||||||
}
|
//
|
||||||
hostingView = container
|
// func attachIfNeeded(to viewController: UIViewController) {
|
||||||
headerView.isHidden = false
|
// guard hostingView !== viewController.view else { return }
|
||||||
}
|
// detach()
|
||||||
|
// let container = viewController.view!
|
||||||
func show() {
|
// container.addSubview(headerView)
|
||||||
headerView.isHidden = false
|
// headerView.snp.makeConstraints { make in
|
||||||
}
|
// topConstraint = make.top.equalToSuperview().constraint
|
||||||
|
// make.leading.trailing.equalToSuperview()
|
||||||
func hide() {
|
// heightConstraint = make.height.equalTo(UIDevice().navHeight).constraint
|
||||||
headerView.isHidden = true
|
// }
|
||||||
}
|
// hostingView = container
|
||||||
|
// headerView.isHidden = false
|
||||||
func detach() {
|
// }
|
||||||
headerView.removeFromSuperview()
|
//
|
||||||
hostingView = nil
|
// func show() {
|
||||||
}
|
// headerView.isHidden = false
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func hide() {
|
||||||
|
// headerView.isHidden = true
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func detach() {
|
||||||
|
// headerView.removeFromSuperview()
|
||||||
|
// hostingView = nil
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,17 +27,27 @@ struct RoleListModel: Codable {
|
||||||
var rows: [RoleItem] = []
|
var rows: [RoleItem] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum RoleSourceType: Int, Codable {
|
||||||
|
case novel
|
||||||
|
case video
|
||||||
|
case other
|
||||||
|
}
|
||||||
|
|
||||||
struct RoleItem: Codable {
|
struct RoleItem: Codable {
|
||||||
var id: String = ""
|
var id: String?
|
||||||
var name: String = ""
|
var name: String?
|
||||||
var coverImage: String = ""
|
var coverImage: String?
|
||||||
|
// 来源封面
|
||||||
|
var sourceCoverImage: String?
|
||||||
|
var sourceName: String?
|
||||||
// 角色头像
|
// 角色头像
|
||||||
var headPortrait: String = ""
|
var headPortrait: String?
|
||||||
var score: Double?
|
var score: Double?
|
||||||
var description: String = ""
|
var description: String?
|
||||||
var updateTime: String = ""
|
var updateTime: String?
|
||||||
var sourceId: String = ""
|
var sourceId: String?
|
||||||
var sourceType: Int = 0
|
// 0:小说;1:漫剧;2:用户自定义
|
||||||
|
var sourceType: RoleSourceType?
|
||||||
var commonCount: Int?
|
var commonCount: Int?
|
||||||
var tags: [tagItem] = []
|
var tags: [tagItem] = []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,9 @@ class FriendsRootHomeController: CLTabRootController<FriendsRootHomeView> {
|
||||||
|
|
||||||
// view.showEmpty(text: "Friends Coming soon")
|
// view.showEmpty(text: "Friends Coming soon")
|
||||||
|
|
||||||
|
// 每个 Tab 子控制器拥有自己的 Header,布局一致
|
||||||
|
_ = TopHeaderManager.shared.buildAndAttach(to: view)
|
||||||
|
|
||||||
setupViews()
|
setupViews()
|
||||||
setupDats()
|
setupDats()
|
||||||
setupEvents()
|
setupEvents()
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ class HomePageRootController: CLTabRootController<HomePageRootView> {
|
||||||
|
|
||||||
//container.viewModel = viewModel
|
//container.viewModel = viewModel
|
||||||
container.setupViewModel(vm: viewModel)
|
container.setupViewModel(vm: viewModel)
|
||||||
|
// 每个 Tab 子控制器拥有自己的 Header,布局一致
|
||||||
|
_ = TopHeaderManager.shared.buildAndAttach(to: view)
|
||||||
|
|
||||||
// Do any additional setup after loading the view.
|
// Do any additional setup after loading the view.
|
||||||
// view.showEmpty(text: "首页 Coming soon")
|
// view.showEmpty(text: "首页 Coming soon")
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ class HomePageRootView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupViews() {
|
private func setupViews() {
|
||||||
|
// TopHeaderManager.shared.addFromSuperView(self)
|
||||||
|
|
||||||
emptyView = {
|
emptyView = {
|
||||||
let v = UIView()
|
let v = UIView()
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ class MeRootPageController: CLTabRootController<MeRootPageView> {
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
// 每个 Tab 子控制器拥有自己的 Header,布局一致
|
||||||
|
_ = TopHeaderManager.shared.buildAndAttach(to: view)
|
||||||
|
|
||||||
setupViews()
|
setupViews()
|
||||||
setupDats()
|
setupDats()
|
||||||
setupEvents()
|
setupEvents()
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ class CLRoleCollectionCell: UICollectionViewCell {
|
||||||
|
|
||||||
lazy var bookImgView: UIImageView = {
|
lazy var bookImgView: UIImageView = {
|
||||||
let imgView = UIImageView()
|
let imgView = UIImageView()
|
||||||
imgView.backgroundColor = .blue
|
|
||||||
imgView.cornerRadius = 7
|
imgView.cornerRadius = 7
|
||||||
return imgView
|
return imgView
|
||||||
}()
|
}()
|
||||||
|
|
@ -135,12 +134,13 @@ class CLRoleCollectionCell: UICollectionViewCell {
|
||||||
func setupData(item: RoleItem) {
|
func setupData(item: RoleItem) {
|
||||||
descLab.text = item.description
|
descLab.text = item.description
|
||||||
nameLab.text = item.name
|
nameLab.text = item.name
|
||||||
coverImgView.sd_setImage(with: URL(string: item.coverImage), placeholderImage: nil)
|
coverImgView.sd_setImage(with: URL(string: item.coverImage ?? ""), placeholderImage: nil)
|
||||||
tagLab.text = item.tags.compactMap { $0.name }.joined(separator: "/")
|
bookImgView.sd_setImage(with: URL(string: item.sourceCoverImage ?? ""), placeholderImage: nil)
|
||||||
// 0:小说;1:漫剧;2:用户自定义
|
tagLab.text = item.tags.compactMap { "#\($0.name)" }.joined(separator: "/")
|
||||||
self.bookBgImgView.isHidden = item.sourceType != 0
|
sourceLab.text = item.score?.truncateString(places: 1)
|
||||||
self.playImgView.isHidden = item.sourceType != 1
|
self.bookBgImgView.isHidden = item.sourceType != .novel
|
||||||
// coverImgView.
|
self.playImgView.isHidden = item.sourceType != .video
|
||||||
|
self.remindLab.text = item.sourceName
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: subviews
|
// MARK: subviews
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import Combine
|
||||||
class RolesRootPageView: CLContainer {
|
class RolesRootPageView: CLContainer {
|
||||||
|
|
||||||
let itemWidth: CGFloat = (UIScreen.width - 30.0) / 2.0
|
let itemWidth: CGFloat = (UIScreen.width - 30.0) / 2.0
|
||||||
var jumpPublisher: AnyPublisher<JumpTarget, Never> { topView.jumpPublisher }
|
var jumpPublisher: AnyPublisher<JumpTarget, Never> { TopHeaderManager.shared.buildAndAttach(to: self).jumpPublisher }
|
||||||
|
|
||||||
// private lazy var pagingView = JXPagingListRefreshView(delegate: self)
|
// private lazy var pagingView = JXPagingListRefreshView(delegate: self)
|
||||||
// lazy var headerView:
|
// lazy var headerView:
|
||||||
|
|
@ -25,33 +25,10 @@ class RolesRootPageView: CLContainer {
|
||||||
var data: [RoleItem] = []
|
var data: [RoleItem] = []
|
||||||
var hasMoreData: Bool = true // 是否还有更多数据
|
var hasMoreData: Bool = true // 是否还有更多数据
|
||||||
|
|
||||||
let remind: [String] = [
|
// lazy var topView: CLTopHeaderView = {
|
||||||
"[The Lsat Oracle of Kael]",
|
// let view = CLTopHeaderView()
|
||||||
"[The Lsat Oracle of Kael]",
|
// return view
|
||||||
"[The Lsat Oracle of Kael]",
|
// }()
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]",
|
|
||||||
"[The Lsat Oracle of Kael]"
|
|
||||||
]
|
|
||||||
|
|
||||||
lazy var topView: CLTopHeaderView = {
|
|
||||||
let view = CLTopHeaderView()
|
|
||||||
return view
|
|
||||||
}()
|
|
||||||
|
|
||||||
lazy var tagsView: CLRoleTagsView = {
|
lazy var tagsView: CLRoleTagsView = {
|
||||||
let tagsView = CLRoleTagsView()
|
let tagsView = CLRoleTagsView()
|
||||||
|
|
@ -85,19 +62,13 @@ class RolesRootPageView: CLContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupViews() {
|
private func setupViews() {
|
||||||
addSubview(self.topView)
|
// TopHeaderManager.shared.addFromSuperView(self)
|
||||||
// addSubview(tagsChooseView)
|
|
||||||
addSubview(tagsView)
|
addSubview(tagsView)
|
||||||
addSubview(collectionView)
|
addSubview(collectionView)
|
||||||
|
|
||||||
topView.snp.makeConstraints { make in
|
|
||||||
make.left.right.top.equalToSuperview()
|
|
||||||
make.height.equalTo(UIDevice().navHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
tagsView.snp.makeConstraints { make in
|
tagsView.snp.makeConstraints { make in
|
||||||
make.right.left.equalToSuperview()
|
make.right.left.equalToSuperview()
|
||||||
make.top.equalTo(topView.snp.bottom).offset(0)
|
make.top.equalTo(TopHeaderManager.shared.buildAndAttach(to: self).snp.bottom).offset(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
collectionView.snp.makeConstraints { make in
|
collectionView.snp.makeConstraints { make in
|
||||||
|
|
@ -206,7 +177,7 @@ extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSourc
|
||||||
let maxHeight = lineHeight * CGFloat(maxLines)
|
let maxHeight = lineHeight * CGFloat(maxLines)
|
||||||
|
|
||||||
// 真实文本高度(不会超过 maxHeight)
|
// 真实文本高度(不会超过 maxHeight)
|
||||||
let textSize = model.name.boundingRect(
|
let textSize = (model.name ?? "").boundingRect(
|
||||||
with: CGSize(width: itemWidth - 20, height: maxHeight), // 高度封顶
|
with: CGSize(width: itemWidth - 20, height: maxHeight), // 高度封顶
|
||||||
options: [.usesLineFragmentOrigin, .usesFontLeading],
|
options: [.usesLineFragmentOrigin, .usesFontLeading],
|
||||||
attributes: [.font: font],
|
attributes: [.font: font],
|
||||||
|
|
@ -216,8 +187,7 @@ extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSourc
|
||||||
let textH = min(textSize.height, maxHeight)
|
let textH = min(textSize.height, maxHeight)
|
||||||
|
|
||||||
// 防止 remind 数组越界,使用模运算或默认值
|
// 防止 remind 数组越界,使用模运算或默认值
|
||||||
let remindIndex = indexPath.item % remind.count
|
let remindText = model.sourceName ?? ""
|
||||||
let remindText = remind[remindIndex]
|
|
||||||
let remindH = remindText.boundingRect(
|
let remindH = remindText.boundingRect(
|
||||||
with: CGSize(width: itemWidth - 20.0, height: .greatestFiniteMagnitude),
|
with: CGSize(width: itemWidth - 20.0, height: .greatestFiniteMagnitude),
|
||||||
options: .usesLineFragmentOrigin,
|
options: .usesLineFragmentOrigin,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// DoubleExt.swift
|
||||||
|
// Visual_Novel_iOS
|
||||||
|
//
|
||||||
|
// Created by mh on 2025/11/11.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Double {
|
||||||
|
/// 截断保留小数位数(向下取整)
|
||||||
|
func truncate(places: Int) -> Double {
|
||||||
|
let divisor = pow(10.0, Double(places))
|
||||||
|
return floor(self * divisor) / divisor
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 截断保留小数位数并格式化为字符串
|
||||||
|
func truncateString(places: Int) -> String {
|
||||||
|
let truncated = truncate(places: places)
|
||||||
|
return String(format: "%.\(places)f", truncated)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue