Compose is a package that allows you to use UIViewController
view as SwiftUI
view.
- iOS 13.0+
- macOS 10.15+
dependencies: [
.package(url: "https://github.com/wlsdms0122/Compose.git", from: "1.0.0")
]
You can inherit ComposableController
to make view controller using SwiftUI
view.
CopmosableController
inherited UIHostingController
.
import Compose
class MainViewController: ComposableController { ... }
To layout view of controller, You should be define initializer. The content body will call when SwiftUI
needs to re-render thre view.
import Compose
class MainViewController: ComposableController {
init() {
super.init() {
Text("Hello World")
}
}
}
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.
And in iOS 14.0 or macOS 11.0 or later, you can choose the type between StateObject
and ObservedObject
using ComposableObject
's method
⚠️ Not use@State
or@Binding
property wrapper inComposableController
. All states are managed by@Published
ofObservableObejct
.
public extension ComposableObject {
@available(iOS 14.0, macOS 11.0, *)
static func state<ObjectType: ObservableObject>(_ object: ObjectType) -> Self where Self == StateObject<ObjectType>
static func observed<ObjectType: ObservableObject>(_ object: ObjectType) -> Self where Self == ObservedObject<ObjectType>
}
import Compose
class MainViewController: ComposableController {
@ComposableObject
final class Environment {
var count: Int = 0
}
override init() {
let env = Environment()
super.init(.observed(env)) {
Text("\(env.count)")
Button("+1") {
env.count += 1
}
}
}
}
The ComposableController
prepared a number of initializers for observable objects. (0-8)
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.
import Compose
class MainViewController: ComposableController {
...
override init() {
...
let env = Environment()
// The `env` object behaves as either an `ObservedObject` or a `StateObject`, depending on its version.
super.init(env)
...
}
}
To reference your own object (view controller), you can call the run(_:)
method after the super initializer call.
import Compose
class MainViewController: ComposableController {
override init() {
super.init()
run { [weak self] in
Button("Present new controller") {
self?.present(ViewController(), animated: true)
}
}
}
}
If you want to convert a SwiftUI View
to a UIView
, use ComposableView
. It's almost same with ComposableController
.
import Compose
/// Define custom `UIView`.
class TitleLabel: ComposableView {
init(frame: CGRect) {
super.init(frame: frame) {
Text("Hello World")
}
}
}
/// Instantiation with `View`
ComposableView(Text("Hello World"))
import Compose
class ListViewController: ComposableController {
init(viewModel: ListViewModel) {
super.init(viewModel)
run {
List(viewModel.items) {
Text("\($0.title)")
}
}
}
}
You can this style to layout complex view. (like Compose
of Android
)
import Compose
class ListViewController: ComposableController {
init(viewModel: ListViewModel) {
super.init(viewModel)
run {
Root(
items: viewModel.items
) {
viewModel.select($0)
}
}
}
}
@ViewBuilder
private func Root(
items: [Item],
onItemTap: @escaping (Item) -> Void
) -> some View {
List(items) {
ListItem(
item: $0,
onItemTap: onItemTap
)
}
}
@ViewBuilder
private func ListItem(
item: Item,
onItemTap: @escaping (Item) -> Void
) -> some View {
Text("\(item.title)")
.onTapGesture {
onItemTap(item)
}
}
import Compose
class ListViewController: ComposableController {
init(viewModel: ListViewModel) {
super.init(ListView(viewModel))
}
}
For preview, instantiate controller and call rootView
in preview provider.
#if DEBUG
struct Main_Previews: PreviewProvider {
static var previews: some View {
MainViewController().rootView
}
}
#endif
Any ideas, issues, opinions are welcome.
Compose is available under the MIT license. See the LICENSE file for more info.