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!
Related
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?
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).
What i want to achieve is: after tapping a small view with some data i want to make it full screen and possibly make it to be a new vc.
For now I animate uiview to full screen with great success, but whole logic of this view is in it's "parent".
Is it possible to animate viewcontroller out of the uiview which is similar (for. eg. like in LayoutTransitions in Android SDK)?
Sample code of my uiview to full screen using autolayout:
sender.view.transform = CGAffineTransformIdentity;
[UIView animateWithDuration:0.2
animations:^{
sender.view.frame = self.view.window.bounds;
} completion:^(BOOL finished) {
[((CSTicketView*)sender.view) showMenu];
}];
[[UIApplication sharedApplication]
setStatusBarHidden:YES
withAnimation:UIStatusBarAnimationFade];
[[self navigationController] setNavigationBarHidden:YES animated:YES];
Let's move your sender view to Window as like below,
AppDelegate * appDelegate = (AppDelegate *) [[UIApplication sharedApplication] delegate];
[appDelegate.window addSubview:sender.view];
[UIView animateWithDuration:0.2
animations:^{
sender.view.frame = self.view.window.bounds;
} completion:^(BOOL finished) {
[((CSTicketView*)sender.view) showMenu];
}];
Absolutely possible.
You can use the new view controller transitions model in iOS7.
I would definitely recommend a few resources to check on top of my basic explanation:
this
and this
I have some sample code for a demonstrator of this here
Ultimately you make a new view controller for the view you are transitioning to, and you also make an NSObject subclass that conforms to UIViewControllerAnimatedTransitioning which contains the code to transition between them. Sounds complex but if you watch the video I linked to and read the other reference it'll make total sense.
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;
I have a view controller which is not fullscreen (has a status bar) and want to present a modal view controller which is fullscreen.
If I hide the status bar at the beginning of the animation (parent's viewWillDisappear or modal's viewWillAppear) then for a moment the parent will be visible without a status bar, looking like a bug.
If I do it at the end of the animation (parent's viewDidDisappear or modal's viewDidAppear) then the status bar will be visible for a moment over the modal view, i.e. it won't appear as the modal view "covered it".
Is there a way to do this nicely?
edit:
One possibility would be to create a UIWindow with windowLevel=alert for at least the duration of the animation. The sample iAd ad seems to cover the status bar nicely without another window, so it must be possible somehow.
Another fun little project. This was the best I could come up with. It's not too bad if you don't mind using your own container controller to manage presenting/dismissing view controllers. I try to do things in a general way but this could be rolled into an app w/ the ContainerViewController if desired.
Note that I only implemented the equivalent of UIModalTransitionStyleCoverVertical. You can customize the animation however you like as well.
Relevant animation code:
- (void)presentViewController:(UIViewController *)viewControllerToPresent
{
// do nothing if no controller
if (!viewControllerToPresent) return;
[__viewControllers addObject:viewControllerToPresent];
CGRect toFrame = viewControllerToPresent.view.frame;
toFrame.origin = CGPointMake(0, CGRectGetMaxY(self.view.bounds));
viewControllerToPresent.view.frame = toFrame;
[UIView transitionWithView:self.view
duration:0.2
options:UIViewAnimationOptionTransitionNone
animations:^{
[[UIApplication sharedApplication] setStatusBarHidden:viewControllerToPresent.wantsFullScreenLayout withAnimation:UIStatusBarAnimationSlide];
[self.view addSubview:viewControllerToPresent.view];
viewControllerToPresent.view.frame = [UIScreen mainScreen].applicationFrame;
}
completion:nil];
}
- (void)dismissViewController
{
// nothing to dismiss if showing first controller
if (__viewControllers.count <= 1) return;
UIViewController *currentViewController = [__viewControllers lastObject];
UIViewController *previousViewController = [__viewControllers objectAtIndex:__viewControllers.count - 2];
[UIView transitionWithView:self.view
duration:0.2
options:UIViewAnimationOptionTransitionNone
animations:^{
[[UIApplication sharedApplication] setStatusBarHidden:previousViewController.wantsFullScreenLayout withAnimation:UIStatusBarAnimationSlide];
CGRect toFrame = currentViewController.view.frame;
toFrame.origin = CGPointMake(0, CGRectGetMaxY(self.view.bounds));
currentViewController.view.frame = toFrame;
}
completion:^(BOOL finished) {
[currentViewController.view removeFromSuperview];
[__viewControllers removeLastObject];
}];
}
I do that in my app with this code:
[[UIApplication sharedApplication] setStatusBarStyle: UIStatusBarStyleBlackOpaque];
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation: UIStatusBarAnimationSlide ];
DocumentListViewController * dl = [[DocumentListViewController alloc] initWithNibName:#"DocumentListView" bundle:nil] ;
UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:dl];
[dl release];
// Go to the list of documents...
[[self.view superview] addSubview:nav.view];
nav.view.alpha = 0.0 ;
[self hideActivityAlert];
[UIView animateWithDuration:1.0 animations:^{
nav.view.alpha = 1.0; } completion:^(BOOL A){
[self.view removeFromSuperview];
[self release];} ];
The status bar is presented shoftly while the animation occurs.
You have to be sure that the first view, when status bar is going hidden will fill the space. Use the property autoresizingMask with proper value.
Here's a solution that seems to work. You can derive the viewcontroller you want to present modally from my TSFullScreenModalViewController, or you can just implement the code right in the view controller itself.
#interface TSFullScreenModalViewController : UIViewController
{
UIWindow* _window;
}
- (void) presentFullScreenModal;
#end
#implementation TSFullScreenModalViewController
- (void) viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear: YES];
[_window resignKeyWindow];
[_window release];
_window = nil;
}
- (void) presentFullScreenModal
{
UIViewController* rvc = [[UIViewController new] autorelease];
rvc.view.backgroundColor = [UIColor clearColor];
_window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds] ;
_window.windowLevel = UIWindowLevelStatusBar+1;
_window.backgroundColor = [UIColor clearColor];
_window.rootViewController = rvc;
[_window makeKeyAndVisible];
[UIApplication sharedApplication].statusBarHidden = YES;
[rvc presentModalViewController: self animated: YES];
[UIApplication sharedApplication].statusBarHidden = NO;
}
#end
Derive your modal view controller, like this:
#interface MyModalViewController : TSFullScreenModalViewController
{
}
- (IBAction) onDismiss:(id)sender;
#end
Use it from another view controller, like this:
- (IBAction) onShowModal:(id)sender
{
MyModalViewController* mmvc = [[MyModalViewController new] autorelease];
[mmvc presentFullScreenModal];
}
Finally, dismiss your view controller as you normally would:
- (IBAction) onDismiss:(id)sender
{
[self dismissModalViewControllerAnimated: YES];
}
Might be a bit of a hack but have you considered:
Take a screenshot programatically of the first view with the status bar (see this SO question)
Create a new view which displays the image you just took in fullscreen (using UIImage's initWithFrame)
Hide the status bar
Present the modal view controller
Then to dismiss the modal view, just reverse the steps.
EDIT:
Won't work for this because you can't take screenshots of the status bar.
It could be as simple as delaying the presentation of your modalViewController using performSelector:withDelay:
Tell the status bar to animate out and then launch the modal controller with the right delay so it coincides with the status bar animation.