Search code examples
iosuiviewuiviewcontrollerswiftmpmovieplayercontroller

IOS: Programmatic rootViewController renders the view differently to Storyboard 'isInitialView'


With the aim to implement a splash screen that only shows once i've modified didFinishLaunchingWithOptions in order to dynamically select the appropriate view controller. The logic seems to work fine, and the view I intended to load is the one launched

However, the UI seems to be missing elements that would otherwise be displayed should I have not altered the didFinishLaunchingWithOptions function.

func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool
{
    window = UIWindow(frame: UIScreen.mainScreen().bounds)
    let storyBoard = UIStoryboard(name: "Main", bundle: nil)
    var entryViewController: UIViewController?

    if NSUserDefaults.standardUserDefaults().boolForKey("hasSeenWelcomeScreen") == true
    {
        entryViewController = storyBoard.instantiateViewControllerWithIdentifier("NavigationController") as? UIViewController
    }
    else
    {
        entryViewController = storyBoard.instantiateViewControllerWithIdentifier("WelcomeViewController") as? UIViewController
        NSUserDefaults.standardUserDefaults().setValue(true, forKey: "hasSeenWelcomeScreen")
        NSUserDefaults.standardUserDefaults().synchronize()
    }

    self.window?.rootViewController = entryViewController
    self.window?.makeKeyAndVisible()

    return true
}

My WelcomeViewController is a simple view with 1 label, 1 button and a movie which plays in the background (resembling Spotify/Vine's welcome screen). Debugging the code I can see the initialization methods do get executed, but is just the frame that does not seem to be displayed when I dynamically override the initial view

import UIKit
import MediaPlayer
import QuartzCore

class WelcomeViewController: UIViewController {

    var moviePlayerController: MPMoviePlayerController = MPMoviePlayerController()

    @IBOutlet weak var loginButton: UIButton!
    @IBOutlet weak var appNameLabel: UILabel!

    override func viewDidLoad()
    {
        super.viewDidLoad()
        buildMoviePreview()
        buildButtonDesign()
    }

    override func viewWillAppear(animated: Bool)
    {
        self.view.addSubview(self.moviePlayerController.view)
        self.view.addSubview(self.loginButton)
        self.view.addSubview(self.appNameLabel)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    override func prefersStatusBarHidden() -> Bool {
        return true
    }

    private func buildButtonDesign()
    {
        loginButton.layer.borderColor = UIColor.whiteColor().CGColor
        loginButton.layer.borderWidth = 2.0
        loginButton.layer.cornerRadius = 7.0
    }

    private func buildMoviePreview()
    {
        let filePath = NSBundle.mainBundle().pathForResource("intro", ofType: "mov")
        self.moviePlayerController.contentURL = NSURL.fileURLWithPath(filePath)
        self.moviePlayerController.movieSourceType = .File
        self.moviePlayerController.repeatMode = .One
        self.moviePlayerController.view.frame = self.view.bounds
        self.moviePlayerController.scalingMode = .AspectFill
        self.moviePlayerController.controlStyle = .None
        self.moviePlayerController.allowsAirPlay = false
        self.moviePlayerController.shouldAutoplay = true
        self.moviePlayerController.play()
    }
}

For completeness, these are the discrepancies in the layout when using the XCode UI debugger. Please note that they differ even though they implement the same viewController. The only difference is that one has been programmatically set as the initial view, while the other has been set as the initial view through storyboard.

View Comparison


Screenshots of the rendering issue side-by-side

uiRendering


Solution

  • Your approach is... unusual. A Storyboard has a root view controller for a reason and typically at startup you would just let the application handle loading the storyboard and installing that root view controller as the window's main view controller. (The Storyboard loaded is specified in the application target's general settings as the "Main Interface")

    In this case, what I would recommend is making your root view controller the "Normal" view of the application... the one you want users to see when they launch the app on a day-to-day basis.

    Define your "on first launch" view controller as a separate view controller in the storyboard and add a modal segue from the root view controller to the on first launch view controller.

    Then in your applicationDidFinishLaunching, if the user has never seen the first launch controller... simply ask the Storyboard to take that segue. If the user has already seen the first launch presentation that segue will be skipped.

    Another issue I see with your code is in your viewWillAppear method. You should not have to add your views as subviews in viewWillAppear... those subviews should already have been set up at the time the view was loaded from the nib file.

    The one exception is the view of your movie player, but your movie player is owned by a separate view controller. That separate view controller is detached from the view controller hierarchy and does not have it's own view controller methods called at the right times. (so it never receives calls like "viewWillAppear" that might tell it to get it's movie ready to play).

    What you probably want to do is implement "awakeFromNib" and make sure that the movie player's view controller is a sub-controller of this view controller. (so in awakeFromNib for the WelcomeViewController use addChildViewController to make sure the movie controller is in the hierarchy).