UPDATE
I ended up fixing the issues with MGSplitViewController, so I am now using this fork of the project: http://github.com/ArtSabintsev/MGSplitViewController At the time of writing this edit, the fork hasn't been pulled into Matt Gemmell's master branch.
Please Note
My iPad app is Landscape only, and is iOS5 and iOS6 compatible
I have a partially working solution, but I need to take it one step further.
For the majority of the screens in my app, I need to present both the master and detail views (normal behavior).
At one point, I need to only the detailVC to be present.
How am I doing it now?
I am using the following UISpliterViewControllerDelegate Method
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
return self.hideRootViewController;
}
where hideRootViewController is a boolean that is set to NO initially.
The Partial Solution
If I change the boolean to YES (in any screen/VC in my app) and rotate the screen, the masterVC disappears, and the detailVC occupies the entire screen.
The Remaining Problem
How do I force this change without physically having to rotate the device, or how do I force a device orientation notification change to redraw UISplitViewController?
The following attempts have failed:
CGAffineTansformation (Identity and small angle transformations)
Posting a UIDeviceOrientationDidChangeNotification
Calling setNeedsLayout on splitVC.view
Presenting and immediately dismissing modalVC's on splitVC
Note: I am not using MGSplitViewController, nor do I want to use that class in this project.
To manually force a rotation, you can use:
[[UIApplication sharedApplication] setStatusBarOrientation:]
Why dont you use mgsplitviewcontroller. I used it in my project, it works wonderful and very easy implement. You can able hide/switch master view easily.
Related
I am several levels deep in a storyboard and want to unroll everything all the way back to the first screen. Fortunately, there are APIs designed to do exactly this:
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
[topController dismissViewControllerAnimated:NO completion:nil];
However, this method seems to "unroll" the view stack one by one, causing each view between my current position and the first screen to briefly invoke viewWillAppear, viewDidAppear, viewWillDisappear, and viewDidDisappear. This causes a cacophony of activity in my app, as most of the intermediate screens do interesting things when they appear. I can set breakpoints in Xcode and watch these methods get invoked in reverse order back to the main screen.
I need a way to pop back to the start of the storyboard without causing every screen along the path to light up and do work.
If this means I cannot use viewWillAppear for this purpose any more, I am willing to switch to a replacement method as long as one exists.
This is expected behaviour. I assume you're presenting the layers of views with presentViewController... calls on each previous view.
You should look at using a UINavigationController as your top level view instead. Then you can just call popToRootViewControllerAnimated: when you want to go all the way back.
I need to know if there is a way to tell a iOS7 device to set a views orientation without the device being rotated. Some way in code to trigger the device to calling the code that tells it which way to display the view.
If the device is in landscape and remains held in landscape orientation while a certain change happens I want to force a change to show the view in portrait orientation, at which point the user would need to turn the device to look at it properly. I'll explain why below
Looking at my app might make my description clearer - it is free to download
I have a number of view controllers (embedded in navigationControllers) and only one of them needs to be rotated into landscape and then only under certain conditions.
Solutions here on StackOverflow seem to be to make a category on UINavigationController giving it shouldAutorotate and supportedInterfaceOrientations methods and then use those methods in the individual viewControllers to block or allow rotations.
This has worked for me .... however
On the one view controller I wish to rotate , I don't want it to rotate all the time.
This view controller is the diveSiteDetailsController, (if you have downloaded the app you need to select dive sites on the first page then click the '+' to see it). It has a UISegmentedController and 4 subviews (3 tableviews and 1 other UIView). The current version on the App Store works fine now i've solved this - but looking at it may help you understand my issue better).
On diveSiteDetailViewController the UISegmentedController is used to switch between the 4 subviews.
All the subviews are used to enter data about the same dive site but as there is a lot of potential data, I have broken it into logical chucks each of which is a subview - location, data (depths,currents, visibility), type of environment and notes.
The .hidden property of each subview is used to make them appear and disappear.
I only want the second subview to rotate (the data view - it has some sliders on it that are easier to work with if in landscape).
restricting this rotation is easy - iI achieved it like this
- (NSUInteger)supportedInterfaceOrientations{
if (self.dsDataRangeSlidingTV.hidden) {
return UIInterfaceOrientationMaskPortrait;
}
return UIInterfaceOrientationMaskAllButUpsideDown;
}
Now the view will only rotate to landscape when the data table view is displayed.
However, once in landscape, if I chose a different subview with the UISegmentedController then they are, obviously, shown in landscape also as the iOS device hasn't done a rotation. This is the situation I am trying to avoid.
Rotating the iOS device will return those views to portrait as expected but i need to trigger the device to to reevaluate its display when I use the UISegmentedController to switch from the data subview to another subview and its that triggering that I don't know how to do.
any suggestions greatly received.
Heres a workaround that is working for me
I've added the following few lines to the end of my method that responds to the UISegmentedControl being tapped.
UIViewController *aDummyController = [[UIViewController alloc]init];
[self presentViewController:aDummyController animated:NO completion:nil];
[self dismissViewControllerAnimated:NO completion:nil];
adding a new viewController and popping it off triggers the rotation . This is a kludgey way of achieving what I wanted.
I found the solution in this post
Is there a documented way to set the iPhone orientation?
all credit to Josh who although not the accepted answer is the one that 99 people currently have up voted.
I still have a bug in that, if I were holding the device in landscape (although the display is portrait view) whilst on the screen that segues into the diveSiteDetailsController then the initial view the diveSiteDetailsController display will be in landscape.
To get around this I created a Bool property called notThisTime on the diveSiteDetailsController and set it to true in the prepareFor Segue on the viewController that called it.
i then did changed supportedInterfaceOrientation to
- (NSUInteger)supportedInterfaceOrientations
{// DLog(#"Running %# '%#'", self.class, NSStringFromSelector(_cmd));
if (self.notThisTime){
return UIInterfaceOrientationMaskPortrait;
}
if (!self.dsDataRangeSlidingTV.hidden) {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
then at the end of the ViewDidLoad method I added
self.notThisTime = NO;
I would still love to hear from anyone with a suggestion how better to handle this. pushing and popping a dummy view to get the iPhone to do an orientation check seems like a work around for something that should just be available as a standard method call.
One final Note - the iOS simulator does not like this - you need to check on the device - it sometimes tries to draw the iPhone container in landscape while the screen is drawn vertically - however it does work fine on the iPhone
I have a strange problem when using a UIScrollView controller combined with iPhone 4 and iOS 5.1.
I have a UIScrollView which has a content size of 640x480 (double screen effectively) and in addition to the swipe to switch between the two "screens" I also permit the user to tap the screen in response to which I call something like...
[scrollView scrollRectToVisible:(CGRectMake 320,0,320,480) animated:YES];
the first 320 would be 0 if the tap occurred whilst the right hand side of the scroll view was displayed (note the scroll view has paging enabled so it can only come to rest either fully left or fully right).
I also have a situation where I sometimes display an additional view controller modally using presentModalViewController over this view controller containing the scroll view.
Everything works perfectly until the modal view controller is presented and subsequently dismissed after which the scrollRectToVisible method will no longer work if animated is set to YES (if I change animated to NO then it works as expected). Note, the tap is still being registered and the scrollRectToVisible being called, it just doesn't do anything when animated is set to YES).
Here's the kicker, this bug only occurs on an iPhone 4 runnings iOS 5.x.
It works perfectly (even after the modal view controller has been displayed) on my:
iPhone 3G running 4.x,
iPhone 3GS running 3.x,
iPod touch (2nd Gen) running 4.x
and most surprisingly the simulator running 5.x.
I wondered if this was a bug in the animation system so disabled the animation on the modal view controller presentation and dismiss and this had no effect, problem still occurred on the iPhone 4 with iOS 5.1.
Anyone got any ideas as to what might be causing this and how I might work around it?
Finally tracked this down. what a pig...
I'm embedding a view from a view controller as a subview of another view controllers view. So my scroll view contains a view which also has an associated view controller.
Prior to iOS 5.x the methods viewWillAppear, viewWillDisappear, viewDidAppear and viewWillDisappear are never called on the sub views view controllers, only the main view controller. Already knowing this I set up my main view controller to manually call the sub views view controllers methods when these events happen.
However it appears that in iOS 5.x this issue has been "fixed" so where I was manually passing the call to viewWillAppear to my sub view controller I no longer need do this under 5.x as the method automatically gets called under 5.x - as a result it's now being called twice under 5.x but still only once when running on a 4.x or earlier device.
As a result, under 5.x my NSTimer used to call my updateUI method is being created twice, but because in viewDidDisappear I only destroy the timer if it is non nil it only gets destroyed once - therefore I'm leaking NSTimers under 5.x through double allocation where I'm not under 4.x.
As a result of multiple NSTimers hanging around all repeatedly calling my updateUI method is that the constant updating of the UI is killing the animation system and so the animation for the scrollView fails when running on an actual device. I guess it continued working OK on the simulator running 5.x as the CPU in the Mac is more than capable of handling the extra workload and still performing the animations correctly.
A simple check in my viewWillAppear method to ensure the NSTimer hasn't already been created has fixed the problem and has kept compatibility with 4.x and earlier.
I frustratingly run up against these kinds of issues every time Apple update their iOS by major versions... The morale of this story is don't assume that long standing classes still exhibit the same behaviour under different revisions of the OS.
I had the same problem. I realized that after modalViewController is dismissed my UIScrollerView shifts downs by 20px, which is the same height as status bar. So, it means when my UIViewController is loaded and UIScrollView is created, UIScrollView thinks there is no status bar, when actually it is there.
So I tried to put in viewDidLoad:
[[UIApplication sharedApplication] setStatusBarHidden:NO];
Now my UIScrollView always stays under status bar, with Y position 20px. It never shifts down.
I finally managed to get this working on an iPhone4 running 5.1. Ensuring the bounces horizontally property for the scroll view is set fixed the problem, though why having this unchecked should cause the problem in the first place is beyond me - I'm pretty certain this is a bug in iOS.
I am starting to develop an iPad app I have a couple of questions:
For UISplitViewController, how do I change the detail view controller (the one on the right with the controller on the left hand side)? Or do I need to have a Navigation Controller and continuously push view controllers?.
I have a UITableViewCell that's designed for landscape mode. Do I have to setup a separate one for portrait mode?
Is there a way I can zoom out in Interface builder so I can see the entire iPad layout (I am using XCode 4.0.2)
Thanks!
1. You can change the two controllers by changing the viewControllers property of your UISplitViewController. If you want to keep the one on the left, you could do
[splitVC setViewControllers:[NSArray arrayWithObjects:[splitVC.viewControllers objectAtIndex:0], myNewViewController, nil]];
Though I believe that won't animate the transition. For this, you could use a classical view controller as your right-hand view, and do the transitions inside of it.
2. No need to do a separate design if you've set your subviews’ AutoresizingMasks appropriately. See the documentations on this, it's quite handy for this kind of situation.
3. Dunno, I have a huge monitor, I'll leave this one to others :p
In my iPad app, I save the state (visible/not visible) of a popover. So, during the launch of the app I initialize the UIPopoverController and tell it to show itself by using presentPopoverFromBarButtonItem:permittedArrowDirections:animated:. For the first argument (UIBarButtonItem), I use self.navigationItem.rightBarButtonItem. However, the popover keeps showing up on the left side of the screen (and not underneath the targeted button).
After the app is launched, the behavior is as expected. Any suggestions how to solve this?
For your information, I initialize the rightBarButtonItem and assign it to the navigationItem in the viewDidLoad method and before asking the popover to present itself. I have tried to call the popover in viewWillAppear and viewDidLoad, but the effect is the same.
My best alternative is to use presentPopoverFromRect:inView:permittedArrowDirections:animated: instead and "guess" the position depending on the orientation of the device.
Update: when I rotate the iPad, the popover does jump to the correct position. It seems that the barButtonItem's position is only determined at the last minute and after I ask my popover to present itself.
In situations like these where timing appears to be important I found that the trick of postponing an action until the next iteration of the run loop helps. If developing for iOS 4.0+, this can be easily achieved with GDC:
// call from viewDidAppear:
dispatch_async(dispatch_get_main_queue(), ^{
// call presentPopoverFromBarButtonItem:permittedArrowDirections:animated: here
});