├── .github
└── pull_request_template
├── .gitignore
├── LICENSE
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── Compose
│ ├── ComposeView.swift
│ ├── CompsableObject.swift
│ ├── Macro
│ │ └── Macros.swift
│ ├── Refresher.swift
│ ├── RootViewBuilder.swift
│ ├── iOS
│ │ ├── ComposableController.swift
│ │ └── ComposableView.swift
│ └── macOS
│ │ ├── ComposableController.macOS.swift
│ │ └── ComposableView.macOS.swift
└── ComposeMacro
│ ├── ComposableObjectMacro.swift
│ └── Plugin.swift
└── Tests
├── ComposeMacroTests
└── ComposeMacroTests.swift
└── ComposeTests
└── ComposeTests.swift
/.github/pull_request_template:
--------------------------------------------------------------------------------
1 | # 📌 Reference
2 | > Add any references to help understand this pull request.
3 |
4 |
5 | # 🔥 Cause
6 | > If there is a reason, write why the code changes was occurred.
7 |
8 |
9 | # 📄 Changes
10 | > Write what is changed.
11 | > You can write more detail informations using MD syntax.
12 |
13 |
14 | # 🚧 Testing
15 | > Write how to test and results of changes using screenshots, gifs, any others.
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Jeong Jineun
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "originHash" : "451a4227a52c5d76284cb42d2af427b33622a8db10ee88a856dc3a76ddb4d6cd",
3 | "pins" : [
4 | {
5 | "identity" : "swift-syntax",
6 | "kind" : "remoteSourceControl",
7 | "location" : "https://github.com/apple/swift-syntax.git",
8 | "state" : {
9 | "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d",
10 | "version" : "509.1.1"
11 | }
12 | }
13 | ],
14 | "version" : 3
15 | }
16 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.10
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 | import CompilerPluginSupport
6 |
7 | let package = Package(
8 | name: "Compose",
9 | platforms: [
10 | .iOS(.v13),
11 | .macOS(.v10_15)
12 | ],
13 | products: [
14 | .library(
15 | name: "Compose",
16 | targets: ["Compose"]
17 | )
18 | ],
19 | dependencies: [
20 | .package(url: "https://github.com/apple/swift-syntax.git", from: "509.0.0")
21 | ],
22 | targets: [
23 | .macro(
24 | name: "ComposeMacro",
25 | dependencies: [
26 | .product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
27 | .product(name: "SwiftCompilerPlugin", package: "swift-syntax")
28 | ]
29 | ),
30 | .target(
31 | name: "Compose",
32 | dependencies: [
33 | "ComposeMacro"
34 | ]
35 | ),
36 | .testTarget(
37 | name: "ComposeTests",
38 | dependencies: ["Compose"]
39 | ),
40 | .testTarget(
41 | name: "ComposeMacroTests",
42 | dependencies: [
43 | "ComposeMacro",
44 | .product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
45 | ]
46 | )
47 | ]
48 | )
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Compoes
2 | Compose is a package that allows you to use `UIViewController` view as `SwiftUI` view.
3 |
4 |
5 |
6 | - [Compoes](#compoes)
7 | - [Requirements](#requirements)
8 | - [Installation](#installation)
9 | - [Swift Package Manager](#swift-package-manager)
10 | - [Getting Started](#getting-started)
11 | - [Extras](#extras)
12 | - [Self reference](#self-reference)
13 | - [ComposableView(UIView) supports](#composableviewuiview-supports)
14 | - [Usecases](#usecases)
15 | - [Common `ViewModel`(`ObservableObject`) usecase](#common-viewmodelobservableobject-usecase)
16 | - [Separate child view usecase](#separate-child-view-usecase)
17 | - [Use exist view](#use-exist-view)
18 | - [Preview](#preview)
19 | - [Contribution](#contribution)
20 | - [License](#license)
21 |
22 | # Requirements
23 | - iOS 13.0+
24 | - macOS 10.15+
25 |
26 | # Installation
27 | ### Swift Package Manager
28 | ```swift
29 | dependencies: [
30 | .package(url: "https://github.com/wlsdms0122/Compose.git", from: "1.0.0")
31 | ]
32 | ```
33 |
34 | # Getting Started
35 | You can inherit `ComposableController` to make view controller using `SwiftUI` view.
36 |
37 | `CopmosableController` inherited `UIHostingController`.
38 | ```swift
39 | import Compose
40 |
41 | class MainViewController: ComposableController { ... }
42 | ```
43 |
44 | To layout view of controller, You should be define initializer. The content body will call when `SwiftUI` needs to re-render thre view.
45 | ```swift
46 | import Compose
47 |
48 | class MainViewController: ComposableController {
49 | init() {
50 | super.init() {
51 | Text("Hello World")
52 | }
53 | }
54 | }
55 | ```
56 |
57 | You can use any `ObservableObject` to manage states of view. Typically use convention that define nested class that adopt `ObservableObject`. And pass it to super class's initializer after instantiate.
58 |
59 | And in iOS 14.0 or macOS 11.0 or later, you can choose the type between `StateObject` and `ObservedObject` using `ComposableObject`'s method
60 |
61 | > ⚠️ Not use `@State` or `@Binding` property wrapper in `ComposableController`. All states are managed by `@Published` of `ObservableObejct`.
62 | ```swift
63 | public extension ComposableObject {
64 | @available(iOS 14.0, macOS 11.0, *)
65 | static func state(_ object: ObjectType) -> Self where Self == StateObject
66 | static func observed(_ object: ObjectType) -> Self where Self == ObservedObject
67 | }
68 | ```
69 |
70 | ```swift
71 | import Compose
72 |
73 | class MainViewController: ComposableController {
74 | @ComposableObject
75 | final class Environment {
76 | var count: Int = 0
77 | }
78 |
79 | override init() {
80 | let env = Environment()
81 |
82 | super.init(.observed(env)) {
83 | Text("\(env.count)")
84 | Button("+1") {
85 | env.count += 1
86 | }
87 | }
88 | }
89 | }
90 | ```
91 |
92 | The `ComposableController` prepared a number of initializers for observable objects. (0-8)
93 |
94 | If you pass the `ObservableObject` itself, it will behave as the `ObservedObject` in iOS 13 or macOS 10.15 and as the `StateObject` in iOS 14.0+ or macOS 11.0+ and later.
95 | ```swift
96 | import Compose
97 |
98 | class MainViewController: ComposableController {
99 | ...
100 | override init() {
101 | ...
102 | let env = Environment()
103 | // The `env` object behaves as either an `ObservedObject` or a `StateObject`, depending on its version.
104 | super.init(env)
105 | ...
106 | }
107 | }
108 | ```
109 |
110 | ## Extras
111 | ### Self reference
112 | To reference your own object (view controller), you can call the `run(_:)` method after the super initializer call.
113 | ```swift
114 | import Compose
115 |
116 | class MainViewController: ComposableController {
117 | override init() {
118 | super.init()
119 | run { [weak self] in
120 | Button("Present new controller") {
121 | self?.present(ViewController(), animated: true)
122 | }
123 | }
124 | }
125 | }
126 | ```
127 |
128 | ### ComposableView(UIView) supports
129 | If you want to convert a SwiftUI `View` to a `UIView`, use `ComposableView`. It's almost same with `ComposableController`.
130 |
131 | ```swift
132 | import Compose
133 |
134 | /// Define custom `UIView`.
135 | class TitleLabel: ComposableView {
136 | init(frame: CGRect) {
137 | super.init(frame: frame) {
138 | Text("Hello World")
139 | }
140 | }
141 | }
142 |
143 | /// Instantiation with `View`
144 | ComposableView(Text("Hello World"))
145 | ```
146 |
147 | ## Usecases
148 | ### Common `ViewModel`(`ObservableObject`) usecase
149 | ```swift
150 | import Compose
151 |
152 | class ListViewController: ComposableController {
153 | init(viewModel: ListViewModel) {
154 | super.init(viewModel)
155 | run {
156 | List(viewModel.items) {
157 | Text("\($0.title)")
158 | }
159 | }
160 | }
161 | }
162 | ```
163 |
164 | ### Separate child view usecase
165 | You can this style to layout complex view. (like `Compose` of `Android`)
166 | ```swift
167 | import Compose
168 |
169 | class ListViewController: ComposableController {
170 | init(viewModel: ListViewModel) {
171 | super.init(viewModel)
172 | run {
173 | Root(
174 | items: viewModel.items
175 | ) {
176 | viewModel.select($0)
177 | }
178 | }
179 | }
180 | }
181 |
182 | @ViewBuilder
183 | private func Root(
184 | items: [Item],
185 | onItemTap: @escaping (Item) -> Void
186 | ) -> some View {
187 | List(items) {
188 | ListItem(
189 | item: $0,
190 | onItemTap: onItemTap
191 | )
192 | }
193 | }
194 |
195 | @ViewBuilder
196 | private func ListItem(
197 | item: Item,
198 | onItemTap: @escaping (Item) -> Void
199 | ) -> some View {
200 | Text("\(item.title)")
201 | .onTapGesture {
202 | onItemTap(item)
203 | }
204 | }
205 | ```
206 |
207 | ### Use exist view
208 | ```swift
209 | import Compose
210 |
211 | class ListViewController: ComposableController {
212 | init(viewModel: ListViewModel) {
213 | super.init(ListView(viewModel))
214 | }
215 | }
216 | ```
217 |
218 | ## Preview
219 | For preview, instantiate controller and call `rootView` in preview provider.
220 |
221 | ```swift
222 | #if DEBUG
223 | struct Main_Previews: PreviewProvider {
224 | static var previews: some View {
225 | MainViewController().rootView
226 | }
227 | }
228 | #endif
229 | ```
230 |
231 | # Contribution
232 | Any ideas, issues, opinions are welcome.
233 |
234 | # License
235 | Compose is available under the MIT license. See the LICENSE file for more info.
236 |
--------------------------------------------------------------------------------
/Sources/Compose/ComposeView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComposeView.swift
3 | //
4 | //
5 | // Created by JSilver on 2023/02/04.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ComposeView<
11 | Content: View
12 | >: View {
13 | // MARK: - View
14 | var body: some View {
15 | content()
16 | }
17 |
18 | // MARK: - Property
19 | @ObservedObject
20 | private var refresher: Refresher
21 | private let content: () -> Content
22 |
23 | // MARK: - Initializer
24 | init(
25 | _ refresher: Refresher,
26 | @ViewBuilder content: @escaping () -> Content
27 | ) {
28 | self._refresher = .init(wrappedValue: refresher)
29 | self.content = content
30 | }
31 |
32 | // MARK: - Public
33 |
34 | // MARK: - Private
35 | }
36 |
37 | struct ComposeView1<
38 | A: ComposableObject,
39 | Content: View
40 | >: View {
41 | // MARK: - View
42 | var body: some View {
43 | content()
44 | }
45 |
46 | // MARK: - Property
47 | @ObservedObject
48 | private var refresher: Refresher
49 | private let a: A
50 |
51 | private let content: () -> Content
52 |
53 | // MARK: - Initializer
54 | init(
55 | _ refresher: Refresher,
56 | _ a: A,
57 | @ViewBuilder content: @escaping () -> Content
58 | ) {
59 | self._refresher = .init(wrappedValue: refresher)
60 | self.a = a
61 | self.content = content
62 | }
63 |
64 | // MARK: - Public
65 |
66 | // MARK: - Private
67 | }
68 |
69 | struct ComposeView2<
70 | A: ComposableObject,
71 | B: ComposableObject,
72 | Content: View
73 | >: View {
74 | // MARK: - View
75 | var body: some View {
76 | content()
77 | }
78 |
79 | // MARK: - Property
80 | @ObservedObject
81 | private var refresher: Refresher
82 | private let a: A
83 | private let b: B
84 |
85 | private let content: () -> Content
86 |
87 | // MARK: - Initializer
88 | init(
89 | _ refresher: Refresher,
90 | _ a: A,
91 | _ b: B,
92 | @ViewBuilder content: @escaping () -> Content
93 | ) {
94 | self._refresher = .init(wrappedValue: refresher)
95 | self.a = a
96 | self.b = b
97 | self.content = content
98 | }
99 |
100 | // MARK: - Public
101 |
102 | // MARK: - Private
103 | }
104 |
105 | struct ComposeView3<
106 | A: ComposableObject,
107 | B: ComposableObject,
108 | C: ComposableObject,
109 | Content: View
110 | >: View {
111 | // MARK: - View
112 | var body: some View {
113 | content()
114 | }
115 |
116 | // MARK: - Property
117 | @ObservedObject
118 | private var refresher: Refresher
119 | private let a: A
120 | private let b: B
121 | private let c: C
122 |
123 | private let content: () -> Content
124 |
125 | // MARK: - Initializer
126 | init(
127 | _ refresher: Refresher,
128 | _ a: A,
129 | _ b: B,
130 | _ c: C,
131 | @ViewBuilder content: @escaping () -> Content
132 | ) {
133 | self._refresher = .init(wrappedValue: refresher)
134 | self.a = a
135 | self.b = b
136 | self.c = c
137 | self.content = content
138 | }
139 |
140 | // MARK: - Public
141 |
142 | // MARK: - Private
143 | }
144 |
145 | struct ComposeView4<
146 | A: ComposableObject,
147 | B: ComposableObject,
148 | C: ComposableObject,
149 | D: ComposableObject,
150 | Content: View
151 | >: View {
152 | // MARK: - View
153 | var body: some View {
154 | content()
155 | }
156 |
157 | // MARK: - Property
158 | @ObservedObject
159 | private var refresher: Refresher
160 | private let a: A
161 | private let b: B
162 | private let c: C
163 | private let d: D
164 |
165 | private let content: () -> Content
166 |
167 | // MARK: - Initializer
168 | init(
169 | _ refresher: Refresher,
170 | _ a: A,
171 | _ b: B,
172 | _ c: C,
173 | _ d: D,
174 | @ViewBuilder content: @escaping () -> Content
175 | ) {
176 | self._refresher = .init(wrappedValue: refresher)
177 | self.a = a
178 | self.b = b
179 | self.c = c
180 | self.d = d
181 | self.content = content
182 | }
183 |
184 | // MARK: - Public
185 |
186 | // MARK: - Private
187 | }
188 |
189 | struct ComposeView5<
190 | A: ComposableObject,
191 | B: ComposableObject,
192 | C: ComposableObject,
193 | D: ComposableObject,
194 | E: ComposableObject,
195 | Content: View
196 | >: View {
197 | // MARK: - View
198 | var body: some View {
199 | content()
200 | }
201 |
202 | // MARK: - Property
203 | @ObservedObject
204 | private var refresher: Refresher
205 | private let a: A
206 | private let b: B
207 | private let c: C
208 | private let d: D
209 | private let e: E
210 |
211 | private let content: () -> Content
212 |
213 | // MARK: - Initializer
214 | init(
215 | _ refresher: Refresher,
216 | _ a: A,
217 | _ b: B,
218 | _ c: C,
219 | _ d: D,
220 | _ e: E,
221 | @ViewBuilder content: @escaping () -> Content
222 | ) {
223 | self._refresher = .init(wrappedValue: refresher)
224 | self.a = a
225 | self.b = b
226 | self.c = c
227 | self.d = d
228 | self.e = e
229 | self.content = content
230 | }
231 |
232 | // MARK: - Public
233 |
234 | // MARK: - Private
235 | }
236 |
237 | struct ComposeView6<
238 | A: ComposableObject,
239 | B: ComposableObject,
240 | C: ComposableObject,
241 | D: ComposableObject,
242 | E: ComposableObject,
243 | F: ComposableObject,
244 | Content: View
245 | >: View {
246 | // MARK: - View
247 | var body: some View {
248 | content()
249 | }
250 |
251 | // MARK: - Property
252 | @ObservedObject
253 | private var refresher: Refresher
254 | private let a: A
255 | private let b: B
256 | private let c: C
257 | private let d: D
258 | private let e: E
259 | private let f: F
260 |
261 | private let content: () -> Content
262 |
263 | // MARK: - Initializer
264 | init(
265 | _ refresher: Refresher,
266 | _ a: A,
267 | _ b: B,
268 | _ c: C,
269 | _ d: D,
270 | _ e: E,
271 | _ f: F,
272 | @ViewBuilder content: @escaping () -> Content
273 | ) {
274 | self._refresher = .init(wrappedValue: refresher)
275 | self.a = a
276 | self.b = b
277 | self.c = c
278 | self.d = d
279 | self.e = e
280 | self.f = f
281 | self.content = content
282 | }
283 |
284 | // MARK: - Public
285 |
286 | // MARK: - Private
287 | }
288 |
289 | struct ComposeView7<
290 | A: ComposableObject,
291 | B: ComposableObject,
292 | C: ComposableObject,
293 | D: ComposableObject,
294 | E: ComposableObject,
295 | F: ComposableObject,
296 | G: ComposableObject,
297 | Content: View
298 | >: View {
299 | // MARK: - View
300 | var body: some View {
301 | content()
302 | }
303 |
304 | // MARK: - Property
305 | @ObservedObject
306 | private var refresher: Refresher
307 | private let a: A
308 | private let b: B
309 | private let c: C
310 | private let d: D
311 | private let e: E
312 | private let f: F
313 | private let g: G
314 |
315 | private let content: () -> Content
316 |
317 | // MARK: - Initializer
318 | init(
319 | _ refresher: Refresher,
320 | _ a: A,
321 | _ b: B,
322 | _ c: C,
323 | _ d: D,
324 | _ e: E,
325 | _ f: F,
326 | _ g: G,
327 | @ViewBuilder content: @escaping () -> Content
328 | ) {
329 | self._refresher = .init(wrappedValue: refresher)
330 | self.a = a
331 | self.b = b
332 | self.c = c
333 | self.d = d
334 | self.e = e
335 | self.f = f
336 | self.g = g
337 | self.content = content
338 | }
339 |
340 | // MARK: - Public
341 |
342 | // MARK: - Private
343 | }
344 |
345 | struct ComposeView8<
346 | A: ComposableObject,
347 | B: ComposableObject,
348 | C: ComposableObject,
349 | D: ComposableObject,
350 | E: ComposableObject,
351 | F: ComposableObject,
352 | G: ComposableObject,
353 | H: ComposableObject,
354 | Content: View
355 | >: View {
356 | // MARK: - View
357 | var body: some View {
358 | content()
359 | }
360 |
361 | // MARK: - Property
362 | @ObservedObject
363 | private var refresher: Refresher
364 | private let a: A
365 | private let b: B
366 | private let c: C
367 | private let d: D
368 | private let e: E
369 | private let f: F
370 | private let g: G
371 | private let h: H
372 |
373 | private let content: () -> Content
374 |
375 | // MARK: - Initializer
376 | init(
377 | _ refresher: Refresher,
378 | _ a: A,
379 | _ b: B,
380 | _ c: C,
381 | _ d: D,
382 | _ e: E,
383 | _ f: F,
384 | _ g: G,
385 | _ h: H,
386 | @ViewBuilder content: @escaping () -> Content
387 | ) {
388 | self._refresher = .init(wrappedValue: refresher)
389 | self.a = a
390 | self.b = b
391 | self.c = c
392 | self.d = d
393 | self.e = e
394 | self.f = f
395 | self.g = g
396 | self.h = h
397 | self.content = content
398 | }
399 |
400 | // MARK: - Public
401 |
402 | // MARK: - Private
403 | }
404 |
405 |
--------------------------------------------------------------------------------
/Sources/Compose/CompsableObject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CompsableObject.swift
3 | //
4 | //
5 | // Created by JSilver on 2023/08/01.
6 | //
7 |
8 | import SwiftUI
9 |
10 | public protocol ComposableObject { }
11 |
12 | public extension ComposableObject {
13 | @available(iOS 14.0, macOS 11.0, *)
14 | static func state(_ object: ObjectType) -> Self where Self == StateObject {
15 | StateObject(wrappedValue: object)
16 | }
17 |
18 | static func observed(_ object: ObjectType) -> Self where Self == ObservedObject {
19 | ObservedObject(initialValue: object)
20 | }
21 | }
22 |
23 | @available(iOS 14.0, macOS 11.0, *)
24 | extension StateObject: ComposableObject { }
25 |
26 | extension ObservedObject: ComposableObject { }
27 |
--------------------------------------------------------------------------------
/Sources/Compose/Macro/Macros.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Macros.swift
3 | //
4 | //
5 | // Created by jsilver on 6/2/24.
6 | //
7 |
8 | import Foundation
9 |
10 | @attached(memberAttribute)
11 | @attached(extension, conformances: ObservableObject)
12 | public macro ComposableObject() = #externalMacro(module: "ComposeMacro", type: "ComposableObjectMacro")
13 |
--------------------------------------------------------------------------------
/Sources/Compose/Refresher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Refresher.swift
3 | //
4 | //
5 | // Created by JSilver on 2023/07/05.
6 | //
7 |
8 | import Foundation
9 |
10 | class Refresher: ObservableObject {
11 | // MARK: - Property
12 |
13 | // MARK: - Initializer
14 |
15 | // MARK: - Public
16 | func refresh() {
17 | objectWillChange.send()
18 | }
19 |
20 | // MARK: - Private
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Compose/RootViewBuilder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RootViewBuilder.swift
3 | //
4 | //
5 | // Created by JSilver on 2023/05/18.
6 | //
7 |
8 | import SwiftUI
9 |
10 | class RootViewBuilder {
11 | // MARK: - Property
12 | private(set) var content: () -> AnyView
13 |
14 | // MARK: - Initializer
15 | init(_ content: @escaping () -> some View) {
16 | self.content = { AnyView(content()) }
17 | }
18 |
19 | convenience init(_ content: some View) {
20 | self.init { content }
21 | }
22 |
23 | // MARK: - Public
24 | func setContent(_ content: @escaping () -> some View) {
25 | self.content = { AnyView(content()) }
26 | }
27 |
28 | // MARK: - Private
29 | }
30 |
--------------------------------------------------------------------------------
/Sources/Compose/iOS/ComposableController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComposableController.swift
3 | //
4 | //
5 | // Created by JSilver on 2023/02/04.
6 | //
7 |
8 | import SwiftUI
9 |
10 | #if os(iOS)
11 | open class ComposableController: UIHostingController {
12 | // MARK: - Property
13 | private let builder: RootViewBuilder
14 | private let refresher: Refresher
15 |
16 | // MARK: - Initializer
17 | public init(
18 | disableSafeArea: Bool = false,
19 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
20 | ) {
21 | let builder = RootViewBuilder(content)
22 | let refresher = Refresher()
23 |
24 | self.builder = builder
25 | self.refresher = refresher
26 |
27 | super.init(
28 | rootView: AnyView(
29 | ComposeView(
30 | refresher
31 | ) {
32 | builder.content()
33 | }
34 | )
35 | )
36 |
37 | _disableSafeArea = disableSafeArea
38 | }
39 |
40 | public init(
41 | disableSafeArea: Bool = false,
42 | _ content: some View
43 | ) {
44 | let builder = RootViewBuilder(content)
45 | let refresher = Refresher()
46 |
47 | self.builder = builder
48 | self.refresher = refresher
49 |
50 | super.init(
51 | rootView: AnyView(
52 | ComposeView(
53 | refresher
54 | ) {
55 | builder.content()
56 | }
57 | )
58 | )
59 |
60 | _disableSafeArea = disableSafeArea
61 | }
62 |
63 | public init<
64 | A: ComposableObject
65 | >(
66 | _ a: A,
67 | disableSafeArea: Bool = false,
68 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
69 | ) {
70 | let builder = RootViewBuilder(content)
71 | let refresher = Refresher()
72 |
73 | self.builder = builder
74 | self.refresher = refresher
75 |
76 | super.init(
77 | rootView: AnyView(
78 | ComposeView1(
79 | refresher,
80 | a
81 | ) {
82 | builder.content()
83 | }
84 | )
85 | )
86 |
87 | _disableSafeArea = disableSafeArea
88 | }
89 |
90 | public init<
91 | A: ComposableObject,
92 | B: ComposableObject
93 | >(
94 | _ a: A,
95 | _ b: B,
96 | disableSafeArea: Bool = false,
97 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
98 | ) {
99 | let builder = RootViewBuilder(content)
100 | let refresher = Refresher()
101 |
102 | self.builder = builder
103 | self.refresher = refresher
104 |
105 | super.init(
106 | rootView: AnyView(
107 | ComposeView2(
108 | refresher,
109 | a,
110 | b
111 | ) {
112 | builder.content()
113 | }
114 | )
115 | )
116 |
117 | _disableSafeArea = disableSafeArea
118 | }
119 |
120 | public init<
121 | A: ComposableObject,
122 | B: ComposableObject,
123 | C: ComposableObject
124 | >(
125 | _ a: A,
126 | _ b: B,
127 | _ c: C,
128 | disableSafeArea: Bool = false,
129 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
130 | ) {
131 | let builder = RootViewBuilder(content)
132 | let refresher = Refresher()
133 |
134 | self.builder = builder
135 | self.refresher = refresher
136 |
137 | super.init(
138 | rootView: AnyView(
139 | ComposeView3(
140 | refresher,
141 | a,
142 | b,
143 | c
144 | ) {
145 | builder.content()
146 | }
147 | )
148 | )
149 |
150 | _disableSafeArea = disableSafeArea
151 | }
152 |
153 | public init<
154 | A: ComposableObject,
155 | B: ComposableObject,
156 | C: ComposableObject,
157 | D: ComposableObject
158 | >(
159 | _ a: A,
160 | _ b: B,
161 | _ c: C,
162 | _ d: D,
163 | disableSafeArea: Bool = false,
164 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
165 | ) {
166 | let builder = RootViewBuilder(content)
167 | let refresher = Refresher()
168 |
169 | self.builder = builder
170 | self.refresher = refresher
171 |
172 | super.init(
173 | rootView: AnyView(
174 | ComposeView4(
175 | refresher,
176 | a,
177 | b,
178 | c,
179 | d
180 | ) {
181 | builder.content()
182 | }
183 | )
184 | )
185 |
186 | _disableSafeArea = disableSafeArea
187 | }
188 |
189 | public init<
190 | A: ComposableObject,
191 | B: ComposableObject,
192 | C: ComposableObject,
193 | D: ComposableObject,
194 | E: ComposableObject
195 | >(
196 | _ a: A,
197 | _ b: B,
198 | _ c: C,
199 | _ d: D,
200 | _ e: E,
201 | disableSafeArea: Bool = false,
202 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
203 | ) {
204 | let builder = RootViewBuilder(content)
205 | let refresher = Refresher()
206 |
207 | self.builder = builder
208 | self.refresher = refresher
209 |
210 | super.init(
211 | rootView: AnyView(
212 | ComposeView5(
213 | refresher,
214 | a,
215 | b,
216 | c,
217 | d,
218 | e
219 | ) {
220 | builder.content()
221 | }
222 | )
223 | )
224 |
225 | _disableSafeArea = disableSafeArea
226 | }
227 |
228 | public init<
229 | A: ComposableObject,
230 | B: ComposableObject,
231 | C: ComposableObject,
232 | D: ComposableObject,
233 | E: ComposableObject,
234 | F: ComposableObject
235 | >(
236 | _ a: A,
237 | _ b: B,
238 | _ c: C,
239 | _ d: D,
240 | _ e: E,
241 | _ f: F,
242 | disableSafeArea: Bool = false,
243 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
244 | ) {
245 | let builder = RootViewBuilder(content)
246 | let refresher = Refresher()
247 |
248 | self.builder = builder
249 | self.refresher = refresher
250 |
251 | super.init(
252 | rootView: AnyView(
253 | ComposeView6(
254 | refresher,
255 | a,
256 | b,
257 | c,
258 | d,
259 | e,
260 | f
261 | ) {
262 | builder.content()
263 | }
264 | )
265 | )
266 |
267 | _disableSafeArea = disableSafeArea
268 | }
269 |
270 | public init<
271 | A: ComposableObject,
272 | B: ComposableObject,
273 | C: ComposableObject,
274 | D: ComposableObject,
275 | E: ComposableObject,
276 | F: ComposableObject,
277 | G: ComposableObject
278 | >(
279 | _ a: A,
280 | _ b: B,
281 | _ c: C,
282 | _ d: D,
283 | _ e: E,
284 | _ f: F,
285 | _ g: G,
286 | disableSafeArea: Bool = false,
287 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
288 | ) {
289 | let builder = RootViewBuilder(content)
290 | let refresher = Refresher()
291 |
292 | self.builder = builder
293 | self.refresher = refresher
294 |
295 | super.init(
296 | rootView: AnyView(
297 | ComposeView7(
298 | refresher,
299 | a,
300 | b,
301 | c,
302 | d,
303 | e,
304 | f,
305 | g
306 | ) {
307 | builder.content()
308 | }
309 | )
310 | )
311 |
312 | _disableSafeArea = disableSafeArea
313 | }
314 |
315 | public init<
316 | A: ComposableObject,
317 | B: ComposableObject,
318 | C: ComposableObject,
319 | D: ComposableObject,
320 | E: ComposableObject,
321 | F: ComposableObject,
322 | G: ComposableObject,
323 | H: ComposableObject
324 | >(
325 | _ a: A,
326 | _ b: B,
327 | _ c: C,
328 | _ d: D,
329 | _ e: E,
330 | _ f: F,
331 | _ g: G,
332 | _ h: H,
333 | disableSafeArea: Bool = false,
334 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
335 | ) {
336 | let builder = RootViewBuilder(content)
337 | let refresher = Refresher()
338 |
339 | self.builder = builder
340 | self.refresher = refresher
341 |
342 | super.init(
343 | rootView: AnyView(
344 | ComposeView8(
345 | refresher,
346 | a,
347 | b,
348 | c,
349 | d,
350 | e,
351 | f,
352 | g,
353 | h
354 | ) {
355 | builder.content()
356 | }
357 | )
358 | )
359 |
360 | _disableSafeArea = disableSafeArea
361 | }
362 |
363 | // MARK: ObservableObject
364 | public init<
365 | A: ObservableObject
366 | >(
367 | _ a: A,
368 | disableSafeArea: Bool = false,
369 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
370 | ) {
371 | let builder = RootViewBuilder(content)
372 | let refresher = Refresher()
373 |
374 | self.builder = builder
375 | self.refresher = refresher
376 |
377 | if #available(iOS 14.0, *) {
378 | super.init(
379 | rootView: AnyView(
380 | ComposeView1(
381 | refresher,
382 | .state(a)
383 | ) {
384 | builder.content()
385 | }
386 | )
387 | )
388 | } else {
389 | super.init(
390 | rootView: AnyView(
391 | ComposeView1(
392 | refresher,
393 | .observed(a)
394 | ) {
395 | builder.content()
396 | }
397 | )
398 | )
399 | }
400 |
401 | _disableSafeArea = disableSafeArea
402 | }
403 |
404 | public init<
405 | A: ObservableObject,
406 | B: ObservableObject
407 | >(
408 | _ a: A,
409 | _ b: B,
410 | disableSafeArea: Bool = false,
411 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
412 | ) {
413 | let builder = RootViewBuilder(content)
414 | let refresher = Refresher()
415 |
416 | self.builder = builder
417 | self.refresher = refresher
418 |
419 | if #available(iOS 14.0, *) {
420 | super.init(
421 | rootView: AnyView(
422 | ComposeView2(
423 | refresher,
424 | .state(a),
425 | .state(b)
426 | ) {
427 | builder.content()
428 | }
429 | )
430 | )
431 | } else {
432 | super.init(
433 | rootView: AnyView(
434 | ComposeView2(
435 | refresher,
436 | .observed(a),
437 | .observed(b)
438 | ) {
439 | builder.content()
440 | }
441 | )
442 | )
443 | }
444 |
445 | _disableSafeArea = disableSafeArea
446 | }
447 |
448 | public init<
449 | A: ObservableObject,
450 | B: ObservableObject,
451 | C: ObservableObject
452 | >(
453 | _ a: A,
454 | _ b: B,
455 | _ c: C,
456 | disableSafeArea: Bool = false,
457 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
458 | ) {
459 | let builder = RootViewBuilder(content)
460 | let refresher = Refresher()
461 |
462 | self.builder = builder
463 | self.refresher = refresher
464 |
465 | if #available(iOS 14.0, *) {
466 | super.init(
467 | rootView: AnyView(
468 | ComposeView3(
469 | refresher,
470 | .state(a),
471 | .state(b),
472 | .state(c)
473 | ) {
474 | builder.content()
475 | }
476 | )
477 | )
478 | } else {
479 | super.init(
480 | rootView: AnyView(
481 | ComposeView3(
482 | refresher,
483 | .observed(a),
484 | .observed(b),
485 | .observed(c)
486 | ) {
487 | builder.content()
488 | }
489 | )
490 | )
491 | }
492 |
493 | _disableSafeArea = disableSafeArea
494 | }
495 |
496 | public init<
497 | A: ObservableObject,
498 | B: ObservableObject,
499 | C: ObservableObject,
500 | D: ObservableObject
501 | >(
502 | _ a: A,
503 | _ b: B,
504 | _ c: C,
505 | _ d: D,
506 | disableSafeArea: Bool = false,
507 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
508 | ) {
509 | let builder = RootViewBuilder(content)
510 | let refresher = Refresher()
511 |
512 | self.builder = builder
513 | self.refresher = refresher
514 |
515 | if #available(iOS 14.0, *) {
516 | super.init(
517 | rootView: AnyView(
518 | ComposeView4(
519 | refresher,
520 | .state(a),
521 | .state(b),
522 | .state(c),
523 | .state(d)
524 | ) {
525 | builder.content()
526 | }
527 | )
528 | )
529 | } else {
530 | super.init(
531 | rootView: AnyView(
532 | ComposeView4(
533 | refresher,
534 | .observed(a),
535 | .observed(b),
536 | .observed(c),
537 | .observed(d)
538 | ) {
539 | builder.content()
540 | }
541 | )
542 | )
543 | }
544 |
545 | _disableSafeArea = disableSafeArea
546 | }
547 |
548 | public init<
549 | A: ObservableObject,
550 | B: ObservableObject,
551 | C: ObservableObject,
552 | D: ObservableObject,
553 | E: ObservableObject
554 | >(
555 | _ a: A,
556 | _ b: B,
557 | _ c: C,
558 | _ d: D,
559 | _ e: E,
560 | disableSafeArea: Bool = false,
561 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
562 | ) {
563 | let builder = RootViewBuilder(content)
564 | let refresher = Refresher()
565 |
566 | self.builder = builder
567 | self.refresher = refresher
568 |
569 | if #available(iOS 14.0, *) {
570 | super.init(
571 | rootView: AnyView(
572 | ComposeView5(
573 | refresher,
574 | .state(a),
575 | .state(b),
576 | .state(c),
577 | .state(d),
578 | .state(e)
579 | ) {
580 | builder.content()
581 | }
582 | )
583 | )
584 | } else {
585 | super.init(
586 | rootView: AnyView(
587 | ComposeView5(
588 | refresher,
589 | .observed(a),
590 | .observed(b),
591 | .observed(c),
592 | .observed(d),
593 | .observed(e)
594 | ) {
595 | builder.content()
596 | }
597 | )
598 | )
599 | }
600 |
601 | _disableSafeArea = disableSafeArea
602 | }
603 |
604 | public init<
605 | A: ObservableObject,
606 | B: ObservableObject,
607 | C: ObservableObject,
608 | D: ObservableObject,
609 | E: ObservableObject,
610 | F: ObservableObject
611 | >(
612 | _ a: A,
613 | _ b: B,
614 | _ c: C,
615 | _ d: D,
616 | _ e: E,
617 | _ f: F,
618 | disableSafeArea: Bool = false,
619 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
620 | ) {
621 | let builder = RootViewBuilder(content)
622 | let refresher = Refresher()
623 |
624 | self.builder = builder
625 | self.refresher = refresher
626 |
627 | if #available(iOS 14.0, *) {
628 | super.init(
629 | rootView: AnyView(
630 | ComposeView6(
631 | refresher,
632 | .state(a),
633 | .state(b),
634 | .state(c),
635 | .state(d),
636 | .state(e),
637 | .state(f)
638 | ) {
639 | builder.content()
640 | }
641 | )
642 | )
643 | } else {
644 | super.init(
645 | rootView: AnyView(
646 | ComposeView6(
647 | refresher,
648 | .observed(a),
649 | .observed(b),
650 | .observed(c),
651 | .observed(d),
652 | .observed(e),
653 | .observed(f)
654 | ) {
655 | builder.content()
656 | }
657 | )
658 | )
659 | }
660 |
661 | _disableSafeArea = disableSafeArea
662 | }
663 |
664 | public init<
665 | A: ObservableObject,
666 | B: ObservableObject,
667 | C: ObservableObject,
668 | D: ObservableObject,
669 | E: ObservableObject,
670 | F: ObservableObject,
671 | G: ObservableObject
672 | >(
673 | _ a: A,
674 | _ b: B,
675 | _ c: C,
676 | _ d: D,
677 | _ e: E,
678 | _ f: F,
679 | _ g: G,
680 | disableSafeArea: Bool = false,
681 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
682 | ) {
683 | let builder = RootViewBuilder(content)
684 | let refresher = Refresher()
685 |
686 | self.builder = builder
687 | self.refresher = refresher
688 |
689 | if #available(iOS 14.0, *) {
690 | super.init(
691 | rootView: AnyView(
692 | ComposeView7(
693 | refresher,
694 | .state(a),
695 | .state(b),
696 | .state(c),
697 | .state(d),
698 | .state(e),
699 | .state(f),
700 | .state(g)
701 | ) {
702 | builder.content()
703 | }
704 | )
705 | )
706 | } else {
707 | super.init(
708 | rootView: AnyView(
709 | ComposeView7(
710 | refresher,
711 | .observed(a),
712 | .observed(b),
713 | .observed(c),
714 | .observed(d),
715 | .observed(e),
716 | .observed(f),
717 | .observed(g)
718 | ) {
719 | builder.content()
720 | }
721 | )
722 | )
723 | }
724 |
725 | _disableSafeArea = disableSafeArea
726 | }
727 |
728 | public init<
729 | A: ObservableObject,
730 | B: ObservableObject,
731 | C: ObservableObject,
732 | D: ObservableObject,
733 | E: ObservableObject,
734 | F: ObservableObject,
735 | G: ObservableObject,
736 | H: ObservableObject
737 | >(
738 | _ a: A,
739 | _ b: B,
740 | _ c: C,
741 | _ d: D,
742 | _ e: E,
743 | _ f: F,
744 | _ g: G,
745 | _ h: H,
746 | disableSafeArea: Bool = false,
747 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
748 | ) {
749 | let builder = RootViewBuilder(content)
750 | let refresher = Refresher()
751 |
752 | self.builder = builder
753 | self.refresher = refresher
754 |
755 | if #available(iOS 14.0, *) {
756 | super.init(
757 | rootView: AnyView(
758 | ComposeView8(
759 | refresher,
760 | .state(a),
761 | .state(b),
762 | .state(c),
763 | .state(d),
764 | .state(e),
765 | .state(f),
766 | .state(g),
767 | .state(h)
768 | ) {
769 | builder.content()
770 | }
771 | )
772 | )
773 | } else {
774 | super.init(
775 | rootView: AnyView(
776 | ComposeView8(
777 | refresher,
778 | .observed(a),
779 | .observed(b),
780 | .observed(c),
781 | .observed(d),
782 | .observed(e),
783 | .observed(f),
784 | .observed(g),
785 | .observed(h)
786 | ) {
787 | builder.content()
788 | }
789 | )
790 | )
791 | }
792 |
793 | _disableSafeArea = disableSafeArea
794 | }
795 |
796 | @MainActor
797 | public required dynamic init?(coder aDecoder: NSCoder) {
798 | fatalError("init(coder:) has not been implemented")
799 | }
800 |
801 | // MARK: - Lifecycle
802 | open override func viewDidLoad() {
803 | super.viewDidLoad()
804 | view.backgroundColor = .clear
805 | }
806 |
807 | open override func viewWillLayoutSubviews() {
808 | super.viewWillLayoutSubviews()
809 | // Invalidate intrinsic content size when root view's layout changed.
810 | view.invalidateIntrinsicContentSize()
811 | }
812 |
813 | // MARK: - Public
814 | public func run(@ViewBuilder _ content: @escaping () -> some View) {
815 | builder.setContent(content)
816 | refresher.refresh()
817 | }
818 |
819 | // MARK: - Private
820 | }
821 |
822 | public extension ComposableController {
823 | func run(_ content: some View) {
824 | run { content }
825 | }
826 | }
827 | #endif
828 |
--------------------------------------------------------------------------------
/Sources/Compose/iOS/ComposableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComposableView.swift
3 | //
4 | //
5 | // Created by JSilver on 2023/02/20.
6 | //
7 |
8 | import SwiftUI
9 | #if os(iOS)
10 | import UIKit
11 |
12 | open class ComposableView: UIView {
13 | // MARK: - Property
14 | public let controller: ComposableController
15 |
16 | public var rootView: AnyView { controller.rootView }
17 | public var content: UIView { controller.view }
18 |
19 | // MARK: - Initializer
20 | public init(
21 | frame: CGRect = .zero,
22 | disableSafeArea: Bool = false,
23 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
24 | ) {
25 | let controller = ComposableController(
26 | disableSafeArea: disableSafeArea,
27 | content
28 | )
29 | self.controller = controller
30 |
31 | super.init(frame: frame)
32 |
33 | addSubview(from: controller)
34 | }
35 |
36 | public init(
37 | frame: CGRect = .zero,
38 | disableSafeArea: Bool = false,
39 | _ content: some View
40 | ) {
41 | let controller = ComposableController(
42 | disableSafeArea: disableSafeArea,
43 | content
44 | )
45 | self.controller = controller
46 |
47 | super.init(frame: frame)
48 |
49 | addSubview(from: controller)
50 | }
51 |
52 | public init<
53 | A: ComposableObject
54 | >(
55 | frame: CGRect = .zero,
56 | _ a: A,
57 | disableSafeArea: Bool = false,
58 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
59 | ) {
60 | let controller = ComposableController(
61 | a,
62 | disableSafeArea: disableSafeArea,
63 | content
64 | )
65 | self.controller = controller
66 |
67 | super.init(frame: frame)
68 |
69 | addSubview(from: controller)
70 | }
71 |
72 | public init<
73 | A: ComposableObject,
74 | B: ComposableObject
75 | >(
76 | frame: CGRect = .zero,
77 | _ a: A,
78 | _ b: B,
79 | disableSafeArea: Bool = false,
80 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
81 | ) {
82 | let controller = ComposableController(
83 | a,
84 | b,
85 | disableSafeArea: disableSafeArea,
86 | content
87 | )
88 | self.controller = controller
89 |
90 | super.init(frame: frame)
91 |
92 | addSubview(from: controller)
93 | }
94 |
95 | public init<
96 | A: ComposableObject,
97 | B: ComposableObject,
98 | C: ComposableObject
99 | >(
100 | frame: CGRect = .zero,
101 | _ a: A,
102 | _ b: B,
103 | _ c: C,
104 | disableSafeArea: Bool = false,
105 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
106 | ) {
107 | let controller = ComposableController(
108 | a,
109 | b,
110 | c,
111 | disableSafeArea: disableSafeArea,
112 | content
113 | )
114 | self.controller = controller
115 |
116 | super.init(frame: frame)
117 |
118 | addSubview(from: controller)
119 | }
120 |
121 | public init<
122 | A: ComposableObject,
123 | B: ComposableObject,
124 | C: ComposableObject,
125 | D: ComposableObject
126 | >(
127 | frame: CGRect = .zero,
128 | _ a: A,
129 | _ b: B,
130 | _ c: C,
131 | _ d: D,
132 | disableSafeArea: Bool = false,
133 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
134 | ) {
135 | let controller = ComposableController(
136 | a,
137 | b,
138 | c,
139 | d,
140 | disableSafeArea: disableSafeArea,
141 | content
142 | )
143 | self.controller = controller
144 |
145 | super.init(frame: frame)
146 |
147 | addSubview(from: controller)
148 | }
149 |
150 | public init<
151 | A: ComposableObject,
152 | B: ComposableObject,
153 | C: ComposableObject,
154 | D: ComposableObject,
155 | E: ComposableObject
156 | >(
157 | frame: CGRect = .zero,
158 | _ a: A,
159 | _ b: B,
160 | _ c: C,
161 | _ d: D,
162 | _ e: E,
163 | disableSafeArea: Bool = false,
164 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
165 | ) {
166 | let controller = ComposableController(
167 | a,
168 | b,
169 | c,
170 | d,
171 | e,
172 | disableSafeArea: disableSafeArea,
173 | content
174 | )
175 | self.controller = controller
176 |
177 | super.init(frame: frame)
178 |
179 | addSubview(from: controller)
180 | }
181 |
182 | public init<
183 | A: ComposableObject,
184 | B: ComposableObject,
185 | C: ComposableObject,
186 | D: ComposableObject,
187 | E: ComposableObject,
188 | F: ComposableObject
189 | >(
190 | frame: CGRect = .zero,
191 | _ a: A,
192 | _ b: B,
193 | _ c: C,
194 | _ d: D,
195 | _ e: E,
196 | _ f: F,
197 | disableSafeArea: Bool = false,
198 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
199 | ) {
200 | let controller = ComposableController(
201 | a,
202 | b,
203 | c,
204 | d,
205 | e,
206 | f,
207 | disableSafeArea: disableSafeArea,
208 | content
209 | )
210 | self.controller = controller
211 |
212 | super.init(frame: frame)
213 |
214 | addSubview(from: controller)
215 | }
216 |
217 | public init<
218 | A: ComposableObject,
219 | B: ComposableObject,
220 | C: ComposableObject,
221 | D: ComposableObject,
222 | E: ComposableObject,
223 | F: ComposableObject,
224 | G: ComposableObject
225 | >(
226 | frame: CGRect = .zero,
227 | _ a: A,
228 | _ b: B,
229 | _ c: C,
230 | _ d: D,
231 | _ e: E,
232 | _ f: F,
233 | _ g: G,
234 | disableSafeArea: Bool = false,
235 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
236 | ) {
237 | let controller = ComposableController(
238 | a,
239 | b,
240 | c,
241 | d,
242 | e,
243 | f,
244 | g,
245 | disableSafeArea: disableSafeArea,
246 | content
247 | )
248 | self.controller = controller
249 |
250 | super.init(frame: frame)
251 |
252 | addSubview(from: controller)
253 | }
254 |
255 | public init<
256 | A: ComposableObject,
257 | B: ComposableObject,
258 | C: ComposableObject,
259 | D: ComposableObject,
260 | E: ComposableObject,
261 | F: ComposableObject,
262 | G: ComposableObject,
263 | H: ComposableObject
264 | >(
265 | frame: CGRect = .zero,
266 | _ a: A,
267 | _ b: B,
268 | _ c: C,
269 | _ d: D,
270 | _ e: E,
271 | _ f: F,
272 | _ g: G,
273 | _ h: H,
274 | disableSafeArea: Bool = false,
275 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
276 | ) {
277 | let controller = ComposableController(
278 | a,
279 | b,
280 | c,
281 | d,
282 | e,
283 | f,
284 | g,
285 | h,
286 | disableSafeArea: disableSafeArea,
287 | content
288 | )
289 | self.controller = controller
290 |
291 | super.init(frame: frame)
292 |
293 | addSubview(from: controller)
294 | }
295 |
296 | // MARK: ObservableObject
297 | public init<
298 | A: ObservableObject
299 | >(
300 | frame: CGRect = .zero,
301 | _ a: A,
302 | disableSafeArea: Bool = false,
303 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
304 | ) {
305 | let controller = ComposableController(
306 | a,
307 | disableSafeArea: disableSafeArea,
308 | content
309 | )
310 | self.controller = controller
311 |
312 | super.init(frame: frame)
313 |
314 | addSubview(from: controller)
315 | }
316 |
317 | public init<
318 | A: ObservableObject,
319 | B: ObservableObject
320 | >(
321 | frame: CGRect = .zero,
322 | _ a: A,
323 | _ b: B,
324 | disableSafeArea: Bool = false,
325 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
326 | ) {
327 | let controller = ComposableController(
328 | a,
329 | b,
330 | disableSafeArea: disableSafeArea,
331 | content
332 | )
333 | self.controller = controller
334 |
335 | super.init(frame: frame)
336 |
337 | addSubview(from: controller)
338 | }
339 |
340 | public init<
341 | A: ObservableObject,
342 | B: ObservableObject,
343 | C: ObservableObject
344 | >(
345 | frame: CGRect = .zero,
346 | _ a: A,
347 | _ b: B,
348 | _ c: C,
349 | disableSafeArea: Bool = false,
350 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
351 | ) {
352 | let controller = ComposableController(
353 | a,
354 | b,
355 | c,
356 | disableSafeArea: disableSafeArea,
357 | content
358 | )
359 | self.controller = controller
360 |
361 | super.init(frame: frame)
362 |
363 | addSubview(from: controller)
364 | }
365 |
366 | public init<
367 | A: ObservableObject,
368 | B: ObservableObject,
369 | C: ObservableObject,
370 | D: ObservableObject
371 | >(
372 | frame: CGRect = .zero,
373 | _ a: A,
374 | _ b: B,
375 | _ c: C,
376 | _ d: D,
377 | disableSafeArea: Bool = false,
378 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
379 | ) {
380 | let controller = ComposableController(
381 | a,
382 | b,
383 | c,
384 | d,
385 | disableSafeArea: disableSafeArea,
386 | content
387 | )
388 | self.controller = controller
389 |
390 | super.init(frame: frame)
391 |
392 | addSubview(from: controller)
393 | }
394 |
395 | public init<
396 | A: ObservableObject,
397 | B: ObservableObject,
398 | C: ObservableObject,
399 | D: ObservableObject,
400 | E: ObservableObject
401 | >(
402 | frame: CGRect = .zero,
403 | _ a: A,
404 | _ b: B,
405 | _ c: C,
406 | _ d: D,
407 | _ e: E,
408 | disableSafeArea: Bool = false,
409 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
410 | ) {
411 | let controller = ComposableController(
412 | a,
413 | b,
414 | c,
415 | d,
416 | e,
417 | disableSafeArea: disableSafeArea,
418 | content
419 | )
420 | self.controller = controller
421 |
422 | super.init(frame: frame)
423 |
424 | addSubview(from: controller)
425 | }
426 |
427 | public init<
428 | A: ObservableObject,
429 | B: ObservableObject,
430 | C: ObservableObject,
431 | D: ObservableObject,
432 | E: ObservableObject,
433 | F: ObservableObject
434 | >(
435 | frame: CGRect = .zero,
436 | _ a: A,
437 | _ b: B,
438 | _ c: C,
439 | _ d: D,
440 | _ e: E,
441 | _ f: F,
442 | disableSafeArea: Bool = false,
443 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
444 | ) {
445 | let controller = ComposableController(
446 | a,
447 | b,
448 | c,
449 | d,
450 | e,
451 | f,
452 | disableSafeArea: disableSafeArea,
453 | content
454 | )
455 | self.controller = controller
456 |
457 | super.init(frame: frame)
458 |
459 | addSubview(from: controller)
460 | }
461 |
462 | public init<
463 | A: ObservableObject,
464 | B: ObservableObject,
465 | C: ObservableObject,
466 | D: ObservableObject,
467 | E: ObservableObject,
468 | F: ObservableObject,
469 | G: ObservableObject
470 | >(
471 | frame: CGRect = .zero,
472 | _ a: A,
473 | _ b: B,
474 | _ c: C,
475 | _ d: D,
476 | _ e: E,
477 | _ f: F,
478 | _ g: G,
479 | disableSafeArea: Bool = false,
480 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
481 | ) {
482 | let controller = ComposableController(
483 | a,
484 | b,
485 | c,
486 | d,
487 | e,
488 | f,
489 | g,
490 | disableSafeArea: disableSafeArea,
491 | content
492 | )
493 | self.controller = controller
494 |
495 | super.init(frame: frame)
496 |
497 | addSubview(from: controller)
498 | }
499 |
500 | public init<
501 | A: ObservableObject,
502 | B: ObservableObject,
503 | C: ObservableObject,
504 | D: ObservableObject,
505 | E: ObservableObject,
506 | F: ObservableObject,
507 | G: ObservableObject,
508 | H: ObservableObject
509 | >(
510 | frame: CGRect = .zero,
511 | _ a: A,
512 | _ b: B,
513 | _ c: C,
514 | _ d: D,
515 | _ e: E,
516 | _ f: F,
517 | _ g: G,
518 | _ h: H,
519 | disableSafeArea: Bool = false,
520 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
521 | ) {
522 | let controller = ComposableController(
523 | a,
524 | b,
525 | c,
526 | d,
527 | e,
528 | f,
529 | g,
530 | h,
531 | disableSafeArea: disableSafeArea,
532 | content
533 | )
534 | self.controller = controller
535 |
536 | super.init(frame: frame)
537 |
538 | addSubview(from: controller)
539 | }
540 |
541 | required public init?(coder: NSCoder) {
542 | fatalError("init(coder:) has not been implemented")
543 | }
544 |
545 | // MARK: - Public
546 | public func run(@ViewBuilder _ content: @escaping () -> some View) {
547 | controller.run(content)
548 | }
549 |
550 | // MARK: - Private
551 | private func addSubview(from controller: UIViewController) {
552 | guard let view = controller.view else { return }
553 |
554 | view.translatesAutoresizingMaskIntoConstraints = false
555 | addSubview(view)
556 |
557 | NSLayoutConstraint.activate([
558 | view.topAnchor.constraint(equalTo: topAnchor),
559 | view.trailingAnchor.constraint(equalTo: trailingAnchor),
560 | view.bottomAnchor.constraint(equalTo: bottomAnchor),
561 | view.leadingAnchor.constraint(equalTo: leadingAnchor)
562 | ])
563 | }
564 | }
565 |
566 | public extension ComposableView {
567 | func run(_ content: some View) {
568 | run { content }
569 | }
570 | }
571 | #endif
572 |
--------------------------------------------------------------------------------
/Sources/Compose/macOS/ComposableController.macOS.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComposableController.macOS.swift
3 | //
4 | //
5 | // Created by JSilver on 2023/05/18.
6 | //
7 |
8 | import SwiftUI
9 |
10 | #if os(macOS)
11 | open class ComposableController: NSHostingController {
12 | // MARK: - Property
13 | private let builder: RootViewBuilder
14 | private let refresher: Refresher
15 |
16 | // MARK: - Initializer
17 | public init(
18 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
19 | ) {
20 | let builder = RootViewBuilder(content)
21 | let refresher = Refresher()
22 |
23 | self.builder = builder
24 | self.refresher = refresher
25 |
26 | super.init(
27 | rootView: AnyView(
28 | ComposeView(
29 | refresher
30 | ) {
31 | builder.content()
32 | }
33 | )
34 | )
35 | }
36 |
37 | public init(
38 | _ content: some View
39 | ) {
40 | let builder = RootViewBuilder(content)
41 | let refresher = Refresher()
42 |
43 | self.builder = builder
44 | self.refresher = refresher
45 |
46 | super.init(
47 | rootView: AnyView(
48 | ComposeView(
49 | refresher
50 | ) {
51 | builder.content()
52 | }
53 | )
54 | )
55 | }
56 |
57 | public init<
58 | A: ComposableObject
59 | >(
60 | _ a: A,
61 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
62 | ) {
63 | let builder = RootViewBuilder(content)
64 | let refresher = Refresher()
65 |
66 | self.builder = builder
67 | self.refresher = refresher
68 |
69 | super.init(
70 | rootView: AnyView(
71 | ComposeView1(
72 | refresher,
73 | a
74 | ) {
75 | builder.content()
76 | }
77 | )
78 | )
79 | }
80 |
81 | public init<
82 | A: ComposableObject,
83 | B: ComposableObject
84 | >(
85 | _ a: A,
86 | _ b: B,
87 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
88 | ) {
89 | let builder = RootViewBuilder(content)
90 | let refresher = Refresher()
91 |
92 | self.builder = builder
93 | self.refresher = refresher
94 |
95 | super.init(
96 | rootView: AnyView(
97 | ComposeView2(
98 | refresher,
99 | a,
100 | b
101 | ) {
102 | builder.content()
103 | }
104 | )
105 | )
106 | }
107 |
108 | public init<
109 | A: ComposableObject,
110 | B: ComposableObject,
111 | C: ComposableObject
112 | >(
113 | _ a: A,
114 | _ b: B,
115 | _ c: C,
116 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
117 | ) {
118 | let builder = RootViewBuilder(content)
119 | let refresher = Refresher()
120 |
121 | self.builder = builder
122 | self.refresher = refresher
123 |
124 | super.init(
125 | rootView: AnyView(
126 | ComposeView3(
127 | refresher,
128 | a,
129 | b,
130 | c
131 | ) {
132 | builder.content()
133 | }
134 | )
135 | )
136 | }
137 |
138 | public init<
139 | A: ComposableObject,
140 | B: ComposableObject,
141 | C: ComposableObject,
142 | D: ComposableObject
143 | >(
144 | _ a: A,
145 | _ b: B,
146 | _ c: C,
147 | _ d: D,
148 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
149 | ) {
150 | let builder = RootViewBuilder(content)
151 | let refresher = Refresher()
152 |
153 | self.builder = builder
154 | self.refresher = refresher
155 |
156 | super.init(
157 | rootView: AnyView(
158 | ComposeView4(
159 | refresher,
160 | a,
161 | b,
162 | c,
163 | d
164 | ) {
165 | builder.content()
166 | }
167 | )
168 | )
169 | }
170 |
171 | public init<
172 | A: ComposableObject,
173 | B: ComposableObject,
174 | C: ComposableObject,
175 | D: ComposableObject,
176 | E: ComposableObject
177 | >(
178 | _ a: A,
179 | _ b: B,
180 | _ c: C,
181 | _ d: D,
182 | _ e: E,
183 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
184 | ) {
185 | let builder = RootViewBuilder(content)
186 | let refresher = Refresher()
187 |
188 | self.builder = builder
189 | self.refresher = refresher
190 |
191 | super.init(
192 | rootView: AnyView(
193 | ComposeView5(
194 | refresher,
195 | a,
196 | b,
197 | c,
198 | d,
199 | e
200 | ) {
201 | builder.content()
202 | }
203 | )
204 | )
205 | }
206 |
207 | public init<
208 | A: ComposableObject,
209 | B: ComposableObject,
210 | C: ComposableObject,
211 | D: ComposableObject,
212 | E: ComposableObject,
213 | F: ComposableObject
214 | >(
215 | _ a: A,
216 | _ b: B,
217 | _ c: C,
218 | _ d: D,
219 | _ e: E,
220 | _ f: F,
221 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
222 | ) {
223 | let builder = RootViewBuilder(content)
224 | let refresher = Refresher()
225 |
226 | self.builder = builder
227 | self.refresher = refresher
228 |
229 | super.init(
230 | rootView: AnyView(
231 | ComposeView6(
232 | refresher,
233 | a,
234 | b,
235 | c,
236 | d,
237 | e,
238 | f
239 | ) {
240 | builder.content()
241 | }
242 | )
243 | )
244 | }
245 |
246 | public init<
247 | A: ComposableObject,
248 | B: ComposableObject,
249 | C: ComposableObject,
250 | D: ComposableObject,
251 | E: ComposableObject,
252 | F: ComposableObject,
253 | G: ComposableObject
254 | >(
255 | _ a: A,
256 | _ b: B,
257 | _ c: C,
258 | _ d: D,
259 | _ e: E,
260 | _ f: F,
261 | _ g: G,
262 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
263 | ) {
264 | let builder = RootViewBuilder(content)
265 | let refresher = Refresher()
266 |
267 | self.builder = builder
268 | self.refresher = refresher
269 |
270 | super.init(
271 | rootView: AnyView(
272 | ComposeView7(
273 | refresher,
274 | a,
275 | b,
276 | c,
277 | d,
278 | e,
279 | f,
280 | g
281 | ) {
282 | builder.content()
283 | }
284 | )
285 | )
286 | }
287 |
288 | public init<
289 | A: ComposableObject,
290 | B: ComposableObject,
291 | C: ComposableObject,
292 | D: ComposableObject,
293 | E: ComposableObject,
294 | F: ComposableObject,
295 | G: ComposableObject,
296 | H: ComposableObject
297 | >(
298 | _ a: A,
299 | _ b: B,
300 | _ c: C,
301 | _ d: D,
302 | _ e: E,
303 | _ f: F,
304 | _ g: G,
305 | _ h: H,
306 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
307 | ) {
308 | let builder = RootViewBuilder(content)
309 | let refresher = Refresher()
310 |
311 | self.builder = builder
312 | self.refresher = refresher
313 |
314 | super.init(
315 | rootView: AnyView(
316 | ComposeView8(
317 | refresher,
318 | a,
319 | b,
320 | c,
321 | d,
322 | e,
323 | f,
324 | g,
325 | h
326 | ) {
327 | builder.content()
328 | }
329 | )
330 | )
331 | }
332 |
333 | // MARK: ObservableObject
334 | public init<
335 | A: ObservableObject
336 | >(
337 | _ a: A,
338 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
339 | ) {
340 | let builder = RootViewBuilder(content)
341 | let refresher = Refresher()
342 |
343 | self.builder = builder
344 | self.refresher = refresher
345 |
346 | if #available(macOS 11.0, *) {
347 | super.init(
348 | rootView: AnyView(
349 | ComposeView1(
350 | refresher,
351 | .state(a)
352 | ) {
353 | builder.content()
354 | }
355 | )
356 | )
357 | } else {
358 | super.init(
359 | rootView: AnyView(
360 | ComposeView1(
361 | refresher,
362 | .observed(a)
363 | ) {
364 | builder.content()
365 | }
366 | )
367 | )
368 | }
369 | }
370 |
371 | public init<
372 | A: ObservableObject,
373 | B: ObservableObject
374 | >(
375 | _ a: A,
376 | _ b: B,
377 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
378 | ) {
379 | let builder = RootViewBuilder(content)
380 | let refresher = Refresher()
381 |
382 | self.builder = builder
383 | self.refresher = refresher
384 |
385 | if #available(macOS 11.0, *) {
386 | super.init(
387 | rootView: AnyView(
388 | ComposeView2(
389 | refresher,
390 | .state(a),
391 | .state(b)
392 | ) {
393 | builder.content()
394 | }
395 | )
396 | )
397 | } else {
398 | super.init(
399 | rootView: AnyView(
400 | ComposeView2(
401 | refresher,
402 | .observed(a),
403 | .observed(b)
404 | ) {
405 | builder.content()
406 | }
407 | )
408 | )
409 | }
410 | }
411 |
412 | public init<
413 | A: ObservableObject,
414 | B: ObservableObject,
415 | C: ObservableObject
416 | >(
417 | _ a: A,
418 | _ b: B,
419 | _ c: C,
420 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
421 | ) {
422 | let builder = RootViewBuilder(content)
423 | let refresher = Refresher()
424 |
425 | self.builder = builder
426 | self.refresher = refresher
427 |
428 | if #available(macOS 11.0, *) {
429 | super.init(
430 | rootView: AnyView(
431 | ComposeView3(
432 | refresher,
433 | .state(a),
434 | .state(b),
435 | .state(c)
436 | ) {
437 | builder.content()
438 | }
439 | )
440 | )
441 | } else {
442 | super.init(
443 | rootView: AnyView(
444 | ComposeView3(
445 | refresher,
446 | .observed(a),
447 | .observed(b),
448 | .observed(c)
449 | ) {
450 | builder.content()
451 | }
452 | )
453 | )
454 | }
455 | }
456 |
457 | public init<
458 | A: ObservableObject,
459 | B: ObservableObject,
460 | C: ObservableObject,
461 | D: ObservableObject
462 | >(
463 | _ a: A,
464 | _ b: B,
465 | _ c: C,
466 | _ d: D,
467 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
468 | ) {
469 | let builder = RootViewBuilder(content)
470 | let refresher = Refresher()
471 |
472 | self.builder = builder
473 | self.refresher = refresher
474 |
475 | if #available(macOS 11.0, *) {
476 | super.init(
477 | rootView: AnyView(
478 | ComposeView4(
479 | refresher,
480 | .state(a),
481 | .state(b),
482 | .state(c),
483 | .state(d)
484 | ) {
485 | builder.content()
486 | }
487 | )
488 | )
489 | } else {
490 | super.init(
491 | rootView: AnyView(
492 | ComposeView4(
493 | refresher,
494 | .observed(a),
495 | .observed(b),
496 | .observed(c),
497 | .observed(d)
498 | ) {
499 | builder.content()
500 | }
501 | )
502 | )
503 | }
504 | }
505 |
506 | public init<
507 | A: ObservableObject,
508 | B: ObservableObject,
509 | C: ObservableObject,
510 | D: ObservableObject,
511 | E: ObservableObject
512 | >(
513 | _ a: A,
514 | _ b: B,
515 | _ c: C,
516 | _ d: D,
517 | _ e: E,
518 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
519 | ) {
520 | let builder = RootViewBuilder(content)
521 | let refresher = Refresher()
522 |
523 | self.builder = builder
524 | self.refresher = refresher
525 |
526 | if #available(macOS 11.0, *) {
527 | super.init(
528 | rootView: AnyView(
529 | ComposeView5(
530 | refresher,
531 | .state(a),
532 | .state(b),
533 | .state(c),
534 | .state(d),
535 | .state(e)
536 | ) {
537 | builder.content()
538 | }
539 | )
540 | )
541 | } else {
542 | super.init(
543 | rootView: AnyView(
544 | ComposeView5(
545 | refresher,
546 | .observed(a),
547 | .observed(b),
548 | .observed(c),
549 | .observed(d),
550 | .observed(e)
551 | ) {
552 | builder.content()
553 | }
554 | )
555 | )
556 | }
557 | }
558 |
559 | public init<
560 | A: ObservableObject,
561 | B: ObservableObject,
562 | C: ObservableObject,
563 | D: ObservableObject,
564 | E: ObservableObject,
565 | F: ObservableObject
566 | >(
567 | _ a: A,
568 | _ b: B,
569 | _ c: C,
570 | _ d: D,
571 | _ e: E,
572 | _ f: F,
573 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
574 | ) {
575 | let builder = RootViewBuilder(content)
576 | let refresher = Refresher()
577 |
578 | self.builder = builder
579 | self.refresher = refresher
580 |
581 | if #available(macOS 11.0, *) {
582 | super.init(
583 | rootView: AnyView(
584 | ComposeView6(
585 | refresher,
586 | .state(a),
587 | .state(b),
588 | .state(c),
589 | .state(d),
590 | .state(e),
591 | .state(f)
592 | ) {
593 | builder.content()
594 | }
595 | )
596 | )
597 | } else {
598 | super.init(
599 | rootView: AnyView(
600 | ComposeView6(
601 | refresher,
602 | .observed(a),
603 | .observed(b),
604 | .observed(c),
605 | .observed(d),
606 | .observed(e),
607 | .observed(f)
608 | ) {
609 | builder.content()
610 | }
611 | )
612 | )
613 | }
614 | }
615 |
616 | public init<
617 | A: ObservableObject,
618 | B: ObservableObject,
619 | C: ObservableObject,
620 | D: ObservableObject,
621 | E: ObservableObject,
622 | F: ObservableObject,
623 | G: ObservableObject
624 | >(
625 | _ a: A,
626 | _ b: B,
627 | _ c: C,
628 | _ d: D,
629 | _ e: E,
630 | _ f: F,
631 | _ g: G,
632 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
633 | ) {
634 | let builder = RootViewBuilder(content)
635 | let refresher = Refresher()
636 |
637 | self.builder = builder
638 | self.refresher = refresher
639 |
640 | if #available(macOS 11.0, *) {
641 | super.init(
642 | rootView: AnyView(
643 | ComposeView7(
644 | refresher,
645 | .state(a),
646 | .state(b),
647 | .state(c),
648 | .state(d),
649 | .state(e),
650 | .state(f),
651 | .state(g)
652 | ) {
653 | builder.content()
654 | }
655 | )
656 | )
657 | } else {
658 | super.init(
659 | rootView: AnyView(
660 | ComposeView7(
661 | refresher,
662 | .observed(a),
663 | .observed(b),
664 | .observed(c),
665 | .observed(d),
666 | .observed(e),
667 | .observed(f),
668 | .observed(g)
669 | ) {
670 | builder.content()
671 | }
672 | )
673 | )
674 | }
675 | }
676 |
677 | public init<
678 | A: ObservableObject,
679 | B: ObservableObject,
680 | C: ObservableObject,
681 | D: ObservableObject,
682 | E: ObservableObject,
683 | F: ObservableObject,
684 | G: ObservableObject,
685 | H: ObservableObject
686 | >(
687 | _ a: A,
688 | _ b: B,
689 | _ c: C,
690 | _ d: D,
691 | _ e: E,
692 | _ f: F,
693 | _ g: G,
694 | _ h: H,
695 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
696 | ) {
697 | let builder = RootViewBuilder(content)
698 | let refresher = Refresher()
699 |
700 | self.builder = builder
701 | self.refresher = refresher
702 |
703 | if #available(macOS 11.0, *) {
704 | super.init(
705 | rootView: AnyView(
706 | ComposeView8(
707 | refresher,
708 | .state(a),
709 | .state(b),
710 | .state(c),
711 | .state(d),
712 | .state(e),
713 | .state(f),
714 | .state(g),
715 | .state(h)
716 | ) {
717 | builder.content()
718 | }
719 | )
720 | )
721 | } else {
722 | super.init(
723 | rootView: AnyView(
724 | ComposeView8(
725 | refresher,
726 | .observed(a),
727 | .observed(b),
728 | .observed(c),
729 | .observed(d),
730 | .observed(e),
731 | .observed(f),
732 | .observed(g),
733 | .observed(h)
734 | ) {
735 | builder.content()
736 | }
737 | )
738 | )
739 | }
740 | }
741 |
742 | @MainActor
743 | public required dynamic init?(coder aDecoder: NSCoder) {
744 | fatalError("init(coder:) has not been implemented")
745 | }
746 |
747 | // MARK: - Lifecycle
748 | open override func viewWillLayout() {
749 | super.viewWillLayout()
750 |
751 | guard let window = view.window,
752 | window.contentViewController == self
753 | else { return }
754 |
755 | // Only attempt to adjust if the window content view controller is itself.
756 | let fittingSize = view.fittingSize
757 | let windowSize = window.frame.size
758 |
759 | guard windowSize.width < fittingSize.width
760 | || windowSize.height < fittingSize.height
761 | else { return }
762 |
763 | // Adjust window size when the window size tries to be smaller than the view size.
764 | let adjustedSize = CGSize(
765 | width: max(windowSize.width, fittingSize.width),
766 | height: max(windowSize.height, fittingSize.height)
767 | )
768 |
769 | window.setContentSize(adjustedSize)
770 | }
771 |
772 | // MARK: - Public
773 | public func run(@ViewBuilder _ content: @escaping () -> some View) {
774 | builder.setContent(content)
775 | refresher.refresh()
776 | }
777 |
778 | // MARK: - Private
779 | }
780 |
781 | public extension ComposableController {
782 | func run(_ content: some View) {
783 | run { content }
784 | }
785 | }
786 | #endif
787 |
--------------------------------------------------------------------------------
/Sources/Compose/macOS/ComposableView.macOS.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComposableView.macOS.swift
3 | //
4 | //
5 | // Created by JSilver on 2023/05/18.
6 | //
7 |
8 | import SwiftUI
9 | #if os(macOS)
10 | import Cocoa
11 |
12 | open class ComposableView: NSView {
13 | // MARK: - Property
14 | public let controller: ComposableController
15 |
16 | public var rootView: AnyView { controller.rootView }
17 | public var content: NSView { controller.view }
18 |
19 | // MARK: - Initializer
20 | public init(
21 | frame: CGRect = .zero,
22 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
23 | ) {
24 | let controller = ComposableController(
25 | content
26 | )
27 | self.controller = controller
28 |
29 | super.init(frame: frame)
30 |
31 | addSubview(from: controller)
32 | }
33 |
34 | public init(
35 | frame: CGRect = .zero,
36 | _ content: some View
37 | ) {
38 | let controller = ComposableController(
39 | content
40 | )
41 | self.controller = controller
42 |
43 | super.init(frame: frame)
44 |
45 | addSubview(from: controller)
46 | }
47 |
48 | public init<
49 | A: ComposableObject
50 | >(
51 | frame: CGRect = .zero,
52 | _ a: A,
53 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
54 | ) {
55 | let controller = ComposableController(
56 | a,
57 | content
58 | )
59 | self.controller = controller
60 |
61 | super.init(frame: frame)
62 |
63 | addSubview(from: controller)
64 | }
65 |
66 | public init<
67 | A: ComposableObject,
68 | B: ComposableObject
69 | >(
70 | frame: CGRect = .zero,
71 | _ a: A,
72 | _ b: B,
73 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
74 | ) {
75 | let controller = ComposableController(
76 | a,
77 | b,
78 | content
79 | )
80 | self.controller = controller
81 |
82 | super.init(frame: frame)
83 |
84 | addSubview(from: controller)
85 | }
86 |
87 | public init<
88 | A: ComposableObject,
89 | B: ComposableObject,
90 | C: ComposableObject
91 | >(
92 | frame: CGRect = .zero,
93 | _ a: A,
94 | _ b: B,
95 | _ c: C,
96 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
97 | ) {
98 | let controller = ComposableController(
99 | a,
100 | b,
101 | c,
102 | content
103 | )
104 | self.controller = controller
105 |
106 | super.init(frame: frame)
107 |
108 | addSubview(from: controller)
109 | }
110 |
111 | public init<
112 | A: ComposableObject,
113 | B: ComposableObject,
114 | C: ComposableObject,
115 | D: ComposableObject
116 | >(
117 | frame: CGRect = .zero,
118 | _ a: A,
119 | _ b: B,
120 | _ c: C,
121 | _ d: D,
122 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
123 | ) {
124 | let controller = ComposableController(
125 | a,
126 | b,
127 | c,
128 | d,
129 | content
130 | )
131 | self.controller = controller
132 |
133 | super.init(frame: frame)
134 |
135 | addSubview(from: controller)
136 | }
137 |
138 | public init<
139 | A: ComposableObject,
140 | B: ComposableObject,
141 | C: ComposableObject,
142 | D: ComposableObject,
143 | E: ComposableObject
144 | >(
145 | frame: CGRect = .zero,
146 | _ a: A,
147 | _ b: B,
148 | _ c: C,
149 | _ d: D,
150 | _ e: E,
151 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
152 | ) {
153 | let controller = ComposableController(
154 | a,
155 | b,
156 | c,
157 | d,
158 | e,
159 | content
160 | )
161 | self.controller = controller
162 |
163 | super.init(frame: frame)
164 |
165 | addSubview(from: controller)
166 | }
167 |
168 | public init<
169 | A: ComposableObject,
170 | B: ComposableObject,
171 | C: ComposableObject,
172 | D: ComposableObject,
173 | E: ComposableObject,
174 | F: ComposableObject
175 | >(
176 | frame: CGRect = .zero,
177 | _ a: A,
178 | _ b: B,
179 | _ c: C,
180 | _ d: D,
181 | _ e: E,
182 | _ f: F,
183 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
184 | ) {
185 | let controller = ComposableController(
186 | a,
187 | b,
188 | c,
189 | d,
190 | e,
191 | f,
192 | content
193 | )
194 | self.controller = controller
195 |
196 | super.init(frame: frame)
197 |
198 | addSubview(from: controller)
199 | }
200 |
201 | public init<
202 | A: ComposableObject,
203 | B: ComposableObject,
204 | C: ComposableObject,
205 | D: ComposableObject,
206 | E: ComposableObject,
207 | F: ComposableObject,
208 | G: ComposableObject
209 | >(
210 | frame: CGRect = .zero,
211 | _ a: A,
212 | _ b: B,
213 | _ c: C,
214 | _ d: D,
215 | _ e: E,
216 | _ f: F,
217 | _ g: G,
218 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
219 | ) {
220 | let controller = ComposableController(
221 | a,
222 | b,
223 | c,
224 | d,
225 | e,
226 | f,
227 | g,
228 | content
229 | )
230 | self.controller = controller
231 |
232 | super.init(frame: frame)
233 |
234 | addSubview(from: controller)
235 | }
236 |
237 | public init<
238 | A: ComposableObject,
239 | B: ComposableObject,
240 | C: ComposableObject,
241 | D: ComposableObject,
242 | E: ComposableObject,
243 | F: ComposableObject,
244 | G: ComposableObject,
245 | H: ComposableObject
246 | >(
247 | frame: CGRect = .zero,
248 | _ a: A,
249 | _ b: B,
250 | _ c: C,
251 | _ d: D,
252 | _ e: E,
253 | _ f: F,
254 | _ g: G,
255 | _ h: H,
256 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
257 | ) {
258 | let controller = ComposableController(
259 | a,
260 | b,
261 | c,
262 | d,
263 | e,
264 | f,
265 | g,
266 | h,
267 | content
268 | )
269 | self.controller = controller
270 |
271 | super.init(frame: frame)
272 |
273 | addSubview(from: controller)
274 | }
275 |
276 | // MARK: ObservableObject
277 | public init<
278 | A: ObservableObject
279 | >(
280 | frame: CGRect = .zero,
281 | _ a: A,
282 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
283 | ) {
284 | let controller = ComposableController(
285 | a,
286 | content
287 | )
288 | self.controller = controller
289 |
290 | super.init(frame: frame)
291 |
292 | addSubview(from: controller)
293 | }
294 |
295 | public init<
296 | A: ObservableObject,
297 | B: ObservableObject
298 | >(
299 | frame: CGRect = .zero,
300 | _ a: A,
301 | _ b: B,
302 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
303 | ) {
304 | let controller = ComposableController(
305 | a,
306 | b,
307 | content
308 | )
309 | self.controller = controller
310 |
311 | super.init(frame: frame)
312 |
313 | addSubview(from: controller)
314 | }
315 |
316 | public init<
317 | A: ObservableObject,
318 | B: ObservableObject,
319 | C: ObservableObject
320 | >(
321 | frame: CGRect = .zero,
322 | _ a: A,
323 | _ b: B,
324 | _ c: C,
325 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
326 | ) {
327 | let controller = ComposableController(
328 | a,
329 | b,
330 | c,
331 | content
332 | )
333 | self.controller = controller
334 |
335 | super.init(frame: frame)
336 |
337 | addSubview(from: controller)
338 | }
339 |
340 | public init<
341 | A: ObservableObject,
342 | B: ObservableObject,
343 | C: ObservableObject,
344 | D: ObservableObject
345 | >(
346 | frame: CGRect = .zero,
347 | _ a: A,
348 | _ b: B,
349 | _ c: C,
350 | _ d: D,
351 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
352 | ) {
353 | let controller = ComposableController(
354 | a,
355 | b,
356 | c,
357 | d,
358 | content
359 | )
360 | self.controller = controller
361 |
362 | super.init(frame: frame)
363 |
364 | addSubview(from: controller)
365 | }
366 |
367 | public init<
368 | A: ObservableObject,
369 | B: ObservableObject,
370 | C: ObservableObject,
371 | D: ObservableObject,
372 | E: ObservableObject
373 | >(
374 | frame: CGRect = .zero,
375 | _ a: A,
376 | _ b: B,
377 | _ c: C,
378 | _ d: D,
379 | _ e: E,
380 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
381 | ) {
382 | let controller = ComposableController(
383 | a,
384 | b,
385 | c,
386 | d,
387 | e,
388 | content
389 | )
390 | self.controller = controller
391 |
392 | super.init(frame: frame)
393 |
394 | addSubview(from: controller)
395 | }
396 |
397 | public init<
398 | A: ObservableObject,
399 | B: ObservableObject,
400 | C: ObservableObject,
401 | D: ObservableObject,
402 | E: ObservableObject,
403 | F: ObservableObject
404 | >(
405 | frame: CGRect = .zero,
406 | _ a: A,
407 | _ b: B,
408 | _ c: C,
409 | _ d: D,
410 | _ e: E,
411 | _ f: F,
412 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
413 | ) {
414 | let controller = ComposableController(
415 | a,
416 | b,
417 | c,
418 | d,
419 | e,
420 | f,
421 | content
422 | )
423 | self.controller = controller
424 |
425 | super.init(frame: frame)
426 |
427 | addSubview(from: controller)
428 | }
429 |
430 | public init<
431 | A: ObservableObject,
432 | B: ObservableObject,
433 | C: ObservableObject,
434 | D: ObservableObject,
435 | E: ObservableObject,
436 | F: ObservableObject,
437 | G: ObservableObject
438 | >(
439 | frame: CGRect = .zero,
440 | _ a: A,
441 | _ b: B,
442 | _ c: C,
443 | _ d: D,
444 | _ e: E,
445 | _ f: F,
446 | _ g: G,
447 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
448 | ) {
449 | let controller = ComposableController(
450 | a,
451 | b,
452 | c,
453 | d,
454 | e,
455 | f,
456 | g,
457 | content
458 | )
459 | self.controller = controller
460 |
461 | super.init(frame: frame)
462 |
463 | addSubview(from: controller)
464 | }
465 |
466 | public init<
467 | A: ObservableObject,
468 | B: ObservableObject,
469 | C: ObservableObject,
470 | D: ObservableObject,
471 | E: ObservableObject,
472 | F: ObservableObject,
473 | G: ObservableObject,
474 | H: ObservableObject
475 | >(
476 | frame: CGRect = .zero,
477 | _ a: A,
478 | _ b: B,
479 | _ c: C,
480 | _ d: D,
481 | _ e: E,
482 | _ f: F,
483 | _ g: G,
484 | _ h: H,
485 | @ViewBuilder _ content: @escaping () -> some View = { EmptyView() }
486 | ) {
487 | let controller = ComposableController(
488 | a,
489 | b,
490 | c,
491 | d,
492 | e,
493 | f,
494 | g,
495 | h,
496 | content
497 | )
498 | self.controller = controller
499 |
500 | super.init(frame: frame)
501 |
502 | addSubview(from: controller)
503 | }
504 |
505 | required public init?(coder: NSCoder) {
506 | fatalError("init(coder:) has not been implemented")
507 | }
508 |
509 | // MARK: - Public
510 | public func run(@ViewBuilder _ content: @escaping () -> some View) {
511 | controller.run(content)
512 | }
513 |
514 | // MARK: - Private
515 | private func addSubview(from controller: NSViewController) {
516 | let view = controller.view
517 |
518 | view.translatesAutoresizingMaskIntoConstraints = false
519 | addSubview(view)
520 |
521 | NSLayoutConstraint.activate([
522 | view.topAnchor.constraint(equalTo: topAnchor),
523 | view.trailingAnchor.constraint(equalTo: trailingAnchor),
524 | view.bottomAnchor.constraint(equalTo: bottomAnchor),
525 | view.leadingAnchor.constraint(equalTo: leadingAnchor)
526 | ])
527 | }
528 | }
529 |
530 | public extension ComposableView {
531 | func run(_ content: some View) {
532 | run { content }
533 | }
534 | }
535 | #endif
536 |
537 |
--------------------------------------------------------------------------------
/Sources/ComposeMacro/ComposableObjectMacro.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComposableObjectMacro.swift
3 | //
4 | //
5 | // Created by jsilver on 6/2/24.
6 | //
7 |
8 | import SwiftSyntax
9 | import SwiftSyntaxBuilder
10 | import SwiftSyntaxMacros
11 |
12 | public struct ComposableObjectMacro { }
13 |
14 | extension ComposableObjectMacro: ExtensionMacro {
15 | public static func expansion(
16 | of node: SwiftSyntax.AttributeSyntax,
17 | attachedTo declaration: some SwiftSyntax.DeclGroupSyntax,
18 | providingExtensionsOf type: some SwiftSyntax.TypeSyntaxProtocol,
19 | conformingTo protocols: [SwiftSyntax.TypeSyntax],
20 | in context: some SwiftSyntaxMacros.MacroExpansionContext
21 | ) throws -> [SwiftSyntax.ExtensionDeclSyntax] {
22 | let extenstion = DeclSyntax("""
23 | extension \(type.trimmed): ObservableObject { }
24 | """)
25 |
26 | return [extenstion.cast(ExtensionDeclSyntax.self)]
27 | }
28 | }
29 |
30 | extension ComposableObjectMacro: MemberAttributeMacro {
31 | public static func expansion(
32 | of node: AttributeSyntax,
33 | attachedTo declaration: some DeclGroupSyntax,
34 | providingAttributesFor member: some DeclSyntaxProtocol,
35 | in context: some MacroExpansionContext
36 | ) throws -> [AttributeSyntax] {
37 | guard !(member.variable?.isPrivateProperty ?? false) else {
38 | return []
39 | }
40 |
41 | guard !(member.variable?.isConstant ?? false) else {
42 | return []
43 | }
44 |
45 | guard member.variable?.binding?.isObservableStoredProperty ?? false else {
46 | return []
47 | }
48 |
49 | return [
50 | AttributeSyntax(
51 | attributeName: IdentifierTypeSyntax(
52 | name: .identifier("Published")
53 | )
54 | )
55 | ]
56 | }
57 | }
58 |
59 | private extension DeclSyntaxProtocol {
60 | var variable: VariableDeclSyntax? {
61 | self.as(VariableDeclSyntax.self)
62 | }
63 | }
64 |
65 | private extension VariableDeclSyntax {
66 | var modifier: DeclModifierSyntax? {
67 | modifiers.first
68 | }
69 |
70 | var binding: PatternBindingListSyntax.Element? {
71 | bindings.first
72 | }
73 |
74 | var isPrivateProperty: Bool {
75 | modifier?.name.text == "private" && modifier?.detail?.detail.text != "set"
76 | }
77 |
78 | var isConstant: Bool {
79 | bindingSpecifier.text == "let"
80 | }
81 | }
82 |
83 | private extension PatternBindingListSyntax.Element {
84 | var identifier: IdentifierPatternSyntax? {
85 | pattern.as(IdentifierPatternSyntax.self)
86 | }
87 |
88 | var isObservableStoredProperty: Bool {
89 | guard let text = identifier?.identifier.text else { return false }
90 |
91 | guard accessorBlock == nil && text != "_register" && text != "_storage" else { return false }
92 |
93 | return true
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Sources/ComposeMacro/Plugin.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Plugin.swift
3 | //
4 | //
5 | // Created by jsilver on 6/2/24.
6 | //
7 |
8 | import SwiftCompilerPlugin
9 | import SwiftSyntaxMacros
10 |
11 | @main
12 | struct Plugin: CompilerPlugin {
13 | let providingMacros: [Macro.Type] = [
14 | ComposableObjectMacro.self
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/Tests/ComposeMacroTests/ComposeMacroTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComposeMacroTests.swift
3 | //
4 | //
5 | // Created by jsilver on 6/2/24.
6 | //
7 |
8 | import SwiftSyntaxMacros
9 | import SwiftSyntaxMacrosTestSupport
10 | import XCTest
11 | @testable import ComposeMacro
12 |
13 | let testMacros: [String: Macro.Type] = [
14 | "ComposableObject": ComposableObjectMacro.self,
15 | ]
16 |
17 | final class ComposeMacroTests: XCTestCase {
18 | // MARK: - Property
19 |
20 | // MARK: - Lifecycle
21 | override func setUp() {
22 |
23 | }
24 |
25 | override func tearDown() {
26 |
27 | }
28 |
29 | // MARK: - Test
30 | func test_that_reduce_macro_expand_source() {
31 | assertMacroExpansion(
32 | """
33 | @ComposableObject
34 | class Environment {
35 | public var a: Int = 0
36 | var b: Int = 0
37 | private(set) var c: Int = 0
38 | private var d: Int = 0
39 | public private(set) var e: Int = 0
40 | public let f: Int = 0
41 | let g: Int = 0
42 | private(set) let h: Int = 0
43 | private let i: Int = 0
44 | public private(set) let j: Int = 0
45 | }
46 | """,
47 | expandedSource: """
48 | class Environment {
49 | @Published
50 | public var a: Int = 0
51 | @Published
52 | var b: Int = 0
53 | @Published
54 | private(set) var c: Int = 0
55 | private var d: Int = 0
56 | @Published
57 | public private(set) var e: Int = 0
58 | public let f: Int = 0
59 | let g: Int = 0
60 | private(set) let h: Int = 0
61 | private let i: Int = 0
62 | public private(set) let j: Int = 0
63 | }
64 |
65 | extension Environment: ObservableObject {
66 | }
67 | """,
68 | macros: testMacros
69 | )
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Tests/ComposeTests/ComposeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ComposeTests.swift
3 | //
4 | //
5 | // Created by JSilver on 2023/02/04.
6 | //
7 |
8 | import XCTest
9 | @testable import Compose
10 |
11 | final class ComposeTests: XCTestCase {
12 | // MARK: - Property
13 |
14 | // MARK: - Lifecycle
15 | override func setUp() async throws {
16 |
17 | }
18 |
19 | override func tearDown() async throws {
20 |
21 | }
22 |
23 | // MARK: - Test
24 | func testExample() throws {
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------