If I understand your question correctly, you are supposing the @IBOutlet
properties should be marked as private
all the time... Well it's not true. But also accessing the properties directly is not safe at all. You see the ViewControllers, TableViewCells and these objects use Implicit unwrapping on optional IBOutlets for reason... You don't need to init ViewController when using storyboards or just when using them somewhere in code... The other way - just imagine you are creating VC programmatically and you are passing all the labels to the initializer... It would blow your head... Instead of this, you come with this in storyboard:
@IBOutlet var myLabel: UILabel!
this is cool, you don't need to have that on init, it will just be there waiting to be set somewhere before accessing it's value... Interface builder will handle for you the initialization just before ViewDidLoad, so the label won't be nil after that time... again before AwakeFromNib
method goes in the UITableViewCell subclass, when you would try to access your bookTitle
label property, it would crash since it would be nil... This is the tricky part about why this should be private... Otherwise when you know that the VC is 100% on the scene allocated there's no need to be shy and make everything private...
When you for example work in prepare(for segue:)
method, you SHOULD NEVER ACCESS THE @IBOutlets. Since they are not allocated and even if they were, they would get overwritten by some internal calls in push/present/ whatever functions...
Okay that's cool.. so what to do now?
When using UITableViewCell
subclass, you can safely access the IBOutlets (ONLY IF YOU USE STORYBOARD AND THE CELL IS WITHIN YOUR TABLEVIEW❗️)
and change their values... you see
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// We shouldn't return just some constructor with UITableViewCell, but who cares for this purposes...
guard let cell = tableView.dequeueReusableCell(withIdentifier: "bookTableViewCell", for: indexPath) else { return UITableViewCell() }
cell.bookTitle.text = "any given text" // This should work ok because of interface builder...
}
The above case should work in MVC pattern, not MVVM or other patterns where you don't use storyboards with tableViewControllers and embed cells too much... (because of registering cells, but that's other article...)
I will give you few pointers, how you can setup the values in the cell/ViewController without touching the actual values and make this safe... Also good practice (safety) is to make the IBOutlets optional to be 100% Safe, but it's not necessary and honestly it would be strange approach to this problem:
ViewControllers:
class SomeVC: UIViewController {
// This solution should be effective when those labels could be marked weak too...
// Always access weak variables NOT DIRECTLY but with safe unwrap...
@IBOutlet var titleLabel: UILabel?
@IBOutlet var subtitleLabel: UILabel?
var myCustomTitle: String?
var myCustomSubtitle: String?
func setup(with dataSource: SomeVCDataSource ) {
guard let titleLabel = titleLabel, let subtitleLabel = subtitleLabel else { return }
// Now the values are safely unwrapped and nothing can crash...
titleLabel.text = dataSource.title
subtitleLabel.text = dataSource.subtitle
}
// WHen using prepare for segue, use this:
override func viewDidLoad() {
super.viewDidLoad()
titleLabel.text = myCustomTitle
subtitleLabel.text = myCustomSubtitle
}
}
struct SomeVCDataSource {
var title: String
var subtitle: String
}
The next problem could be this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let destinationVC = segue.destination as? SomeVC else { return }
let datasource = SomeVCDataSource(title: "Foo", subtitle: "Bar")
// This sets up cool labels... but the labels are Nil before the segue occurs and even after that, so the guard in setup(with dataSource:) will fail and return...
destinationVC.setup(with: datasource)
// So instead of this you should set the properties myCustomTitle and myCustomSubtitle to values you want and then in viewDidLoad set the values
destinationVC.myCustomTitle = "Foo"
destinationVC.myCustomSubtitle = "Bar"
}
You see, you don' need to set your IBOutlets to private since you never know how you will use them If you need any more examples or something is not clear to you, ask as you want... Wish you happy coding and deep learning!