deep-linking-sample/Apps/Wikipedia/Wikipedia/Code/OldTalkPageHeaderView.swift
Javier Cicchelli 9bcdaa697b [Setup] Basic project structure (#1)
This PR contains all the work related to setting up this project as required to implement the [Assignment](https://repo.rock-n-code.com/rock-n-code/deep-linking-assignment/wiki/Assignment) on top, as intended.

To summarise this work:
- [x] created a new **Xcode** project;
- [x] cloned the `Wikipedia` app and inserted it into the **Xcode** project;
- [x] created the `Locations` app and also, its `Libraries` package;
- [x] created the `Shared` package to share dependencies between the apps;
- [x] added a `Makefile` file and implemented some **environment** and **help** commands.

Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Reviewed-on: rock-n-code/deep-linking-assignment#1
2023-04-08 18:37:13 +00:00

207 lines
7.6 KiB
Swift

import UIKit
protocol OldTalkPageHeaderViewDelegate: AnyObject {
func tappedLink(_ url: URL, headerView: OldTalkPageHeaderView, sourceView: UIView, sourceRect: CGRect?)
func tappedIntro(headerView: OldTalkPageHeaderView)
}
class OldTalkPageHeaderView: UIView {
weak var delegate: OldTalkPageHeaderViewDelegate?
struct ViewModel {
let header: String
let title: String
let info: String?
let intro: String?
}
@IBOutlet private var headerLabel: UILabel!
@IBOutlet private(set) var titleTextView: UITextView!
@IBOutlet private(set) var infoLabel: UILabel!
@IBOutlet private var introTextView: UITextView!
private var viewModel: ViewModel?
private var theme: Theme?
private var hasInfoText: Bool {
return viewModel?.info != nil
}
private var hasIntroText: Bool {
return viewModel?.intro != nil
}
private var hasTitleText: Bool {
if let viewModel = viewModel {
return viewModel.title.count > 0
}
return false
}
var semanticContentAttributeOverride: UISemanticContentAttribute = .unspecified {
didSet {
textAlignmentOverride = semanticContentAttributeOverride == .forceRightToLeft ? NSTextAlignment.right : NSTextAlignment.left
headerLabel.semanticContentAttribute = semanticContentAttributeOverride
titleTextView.semanticContentAttribute = semanticContentAttributeOverride
infoLabel.semanticContentAttribute = semanticContentAttributeOverride
introTextView.semanticContentAttribute = semanticContentAttributeOverride
}
}
private var textAlignmentOverride: NSTextAlignment = .left {
didSet {
headerLabel.textAlignment = textAlignmentOverride
titleTextView.textAlignment = textAlignmentOverride
infoLabel.textAlignment = textAlignmentOverride
introTextView.textAlignment = textAlignmentOverride
}
}
override init(frame: CGRect) {
assertionFailure("init(frame) not setup for TalkPageHeaderView")
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
setup()
}
func setup() {
infoLabel.numberOfLines = 0
titleTextView.isEditable = false
titleTextView.isScrollEnabled = false
titleTextView.delegate = self
titleTextView.textContainerInset = UIEdgeInsets.zero
titleTextView.textContainer.lineFragmentPadding = 0
introTextView.isEditable = false
introTextView.isScrollEnabled = false
introTextView.delegate = self
introTextView.textContainer.maximumNumberOfLines = 3
introTextView.textContainer.lineBreakMode = .byTruncatingTail
introTextView.textContainerInset = UIEdgeInsets.zero
introTextView.textContainer.lineFragmentPadding = 0
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tappedIntro(sender:)))
introTextView.addGestureRecognizer(tapGestureRecognizer)
headerLabel.accessibilityTraits = .header
titleTextView.accessibilityTraits = .header
updateFonts(with: traitCollection)
}
func configure(viewModel: ViewModel) {
self.viewModel = viewModel
if hasInfoText {
infoLabel.text = viewModel.info
introTextView.isHidden = false
} else {
infoLabel.isHidden = true
}
headerLabel.text = viewModel.header
if hasTitleText {
let titleAttributedString = viewModel.title.byAttributingHTML(with: .boldTitle1, boldWeight: .bold, matching: traitCollection, color: titleTextView.textColor, linkColor: theme?.colors.link, handlingSuperSubscripts: true)
titleTextView.attributedText = titleAttributedString
titleTextView.isHidden = false
} else {
titleTextView.isHidden = true
}
if let intro = viewModel.intro {
introTextView.isHidden = false
setupIntro(text: intro)
} else {
introTextView.isHidden = true
}
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
guard !UIAccessibility.isVoiceOverRunning else {
return super.point(inside: point, with: event)
}
let titleConvertedPoint = self.convert(point, to: titleTextView)
if titleTextView.point(inside: titleConvertedPoint, with: event) {
return true
}
let introConvertedPoint = self.convert(point, to: introTextView)
if introTextView.point(inside: introConvertedPoint, with: event) {
return true
}
return false
}
private func setupIntro(text: String) {
introTextView.attributedText = text.byAttributingHTML(with: .footnote, boldWeight: .semibold, matching: traitCollection, color: introTextView.textColor, linkColor: theme?.colors.link, handlingLists: true, handlingSuperSubscripts: true, tagMapping: ["a": "b"])
}
@objc private func tappedIntro(sender: UITextView) {
delegate?.tappedIntro(headerView: self)
}
// MARK: - Dynamic Type
// Only applies new fonts if the content size category changes
open override func setNeedsLayout() {
maybeUpdateFonts(with: traitCollection)
super.setNeedsLayout()
}
override open func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
setNeedsLayout()
}
var contentSizeCategory: UIContentSizeCategory?
fileprivate func maybeUpdateFonts(with traitCollection: UITraitCollection) {
guard contentSizeCategory == nil || contentSizeCategory != traitCollection.wmf_preferredContentSizeCategory else {
return
}
contentSizeCategory = traitCollection.wmf_preferredContentSizeCategory
updateFonts(with: traitCollection)
}
func updateFonts(with traitCollection: UITraitCollection) {
headerLabel.font = UIFont.wmf_font(DynamicTextStyle.semiboldFootnote, compatibleWithTraitCollection: traitCollection)
titleTextView.font = UIFont.wmf_font(DynamicTextStyle.boldTitle1, compatibleWithTraitCollection: traitCollection)
infoLabel.font = UIFont.wmf_font(DynamicTextStyle.footnote, compatibleWithTraitCollection: traitCollection)
if let intro = viewModel?.intro {
setupIntro(text: intro)
}
}
}
extension OldTalkPageHeaderView: Themeable {
func apply(theme: Theme) {
self.theme = theme
titleTextView.backgroundColor = theme.colors.paperBackground
headerLabel.textColor = theme.colors.secondaryText
titleTextView.textColor = theme.colors.primaryText
infoLabel.textColor = theme.colors.secondaryText
introTextView.textColor = theme.colors.primaryText
introTextView.backgroundColor = theme.colors.paperBackground
backgroundColor = theme.colors.paperBackground
}
}
// MARK: UITextViewDelegate
extension OldTalkPageHeaderView: UITextViewDelegate {
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
delegate?.tappedLink(URL, headerView: self, sourceView: textView, sourceRect: textView.frame(of: characterRange))
return false
}
}