I am attempting to cross-dissolve between two view controllers with a custom segue, the segue initiates properly and performs the animation as intended - however when in landscape mode the destination view controller appears in its portrait layout for the duration of the animation. Once the animation completes the destination view controller "snaps" to its landscape layout. Both of the view controllers are set up using auto layout.
Below is my custom segue's "perform" method:
- (void)perform
{
UIViewController *srcController = self.sourceViewController;
UIViewController *dstController = self.destinationViewController;
dstController.view.transform = srcController.view.transform;
[UIView transitionFromView:srcController.view
toView:dstController.view
duration:self.animationDuration
options:self.animationOptions
completion:self.animationCompletionBlock];
}
Is it necessary to manually tell the destination view controller to trigger autolayout before it is displayed? And if so how would this be performed? Using the built-in segue with a cross-dissolve does not appear to encounter this problem, I have attempted (without success) to determine what it is doing that my custom segue is not.
This has had me stumped for a while, any help would be appreciated and please let me know if more details are needed.
So it turns out that the solution to this problem was more simple than I would have imagined. Adding the following line ensures the destination view controller has the right bounds when it lays out its elements:
dstController.view.bounds = srcController.view.bounds;
So the rest of the code becomes:
- (void)perform
{
UIViewController *srcController = self.sourceViewController;
UIViewController *dstController = self.destinationViewController;
dstController.view.transform = srcController.view.transform;
dstController.view.bounds = srcController.view.bounds;
[UIView transitionFromView:srcController.view
toView:dstController.view
duration:self.animationDuration
options:self.animationOptions
completion:self.animationCompletionBlock];
}
Related
- (UIViewController *)activityViewController
I created a custom UIACtivity that returns a view controller that displays a popup. This allows the user to do some editing before performing the actual activity.
With ios below 8, my background with transparency that looks like an overlay works (I can see my game underneath) but after updating to ios8, the background becomes solid color gray. I checked the UIImageView displaying my overlay image with alpha and it is set to clear. Can someone tell me why the background suddenly becomes solid? I couldn't see the view of my game underneath anymore.
Here's my code:
- (IBAction)didPressShareButton:(id)sender
{
...
[_rootViewController presentViewController:[self getActivityViewController] animated:YES completion:nil];
...
}
The _rootViewController is the main view controller of my application.
The [self getActivityViewController] returns an instance of UIActivityViewController which includes my custom UIActivity for instagram
My InstagramUIActivity overrides this function to return a custom viewcontroller (see attached image)
- (UIViewController *)activityViewController
{
dismissalAC = [[InstagramDismissal alloc]init];
presentationAC = [[InstagramPresentation alloc]init];
instagramVC = [[InstagramViewController alloc]initWithInstagramPhoto:_instagramPhoto];
instagramVC.delegate = self;
if ([instagramVC respondsToSelector:#selector(setTransitioningDelegate:)]) {
instagramVC.transitioningDelegate = self;
}
return instagramVC;
}
dismissalAC and presentationAC are just objects that implement the UIViewControllerAnimatedTransitioning protocol so I could have my own transition animation.
When I return my custom view controller, it pops up but along with it is a view with white background. I don't know why.
try
instagramVC.modalPresentationStyle = UIModalPresentationOverFullScreen;
or
instagramVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;
I encountered this problem in my apps too.
Since iOS 8, Apple forbids subclassing nor customizing the subviews of an UIActivityViewController.
If you did so on your app, the app shows an overlay over your view and an empty gray list without any buttons. In this case, you must kill your app to dismiss the UIActivityViewController.
To replace this behavior, I simply creating a view (either programmatically or from storyboard) with the same layout and you can make it appear from bottom of the screen (with an animation). Ask me some example code if needed.
I have a very basic container view that contains a sidebar and swaps out the view controllers in the content area (think UISplitView but with a small icon sidebar / vertical UITabBar).
The container view controller uses autoLayout and resizes correctly when rotated.
Content viewController 1 uses autolayout and was made with IB, so it has a xib file.
Content viewController 2 inherits from UITableViewController and does not use a xib.
If I assign viewController 1 as the root view controller and rotate, the resize works and here are the callbacks that I get in viewController 1:
willRotateToInterfaceOrientation
updateViewConstraints
viewWillLayoutSubviews
didRotateFromInterfaceOrientation
However, if I assign my container view controller as the root view controller, load viewController 1 and rotate, the resize does not work. And I only get the following callbacks inside viewController 1:
willRotateToInterfaceOrientation
didRotateFromInterfaceOrientation
Inside my view controller container, here's how I swap the view controllers:
[self addChildViewController:toViewController];
[toViewController didMoveToParentViewController:self];
// Remove the old view controller
[fromViewController willMoveToParentViewController:nil];
[fromViewController.view removeFromSuperview];
[fromViewController removeFromParentViewController];
// Add the new view
[self.contentContainerView addSubview:toViewController.view];
Now, I do get the callbacks that a rotation is about to happen, but it seems as if neither updateViewConstraints nor viewWillLayoutSubviews is called. This explains why the resize is not happening, but why are those methods not called once I put the view controller in a container view?
I also tried to explicitly return YES in my container on both
shouldAutomaticallyForwardAppearanceMethods
and
shouldAutomaticallyForwardAppearanceMethods
although this should be the default already.
Also, the view controller not made with IB (view controller 2) resizes correctly when rotating inside the container. However, I don't explicitly use NSLayoutConstraints on this one, so I suspect it's defaulting to Springs and Struts for the resizing when rotating.
Do I need to forward some other events on my view controller container to get the auto layout view controller to resize correctly when rotating?
OK, I think I was missing this method here in my view controller container:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
self.contentViewController.view.frame = self.contentContainerView.bounds;
}
While this resizes now correctly when rotating, it still doesn't trigger
updateViewConstraints
in my child view controller. Interesting
Seems like iOS 8 does call updateViewConstraints for you. But iOS 7 didn't. To get this called in iOS 7, call setNeedsUpdateConstraints, like this:
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
[super willAnimateRotationToInterfaceOrientation:interfaceOrientation duration:duration];
BOOL isiOS7 = floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_7_1;
if (isiOS7) {
// Trigger a call to updateViewConstraints
[self.view setNeedsUpdateConstraints];
}
}
In updateLayoutConstraints, a good way to check which orientation is the one to layout for is to check the status bar's orientation. This works for 7 and 8.
UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
BOOL layoutAsLandscape = UIInterfaceOrientationIsLandscape(orientation);
I setup a storyboard based on the Master-Detail Application, embed the detail view in a navigation controller, and add a new table view controller object which I will use as a second detail view controller.
I then push the new detail view controller with the following code (instead of a segue because I am pushing both a root view and a detail view controller at the same time. Only the detail view code is shown).
// Push the detailView view controller:
NewClass *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"test"];
newViewController.navigationItem.hidesBackButton = YES;
self.splitViewController.delegate = newViewController;
[self.detailViewController pushViewController:newViewController animated:YES];
This works perfectly, EXCEPT that the splitView delegate methods are never called before or after the push. If I do this while in portrait mode, after it pushes the detailViewController, the button to drop down the masterView popover does not show up UNTIL I rotate to landscape mode and then back to portrait mode.
How can I cause the willHideViewController/willShowViewController split view controller delegate methods to be called or manually cause them to be called?
So from what I found, it doesn't call the method because the orientation hasn't changed.
What you have to do is to pass the button from the presenting view controller since it's already tied to the popover like this:
if(self.navigationItem.leftBarButtonItem != nil) {
newViewController.navigationItem.leftBarButtonItem = self.navigationItem.leftBarButtonItem;
}
// Push the newViewController
I have added a ViewvController(B) as subview on ViewController(A). In ViewController A(SuperView) UIModelPresentationFullScreen working fine. But when am calling UIModelPresentationFull in ViewController B(SubView) it modelview showing in Portrait mode and that is also not fully viewed. How to solve this problem. Can any one help me please. I have tried 2 days.
This is what I tried in both the superview and subview...
picFBCapture *fbCapt = [[picFBCapture alloc] init];
//[self.navigationController pushViewController:fbCapt animated:YES];
//fbCapt.modalPresentationStyle = UIModalTransitionStyleFlipHorizontal;
fbCapt.modalTransitionStyle = UIModalPresentationFullScreen;
[self presentModalViewController:fbCapt animated:NO];
[fbCapt release];
Thanks in advance..
The problem is that if you add a view controller as a subview it's not connected to the view controller hierarchy and thus certain things doesn't work. You should avoid adding view controllers as subviews whenever possible, since this is not how Apple intend view controllers to be used, but sometimes it can't be avoided.
If this is one of those cases when it can't be avoided you should save a reference to view controller A in view controller B and then call presentModalViewController: on view controller A (that is connected to the view controller hierarchy) instead of self (view controller B, that isn't connected).
EDIT: In controller A you probably have code looking something like:
[self.view addSubview:controllerB.view];
In conjunction to this line add:
controllerB.controllerA = self;
I hope you know how to create properties, but if not here's a hint:
#property (nonatomic, assign) UIViewController *controllerA;
The rest you should be able to figure out using Google and the documentation.
You will have to handle viewController B's view in landscape by yourself. Since viewController B has been added as a subview, its view controller will not be handling its landscape orientation. The UIModalPresentationFullScreen style (landscape and portrait) will work only if viewController B is shown, ie not as subview but as a full view itself.
I'm trying to add a split view inside of a tab bar, and since the split view isn't the root, it doesn't properly get the rotation notifications, so the delegate's methods are never called to add the button to the toolbar in the detail view.
I've rigged it up so I can generate the popover when rotated, but when this method is called, the view dissappears from the landscape mode, and if you activate it and then rotate back into landscape, it's a black empty box where the master view used to be. How do I get rid of this occuring?
-(void) displayPopover:(id)sender
{
//Toggle the popover: if it's showing, hide it
if (popoverController != nil && [popoverController isPopoverVisible])
{
[popoverController dismissPopoverAnimated:NO];
}
else
{
//Create a Popover displaying the master view
if (popoverController == nil)
{
popoverController=[[UIPopoverController alloc] initWithContentViewController:self->rootController];
popoverController.popoverContentSize=CGSizeMake(300, 500);
}
[popoverController presentPopoverFromBarButtonItem:[detailController.toolbar.items objectAtIndex:0] permittedArrowDirections:UIPopoverArrowDirectionAny animated:NO];
}
You will need to remove all the objects from window using:
[appdelegate window ] subviews] objectAtIndex:0] removeFromSuperview];
Then add your splitview to the window, you can get the view callbacks.
I would recommend either finding a way to get your SplitViewController to be root, or creating a custom subclass of the UISplitViewController that allows for non-root placement.
I really like what Matt Gemmell did here: http://mattgemmell.com/2010/07/31/mgsplitviewcontroller-for-ipad
Using a custom subclass like Matt's will allow you to benefit from all the same delegate methods that a SplitView as root would allow. I used it in a project where I wanted my SplitView to appear as a modal - almost impossible with a traditional UISplitViewController.
so your split view has rotation enabled (shouldAutorotateToInterfaceOrientation:) now you have to make sure that the tab controller has also rotation enabled (should be the appDelegate, am I right?) AND you have to make sure that every other view that is in your TabBar has also rotation enabled!
so if your TabBar contains 2 tabs you have to set the rotation in 3 classes.