|
|
|
@ -3,9 +3,14 @@ import UIKit |
|
|
|
public class PlayerView: UIView { |
|
|
|
private let titleLabel: UILabel = UILabel() |
|
|
|
private let langLabel: UILabel = UILabel() |
|
|
|
private let langContainer: UIView = UIView() |
|
|
|
private let cnLabel: UILabel = UILabel() |
|
|
|
private let enLabel: UILabel = UILabel() |
|
|
|
private let stackView: UIStackView |
|
|
|
private var lastDynamicTextLines: Int = -1 |
|
|
|
private let langRow: UIStackView = UIStackView() |
|
|
|
private var lastBoundsSize: CGSize = .zero |
|
|
|
private var lastLangText: String? = nil |
|
|
|
|
|
|
|
public override init(frame: CGRect) { |
|
|
|
stackView = UIStackView() |
|
|
|
@ -24,42 +29,84 @@ public class PlayerView: UIView { |
|
|
|
} |
|
|
|
|
|
|
|
private func setupUI() { |
|
|
|
backgroundColor = .white |
|
|
|
backgroundColor = UIColor(red: 40.0 / 255.0, green: 40.0 / 255.0, blue: 40.0 / 255.0, alpha: 90.0 / 255.0) |
|
|
|
layer.cornerRadius = 16 |
|
|
|
layer.borderWidth = 1 |
|
|
|
layer.borderColor = UIColor(white: 1.0, alpha: 46.0 / 255.0).cgColor |
|
|
|
layer.shadowColor = UIColor.black.cgColor |
|
|
|
layer.shadowOpacity = 0.35 |
|
|
|
layer.shadowRadius = 12 |
|
|
|
layer.shadowOffset = CGSize(width: 0, height: 4) |
|
|
|
|
|
|
|
titleLabel.text = "实时翻译" |
|
|
|
titleLabel.font = .boldSystemFont(ofSize: 22) |
|
|
|
titleLabel.textColor = .black |
|
|
|
titleLabel.textColor = UIColor(white: 1.0, alpha: 235.0 / 255.0) |
|
|
|
titleLabel.textAlignment = .center |
|
|
|
titleLabel.numberOfLines = 1 |
|
|
|
titleLabel.isHidden = true |
|
|
|
|
|
|
|
langLabel.text = "中文(简体) -> 英语" |
|
|
|
langLabel.font = .systemFont(ofSize: 14) |
|
|
|
langLabel.textColor = .darkGray |
|
|
|
langLabel.textAlignment = .left |
|
|
|
langLabel.text = "src -> tgt" |
|
|
|
langLabel.font = .systemFont(ofSize: 12) |
|
|
|
langLabel.textColor = UIColor(white: 1.0, alpha: 235.0 / 255.0) |
|
|
|
langLabel.textAlignment = .center |
|
|
|
langLabel.numberOfLines = 1 |
|
|
|
langLabel.lineBreakMode = .byTruncatingTail |
|
|
|
langLabel.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
langLabel.setContentHuggingPriority(.required, for: .horizontal) |
|
|
|
langLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) |
|
|
|
|
|
|
|
langContainer.backgroundColor = UIColor(white: 1.0, alpha: 36.0 / 255.0) |
|
|
|
langContainer.layer.cornerRadius = 10 |
|
|
|
langContainer.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
langContainer.setContentHuggingPriority(.required, for: .horizontal) |
|
|
|
langContainer.setContentCompressionResistancePriority(.required, for: .horizontal) |
|
|
|
langContainer.addSubview(langLabel) |
|
|
|
NSLayoutConstraint.activate([ |
|
|
|
langLabel.topAnchor.constraint(equalTo: langContainer.topAnchor, constant: 6), |
|
|
|
langLabel.leadingAnchor.constraint(equalTo: langContainer.leadingAnchor, constant: 10), |
|
|
|
langLabel.trailingAnchor.constraint(equalTo: langContainer.trailingAnchor, constant: -10), |
|
|
|
langLabel.bottomAnchor.constraint(equalTo: langContainer.bottomAnchor, constant: -6), |
|
|
|
]) |
|
|
|
|
|
|
|
cnLabel.font = .systemFont(ofSize: 18) |
|
|
|
cnLabel.textColor = .black |
|
|
|
cnLabel.font = .systemFont(ofSize: 14) |
|
|
|
cnLabel.textColor = UIColor(white: 1.0, alpha: 235.0 / 255.0) |
|
|
|
cnLabel.textAlignment = .left |
|
|
|
cnLabel.numberOfLines = 0 |
|
|
|
cnLabel.lineBreakMode = .byTruncatingTail |
|
|
|
|
|
|
|
enLabel.font = .systemFont(ofSize: 18) |
|
|
|
enLabel.textColor = UIColor(red: 0.24, green: 0.54, blue: 0.90, alpha: 1.0) |
|
|
|
enLabel.font = .systemFont(ofSize: 15) |
|
|
|
enLabel.textColor = UIColor(red: 144.0 / 255.0, green: 202.0 / 255.0, blue: 249.0 / 255.0, alpha: 1.0) |
|
|
|
enLabel.textAlignment = .left |
|
|
|
enLabel.numberOfLines = 0 |
|
|
|
enLabel.lineBreakMode = .byTruncatingTail |
|
|
|
|
|
|
|
let para = NSMutableParagraphStyle() |
|
|
|
para.lineSpacing = 4 |
|
|
|
cnLabel.attributedText = NSAttributedString(string: "正在识别...", attributes: [.paragraphStyle: para]) |
|
|
|
enLabel.attributedText = NSAttributedString(string: "翻译中...", attributes: [.paragraphStyle: para]) |
|
|
|
para.lineSpacing = 2 |
|
|
|
para.lineHeightMultiple = 1.1 |
|
|
|
cnLabel.attributedText = NSAttributedString(string: "", attributes: [.paragraphStyle: para]) |
|
|
|
enLabel.attributedText = NSAttributedString(string: "", attributes: [.paragraphStyle: para]) |
|
|
|
|
|
|
|
stackView.axis = .vertical |
|
|
|
stackView.spacing = 12 |
|
|
|
stackView.spacing = 10 |
|
|
|
stackView.alignment = .fill |
|
|
|
stackView.distribution = .fill |
|
|
|
stackView.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
stackView.addArrangedSubview(titleLabel) |
|
|
|
stackView.addArrangedSubview(langLabel) |
|
|
|
langRow.axis = .horizontal |
|
|
|
langRow.alignment = .fill |
|
|
|
langRow.distribution = .fill |
|
|
|
langRow.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
let leftSpacer = UIView() |
|
|
|
let rightSpacer = UIView() |
|
|
|
leftSpacer.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
rightSpacer.translatesAutoresizingMaskIntoConstraints = false |
|
|
|
langRow.addArrangedSubview(leftSpacer) |
|
|
|
langRow.addArrangedSubview(langContainer) |
|
|
|
langRow.addArrangedSubview(rightSpacer) |
|
|
|
NSLayoutConstraint.activate([ |
|
|
|
leftSpacer.widthAnchor.constraint(equalTo: rightSpacer.widthAnchor), |
|
|
|
]) |
|
|
|
stackView.addArrangedSubview(langRow) |
|
|
|
stackView.addArrangedSubview(cnLabel) |
|
|
|
stackView.addArrangedSubview(enLabel) |
|
|
|
|
|
|
|
@ -86,13 +133,61 @@ public class PlayerView: UIView { |
|
|
|
langLabel.text = lang |
|
|
|
} |
|
|
|
let para = NSMutableParagraphStyle() |
|
|
|
para.lineSpacing = 4 |
|
|
|
para.lineSpacing = 2 |
|
|
|
para.lineHeightMultiple = 1.1 |
|
|
|
if let cn = cn { |
|
|
|
cnLabel.attributedText = NSAttributedString(string: cn, attributes: [.paragraphStyle: para]) |
|
|
|
} |
|
|
|
if let en = en { |
|
|
|
enLabel.attributedText = NSAttributedString(string: en, attributes: [.paragraphStyle: para]) |
|
|
|
} |
|
|
|
setNeedsLayout() |
|
|
|
} |
|
|
|
|
|
|
|
public override func layoutSubviews() { |
|
|
|
super.layoutSubviews() |
|
|
|
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius).cgPath |
|
|
|
let currentLangText = langLabel.text |
|
|
|
if bounds.size != lastBoundsSize || currentLangText != lastLangText { |
|
|
|
lastBoundsSize = bounds.size |
|
|
|
lastLangText = currentLangText |
|
|
|
updateDynamicTextLineLimit() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private func updateDynamicTextLineLimit() { |
|
|
|
let fittingWidth = max(0, bounds.width - layoutMargins.left - layoutMargins.right) |
|
|
|
if fittingWidth <= 0 { return } |
|
|
|
|
|
|
|
let visibleArrangedSubviews = stackView.arrangedSubviews.filter { !$0.isHidden } |
|
|
|
let fixedSubviews = visibleArrangedSubviews.filter { $0 !== cnLabel && $0 !== enLabel } |
|
|
|
let fixedHeight = fixedSubviews.reduce(CGFloat(0)) { partialResult, view in |
|
|
|
partialResult |
|
|
|
+ view.systemLayoutSizeFitting( |
|
|
|
CGSize(width: fittingWidth, height: UIView.layoutFittingCompressedSize.height), |
|
|
|
withHorizontalFittingPriority: .required, |
|
|
|
verticalFittingPriority: .fittingSizeLevel |
|
|
|
).height |
|
|
|
} |
|
|
|
let totalSpacing = CGFloat(max(0, visibleArrangedSubviews.count - 1)) * stackView.spacing |
|
|
|
|
|
|
|
let availableHeight = |
|
|
|
bounds.height |
|
|
|
- layoutMargins.top |
|
|
|
- layoutMargins.bottom |
|
|
|
- fixedHeight |
|
|
|
- totalSpacing |
|
|
|
|
|
|
|
let cnLineHeight = cnLabel.font.lineHeight * 1.1 + 2 |
|
|
|
let enLineHeight = enLabel.font.lineHeight * 1.1 + 2 |
|
|
|
let combinedLineHeight = cnLineHeight + enLineHeight |
|
|
|
|
|
|
|
let perLabelLines = max(1, min(Int(availableHeight / combinedLineHeight), 6)) |
|
|
|
|
|
|
|
if perLabelLines != lastDynamicTextLines { |
|
|
|
lastDynamicTextLines = perLabelLines |
|
|
|
cnLabel.numberOfLines = perLabelLines |
|
|
|
enLabel.numberOfLines = perLabelLines |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|