├── .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 | --------------------------------------------------------------------------------