I am building an application using UISplitViewController as my root view controller (as prescribed by Apple). However, I needed a custom view for login / management to be displayed prior to the UISplitViewController, so I created a custom UIStoryboardSegue that calls some custom animations. I am attempting to recreate the push / pop segues through a modal segue, without actually pushing an popping view controllers. I've implemented everything correctly, however, at the end of my animation I have a flicker. Here is a gif of it:
Here is my custom Segue's code:
- (void)perform {
UIViewController *srcViewController = (UIViewController *) self.sourceViewController;
UIViewController *destViewController = self.destinationViewController;
UIView *prevView = srcViewController.view;
UIView *destView = destViewController.view;
UIWindow *window = [[[UIApplication sharedApplication] delegate] window];
[window insertSubview:destView aboveSubview:prevView];
[destView enterRight:0.1 then:^{
[destView removeFromSuperview];
[srcViewController.presentingViewController dismissViewControllerAnimated: NO completion:nil];
}];
}
And here is my custom animation (Implemented as a category on UIView):
-(void)enterRight:(float)delay then:(void(^)(void))after
{
CGPoint moveTo = self.center;
CGPoint moveFrom = self.center;
// Grab a point from off the screen
CGFloat simpleOffscreen = [UIScreen mainScreen].bounds.size.width;
// come from off the right side (+)
moveFrom.x = moveFrom.x + simpleOffscreen;
self.center = moveFrom;
self.hidden = NO;
[UIView animateWithDuration:0.5
delay:delay
usingSpringWithDamping:1
initialSpringVelocity:0.1
options:UIViewAnimationOptionCurveEaseIn
animations:^
{
self.center = moveTo;
}
completion:^(BOOL finished)
{
if (after) after();
}
];
}
As you can see in the Segue, I am animating the view into the current view controller, then without animation presenting the actual destination view controller. I think this is where the flicker is introduced, yet I am unsure about how to go about preventing this.
My Storyboard for this custom segue is
Anyone know how to implement this?
Related
I'm trying to make a custom alertView (for iOS7+) on my own but I struggle with the alertView presentation.
I have a UIViewController with a black background (alpha set to 0.25f), and a alertView as subview.
When I want to show the alertView, I present modally the viewController:
-(void) show
{
UIWindow* window = [[UIApplication sharedApplication] keyWindow];
self.modalTransitionStyle = UIModalPresentationCustom;
self.transitioningDelegate = self;
[window.rootViewController presentViewController:self animated:YES completion:nil];
}
And here is my animator object:
-(NSTimeInterval) transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
NSLog(#"%s",__PRETTY_FUNCTION__);
return 2;
}
-(void) animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
NSLog(#"%s",__PRETTY_FUNCTION__);
UIView* toView = [transitionContext viewForKey:UITransitionContextToViewKey];
toView.alpha = 0;
UIView* container = [transitionContext containerView];
[container addSubview:toView];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toView.alpha = 0.5;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
The thing is: the modal VC is fading with the presenting VC in background as its supposed to do, but when the animation ends the presenting VC is removed from the background.
If I call [transitionContext completeTransition:YES]; instead, the presenting VC is in background but the modal VC is removed at animation end, so I guess the context cancels the presentation if we send 'NO'.
Is there a way to keep the presenting VC in background without having to make a snapshot of it and set it as background of the modal VC's view?
I've tried this solution and it works on both iOS 7 and 8:
if ([[UIDevice currentDevice].systemVersion integerValue] >= 8)
{
//For iOS 8
presentingViewController.providesPresentationContextTransitionStyle = YES;
presentingViewController.definesPresentationContext = YES;
presentedViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
}
else
{
//For iOS 7
presentingViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
}
Note: Be aware of the difference between 'presentingViewController' and 'presentedViewController'.
iOS8+
For iOS8+ you can use below code snippet
SecondViewController *secondViewController = [[SecondViewController alloc] init];
secondViewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:secondViewController animated:YES completion:nil];
My case might differ from yours, but the information might be useful for the conversation.
In Storyboard, I changed my segue's Presentation to state "Over Full Screen" and it did the trick.
I think what you are seeing is the default behavior of iOS.
View controllers are not supposed to be non-opaque when presented as modal view controllers. iOS removes the underlaying view controller when the animation is complete, in order to speed up composition when the presented view controller is supposed to take up the entire screen. There is no reason to draw a view controller - which might be complex in it's view hierarchy - when it is not even visible on screen.
I think your only solution is to do a custom presentation.
Remark: I did not test this. But it goes something like this.
/* Create a window to hold the view controller. */
UIWindow *presenterWindow = [[UIWindow alloc] init];
/* Make the window transparent. */
presenterWindow.backgroundColor = [UIColor clearColor];
presenterWindow.opaque = NO;
/* Set the window rootViewController to the view controller you
want to display as a modal. */
presenterWindow.rootViewController = myModalViewController;
/* Setup animation */
CGRect windowEndFrame = [UIScreen mainScreen].bounds;
CGRect windowStartFrame = windowEndFrame;
/* Adjust the start frame to appear from the bottom of the screen. */
windowStartFrame.origin.y = windowEndFrame.size.height;
/* Set the window start frame. */
presenterWindow.frame = windowStartFrame;
/* Put the window on screen. */
[presenterWindow makeKeyAndVisible];
/* Perform the animation. */
[UIView animateWithDuration:0.5
delay:.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
presenterWindow.frame = windowEndFrame;
}
completion:^(BOOL finished){
/* Your transition end code */
}];
This does however leave you with no option to use any of the presenting view controller logic build into UIViewController. You'll need to figure yourself, when the presented view controller is done, and then reverse the animation and remove the UIWindow from screen.
The ViewController is not supposed to be transparent when you present it or push it. You can try adding it as subview. And for transition effect change its frame immediately after adding as subview. Make its frame somewhere outside the visible view and then animate it to change frame to visible view.
Hope this helps.
For your information,
I finally made my custom alertView a subclass of UIView for the "popUp part".
To show it, I just add the alertView as subview of the keyWindow with the constraints to center it, and put a transparent black background view behind it.
As it's not a controller, I have to manage UI rotation by myself (only for iOS 7, it rotates well with the UI in iOS 8).
I'm presenting a modal UINavigationController with an interactive dismiss transition. The parent view controller has a dark status bar and the modal view controller a light status bar. I'm using the iOS 7 view controller-based status bar appearance configuration.
All works fine as long as I present and dismiss the view controller non-interactively. However, when I start an interactive dismiss transition and cancel it, the status bar color remains dark.
I created a sample project. Tap the "Menu" button, then start the interactive transition by panning from the right screen edge.
Things I've tried:
calling -setNeedsStatusBarAppearanceUpdate on any of the navigation and view controllers involved after the transition has been canceled
Changing the navigationBar.barStyle to UIBarStyleDefault and back to UIBarStyleBlack
I also verified that the statusBarStyle of my modal navigation controller is set correctly:
(lldb) p (UIStatusBarStyle) [[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentedViewController] preferredStatusBarStyle]
(UIStatusBarStyle) $8 = UIStatusBarStyleLightContent
Still, the status bar is black.
Any further idea what I could try?
To me this looks like a bug (rdar://15902745) in UINavigationController. After a canceled dismissal UINavigationController doesn't query again its presentedViewController for the preferredStatusBarStyle but uses the preferredStatusBarStyle from itself. I worked around this by overwriting -childViewControllerForStatusBarStyle:
- (UIViewController*)childViewControllerForStatusBarStyle {
if (self.presentedViewController) {
return self.presentedViewController.childViewControllerForStatusBarStyle;
}
return [super childViewControllerForStatusBarStyle];
}
Then, to animate the change during (and not after) the dismissal, I also overwrote -preferredStatusBarStyle.
I pushed the workaround to the sample project.
Do not forget to call
[self.transitionContext cancelInteractiveTransition];
within your's UIPercentDrivenInteractiveTransition subclasses - (void)cancelInteractiveTransition implementation. For inspiration, this is mine implementation
- (void)cancelInteractiveTransition {
id<UIViewControllerContextTransitioning> transitionContext = self.transitionContext;
[transitionContext cancelInteractiveTransition];
UIView *fromView = [UIViewController fromViewForTransitioningContext:transitionContext];
UIView *toView = [UIViewController toViewForTransitioningContext:transitionContext];
if (self.presenting)
{
CGRect endFrame = CGRectOffset([[transitionContext containerView] bounds], 0, CGRectGetHeight([[transitionContext containerView] bounds]));
[UIView animateWithDuration:ANIMATION_DURATION_PAN animations:^{
toView.frame = endFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:NO];
}];
}
else {
CGRect endFrame = [[transitionContext containerView] bounds];
[UIView animateWithDuration:ANIMATION_DURATION_PAN animations:^{
fromView.frame = endFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:NO];
}];
}
}
edit: this seems to help on iOS 8.4. Tested on 7.1 but no way. Maybe Apple fixed it lately.
I'm making custom present and dismiss transitions and have some problems with it. What I want to do is repeat this cool deep animations in iOS 7 (when we open/close some app).
I have First and Second Controllers. All animations are in First controller (it supports UIViewControllerTransitioningDelegate and UIViewControllerAnimatedTransitioning). So, I'm just checking: if it is presenting - I'm doing one animations (scaling up first and second view), if it is dismissing - I'm doing another animation (scaling down first and second view). Present animation works fine, the problem occurs with dismiss animation. In some reason when I'm scaling down my second controller (it is UINavigationController), I see black background behind it (and it's wrong, because I want to see my First controller while it's scaling down). Here is my code from First Controller
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIView *transitionView = [transitionContext containerView];
id toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
id fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
BOOL isPresenting;
isPresenting = [toViewController isKindOfClass:[UINavigationController class]];
UINavigationController *navigator = isPresenting ? toViewController : fromViewController;
if (isPresenting) {
[transitionView addSubview:navigator.view];
navigator.view.transform = CGAffineTransformMakeScale(0.1, 0.1);
navigator.view.alpha = 0;
}
navigator.view.center = self.startButton.center;
void(^AnimationBlock)(void) = ^ {
if (isPresenting) {
navigator.view.transform = CGAffineTransformMakeScale(1, 1);
self.view.transform = CGAffineTransformMakeScale(4, 4);
navigator.view.alpha = 1;
self.startButton.alpha = 0;
} else {
navigator.view.transform = CGAffineTransformMakeScale(0.1, 0.1);
self.view.transform = CGAffineTransformMakeScale(1, 1);
navigator.view.alpha = 0;
self.startButton.alpha = 1;
}
};
[UIView animateWithDuration:1
delay:0.0f
usingSpringWithDamping:50.0
initialSpringVelocity:4
options:UIViewAnimationOptionLayoutSubviews
animations:^{
AnimationBlock();
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
if (!isPresenting) {
[navigator.view removeFromSuperview];
}
}];
}
- (void)completeTransitionInContext:(id<UIViewControllerContextTransitioning>)transitionContext{
[transitionContext completeTransition:YES];
}
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
return 1;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return self;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
return self;
}
Please tell me if I should to provide some additional code or screens.
Thanks in advance!
You need to set modalPresentationStyle = UIModalPresentationCustom on the toVC before presenting it if you want to keep the fromVC in the window hierarchy after the present transition completes.
See my implementation of the sample code for WWDC Session 218: Custom Transitions Using View Controllers. If you click on 'Options' you'll see this type of transition. The relevant code is in SOLViewController.m prepareForSegue: and SOLOptionsTransitionAnimator.m
https://github.com/soleares/SOLPresentingFun
When you present the custom VC, you should use:
vc.modalPresentationStyle = UIModalPresentationCustom;
but, if you typed wrong to
vc.modalTransitionStyle = UIModalPresentationCustom;
you will get a black background behind the custom VC.
I think it is a good idea to use two separate AnimationController classes for Present and Dismiss animations and implement UIViewControllerTransitioningDelegate ( animationControllerForPresentedController and animationControllerForDismissedController ) inside your Parent ViewController.
To create an AnimationController just subclass NSObject and implement UIViewControllerAnimatedTransitioning there.
Hope it helps.
I am integrating Pushwoosh SDK for Push Notification, which is using UIWindow to represent HTML page sent from Pushwoosh portal
- (void) showWebView {
self.richPushWindow.alpha = 0.0f;
self.richPushWindow.windowLevel = UIWindowLevelStatusBar + 1.0f;
self.richPushWindow.hidden = NO;
self.richPushWindow.transform = CGAffineTransformMakeScale(0.01, 0.01);
[UIView animateWithDuration:0.3 animations:^{
self.richPushWindow.transform = CGAffineTransformIdentity;
self.richPushWindow.alpha = 1.0f;
} completion:^(BOOL finished) {
}];
}
- (void) showPushPage:(NSString *)pageId {
NSString *url = [NSString stringWithFormat:kServiceHtmlContentFormatUrl, pageId];
HtmlWebViewController *vc = [[HtmlWebViewController alloc] initWithURLString:url];
vc.delegate = self;
vc.supportedOrientations = supportedOrientations;
self.richPushWindow.rootViewController = vc;
[vc view];
}
And on closing on HTML page it calls
self.richPushWindow.transform = CGAffineTransformIdentity;
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.richPushWindow.transform = CGAffineTransformMakeScale(0.01, 0.01);
self.richPushWindow.alpha = 0.0f;
} completion:^(BOOL finished) {
AppDelegate * appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
self.richPushWindow.hidden = YES;
}];
Now I want to call my view controller on closing of this HTML page. So I tried to present myviewcotrlller in this block completion but not presenting.
Actually here problem is that there are two UIWindows in my app one of app and other used by sdk. Now if i try to present view controller from this html page which is on separate UIWindow so it creates a separate hierarchy and when i close this window also removes my presented viewconroller due to parent-child relationship. And if do not close this window then how to come back to actual flow of app.
I want that new controller should be presented from that new window and after that window should be close and flow of app should not be affected by additional window. Is it possible? If my concept is wrong, Please help if anyone has idea
Edit: second uiwindow never be key window, it only becomes visible by setting higher windowlevel and become hidden
The problem is that right after this completion block richPushWindow will be gone, effectively this means you are trying to present view controller on a hidden window.
The solution is very simple. Use main window to present view controller. Some pseudocode:
Add view from the ViewContoller to the main window subviews:
[appDelegate.window addSubview:myViewController.view];
Modal:
[appDelegate.window.rootViewController presentModalViewController:myViewController]
Using View Controller hierarchy:
Push your viewController to the main viewController stack. For example if you have navigationController, just push it there.
I hope it helps!
I have vc1.view covering the whole screen, and I want to be able to dim vc1.view, and have vc2.view zoom into the whole screen.
I don't have any navigation controller in the app, so what's the best practice to achieve my goal? The solution I'm thinking of is:
Add both vc1.view and vc2.view into a common container view
Use [UIView transitionFromView:vc1.view toView:vc2.view ......]
I dislike the idea of having to add views of different vc into a common container view. Any suggestions? Thanks in advance.
You can use transitionFromView:toView:... without adding the new view to a common container, because that transition method takes care of adding the view. The following worked for me. The code is in the view controller whose view is the "from view". I'm using a cross fade here, but you could change that to any of the other available methods:
-(void)switchViews:(id)sender {
UIWindow *win = self.view.window;
YellowController *yellow = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
yellow.view.frame = self.view.frame;
[UIView transitionFromView:self.view toView:yellow.view duration:2 options:UIViewAnimationOptionTransitionCrossDissolve completion:^(BOOL finished) {
win.rootViewController = yellow;
}];
}
However, to do a custom transition, you do have to add the new view as a subview of whatever view the "from view" is in (I think). In this example, that is the window's view. This code grows the new view from the center of the old one, while that one fades out. At the end of the transition, the view controller is switched to the one that owns the new view (yellow in this case)
After Edit: I changed this method to use a CGAffineTransform (thanks to jrturton for that suggestion made in an answer to my question):
-(void)switchViews3:(id)sender {
UIWindow *win = self.view.window;
YellowController *yellow = [self.storyboard instantiateViewControllerWithIdentifier:#"Yellow"];
yellow.view.frame = self.view.frame;
yellow.view.transform = CGAffineTransformMakeScale(.1, .1);
[win addSubview:yellow.view];
[UIView animateWithDuration:.6 animations:^{
yellow.view.transform = CGAffineTransformIdentity;
self.view.alpha = 0;
}
completion:^(BOOL finished) {
[self.view removeFromSuperview];
win.rootViewController = yellow;
}];
}
To present from vc1 to vc2 without a navigation controller, use
[vc1 presentViewController:vc2 animated:YES completion:nil];
To change the presenting style, Apple provides a few. You just need to set it before calling the above code:
vc2.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
Here is the list:
typedef enum {
UIModalTransitionStyleCoverVertical = 0,
UIModalTransitionStyleFlipHorizontal,
UIModalTransitionStyleCrossDissolve,
UIModalTransitionStylePartialCurl,
} UIModalTransitionStyle;