This playground demonstrates key SwiftUI and Combine concepts including:
- Network requests using async/await and Combine
- MVVM architecture
- SwiftUI views and navigation
- Protocol-based networking
- JSON decoding
import Foundation
import SwiftUI
import PlaygroundSupport
import Combine
// MARK: - Networking
// Protocol defining network operations
protocol NetworkManaging {
func fetch<T:Decodable>(url: String) throws -> T
func uploadFile(url: String)
}
extension NetworkManaging {
func uploadfile(url: String) {
}
}
// MARK: - Models
// Post model conforming to Decodable for JSON parsing and Identifiable for SwiftUI lists
struct Post: Decodable, Identifiable {
let id: Int
let title: String
}
extension Post: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
// MARK: - Network Functions
// Async/await version of fetch posts
func fetchPosts() async throws -> [Post] {
let (data, _) = try await URLSession.shared.data(for: URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/posts")!))
return try JSONDecoder().decode([Post].self, from: data)
}
// Combine version of fetch posts
func fetchPostsCombine() -> AnyPublisher<[Post], Error> {
return URLSession.shared.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/posts")!)
.map(\.data)
.decode(type: [Post].self, decoder: JSONDecoder())
.eraseToAnyPublisher()
}
Task {
do {
let posts = try await fetchPosts()
for post in posts {
print("Title: \(post.title)")
}
} catch {
print("Failed to fetch posts: \(error)")
}
}
// MARK: - View Model
// ViewModel using @MainActor to ensure UI updates happen on main thread
@MainActor
class VM: ObservableObject {
// Published property for SwiftUI to observe
@Published var posts: [Post] = []
// Set to store and manage Combine subscriptions
private var cancellables = Set<AnyCancellable>()
init() {
}
func load() {
Task { [weak self] in
guard let self = self else { return }
self.posts = try! await fetchPosts()
}
}
func loadCombine() {
fetchPostsCombine().sink { completion in
switch completion {
case .failure(_):
print("Error")
case .finished:
break
}
} receiveValue: { posts in
self.posts = posts
}
.store(in: &cancellables)
}
}
// MARK: - Views
// Main content view using NavigationStack for navigation
struct ContentView: View {
// StateObject to maintain ViewModel instance
@StateObject var vm: VM = VM()
var body: some View {
NavigationStack {
ForEach(vm.posts) { post in
NavigationLink {
Text(post.title)
} label: {
Text(post.title)
}
}
}
.onAppear {
vm.loadCombine()
}
}
}
PlaygroundPage.current.setLiveView(ContentView())