Cannot assign self as delegate in property initializer - ios

I am getting a strange error message when I try to access self when initializing a property on my class. It seems like my class has a weird type.
This is the error:
Cannot assign a value of type 'ViewController -> () ViewController' to a value of type 'ModelControllerDelegate?'
This is the code, simple example in a playground:
import Foundation
import UIKit
protocol ModelControllerDelegate
{
func modelControllerDidFinishTask()
}
class ModelController
{
var delegate : ModelControllerDelegate?
}
class ViewController : UIViewController, ModelControllerDelegate
{
var modelController : ModelController =
{
let controller = ModelController()
controller.delegate = self
return controller
}()
func modelControllerDidFinishTask()
{
}
}

You are trying to use self when not fully initialised. Might work if you use a lazy initialisation:
lazy var modelController : ModelController = { ...

When initialising a property with a closure like this the closure is run before the instance is fully initialised. This means there are a few things you can't do here:
Access other property values
Use self
Call any instance methods
To get around this you could either initialise modelController entirely in the ViewController's init, or at least defer the setting of its delegate until then.

Related

Create a member for the UIViewController class

I tried to do some research but couldn't figure it out, so is it possible to create a member for the class UIViewController, or any class for that matter?
In every single one of my UIViewController subclasses I declare the data member
userdata = [NSManagedObject]()
So I was wondering if I could declare the variable "userdata" inside the actual UIViewController class, either directly or through an external file.
You can simply create a sub-class of UIViewController which has the userdata property and then derive all of your view controllers from that class instead of UIViewController
Something like:
class BaseViewController:UIViewContrller {
var userdata = [NSManagedObject]()
}
class NewViewController:BaseViewController {
// Your sub view controller implementation goes here
}
You should use extensions.
extension UIViewController {
var userData : [NSManagedObject] {
get {
return [NSManagedObject]()
}
}
}
If you don't want every UIViewController to have that property, you will have to use subclassing.
class DataViewController:UIViewContrller {
var userdata = [NSManagedObject]()
}
class NewViewController:DataViewController {
// Do something stuff to the View here
}
You can use extensions if userData is a computed property:
extension UIViewController {
var userData: [NSManagedObject] {
get { return an array from somewhere else }
set { set the value to somewhere else }
}
}
If your property is not computed but stored, you must use a subclass:
class BaseViewController: UIViewController {
var userData: [NSManagedObject] = []
}
And make every VC of yours inherit this class. The disadvantage of using this approach is that your view controllers can't inherit any other class, like UITableViewController.
So here is the best method I came up with.
Create a protocol:
protocol MyVC {
var userData: [NSManagedObject] { get set }
}
Now make every VC of yours conform to this protocol. In every VC, just start typing userData and use enter to select the right completion that Xcode provides and the property will be automatically added for you. If you forgot to do this, the compilation will fail.

Create an array of objects that implements a specific protocol

TL;DR
I'm looking for an array type (var array = [TheTypeImLookingFor]()) like 'all objects that subclasses UIViewController and implements the protocol MyProtocol.
Explanation
I'm building a kind of wizard view with a container view and embedded child views (controller). No problem, this will work as long, as I have only one base type of child view controllers.
Due to the content of screens, I have now a bunch of view controllers of type MyTableViewController which is a subclass of UITableViewController and other view controllers that have regular UIViewControllers as base.
All of the view controllers have one thing in common. A default data property myData: MyObject.
I created a protocol MyProtocol that contains this property.
Now, I have to combine all this view controllers into one array to use it as wizard steps. As long as I only have to access the view controller methods (array items are type of UIViewController) I'm able to use var viewControllers = [UIViewController]() or if I wanna only access the myData property, I change the array item type to MyObject.
But the problem is, I have to access the methods from the UIViewController and from the protocol.
That's why I'm looking for an array type like 'all objects that subclasses UIViewController and implements the protocol MyProtocol.
I tried:
var viewControllers = [UIViewController: MyProtocol]() // is a dict
`var viewControllers = UIViewController where MyProtocol
`var viewControllers = UIViewController.conforms(to: MyProtocol)
...
But nothing works as expected.
As far as I know, there's currently no way to type something so that it describes anything which inherits from a given class and conforms to a given protocol.
One possible hacky workaround would be to just create a wrapper type in order to perform typecasting for you in the case that you need to treat the instance as a MyProtocol.
struct MyProtocolViewController {
let base: UIViewController
init<T : UIViewController>(_ base: T) where T : MyProtocol {
self.base = base
}
func asMyProtocol() -> MyProtocol {
return base as! MyProtocol
}
}
Now you can create a [MyProtocolViewController], and can either treat an element as a UIViewController, or a MyProtocol.
// given that ViewController and AnotherViewController conform to MyProtocol.
let viewControllers = [MyProtocolViewController(ViewController()),
MyProtocolViewController(AnotherViewController())]
for viewController in viewControllers {
print(viewController.asMyProtocol().myData)
print(viewController.base.prefersStatusBarHidden)
}
You could use protocol composition with a placeholder protocol for the class:
protocol UIViewControllerClass {}
extension UIViewController: UIViewControllerClass {}
protocol MyProtocol:class {}
class MySpecialVC:UIViewController,MyProtocol {}
var viewControllers = [UIViewControllerClass & MyProtocol]()
viewControllers.append( MySpecialVC() )
This covers the type safety part but doesn't let you access UIViewController methods without type casting. You can reduce the type casting ugliness by adding a typed property to your protocol (when it applies to the base class)
extension MyProtocol where Self: UIViewControllerClass
{
var vc:UIViewController { return self as! UIViewController }
}
// accessing the view controller's methods would then only require insertion of a property name.
viewControllers.first!.vc.view
Alternatively, you could define the UIViewController methods you need to call in the placeholder protocol but that could quickly become tiresome and redundant if you're going to use many of them.
Why not simply create :
Why not creating :
class ObservingViewController : UIViewController, MyProtocol {
}
var viewControllers : [ObservingViewController] = []
You can also create a protocol that defines all the UIViewController functions that you need. Make sure that you copy the method signature, otherwise you will have to implement the functions again.
protocol UIViewControllerInteractions {
//copy the signature from the methods you want to interact with here, e.g.
var title: String? { get set }
}
Then, you can extend your existing protocol.
protocol MyProtocol: UIViewControllerInteractions { }
Or create a new protocol that extends UIViewControllerInteractions and MyProtocol.
protocol MyProtocolViewController: UIViewControllerInteractions, MyProtocol { }
Now, when you extend your SubclassUIViewController, you still only have to add your myData because the methods in the UIViewControllerInteractions are already implemented by UIViewController (that's why we copied the method signature)
class SubclassUIViewController: MyProtocol {
var myData ...
}
You can now have an array of MyProtocol or MyProtocolViewController and also call the methods defined in UIViewControllerInteractions which will call the UIViewController methods.
var viewController: [MyProtocol] = [...]
viewController.forEach { (vc) in
print(vc.myData)
print(vc.title)
}
I had a similar issue and solved it with a custom base class. Imagine an array like:
var viewControllers: [MapViewController]
which all should extend from UIViewController and implement the following protocol:
protocol MapViewControllerDelegate {
func zoomToUser()
}
Then I've declared a base class like:
class MapViewController: UIViewController {
var delegate: MapViewControllerDelegate?
}
Caution: this class doesn't implement the above protocol but holds a property which provides the desired functionality. The next step is to define one of the UIViewController that will be added to the array:
class GoogleMapsViewController: MapViewController {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
}
}
extension GoogleMapsViewController: MapViewControllerDelegate {
func zoomToUser() {
// Place custom google maps code here
}
}
The important part is located in the viewDidLoad method. The view controller assigns itself as the delegate.
Usage:
let googleMapsViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "GoogleMapsViewController") as! GoogleMapsViewController
let mapboxMapsViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MapboxMapsViewController") as! MapboxMapsViewController
let mapViewControllers: [MapViewController] = [googleMapsViewController, mapboxViewController]
for mapVC in mapViewControllers {
mapVC.delegate?.zoomToUser()
}
The benefits:
The MapViewController is like an abstract class and If I change the MapViewControllerDelegate the compiler forces me to implement the changes in the GoogleMapsViewController and in the MapboxMapsViewController.
If I need a second protocol I could just implement a second delegate property.
No type casting needed like in the other answers. Each UIViewController is still a UIViewController and provides all its methods.

Updating a UILabel via protocol results in crash (found nil)

I want to implement the MVP pattern for a new app. So the View shouldn't have any logic besides one that exclusively concerns UI elements. Therefore I want to request initial data from an "Interpreter" (interpreting user input in later code), which in turn requests data from my model and gives it to the "Presenter". The presenter holds a protocol with functions of the view.
The problem is: Calling updateUIData() from the presenter results in a
fatal error: unexpectedly found nil while unwrapping an Optional value
while calling the function from within the View at the same position is working just fine.
I suspect the error comes from the initialization of the specific MainViewController in the init of the presenter, but I don't know how to resolve this, if my guess is right.
Here's my (relevant) code:
MainViewController:
class MainViewController: UIViewController {
lazy var interpreter = Interpreter() // lazy needed b/c Interpreter holds Presenter which holds MainViewController
#IBOutlet var dateLabel: UILabel!
#IBOutlet var totalTimeLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// updateUIData()
requestData()
}
func requestData() {
interpreter.requestData()
}
}
extension MainViewController: MainViewSetters {
func updateUIData() {
dateLabel.text = "Data"
totalTimeLabel.text = "loaded"
}
}
MainViewSetters (Protocol):
protocol MainViewSetters {
func updateUIData()
}
Interpreter:
class Interpreter {
let presenter = Presenter()
func requestData() {
// normally: get data from model and pass it to presenter
presenter.presentData()
}
}
Presenter:
class Presenter {
let mainView: MainViewSetters
init(withMainViewController mainVC: MainViewSetters = MainViewController()) {
mainView = mainVC
}
func presentData() {
mainView.updateUIData()
}
}
Your problem here is that you are not passing the reference to MainViewController to your instance of Presenter.
This code :
lazy var interpreter = Interpreter()
Should be more like this : (Type is needed here because with lazy the compiler can't infer properly)
lazy var interpreter: Interpreter = Interpreter(for: self)
You then have to create a special initializer in Interpreter which will pass the viewController instance to its presenter property :
class Interpreter {
let presenter: Presenter
init(for viewController: MainViewSetters) {
presenter = Presenter(withMainViewController: viewController)
}
func requestData() {
// normally: get data from model and pass it to presenter
presenter.presentData()
}
}
I also highly suggest you to remove the default value to Presenter's init method, it's very unlikely you'll want to assign a random instance of MainViewController as mainView of any Presenter object.
Finally, please note that this code is creating a retain cycle and neither your MainViewController instance nor your Presenter instance will be deallocated. This is due to the fact the Presenter class holds a strong reference to the MainViewController instance with its property mainView. To fix this you have to mark the mainView as weak as well as making it optional.
Please see the fixed implementation below :
class Presenter {
weak var mainView: MainViewSetters?
init(withMainViewController mainVC: MainViewSetters) {
mainView = mainVC
}
func presentData() {
mainView?.updateUIData()
}
}
For weak to be acceptable on a property of type MainViewSetters (which is not a real type but only a protocol) you have to specify that its a protocol that will only be applied to classes :
protocol MainViewSetters: class {
func updateUIData()
}
You are initializing interpreter passing a default MainViewController().
Change that code from:
lazy var interpreter = Interpreter()
to
lazy var interpreter = Interpreter(withMainViewController: self)

Setting the delegate on initialization of object in conforming "header"

I am getting an error trying to set the delegate of my ParseController, which is a property on my VC.
protocol ParseControllerDelegate: NSObjectProtocol {
var parseController: ParseController! {get set}
//TODO:- Instead of this function, i should use a closure, but couldnt get the sytnax right!
func updateUI()
}
class ParseController: NSObject {
weak var delegate: ParseControllerDelegate!
var dataArray:[AnyObject]!
init(delegate:ParseControllerDelegate!) {
self.delegate = delegate
}
Then when I try to set this in my VC "header" I get an error.
var parseController:ParseController! = ParseController(delegate: self)
I am able to set in viewDidLoad with this method, but ideally I would like to set the delegate where it is. The error I get is
Type 'MainVC -> () -> MainVC!' does not conform to protocol 'ParseControllerDelegate'
MainVC is my viewController.
Things I tried:
Casting (self as ParseControlleDelegate)
self()
self!
Im sure it is something simple, but I can't figure out the proper syntax.
Figured it out!
Proper syntax is:
lazy var parseController:ParseController = ParseController(delegate: self)
*Note: I had to remove the delegate requirement from my protocol, as I got a compiler warning that "lazy" isn't allowed on a protocol requirement, and the delegate requirement was not met once I declared it lazy in my VC.

Compiler error when assigning the Delegate for a Protocol in Swift iOS

I have a problem assigning the delegate for an object that is an instance of a class that defines a protocol in Swift as follows:
I simplified the code to the bare bones to exemplify the issue:
This is the class with the protocol
protocol TheProtocol {
func notifyDelegate()
}
class ClassWithProtocol: NSObject {
var delegate: TheProtocol?
fire() {
delegate?.notifyDelegate()
}
}
This is the class the conforms to the Protocol
class ClassConformingToProtocol: NSObject, TheProtocol {
var object: ClassWithProtocol?
func notifyDelegate() {
println("OK")
}
init() {
object = ClassWithProtocol()
object?.delegate = self // Compiler error - Cannot assign to the result of this expression
object?.fire()
}
}
I have tried all sort of alternatives to assign the delegate without success. Any idea what I am missing?
The Known Issues section of the Release Notes says:
You cannot conditionally assign to a property of an optional object.
(16922562)
For example, this is not supported:
let window: NSWindow? = NSApplication.sharedApplication.mainWindow
window?.title = "Currently experiencing problems"
So you should do something like if let realObject = object { ... }

Resources