SDK 化收尾: - 新增 RDPDFReaderView 本地 pod(阅读容器、成品控制器、划线/注释、画笔、 目录/设置面板、主题与持久化契约),Demo 精简为 PDFKit 页面提供者 + 持久化宿主 - Podfile/工程接入 RDPDFReaderView 与 SnapKit,示例书改用 PDF 阅读器示例文件 手机横屏单页竖滑修复: - cell 高度改为宽度适配后的纸张高度(sizeForItemAt + 宿主按页提供纵横比), 纵向超出一屏的内容由外层 collectionView 连续滚动承接,不再被裁掉 - 页面内部不再开启同向嵌套的兜底竖向拖动,修复外层滚动被吞、无法滚到下一页 - 修复 applyContentSize 先评估拖动开关后写 contentSize 的顺序缺陷 - 页码按视口中心命中 cell 计算,补 didEndDragging 兜底;旋转重锚点与 transitionToPage 改用真实布局位置 - 横竖屏同为竖滑时也重建 cell 并刷新全部可见页的适配配置 - 横屏单页点击不再跳页,只显隐工具栏;翻页交给连续竖滑 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
143 lines
5.1 KiB
Swift
143 lines
5.1 KiB
Swift
import UIKit
|
|
import SnapKit
|
|
|
|
final class RDEPUBAnnotationEditorViewController: UIViewController, UITextViewDelegate {
|
|
|
|
private let quote: String
|
|
private let initialNote: String?
|
|
private let theme: RDEPUBReaderTheme
|
|
private let onSave: (String?) -> Void
|
|
private let textView = UITextView()
|
|
private let countLabel = UILabel()
|
|
|
|
init(
|
|
quote: String,
|
|
initialNote: String?,
|
|
theme: RDEPUBReaderTheme,
|
|
onSave: @escaping (String?) -> Void
|
|
) {
|
|
self.quote = quote
|
|
self.initialNote = initialNote
|
|
self.theme = theme
|
|
self.onSave = onSave
|
|
super.init(nibName: nil, bundle: nil)
|
|
title = "写注释"
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
view.backgroundColor = theme.toolBackgroundColor
|
|
configureNavigation()
|
|
configureContent()
|
|
updateCount()
|
|
DispatchQueue.main.async { [weak self] in self?.textView.becomeFirstResponder() }
|
|
}
|
|
|
|
private func configureNavigation() {
|
|
navigationItem.leftBarButtonItem = UIBarButtonItem(
|
|
title: "取消", style: .plain, target: self, action: #selector(cancelAction)
|
|
)
|
|
navigationItem.rightBarButtonItem = UIBarButtonItem(
|
|
title: "保存", style: .done, target: self, action: #selector(saveAction)
|
|
)
|
|
navigationController?.navigationBar.tintColor = .systemBlue
|
|
navigationController?.navigationBar.titleTextAttributes = [
|
|
.foregroundColor: theme.toolControlTextColor,
|
|
.font: UIFont.systemFont(ofSize: 17, weight: .semibold)
|
|
]
|
|
}
|
|
|
|
private func configureContent() {
|
|
let quoteCard = UIView()
|
|
let rail = UIView()
|
|
let quoteLabel = UILabel()
|
|
let inputCard = UIView()
|
|
|
|
view.addSubview(quoteCard)
|
|
quoteCard.addSubview(rail)
|
|
quoteCard.addSubview(quoteLabel)
|
|
view.addSubview(inputCard)
|
|
inputCard.addSubview(textView)
|
|
inputCard.addSubview(countLabel)
|
|
|
|
quoteCard.backgroundColor = theme.contentBackgroundColor.withAlphaComponent(0.78)
|
|
quoteCard.layer.cornerRadius = 16
|
|
rail.backgroundColor = UIColor(red: 0.94, green: 0.63, blue: 0.18, alpha: 1)
|
|
rail.layer.cornerRadius = 2
|
|
quoteLabel.text = quote
|
|
quoteLabel.numberOfLines = 5
|
|
quoteLabel.font = UIFont.systemFont(ofSize: 16)
|
|
quoteLabel.textColor = theme.contentTextColor.withAlphaComponent(0.85)
|
|
|
|
inputCard.backgroundColor = theme.contentBackgroundColor
|
|
inputCard.layer.cornerRadius = 18
|
|
inputCard.layer.shadowColor = UIColor.black.cgColor
|
|
inputCard.layer.shadowOpacity = 0.06
|
|
inputCard.layer.shadowRadius = 18
|
|
inputCard.layer.shadowOffset = CGSize(width: 0, height: 6)
|
|
textView.text = initialNote
|
|
textView.delegate = self
|
|
textView.font = UIFont.systemFont(ofSize: 17)
|
|
textView.textColor = theme.contentTextColor
|
|
textView.backgroundColor = .clear
|
|
textView.accessibilityIdentifier = "epub.reader.annotation.editor"
|
|
countLabel.font = UIFont.monospacedDigitSystemFont(ofSize: 12, weight: .regular)
|
|
countLabel.textColor = .secondaryLabel
|
|
countLabel.textAlignment = .right
|
|
|
|
quoteCard.snp.makeConstraints { make in
|
|
make.top.equalTo(view.safeAreaLayoutGuide).offset(20)
|
|
make.horizontalEdges.equalToSuperview().inset(20)
|
|
}
|
|
rail.snp.makeConstraints { make in
|
|
make.leading.equalToSuperview().offset(16)
|
|
make.verticalEdges.equalToSuperview().inset(16)
|
|
make.width.equalTo(4)
|
|
}
|
|
quoteLabel.snp.makeConstraints { make in
|
|
make.leading.equalTo(rail.snp.trailing).offset(14)
|
|
make.trailing.equalToSuperview().inset(18)
|
|
make.verticalEdges.equalToSuperview().inset(16)
|
|
}
|
|
inputCard.snp.makeConstraints { make in
|
|
make.top.equalTo(quoteCard.snp.bottom).offset(18)
|
|
make.horizontalEdges.equalToSuperview().inset(20)
|
|
make.bottom.equalTo(view.keyboardLayoutGuide.snp.top).offset(-16)
|
|
make.height.greaterThanOrEqualTo(210)
|
|
}
|
|
textView.snp.makeConstraints { make in
|
|
make.top.horizontalEdges.equalToSuperview().inset(14)
|
|
make.bottom.equalTo(countLabel.snp.top).offset(-8)
|
|
}
|
|
countLabel.snp.makeConstraints { make in
|
|
make.horizontalEdges.equalToSuperview().inset(16)
|
|
make.bottom.equalToSuperview().inset(12)
|
|
}
|
|
}
|
|
|
|
func textViewDidChange(_ textView: UITextView) {
|
|
if textView.text.count > 1000 {
|
|
textView.text = String(textView.text.prefix(1000))
|
|
}
|
|
updateCount()
|
|
}
|
|
|
|
private func updateCount() {
|
|
countLabel.text = "\(textView.text.count)/1000"
|
|
}
|
|
|
|
@objc private func cancelAction() {
|
|
dismiss(animated: true)
|
|
}
|
|
|
|
@objc private func saveAction() {
|
|
let note = textView.text.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
onSave(note.isEmpty ? nil : note)
|
|
dismiss(animated: true)
|
|
}
|
|
}
|