Clean way to force view to load subviews early

2019-02-21 12:33发布

问题:

Recently I wrote some code where I tried to refer to an outlet on a UIViewController I'd just instantiated with [storyboard instantiateViewControllerWithIdentifier] and modify the subview that the outlet pointed to before presenting the ViewController. It didn't work because the ViewController's view hadn't loaded its subviews yet, including the one that my outlet referred to, so the property just gave me a null pointer.

After (with some struggle) tracking down the cause of my issue in the debugger, I Googled around and learned, through answers like this one, that I can cause the view to load its subviews without being displayed by calling the myViewController.view getter. After that, I can access my outlet without any problems.

It's a clear hack, though, and Xcode - quite rightly - doesn't like it, and angrily protests with this warning:

Property access result unused - getters should not be used for side effects

Is there a non-hacky alternative way to do this that doesn't involved abusing the .view getter? Alternatively, are there canonical/idiomatic patterns for this scenario involving something like dynamically adding a handler to be called as soon as the subviews are loaded?

Or is the standard solution just to replace myViewController.view with [myViewController view] to shut up Xcode's warning, and then live with the hack?

回答1:

On iOS 9 or newer, one can use:

viewController.loadViewIfNeeded()

Docs: https://developer.apple.com/reference/uikit/uiviewcontroller/1621446-loadviewifneeded



回答2:

I agree that forcing a view to load should be avoided but I ran into a case where it seemed the only reasonable solution to a problem (popping a UINavigationController containing a UISearchController that had yet to be invoked causes a nasty console says warning).

What I did was use new iOS9 API loadViewIfNeeded and for pre-iOS9 used viewController.view.alpha = 1.0. Of course a good comment above this code will prevent you (or someone else) removing this code later thinking it is unneeded.

The fact that Apple is now providing this API signals it can be needed from time to time.



回答3:

merged Rudolph/Swany answers for pre ios9 deployment targets

    if #available(iOS 9.0, *) {
        loadViewIfNeeded()
    }
    else {
        // _ = self.view works but some Swift compiler genius could optimize what seems like a noop out
        // hence this perversion from this recipe http://stackoverflow.com/questions/17279604/clean-way-to-force-view-to-load-subviews-early
        view.alpha = 1
    }


回答4:

If I understand you correctly, I think there's another fairly standard solution: move the outlet modification/configuration code into a viewDidLoad method (of the recently instantiated VC).

The topic is also discussed in this question.

It would require some restructuring, but it might give you a "cleaner" design in terms of MVC if your incoming VC handled its own configuration, and it would avoid the "You should never call this method directly" stricture on loadView.



回答5:

Not sure how much clean this way, but it still works fine:

_ = vc.view


回答6:

You can call [myViewController loadView] to explicitly load the view, instead of abusing the .view getter. The .view getter actually calls loadView if necessary when called.

It's still not a very nice solution, since the UIView Documentation's section on loadView explicitly instructs that

You should never call this method directly