Usually, I write this function to init my presenter from viewController. But I want to use init() for making this. What can I do?
ViewController :
class ViewController: UIViewController {
private let viewPresenter = viewPresenter()
override func viewDidLoad() {
self.viewPresenter.attachView(controller: self)
}
#IBAction func faceBookButtonTapped(sender: UIButton) {
self.viewPresenter.showLoginWindow()
}
}
ViewPresenter :
class ViewPresenter {
var controller: ViewController?
func attachView(controller: ViewController) {
self.controller = controller
}
}
So, If I make this Init in my presenter :
let controller: ViewController?
init (controller:ViewController) {
self.controller = controller
}
And try to init like this in viewController:
private let viewPresenter = ViewPresenter(controller: self)
Xcode give me this error:
Cannot convert value of type '(NSObject) -> () -> ViewController' to expected argument type 'ViewController'
the problem is that there is no "self" at that point in time where you want to initialize your presenter because your view controller is initialized later. You could work around this problem by declaring your presenter as lazy like this
fileprivate lazy var viewPresenter: ViewPresenter = {
return ViewPresenter(controller: self)
}()
or you could just initialize your presenter later in viewDidLoad
private var viewPresenter: ViewPresenter!
override func viewDidLoad() {
super.viewDidLoad()
self.viewPresenter = ViewPresenter(controller: self)
}
If you ask me the best approach is (as you already did) to attach the view in viewDidLoad to the presenter.
Hope this helps.
Related
I have a viewController with another containerView insider set up to appear temporarily (added programmatically). The containerView is a sort of operation bar, which allows you to change values of the viewController. The protocol called from an IBAction of a button however, does not call the protocol set up inside the viewController class.
Here is the code from both classes:
class viewController: UIViewController, updateListDelegate {
let dataSource = containerView()
override func viewDidLoad() {
super.viewDidLoad()
dataSource.delegate = self
}
func updateList(sender: containerView) {
print("is called") //is not printed
}
}
The code from the containerView:
protocol updateListDelegate {
func updateList(containerView)
}
class containerView: UIViewController {
var delegate: updateListDelegate?
#IBAction func AddSong(_ sender: UIButton) {
self.delegate?.updateList(sender: self)
}
}
If this method is only to be called from one object, then, in my opinion, I would not define a protocol. If multiple objects are to call this method, then I would define a protocol. This is typically how you would call a method backwards, using a basic delegate.
class ViewController: UIViewController {
let container = ContainerView()
override func viewDidLoad() {
super.viewDidLoad()
container.viewControllerDelegate = self
// push to this instance of container at some point
}
func doSomething() {
print("great success")
}
}
class ContainerView: UIViewController {
weak var viewControllerDelegate: ViewController?
#objc func someAction() {
if let viewControllerDelegate = viewControllerDelegate {
viewControllerDelegate.doSomething()
}
}
}
// prints "great success" when someAction() called
One of the most common mistakes people make is not keeping track of instances. For delegates to work, you must be sure you are using the specific instances that you've instantiated and assigned those delegates to.
I want to remove repetitive code so I would like to create a simple MVP base view controller that will tie together a model, view and presenter types and automatically connect them e.g.:
class BaseMvpViewController<M: MvpModel, V: MvpView, P: MvpPresenter>: UIViewController {
Where my model and view are empty protocols:
protocol MvpModel {}
protocol MvpView: class {} // class is needed for weak property
and presenter looks like this:
protocol MvpPresenter {
associatedtype View: MvpView
weak var view: View? { get set }
func onAttach(view: View)
func onDetach(view: View)
}
This is my whole BaseMvpViewController:
class BaseMvpViewController<M: MvpModel, V, P: MvpPresenter>: UIViewController, MvpView {
typealias View = V
var model: M? = nil
var presenter: P!
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
deinit {
presenter.onDetach(view: self as! View)
}
override func viewDidLoad() {
createPresenter()
super.viewDidLoad()
presenter.onAttach(view: self as! View)
}
func createPresenter() {
guard presenter != nil else {
preconditionFailure("Presenter was not created or it was not assigned into the `presenter` property!")
}
}
}
The problem is that the V must be without the protocol i.e. cannot be V: MvpView. Otherwise specific implementation of a VC must have a class/struct and not just a protocol for the MvpView. All my views are just protocols and my VCs will implement them e.g.
class MyViewController: BaseMvpViewController<MyModel, MyView, MyPresenter>, MyView
Now the compiler complains in the onAttach() and onDetach() methods that "argument type 'V' does not conform to expected type 'MvpView'"
So I tried an extension:
extension BaseMvpViewController where V: MvpView {
override func viewDidLoad() {
presenter.onAttach(view: self as! View)
}
}
yet another compiler error: "cannot invoke 'onAttach' with an argument list of type '(view: V)'". There is another small compilation error "Members of constrained extensions cannot be declared #objc" where I override func viewDidLoad() in the extension. This can be fixed by my own method and calling that one from viewDidLoad in the custom class. Any idea how to achieve what I want?
This is a similar/same issue like Using some protocol as a concrete type conforming to another protocol is not supported but maybe something has been improved in the Swift world since then. Or did I really hit a hard limit in the current Swift's capabilities?
In have finally found a solution, the problem was in casting self as! View, it must be self as! P.View. And there cannot be a base protocol for view because protocols do not conform to themselves in Swift. Here is my complete code:
protocol MvpPresenter {
associatedtype View
var view: View? { get set }
var isAttached: Bool { get }
func onAttach(view: View)
func onDetach(view: View)
}
/// Default implementation for the `isAttached()` method just checks if the `view` is non nil.
extension MvpPresenter {
var isAttached: Bool { return view != nil }
}
class BaseMvpViewController<M, V, P: MvpPresenter>: UIViewController {
typealias View = V
var viewModel: M? = nil
private(set) var presenter: P!
//MARK: - Initializers
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override public init(nibName: String?, bundle: Bundle?) {
super.init(nibName: nibName, bundle: bundle)
}
deinit {
presenter.onDetach(view: self as! P.View)
}
//MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
presenter = createPresenter()
}
override func viewWillAppear(_ animated: Bool) {
guard let view = self as? P.View else {
preconditionFailure("MVP ViewController must implement the view protocol `\(View.self)`!")
}
super.viewWillAppear(animated)
if (!presenter.isAttached) {
presenter.onAttach(view: view)
}
}
//MARK: - MVP
/// Override and return a presenter in a subclass.
func createPresenter() -> P {
preconditionFailure("MVP method `createPresenter()` must be override in a subclass and do not call `super.createPresenter()`!")
}
}
And a sample VC:
class MyGenericViewController: BaseMvpViewController<MyModel, MyView, MyPresenter>, MyView {
...
override func createPresenter() -> MainPresenter {
return MyPresenter()
}
...
}
This VC will automatically have a viewModel property of type MyModel (could be anything e.g. struct, class, enum, etc), property presenter of type MyPresenter and this presenter will be automatically attached between viewDidLoad and viewWillAppear. One method must be overridden, the createPresenter() where you must create and return a presenter. This is called before the custom VC's viewDidLoad method. Presenter is detached in the deinit.
The last problem is that generic view controllers cannot be used in interface builder (IB), because IB talks to code via Objective-C runtime and that does not know true generics, thus does not see our generic VC. The app crashes when instantiating a generic VC from a storyboard/xib. There is a trick though that fixes this. Just load the generic VC manually into the Objective-C runtime before any instantiation from storyboard/xib. Good is in AppDelegate's init method:
init() {
...
MyGenericViewController.load()
...
}
EDIT 1:
I have found the loading of generic VC into Objective-C runtime in this SO answer https://stackoverflow.com/a/43896830/671580
EDIT 2:
Sample presenter class. The mandatory things is the typealias, the weak var view: View? and the onAttach & onDetach methods. Minimum implementation of the attach/detach methods is also provided.
class SamplePresenter: MvpPresenter {
// These two are needed!
typealias View = SampleView
weak var view: View?
private let object: SomeObject
private let dao: SomeDao
//MARK: - Initializers
/// Sample init method which accepts some parameters.
init(someObject id: String, someDao dao: SomeDao) {
guard let object = dao.getObject(id: id) else {
preconditionFailure("Object does not exist!")
}
self.object = object
self.dao = dao
}
//MARK: - MVP. Both the onAttach and onDetach must assign the self.view property!
func onAttach(view: View) {
self.view = view
}
func onDetach(view: View) {
self.view = nil
}
//MARK: - Public interface
/// Sample public method that can be called from the view (e.g. a ViewController)
/// that will load some data and tell the view to display them.
func loadData() {
guard let view = view else {
return
}
let items = dao.getItem(forObject: object)
view.showItems(items)
}
//MARK: - Private
}
How to pass UIViewController to another class in Swift?
I have SomeViewController class:
class RidingViewController: UIViewController {
fileprivate let header = RidingViewHeader(controller: self)
override func viewDidLoad() {
super.viewDidLoad()
header.setNavigationBar()
}
...
}
And I want to separate some code and to set up its header in the another class. I've created this class
class RidingViewHeader {
var controller: UIViewController
let navigationBar= UINavigationBar()
let navigationItem = UINavigationItem()
init(controller: UIViewController) {
self.controller = controller
}
...
}
In this case I get an error:
Cannot convert value of type '(NSObject) -> () -> UIViewController' to expected argument type 'UIViewController'
What is the better way of doing it?
You can't access self until the view controller has been initialized. You could make it a lazy variable:
fileprivate lazy var header: RidingViewHeader = {
return RidingViewHeader(controller: self)
}()
I have two classes. One class is named ViewController and the other class is named TabView.
My goal is to call a function changeTab() which is inside the TabView class from the ViewController.
Somehow I am having trouble with it because everytime my delegate is nil.
Here is my code for ViewController:
protocol TabViewProtocol: class {
func changeTab()
}
class ViewController: NSViewController {
// delegate
weak var delegateCustom : TabViewProtocol?
override func viewDidLoad() {
print(delegateCustom) // outputs "nil"
}
buttonClickFunction() {
print(delegateCustom) // outputs "nil"
delegateCustom?.changeTab() // doesn't work
}
}
Here is my code for TabView:
class TabView: NSTabViewController, TabViewProtocol {
let myVC = ViewController()
override func viewDidLoad() {
super.viewDidLoad()
myVC.delegateCustom = self
}
func changeTab() {
print("test succeed")
}
}
Can someone explain me what I am doing wrong? - I am new to delegates and protocols...
You are using the delegate pattern wrongly. It is hard to tell which controller you want to define the protocol for and which one you want to adopt it - but here is one possible way.
// 1. Define your protocol in the same class file as delegate property.
protocol TabViewProtocol: class {
func changeTab()
}
// 2. Define your delegate property
class ViewController: NSViewController {
// delegate
weak var delegateCustom : TabViewProtocol?
override func viewDidLoad() {
// It should be nil as you have not set the delegate yet.
print(delegateCustom) // outputs "nil"
}
func buttonClickFunction() {
print(delegateCustom) // outputs "nil"
delegateCustom?.changeTab() // doesn't work
}
}
// 3. In the class that will use the protocol add it to the class definition statement
class TabView: NSTabViewController, TabViewProtocol {
let myVC = ViewController()
override func viewDidLoad() {
super.viewDidLoad()
myVC.delegateCustom = self
// Should output a value now
print(myVC.delegateCustom) // outputs "self"
}
func changeTab() {
print("test succeed")
}
}
you are creating a new instance in this line:
let myVC = ViewController()
you should get existing instance of your ViewController.then set
myVC.delegateCustom = self
I present my secondViewController from (attendanceViewController) and in dismiss completion I'm trying to pass parameters and call functions. The AttendanceViewController appears and the function is called. The problem is that all the Objects are nil when dismiss(#IBOutlet weak var tableView: UITableView! , #IBOutlet weak var boxTypeSKU: UIView!....all)
self.presentingViewController!.dismissViewControllerAnimated(true, completion: { _ i
let attView: AttendanceViewController = self.storyboard!.instantiateViewControllerWithIdentifier("AttendanceViewID") as! AttendanceViewController
attView.currAttendance = self.currAttendance
attView.searchProductWithSKU("\(sku)")
})
I solved my problem using Protocols like this tutorial (http://swiftdeveloperblog.com/pass-information-back-to-the-previous-view-controller/) I think it's more elegant and efficient.
There's my updated code:
In second view Controller (BarcodeScannerViewController.swift) I do it:
protocol BarcodeScannerProtocol {
func setSKUScanner(sku: String)
}
class BarcodeScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
var delegate:BarcodeScannerProtocol?
func back() {
let sku = (barcode as NSString).substringWithRange(NSMakeRange(6, 8))
delegate?.setSKUScanner(sku)
self.presentingViewController!.dismissViewControllerAnimated(true, completion: { _ in
}
}
In first view controller (AttendanceViewController.swift):
class AttendanceViewController: UIViewController, BarcodeScannerProtocol {
var strSKUScanner : String?
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if let skuScanned = strSKUScanner {
searchProductWithSKU(skuScanned)
} else {
fetchProducts()
}
}
// MARK: BarcodeScannerProtocol functions
func setSKUScanner(sku: String) {
self.strSKUScanner = sku
}
}
The first thing to be noticed is that a new instance of AttendanceViewController is being instantiated. This means that the properties are not being set on the correct object. There needs to be a reference to the view controller that presented the secondViewController. How that is done is up to you, but I recommend a callback containing the currAttendance variable. This would be a property on the presented view controller. Once the callback is called by the presented view controller, the parent AttendanceViewController can set its own property and dismiss the presented view controller and call the searchProductWithSKU(_:) method.