Should we use observer methods to modify IBOutlet properties - ios

I have seen code where IBOutlets modify their properties using a didSet like so..
#IBOutlet private weak var tableView: UITableView! {
didSet {
tableView.dataSource = self
tableView.delegate = self
}
}
Is this considered good practice, or should we create a configure method in viewDidLoad?

Actually didSet here
outer part
didSet {
// refresh
}
makes more sense if the outer var you observe is rapidly changing / real time so you need to react to this change , but for the current case which is the table is set only once from IB inner init using didSet has no bounce over putting the code inside viewDidLoad

usually I do this:
func setupTableView() {
self.tableView.delegate = self
self.tableView.dataSource = self
}
So i call this method on my viewDidLoad()

Related

Cannot use instance member 'generateRandomNumbers' within property initializer; property initializers run before 'self' is available

import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView1: UITableView!
var array = generateRandomNumbers(size: 1000)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView1.delegate = self
tableView1.dataSource = self
// var array = self.generateRandomNumbers(size: 1000)
// lazy var array = generateRandomNumbers(size: 1000)
// print(array)
}
func generateRandomNumbers(size: Int) -> [Int] {
guard size > 0 else {
return [Int]()
}
let result = Array(0..<size).shuffled()
return result
}
You are asking var array to receive a "default" value from the function generateRandomNumbers(). The "default" value is set during the initialisation of the instance of your view controller, so before the instance exists, so before the generateRandomNumbers() function exists. The compiler doesn't know how to give a value to array before the instance has finished being created.
A workaround is to set a dummy default value, then change it during initialisation, like this:
#IBOutlet weak var tableView1: UITableView!
var array = [Int]() // This is an empty array, the compiler can create it before the generateRandomNumbers() is available
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
tableView1.delegate = self
tableView1.dataSource = self
...
array = generateRandomNumbers(size: 1000) // Now that all variables are initialised, you can change their values
Well, the basic answer is the concept of understanding.
var array = generateRandomNumbers(size: 1000)
When the code is compiled, it first tries to compile the variables first. So at that time, self isn't initiated. So, therefore, it doesn't recognize the method generateRandomNumbers. You get the error.
The best possible solution is actually using
lazy var array = generateRandomNumbers(size: 1000)
When you use lazy it doesn't try to initialize until it is needed. So they are skipped for the first time compilation.
If you don't need lazy simply you have to declare it in the viewDidLoad()
var array = [Int]()
override func viewDidLoad() {
super.viewDidLoad()
array = generateRandomNumbers(size: 1000)
}

RxSwift calling bind fires immediately vs subscribe(onNext: )

Everything I've read says that bind(to:) calls subscribe(onNext:) within it. So I assume I should be able to swap out some stuff, but when I use `bind(to:) the thing it's binding to fires immediately. Here's my example:
ButtonCollectionViewCell
class ButtonCollectionViewCell: UICollectionViewCell {
lazy var buttonTapped: Observable<Void> = { _buttonTapped.asObservable() }()
private var _buttonTapped = PublishSubject<Void>()
private var disposeBag = DisposeBag()
#IBOutlet private weak var textLabel: UILabel!
#IBOutlet private weak var actionButton: UIButton!
// MARK: - Lifecycle
override func awakeFromNib() {
super.awakeFromNib()
actionButton.rx.tap.bind(to: _buttonTapped).disposed(by: disposeBag)
}
override func prepareForReuse() {
disposeBag = DisposeBag()
}
}
Now when I do the following below everything works as expected and it prints to the console when I tap the button
ViewController with a collection view
func createButtonCell() {
let buttonCell = ButtonCollectionViewCell() // there's more code to create it, this is just for simplicity
buttonCell.buttonTapped.subscribe { _ in
print("tapped")
}.disposed(by: disposeBag)
return buttonCell
}
However, if I change the above to:
func createButtonCell() {
let buttonCell = ButtonCollectionViewCell()
buttonCell.buttonTapped.bind(to: buttonTapped)
return buttonCell
}
private func buttonTapped(_ sender: Observable<Void>) {
print("tapped")
}
The "tapped" is printed out right before I get to the cell when scrolling which I assume is when it's being created.
I don't understand this. I thought I could almost swap out the implementations? I would like to use the second example above there as I think it's neater but can't figure out how.
Your two examples are not identical...
In the first example you have: .subscribe { _ in print("tapped") } which is not a subscribe(onNext:) call. The last closure on the subscribe is being used, not the first. I.E., you are calling subscribe(onDisposed:).
Also, your ButtonCollectionViewCell is setup wrong. You bind in the awakeFromNib() which is only called once, and dispose in the prepareForReuse() which is called multiple times. One of the two needs to be move to a more appropriate place...
UPDATE
You could either rebind your subject after reseating the disposeBag, or you could just not put the chain in the dispose bag in the first place by doing:
_ = actionButton.rx.tap
.takeUntil(rx.deallocating)
.bind(to: _buttonTapped)

"unexpectedly found nil" out of viewDidLoad

I have some outlets in my view and I try to edit them programatically in a function.
Xcode says:
unexpectedly found nil while unwrapping an optional value
However, when I edit outlets in viewDidLoad() just after super.viewDidLoad(), it works like a charm.
With the function :
func test(){
localDeviceNameView.stringValue = "some stuff" //Found nil here
}
With viewDidLoad :
override func viewDidLoad() {
super.viewDidLoad()
localDeviceNameView.stringValue = "some stuff" //Works well
}
How can I make it work?
You can use didSet on your variable. Like this :
#IBOutlet var localDeviceNameView : UIView! {
didSet {
localDeviceNameView.stringValue = "some stuff"
}
}
What's good with this approach is that you set your stringValue only when the localDeviceNameView is set, which is right when it has been loaded from the NIB/Storyboard.
I think this is exactly what you need.
You are trying to call test() before views are completely loaded, so they are not exist yet. Do it this way:
class MyController: UIViewController {
var something = ""
override viewDidLoad() {
super.viewDidLoad()
localDeviceNameView.stringValue = something
}
}
Or call you'r test() method only if you sure than all views are loaded (viewDidLoad() was called)

Swift: Delegation protocol not setting UILabel properly

I have the following Protocol:
protocol SoundEventDelegate{
func eventStarted(text:String)
}
which I call in this class:
class SoundEvent {
var text:String
var duration:Double
init(text: String, duration: Double){
self.text = text
self.duration = duration
}
var delegate : SoundEventDelegate?
func startEvent(){
delegate?.eventStarted(self.text)
}
func getDuration() -> Double{
return self.duration //TODO is this common practice?
}
}
Which I have my ViewController conform to:
class ViewController: UIViewController, SoundEventDelegate {
//MARK:Properties
#IBOutlet weak var beginButton: UIButton!
#IBOutlet weak var kleinGrossLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//DELEGATE method
func eventStarted(text:String){
kleinGrossLabel.text = text
}
//MARK: actions
#IBAction func startImprovisation(sender: UIButton) {
var s1:Sentence = Sentence(type: "S3")
var s2:Sentence = Sentence(type: "S1")
var newModel = SentenceMarkov(Ult: s1, Penult: s2)
s1.start()
beginButton.hidden = true
}
}
But when I run the app kleinGrossLabel.text does not change. Am I referring to the label in the wrong way? Or is it the way that I do delegation that is incorrect?
Here are links to the complete Class definitions of Sentence and SentenceMarkov
https://gist.github.com/anonymous/9757d0ff00a4df7a29cb - Sentence
https://gist.github.com/anonymous/91d5d6a59b0c69cba915 - SentenceMarkov
You never set the delegate property. It's nil. It will never be called.
First off it's not common practice to have a setter in swift. if you want to have a readonly property you can use private(set) var propertyName
in other cases simply access the property like mentioned in the comment
Also i don't see a reason why you eventArray in sentence is of type [SoundEvent?] not [SoundEvent] as SoundEventdoes not seem to have a failable initialiser
Like mentioned before you need to not only implement the SoundEventDelegate protocol but also set the delegate
the problem is that you can't really access the SoundEventDelegate from the viewcontroller because you instantiate the SoundEvents inside Sentence
var soundEventDelegate: SoundEventDelegate?
the easiest way to do this would be adding a soundEventDelegate property for sentence and setting it like this:
let s1:Sentence = Sentence(type: "S3")
let s2:Sentence = Sentence(type: "S1")
s1.soundEventDelegate = self
s2.soundEventDelegate = self
and inside sound you would need the set the delegate for every event to the soundEventDelegate of Sentence
you could do it like this:
var soundEventDelegate: SoundEventDelegate? = nil {
didSet {
eventArray.forEach({$0.delegate = soundEventDelegate})
}
}
or write another initialiser that takes the delegate
hope this helps
p.s: you shouldn't inherit form NSObject in swift excepts it's really necessary

Configure UIView in viewDidLoad or var's didSet

Consider this example of configuring MKMapView's map type. Should it be done in viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
mapView.mapType = MKMapType.Hybrid
}
or in the var's didSet?
#IBOutlet weak var mapView: MKMapView! {
didSet {
mapView.mapType = MKMapType.Hybrid
}
}
Both work, what's the Swift preferred way?
They each have a different use.
If you want the mapType set every time the property is set, use didSet.
If you only want the mapType set once when the view is loaded, use viewDidLoad.
Given what you are doing I would say that didSet is the more correct choice here.
BTW - this has nothing to do with "the Swift preferred way". The same logic applies regardless of language.

Resources