Skip to content

Commit 051778c

Browse files
committed
Add Tapestry
1 parent 5e43873 commit 051778c

File tree

7 files changed

+185
-4
lines changed

7 files changed

+185
-4
lines changed

Images/tapestry.png

985 KB
Loading

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## News
22

3-
iOS news app in the style of Apollo, Apple News, Artifact, Axios, BBC, Bluesky, Cash App, CNN, Facebook, Facebook News, FastNews, Flipboard, Instagram, Lil News, NBC News, Particle, Reddit, Robinhood, The New York Times, The Washington Post, The Wall Street Journal, Threads, UIKit, X (Twitter) :newspaper:
3+
iOS news app in the style of Apollo, Apple News, Artifact, Axios, BBC, Bluesky, Cash App, CNN, Facebook, Facebook News, FastNews, Flipboard, Instagram, Lil News, NBC News, Particle, Reddit, Robinhood, Tapestry, The New York Times, The Washington Post, The Wall Street Journal, Threads, UIKit, X (Twitter) :newspaper:
44

55
![](https://github.com/dkhamsing/news/actions/workflows/xcodebuild-ios14.yml/badge.svg) ![](https://github.com/dkhamsing/news/actions/workflows/xcodebuild-ios15.yml/badge.svg) ![](https://github.com/dkhamsing/news/actions/workflows/xcodebuild-ios16.yml/badge.svg) ![](https://github.com/dkhamsing/news/actions/workflows/xcodebuild-ios17.yml/badge.svg) ![](https://github.com/dkhamsing/news/actions/workflows/xcodebuild-tvos13.yml/badge.svg)
66

@@ -9,7 +9,7 @@ iOS news app in the style of Apollo, Apple News, Artifact, Axios, BBC, Bluesky,
99

1010
<img width=300 src=Images/demo.gif>
1111

12-
<img src=Images/apollo.png height=400> <img src=Images/applenews.png height=400> <img src=Images/artifact.png height=400> <img src=Images/axios.png height=400> <img src=Images/bbc.png height=400> <img src=Images/bluesky.png height=400> <img src=Images/cashapp.png height=400> <img src=Images/cnn.png height=400> <img src=Images/facebook.png height=400> <img src=Images/facebooknews.png height=400> <img src=Images/flipboard.png height=400> <img src=Images/fastnews.png height=400> <img src=Images/instagram.png height=400> <img src=Images/lilnews.png height=400> <img src=Images/nbcnews.png height=400> <img src=Images/particle2.png height=400> <img src=Images/reddit.png height=400> <img src=Images/robinhood.png height=400> <img src=Images/nyt.png height=400> <img src=Images/threads.png height=400> <img src=Images/wsj.png height=400> <img src=Images/washingtonpost.png height=400> <img src=Images/uikit.png height=400> <img src=Images/x.png height=400>
12+
<img src=Images/apollo.png height=400> <img src=Images/applenews.png height=400> <img src=Images/artifact.png height=400> <img src=Images/axios.png height=400> <img src=Images/bbc.png height=400> <img src=Images/bluesky.png height=400> <img src=Images/cashapp.png height=400> <img src=Images/cnn.png height=400> <img src=Images/facebook.png height=400> <img src=Images/facebooknews.png height=400> <img src=Images/flipboard.png height=400> <img src=Images/fastnews.png height=400> <img src=Images/instagram.png height=400> <img src=Images/lilnews.png height=400> <img src=Images/nbcnews.png height=400> <img src=Images/particle2.png height=400> <img src=Images/reddit.png height=400> <img src=Images/robinhood.png height=400> <img src=Images/tapestry.png height=400> <img src=Images/nyt.png height=400> <img src=Images/threads.png height=400> <img src=Images/wsj.png height=400> <img src=Images/washingtonpost.png height=400> <img src=Images/uikit.png height=400> <img src=Images/x.png height=400>
1313

1414
#### Bonus
1515

@@ -42,6 +42,7 @@ iOS news app in the style of Apollo, Apple News, Artifact, Axios, BBC, Bluesky,
4242
- [Particle](https://particle.news)
4343
- [Reddit](https://www.reddit.com)
4444
- [Robinhood](https://robinhood.com)
45+
- [Tapestry](https://usetapestry.com)
4546
- [The New York Times](https://www.nytimes.com)
4647
- [The Wall Street Journal](https://www.wsj.com)
4748
- [The Washington Post](https://www.washingtonpost.com)

Xcode/TheNews.xcodeproj/project.pbxproj

+16
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@
9595
D74AB4E72D6AA5CE008DF432 /* ParticleHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74AB4E52D6AA5CE008DF432 /* ParticleHandler.swift */; };
9696
D7563B882ADC56390093C3FF /* ThreadsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7563B862ADC56390093C3FF /* ThreadsCell.swift */; };
9797
D7563B892ADC56390093C3FF /* ThreadsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7563B872ADC56390093C3FF /* ThreadsHandler.swift */; };
98+
D7BC21C92D726BE10050836A /* TapestryHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7BC21C72D726BE10050836A /* TapestryHandler.swift */; };
99+
D7BC21CA2D726BE10050836A /* TapestryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7BC21C82D726BE10050836A /* TapestryCell.swift */; };
98100
D7E879102D7132D600370650 /* BlueskyCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E8790E2D7132D600370650 /* BlueskyCell.swift */; };
99101
D7E879112D7132D600370650 /* BlueskyHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E8790F2D7132D600370650 /* BlueskyHandler.swift */; };
100102
/* End PBXBuildFile section */
@@ -216,6 +218,8 @@
216218
D74AB4E52D6AA5CE008DF432 /* ParticleHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParticleHandler.swift; sourceTree = "<group>"; };
217219
D7563B862ADC56390093C3FF /* ThreadsCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadsCell.swift; sourceTree = "<group>"; };
218220
D7563B872ADC56390093C3FF /* ThreadsHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadsHandler.swift; sourceTree = "<group>"; };
221+
D7BC21C72D726BE10050836A /* TapestryHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TapestryHandler.swift; sourceTree = "<group>"; };
222+
D7BC21C82D726BE10050836A /* TapestryCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TapestryCell.swift; sourceTree = "<group>"; };
219223
D7E8790E2D7132D600370650 /* BlueskyCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlueskyCell.swift; sourceTree = "<group>"; };
220224
D7E8790F2D7132D600370650 /* BlueskyHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlueskyHandler.swift; sourceTree = "<group>"; };
221225
/* End PBXFileReference section */
@@ -502,6 +506,7 @@
502506
65A01C51258BD4ED007565AB /* NBCNews */,
503507
6507852E2587038E001ABDAE /* NYT */,
504508
D74AB4E32D6AA5CE008DF432 /* Particle */,
509+
D7BC21C62D726BE10050836A /* Tapestry */,
505510
6507851D2587038E001ABDAE /* Reddit */,
506511
657B6580277C13C8002C955A /* Robinhood */,
507512
D7563B852ADC56390093C3FF /* Threads */,
@@ -593,6 +598,15 @@
593598
path = Threads;
594599
sourceTree = "<group>";
595600
};
601+
D7BC21C62D726BE10050836A /* Tapestry */ = {
602+
isa = PBXGroup;
603+
children = (
604+
D7BC21C72D726BE10050836A /* TapestryHandler.swift */,
605+
D7BC21C82D726BE10050836A /* TapestryCell.swift */,
606+
);
607+
path = Tapestry;
608+
sourceTree = "<group>";
609+
};
596610
D7E8790D2D7132D600370650 /* Bluesky */ = {
597611
isa = PBXGroup;
598612
children = (
@@ -797,6 +811,7 @@
797811
6507853A2587038F001ABDAE /* BBCCell.swift in Sources */,
798812
65293A45244172F2002E5BC3 /* NewsViewController.swift in Sources */,
799813
65D47D9D25941149009701BB /* FacebookNewsCell.swift in Sources */,
814+
D7BC21C92D726BE10050836A /* TapestryHandler.swift in Sources */,
800815
650D8DBB258D5C7F006FFC70 /* ApolloCell.swift in Sources */,
801816
65A01C5F258BD51E007565AB /* NBCNewsHandler.swift in Sources */,
802817
650785512587038F001ABDAE /* CNNHandler.swift in Sources */,
@@ -814,6 +829,7 @@
814829
650785452587038F001ABDAE /* FlipboardCell.swift in Sources */,
815830
6507853B2587038F001ABDAE /* FacebookHandler.swift in Sources */,
816831
6507853E2587038F001ABDAE /* FastNewsCell.swift in Sources */,
832+
D7BC21CA2D726BE10050836A /* TapestryCell.swift in Sources */,
817833
650785482587038F001ABDAE /* NewsTableHandler.swift in Sources */,
818834
65A79AB8277BD5D800505313 /* CashAppCell.swift in Sources */,
819835
655C96FE258E5E7D00095493 /* AppleNewsCellLarge.swift in Sources */,

Xcode/TheNews/NewsViewController.swift

+5
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class NewsViewController: UIViewController {
3030
var handlerParticle = ParticleHandler()
3131
var handlerReddit = RedditHandler()
3232
var handlerRobinhood = RobinhoodHandler()
33+
var handlerTapestry = TapestryHandler()
3334
var handlerThreads = ThreadsHandler()
3435
var handlerTwitter = TwitterHandler()
3536
var handlerUIKit = UIKitHandler()
@@ -76,6 +77,7 @@ class NewsViewController: UIViewController {
7677
handlerParticle.articles = articles
7778
handlerReddit.articles = articles
7879
handlerRobinhood.articles = articles
80+
handlerTapestry.articles = articles
7981
handlerThreads.articles = articles
8082
handlerTwitter.articles = articles
8183
handlerUIKit.articles = articles
@@ -220,6 +222,9 @@ private extension NewsViewController {
220222
case .robinhood:
221223
viewTable.dataSource = handlerRobinhood
222224
viewTable.delegate = handlerRobinhood
225+
case .tapestry:
226+
viewTable.dataSource = handlerTapestry
227+
viewTable.delegate = handlerTapestry
223228
case .threads:
224229
viewTable.dataSource = handlerThreads
225230
viewTable.delegate = handlerThreads

Xcode/TheNews/NewsViewModel.swift

+5-2
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ class NewsViewModel {
9494
particle,
9595
reddit,
9696
robinhood,
97+
tapestry,
9798
thenyt,
9899
thewashingtonpost,
99100
thewsj,
@@ -127,7 +128,7 @@ class NewsViewModel {
127128
return "UIKit"
128129
case .bbc, .cnn:
129130
return self.rawValue.uppercased()
130-
case .apollo, .artifact, .axios, .bluesky, .facebook, .flipboard, .instagram, .reddit, .particle, .robinhood, .threads:
131+
case .apollo, .artifact, .axios, .bluesky, .facebook, .flipboard, .instagram, .reddit, .particle, .robinhood, .tapestry, .threads:
131132
return self.rawValue.capitalized
132133
}
133134
}
@@ -181,7 +182,9 @@ class NewsViewModel {
181182
return [ParticleCell.identifier]
182183
case .robinhood:
183184
return [RobinhoodCell.identifier, RobinhoodCellLarge.identifier]
184-
case .threads:
185+
case .tapestry:
186+
return [TapestryCell.identifier]
187+
case .threads:
185188
return [ThreadsCell.identifier]
186189
case .twitter:
187190
return [TwitterCell.identifier]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import UIKit
2+
3+
class TapestryCell: NewsCell {
4+
5+
static let identifier: String = "TapestryCell"
6+
private static let logoSize = CGSize(width: 42, height: 42)
7+
8+
let author = UILabel()
9+
let container = UIView()
10+
let url = UILabel()
11+
12+
override func config() {
13+
super.config()
14+
15+
let mainColor = rand
16+
17+
logo.layer.cornerRadius = TapestryCell.logoSize.width / 2
18+
logo.layer.masksToBounds = true
19+
20+
author.font = .preferredFont(forTextStyle: .headline)
21+
22+
ago.textColor = mainColor
23+
ago.font = .preferredFont(forTextStyle: .body)
24+
25+
url.textColor = mainColor
26+
url.font = .boldSystemFont(ofSize: 13)
27+
28+
container.layer.cornerRadius = 10
29+
container.layer.masksToBounds = true
30+
container.backgroundColor = mainColor.withAlphaComponent(0.3)
31+
32+
title.font = .boldSystemFont(ofSize: 14)
33+
34+
source.font = .preferredFont(forTextStyle: .headline)
35+
source.textColor = mainColor
36+
37+
let containerView = UIView()
38+
containerView.backgroundColor = mainColor.withAlphaComponent(0.2)
39+
containerView.layer.cornerRadius = 10
40+
containerView.clipsToBounds = true
41+
42+
contentView.addSubviewForAutoLayout(containerView)
43+
contentView.addSubviewForAutoLayout(logo)
44+
45+
NSLayoutConstraint.activate([
46+
containerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
47+
containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10),
48+
49+
containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 25),
50+
containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
51+
52+
logo.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
53+
logo.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
54+
logo.heightAnchor.constraint(equalToConstant: TapestryCell.logoSize.height),
55+
logo.widthAnchor.constraint(equalToConstant: TapestryCell.logoSize.width),
56+
])
57+
58+
let stripe = UIView()
59+
stripe.backgroundColor = source.textColor
60+
61+
[stripe, source, author, ago, summary, container].forEach {
62+
containerView.addSubviewForAutoLayout($0)
63+
}
64+
65+
let imageHeight: CGFloat = 164
66+
let inset: CGFloat = 10
67+
NSLayoutConstraint.activate([
68+
stripe.topAnchor.constraint(equalTo: containerView.topAnchor),
69+
stripe.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
70+
stripe.widthAnchor.constraint(equalToConstant: 10),
71+
72+
source.topAnchor.constraint(equalTo: containerView.topAnchor, constant: inset + 5),
73+
source.leadingAnchor.constraint(equalTo: logo.trailingAnchor, constant: inset),
74+
75+
author.topAnchor.constraint(equalTo: source.bottomAnchor),
76+
author.leadingAnchor.constraint(equalTo: source.leadingAnchor),
77+
78+
ago.topAnchor.constraint(equalTo: source.topAnchor),
79+
ago.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -inset - 8),
80+
81+
summary.topAnchor.constraint(equalTo: author.bottomAnchor, constant: inset - 5),
82+
summary.leadingAnchor.constraint(equalTo: source.leadingAnchor),
83+
summary.trailingAnchor.constraint(equalTo: ago.trailingAnchor),
84+
85+
container.topAnchor.constraint(equalTo: summary.bottomAnchor, constant: inset),
86+
container.leadingAnchor.constraint(equalTo: logo.trailingAnchor, constant: inset),
87+
container.trailingAnchor.constraint(equalTo: ago.trailingAnchor),
88+
containerView.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: inset + 5)
89+
])
90+
91+
[articleImageView, url, title].forEach {
92+
container.addSubviewForAutoLayout($0)
93+
}
94+
95+
NSLayoutConstraint.activate([
96+
articleImageView.topAnchor.constraint(equalTo: container.topAnchor, constant: 0),
97+
articleImageView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
98+
container.trailingAnchor.constraint(equalTo: articleImageView.trailingAnchor),
99+
articleImageView.heightAnchor.constraint(equalToConstant: imageHeight),
100+
101+
title.topAnchor.constraint(equalTo: articleImageView.bottomAnchor, constant: inset - 4),
102+
title.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: inset),
103+
container.trailingAnchor.constraint(equalTo: title.trailingAnchor, constant: inset),
104+
105+
url.topAnchor.constraint(equalTo: title.bottomAnchor, constant: 4),
106+
url.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: inset),
107+
url.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -inset),
108+
container.bottomAnchor.constraint(equalTo: url.bottomAnchor, constant: inset - 4)
109+
])
110+
}
111+
112+
var rand: UIColor {
113+
let colors: [UIColor] = [
114+
.systemBlue,
115+
.systemGreen,
116+
.systemRed,
117+
.systemGray,
118+
.systemPink,
119+
.systemYellow,
120+
.systemOrange
121+
]
122+
123+
if let color = colors.randomElement() { return color }
124+
return .systemBlue
125+
}
126+
127+
func load(article: Article) {
128+
author.text = article.author ?? article.source?.name
129+
summary.text = article.description
130+
title.text = article.titleDisplay
131+
ago.text = article.ago
132+
source.text = article.source?.name
133+
url.text = article.url?.host?.replacingOccurrences(of: "www.", with: "")
134+
load(urlString: article.urlToImage, downloader: ImageDownloader.shared)
135+
loadLogo(urlString: article.urlToSourceLogo, size: TapestryCell.logoSize)
136+
}
137+
138+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import UIKit
2+
3+
class TapestryHandler: NewsTableHandler {
4+
5+
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
6+
return articles.count
7+
}
8+
9+
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
10+
let cell = tableView.dequeueReusableCell(withIdentifier: TapestryCell.identifier) as! TapestryCell
11+
12+
let article = articles[indexPath.row]
13+
cell.load(article: article)
14+
15+
return cell
16+
}
17+
18+
}

0 commit comments

Comments
 (0)