Visual_Novel_iOS/crush/Crush/Src/Components/UI/BaseView/TitleView.swift

199 lines
6.6 KiB
Swift
Raw Normal View History

2025-10-09 10:29:35 +00:00
//
// TitleView.swift
// Crush
//
// Created by Leon on 2025/7/18.
//
import UIKit
class TitleView: UIView {
// MARK: - Properties
private var stackV: UIStackView
private(set)var titleLabel: LineSpaceLabel
private var subTitleLabel: LineSpaceLabel
private var stackSpacing: CGFloat = 16
var title: String? {
didSet {
titleLabel.text = title
}
}
var subtitle: String? {
didSet {
if let subtitle = subtitle, !subtitle.isEmpty {
subTitleLabel.isHidden = false
subTitleLabel.text = subtitle
} else {
subTitleLabel.isHidden = true
subTitleLabel.text = ""
}
}
}
var attributeSubTitle: NSAttributedString? {
didSet {
if let attributeSubTitle = attributeSubTitle, attributeSubTitle.length > 0 {
subTitleLabel.isHidden = false
subTitleLabel.text = ""
subTitleLabel.attributedText = attributeSubTitle
} else {
subTitleLabel.isHidden = true
subTitleLabel.attributedText = nil
}
}
}
var titleHidden: Bool = false {
didSet {
titleLabel.isHidden = titleHidden
}
}
/// Default: 16
var optionInnerTopPadding: CGFloat = 16 {
didSet {
makeStackConstraint()
}
}
/// Default: 16
var optionInnerBottomPadding: CGFloat = 16 {
didSet {
makeStackConstraint()
}
}
var optionInnerLRPadding: CGFloat = 24 {
didSet {
makeStackConstraint()
}
}
var optionInnerTrailingAppend: CGFloat = 0 {
didSet {
makeStackConstraint()
}
}
var alwaysDarkMode: Bool = false {
didSet {
if alwaysDarkMode {
titleLabel.textColor = .c.ctpn//EPSystemToken.darkColor(.txtPrimaryNormal)
subTitleLabel.textColor = .c.ctsn//EPSystemToken.darkColor(.txtSecondaryNormal)
}
}
}
// MARK: - Initialization
override init(frame: CGRect) {
stackV = UIStackView()
titleLabel = LineSpaceLabel()
subTitleLabel = LineSpaceLabel()
super.init(frame: frame)
// Configure stack view
stackV.spacing = stackSpacing
stackV.axis = .vertical
addSubview(stackV)
stackV.snp.makeConstraints { make in
make.top.equalToSuperview().offset(optionInnerTopPadding)
make.bottom.equalToSuperview().offset(-optionInnerBottomPadding)
make.leading.equalToSuperview().offset(optionInnerLRPadding)
make.trailing.equalToSuperview().offset(-optionInnerLRPadding).priority(999)
}
// Configure title label
let titleT = CLSystemToken.typography(token: .thm)
titleLabel.config(titleT)
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
stackV.addArrangedSubview(titleLabel)
// Configure subtitle label
let subTitleT = CLSystemToken.typography(token: .tbm)
subTitleLabel.config(subTitleT)
subTitleLabel.numberOfLines = 0
stackV.addArrangedSubview(subTitleLabel)
// Theme configuration
self.titleLabel.textColor = .c.ctpn //EPSystemToken.color(.txtPrimaryNormal)
self.subTitleLabel.textColor = .c.ctsn //EPSystemToken.color(.txtSecondaryNormal)
// Default state
subTitleLabel.isHidden = true
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Helper Methods
private func makeStackConstraint() {
stackV.snp.remakeConstraints { make in
make.top.equalToSuperview().offset(optionInnerTopPadding)
make.bottom.lessThanOrEqualToSuperview().offset(-optionInnerBottomPadding)
make.leading.equalToSuperview().offset(optionInnerLRPadding)
make.trailing.equalToSuperview().offset(-self.optionInnerLRPadding - self.optionInnerTrailingAppend)
}
}
// MARK: - Public Methods
// func precalcHeight() -> CGFloat {
// preCalculateHeight()
// }
func preCalculateHeight() -> CGFloat {
let topBottomPadding = optionInnerTopPadding + optionInnerBottomPadding
// Calcualte way 1: height: 36
// let titleT = CLSystemToken.typography(token: .thm)
// let lineSpacing = titleT.lineHeight * 0.5
// let text = titleLabel.text ?? ""
// let labelWidth = UIScreen.main.bounds.width - optionInnerLRPadding * 2 - optionInnerTrailingAppend
// var titleHeight = heightForText(text, font: titleT.font!, width: labelWidth, lineSpacing: lineSpacing)
// Calculate way 2: height: 33.6
let titleHeight = titleLabel.isHidden ? 0 : titleLabel.sizeThatFits(CGSize(width: UIScreen.main.bounds.width - optionInnerLRPadding * 2 - optionInnerTrailingAppend, height: .greatestFiniteMagnitude)).height
// dlog("oldCalculateHeight: \(oldCalculateHeight), bouncingCalculate: \(titleHeight)")
let subTitleHeight = subTitleLabel.isHidden ? 0 : subTitleLabel.sizeThatFits(CGSize(width: UIScreen.main.bounds.width - optionInnerLRPadding * 2 - optionInnerTrailingAppend, height: .greatestFiniteMagnitude)).height
var total = topBottomPadding + titleHeight + subTitleHeight
if titleHeight > 0 && subTitleHeight > 0 {
total += stackSpacing
}
return ceil(total)
}
func setupTitleSubtitleAlignment(_ alignment: NSTextAlignment) {
titleLabel.textAlignment = alignment
subTitleLabel.textAlignment = alignment
}
// MARK: - Helper Methods
func heightForText(_ text: String, font: UIFont, width: CGFloat, lineSpacing: CGFloat = 0) -> CGFloat {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byWordWrapping
paragraphStyle.lineSpacing = lineSpacing
let attributes: [NSAttributedString.Key: Any] = [
.font: font,
.paragraphStyle: paragraphStyle
]
let size = CGSize(width: width, height: .greatestFiniteMagnitude)
let rect = (text as NSString).boundingRect(
with: size,
options: [.usesLineFragmentOrigin, .usesFontLeading],
attributes: attributes,
context: nil
)
return ceil(rect.height)
}
}