SchoonSauce
SchoonSauce

Reputation: 291

How to properly implement lazy instantiation with an @IBOutlet property in Swift

I am Learning iOS Development with Big Nerd Ranch's latest iOS book. I have chosen to implement their apps in Swift. In one of their apps, they have the following code in Objective C:

- (UIView *)headerView
{
    // If you have not loaded the header view yet...
    if (!_headerView) {

        // Load HeaderView.xib
        [[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self options:nil]
    }

    return _headerView;
}

Apple's Swift guide on "@IBOutlet":

When you declare an outlet in Swift, the compiler automatically converts the type to a weak implicitly unwrapped optional and assigns it an initial value of nil. In effect, the compiler replaces @IBOutlet var name: Type with @IBOutlet weak var name: Type! = nil.

As it was pointed out in Lazy loading Properties in swift, there are a couple of different options. None of them in that post explicitly mention lazy initialization with @IBOutlet, so I've done by best to implement their suggestions, and would like to know what would be considered best practices.

Attempt #1(failed): following a similar pattern, as the example from AppDelegate.swift. This brings the issue "'IBOutlet' attribute requires property to be mutable"

@IBOutlet var headerView : UIView  {
    // If the HeaderView has not been loaded yet...
    if !_headerView {
        // Load HeaderView.xib
        NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)
    }
        return _headerView!
}
var _headerView : UIView? = nil

Attempt #2(failed): using any variation of "@lazy" with "@IBOutlet" didn't worked because "@lazy" needs an initializer, but if a closure is used, then "@IBOutlet" has the same issue as from Attempt #1

Attempt #3(successful?): this is the only way I was able to get this to work. I got the idea from a somewhat different question, Lazy property initialization in Swift. My understanding of what is happening is headerView is actually declared as "@IBOutlet weak var headerView : UIView! = nil", will only be initialized once with the TableViewController subclass I have, and that initialization will be "lazy" in that it only occurs when the TableViewController needs to be loaded.

@IBOutlet var headerView : UIView

func loadHeaderView() {
    // If the HeaderView has not been loaded yet...
    if !headerView {
        // Load HeaderView.xib
        println("loaded HeaderView")
        NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)
    }
}

override func viewDidLoad() {
    super.viewDidLoad()
    loadHeaderView()
    tableView.tableHeaderView = headerView
}

So, how can this be improved?

Is viewDidLoad() the correct function to use?

Thanks

Upvotes: 4

Views: 6866

Answers (4)

user4414149
user4414149

Reputation:

You can also use a didSet:

 @IBOutlet weak var profileImageView: UIImageView! {
        didSet {
            profileImageView.image = profileImage
            profileImageView.layer.masksToBounds = true
            profileImageView.layer.cornerRadius = 16
        }
    }

Upvotes: 2

T.Roth
T.Roth

Reputation: 145

The following works...

@IBOutlet lazy var headerView : UIView? = {
    return NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)[0] as? UIView
    }()

then set the headerView

override func viewDidLoad() {
  super.viewDidLoad()
  tableView.tableHeaderView = headerView
}

Upvotes: 4

jrturton
jrturton

Reputation: 119262

A lazily loaded outlet makes no sense- if it's an outlet, it's populated when loading the nib, not from code. If you're loading it from code, it doesn't need to be an outlet, so you can use @lazy.

Upvotes: 7

Nate Cook
Nate Cook

Reputation: 93286

You aren't actually providing a closure for headerView with that code, you're declaring it as a read-only computed property. @IBOutlet properties need to be mutable so the XIB/Storyboard can do its magic, so you'd need to implement it with both a getter and a setter, like this:

@IBOutlet var headerView : UIView {
get {
    if !_headerView {
        NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)
    }
    return _headerView!
}
set {
    _headerView = newValue
}
}
var _headerView : UIView? = nil

Upvotes: 5

Related Questions