Setting root VC programmatically - ios

I'm a Swift beginner, so take it easy on me. I'll be specific in my question.
I deleted the storyboard (I want to learn how to build UI programmatically using Swift).
The below code is placed in the AppDelegate.swift
I also have ViewController.swift in the project explorer.
Firstly, is there a way to use anything other then UINavigationController..? Or having a UINavigationController is a must requirement..?
If not a must, how can I just refer it to a ScrollView for example..?
2ndly, with further research into Apple's own guide, they stated I can also use window?.isHidden = false... Is there a difference between using the former line, and window?.makeKeyAndVisible()..?
Sorry if my question doesn't make sense programmatically, like I said, I'm a beginner, but I'm determined to understand why I write the code I write or copy.
Thank you.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = UINavigationController (rootViewController: ViewController())
return true
}
...
}

A window needs a view controller. It doesn't have to be a UINavigation Controller, but it does need to be a view controller. Not all apps start with a navigation controller. In fact, you can see this yourself by looking at the storyboard that Xcode provides for you in new projects. It's just a ViewController. Can you use a ScrollView instead? If you put the scrollview (which is a UIView) into a UIViewController, sure you can.
Hiding a window just makes it appear and disappear. Making it the key window means that it will be the window that receives events. You might not think that it is important in iOS apps, but iOS apps can have more than one window. In the case of starting an application, it's a good idea to define the window that will be visible and be key, not just the one that will be visible.

Firstly, is there a way to use anything other then UINavigationController..? Or having a UINavigationController is a must requirement..?
You can use any UIViewController subclass as the root view controller. e.g. UITabBarController, UIPageViewController, UIViewController, UITableViewController...
how can I just refer it to a ScrollView for example..?
You can't set a UIScrollView as the root view controller. You can, however, add the UIScrollView as a subview of the UIWindow. I don't recommend you to do this though, because using VCs will make your code more manageable, with different classes managing different views.
Is there a difference between using the former line, and window?.makeKeyAndVisible()..?
Yes, if you look at the docs of makeKeyAndVisible:
This is a convenience method to show the current window and position it in front of all other windows at the same level or lower. If you only want to show the window, change its
isHidden property to false.
So yeah, calling makeKeyAndVisible will make the window the "key window".
According to here, key window behaves like this:
The key window receives keyboard and other non-touch related events. Only one window at a time may be the key window.

Related

How do I transition my project from using a Storyboard to no longer using one?

If I have an Xcode project that already uses a Storyboard, and now I want to switch to loading the view controller programmatically, how do I do that?
Here is an in-depth article about doing this. The TLDR is:
In the project settings, clear the Main Interface:
In your didFinishLaunching use code such as the following:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
let homeViewController = UIViewController()
homeViewController.view.backgroundColor = UIColor.red
window!.rootViewController = homeViewController
window!.makeKeyAndVisible()
return true
}
Slowly. One step at a time.
Storyboards are not all-or-nothing. You can continue to use a Storyboard for places where it is helpful, and you can use programmatic loading for places where it isn't, and you can transition between them one step at a time.
A first step if you wanted to unwind a piece from the Storyboard is to remove the segues leading in and out of it, and then programmatically load that view controller from the Storyboard with instantiateViewController(withIdentifier:).
You could then pull a given scene into its own separate Storyboard (select the scene, Editor, Refactor to Storyboard...) so that it's completely independent of all the other scenes. Then you can rework that one view controller as programmatic code without interrupting anything else in the project. You can also mix-and-match here; loading the view controller from the Storyboard, but handling initialization by hand in init(nibName:bundle:).
You can also cut and paste a storyboard view into its own XIB file, which sometimes is easier to work with, without giving up the GUI layout system. Select the view, copy, create a new XIB file (it's called "View" in the templates), and paste it. This is a bit easier to use with init(nibName:bundle:).
But the key point is to not try to rework your entire project all at once. If there's some portion where Storyboards are causing trouble, refactor them out. If you just like programmatic code better, then keep refactoring one step at a time until you're done or until you've done enough to be happy.
As Senseful's answer notes, the top-level scene is slightly special. You can change it with or without changing the other scenes.

How to set initial storyboard

I have two storyboards one of them is default called Main and the other one I have just added is called Admin.
Main is used for customer, Admin is used for owner of the app.
I wonder how do you set initial/main interface storyboard programmatically.
P.S. I know how to change it via xcode but do not know programatically.
You do not set an initial storyboard programmatically.
Here's how it works. Either you have a main storyboard listed in Xcode under Main Interface or you don't:
If you do, that is the initial storyboard, period. That storyboard is loaded automatically at launch time, and its initial view controller becomes the window's root view controller (and the interface is then displayed automatically).
If you don't (that is, if the Main Interface field is empty), then nothing happens automatically. It is up to your code to obtain a view controller, somehow (possibly from a storyboard), and make its the window's root view controller (and to show the interface).
So, to sum up, either everything happens automatically or nothing happens automatically. There is no intermediate state, as you seem to imagine, in which you can programmatically change things so that a different storyboard is loaded automatically.
There is, however, a kind of intermediate state where you permit the Main Interface storyboard to load but then you ignore it. In your implementation of application:didFinishLoading..., you would then sometimes do the thing I said in the second bullet point, i.e. load a different view controller and make it the root view controller. This works because the automatic stuff I mentioned in the first bullet point has already happened by the time application:didFinishLoading... is called. So, in effect, your code sometimes permits that automatic stuff and sometimes overrides it.
In this example from my own code, we either use the Main Interface storyboard to load the initial view controller or we load a different view controller from the storyboard ourselves, depending on a value in User Defaults:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if let rvc = self.window?.rootViewController {
if NSUserDefaults.standardUserDefaults().objectForKey("username") as? String != nil {
self.window!.rootViewController = rvc.storyboard!.instantiateViewControllerWithIdentifier("root")
}
}
return true
}
You can set your storyboard programmatically like this in the app delegate:
window = UIWindow(frame: UIScreen.main.bounds)
// Or "Admin"
window!.rootViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController()!
window!.makeKeyAndVisible()
You could actually set any view controller. That's how no-storyboard approach is wired up.
Oh, another advantage of doing it in code is it delays the storyboard initialization. If you use the "Main Interface" settings in Xcode, the storyboard is actually initialized BEFORE the application:didFinishLaunchingWithOptions method.

iOS programming: Class and Instance

I think I might ask a fool question but it does confuse me for a while.
I am a beginner for iOS developing and the example I have seen online that people always write code inside the viewController's class.
However, according to my experience in C++, I think a class is just a template for reuse. You can only use it when it has been initialized.
So the thing that performs the work is the instance.
My question would be when/who create the viewController instance in an app?
I imagine you are referring to the template ViewController, the one that always comes with a new single page application project, for instance.
It's being created "under the hood" once your app finishes launching, but what it is basically doing is the following:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame: UIScreen.main.bounds)
window?.makeKeyAndVisible()
window?.rootViewController = ViewController();
return true
}
In fact, that's what you need to do if you want to delete your storyboard and work fully programmatically, in addition to clearing the Main Interface info in you project's general info as follows:
And if you want to show another custom ViewController class, you can present it from another ViewController as in
let secondViewController = MyCustomViewController()
// this line will place the MyCustomViewController instance on top of the current ViewController
present(secondViewController, animated: true, completion: nil)
I hope I could help!

UISplitViewController: Wrong top layout guide in master view with UISplitViewControllerDisplayModeAllVisible

I have a standard split view controller that I want to always show it's master view on iPads.
In the master view's viewDidLoad, I'm calling:
self.splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
With this one line in place, I'm getting my desired effect (always shows the master). However, a strange thing also happens: The top layout guide appears to move to the top of the master view, under the navigation bar.
You can see the effect in this picture, where the first cell in the table view is partially hidden behind the navigation bar. In fact, there is a green view above it (it's a placeholder for something that will be there soon) that is totally hidden.
If I rotate the device to landscape then back to portrait, autolayout works as expected, and the views appear in the correct place:
I've tried the following in both viewDidLoad and viewDidAppear to try to force the views to lay out properly, but it's had no effect:
[self.splitViewController.view setNeedsLayout];
[self.splitViewController.view layoutIfNeeded];
I'm looking for any solutions/suggestions.
I want to answer my own question with a hindsight point of view.
Apple mentioned in its documentation:
When building your app’s user interface, the split view controller is typically the root view controller of your app’s window.
And later warns:
You cannot push a split view controller onto a navigation stack. Although it is possible to install a split view controller as a child in some other container view controllers, doing is not recommended in most cases. Split view controllers are normally installed at the root of your app’s window. For tips and guidance about ways to implement your interface, see iOS Human Interface Guidelines.
Although they're not outright saying never to use UISplitViewController as a non-root view controller, I have found that UISplitViewController behaves unreliably when used in a non-root manner. The bug mentioned in the question is just one of many other problems you will encounter, the worse of which (in my experience) is not propagating-viewWillAppear or -viewDidAppear calls to child view controllers.
If you want a non-root split view, I suggest roll your own custom split view.
The only thing that's worked for me is this:
https://stackoverflow.com/a/22084634/1919412
In my case, the issue only shows up the first time the user rotates from landscape to portrait mode (and without manually revealing the master pane first). So even though Rivera's workaround causes a jarring visual blip, it only has to happen at most once per launch.
I recently ran into the same issue and solved it by setting the preferredDisplayMode on launch like so:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
...
if let splitController = self.window?.rootViewController as? UISplitViewController {
splitController.preferredDisplayMode = .allVisible
}
...
return true
}
Well:
I was facing the same problem (I guess).
But If I call:
.preferredDisplayMode=UISplitViewControllerDisplayModePrimaryOverlay
It worked like a charm.

Swift: How should the App Delegate get a reference to the View Controller?

I've started a new Swift project, I'm playing around with things to see how the wiring works with storyboards since I've never used them before.
The project is a single-view app using the default storyboard created by Xcode 6.1. It generates the AppDelegate.swift and ViewController.swift classes as well as Main.storyboard.
Btw, I'm kind of going off of this tutorial:
http://www.raywenderlich.com/74904/swift-tutorial-part-2-simple-ios-app
I've got things working with a button and a couple of textview controls that I added using the storyboard Interface Builder.
What I'd like to do now, is hook up the app delegate's application didFinishLaunching event to the view controller.
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[NSObject: AnyObject]?) -> Bool {
}
I've found many StackOverflow articles discussing this, however the examples are all around instantiating your own view controller. I want to simply get a reference to the view controller that was launched via the storyboard.
What's the best way to do this? Feel free to point me to the appropriate docs, or other posts.
I finally found this article on checking the current view controller, which had the logic I was looking for:
var myViewController : ViewController!
...
if let viewControllers = self.window?.rootViewController?.childViewControllers {
for viewController in viewControllers {
if viewController.isKindOfClass(ViewController) {
myViewController = viewController as ViewController
println("Found the view controller")
}
}
}

Resources