|
| 1 | +import UIKit |
| 2 | + |
| 3 | +/// A mock post content view used in previews to simulate real article content |
| 4 | +/// below the `ReaderPostHeaderView`. |
| 5 | +@available(iOS 17, *) |
| 6 | +final class ReaderMockPostContentView: UIView { |
| 7 | + |
| 8 | + private let textView = UITextView() |
| 9 | + |
| 10 | + override init(frame: CGRect) { |
| 11 | + super.init(frame: frame) |
| 12 | + |
| 13 | + textView.isEditable = false |
| 14 | + textView.isScrollEnabled = false |
| 15 | + textView.backgroundColor = .clear |
| 16 | + textView.adjustsFontForContentSizeCategory = true |
| 17 | + textView.textContainerInset = UIEdgeInsets(top: 8, left: 12, bottom: 16, right: 12) |
| 18 | + |
| 19 | + addSubview(textView) |
| 20 | + textView.pinEdges() |
| 21 | + } |
| 22 | + |
| 23 | + required init?(coder: NSCoder) { |
| 24 | + fatalError("init(coder:) has not been implemented") |
| 25 | + } |
| 26 | + |
| 27 | + func apply(_ setting: ReaderDisplaySettings) { |
| 28 | + textView.attributedText = Self.makePostContent(with: setting) |
| 29 | + } |
| 30 | + |
| 31 | + private static func makePostContent(with setting: ReaderDisplaySettings) -> NSAttributedString { |
| 32 | + let colors = setting.color |
| 33 | + let bodyFont = setting.font(with: .body) |
| 34 | + let headingFont = setting.font(with: .title3, weight: .bold) |
| 35 | + |
| 36 | + let bodyParagraph = NSMutableParagraphStyle() |
| 37 | + bodyParagraph.lineSpacing = 4 |
| 38 | + bodyParagraph.paragraphSpacing = 12 |
| 39 | + |
| 40 | + let headingParagraph = NSMutableParagraphStyle() |
| 41 | + headingParagraph.lineSpacing = 4 |
| 42 | + headingParagraph.paragraphSpacing = 4 |
| 43 | + headingParagraph.paragraphSpacingBefore = 8 |
| 44 | + |
| 45 | + let body: [NSAttributedString.Key: Any] = [ |
| 46 | + .font: bodyFont, |
| 47 | + .foregroundColor: colors.foreground, |
| 48 | + .paragraphStyle: bodyParagraph |
| 49 | + ] |
| 50 | + let heading: [NSAttributedString.Key: Any] = [ |
| 51 | + .font: headingFont, |
| 52 | + .foregroundColor: colors.foreground, |
| 53 | + .paragraphStyle: headingParagraph |
| 54 | + ] |
| 55 | + |
| 56 | + let result = NSMutableAttributedString() |
| 57 | + result.append(NSAttributedString(string: "We love working with talented artists from around the world, and this year, we invited Cinta to capture Automattic's holiday spirit in an illustration for our holiday card. We're excited to introduce Cinta and share her wonderful work with you!\n", attributes: body)) |
| 58 | + result.append(NSAttributedString(string: "How would you describe your artistic style in three words, and why those three?\n", attributes: heading)) |
| 59 | + result.append(NSAttributedString(string: "Colorful, conceptual, and playful. I like combining strong visual impact with ideas that invite interpretation and a sense of joy.\n", attributes: body)) |
| 60 | + result.append(NSAttributedString(string: "What draws you to your medium?\n", attributes: heading)) |
| 61 | + result.append(NSAttributedString(string: "I'm drawn to traditional techniques such as ink on paper because drawing with brushes and a fluid medium like ink allows me to give the line a strong sense of expressiveness and texture. I enjoy working with the imperfections and unexpected accidents of analog processes, as they add a sense of soul and authenticity to the final illustration. I then apply color digitally, combining the warmth of traditional media with the flexibility of digital tools.", attributes: body)) |
| 62 | + return result |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +// MARK: - ReaderPostHeaderPreviewController |
| 67 | + |
| 68 | +@available(iOS 17, *) |
| 69 | +final class ReaderPostHeaderPreviewController: UIViewController { |
| 70 | + private let scrollView = UIScrollView() |
| 71 | + private let headerView = ReaderPostHeaderView() |
| 72 | + private let contentView = ReaderMockPostContentView() |
| 73 | + private let viewModel: ReaderPostHeaderView.ViewModel |
| 74 | + private var currentSetting = ReaderDisplaySettings.standard |
| 75 | + |
| 76 | + init(viewModel: ReaderPostHeaderView.ViewModel) { |
| 77 | + self.viewModel = viewModel |
| 78 | + super.init(nibName: nil, bundle: nil) |
| 79 | + } |
| 80 | + |
| 81 | + required init?(coder: NSCoder) { |
| 82 | + fatalError("init(coder:) has not been implemented") |
| 83 | + } |
| 84 | + |
| 85 | + override func viewDidLoad() { |
| 86 | + super.viewDidLoad() |
| 87 | + |
| 88 | + let stack = UIStackView(arrangedSubviews: [headerView, contentView]) |
| 89 | + stack.axis = .vertical |
| 90 | + |
| 91 | + view.addSubview(scrollView) |
| 92 | + scrollView.pinEdges() |
| 93 | + |
| 94 | + scrollView.addSubview(stack) |
| 95 | + stack.pinEdges() |
| 96 | + stack.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true |
| 97 | + |
| 98 | + headerView.configure(with: viewModel) |
| 99 | + applyDisplaySetting(.standard) |
| 100 | + |
| 101 | + navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "chevron.backward"), style: .plain, target: nil, action: nil) |
| 102 | + navigationItem.rightBarButtonItems = [ |
| 103 | + UIBarButtonItem(image: UIImage(systemName: "ellipsis"), style: .plain, target: nil, action: nil), |
| 104 | + UIBarButtonItem(image: UIImage(systemName: "square.and.arrow.up"), style: .plain, target: nil, action: nil), |
| 105 | + ] |
| 106 | + |
| 107 | + toolbarItems = [ |
| 108 | + UIBarButtonItem(systemItem: .flexibleSpace), |
| 109 | + UIBarButtonItem(title: currentSetting.color.label, menu: makeThemeMenu()), |
| 110 | + ] |
| 111 | + } |
| 112 | + |
| 113 | + override func viewWillAppear(_ animated: Bool) { |
| 114 | + super.viewWillAppear(animated) |
| 115 | + navigationController?.setToolbarHidden(false, animated: false) |
| 116 | + } |
| 117 | + |
| 118 | + private func applyDisplaySetting(_ setting: ReaderDisplaySettings) { |
| 119 | + currentSetting = setting |
| 120 | + headerView.apply(setting) |
| 121 | + contentView.apply(setting) |
| 122 | + view.backgroundColor = setting.color.background |
| 123 | + scrollView.backgroundColor = setting.color.background |
| 124 | + } |
| 125 | + |
| 126 | + private func makeThemeMenu() -> UIMenu { |
| 127 | + let colorActions = ReaderDisplaySettings.Color.allCases.map { color in |
| 128 | + UIAction(title: color.label, state: currentSetting.color == color ? .on : .off) { [weak self] _ in |
| 129 | + guard let self else { return } |
| 130 | + let setting = ReaderDisplaySettings(color: color, font: self.currentSetting.font, size: self.currentSetting.size) |
| 131 | + self.applyDisplaySetting(setting) |
| 132 | + self.toolbarItems?[1] = UIBarButtonItem(title: "Theme", menu: self.makeThemeMenu()) |
| 133 | + } |
| 134 | + } |
| 135 | + return UIMenu(title: "Theme", children: colorActions) |
| 136 | + } |
| 137 | +} |
0 commit comments