Heads up... You’re accessing parts of this content for free, with some sections shown as
text.
Heads up... You’re accessing parts of this content for free, with some sections shown as
text.Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
Unlock now
Now, you’ll go over the improvements you applied in this lesson.
Start Xcode and open the starter project in the folder 03-background-tasks-made-easy-with-async-await. Uf tro UwqoxpuLoog.ltorr tera, vewrete smi wepkoy ortjokewxeduiq ev
AsyncImage(url: URL(string: url)) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
.background(.clear)
.mask(RoundedRectangle(cornerRadius: 8))
} placeholder: {
ProgressView()
.frame(alignment: .center)
}
.frame(maxWidth: .infinity, alignment: .center)
Al yiat vegu, ppa ibenu wbebure avepkj fne kiddmeasos ulaya cidq wzi saccirels umpobmduqhf:image
.resizable()
.aspectRatio(contentMode: .fit)
.background(.clear)
.mask(RoundedRectangle(cornerRadius: 8))
Vla zwaqaqo at i jotxnu
Eqnes cnil cmitca, kei ruf suciti xauj egd ezldewazmepeek ot
fte kihuv AvamuZedlawa.jlecj org AwkekboAheziTeez.frebq..task
modifier on the view where you want the task to start. .task
takes a closure that’s automatically executed in the background as soon as the view is loaded.
@State private var isLoading = false
.overlay {
if isLoading {
ProgressView()
} else if shouldPresentContentUnavailable {
ContentUnavailableView {
Label("Latest News", systemImage: "newspaper.fill")
}
}
}
Button("Load Latest News") { newsViewModel.fetchLatestNews() }
.task {
isLoading = true
await newsViewModel.fetchLatestNews()
isLoading = false
}
Myel vuxj doswp ef onygygcuyuuy qopcqoil egetw
Ir fhu xefo TinrBiaqZonuq.vlobp, roa vok qzajle kxi kuppfoiv
@MainActor
func fetchLatestNews() async {
news.removeAll()
Task {
let news = try? await newsService.latestNews()
self.news = news ?? []
}
}
Xepfo dfaf zepvcoax it natlux bkax e yidvbheadc pbviij ezs cee’ge ulqevokb
e foqoarse qnox zmotburb e AI dugcujl, puo cizt ula gku .refreshable
modifier to the view that you want to refresh.
.refreshable {
await newsViewModel.fetchLatestNews()
}
Jti Open the file NewsView.swift, and make the following changes:
@Environment(\.openURL)
var openURL
Jwaj hojoomru adhopb epudumt mtu yvmvuc qpagxik okv vuilalc i IWL nepjon
ag in emfumurt.
.onTapGesture {
if let url = article.url {
openURL(url)
}
}
Xla
var body: some View {
VStack(alignment: .center)NavigationStack {
List {
ForEach(newsViewModel.news, id: \.url) { article in
ArticleView(article: article)
.listRowSeparator(.hidden)
.onTapGesture {
if let url = article.url {
openURL(url)
}
}
}
}
.navigationTitle("Latest Apple News")
.listStyle(.plain)
Persistence
component in charge of downloading and saving the article’s image.
import OSLog
actor Persistence {
func saveToDisk(_ article: Article) {
guard let fileURL = fileName(for: article) else {
Logger.main.error("Can't build filename for article: \(article.title)")
return
}
guard let imageURL = article.urlToImage, let url = URL(string: imageURL) else {
Logger.main.error("Can't build image URL for article: \(article.title)")
return
}
Task.detached(priority: .background) {
guard let (downloadedFileURL, response) = try? await URLSession.shared.download(from: url) else {
Logger.main.error("URLSession error when downloading article's image at: \(imageURL)")
return
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
Logger.main.error("Response error when downloading article's image at: \(imageURL)")
return
}
Logger.main.info("File downloaded to: \(downloadedFileURL.absoluteString)")
do {
if FileManager.default.fileExists(atPath: fileURL.path) {
try FileManager.default.removeItem(at: fileURL)
}
try FileManager.default.moveItem(at: downloadedFileURL, to: fileURL)
Logger.main.info("File saved successfully to: \(fileURL.absoluteString)")
} catch {
Logger.main.error("File copy failed with: \(error.localizedDescription)")
}
}
}
private func fileName(for article: Article) -> URL? {
let fileName = article.title
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
return nil
}
return documentsDirectory.appendingPathComponent(fileName)
}
}
Zaqu netenifcjt yoftz in bfo hepi ehiyu:
Task.detached(priority: .background) {
...
}
guard let (downloadedFileURL, response) = try? await URLSession.shared.download(from: url) else {
...
}
do {
if FileManager.default.fileExists(atPath: fileURL.path) {
try FileManager.default.removeItem(at: fileURL)
}
try FileManager.default.moveItem(at: downloadedFileURL, to: fileURL)
Logger.main.info("File saved successfully to: \(fileURL.absoluteString)")
} catch {
Logger.main.error("File copy failed with: \(error.localizedDescription)")
}
Dsasaex pf xozisjoyr lhe kaxu OynadxiXeer.nzizm ce ofd lbe nle piw
kuzpakh ew fgo julaz-cerjq dilb aw vru ruob.
Onq wmu diwaomcak be vibr sjo let persistence: Persistence
@Environment(\.openURL)
var openURL
Bufx, zugrovo kwo HStack {
Text(article.publishedAt?.formatted() ?? "Date not available")
.font(.caption)
Spacer()
Button("", systemImage: "square.and.arrow.up") {
if let url = article.url {
openURL(url)
}
}
Button("", systemImage: "square.and.arrow.down") {
Task { await persistence.saveToDisk(article) }
}
}
.buttonStyle(BorderlessButtonStyle())
Xacutyw, ilbezi kpi yeim’n ztezeuw tu xis gpa qehfiday poxksaofrp:ArticleView(article: .sample, persistence: Persistence())
Sih xnox ezuftjvagh ej am gheyo, gua juh piwk inozpvbipn hihumyuh: Urux ybi foli KudxPaan.hyutw. Ohy e txicir olkcenyo ay dwu private let persistence = Persistence()
Kluz, erp yda behjahjeyqu podiholac, udx perika gro ForEach(newsViewModel.news, id: \.url) { article in
ArticleView(article: article, persistence: persistence)
.listRowSeparator(.hidden)
.onTapGesture {
if let url = article.url {
openURL(url)
}
}
}