Best way to switch between UISplitViewController and other view controllers? - iphone

I'm authoring an iPad app. One of the screens in the app is perfectly suited to using a UISplitViewController. However, the top level of the app is a main menu, which I don't want to use a UISplitViewController for. This presents a problem, because Apple state that:
UISplitViewController should be the top level view controller in the app, i.e. its view should be added as the subview of UIWindow
if used, UISplitViewController should be there for the lifetime of the app -- i.e. don't remove its view from UIWindow and put another in place, or vice versa
Having read around and experimented, it seems to only viable option to satisfy Apple's requirements and our own is to use modal dialogs. So our app has a UISplitViewController at the root level (i.e. its view added as the subview of UIWindow), and to show our main menu, we push it as a full-screen modal dialog onto the UISplitViewController. Then by dismissing the main menu view controller modal dialog, we can actually show our split view.
This strategy seems to work fine. But it begs the questions:
1) Is there any better way of structuring this, without modals, that also meets all the requirements mentioned? It seems a bit odd having the main UI appear by virtue of being pushed as a modal dialog. (Modals are supposed to be for focused user tasks.)
2) Am I at risk of app store rejection because of my approach? This modal strategy is probably 'misusing' modal dialogs, as per Apple's human interface guidelines. But what other choice have they given me? Would they know that I'm doing this, anyway?

I seriously didn't believe that this concept of having some UIViewController to show before UISplitViewController (login form for example) turns out to be so complicated, until I had to create that kind of view hiearchy.
My example is based on iOS 8 and XCode 6.0 (Swift), so I'm not sure if this problem existed before in a same way, or it's due to some new bugs introduced with iOS 8, but from all of the similar questions I found, I didn't see complete 'not very hacky' solution to this problem.
I'll guide you through some of the things I have tried before I ended up with a solution (at the end of this post). Each example is based on creating new project from Master-Detail template without CoreData enabled.
First try (modal segue to UISplitViewController):
create new UIViewController subclass (LoginViewController for example)
add new view controller in storyboard, set it as initial view controller (instead of UISplitViewController) and connect it to LoginViewController
add UIButton to LoginViewController and create modal segue from that button to UISplitViewController
move boilerplate setup code for UISplitViewController from AppDelegate's didFinishLaunchingWithOptions to LoginViewController's prepareForSegue
This almost worked. I say almost, because after the app is started with LoginViewController and you tap button and segue to UISplitViewController, there is a strange bug going on: showing and hiding master view controller on orientation change is no longer animated.
After some time struggling with this problem and without real solution, I thought that it's somehow connected with that weird rule that UISplitViewController must be rootViewController (and in this case it isn't, LoginViewController is) so I gave up from this not so perfect solution.
Second try (modal segue from UISplitViewController):
create new UIViewController subclass (LoginViewController for example)
add new view controller in storyboard, and connect it to LoginViewController (but this time leave UISplitViewController to be initial view controller)
create modal segue from UISplitViewController to LoginViewController
add UIButton to LoginViewController and create unwind segue from that button
Finally, add this code to AppDelegate's didFinishLaunchingWithOptions after boilerplate code for setting up UISplitViewController:
window?.makeKeyAndVisible()
splitViewController.performSegueWithIdentifier("segueToLogin", sender: self)
return true
or try with this code instead:
window?.makeKeyAndVisible()
let loginViewController = splitViewController.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController
splitViewController.presentViewController(loginViewController, animated: false, completion: nil)
return true
Both of these examples produce same several bad things:
console outputs: Unbalanced calls to begin/end appearance transitions for <UISplitViewController: 0x7fc8e872fc00>
UISplitViewController must be shown first before LoginViewController is segued modally (I would rather present only the login form so the user doesn't see UISplitViewController before logged in)
Unwind segue doesn't get called (this is totally other bug, and I'm not going into that story now)
Solution (update rootViewController)
The only way I found which works properly is if you change window's rootViewController on the fly:
Define Storyboard ID for LoginViewController and UISplitViewController,
and add some kind of loggedIn property to AppDelegate.
Based on this property, instantiate appropriate view controller and after that set it as rootViewController.
Do it without animation in didFinishLaunchingWithOptions but animated when called from the UI.
Here is sample code from AppDelegate:
var loggedIn = false
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
setupRootViewController(false)
return true
}
func setupRootViewController(animated: Bool) {
if let window = self.window {
var newRootViewController: UIViewController? = nil
var transition: UIViewAnimationOptions
// create and setup appropriate rootViewController
if !loggedIn {
let loginViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("LoginVC") as LoginViewController
newRootViewController = loginViewController
transition = .TransitionFlipFromLeft
} else {
let splitViewController = window.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("SplitVC") as UISplitViewController
let navigationController = splitViewController.viewControllers[splitViewController.viewControllers.count-1] as UINavigationController
navigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem()
splitViewController.delegate = self
let masterNavigationController = splitViewController.viewControllers[0] as UINavigationController
let controller = masterNavigationController.topViewController as MasterViewController
newRootViewController = splitViewController
transition = .TransitionFlipFromRight
}
// update app's rootViewController
if let rootVC = newRootViewController {
if animated {
UIView.transitionWithView(window, duration: 0.5, options: transition, animations: { () -> Void in
window.rootViewController = rootVC
}, completion: nil)
} else {
window.rootViewController = rootVC
}
}
}
}
And this is sample code from LoginViewController:
#IBAction func login(sender: UIButton) {
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
delegate.loggedIn = true
delegate.setupRootViewController(true)
}
I would also like to hear if there is some better/cleaner way for this to work properly in iOS 8.

Touche! Ran in to the same issue and solved it the same way using modals. In my case it was a login view and then the main menu as well to be shown before the splitview. I used the same strategy as thought out by you. I (as well as several other knowledgeable iOS folks I spoke to) could not find a better way out. Works fine for me. User never notices the modal anyway. Present them so. And yes I can also tell you that there are quite a few apps doing the same under the hood tricks on the App store. :) On another note, do let me know if you figure a better way out somehow someway sometime :)

And who said you can have only one window ? :)
See if my answer on this similar question can help.
This approach is working very well for me. As long as you don't have to worry about multiple displays or state restoration, this linked code should be enough to do what you need: you don't have to make your logic look backwards or rewrite existing code, and can still take advantage of the UISplitView in a deeper level within your application - without (AFAIK) breaking Apple guidelines.

For future iOS developers running into the same issue: here's another answer and explanations. You HAVE to make it root view controller. If it is not, overlay a modal.
UISplitviewcontroller not as a rootview controller

Just ran into this problem on a project and thought I'd share my solution. In our case (for iPad), we wanted to start with a UISplitViewController with both view controllers visible (using preferredDisplayMode = .allVisible). At some point in the detail (right) hierarchy (we had a navigation controller for this side, too) we wanted to push a new view controller over the entire split view controller (not use a modal transition).
On iPhone, this behavior comes for freeā€”as only one view controller is visible at any time. But on iPad we had to figure something else out. We ended up going with a root container view controller that adds the split view controller to it as a child view controller. This root view controller is embedded in a navigation controller. When the detail view controller in the split view controller wants to push a new controller over the entire split view controller, the root view controller pushes this new view controller with its navigation controller.

I'd like to contribute my approach to presenting a UISplitViewController, as you might like to via -presentViewController:animated:completion: (we all know that won't work though).
I created a UISplitViewController subclass which responds to:
-presentAsRootViewController
-returnToPreviousViewController
The class, which like other successful approaches, sets the UISplitViewController as the window's rootViewController but does so with an animation similar to what you get (by default) with -presentViewController:animated:completion:
PresentableSplitViewController.h
#import <UIKit/UIKit.h>
#interface PresentableSplitViewController : UISplitViewController
- (void) presentAsRootViewController;
#end
PresentableSplitViewController.m
#import "PresentableSplitViewController.h"
#interface PresentableSplitViewController ()
#property (nonatomic, strong) UIViewController *previousViewController;
#end
#implementation PresentableSplitViewController
- (void) presentAsRootViewController {
UIWindow *window=[[[UIApplication sharedApplication] delegate] window];
_previousViewController=window.rootViewController;
UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES];
window.rootViewController = self;
[window insertSubview:windowSnapShot atIndex:0];
CGRect dstFrame=self.view.frame;
CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform);
offset.width*=self.view.frame.size.width;
offset.height*=self.view.frame.size.height;
self.view.frame=CGRectOffset(self.view.frame, offset.width, offset.height);
[UIView animateWithDuration:0.5
delay:0.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
self.view.frame=dstFrame;
} completion:^(BOOL finished) {
[windowSnapShot removeFromSuperview];
}];
}
- (void) returnToPreviousViewController {
if(_previousViewController) {
UIWindow *window=[[[UIApplication sharedApplication] delegate] window];
UIView *windowSnapShot = [window snapshotViewAfterScreenUpdates:YES];
window.rootViewController = _previousViewController;
[window addSubview:windowSnapShot];
CGSize offset=CGSizeApplyAffineTransform(CGSizeMake(0, 1), window.rootViewController.view.transform);
offset.width*=windowSnapShot.frame.size.width;
offset.height*=windowSnapShot.frame.size.height;
CGRect dstFrame=CGRectOffset(windowSnapShot.frame, offset.width, offset.height);
[UIView animateWithDuration:0.5
delay:0.0
usingSpringWithDamping:1.0
initialSpringVelocity:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
windowSnapShot.frame=dstFrame;
} completion:^(BOOL finished) {
[windowSnapShot removeFromSuperview];
_previousViewController=nil;
}];
}
}
#end

I did a UISplitView as initial view, than it goes modally to a fullscreen UIView and back to UISplitView. If you need to go back to the SplitView you have to use a custom segue.
Read this link (translate it from japanese)
UIViewController to UISplitViewController

Adding to the answer of #tadija I am in a similar situation:
My app was for phones only, and I am adding a tablet UI. I decided doing it in Swift in the same app - and eventually migrate all the app to use the same storyboard (when I feel the IPad version is stable, using it for phones should be trivial with the new classes from XCode6).
No segues were defined in my scene yet and it still works.
My the code in my app delegate is in ObjectiveC, and is slightly different - but uses the same idea.
Note that I am using the default view controller from the scene, unlike previous examples. I feel this will also work on IOS7/IPhone in which the runtime will generate a regular UINavigationController instead of a UISplitViewController. I might even add new code which will push the login view controller on IPhones, instead of changing the rootVC.
- (void) setupRootViewController:(BOOL) animated {
UIViewController *newController = nil;
UIStoryboard *board = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
UIViewAnimationOptions transition = UIViewAnimationOptionTransitionCrossDissolve;
if (!loggedIn) {
newController = [board instantiateViewControllerWithIdentifier:#"LoginViewController"];
} else {
newController = [board instantiateInitialViewController];
}
if (animated) {
[UIView transitionWithView: self.window duration:0.5 options:transition animations:^{
self.window.rootViewController = newController;
NSLog(#"setup root view controller animated");
} completion:^(BOOL finished) {
NSLog(#"setup root view controller finished");
}];
} else {
self.window.rootViewController = newController;
}
}

Another option: In the details view controller I display a modal view controller:
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
if (!appDelegate.loggedIn) {
// display the login form
let storyboard = UIStoryboard(name: "Storyboard", bundle: nil)
let login = storyboard.instantiateViewControllerWithIdentifier("LoginViewController") as UIViewController
self.presentViewController(login, animated: false, completion: { () -> Void in
// user logged in and is valid now
self.updateDisplay()
})
} else {
updateDisplay()
}
Don't dismiss the login controller without setting the login flag. Note that in IPhones the master view controller will come first, so a very similar code will need to be on the master view controller.

Related

React Native Push rather than Present native iOS view

I want to display a native view controller (ABPersonViewController) within a react native application.
I can present the view but I cannot push the view. This code fragment works and presents the view but there is no back button to my app:
let personViewController = ABPersonViewController()
let rootViewController:UIViewController?
= UIApplication.shared.delegate?.window??.rootViewController!
personViewController.displayedPerson = record!
// this works
rootViewController!.present(personViewController, animated: true)
If I try to push the view rather than present the view, nothing happens because navigationController is nil:
if (rootViewController!.navigationController != nil) {
rootViewController!.navigationController!.pushViewController(personViewController, animated: true)
}
How can I push a native view within react native iOS?
In React native android, view contacts can easily be done using this code in a custom module:
final Activity activity = getCurrentActivity();
Intent contactIntent = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, id);
contactIntent.setData(uri);
activity.startActivity(contactIntent);
I wish it was that easy in react native iOS!
I tried #Artal's solution for iOS and it works up to a point.
I allocated a UINavigationController that used the existing RN UIViewController as its root and set it as the rootViewController of the window. Here is the code:
UIViewController *rootViewController = [UIViewController new];
UINavigationController *navigationController = [[UINavigationController alloc]initWithRootViewController:rootViewController];
navigationController.navigationBarHidden = YES;
rootViewController.view = rootView;
self.window.rootViewController = navigationController;
In my swift code I then cast the rootViewController to UINavigationController, set isNavigationBarHidden to false and pushed the personViewController
let navigationController:UINavigationController = rootViewController as! UINavigationController;
DispatchQueue.main.async {
navigationController.isNavigationBarHidden = false;
navigationController.pushViewController(personViewController, animated: true)
}
The problem came when I overrode the UINavigationController ShouldPopOnBackButton in order to set navigationBarHidden to true when I returned to RN. I followed the approach in https://stackoverflow.com/a/19132881/826435
#implementation UINavigationController (ShouldPopOnBackButton)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
if([self.viewControllers count] < [navigationBar.items count]) {
return YES;
}
dispatch_async(dispatch_get_main_queue(), ^{
self.navigationBarHidden = YES;
[self popViewControllerAnimated:YES];
});
return NO;
}
#end
AftershouldPopItem is called, the RN view is restored but the RN keyboard is then subsequently disabled for all input fields when running on the simulator; it works fine on a device though.
Any ideas why the RN keyboard on the simulator is disabled?
James
You can't push a view controller because the default rootViewController of a RN app is a UIViewController and not a UINavigationController, and that's also why you see that it's nil.
If you want to use a native navigation stack and push another native screen you have two options:
Use a native navigation solution such as RNN. The downside of this approach is that it will require changing the way you currently handle navigation completely. Note: although RN comes with an out-of-the-box solution for native navigation (NavigatorIOS), it won't allow you to push other native screens. You might be able to hack it by accessing some private properties but i'm not sure that you want to go there.
Change the root of your app in your AppDelegate. You can allocate a UINavigationController that will use the existing RN UIViewController as its root and set it as the rootViewController of your window. Note that this kind of change might have other implications on your app; for example: you will get the UINavigationController native navigation-bar and possible some other side effects.
Maybe you can pass Objective-C navigationController to Swift method.
(UINavigationController *)[UIApplication sharedApplication].keyWindow.rootViewController can convert to navigationController in React Native.
BUT
UIApplication.shared.keyWindow?.rootViewController as? UINavigationController
The solution (2) suggested by #Artal does work on a device but on a simulator the keyboard is disabled after returning from the native view.

Custom segue to push a specific UIViewController to UINavigationController

I want to create a custom segue that acts in the same way as the standard push segue does when used on UINavigationController view controllers. I've implemented my custom segue:
CustomSegue.m
-(void)perform {
UIViewController *source = (UIViewController *)[self sourceViewController];
UIViewController *dest =(UIViewController *)[self destinationViewController];
if (1==2 //test) {
[source.navigationController pushViewController:dest animated:YES];
}
else {
UIViewController *altDest = [[UIStoryboard storyboardWithName:#"MainStoryboard" bundle:NULL]
instantiateViewControllerWithIdentifier:#"alternateView"];
[source.navigationController pushViewController:altDest animated:YES];
}
As you can see, the reason I want to use a custom push segue is so that I can decide which view controller to push based on the user configuration (currently only checking a trivial 1==2 expression). I can instantiate the alternate view controller with no issue, but what I want to be able to do is go back and forth without reloading the view controller each time (using the back and next buttons). Is there a way to retrieve an existing instance from the storyboard, or some standard way of doing this?
Instead of a custom segue with its perform, the way to do what you describe, i.e. choose in real time whether to push dest or altDest, is either (1) do not use segues at all and just call pushViewController directly as you are doing here, or (2) prepare two segues emanating from the view controller as a whole, and call performSegueWithIdentifier: to say which we should perform.
As for going directly from dest to altDest, you can push altDest on top of dest and then remove dest from the stack of the navigation controller's view controllers.
Like so much about about iOS, this is all so much easier and more obvious if you do not use a storyboard at all. This is why I don't like storyboards: they are so simple-minded and limiting, and they distract one's attention from the way iOS really works.
There is no way to retrieve an existing controller from a storyboard -- it would be nice if there were a controllerWithIdentifier: method to do that, but there isn't. Segues (other than unwinds) always instantiate new controllers, so I don't think you can do what you want with a segue. If you want to be going forward (push) to the same controller multiple times, then you need to do it in code by creating a property that points to your controller, and checking if that controller exists before pushing to it.
As the others have pointed out, you can't use a segue to push to an existing instance of a controller. The process of performing a segue always creates a new instance the destination controller for you.
Personally, when I'm jumping between existing instances of view controllers, I think "container view controller", such as a UIPageViewController, which makes it really easy to transition between two or more controllers, without necessarily reinstantiating them every time.
If you don't like the constraints the page view controller imposes (e.g. maybe you don't like the fact that iOS 5 version only supports page curl transitions, or that iOS 6 only adds the scroll transition, and you want something else), then you'd do a custom container view controller.
For example, if I wanted to jump between two view controllers and not reinstantiate them every time, I'd first create a custom container view controller, the "parent", and make sure I have a property to keep track of which child I'm currently at:
#property (nonatomic) NSInteger childViewIndex;
If supporting iOS 6.0 and above only, I'd then add a "container view" to my parent view controller's scene. If supporting iOS versions prior to 6.0, I'd add a standard UIView to the scene and then manually instantiate the first child controller:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
UIViewController *controller;
// add the first child
UIViewController *controller = [self addChildWithIdentifier:#"One"];
[self.containerView addSubview:controller.view];
[controller didMoveToParentViewController:self];
self.childViewIndex = 0;
}
- (UIViewController *)addChildWithIdentifier:(NSString *)storyboardIdentifier
{
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:storyboardIdentifier];
[self addChildViewController:controller];
controller.view.frame = self.containerView.bounds;
controller.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
return controller;
}
Then, when I want to transition to the second child (or transition back to the first child), I'd call the following routine in the parent view controller:
- (void)transitionToViewControllerIndex:(NSInteger)index
{
// don't do anything if we're trying to transition to ourselves!
if (index == self.childViewIndex)
return;
// identify the two controllers in question
UIViewController *sourceController = self.childViewControllers[self.childViewIndex];
UIViewController *destinationController;
// if we're asking for page 2, but we only have one defined, then we'll have to instantiate it
BOOL instantiateDestination = (index == 1 && [self.childViewControllers count] < 2);
if (instantiateDestination)
destinationController = [self addChildWithIdentifier:#"Two"];
else
destinationController = self.childViewControllers[index];
// configure the destination controller's frame
destinationController.view.frame = sourceController.view.frame;
// if you're jumping back and forth, set the animation appropriate for the
// direction we're going
UIViewAnimationOptions options;
if (index > self.childViewIndex)
{
options = UIViewAnimationOptionTransitionFlipFromRight;
}
else
{
options = UIViewAnimationOptionTransitionFlipFromLeft;
}
// now transition to that destination controller
[self transitionFromViewController:sourceController
toViewController:destinationController
duration:0.5
options:options
animations:^{
// for simple flip, you don't need anything here,
// but docs say this can't be NULL; if you wanted
// to do some other, custom annotation, you'd do it here
}
completion:^(BOOL finished) {
if (instantiateDestination)
[destinationController didMoveToParentViewController:self];
}];
self.childViewIndex = index;
}
Thus, to transition to the second child view controller, you could simply call:
[self transitionToViewControllerIndex:1];
If you want to transition back, you could call:
[self transitionToViewControllerIndex:0];
I'm only scratching the surface here, but container view controllers (or if none of the standard ones do the job for you, a custom container view controller) is precisely what you need.
For more information, see:
Creating Custom Container View Controllers in the View Controller Programming Guide for iOS.
Implementing UIViewController Containment in the WWDC 2011 Session Videos (Apple developer ID required).
Implementing a Container View Controller in the UIViewController Class Reference.
Page View Controllers in the View Controller Catalog for iOS.

How can I create one UIViewController with two views that display one or the other depending on button clicked

I'm quite new to iOS development and I am stuck. Currently I am using one tab controller to switch between two view controllers (list and map view). This made it easier to use storyboard to configure the look of the two views.
Now the requirements have changed and the app needs to have one view controller with a segmented control that on click, displays either the list or the map view. In order to do this, I would need to make one view controller that can display list/map view.
I understand how the segmented controller part works, but I'm just stuck on how I can go about having two views with one or the other displayed in the same area.
How can I go about having two views in one view controller (if possible, utilizing storyboard)?
Thanks in advance!
You should not have two main views in a single view controller, instead you need to create one view controller per view that you want to show. However you can certainly have multiple subviews in a single view controller, which may be what works for you.
There are a number of approaches to solve this the problem, the correct approach would be to create a container UIViewController, and add as its childs the 2 viewcontrollers you want to show, them simply set the view to the view controller you want to display, but that would probably be overly complicated since you mention you are new to iOS development.
Therefore an easy solution (not sure if you can implement this in storyboard - since I don't like it), would be to have a single view controller, with the tabs, and 2 subviews of the main view, then you can simply switch between views by doing something like this:
[self.view addSubview:view1];
//to switch
[view1 removeFromSuperview];
[self.view addSubView:view2];
alternatively, you do not really need to remove it from superview but just hide it, and then use bringSubViewToFront to show the view that you need.
If you want to use the other approach I would recommend looking for this video the WWDC 2011 video titled "Implementing UIViewController Containment". This other question should be useful to: UISegmented control with 2 views
Hope that helps.
Using storyboard api you can switch between 2 viewControllers
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController *viewController = [self viewControllerForSegmentIndex:self.typeSegmentedControl.selectedSegmentIndex];
[self addChildViewController:viewController];
viewController.view.frame = self.contentView.bounds;
[self.contentView addSubview:viewController.view];
self.currentViewController = viewController;
}
- (UIViewController *)viewControllerForSegmentIndex:(NSInteger)index {
UIViewController *viewController;
switch (index) {
case 0:
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"FirstViewController"];
break;
case 1:
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
break;
}
return viewController;
}
- (IBAction)segmentChanged:(UISegmentedControl *)sender {
UIViewController *viewController = [self viewControllerForSegmentIndex:sender.selectedSegmentIndex];
[self addChildViewController:viewController];
[self transitionFromViewController:self.currentViewController toViewController:viewController duration:0.0 options:UIViewAnimationOptionTransitionNone animations:^{
[self.currentViewController.view removeFromSuperview];
viewController.view.frame = self.contentView.bounds;
[self.contentView addSubview:viewController.view];
} completion:^(BOOL finished) {
[viewController didMoveToParentViewController:self];
[self.currentViewController removeFromParentViewController];
self.currentViewController = viewController ;
}];
self.navigationItem.title = viewController.title;
}
This is in reference to iOS tutorial by Raywenderlich. Hope this helps
With Storyboard it is possible in this way.
Create UIViewController with UISegmentControl and UITableView+UITableViewCell added to it.
Now you want to add MKMapView as well, hoverer, if you simply try to place the MapView on the ViewController, it will be added as new TableView cell, which is not what we want.
That's why you should not do it so. Instead, MapView has to be added to Storyboard's List of ViewControllers
Adjust the size and origin of MapView to be the same as TableView ones.
Now, setHidden to YES for either TableView of MapView, create and synthesize outlets for them. Then in Segment control Value Changed method implement switching:
- (IBAction)switchView:(id)sender {
self.theTableView.hidden = !self.theTableView.hidden;
self.theMapView.hidden = !self.theMapView.hidden;
if (!self.theTableView.hidden) {
[self.theTableView reloadData];
}
}

using the UIPageViewController in a storyboard

I'm looking to use the UIPageViewController with Xcode's storyboard. The only example I could find was this tutorial on how to implement UIPageViewcontroller using separate xib files.
However in this tutorial the UIPageViewController is instantiated programmatically within a class that extends UIViewController. This makes it complicated to translate in to how the storyboard interprets the UIPageViewcontroller (which is as an instance on its own).
Any help on how to create a functioning UIPageViewController with Xcode's storyboard will be much appreciated.
UPDATE: I managed to resolve this issue by making a new project in Xcode using the default pagecontroller template. It used the storyboard and was easy to follow.
UPDATE: I managed to resolve this issue by making a new project in xcode using the default pagecontroller template. It used the storyboard and was easy to follow.
Setting up the Xcode Project to be a PageViewController is one way of accomplishing this, however if you'd like to include a PageViewController within an already existing storyboard you can do this as well.
If you drag and drop a PageViewController Scene onto your storyboard and wire up a segue, you can push to that PageViewController. However there appears to be some bugs in the storyboard for setting up the PageViewController properly, for instance you cannot wire up the delegate and datasource.
An easy way around this is to simply wire the delegate/datasource up in your init method:
- (instancetype) initWithCoder:(NSCoder *)aDecoder
{
if(self = [super initWithCoder:aDecoder])
{
self.delegate = self;
self.dataSource = self;
}
return self;
}
This will cause your delegate and datasource methods to be called properly, assuming of course you want the datasource and delegates to be the PageViewController. Once this is set up you will need to ensure that there is a view controller when the view is loaded. You can do this with the setViewControllers method on PageViewController class in your viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
[self setViewControllers:#[sweetViewController] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:^(BOOL finished) {
NSLog(#"Hooray I set my initial viewcontroller for my page view controller");
}];
}
When the PageViewController is created, it will start with your sweetViewController, and then begin calling your datasource and delegate methods as needed.
There's an easy way to do this with Xcode 7. Have the ViewController that will be your datasource and delegate in one Storyboard Scene, with your UIPageViewController embedded into a ContainerView. Then capture the UIPageViewController in your ViewController's prepareForSegue Method.
e.g. in Swift:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier where identifier == "yourPageVCIdentifier"{
if let pageController = segue.destinationViewController as? UIPageViewController {
pageController.dataSource = self
pageController.delegate = self
self.pageVC = pageController
}
}
}

How to find root UIViewController

I have a view that is UIViewController (root) that handles ads and a UINavigationController. In that UINavigationController I have typical layers of controllers. In my custom settings table view controller, I have a button to contact me for support. When the user clicks this button I create a MFMailComposeViewController and would like to present it. I can't present it from the settings table view as it will be underneath my ads, so I need to reference the root view and present it from there. I've tried self.parentViewController.parentViewController where self is the settings table view, but that doesn't work. How should I reference this. It seems like a bad design to have to reference the root view directly and pass it to the settings view.
Get the current keyWindow:
UIWindow *window = [UIApplication sharedApplication].keyWindow;
Get its rootViewController:
UIViewController *rootViewController = window.rootViewController;
NOTE: If an UIAlertView is currently being shown, a new window is being created and assigned to be the keyWindow. There might be other exceptional cases as well that will not keep your application window to be the keyWindow.
In my experience, this is the ideal solution to get the rootViewController:
[[[[UIApplication sharedApplication] delegate] window] rootViewController]
Note: In-case of an active UIAlert or UIActionSheet, you get the Alert or Action Sheet as your key window.
Use the app singleton. Something like:
[[[UIApplication sharedApplication] delegate] rootViewController] should get it if your viewController that is the root is named rootViewController
You can always solve this with 1 line of code but I recommend this Swift way to do it, you can call this from anywhere, its also crash and bug safe:
/// EZSwiftExtensions - Gives you the VC on top so you can easily push your popups
var topMostVC: UIViewController? {
var presentedVC = UIApplication.sharedApplication().keyWindow?.rootViewController
while let pVC = presentedVC?.presentedViewController {
presentedVC = pVC
}
if presentedVC == nil {
print("EZSwiftExtensions Error: You don't have any views set. You may be calling them in viewDidLoad. Try viewDidAppear instead.")
}
return presentedVC
}
Its included as a standard function in:
https://github.com/goktugyil/EZSwiftExtensions
Get the UIApplication object.
Cycle through the windows array to find the keyWindow.
And then get the rootViewController property.