import Foundation
struct Photo: Codable, Identifiable {
let id: Int
let title: String
let url: String
}
import Foundation
import UIKit
final class APIService {
static let shared = APIService()
private init() {}
func fetchPhotos() async throws -> [Photo] {
let url = URL(
string: "https://jsonplaceholder.typicode.com/photos?_limit=20"
)!
let (data, _) = try await URLSession.shared.data(from: url)
return try JSONDecoder().decode([Photo].self, from: data)
}
func downloadImage(from urlString: String) async -> UIImage? {
guard let url = URL(string: urlString) else {
return nil
}
do {
let (data, _) = try await URLSession.shared.data(from: url)
return UIImage(data: data)
} catch {
return nil
}
}
}
import SwiftUI
import Combine
@MainActor
final class PhotoListViewModel: ObservableObject {
@Published var photos: [Photo] = []
@Published var isLoading = false
func fetchPhotos() async {
isLoading = true
do {
photos = try await APIService.shared.fetchPhotos()
} catch {
print(error.localizedDescription)
}
isLoading = false
}
}
import SwiftUI
import Combine
@MainActor
final class ImageLoader: ObservableObject {
@Published var image: UIImage?
func load(url: String) {
Task {
let downloadedImage =
await APIService.shared.downloadImage(from: url)
self.image = downloadedImage
}
}
}
import SwiftUI
import Combine
struct PhotoRow: View {
let photo: Photo
@StateObject private var loader = ImageLoader()
var body: some View {
HStack {
Group {
if let image = loader.image {
Image(uiImage: image)
.resizable()
} else {
ProgressView()
}
}
.frame(width: 80, height: 80)
Text(photo.title)
.font(.headline)
}
.onAppear {
loader.load(url: photo.url)
}
}
}
import SwiftUI
struct PhotoListView: View {
@StateObject private var vm = PhotoListViewModel()
var body: some View {
NavigationView {
List(vm.photos) { photo in
PhotoRow(photo: photo)
}
.navigationTitle("Photos")
.task {
await vm.fetchPhotos()
}
}
}
}
Top comments (0)