ReadViewSDK/Sources/RDReaderView/EPUBUI/RDEPUBReaderSettingsViewController.swift
shen 54798ba578 refactor: 添加中文注释 + 优化模块结构
- 给全部 78 个 Swift 源文件添加详细的中文注释(文件级、类级、方法级)
- 删除 LegacyRDReaderController/ 死代码目录(16 文件 4592 行)
- 根目录翻页容器文件移入 ReaderView/ 目录
- Resources/ 移入 EPUBCore/Resources/(与使用者归属一致)
- RDEPUBTextIndexTable.swift 移入 EPUBTextRendering/(消除反向依赖)
- RDURLReaderController.swift 移入 EPUBUI/(入口控制器归入 UI 层)
- 更新 podspec 资源路径
2026-05-25 10:19:14 +08:00

312 lines
13 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import UIKit
// MARK: -
/// EPUB
///
/// iOS 13+ SF Symbols 退
final class RDEPUBReaderSettingsViewController: UIViewController {
// MARK:
/// 0.0 ~ 1.0
var onBrightnessChange: ((CGFloat) -> Void)?
/// pt
var onFontSizeChange: ((CGFloat) -> Void)?
///
var onLineHeightChange: ((CGFloat) -> Void)?
///
var onDisplayTypeChange: ((RDReaderView.DisplayType) -> Void)?
///
var onThemeChange: ((RDEPUBReaderTheme) -> Void)?
// MARK:
///
private enum ThemePreset: Int, CaseIterable {
case light
case yellow
case green
case pink
case blue
case dark
var title: String {
switch self {
case .light: return "浅色"
case .yellow: return "米黄"
case .green: return "青绿"
case .pink: return "粉色"
case .blue: return "蓝灰"
case .dark: return "夜间"
}
}
var theme: RDEPUBReaderTheme {
switch self {
case .light: return .light
case .yellow: return .yellow
case .green: return .green
case .pink: return .pink
case .blue: return .blue
case .dark: return .dark
}
}
}
private let scrollView = UIScrollView()
private let contentStack: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 20
return stackView
}()
private let brightnessSlider = UISlider()
private let fontValueLabel = UILabel()
private let decreaseFontButton = UIButton(type: .system)
private let increaseFontButton = UIButton(type: .system)
private let lineHeightControl = UISegmentedControl(items: ["紧凑", "标准", "宽松"])
private let displayTypeControl = UISegmentedControl(items: ["仿真", "横滑", "竖滑"])
private let themeStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.spacing = 12
return stackView
}()
private var themeButtons: [UIButton] = []
/// 1.3 1.6 1.9
private let lineHeightValues: [CGFloat] = [1.3, 1.6, 1.9]
///
private var currentConfiguration: RDEPUBReaderConfiguration
init(configuration: RDEPUBReaderConfiguration, brightness: CGFloat) {
self.currentConfiguration = configuration
super.init(nibName: nil, bundle: nil)
brightnessSlider.value = Float(brightness)
title = "样式设置"
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationItems()
setupViews()
syncControls()
applyTheme(currentConfiguration.theme)
}
private func setupNavigationItems() {
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "完成", style: .done, target: self, action: #selector(doneAction))
}
private func setupViews() {
view.addSubview(scrollView)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(contentStack)
contentStack.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
contentStack.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16),
contentStack.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -16),
contentStack.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 20),
contentStack.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -24),
contentStack.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -32)
])
brightnessSlider.minimumValue = 0
brightnessSlider.maximumValue = 1
brightnessSlider.addTarget(self, action: #selector(brightnessChanged(_:)), for: .valueChanged)
fontValueLabel.font = UIFont.monospacedDigitSystemFont(ofSize: 16, weight: .semibold)
fontValueLabel.textAlignment = .center
fontValueLabel.setContentHuggingPriority(.required, for: .horizontal)
configureFontButton(decreaseFontButton, title: "A-")
configureFontButton(increaseFontButton, title: "A+")
decreaseFontButton.addTarget(self, action: #selector(decreaseFontAction), for: .touchUpInside)
increaseFontButton.addTarget(self, action: #selector(increaseFontAction), for: .touchUpInside)
lineHeightControl.addTarget(self, action: #selector(lineHeightChanged(_:)), for: .valueChanged)
displayTypeControl.addTarget(self, action: #selector(displayTypeChanged(_:)), for: .valueChanged)
ThemePreset.allCases.forEach { preset in
let button = UIButton(type: .system)
button.tag = preset.rawValue
button.layer.cornerRadius = 18
button.layer.borderWidth = 1.5
button.backgroundColor = preset.theme.contentBackgroundColor
button.accessibilityLabel = preset.title
button.addTarget(self, action: #selector(themeButtonAction(_:)), for: .touchUpInside)
themeButtons.append(button)
themeStackView.addArrangedSubview(button)
NSLayoutConstraint.activate([
button.heightAnchor.constraint(equalToConstant: 36)
])
}
contentStack.addArrangedSubview(makeSection(title: "亮度", content: brightnessSlider))
contentStack.addArrangedSubview(makeSection(title: "字号", content: makeFontSizeRow()))
contentStack.addArrangedSubview(makeSection(title: "行距", content: lineHeightControl))
contentStack.addArrangedSubview(makeSection(title: "翻页方式", content: displayTypeControl))
contentStack.addArrangedSubview(makeSection(title: "主题", content: themeStackView))
}
private func makeSection(title: String, content: UIView) -> UIView {
let container = UIStackView()
container.axis = .vertical
container.spacing = 10
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = UIFont.systemFont(ofSize: 14, weight: .semibold)
content.translatesAutoresizingMaskIntoConstraints = false
container.addArrangedSubview(titleLabel)
container.addArrangedSubview(content)
return container
}
private func makeFontSizeRow() -> UIView {
let stackView = UIStackView(arrangedSubviews: [decreaseFontButton, fontValueLabel, increaseFontButton])
stackView.axis = .horizontal
stackView.alignment = .center
stackView.distribution = .fill
stackView.spacing = 12
NSLayoutConstraint.activate([
decreaseFontButton.widthAnchor.constraint(equalToConstant: 64),
increaseFontButton.widthAnchor.constraint(equalToConstant: 64),
decreaseFontButton.heightAnchor.constraint(equalToConstant: 36),
increaseFontButton.heightAnchor.constraint(equalToConstant: 36)
])
return stackView
}
private func configureFontButton(_ button: UIButton, title: String) {
button.setTitle(title, for: .normal)
button.titleLabel?.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
button.layer.cornerRadius = 18
button.layer.borderWidth = 1
}
private func syncControls() {
fontValueLabel.text = String(Int(currentConfiguration.fontSize.rounded()))
let lineHeightIndex = lineHeightValues.enumerated().min { abs($0.element - currentConfiguration.lineHeightMultiple) < abs($1.element - currentConfiguration.lineHeightMultiple) }?.offset ?? 1
lineHeightControl.selectedSegmentIndex = lineHeightIndex
switch currentConfiguration.displayType {
case .pageCurl:
displayTypeControl.selectedSegmentIndex = 0
case .horizontalScroll:
displayTypeControl.selectedSegmentIndex = 1
case .verticalScroll:
displayTypeControl.selectedSegmentIndex = 2
}
let selectedPreset = ThemePreset.allCases.first(where: { $0.theme == currentConfiguration.theme }) ?? .light
updateThemeSelection(selectedPreset)
}
private func applyTheme(_ theme: RDEPUBReaderTheme) {
view.backgroundColor = theme.contentBackgroundColor
scrollView.backgroundColor = theme.contentBackgroundColor
contentStack.arrangedSubviews
.compactMap { $0 as? UIStackView }
.flatMap { $0.arrangedSubviews }
.compactMap { $0 as? UILabel }
.forEach { $0.textColor = theme.contentTextColor }
fontValueLabel.textColor = theme.contentTextColor
[decreaseFontButton, increaseFontButton].forEach { button in
button.setTitleColor(theme.toolControlTextColor, for: .normal)
button.layer.borderColor = theme.toolControlBorderUnselectColor.cgColor
button.backgroundColor = theme.toolBackgroundColor
}
[lineHeightControl, displayTypeControl].forEach { control in
control.backgroundColor = theme.toolBackgroundColor
if #available(iOS 13.0, *) {
control.selectedSegmentTintColor = theme.toolControlTextColor.withAlphaComponent(0.14)
} else {
control.tintColor = theme.toolControlTextColor
}
control.setTitleTextAttributes([.foregroundColor: theme.contentTextColor], for: .normal)
control.setTitleTextAttributes([.foregroundColor: theme.toolControlTextColor], for: .selected)
}
navigationController?.navigationBar.tintColor = theme.toolControlTextColor
navigationController?.navigationBar.barTintColor = theme.toolBackgroundColor
navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: theme.toolControlTextColor]
updateThemeSelection(ThemePreset.allCases.first(where: { $0.theme == theme }) ?? .light)
}
private func updateThemeSelection(_ preset: ThemePreset) {
themeButtons.forEach { button in
let isSelected = button.tag == preset.rawValue
button.layer.borderWidth = isSelected ? 2 : 1
button.layer.borderColor = isSelected ? currentConfiguration.theme.toolControlTextColor.cgColor : currentConfiguration.theme.toolControlBorderUnselectColor.cgColor
}
}
@objc private func doneAction() {
dismiss(animated: true)
}
@objc private func brightnessChanged(_ slider: UISlider) {
onBrightnessChange?(CGFloat(slider.value))
}
@objc private func decreaseFontAction() {
let nextValue = max(12, currentConfiguration.fontSize - 1)
guard nextValue != currentConfiguration.fontSize else { return }
currentConfiguration.fontSize = nextValue
fontValueLabel.text = String(Int(nextValue.rounded()))
onFontSizeChange?(nextValue)
}
@objc private func increaseFontAction() {
let nextValue = min(36, currentConfiguration.fontSize + 1)
guard nextValue != currentConfiguration.fontSize else { return }
currentConfiguration.fontSize = nextValue
fontValueLabel.text = String(Int(nextValue.rounded()))
onFontSizeChange?(nextValue)
}
@objc private func lineHeightChanged(_ control: UISegmentedControl) {
let index = max(0, min(control.selectedSegmentIndex, lineHeightValues.count - 1))
let value = lineHeightValues[index]
currentConfiguration.lineHeightMultiple = value
onLineHeightChange?(value)
}
@objc private func displayTypeChanged(_ control: UISegmentedControl) {
let displayType: RDReaderView.DisplayType
switch control.selectedSegmentIndex {
case 1:
displayType = .horizontalScroll
case 2:
displayType = .verticalScroll
default:
displayType = .pageCurl
}
currentConfiguration.displayType = displayType
onDisplayTypeChange?(displayType)
}
@objc private func themeButtonAction(_ sender: UIButton) {
guard let preset = ThemePreset(rawValue: sender.tag) else { return }
currentConfiguration.theme = preset.theme
applyTheme(preset.theme)
onThemeChange?(preset.theme)
}
}