Skip to content

Commit 397ea08

Browse files
jacobsimeonJacob Morris
authored and
Jacob Morris
committed
Replace SwiftUI article view with UIKit-based version
- Textile: add support paragraph and text alignment - Resurrect code for generating NSAttributedString from TextContent - Extend Header (particle component) to provide a Style for each header level - Extend item to provide text alignment based on article language - Extract Style+ReaderSettings This is a little helper extension that provides an adjusted style based on the given reader settings - Add UICollectionView+registration A small extension that makes it a tad bit easier to register/dequeue cell classes in a collection view. - Add TextContentCell This is a collection view cell that contains a single UITextView. The text view will be populated with an attributed string and displayed in an article view. - Add EmptyCell This is a placeholder cell that is used for unsupported/unrecognized particle components. - Remove SwiftUI implementation of Article view - Rename PocketKit/Reader -> PocketKit/ReaderSettings This folder only contains the ReaderSettingsView and corresponding ReaderSettings model - Remove unnecessary usages of `@available` and `canImport` - Update UI tests to scroll down after verifying each particle component - Allow Xcode to update Package.resolved
1 parent cad02fb commit 397ea08

24 files changed

+412
-338
lines changed

Pocket.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"object": {
33
"pins": [
44
{
5-
"package": "apollo-ios",
5+
"package": "Apollo",
66
"repositoryURL": "https://github.com/apollographql/apollo-ios.git",
77
"state": {
88
"branch": null,
@@ -56,7 +56,7 @@
5656
}
5757
},
5858
{
59-
"package": "sentry-cocoa",
59+
"package": "Sentry",
6060
"repositoryURL": "https://github.com/getsentry/sentry-cocoa.git",
6161
"state": {
6262
"branch": null,

PocketKit/Sources/PocketKit/Article/ArticleComponentView.swift

-73
This file was deleted.

PocketKit/Sources/PocketKit/Article/ArticleView.swift

-41
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import UIKit
2+
import Sync
3+
import Textile
4+
import Combine
5+
6+
7+
private extension Style {
8+
static let bodyText: Style = .body.serif
9+
static let byline: Style = .body.sansSerif.with(color: .ui.grey2)
10+
static let copyright: Style = .body.serif.with(size: .p4).with(slant: .italic)
11+
static let message: Style = .body.serif.with(slant: .italic)
12+
static let quote: Style = .body.serif.with(slant: .italic)
13+
static let title: Style = .header.sansSerif.h1
14+
static let pre: Style = .body.sansSerif
15+
}
16+
17+
class ArticleViewController: UICollectionViewController {
18+
var item: Item? {
19+
didSet {
20+
DispatchQueue.main.async {
21+
self.collectionView.reloadData()
22+
}
23+
}
24+
}
25+
26+
private var article: Article? {
27+
item?.particle
28+
}
29+
30+
private let readerSettings: ReaderSettings
31+
32+
private var subscriptions: [AnyCancellable] = []
33+
34+
init(readerSettings: ReaderSettings) {
35+
self.readerSettings = readerSettings
36+
37+
let layout = UICollectionViewFlowLayout()
38+
layout.minimumLineSpacing = 8
39+
super.init(collectionViewLayout: layout)
40+
41+
collectionView.accessibilityIdentifier = "article-view"
42+
collectionView.register(cellClass: TextContentCell.self)
43+
collectionView.register(cellClass: EmptyCell.self)
44+
45+
readerSettings.objectWillChange.sink { _ in
46+
DispatchQueue.main.async {
47+
self.collectionView.reloadData()
48+
}
49+
}.store(in: &subscriptions)
50+
}
51+
52+
required init?(coder: NSCoder) {
53+
fatalError("Unable to instantiate \(Self.self) from xib/storyboard")
54+
}
55+
56+
private func attributedText(textContent: TextContent, style: Style) -> NSAttributedString {
57+
let adjustedStyle = style
58+
.with(settings: readerSettings)
59+
.with(paragraph: style.paragraph.with(
60+
alignment: item?.textAlignment ?? .left
61+
))
62+
63+
return textContent.attributedString(baseStyle: adjustedStyle)
64+
}
65+
}
66+
67+
extension ArticleViewController {
68+
override func collectionView(
69+
_ collectionView: UICollectionView,
70+
numberOfItemsInSection section: Int
71+
) -> Int {
72+
return article?.content.count ?? 0
73+
}
74+
75+
override func collectionView(
76+
_ collectionView: UICollectionView,
77+
cellForItemAt indexPath: IndexPath
78+
) -> UICollectionViewCell {
79+
switch article?.content[indexPath.item] {
80+
case .bodyText(let bodyText):
81+
return textCell(at: indexPath, textContent: bodyText.text, style: .bodyText)
82+
case .byline(let byline):
83+
return textCell(at: indexPath, textContent: byline.text, style: .byline)
84+
case .copyright(let copyright):
85+
return textCell(at: indexPath, textContent: copyright.text, style: .copyright)
86+
case .header(let header):
87+
return textCell(at: indexPath, textContent: header.text, style: header.style)
88+
case .message(let message):
89+
return textCell(at: indexPath, textContent: message.text, style: .message)
90+
case .pre(let pre):
91+
return textCell(at: indexPath, textContent: pre.text, style: .pre)
92+
case .publisherMessage(let publisherMessage):
93+
return textCell(at: indexPath, textContent: publisherMessage.text, style: .message)
94+
case .quote(let quote):
95+
return textCell(at: indexPath, textContent: quote.text, style: .quote)
96+
case .title(let title):
97+
return textCell(at: indexPath, textContent: title.text, style: .title)
98+
case .none, .image, .unsupported:
99+
return emptyCell(at: indexPath)
100+
}
101+
}
102+
103+
private func textCell(
104+
at indexPath: IndexPath,
105+
textContent: TextContent,
106+
style: Style
107+
) -> TextContentCell {
108+
let cell: TextContentCell = collectionView.dequeueCell(for: indexPath)
109+
cell.attributedText = attributedText(textContent: textContent, style: style)
110+
111+
return cell
112+
}
113+
114+
func emptyCell(at indexPath: IndexPath) -> EmptyCell {
115+
return collectionView.dequeueCell(for: indexPath)
116+
}
117+
}
118+
119+
extension ArticleViewController: UICollectionViewDelegateFlowLayout {
120+
func collectionView(
121+
_ collectionView: UICollectionView,
122+
layout collectionViewLayout: UICollectionViewLayout,
123+
sizeForItemAt indexPath: IndexPath
124+
) -> CGSize {
125+
switch article?.content[indexPath.item] {
126+
case .bodyText(let bodyText):
127+
return textSize(for: bodyText.text, style: .bodyText)
128+
case .byline(let byline):
129+
return textSize(for: byline.text, style: .byline)
130+
case .copyright(let copyright):
131+
return textSize(for: copyright.text, style: .copyright)
132+
case .header(let header):
133+
return textSize(for: header.text, style: header.style)
134+
case .message(let message):
135+
return textSize(for: message.text, style: .message)
136+
case .pre(let pre):
137+
return textSize(for: pre.text, style: .pre)
138+
case .publisherMessage(let publisherMessage):
139+
return textSize(for: publisherMessage.text, style: .message)
140+
case .quote(let quote):
141+
return textSize(for: quote.text, style: .quote)
142+
case .title(let title):
143+
return textSize(for: title.text, style: .title)
144+
case .none, .image, .unsupported:
145+
return .zero
146+
}
147+
}
148+
149+
private func textSize(for textContent: TextContent, style: Style) -> CGSize {
150+
let text = attributedText(textContent: textContent, style: style)
151+
152+
let maxWidth = collectionView.frame.width
153+
return CGSize(
154+
width: maxWidth,
155+
height: text.boundingRect(
156+
with: CGSize(width: maxWidth, height: .infinity),
157+
options: [.usesFontLeading, .usesLineFragmentOrigin],
158+
context: nil
159+
).size.height.rounded(.up)
160+
)
161+
}
162+
}
163+

PocketKit/Sources/PocketKit/Article/ArticleViewState.swift

-19
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import UIKit
2+
3+
4+
class EmptyCell: UICollectionViewCell {
5+
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import UIKit
2+
3+
4+
class TextContentCell: UICollectionViewCell {
5+
private let textView = UITextView()
6+
7+
override init(frame: CGRect) {
8+
super.init(frame: frame)
9+
10+
textView.textContainerInset = .zero
11+
textView.isEditable = false
12+
textView.isScrollEnabled = false
13+
14+
contentView.addSubview(textView)
15+
textView.translatesAutoresizingMaskIntoConstraints = false
16+
17+
NSLayoutConstraint.activate([
18+
textView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
19+
textView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
20+
textView.topAnchor.constraint(equalTo: contentView.topAnchor),
21+
textView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
22+
])
23+
}
24+
25+
var attributedText: NSAttributedString? {
26+
get {
27+
textView.attributedText
28+
}
29+
set {
30+
textView.attributedText = newValue
31+
}
32+
}
33+
34+
required init?(coder: NSCoder) {
35+
fatalError("Unable to instantiate \(Self.self) from xib/storyboard")
36+
}
37+
}

0 commit comments

Comments
 (0)