0

I have created an extra class for my picker view. A PickerView is shown as I press a BarButtonItem and a normal button. This works fine. I want that when I select an item the title of the button is changed.

I try this with a delegate.

In my picker class I have created a protocol:

protocol CodeDelegate {
    func getCode(code: String)
}

class Picker: UIPickerView {

    var codeDelegate: CodeDelegate?

  func setupPickerViewForRegion(view: UIView, barButtonItem: UIBarButtonItem) {
        codeDelegate = HomeViewController()
        
        barButtonRegion = barButtonItem
        
        viewFromController = view
        createToolbar()
        closeWhenTapOnScreen()
        
        UIApplication.shared.windows.first(where: { $0.isKeyWindow })?.addSubview(blurScreenView)
        
        view.addSubview(textField)
        textField.inputView = pickerView
        textField.becomeFirstResponder()
    }

.
.
.
.
}

extension Picker: UIPickerViewDelegate {

.
.
.

 func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    
        let regionCode = regions[row].prefix(2)
            
        codeDelegate!.getCode(code: String(regionCode))

    }
}

Once a value is selected, the delegate is executed.

I have made my other class conform to the Protocol.

class HomeViewController: UIViewController, CodeDelegate {

    @IBOutlet weak var pickRegion: UIBarButtonItem!

func getCode(code: String) {
        pickRegion.title = code
    }
}

In the Delegate method, I want to assign the passed string to the button. However, I get the error there:

Projekt[7585:218750] Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value: file Projekt/HomeViewController.swift, line 270

All my outlets are still connected, so why this error is thrown?

adri567
  • 533
  • 5
  • 20

1 Answers1

1

Your code has 2 different problems.

First, you set your picker's delegate with the code codeDelegate = HomeViewController(). The expression HomeViewController() creates a new, empty instance of HomeViewController that has nothing to do with anything on the screen. Creating a view controller like that does not let it load its views from its storyboard/XIB file, so it's outlets will be nil. That sets you up for your crash, since when you call the delegate method, your view controller will try to invoke outlets that are nil. You should set the delegate from your HomeViewController.

Something like the following:

let picker = Picker() 
picker.delegate = self

The second problem with your code is a very bad habit. The line codeDelegate!.getCode(code: String(regionCode)) will crash if codeDelegate is nil. (! is the "force unwrap" operator, also known as the "crash if nil" operator.) You should instead use codeDelegate?.getCode(code: String(regionCode)) (Change the ! to a ?.) This isn't likely the cause of your crash, but it is still a very bad thing to do with optionals.

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • Oh yeah, I see. This is a horrible mistake by myself. I set the delegate inside my HomeViewController and now it is working fine. Thank you! – adri567 Dec 10 '20 at 13:20
  • Creating a view controller by calling it's initializer (e.g. directly `ViewControllerClass()`) is almost always wrong. The only time where it's not is if the view controller creates its view hierarchy in code instead of having it loaded from a Storyboard or XIB file. – Duncan C Dec 10 '20 at 13:53