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@Stateor@Bindingproperty wrapper inComposableController. All states are managed by@PublishedofObservableObejct.
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
}
}
#endifAny ideas, issues, opinions are welcome.
Compose is available under the MIT license. See the LICENSE file for more info.
