Reputation: 291
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
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
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
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
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