Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift/iOS: IBOutlet nil after loading view controller

I'm building an app (in XCode 8.2.1) where some objects are displayed on a 2D board, and when the user taps one of these objects some info should be displayed about it as a styled modal info box. My design is to have the info written in a separate view controller, which I would display when needed.

I've designed a basic stub for the second view controller and added a single label to it in the interface builder. Then I've ctrl-linked this label to my custom VC class:

class InfoViewController: UIViewController {
    @IBOutlet weak var info: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    func displayInfo() {
        info.attributedText = NSAttributedString(string: "abc")
    }
}

However, when I test my app and tap the object, the info field is nil even in the viewDidLoad() method of my custom VC class. The way I'm displaying my VC is as follows:

let infoViewController = InfoViewController()
infoViewController.modalPresentationStyle = .overCurrentContext
self.present(infoViewController, animated: true, completion: nil)
infoViewController.displayInfo()

(Note: In the end I will have only one single instance of InfoViewController but this is just for testing. I don't expect having a global instance would make any difference?)

As I said, be it inside the viewDidLoad() method or in the displayInfo() method, info is always nil, such that setting its attributedString attribute crashes the app. Thinking the present method might be called asynchronously, I've tried calling displayInfo() from inside viewDidLoad(), but that didn't make any difference.

Can anyone tell my what I've forgotten that would allow my IBOutlet from being properly initialized properly?

Thanks!

David

like image 388
David Avatar asked Jan 03 '17 21:01

David


People also ask

Why is IBOutlet weak in IOS?

In Mac development an IBOutlet is usually a weak reference: if you have a subclass of NSViewController only the top-level view will be retained and when you dealloc the controller all its subviews and outlets are freed automatically. UiViewController use Key Value Coding to set the outlets using strong references.

What is IBOutlet Swift?

Swift code is associated with graphical interface elements through the use of outlets and actions. An IB Outlet (short for Interface Builder outlet) is a graphical component that your code links to. An IB action is the reverse: It is a method in your code that a graphical component links to.

How do I add a ViewController to a storyboard?

To create a new view controller, select File->New->File and select a Cocoa Touch Class. Choose whether to create it with Swift or Objective-C and inherit from UIViewController . Don't create it with a xib (a separate Interface Builder file), as you will most likely add it to an existing storyboard.

What is viewDidLoad?

viewDidLoad is called when the ViewController has loaded its view hierarchy into memory. This is the point where you can perform your customized initialisation for your view controller. For instance, if your view controller has a UILabel and you want to set a custom text to it, this is the point where you do that.


1 Answers

The problem is the reference to InfoViewController(), which instantiates the view controller independent of any storyboard scene. You want to use instantiateViewController:

let infoViewController = storyboard?.instantiateViewController(withIdentifier: "Info") as! InfoViewController
infoViewController.modalPresentationStyle = .overCurrentContext
present(infoViewController, animated: true) {
    infoViewController.displayInfo()
}

A couple of notes:

  • This assumes that (a) you've given the scene in the storyboard a "storyboard id"; (b) you've set the base class for that scene to InfoViewController.

  • Note, I called displayInfo in the completion handler of present because you probably don't want that called until the scene has been presented and the outlets have been hooked up.


Alternatively, you can update non-outlet properties of the InfoViewController immediately after instantiating it and then have its viewDidLoad take those properties and update the outlets, e.g.:

class InfoViewController: UIViewController {
    var info: String!
    @IBOutlet weak var infoLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        infoLabel.attributedText = NSAttributedString(string: info)
    }
}

Note, I changed the @IBOutlet name to be infoLabel and added the String property called info. That tends to be the convention, that outlets bear some suffix indicating the type of control, and model objects, like the String property, are without the suffix. (You'll just want to make sure you remove that old outlet in the connections inspector in IB so that you don't have problems with these property name changes.)

Anyway, you can then do:

let infoViewController = storyboard?.instantiateViewController(withIdentifier: "Info") as! InfoViewController
infoViewController.info = "abc"
infoViewController.modalPresentationStyle = .overCurrentContext
present(infoViewController, animated: true, completion: nil)

The key point is don't try to update outlets of the scene immediately after instantiating it, but make sure that this is deferred until after viewDidLoad was called.

like image 125
Rob Avatar answered Sep 20 '22 16:09

Rob