6
6
//
7
7
8
8
import SwiftUI
9
+ import Combine
10
+
11
+ final class MagazineEditViewModel : ObservableObject {
12
+ @Published var title : String = " "
13
+ @Published var subTitle : String = " "
14
+ @Published var contentImage : NSImage ?
15
+ @Published var bodyImage : NSImage ?
16
+ @Published var isLoading : Bool = false
17
+ // assigning properties
18
+ @Published var canSaveState : Bool = false
19
+ var cancellables = Set < AnyCancellable > ( )
20
+
21
+ enum ImageCategory {
22
+ case content
23
+ case body
24
+ }
25
+
26
+ init ( ) {
27
+ $title
28
+ . combineLatest ( $subTitle, $contentImage, $bodyImage)
29
+ . map { title, subTitle, contentImage, bodyImage in
30
+ return !title. isEmpty && !subTitle. isEmpty && contentImage != nil && bodyImage != nil
31
+ }
32
+ . assign ( to: & $canSaveState)
33
+ }
34
+
35
+ func fecthNSImage( url: URL , category: ImageCategory ) {
36
+ URLSession . shared. dataTaskPublisher ( for: url)
37
+ . receive ( on: DispatchQueue . main)
38
+ . compactMap { ( data: Data , response: URLResponse ) -> NSImage ? in
39
+ guard let response = response as? HTTPURLResponse ,
40
+ ( 200 ..< 300 ) . contains ( response. statusCode) ,
41
+ let nsImage = NSImage ( data: data) else { return nil }
42
+ return nsImage
43
+ }
44
+ . sink { status in
45
+ switch status {
46
+ case . finished:
47
+ print ( " fetch done " )
48
+ case . failure( let error) :
49
+ print ( error)
50
+ }
51
+ } receiveValue: { [ weak self] nsImage in
52
+ switch category {
53
+ case . content:
54
+ self ? . contentImage = nsImage
55
+ case . body:
56
+ self ? . bodyImage = nsImage
57
+ }
58
+ }
59
+ . store ( in: & cancellables)
60
+
61
+ }
62
+ }
9
63
10
64
// openPannel: [https://serialcoder.dev/text-tutorials/macos-tutorials/save-and-open-panels-in-swiftui-based-macos-apps/]
11
65
// drag and drop image: [https://www.hackingwithswift.com/quick-start/swiftui/how-to-support-drag-and-drop-in-swiftui]
12
66
struct MagazineEditView : View {
13
67
@Binding var flow : Flow
14
- @State private var title : String = " "
15
- @State private var subTitle : String = " "
16
- @State private var contentImage : NSImage ?
17
- @State private var bodyImage : NSImage ?
68
+ @StateObject var vm = MagazineEditViewModel ( )
18
69
@EnvironmentObject var perfumeStore : PerfumeStore
19
70
@EnvironmentObject var magazineStore : MagazineStore
20
71
var perfumes : [ Perfume ] {
@@ -33,42 +84,52 @@ struct MagazineEditView: View {
33
84
. font ( . footnote)
34
85
. foregroundColor ( . secondary)
35
86
36
- HStack {
37
- Spacer ( )
38
- Button {
39
- // 모든 조건이 부합해야 저장기능 가능
40
- guard !title. isEmpty && !subTitle. isEmpty,
41
- let contentImage = contentImage,
42
- let bodyImage = bodyImage else { return }
43
-
44
- // 새로운 메거진 생성
45
- let magazine = Magazine (
46
- title: title,
47
- subTitle: subTitle,
48
- contentImage: " " ,
49
- bodyImage: " " ,
50
- createdDate: Date . now. timeIntervalSince1970,
51
- perfumeIds: perfumes. map { $0. perfumeId }
52
- )
53
-
54
- Task {
55
- // 메거진 서버에 저장
56
- await magazineStore. createMagazine ( magazine: magazine, selectedContentUImage: contentImage, selectedBodyUImage: bodyImage)
57
- // 읽기 모드
58
- flow = . read
87
+ if magazineStore. magazine == nil {
88
+ HStack {
89
+ Spacer ( )
90
+ Button {
91
+ flow = . create
92
+ } label: {
93
+ Label ( " back " , systemImage: " chevron.left " )
59
94
}
60
- } label: {
61
- Text ( " Save " )
62
- }
63
- } // HSTACK(SAVE)
95
+ Button {
96
+ // 새로운 메거진 생성
97
+ let magazine = Magazine (
98
+ title: vm. title,
99
+ subTitle: vm. subTitle,
100
+ contentImage: " " ,
101
+ bodyImage: " " ,
102
+ createdDate: Date . now. timeIntervalSince1970,
103
+ perfumeIds: perfumes. map { $0. perfumeId }
104
+ )
105
+
106
+
107
+
108
+ Task {
109
+ // upload start
110
+ vm. isLoading = true
111
+ // 메거진 서버에 저장
112
+ await magazineStore. createMagazine ( magazine: magazine, selectedContentUImage: vm. contentImage, selectedBodyUImage: vm. bodyImage)
113
+ // 읽기 모드
114
+ flow = . read
115
+ // upload end
116
+ vm. isLoading = false
117
+ }
118
+
119
+ } label: {
120
+ Text ( " Save " )
121
+ }
122
+ . disabled ( !vm. canSaveState)
123
+ } // HSTACK(SAVE)
124
+ }
64
125
65
126
Form {
66
- TextField ( text: $title, prompt: Text ( " Required.. " ) , axis: . vertical) {
127
+ TextField ( text: $vm . title, prompt: Text ( " Required.. " ) , axis: . vertical) {
67
128
Text ( " Title " )
68
129
}
69
130
. textFieldStyle ( RoundedBorderTextFieldStyle ( ) )
70
131
71
- TextField ( text: $subTitle, prompt: Text ( " Required.. " ) , axis: . vertical) {
132
+ TextField ( text: $vm . subTitle, prompt: Text ( " Required.. " ) , axis: . vertical) {
72
133
Text ( " Sub Title " )
73
134
}
74
135
. textFieldStyle ( RoundedBorderTextFieldStyle ( ) )
@@ -109,10 +170,10 @@ struct MagazineEditView: View {
109
170
Button {
110
171
if let url = showOpenPanel ( ) ,
111
172
let nsImage = NSImage ( contentsOf: url) {
112
- contentImage = nsImage
173
+ vm . contentImage = nsImage
113
174
}
114
175
} label: {
115
- if let contentImage = contentImage {
176
+ if let contentImage = vm . contentImage {
116
177
Image ( nsImage: contentImage)
117
178
. resizable ( )
118
179
. frame ( width: 250 , height: 250 , alignment: . center)
@@ -122,7 +183,7 @@ struct MagazineEditView: View {
122
183
. fill ( . quaternary)
123
184
. frame ( width: 250 , height: 250 )
124
185
. overlay {
125
- Text ( " Select the image! \n ➕ " )
186
+ Text ( " Select the image! \n **+** " )
126
187
. multilineTextAlignment ( . center)
127
188
}
128
189
@@ -131,7 +192,7 @@ struct MagazineEditView: View {
131
192
. buttonStyle ( . plain)
132
193
. dropDestination ( for: Data . self) { items, location in
133
194
if let item = items. first {
134
- contentImage = NSImage ( data: item)
195
+ vm . contentImage = NSImage ( data: item)
135
196
}
136
197
137
198
return true
@@ -147,10 +208,10 @@ struct MagazineEditView: View {
147
208
Button {
148
209
if let url = showOpenPanel ( ) ,
149
210
let nsImage = NSImage ( contentsOf: url) {
150
- bodyImage = nsImage
211
+ vm . bodyImage = nsImage
151
212
}
152
213
} label: {
153
- if let bodyImage = bodyImage {
214
+ if let bodyImage = vm . bodyImage {
154
215
Image ( nsImage: bodyImage)
155
216
. resizable ( )
156
217
. frame ( width: 250 , height: 250 , alignment: . center)
@@ -160,15 +221,15 @@ struct MagazineEditView: View {
160
221
. fill ( . quaternary)
161
222
. frame ( width: 250 , height: 250 )
162
223
. overlay {
163
- Text ( " Select the image! \n ➕ " )
224
+ Text ( " Select the image! \n **+** " )
164
225
. multilineTextAlignment ( . center)
165
226
}
166
227
}
167
228
}
168
229
. buttonStyle ( . plain)
169
230
. dropDestination ( for: Data . self) { items, location in
170
231
if let item = items. first {
171
- bodyImage = NSImage ( data: item)
232
+ vm . bodyImage = NSImage ( data: item)
172
233
}
173
234
174
235
return true
@@ -182,14 +243,65 @@ struct MagazineEditView: View {
182
243
} // VSTACK
183
244
. padding ( )
184
245
} // SCROLL
246
+ . onAppear {
247
+ switch flow {
248
+ case . edit:
249
+ if let magazine = magazineStore. magazine,
250
+ let contentImageURL = URL ( string: magazine. contentImage) ,
251
+ let bodyImageURL = URL ( string: magazine. bodyImage) {
252
+ vm. title = magazine. title
253
+ vm. subTitle = magazine. subTitle
254
+ vm. fecthNSImage ( url: contentImageURL, category: . content)
255
+ vm. fecthNSImage ( url: bodyImageURL, category: . body)
256
+ // images
257
+ // url -> download -> NSImage
258
+ }
259
+ default :
260
+ vm. title = " "
261
+ vm. subTitle = " "
262
+ vm. contentImage = nil
263
+ vm. bodyImage = nil
264
+ }
265
+ }
266
+ . overlay ( content: {
267
+ if vm. isLoading {
268
+ ProgressView ( )
269
+ . frame ( width: 200 , height: 200 )
270
+ . background ( Material . ultraThinMaterial)
271
+ . clipShape ( RoundedRectangle ( cornerRadius: 20.0 ) )
272
+ }
273
+ } )
274
+ . onChange ( of: magazineStore. magazine) { magazine in
275
+ flow = . read
276
+ }
185
277
. toolbar {
186
- ToolbarItem {
187
- Button {
188
- flow = . create
189
- } label: {
190
- Image ( systemName: " chevron.left " )
278
+ if let magazine = magazineStore. magazine {
279
+ ToolbarItem ( placement: ToolbarItemPlacement . automatic) {
280
+ Button ( " done " ) {
281
+ flow = . read
282
+ // 기존 메거진 업데이트
283
+ let magazine = Magazine (
284
+ id: magazine. id,
285
+ title: vm. title,
286
+ subTitle: vm. subTitle,
287
+ contentImage: " " ,
288
+ bodyImage: " " ,
289
+ createdDate: Date . now. timeIntervalSince1970,
290
+ perfumeIds: perfumes. map { $0. perfumeId }
291
+ )
292
+
293
+ Task {
294
+ // upload start
295
+ vm. isLoading = true
296
+ // 메거진 서버에 저장
297
+ await magazineStore. createMagazine ( magazine: magazine, selectedContentUImage: vm. contentImage, selectedBodyUImage: vm. bodyImage)
298
+ // 읽기 모드
299
+ flow = . read
300
+ // upload end
301
+ vm. isLoading = false
302
+ }
303
+ }
191
304
}
192
-
193
305
}
194
306
}
195
307
}
0 commit comments