UIPickerView with "external" DataSource and Delegate in Swift
Asked Answered
U

1

9

I have two different UIPickerViews in my View. They work great when I set the dataSource and the delegate to the View they are hosted in via the storyboard, but when I try to do that via code as described below, it does not work.

Both pickers shall have different data to display (and maybe even different behaviours for the delegate). Thus I would like to connect them to different data sources programmatically.

I tried to create my own class implementing the UIPickerViewDataSource- and UIPickerViewDelegate-Protocols and connecting objects of that class to my PickerViews, but it does not work. An exception is thrown at runtime terminating with uncaught exception of type NSException stating this:

2015-01-09 17:50:05.333 Pet Stats[4953:244338] -[NSConcreteMapTable numberOfComponentsInPickerView:]: unrecognized selector sent to instance 0x7b4616d0
2015-01-09 17:50:05.338 Pet Stats[4953:244338] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteMapTable numberOfComponentsInPickerView:]: unrecognized selector sent to instance 0x7b4616d0'

How can I get this to work? What did I miss? Here is my code:

WeightWheelController.swift

import UIKit

class WeightWheelController: NSObject, UIPickerViewDelegate, UIPickerViewDataSource {
    let ElementCount: Int!

    init(pickerInterval: Int) {
        ElementCount = pickerInterval
    }

    func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return ElementCount
    }

    func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
        return String(row + 1)
    }

    func pickerView(pickerView: UIPickerView!, didSelectRow row: Int, inComponent component: Int)
    {
        println("External Controller:" + String(row + 1))
    }
}

WeightWheelInputViewController.swift

import UIKit

class WeightWheelInputViewController: UIViewController {
    @IBOutlet weak var picker1: UIPickerView!        
    @IBOutlet weak var picker2: UIPickerView!

    override func viewDidLoad() {
        super.viewDidLoad()

        //picker attached to c1 should show number from 1 to 150
        let c1 = WeightWheelController(pickerInterval: 150)

        //picker attached to c1 should show number from 1 to 10
        let c2 = WeightWheelController(pickerInterval: 10)

        picker1.dataSource = c1
        picker1.delegate = c1

        picker2.dataSource = c2
        picker2.delegate = c2
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

BRIEF UPDATE:

In this question I have found that you can use different tags for different picker views. That would be one option; yet, I don't like it. I would like to rather follow a MVC'ish approach and connect different controllers to each picker. Isn't that possible in any way?

Unanimity answered 9/1, 2015 at 17:5 Comment(0)
S
18

Both delegate and datasource are unowned references. This means that c1 and c2 will get released as soon as you go out of scope. Try declaring c1 and c2 as properties of the class.

Unowned references do not create a strong hold on the referred object (a.k.a. they don't increase the retain count in order to prevent ARC from deallocating the referred object).

Also make sure you remove the delegate and datasource properties of the pickerviews from the interface builder.

class WeightWheelInputViewController: UIViewController {
    @IBOutlet weak var picker1: UIPickerView!        
    @IBOutlet weak var picker2: UIPickerView!

    var c1 : WeightWheelController!
    var c2 : WeightWheelController!

    override func viewDidLoad() {
        super.viewDidLoad()

        c1 = WeightWheelController(pickerInterval: 150)

        c2 = WeightWheelController(pickerInterval: 10)

        picker1.dataSource = c1
        picker1.delegate = c1

        picker2.dataSource = c2
        picker2.delegate = c2
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
Steelmaker answered 9/1, 2015 at 17:48 Comment(3)
Great response! Worked right out of the box! Thank you so much!!! Yet, what I do not get is this: In Java you could do what I coded above, and the GC will not collect the objects with the "weak" reference, because they are now referenced by the PickerView. How does swift handle that different?Unanimity
well. for that you need to learn about automatic reference counting (ARC). I suggest you read apple's documentation about it. developer.apple.com/library/ios/documentation/Swift/Conceptual/…Steelmaker
Stupendous answer. I was going nuts. Thanks a million!Mimamsa

© 2022 - 2024 — McMap. All rights reserved.