Search code examples
swiftsubclassing

Extracting @IBOutlets properties values out of the main view controller


Since the beginning I've been working with the MVP model in order to keep things going but now, months later, I realize that most of my projects have "Massive View Controllers". Extensions worked well at first to unclutter the actual view controller file but technically the code is still in the view controller.

A more practical approach to my question is in my last project were I have an In-App purchase after which the user gets some new features unlocked. We are supposing that the entire StoreKit logic is doing its job, the purchase was successfully finished and you are just about to give the new functionality to the user.

In order to handle this I have a function func unlockFeatures inside the main view controller (PreferencesViewController) that is triggered by and observer when the In-App-Purchase is successfully made.

class PreferencesViewController: UIViewController {

    ...
    @obc func unlockFeatures {
        InAppPuchaseButton.isEnabled = false
        InAppPuchaseButton.applyOpacity(opacity: 1.0)
    }
    ...
}

At this point, when I only had to enable and change the opacity of one single button everything seems clean. My question is how to handle situations when inside the func unlockFeatures you need to handle multiple lines of UI code like this:

class PreferencesViewController: UIViewController {

    ...
    @obc func unlockFeatures {
        InAppPuchaseButton.isEnabled = false
        InAppPuchaseButton.applyOpacity(opacity: 1.0)
        InAppPuchaseButton.isHidden = false
        RefreshLabel.text = "DOWNLOADING EVENTS"
        self.activityIndicator.stopAnimating()
        //And keep going for 50 lines more of code!!
    }
    ...
}

To me it looks cluttered and I would like to extract this function outside the PreferencesViewController and call it just when I need it. Is there a way to extract all this UI logic outside the view controller class or this is a bad practice?


Solution

  • The in-app purchases example is an excellent one. What we'd like to do here is provide a single centralized object that can (for example):

    • Act as the locus for IAP-related code that doesn't involve the interface, such as acting as the SKPaymentTransactionObserver

    • Act as a gateway for the rest of the app to be able to ask whether the user has made a purchase (of course our object will know the answer by consulting user defaults, but that will be an implementation detail hidden by its public API)

    It makes sense to break these functionalities off from the view controller, because they have nothing to do with controlling any views.

    So this is a perfect opportunity to use a helper class. A typical implementation would be a class that dispenses a singleton instance, which we obtain and store in a property of the app delegate or of the root view controller, either of which is persistent and easily reached from anywhere in the app.


    In a broader context, I use this sort of helper object all the time, to tackle exactly the sort of issue you describe. For example, in a game app, I had a lot of code in the main view controller until I broke off several areas of operation into helper classes, because they really had nothing to do with the control of the views. For example, the logic of the timer and maintenance of the score went off into a helper object. The "state machine" that decides what to do next when we come to the next stage of the game went off into a helper object. And so on.


    In your revised question, you say:

    To me it looks cluttered and I would like to extract this function outside the PreferencesViewController and call it just when I need it. Is there a way to extract all this UI logic outside the view controller class or this is a bad practice?

    There is nothing "cluttered" here; it's all in one function, which is right. The good practice is to have the UI manipulation inside the view controller class but possibly call it from elsewhere (e.g. by the helper I described in my answer). Actually what I would do is have the helper post a notification so that it is agnostic about what will happen; it just shouts "Okay, the user purchased!" and the view controller receives the notification and calls that method.