应用改用 UIKit 重写,删除 SwiftUI 版本
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
import UIKit
|
||||
|
||||
final class WordDetailViewController: UIViewController {
|
||||
private let word: Word
|
||||
private let speech = SpeechService.shared
|
||||
|
||||
private let speakButton = UIButton(type: .system)
|
||||
private var isSpeaking: Bool { speech.speakingText == word.word }
|
||||
|
||||
init(word: Word) {
|
||||
self.word = word
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) { fatalError() }
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = .systemBackground
|
||||
title = word.word
|
||||
navigationItem.largeTitleDisplayMode = .never
|
||||
buildLayout()
|
||||
updateSpeakButton()
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
speech.onSpeakingChanged = { [weak self] in
|
||||
self?.updateSpeakButton()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateSpeakButton() {
|
||||
var config = speakButton.configuration
|
||||
config?.title = isSpeaking ? "朗读中" : "发音"
|
||||
config?.image = UIImage(systemName: isSpeaking ? "speaker.wave.3.fill" : "speaker.wave.2.fill")
|
||||
speakButton.configuration = config
|
||||
speakButton.isEnabled = !isSpeaking
|
||||
}
|
||||
|
||||
private func buildLayout() {
|
||||
let scrollView = UIScrollView()
|
||||
view.addSubview(scrollView)
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
|
||||
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
||||
])
|
||||
|
||||
let stack = UIStackView()
|
||||
stack.axis = .vertical
|
||||
stack.spacing = 32
|
||||
scrollView.addSubview(stack)
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
stack.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 16),
|
||||
stack.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: 16),
|
||||
stack.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -16),
|
||||
stack.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -16),
|
||||
stack.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor, constant: -32)
|
||||
])
|
||||
|
||||
stack.addArrangedSubview(makeHeader())
|
||||
stack.addArrangedSubview(makeCard())
|
||||
}
|
||||
|
||||
private func makeHeader() -> UIView {
|
||||
let wordLabel = UILabel()
|
||||
wordLabel.text = word.word
|
||||
wordLabel.font = .systemFont(ofSize: 48, weight: .bold)
|
||||
wordLabel.textAlignment = .center
|
||||
|
||||
let phoneticLabel = UILabel()
|
||||
phoneticLabel.text = word.phonetic
|
||||
phoneticLabel.font = .preferredFont(forTextStyle: .title3)
|
||||
phoneticLabel.textColor = .secondaryLabel
|
||||
phoneticLabel.textAlignment = .center
|
||||
|
||||
var config = UIButton.Configuration.borderedProminent()
|
||||
config.imagePadding = 6
|
||||
config.contentInsets = .init(top: 10, leading: 24, bottom: 10, trailing: 24)
|
||||
speakButton.configuration = config
|
||||
speakButton.addAction(UIAction { [weak self] _ in
|
||||
guard let self else { return }
|
||||
speech.speak(word.word)
|
||||
}, for: .touchUpInside)
|
||||
|
||||
let stack = UIStackView(arrangedSubviews: [wordLabel, phoneticLabel, speakButton])
|
||||
stack.axis = .vertical
|
||||
stack.spacing = 12
|
||||
stack.alignment = .center
|
||||
return stack
|
||||
}
|
||||
|
||||
private func makeCard() -> UIView {
|
||||
let card = UIView()
|
||||
card.backgroundColor = .quaternarySystemFill.withAlphaComponent(0.5)
|
||||
card.layer.cornerRadius = 16
|
||||
|
||||
let meaningTitle = makeCaption("释义")
|
||||
let meaningLabel = UILabel()
|
||||
meaningLabel.text = word.meaning
|
||||
meaningLabel.font = .preferredFont(forTextStyle: .title2)
|
||||
meaningLabel.numberOfLines = 0
|
||||
|
||||
let divider = UIView()
|
||||
divider.backgroundColor = .separator
|
||||
divider.translatesAutoresizingMaskIntoConstraints = false
|
||||
divider.heightAnchor.constraint(equalToConstant: 1.0 / UIScreen.main.scale).isActive = true
|
||||
|
||||
let exampleTitle = makeCaption("例句")
|
||||
let exampleLabel = UILabel()
|
||||
exampleLabel.text = word.example
|
||||
exampleLabel.numberOfLines = 0
|
||||
let exampleMeaningLabel = UILabel()
|
||||
exampleMeaningLabel.text = word.exampleMeaning
|
||||
exampleMeaningLabel.font = .preferredFont(forTextStyle: .subheadline)
|
||||
exampleMeaningLabel.textColor = .secondaryLabel
|
||||
exampleMeaningLabel.numberOfLines = 0
|
||||
|
||||
let exampleSpeakButton = UIButton(type: .system)
|
||||
exampleSpeakButton.setImage(UIImage(systemName: "speaker.wave.2"), for: .normal)
|
||||
exampleSpeakButton.addAction(UIAction { [weak self] _ in
|
||||
guard let self else { return }
|
||||
speech.speak(word.example)
|
||||
}, for: .touchUpInside)
|
||||
exampleSpeakButton.setContentHuggingPriority(.required, for: .horizontal)
|
||||
|
||||
let exampleTextStack = UIStackView(arrangedSubviews: [exampleLabel, exampleMeaningLabel])
|
||||
exampleTextStack.axis = .vertical
|
||||
exampleTextStack.spacing = 4
|
||||
let exampleRow = UIStackView(arrangedSubviews: [exampleTextStack, exampleSpeakButton])
|
||||
exampleRow.axis = .horizontal
|
||||
exampleRow.alignment = .top
|
||||
exampleRow.spacing = 8
|
||||
|
||||
let stack = UIStackView(arrangedSubviews: [meaningTitle, meaningLabel, divider, exampleTitle, exampleRow])
|
||||
stack.axis = .vertical
|
||||
stack.spacing = 16
|
||||
stack.setCustomSpacing(6, after: meaningTitle)
|
||||
stack.setCustomSpacing(6, after: exampleTitle)
|
||||
|
||||
card.addSubview(stack)
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
stack.topAnchor.constraint(equalTo: card.topAnchor, constant: 16),
|
||||
stack.leadingAnchor.constraint(equalTo: card.leadingAnchor, constant: 16),
|
||||
stack.trailingAnchor.constraint(equalTo: card.trailingAnchor, constant: -16),
|
||||
stack.bottomAnchor.constraint(equalTo: card.bottomAnchor, constant: -16)
|
||||
])
|
||||
return card
|
||||
}
|
||||
|
||||
private func makeCaption(_ text: String) -> UILabel {
|
||||
let label = UILabel()
|
||||
label.text = text
|
||||
label.font = .preferredFont(forTextStyle: .caption1)
|
||||
label.textColor = .secondaryLabel
|
||||
return label
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user