SwiftUI app architecture we use.
MVVM-F = MVVM architecture + Factory;
SOLID principles are the core of our architecture.
1. For assembling project we use DI pattern with Swinject library.
App has shared Container where we register all shared dependencies we need to use across the whole app.
2. Every View Module for example has the following structure:
Abstraction for creation View. Factory is very important part of this architecture, because it injects dependencies. BaseFactory contains Container we use to inject dependencies that we need. We can inject factories in other views via DI and it gives us a lot of flexibility. We use type erased AnyView for more flexibility.
protocol <Name>ViewFactory {
func make()-> AnyView
}
It contains the logic of the presentation. It has the bindings for input and output. The biggest problem here is that ObservableObject has Self requirement and can't be used as an Abstraction like a protocol and even as an abstract class. If you try to use ConcreteViewModel as a child of a base abstract ViewModel class with properties and abstract methods, then @Published properties will not update the view. And this is a huge fail for flexibility because we can't use viewModel as an abstraction.
// for example
class <Name>ViewModel : ObservableObject {
private let reachabillityManager: ReachabilityManager
init(reachabillityManager: ReachabilityManager) {
self.reachabillityManager = reachabillityManager
super.init()
self.computePublishers()
}
@Published var input: String = ""
@Published var isButtonEnabled = false
func computePublishers() {
self.reachabillityManager.reachable.combineLatest($input).map { (reachable, input) -> Bool in
if !reachable{
return false
}
return input.count >= 5
}.receive(on: RunLoop.main)
.assign(to: \MainViewModel.isButtonEnabled, on: self)
.store(in: &cancellableBag)
}
}
Just simple SwiftUI view.
It contains viewModel and child view factories.
struct <Name>View: View {
@ObservedObject var viewModel: <Name>ViewModel
let otherViewFactory: <Other>ViewFactory
@State private var isActiveDetail = false
var body: some View {
NavigationView {
VStack {
TextField("Input", text: $viewModel.input).frame(height: 50.0)
NavigationLink(destination: otherViewFactory.make(value: viewModel.input), isActive: $isActiveDetail, label: {Text("GO!")}).disabled(!viewModel.isButtonEnabled)
}.frame(width: 200.0).navigationBarTitle("Main")
}
}
}