Implementing MVP using protocols - swift

I'm trying to implement MVP using protocols,
I have View controller that holds a reference to a presenter protocol.
The presenter protocol contains a reference to the view
and has associatedtype that represent the ResultType.
The ResultType is different at each presenter.
for example:
class PresenterA: PresenterProtocol {
weak var view: ViewController!
typealias ResultType = String
var onDidPressCallback: ((ResultType) -> Void)?
}
It also can be
class PresenterB: PresenterProtocol {
weak var view: ViewController!
typealias ResultType = Apple
var onDidPressCallback: ((ResultType) -> Void)?
}
The problem start's when Im holding a reference to the presenter
from the ViewController.
class ViewController: UIViewController {
var presenter: PresenterProtocol!
}
Of course It is not possible and I get this error:
Protocol 'PresenterProtocol' can only be used as a generic constraint
because it has Self or associated type requirements
So I tried:
class ViewController<T: PresenterProtocol>: UIViewController {
var presenter: T!
}
But now the PresenterProtocol has this issue:
Reference to generic type 'ViewController' requires arguments in <...>
What am I doing wrong? And how can I solve it?
Plus, let's say I can not support new versions so I cannot use opaque type (some keyword).

In answer to your question, when you make the view controller generic, the error says that the “generic type 'ViewController' requires arguments in <...>”. So you can do precisely that:
class PresenterA: PresenterProtocol {
weak var view: ViewController<PresenterA>!
...
}
You will probably end up wanting to make a required initializer so you can instantiate these presenters, e.g.:
protocol PresenterProtocol {
associatedtype ResultType
var onDidPressCallback: ((ResultType) -> Void)? { get set }
init()
}
class PresenterA: PresenterProtocol {
weak var view: ViewController<PresenterA>!
var onDidPressCallback: ((String) -> Void)?
required init() { ... }
}
class PresenterB: PresenterProtocol {
weak var view: ViewController<PresenterB>!
var onDidPressCallback: ((Apple) -> Void)?
required init() { ... }
}
class ViewController<Presenter: PresenterProtocol>: UIViewController {
var presenter = Presenter()
override func viewDidLoad() {
super.viewDidLoad()
presenter.onDidPressCallback = { value in
// show the value in the UI
}
...
}
}
Hopefully, that answers the question, but there are a few issues here:
Presenters should be independent of UIKit. You do not want to have a reference to your view controller (or have any UIKit dependencies) in the presenter. Only the “view” (views, view controllers, etc.) should have UIKit dependencies. This separation of responsibilities is a core concept of MVP.
There are a number of options to allow the presenter to inform the view of events (such as delegate protocols, async-await, etc.). But you have a closure variable, and that is a perfectly adequate way to proceed. The presenter does not need a reference to the view controller, but rather should simply call the appropriate closure. The view controller, obviously, will just set those closure variables so it can respond to the presenter events.
But, as I said, there are a variety of different approaches to inform the “view” of events triggered by the presenter. But the presenter should not be reaching into the view controller, itself. No reference to the view controller is required or desired.
By making the view controller a generic, it can no longer be instantiated via standard UIKit mechanisms (storyboards, NIBs, etc.). Unless you really want to lose the benefits of IB outlets, actions, etc., and do everything programmatically, you probably do not want to make it generic. You introduce significant development/maintenance costs for the sake of generics.
Besides, a view controller for Apple and a view controller for String will probably have unique controls. Making it a generic might feel intuitively appealing at first, but in practice, it falls apart over time. The few times I went down this road, I ended up regretting it. As the UI evolves, you end up refining/specializing the UI for the individual types over time. The few times I tried permutations of this pattern, I found myself ripping it out later. It feels so intuitive, but it often becomes a hindrance later.
Fortunately, when you remove the reference to the view controller from the presenter and do not make the view controller generic, much of this noise disappears:
protocol PresenterProtocol {
associatedtype ResultType
var onDidPressCallback: ((ResultType) -> Void)? { get set }
}
class PresenterA: PresenterProtocol {
var onDidPressCallback: ((String) -> Void)?
// ...
}
class PresenterB: PresenterProtocol {
var onDidPressCallback: ((Apple) -> Void)?
// ...
}
class ViewControllerA: UIViewController {
var presenter = PresenterA()
override func viewDidLoad() {
super.viewDidLoad()
presenter.onDidPressCallback = { value in
// show the data in the UI
}
// ...
}
}
At this point, the protocol becomes more of a contract to ensure that all the presenters follow certain conventions for the shared functionality. But we no longer tie ourselves down, limiting our presenters to least-common-denominator functionality.

Related

How can my Protocol oriented to specify ViewModel?

I have a problem from Protocol oriented ViewModels.
I have two very similar pages,so i made a baseController and baseViewModel to put shared properties and methods. And made two other controllers and viewModels for two pages.
And I made a protocol to define the properties and methods.
My baseController has var viewModel: BaseViewModelProtocol .
But my other two controller cannot use the properties and methods from their viewModel, it's says
Value of type 'BaseViewModelProtocol?' has no member ''
ViewModel1 is for Controller1, ViewModel2 is for Controller2, here is my example
protocol BaseViewModelProtocol {
var name: String { get }
func reset()
}
class BaseViewModel: BaseViewModelProtocol {
func reset() { }
}
class ViewModel1: BaseViewModel {
var score: Int = 0
func someMethods() {}
}
class ViewModel2: BaseViewModel {
var money: Int = 1000
func something() {
print("something")
}
}
class BaseViewController: UIViewController {
var viewModel: BaseViewModelProtocol?
init(viewModel: BaseViewModelProtocol) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
}
class ViewController1: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
print(viewModel?.score) //it is error!!!!
}
}
When i init the Controller, i Cannot use any properties and methods from ViewModel2, and the same like controller1.
class ViewController2: BaseOrderViewController {
override func viewDidLoad() {
super.viewDidLoad()
viewModel?.something(). //it is error!!!!
}
}
how to my protocol oriented viewModel?
After reading your post I feel like there is too many things that should be addressed here.
You are trying to ask something before understanding important characteristics of the Object Oriented Programmation paradigm. I recommend you to try and search information about class abstraction, composition vs inheritance and specificaly how to use the protocols in Swift.
Maybe you could start with https://cocoacasts.com/how-to-create-an-abstract-class-in-swift.
Even so, i am going to try to point out some issues in your code.
Seems like you tried to "hide" your source code by changing your classes and properties' names. The way it's done makes it harder to read, understand and introduces mistakes.
Inheriting from another class:
class ViewModel1: BaseViewModel
it's not the same as inheriting from a protocol:
class BaseViewModel: BaseViewModelProtocol
while the first one provides you a default implementation of a method that can be overrided, the second, doesn't. So it is mandatory to provide the implementation of the method in every class that inherits the protocol.
Note that the BaseViewController has a property with type BaseViewModelProtocol. That protocol doesn't have a method called "something" neither can inherit that function from another. It's within reason that the compiler shows the error.
If you want to use "something" from a Class that inherits BaseViewController, you have many ways. You could change the type of the viewModel in the BaseViewController to BaseViewModel after adding there an implementation of "something". You could also add the function to the protocol and make sure that it's implemented in all of the classes which inherit the protocol...
I hope I have helped you.

How do you share a data model between a UIKit view controller and a SwiftUI view that it presents?

My data model property is declared in my table view controller, and the SwiftUI view is modally presented. I'd like the presented Form input to manipulate the data model. The resources I've found on data flow are just between SwiftUI views, and the resources I've found on UIKit integration are on embedding UIKit in SwiftUI rather than the other way around.
Furthermore, is there a good approach for a value type (in my case struct) data model, or would it be worth remodeling it as a class so that it's a reference type?
Let's analyse...
My data model property is declared in my table view controller and the SwiftUI view is modally presented.
So here is what you have now (probably simplified)
struct DataModel {
var value: String
}
class ViewController: UIViewController {
var dataModel: DataModel
// ... some other code
func showForm() {
let formView = FormView()
let controller = UIHostingController(rootView: formView)
self.present(controller, animating: true)
}
}
I'd like the presented Form input to manipulate the data model.
And here an update above with simple demo of passing value type data into SwiftUI view and get it back updated/modified/processed without any required refactoring of UIKit part.
The idea is simple - you pass current model into SwiftUI by value and return it back in completion callback updated and apply to local property (so if any observers are set they all work as expected)
Tested with Xcode 12 / iOS 14.
class ViewController: UIViewController {
var dataModel: DataModel
// ... some other code
func showForm() {
let formView = FormView(data: self.dataModel) { [weak self] newData in
self?.dismiss(animated: true) {
self?.dataModel = newData
}
}
let controller = UIHostingController(rootView: formView)
self.present(controller, animated: true)
}
}
struct FormView: View {
#State private var data: DataModel
private var completion: (DataModel) -> Void
init(data: DataModel, completion: #escaping (DataModel) -> Void) {
self._data = State(initialValue: data)
self.completion = completion
}
var body: some View {
Form {
TextField("", text: $data.value)
Button("Done") {
completion(data)
}
}
}
}
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.

Using protocol's associated type in generic functions

I'm trying to write a simple MVP pattern to follow in my app, so I've written two porotocols to define View Controller and Presenters:
protocol PresenterType: class {
associatedtype ViewController: ViewControllerType
var viewController: ViewController? { get set }
func bind(viewController: ViewController?)
}
protocol ViewControllerType: class {
associatedtype Presenter: PresenterType
var presenter: Presenter { get }
init(presenter: Presenter)
}
After having those defined I started writing some RootViewController and RootViewPresenter. The latter looks like:
protocol RootViewControllerType: ViewControllerType {
}
final class RootPresenter<VC: RootViewControllerType>: PresenterType {
weak var viewController: VC?
func bind(viewController: VC?) {
self.viewController = viewController
}
}
Up to this point everything complies and is fine, but when I start implementing View Controller like this:
protocol RootPresenterType: PresenterType {
}
final class RootViewController<P: RootPresenterType>: UIViewController, ViewControllerType {
let presenter: P
init(presenter: Presenter) {
self.presenter = presenter
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
presenter.bind(viewController: self)
}
}
Immediately I get the following error message:
Cannot convert value of type 'RootViewController' to expected argument type '_?'
I know that protocols with associated types can introduce some limitations, but this example is pretty straightforward and I can't make it work. Is it possible to achieve something that I want, or do I have to look for some other, less Swifty pattern?
I don't think what you're trying to achieve is possible due to the circular dependency between the respective associated types of the PresenterType and ViewControllerType protocols.
Consider for a moment if the suspect code above did compile ... how would you go about instantiating either the RootPresenter or RootViewController classes? Because both depend on one another, you'll end up with errors like the following:
As you can see, the compiler can't fully resolve the generic parameters due to the associated types.
I think your best bet is to remove the associated type from one or both of the protocols. For example, removing the associated type from the PresenterType protocol and updating the RootPresenter class breaks the circular dependency and allows your code to compile normally.
protocol PresenterType: class {
var viewController: UIViewController? { get set }
func bind(viewController: UIViewController?)
}
final class RootPresenter: PresenterType {
weak var viewController: UIViewController?
func bind(viewController: UIViewController?) {
self.viewController = viewController
}
}

Delegation on a class in Swift

I have a delegation/initialization problem I can't seem to solve. Basically I have a storyboard with a few View controllers. Inside the storyboard there is this "View controller" which consists of a UITableview that I have connected with a DeviceListViewController class so that it populates the information. In here I have declared the following protocol:
protocol DeviceListViewControllerDelegate: UIAlertViewDelegate {
var connectionMode:ConnectionMode { get }
func connectPeripheral(peripheral:CBPeripheral, mode:ConnectionMode)
func stopScan()
func startScan()
}
and inside the class itself I have a init method like this (which is probably wrong but I didn't know what else I could do at this point):
convenience init(aDelegate: DeviceListViewControllerDelegate) {
self.init()
self.delegate = aDelegate
}
Then there is this second class that is not attached to any view controller called BLEMainViewController. It should be a singleton handling all the bluetooth actions. This means I should be able to delegate some stuff between DevicelistViewController and BLEMainViewController.
In the BLEMainViewController I have inherited the DeviceListViewControllerDelegate:
class BLEMainViewController: NSObject, DeviceListViewControllerDelegate {
var deviceListViewController:DeviceListViewController!
var delegate: BLEMainViewControllerDelegate?
static let sharedInstance = BLEMainViewController()
}
override init() {
super.init()
// deviceListViewController.delegate = self
deviceListViewController = DeviceListViewController(aDelegate: self)
}
The problem is that BLEMainViewController is not attached to any View Controller (and it shouldn't IMO) but it needs to be initialized as a singleton in order to handle all the BLE actions. Can anyone point me in the right direction (with an example preferably) on how to work around this?
I think you simply used wrong code architecture.
The BLEManager is a shared-instance, you can call it from everywhere, set it properties, and call its methods.
Its can delegate your view-controller with any predefine events you will add to its protocol and provide proper implementation
Here is some code, hope it helps
protocol BLEManagerDelegate{
func bleManagerDidStartScan(manager : BLEManager)
}
class BLEManager: NSObject {
static let sharedInstance = BLEManager()
var delegate: BLEManagerDelegate?
var devices : [AnyObject] = []
func startScan(){
delegate?.bleManagerDidStartScan(self)
//do what ever
}
func stopScan(){
}
}

Swift - Cast [AnyObject]! to [Protocol]! directly

I am trying to overcome the limitation explained here about not being able to directly connect IB elements to outlets with protocol types.
The workaround mentioned is not ideal because what I need an Outlet Collection and connecting a new element means redoing this again and again and that defeats the purpose of this part of my project.
So I figured I would create an intermediate Controller whose sole purpose was to readily translate a an AnyObject outlet collection to the array typed with my Protocol.
I come to discover that the array casting you see below throws the error: "Type 'FormField' does not conform to protocol 'AnyObject'"
However, the simple per-item loop commented out actually works.
I would like to understand why the former fails and if there is a way to actually avoid the per-item loop.
class FormViewController: UIViewController, Form {
#IBOutlet var fieldCollection: [AnyObject]!
var formFields: [FormField]!
override func viewDidLoad() {
super.viewDidLoad()
self.formFields = self.fieldCollection as! [FormField]!
/*
self.formFields = [FormField]()
for field in self.fieldCollection {
self.formFields.append(field as! FormField)
}*/
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You didn't show how the FormField protocol is defined but I'm guessing it doesn't have the AnyObject class as a constraint.
for example: protocol FormField:class, AnyObject {}
If it did, the type cast would work.
you could then declare it as:
class FormViewController: UIViewController, Form {
#IBOutlet var fieldCollection: [AnyObject]!
var formFields: [FormField] { return fieldCollection as! [FormField] }
and I believe you could event do this (in Swift 2.1):
#IBOutlet var fieldCollection: [FormField]!