6
6
//
7
7
8
8
import SwiftUI
9
+ import async_task
9
10
10
11
fileprivate typealias ImageSize = OpenAIImageSize
12
+ fileprivate typealias TaskModel = Async . SingleTask < Image , AsyncImageErrors >
11
13
12
14
/// Async image component to load and show OpenAI image from OpenAI image API
13
15
@available ( iOS 15 . 0 , macOS 12 . 0 , tvOS 15 . 0 , watchOS 8 . 0 , * )
14
16
public struct OpenAIAsyncImage < Content: View , T: IOpenAILoader > : View {
15
17
18
+ @StateObject private var taskModel = TaskModel ( errorMapper: errorMapper)
19
+
16
20
/// Custom view builder template type alias
17
21
public typealias ImageProcess = ( ImageState ) -> Content
18
22
19
23
/// Default loader, injected from environment
20
24
@Environment ( \. openAIDefaultLoader) var defaultLoader : OpenAIDefaultLoader
21
-
22
- // MARK: - Private properties
23
-
24
- /// State variable to hold the OpenAI image
25
- @State private var image : Image ?
26
-
27
- /// State variable to hold any errors encountered during loading
28
- @State private var error : Error ?
29
-
30
- /// State variable to hold the current task responsible for loading the image
31
- @State private var task : Task < Void , Never > ?
32
-
25
+
33
26
// MARK: - Config
34
27
35
28
/// A binding to the text prompt describing the desired image. The maximum length is 1000 characters
@@ -85,15 +78,13 @@ public struct OpenAIAsyncImage<Content: View, T: IOpenAILoader>: View {
85
78
}
86
79
}
87
80
. onChange ( of: prompt) { _ in
88
- cancelTask ( )
89
- clear ( )
90
- task = getTask ( )
81
+ start ( )
91
82
}
92
83
. onAppear {
93
- task = getTask ( )
84
+ start ( )
94
85
}
95
86
. onDisappear {
96
- cancelTask ( )
87
+ cancel ( )
97
88
}
98
89
}
99
90
@@ -102,8 +93,8 @@ public struct OpenAIAsyncImage<Content: View, T: IOpenAILoader>: View {
102
93
/// - Returns: The current image state status
103
94
private func getState ( ) -> ImageState {
104
95
105
- if let image { return . loaded( image) }
106
- else if let error { return . loadError( error) }
96
+ if let image = taskModel . value { return . loaded( image) }
97
+ else if let error = taskModel . error { return . loadError( error) }
107
98
108
99
return . loading
109
100
}
@@ -139,42 +130,19 @@ public struct OpenAIAsyncImage<Content: View, T: IOpenAILoader>: View {
139
130
}
140
131
return try await loadImageDefault ( prompt, with: size, model: model)
141
132
}
142
-
143
- /// Sets the image on the main thread
144
- /// - Parameter value: The image to be set
145
- @MainActor
146
- private func setImage( _ value : Image ) {
147
- image = value
148
- }
149
-
150
- /// Clears the image and error state properties
151
- @MainActor
152
- private func clear( ) {
153
- image = nil
154
- error = nil
155
- }
156
-
157
- /// Cancels the current loading task if any
158
- private func cancelTask( ) {
159
- task? . cancel ( )
160
- task = nil
161
- }
162
-
133
+
163
134
/// Creates and returns a task to fetch the OpenAI image
164
135
/// - Returns: A task that fetches the OpenAI image
165
- private func getTask( ) -> Task < Void , Never > {
166
- Task {
167
- do {
168
- if let image = try await loadImage ( prompt, with: size, model: model) {
169
- setImage ( image)
170
- }
171
- } catch is CancellationError {
172
- self . error = AsyncImageErrors . cancellationError
173
- } catch{
174
- self . error = error
175
- }
136
+ private func start( ) {
137
+ taskModel. start {
138
+ try await loadImage ( prompt, with: size, model: model)
176
139
}
177
140
}
141
+
142
+ /// Cancel task
143
+ private func cancel( ) {
144
+ taskModel. cancel ( )
145
+ }
178
146
}
179
147
180
148
// MARK: - Public extensions -
@@ -232,3 +200,12 @@ fileprivate func imageTpl(_ state : ImageState) -> some View{
232
200
case . loading : ProgressView ( )
233
201
}
234
202
}
203
+
204
+ @Sendable
205
+ fileprivate func errorMapper( _ error : Error ? ) -> AsyncImageErrors ? {
206
+ if error is CancellationError {
207
+ return . cancellationError
208
+ }
209
+
210
+ return nil
211
+ }
0 commit comments