H. Daniel
H. Daniel

Reputation: 5

delegate method not getting called with UITabBarController

In FourthViewController, I have a slider, which has values ranging from 1 to 1000. The value that is set gets sent via the delegate to PatternViewController, where it should be used to do sth (I put the print for testing purposes).

I've worked with delegates before and it was all ok, checked the code multiple times and multiple answers here on stack, I can't seem to find the issue. Any help would be much appreciated

update: I have added a button so that it would be easier to track along. It turns out that by pressing first time the button, nothing happens. but if I first checkout the PatternViewController, then I go back to FourthViewController and press the button, the delegate gets triggered. anyone got any idea on why is this happening?

FourthViewController

import UIKit

class FourthViewController: UIViewController {

//MARK: Outlets

@IBOutlet var persistenceButton: UIButton!
@IBOutlet var persistenceSlider: UISlider!
@IBOutlet var persistenceLabel: UILabel!
weak var delegate: FourthViewControllerDelegate?

//MARK: Stored Properties - Constants

let userDefaults = UserDefaults.standard
let keyName = "sliderValue"

//MARK: Initializer

override func viewDidLoad() {
    super.viewDidLoad()
    loadSliderValue()
    initialSetUp()
    }

//MARK: Actions

@IBAction func handleValueChanged(_ sender: UISlider) {
    updateLabel()
    persistSliderValue(value: persistenceSlider.value, key: keyName)
    }

//MARK: Methods

func updateLabel() {
    persistenceLabel.text = String(format: "%.2f", persistenceSlider.value)
    }

func persistSliderValue(value: Float, key: String) {
    userDefaults.set(value, forKey: key)
    }

func loadSliderValue() {
    let persistedValue = userDefaults.float(forKey: keyName)
    persistenceSlider.value = persistedValue
    updateLabel()
    }
}

func initialSetUp()  {
    persistenceButton.addTarget(self, action: #selector(handleButtonPressed), for: .touchUpInside)
    }

@objc func handleButtonPressed() {
    delegate?.valueChanged(value: persistenceSlider.value)
    }
}

PatternViewController

import UIKit

class PatternViewController: UIViewController, FourthViewControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        setUp()
    }

    func setUp() {
        if let tabBar = self.tabBarController, let viewController = tabBar.viewControllers, let fourthViewController = viewController[3] as? FourthViewController  {
            fourthViewController.delegate = self
        }
    }

    func valueChanged(value: Float) {
        print(value)
    }
}

Upvotes: 0

Views: 656

Answers (1)

Rob
Rob

Reputation: 438307

It depends upon how you instantiated the tab view controller. If you do it with storyboards, for example, the view controllers for the respective tabs are instantiated lazily, only instantiated as the user taps on them. (This helps reduce latency resulting from instantiating all four of the tabs’ view controllers.)

While you theoretically could go ahead and have the tab bar controller instantiate the four view controllers programmatically up front, rather than just-in-time via the storyboard, I might instead consider specifying a UITabBarControllerDelegate for the tab bar controller. Have the tab bar controller’s delegate method update the relevant tab’s view controller’s model.

Here is an example with two tabs, the first has a slider and the second has a label that displays the slider’s value. In this simplified example, I’ve moved the model object (the value associated with the slider) into the tab bar controller, and it passes it to the second view controller when you select the associated tab.

//  TabViewController.swift

import UIKit

class TabBarController: UITabBarController {
    var value: Float = 0.5
    
    override func viewDidLoad() {
        super.viewDidLoad()

        delegate = self
    }
}

// MARK: - UITabBarControllerDelegate

extension TabViewController: UITabBarControllerDelegate {
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        guard let viewController = viewController as? SecondViewController else { return }

        viewController.value = value
    }
}

And

//  FirstViewController.swift

import UIKit

class FirstViewController: UIViewController {

    @IBOutlet weak var slider: UISlider!

    override func viewDidLoad() {
        super.viewDidLoad()
        guard let tabBarController = tabBarController as? TabViewController else { return }
        slider.value = tabBarController.value
    }

    @IBAction func didAdjustSlider(_ sender: UISlider) {
        guard let tabBarController = tabBarController as? TabViewController else { return }
        tabBarController.value = sender.value
    }

}

And

//  SecondViewController.swift

import UIKit

class SecondViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    var value: Float = 0 { didSet { updateLabel() } }

    let formatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .percent
        return formatter
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        updateLabel()
    }

    func updateLabel() {
        label?.text = formatter.string(for: value)
    }
}

enter image description here

Probably needless to say, I not only set the base view controller class for the two tab’s view controllers, but also set the base class for the tab bar controller’s storyboard scene to the above TabBarController.

Upvotes: 1

Related Questions