Skip to content

Commit 4da357e

Browse files
Added actors example
1 parent 2e1d0c3 commit 4da357e

3 files changed

Lines changed: 133 additions & 0 deletions

File tree

SwiftUIExamples/SwiftUIExamples.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
10AB454B2DD257A100967614 /* BindingExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10AB454A2DD257A100967614 /* BindingExample.swift */; };
3131
4FB02AD62E3B3B320010B8D8 /* DependencyInjectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FB02AD52E3B3B320010B8D8 /* DependencyInjectionTests.swift */; };
3232
4FEBFAB62E3C85BC00D72E78 /* MemoryLeakExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FEBFAB52E3C85BC00D72E78 /* MemoryLeakExamples.swift */; };
33+
4FEBFAB82E3CBDA500D72E78 /* ActorsExample.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FEBFAB72E3CBDA500D72E78 /* ActorsExample.swift */; };
3334
/* End PBXBuildFile section */
3435

3536
/* Begin PBXContainerItemProxy section */
@@ -81,6 +82,7 @@
8182
10AB454A2DD257A100967614 /* BindingExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BindingExample.swift; sourceTree = "<group>"; };
8283
4FB02AD52E3B3B320010B8D8 /* DependencyInjectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyInjectionTests.swift; sourceTree = "<group>"; };
8384
4FEBFAB52E3C85BC00D72E78 /* MemoryLeakExamples.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryLeakExamples.swift; sourceTree = "<group>"; };
85+
4FEBFAB72E3CBDA500D72E78 /* ActorsExample.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActorsExample.swift; sourceTree = "<group>"; };
8486
/* End PBXFileReference section */
8587

8688
/* Begin PBXFrameworksBuildPhase section */
@@ -122,6 +124,7 @@
122124
109998162E255A4700A8A3C9 /* RetryCatchExample.swift */,
123125
109998182E255FC200A8A3C9 /* ButtonThrottle.swift */,
124126
1099981A2E2566FB00A8A3C9 /* HandleEventsPublisher.swift */,
127+
4FEBFAB72E3CBDA500D72E78 /* ActorsExample.swift */,
125128
);
126129
path = Combine;
127130
sourceTree = "<group>";
@@ -357,6 +360,7 @@
357360
10525C772E2D2D1A00D7C510 /* git_hub_repos_async_app.swift in Sources */,
358361
102329BE2E1EC5B100ABFEA1 /* LoginView.swift in Sources */,
359362
10AB454B2DD257A100967614 /* BindingExample.swift in Sources */,
363+
4FEBFAB82E3CBDA500D72E78 /* ActorsExample.swift in Sources */,
360364
072EEEAF2E1E8CE40007B3A4 /* SearchViewUsingCombine.swift in Sources */,
361365
072EEEC42E1E98DA0007B3A4 /* DataTaskPublisher.swift in Sources */,
362366
109998132E252E7400A8A3C9 /* FlatMapVsSwitchToLatest1.swift in Sources */,
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import SwiftUI
2+
import Foundation
3+
4+
// MARK: - Data Models
5+
struct MealCategoryResponse: Decodable {
6+
let categories: [Food]
7+
}
8+
9+
struct Food: Identifiable, Decodable, Hashable {
10+
let id: String
11+
let name: String
12+
let thumbnailURL: URL
13+
let description: String
14+
15+
enum CodingKeys: String, CodingKey {
16+
case id = "idCategory"
17+
case name = "strCategory"
18+
case thumbnailURL = "strCategoryThumb"
19+
case description = "strCategoryDescription"
20+
}
21+
}
22+
// MARK: - Actor
23+
24+
/// Actor that manages fetching and storing food items in a thread-safe way.
25+
actor FoodStore {
26+
private(set) var foods: [Food] = []
27+
28+
func fetchFoodData() async throws {
29+
guard let url = URL(string: "https://github.com/janeshsutharios/REST_GET_API/raw/refs/heads/main/food.json") else {
30+
throw URLError(.badURL)
31+
}
32+
33+
let (data, _) = try await URLSession.shared.data(from: url)
34+
let decodedFoods = try JSONDecoder().decode(MealCategoryResponse.self, from: data)
35+
self.foods = decodedFoods.categories
36+
}
37+
38+
func getFoods() -> [Food] {
39+
return foods
40+
}
41+
}
42+
43+
// MARK: - ViewModel
44+
@MainActor
45+
class FoodViewModel: ObservableObject {
46+
@Published var items: [Food] = []
47+
@Published var error: String? = nil
48+
private let store = FoodStore()
49+
50+
func load() {
51+
Task {
52+
let result = await getFoods()
53+
switch result {
54+
case .success(let foods):
55+
await MainActor.run {
56+
self.items = foods
57+
self.error = nil
58+
}
59+
case .failure(let err):
60+
await MainActor.run {
61+
self.error = err.localizedDescription
62+
}
63+
}
64+
}
65+
}
66+
67+
nonisolated func getFoods() async -> Result<[Food], Error> {
68+
do {
69+
try await store.fetchFoodData()
70+
let foods = await store.getFoods()
71+
return .success(foods)
72+
} catch {
73+
return .failure(error)
74+
}
75+
}
76+
77+
}
78+
79+
// MARK: - View
80+
81+
struct FoodListView: View {
82+
@StateObject private var viewModel = FoodViewModel()
83+
84+
var body: some View {
85+
NavigationView {
86+
List(viewModel.items) { item in
87+
HStack(alignment: .top, spacing: 12) {
88+
AsyncImage(url: item.thumbnailURL) { image in
89+
image
90+
.resizable()
91+
.aspectRatio(contentMode: .fill)
92+
.frame(width: 90, height: 90)
93+
.clipShape(Circle())
94+
} placeholder: {
95+
ProgressView()
96+
}
97+
VStack(alignment: .leading, spacing: 4) {
98+
Text(item.name)
99+
.font(.headline)
100+
Text(item.description)
101+
.font(.subheadline)
102+
.foregroundColor(.gray)
103+
}
104+
}
105+
}
106+
.navigationTitle("🍱 Food Menu")
107+
.onAppear {
108+
viewModel.load()
109+
}
110+
.overlay {
111+
if let error = viewModel.error {
112+
Text("Error: \(error)")
113+
.foregroundColor(.red)
114+
.padding()
115+
}
116+
}
117+
}
118+
}
119+
}
120+
121+
// MARK: - Preview
122+
123+
struct ContentView_Previews: PreviewProvider {
124+
static var previews: some View {
125+
FoodListView()
126+
}
127+
}

SwiftUIExamples/SwiftUIExamples/SwiftUIExamplesApp.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ struct DashboardView: View {
3737
ExampleItem(title: "Aync Await with API and protocols tests", destination: AnyView(RepoListView(username: "janeshsutharios"))),
3838
ExampleItem(title: "Dependency-Injection ", destination: AnyView(ProjectsListView())),
3939
ExampleItem(title: "MemoryLeakExamples ", destination: AnyView(MemoryLeakExamples())),
40+
ExampleItem(title: "Actors example ", destination: AnyView(FoodListView())),
41+
4042
]
4143
var body: some View {
4244
NavigationView {

0 commit comments

Comments
 (0)