I've looked at a few examples but I haven't been able to get things to work properly yet. I am in a large code base that is a universal app. I am bringing in a .xib like so:
TestView *newView =
[[TestView alloc] init];
[self presentViewController:newView animated:YES completion:nil];
That loads the .xib just fine on the iPhone side of things, but in the iPad sim the .xib covers the whole screen. How can I make it to only cover the details view? I understand that in the appdelegate it makes two view controllers (i.e. the splitviewcontroller) but I have no option to do something like
[detailViewController presentViewController:newView animated:YES completion:nil];
You have to create an instance of a UISplitViewController in the AppDelegate. The UISplitViewController is only availabe on the iPad, so you have to enclose this in an if-statement
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
UIViewController *master = [self buildMaster];
UIViewController *detail = [self buildDetail];
UISplitViewController *splitViewController = [[UISplitViewController alloc] init];
splitViewController.viewControllers = #[master, detail];
[self.window setRootViewController:splitViewController];
}
Related
I'm want to make an app that uses a UISplitViewControler on the iPad (as far as I understand it's only available on the iPad) but I want the app to be universal.
The setup is like this:
I have a UITableView (as a master view) and when I select a row it should display a detail view of that cell. I am using a Storyboard and I can't figure out how to implement the split view only for the iPad.
What would be the easiest way to achieve that? Thanks.
You don't need two storyboards to do this.You can use them both in a single storyboard.For iphone ,we normally use a class SWRevealViewController(if you are new to iOS coding ..:)) for side menu and splitviewcontroller for ipad.We can also use SWRevealViewController for ipad as well.It depends on your requirement.
For universal apps,create viewcontrollers using size Classes(usually we use any height any width for universal apps ).
change these size classes and create different viewcontrollers for ipad and iphones as required.In most cases any height any width will do the job.
After creating the viewcontrollers,in the appdelegate ,using the instantiateViewcontrollerWithIdentifier method, load the required viewcontroller.
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
// The device is an iPad running ios 3.2 or later.
}
else {
// The device is an iPhone or iPod touch.
}
For ipad load the splitviewcontroller. and swrevealviewcontroller for iPhone.
This is the core basics.If you need any more information,let me know.
EDIT
Have you seen an arrowmark ath the initial VC(viewcontroller) in the storyboard?This vc is loaded first after the launch screen.In my app,I have a home screen which is common to both iphone and ipad(using size classes as mentioned above).So I can set this vc as the initial VC.In this case I don't have to do anything in the appdelegate.But if I have a different home screen for ipad,then I can make a condition check in the appdelegate didFinishLaunchingWithOptions
You can load the First screen like this.You should follow through splitVC tutorilal and swrevealcontroller tutorial to set the side menu.You should load the SWrevealVC or splitViewcontroller only if the first screen contains the side menu.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UISplitViewController *split = [storyboard instantiateViewControllerWithIdentifier:#"SplitViewController"];
[AppDelegate setRootController:split storyboard:storyboard actiontype:0];
}
else if(UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *split = [storyboard instantiateViewControllerWithIdentifier:#"SWrevealVC"];
[AppDelegate setRootController:split storyboard:storyboard actiontype:-1];
}
return YES;
}
+(void)setRootController:(UIViewController*)controller
storyboard:(UIStoryboard*)storyboard actiontype:(int) actiontype;
{
if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad && actiontype == 0)
{
UISplitViewController *splitViewController = (UISplitViewController *)controller;
//splitViewController.presentsWithGesture = false;
UINavigationController *masterNavigationController = [splitViewController.viewControllers objectAtIndex:0];
SideMenuViewController *controller = (SideMenuViewController *)masterNavigationController.topViewController;
controller.splitViewController = splitViewController;
splitViewController.delegate = (id)controller;
}
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[UIView
transitionWithView:appDelegate.window
duration:0.5
options:UIViewAnimationOptionAllowAnimatedContent
animations:^(void) {
BOOL oldState = [UIView areAnimationsEnabled];
[UIView setAnimationsEnabled:NO];
appDelegate.window.rootViewController = controller;
[UIView setAnimationsEnabled:oldState];
}
completion:nil];
}
The code may look lengthy,but take it simple.You can only understand the logic if u do things.
I would like to create an application like this:
On iphone (both portrait and landscape) and ipad portrait, I have a table view screen, tap on item row will navigate to another detail screen look like other basic application.
But when I rotate screen to go to landscape on ipad, the screen now has two section views
Here is what I did:
Write a method isInLandscapeTablet to detect ipad landscape
Use UINavigationController as a root controller to control all other views
In portrait screen, push a viewcontroller contains tableview to root controller
In landscape tablet screen, attach tableview controller and detail controller to UISplitViewController, then push it into root controller
But the problem is I can't push UISplitViewController to root controller, as it requires to be a root controller.
I wonder how I can handle this problem
And is my approach correct? Is there any other way?
Update: I change the root view controller like this
// this snippet is in UINavigationController (I use as root viewcontroller)
if([self isInTabletLandscape]){
self.splitViewController.viewControllers = [NSArray arrayWithObjects:[[CategoryViewController alloc] initWithNibName:#"CategoryViewController" bundle:nil], self.propertyLandViewController, nil];
[[UIApplication sharedApplication].keyWindow setRootViewController:self.splitViewController];
}else{
// it doesn't work
[[UIApplication sharedApplication].keyWindow setRootViewController:self];
}
}
After knowing the device whether it is iPad or iPhone. You Can try to remove the RootViewController.
appDelegate.window.rootViewController = nil;
Then you set the root view controller with a new SplitViewContloller
id objClass =[[SplitViewController alloc]initWithNibName:#"SplitViewController" bundle:nil];
masterVC.delegate = detailVC;
detailVC.delegate = objClass;
[objClass setViewControllers:#[masterNavigate,detailNavigate]];
[appDelegate.window setRootViewController:objClass];
My suggestion is not to use Split View Controller at all. Create a custom View Controller, which will embed your table view controller and the 2nd controller. Also, you can implement the interface-rotation logic in the custom controller you create.
If you are developing on iOS 8 you should use Size Classes, so you can totally change the layout depending on iPhone/iPad portrait and iPad Landscape. Unfortunately on iOS 7, size classes only differentiate iPhone and iPad.
In both case the right part (2), can be easily handle with a containerView.
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/SplitViewControllers.html
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
MyFirstViewController* firstVC = [[MyFirstViewController alloc] init];
MySecondViewController* secondVC = [[MySecondViewController alloc] init];
if ( ([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortrait) ){
UISplitViewController* splitVC = [[UISplitViewController alloc] init];
splitVC.viewControllers = [NSArray arrayWithObjects:firstVC, secondVC, nil];
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
window.rootViewController = splitVC;
[window makeKeyAndVisible];
}
else
{
// Display tableview
}
return YES;
}
I assume this may help you..
In Storyboard I have a TabBarController set as the initial view controller which connects ("view controller" relationship) to a Navigation Controller, which in turn connects to a View controller (iphone5VC).
How is it possible to programmatically change the view controller iphone5VC to iphone4VC? I have to decide which of iphone5VC or iphone4VC I will display depending on the phone size (iphone4, 5)
Thanks a lot to both of you. I finally decided to have only one Storyboard and use specific viewcontrollers on an adhoc basis when the 3.5 screen really needs to have a slightly different layout.
What I did is:
Added <UITabBarControllerDelegate> in the viewcontroller .h file from which the user presses on the TabBar to select the view.
Added in the viewdidLoad of the .m file:
UITabBarController *tbc = self.tabBarController;
[tbc setDelegate:self];
and then added in the same file:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
UIStoryboard *myStoryBoard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
CGSize result = [[UIScreen mainScreen] bounds].size;
if (tabBarController.selectedIndex == 1) // button # 2 pressed
{
if (result.height == IPHONE4_HEIGHT)
{
navController = (UINavigationController *) [myStoryBoard instantiateViewControllerWithIdentifier:#"ViewControlleriphone4"];
[self presentViewController:navController animated:NO completion:nil];
}
else
{
navController = (UINavigationController *) [myStoryBoard instantiateViewControllerWithIdentifier:#"ViewControlleriphone5"];
[self presentViewController:navController animated:NO completion:nil];
}
}
}
If I'm reading your question correctly, you want to include a "check" in your app where the result will set the size of your view controllers, and I'm assuming the layout of the views contained within, based upon which phone the user has. If I have this correct, check out this thread. Also, I would strongly recommend using Auto Layout, which will automatically place the views inside your view controller, regardless of screen size and layout (portrait/landscape).
If I'm not understanding your question, maybe paste a screenshot or some code. Hope this helps, good luck!
So if i am understanding you correctly, you really want to load a separate storyboard (and associated viewController)depending on the device type.
if so, what you need to do is have your main storyboard only contain the initial TabBarController, Navigation Controller, and a subclass of UIViewController we'll call dynamicViewController. The dynamicViewController will load the appropriate storyboard based on the device type. Obviously the various storyboards will need to exist (in the code below, the storyboards are named iphoneV4.storyboard and iphoneV5.storyboards)
So your dynamicViewController, simply needs the following -(void)viewDidLoad method;
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController __unused * targetViewController = nil;
CGRect screenBounds = [[UIScreen mainScreen] bounds];
UIStoryboard *targetStoryboard = (screenBounds.size.height == 568) ? [UIStoryboard storyboardWithName:#"iphoneV5" bundle:nil] : [UIStoryboard storyboardWithName:#"iphoneV4" bundle:nil];
targetViewController = [targetStoryboard instantiateInitialViewController];
if (self.parentViewController && ![self.parentViewController isKindOfClass:UITabBarController.class] && ![self.parentViewController isKindOfClass:UINavigationController.class]) {
// replace self with the target view controller
[self.parentViewController addChildViewController:targetViewController];
[self.view.superview insertSubview:targetViewController.view aboveSubview:self.view];
[self.view removeFromSuperview];
[self removeFromParentViewController];
} else { // tab bars, nav controllers, and modal dialogs
[self addChildViewController:targetViewController];
CGRect f = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
targetViewController.view.frame = f;
[self.view addSubview:targetViewController.view];
}
}
I am using the following code to dismiss modal view controllers:
- (IBAction)done {
#ifdef __IPHONE_5_0
if ([self respondsToSelector:#selector(presentingViewController)])
[self.presentingViewController dismissModalViewControllerAnimated:YES];
else
#endif
[self.parentViewController dismissModalViewControllerAnimated:YES];
}
If I runt he simulator using iOS 4.3 iPad, it uses self.parentViewController and works fine. However, when I runt he simulator using iOS 6.0 iPad the simulator crashes right after the view is dismissed using self.presentingViewController.
I do not have an actual iPad to test on... any ideas?
EDIT:
below is the code that creates the modal view controller.
NSArray* errors = [[NSArray alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Errors" ofType:#"plist"]];
UIViewController* vc;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
DocumentsViewController_iPad* docsVC = [[DocumentsViewController_iPad alloc] initWithNibName:#"DocumentsViewController-iPad" bundle:nil];
docsVC.documents = errors;
docsVC.errors = YES;
docsVC.navTitle = #"Troubleshooting";
vc = docsVC;
} else {
DocumentsViewController* docsVC = [[DocumentsViewController alloc] initWithNibName:nil bundle:nil];
docsVC.documents = errors;
docsVC.errors = YES;
docsVC.navTitle = #"Troubleshooting";
vc = docsVC;
}
vc.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:vc animated:YES];
[vc release];
A few things:
Yes, you should use dismissViewControllerAnimated:completion: as #rdelmar said
You should call it on presentingViewController, not parent
You can skip going to the presenting controller and dismiss self, it will forward this message to the presenting controller if needed.
dismissModalViewControllerAnimated: is depreciated, use dismissViewControllerAnimated:completion: instead.
I have a view-based application with three xib files, each with its own view controllers. How do I change from one to another? I use this to move from xib 1 to xib 2, but when I use the same code to move from xib 2 to xib 1, i get a EXC_BAD_ACCESS on the [self presentModal....] line.
MapView *controller = [[MapView alloc] initWithNibName:#"MapView" bundle:nil];
controller.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:controller animated:YES];
How can I freely move from one xib to another?
What I think you are trying to do is is present a modal view and then dismiss it, right? If that is the case then you put the code below in the method that you use to dismiss it(e.g. -(IBAction)dissmissModalView)
[self.parentViewController dismissModalViewControllerAnimated:YES];
Hopefully that works. Let me know.
initWithNibName isn't really necessary... you can change that to nil.
So, here is the correct code (without animation):
MapView *mapView = [[MapView alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:mapView animated:NO];
You should not be receiving EXC_BAD_ACCESS when trying to go back to view 1 using present. If you cannot resolve it, just use this instead:
[self dismissModalViewControllerAnimated:YES];
The second view controller will disappear and the first view controller will be visible again.
Note that presenting modal view controllers like the other answers here will mean that you have an ever-accumulating stack of view controllers. Use the application long enough and it will crash.
Instead, you can swap out the view from the application's window. Here's one way of doing that:
Add a data member to your app delegate to store the current view:
#class MyAppDelegate : NSObject <...>
{
UIViewController* currentVC;
}
and add a message there to swap VCs:
-(void)setCurrentVC:(UIViewController*)newVC
{
if (newVC==currentVC) return;
if (currentVC!=nil)
[currentVC.view removeFromSuperview];
currentVC = newVC;
if (newVC!=nil)
[self.window addSubview:newVC.view];
}
and to swap from one screen to another:
MapView* mapView = [[MapView alloc] init];
[[[UIApplication shared] delegate] setCurrentVC:mapView];