When it comes to organizing the UI code, best practices mandate to have 3 parts:
- view (only visual structure, styling, animations)
- model (your data and business logic)
- a secret sauce that connects view and model
In UIKit we use MVP approach where a UIViewController subclass typically represents the secret sauce part.
In SwiftUI it is easier to use the MVVM approach due to the provided databinding facitilies. In MVVM the "ViewModel" is the secret sauce. It is a custom struct that holds the model data ready for your view to present, triggers view updates when the model data is updated, and forwards UI actions to do something with your model.
For example a form that edits a name could look like so:
struct MyForm: View {
let viewModel: MyFormViewModel
var body: some View {
Form {
TextField("Name", text: $viewModel.name)
Button("Submit", action: { self.viewModel.submit() })
}
}
}
class MyFormViewModel {
var name: String // implement a custom setter if needed
init(name: String) { this.name = name }
func submit() {
print("submitting: \(name)")
}
}
Having this, it is easy to forward the UI action to UIKit controller. One standard way is to use a delegate protocol:
protocol MyFormViewModelDelegate: class {
func didSubmit(viewModel: MyFormViewModel)
}
class MyFormViewModel {
weak var delegate: MyFormViewModelDelegate?
func submit() {
self.delegate?.didSubmit(viewModel: self)
}
...
Finally, your UIViewController can implement MyFormViewModelDelegate, create a MyFormViewModel instance, and subscribe to it by setting self
as a delegate
), and then pass the MyFormViewModel object to the MyForm view.
Improvements and other tips:
- If this is too old-school for you, you can use Combine instead of the delegate to subscribe/publish a
didSubmit
event.
- In this simple example the model is just a String. Feel free to use your custom model data type.
- There's no guarantee that MyFormViewModel object stays alive when the view is destroyed, so probably it is wise to keep a strong reference somewhere if you want it survive for longer.
$viewModel.name
syntax is a magic that creates a Binding<String>
instance referring to the mutable name
property of the MyFormViewModel.