0

The value of the variable 'switcheroo' in the view controller below is always the same when I attempt to access it via a singleton. I am trying to access its value from a custom label class that prints the characters of the label one by one. When the label is set, I try to get the updated value of switcheroo in the Viewcontroller singleton. However it always returns the initial value of switcheroo, not the updated value (which I can trace in the viewcontroller). Am I doing something wrong?

class TheViewController: UITableViewController, UIGestureRecognizerDelegate, UITabBarControllerDelegate {

   static let shared = TheViewController()
   var switcheroo = 0

   ... various operations that change the value of switcheroo...
}

class CustomLabel: UILabel {

  required init?(coder aDecoder: NSCoder) {
     super.init(coder: aDecoder)
  } 

  override var attributedText: NSAttributedString? {

    didSet { 

        DispatchQueue.main.async {

          let characterDelay = TimeInterval(0.01 + Float(arc4random()) / Float(UInt32.max)) / 100

                for (index, _) in attributedText.string.enumerated() {

                    DispatchQueue.main.asyncAfter(deadline: .now() + characterDelay * Double(index)) {
                        print("switcheroo value in TheViewController is now: \(TheViewController.shared.switcheroo)")
                        super.attributedText = attributedText.attributedSubstring(from: NSRange(location: 0, length: index+1))
                    }
                }
            }
}
lucius degeer
  • 403
  • 1
  • 6
  • 17
  • 2
    why would you want a view controller singleton? also: a view should never need to access a view controller. – vikingosegundo Mar 02 '20 at 02:51
  • 3
    You shouldn't use singletons for view controllers. In any case, I bet you are creating a fresh instance of `TheViewController` and updating the `switcheroo` property on that fresh instance. – Rob C Mar 02 '20 at 02:54
  • You can check this post how to create a singleton that you can use on all your view controllers https://stackoverflow.com/questions/47477776/ble-peripheral-disconnects-when-navigating-to-different-viewcontroller/47481780 – Leo Dabus Mar 02 '20 at 03:58
  • Can you really make a singleton of a class that inherits from a non singleton class? What about all the superclass init methods, they cannot be made private for one thing. I don’t see this as doable. – Joakim Danielson Mar 02 '20 at 06:10
  • @vikingosegundo Thanks for your responses. I'm open to other solutions. All I need to do is get the current value of 'switcheroo' from the view controller that instantiates these custom labels. I need the current state of 'switcheroo' to determine if the text animation of the label should continue or not. – lucius degeer Mar 02 '20 at 13:40
  • 1
    Unrelated, I might simplify the definition of `characterDelay`. For example, if you wanted it between 1 and 2 one hundredths of a second, a slightly more contemporary and natural syntax would be `let characterDelay = TimeInterval.random(in: 0.01...0.02)`. – Rob Mar 02 '20 at 18:07

3 Answers3

1

I would not suggest making a view controller a singleton solely for the purpose of some shared state or model property. View controllers have their own life cycle patterns (e.g. instantiated from storyboard scenes, released when they are dismissed, recreated when presented again, etc.), and you’re likely to have issues arising from dealing with separate instances of your view controller(s).

Instead, don’t try to fight the standard view controller life cycle, but rather just move this property into a separate object, e.g.

final class StateManager {
    static let shared = StateManager()

    var switcheroo = 0

    private init() { }
}

Then your view controllers can use that:

class ViewController: UIViewController {

    ...

    func examineSwitcheroo() {
        print(StateManager.shared.switcheroo)
    }

    func updateSwitcheroo(to value: Int) {
        StateManager.shared.switcheroo = value
    }
}

This way, you enjoy this shared state, without entangling normal view controller life cycles in this process.

Now, what the right name for this singleton, StateManager in my example, depends entirely upon what this shared property is. But there’s not enough information about what this switcheroo object really is to offer better counsel on this matter.

And, probably needless to say, it’s a separate question as to whether you really should be using singletons at all for state variables and model objects, but that’s beyond the scope of this question.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thanks for your crystal clear response. In essence, switcheroo switches a tableviewcontroller between two data sources. Because the data sources are rendered as animated text asynchronously, the animations sometimes continue despite the tableview having switched data sources. – lucius degeer Mar 02 '20 at 17:29
  • In other words, I get the wrong text in the label if the animation takes longer to finish because it is a leftover asynchronous operation. So, I need to have a condition in the anychronous animation that checks if 'switcheroo' has switched values in the tableviewcontroller in order to determine if the animation should continue or end. If you have any thoughts I'm very open to alternative approaches! Thanks again – lucius degeer Mar 02 '20 at 17:29
  • 1
    The notion of lots of `asyncAfter` calls is problematic because (a) with timer coalescing you’ll see it start to clump latter ones together; and (b) it’s hard to cancel them. I’d instead suggest a repeating timer which you can `invalidate` when you want to stop the animation when you go to another “data source”. But this asynchronous timer-based animation feels like a very different question from the singleton pattern asked here, so feel free to post a separate question, if you are still unclear... – Rob Mar 02 '20 at 18:04
  • Thanks. I'll try the timer approach again. I couldn't get it to work, but likely for a silly reason. – lucius degeer Mar 02 '20 at 18:48
0

If you have determined that having a ViewController singleton is the right decision, the likely answer is that you are not using that shared instance every time, instead accidentally calling the initializer at some point in your project (possibly Xcode is doing it automatically through interfaces).

To search through your entire project, you can use cmd + shift + F and then type TheViewController(). There should only be one occurrence (the shared instance). Be sure to also check for TheViewController.init(). That will find any time you do it.

If the issue persists, perhaps try setting the shared instance to self in the viewDidLoad method of TheViewController?

Hope this helps!

Sam
  • 2,350
  • 1
  • 11
  • 22
0

Don't manage your application's data in your view controller(s). The Cocoa and Cocoa Touch frameworks use the MVC paradigm, where the M is meant to stand for model, i.e. the application's data model. Any data that needs to be preserved, or that's relevant beyond the scope of the view controller, should be stored and managed in a model object. If you give your view controller's a reference to the model when you create them, you never need to worry about passing data from one view controller to another; instead, they each operate on the model, and any data they need comes from the model.

Caleb
  • 124,013
  • 19
  • 183
  • 272