iOS7 slideout drawer draggable bug - ios

I am trying to implement a slideout drawer similar to a the one found in this guide: http://www.raywenderlich.com/32054/how-to-create-a-slide-out-navigation-like-facebook-and-path
I have two subviews that are added to the navigation controller, The drawer is initialized as follows in viewDidLoad:
self.drawerViewController = [[DrawerViewController alloc] initWithNibName:#"drawer" bundle:nil];
self.drawerViewController.view.autoresizingMask = UIViewAutoresizingFlexibleHeight;
[self.view addSubview:self.drawerViewController.view];
[self addChildViewController: self.drawerViewController];
[self.drawerViewController didMoveToParentViewController:self];
self.drawerViewController.view.frame = CGRectMake(0, 0, 150, self.view.frame.size.height);
then the contentview is initialized using a viewcontroller from my storyboard (and i call send subviewtoback to move the drawer behind it):
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
self.contentViewController = [sb instantiateViewControllerWithIdentifier:#"HomeViewController"];
[self.view sendSubviewToBack:self.drawerViewController.view];
[self pushViewController:self.contentViewController animated:YES];
I move the main content view by animating the frame of the contentview which uncovers the drawer underneath:
[UIView animateWithDuration:SLIDE_TIME delay:0 options:UIViewAnimationOptionBeginFromCurrentState
animations:^{self.contentViewController.view.frame = CGRectMake(150, 0, self.view.frame.size.width, self.view.frame.size.height);
}completion:nil];
Which works but if I do something like click the margin of the drawer and drag, the drawer can end up covering the entire screen in the iphone simulator or disappearing showing the black background (which is more easily reproduced when rotating the device while showing drawer). My question is why is the drawerview draggable and how do you prevent this?
Edit:
I have found the source of the dragging is from this addChildViewController line:
[self addChildViewController: self.drawerViewController];
However removing this doesnt allow users to click the table cells anymore.

The problem was using a navigation controller as a container of subviews. I don't think it's meant to be used this way so I ended up scrapping this and rewriting it with a uiviewcontroller container instead.

Related

iOS - Insert subview between the background and toolbar of another view

Pardon my ugly illustration below:
I have a view controller A that has a toolbar T at the bottom; toolbar is part of controller A, created by choosing "Opaque Toolbar" as "Bottom Bar" in the Attributes Inspector of view controller A. I'd like a banner B to slide up from the top of toolbar T, stay there for 2 seconds then disappear. Banner B is not within the view hierarchy of controller A. The issue right now is that the banner covers the toolbar during the sliding up process; I'd like the notification banner to emerge from the top of the toolbar, initially completely covered by the toolbar, instead of covering the toolbar.
Changing the layer index of the banner doesn't work, because the banner is not within the layer system of the view below it.
Here's my code snippet that adds the banner and animates it:
[containerView addSubview:innerView];
// Animate In
[UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
[innerView setFrame:target];
} completion:^(BOOL finished) {
if (finished) {
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
[innerView addGestureRecognizer:tapGestureRecognizer];
}
}];
Is there a way to insert the banner so that it appears above the background of controller A but below the toolbar of the controller?
Maybe you can try :
[self.view bringSubviewToFront : toolbarView]; //Top level
[self.view insertSubview:bannerView belowSubview: toolBarView]; //below your toolbar
I assume your containerView is the view of a UINavigationController, as you cannot add a toolbar in the attributes inspector for UIViewController. I would suggest you just add the banner to self.view
[self.view addSubview:innerView];
If you feel like this will not work. You should probably subclass UINavigationController and add the subview of it.
[self.view insertSubview:bannerView belowSubview: self.toolbar]; //toolbar is a property of UINavigationController
I suppose it's possible to say something like:
[self.navigationController.view insertSubview:bannerView belowSubview: self.navigationController.toolBarView];
Generally you want view controllers to manager their own view hierarchy. So it is better to avoid the third options.

Why isn't the rootViewController of my modally presented (form sheet) navController aware of it's smaller size when presented modally?

I am working on an iPad app with a few different modal views, and this code is pretty common:
UIViewController *v1 = [[UIViewController alloc] init];
UINavigationController *nav1 = [[UINavigationController alloc] initWithRootViewController:v1];
nav1.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:nav1 animated:YES completion:nil];
It could be that I am doing this wrong, but this is how I am presenting a navController-nested vc modally.
The problem is that within the v1 class, any reference to self.frame/bounds results in full screen dimensions:768x1024. Even though the navController clearly isn't being displayed with that size.
What should I be doing to make it so that the v1 vc knows how big it actually is? So that if I wanted to add, say, a tableView, it would know how big it should be?
Thanks!
EDIT:
I have tried a few more things, and still don't have a solution to this problem. I have made a simple sample project to illustrate the problem I am having. I just have one view and this is the core of the code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Frame: %#", NSStringFromCGRect(self.view.frame));
NSLog(#"Bounds: %#", NSStringFromCGRect(self.view.bounds));
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(self.view.frame.size.width - 400, 0, 400, 400);
button.backgroundColor = [UIColor redColor];
[button addTarget:self action:#selector(presentModal) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
- (void)presentModal {
SSViewController *view = [[SSViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:view];
nav.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentViewController:nav animated:YES completion:nil];
}
When this view loads, I have a big red button that is up against the top right corner of my view. When I press the button, it loads the same VC in a modal view embedded in a navController. The button shows up nearly off screen because the frame hasn't changed. It still shows as full screen. Here is a link to the project.
Not sure why you're having the issue you're having. I'm using the following code:
- (void)presentNewView {
NewViewController *newVC = [[NewViewController alloc] initWithNibName:nil bundle:nil];
newVC.view.backgroundColor = [UIColor redColor];
UINavigationController *newNC = [[UINavigationController alloc] initWithRootViewController:newVC];
newNC.modalPresentationStyle = UIModalPresentationFormSheet;
[self.navigationController presentViewController:newNC animated:YES completion:NULL];
}
.. it results in the following in the simulator:
.. and when I print out the first ViewController's frame and bounds (I thought it might be an issue with the two) I get the following:
frame height: 1024.000000
frame width: 768.000000
bounds height: 1024.000000
bounds width: 768.000000
.. and when I print out the presented ViewController's frame/bounds I get the following:
frame height: 620.000000
frame width: 540.000000
bounds height: 620.000000
bounds width: 540.000000
How are you determining the size of the frame exactly? Any reference within the v1 class that was presented modally SHOULD know its actual size, like I showed above.
EDIT
The major difference I found with my code and yours, is that in my code I created a subclass of my view controller "NewViewController" and was printing out the frame from within that class. The class itself seems to be aware of its correct bounds, but the class the presented it seems not to be. This is demonstrated by printing the view property from the ViewController class that presented it:
NewViewController's View From Presenting Class: frame = (0 0; 768 1024)
..compared to printing out the self.view from within the ViewDidAppear method of NewViewController itself:
NewViewController's View Did Appear: frame = (0 0; 540 576)
Moral of the story, if you are going to be presenting a UIViewController in the way you've shown, you're likely going to want to subclass UIViewController anyway so you can customize it however you want, so within that file if you reference self.view or self.bounds you will be getting the ACTUAL view/bounds.
EDIT #2
Based on the project you provided, the reason why you are having that issue is because you are printing out the frame/bounds of the view in viewDidLoad as opposed to viewDid/viewWillAppear. Adding those NSLog statements to VWA or VDA provides you the correct frame, so as I said in my initial edit, you should be fine accessing the view of the modal correctly at that point.
It's a new feature of iOS7. If you embed a UIViewController in navigation bar, it won't get smaller, because by default navigation bar is translucent.
You will see it if you change the background color of a view controller, that the top part of it is actually behind the navigation bar.
To lay out the v1 view controller underneath the navigation bar, you can use following code:
if ([v1 respondsToSelector:#selector(edgesForExtendedLayout)]) {
v1.edgesForExtendedLayout = UIRectEdgeNone;
}
It will behave just as in iOS6.
when presenting view controllers modally from a child view controller (one that has lass than the full screen and is a child of another view controller..) it is important to do this so that the modal controller knows the size of the canvas its appearing in
childViewController.definesPresentationContext = YES;
modalViewControllerWhichIsAboutToBePushed.modalPresentationStyle = UIModalPresentationCurrentContext

iOS - Semi-transparent modal view controller

I want to present a view controller with a slightly transparent background modally over the current view, such that the first view is slightly visible under the modal view.
I set the alpha value of the modal view controller and set the modalPresentationStyle to UIModalPresentationCurrentContext, as suggested in another post.
The result is that the view background is transparent when animating up, but when view controller is in place it changes to opaque black. It goes back to being transparent while animating the dismissal.
How can I get it to be transparent when active ?
I have tested in iOS 6 and 7. The code I am using follows:
MyModalViewController *viewController = [[MyModalViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:viewController];
[navController setNavigationBarHidden:YES];
self.navigationController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self.navigationController presentViewController:navController animated:YES completion:NULL];
iOS 8 added a new modal presentation style specifically for this purpose:
presentedViewController.modalPresentationStyle = UIModalPresentationOverFullScreen
From the spec:
UIModalPresentationOverFullScreen
A view presentation style in which the presented view covers the screen. The views beneath the presented content are not removed from the view hierarchy when the presentation finishes. So if the presented view controller does not fill the screen with opaque content, the underlying content shows through.
If you are targeting ios 8 and above you can set the modal presentation style to "over current context" and you are done.
If ios 7 and below, you would have to create a custom transition style so that the presenting screen doesn't go blank after transition. That is rather complicated.
The solution I present offers a lot of flexibility: make a screenshot before showing the modal dialog and set that as the background image for the application window. By default, that background is black (that is what you see when the back view controller dissapears). Change the background to the screenshot of the app. Make the screenshot in the viewWillAppear or viewDidLoad method of your transparent view. This works even with push segues, not only modal dialogs, but you should avoid animations. In general, avoid animations which affect the position of the background view because those will make it seem like it snaps back into place when transition finishes. It is a good idea to reset the background to its previous black image on viewDidDissapear to avoid unwanted effects.
You can maintain a stack of such background images and you can do multiple "transparent" push seques. Or have some complex/deep menu which appears on top of some main screen. For these many reasons I think this solution is better than rolling your own transitioning code. It is more flexible and easier to implement, and you don't have to deal with the animations yourself.
The reason that the BG view controllers disappear after a modal is shown is that the default transition in iOS 7 removes the BG view after animation completed. If you define your own transition and you set your BG view not to be removed (just changing its alpha) then you will have the transparent modal view.
Same problem occured to me. I have solved it by looking at the following url about a custom alert controller. I managed to get it working even with a UINavigationController.
Swift
let viewController = UIViewController()
viewController.providesPresentationContextTransitionStyle = true
viewController.definesPresentationContext = true
viewController.modalPresentationStyle = .overCurrentContext
viewController.modalTransitionStyle = .crossDissolve
DispatchQueue.main.async {
self.navigationController?.present(viewController, animated: true, completion: nil)
}
Objective C
UIViewController *viewController = [UIViewController new];
viewController.providesPresentationContextTransitionStyle = true;
viewController.definesPresentationContext = true;
viewController.modalPresentationStyle = UIModalPresentationOverCurrentContext;
viewController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
dispatch_async(dispatch_get_main_queue(), ^{
[self.navigationController presentViewController:viewController animated:true completion:nil];
});
Here is a solution.
Create your presenting view controller. Add a backView to this view controller's main view. Name this as backView.
In SecondViewController.m
-(void)viewDidLoad
{
// Make the main view's background clear, the second view's background transparent.
self.view.backgroundColor = [UIColor clearColor];
UIView* backView = [[UIView alloc] initWithFrame:self.view.frame];
backView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.6];
[self.view addSubview:backView];
}
Now you have a view controller with half transparent background. You can add anything you want to the self.view , the rest will be half transparent.
After that, in FirstViewController.m
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:secondViewController animated:YES completion:nil];
My solution is this:
Create a custom transparent overlay UIView that comes over any view, navigationbar and tabbbar.
-In the navigation controller (or tabbar controller) that your view controller is embedded in I create a custom view with it's frame equal to the frame of the navigation controller's view.
-Then I set it offscreen by setting it's origin.y to navigationController.view.height
-Then I create 2 functions -(void)showOverlay and -(void)hideOverlay that animate the overlay view on and off screen:
- (void)hideOverlay{
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
CGRect frm = self.helpView.frame;//helpView is my overlay
frm.origin.y = self.offscreenOffset; //this is an Y offscreen usually self.view.height
self.helpView.frame = frm;
[UIView commitAnimations];
}
- (void)showOverlay{
[self.view bringSubviewToFront:self.helpView];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.3];
CGRect frm = self.helpView.frame;
frm.origin.y = self.onscreenOffset;
self.helpView.frame = frm;
[UIView commitAnimations];
}
-In my view controller I can just call
[(MyCustomNavCtrl *)self.navigationController showOverlay];
[(MyCustomNavCtrl *)self.navigationController hideOverlay];
And that's about it.
FYI: The syntax is now:
childVC.modalPresentationStyle = UIModalPresentationStyle.OverFullScreen
Why don't you try setting this in AppDelegate
self.window.rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
then changing the alpha on the view being presented

How to keep background transparent when presentViewController in iPhone?

I want to keep background transparent like UIActivityController in iPhone iOS6
I try to clear color and opaque but background always black like this (iPhone iOS5 simulator)
This is presentViewController code:
ShareViewController *controller = [[ShareViewController alloc] initWithNibName:#"ShareViewController_iPhone" bundle:nil];
controller.delegate = self;
[self presentViewController:controller animated:YES completion:^{
NSLog(#"Activity complete");
}];
Please help! Thanks!
See what you have to do is add a UIView behind the custom UIActionsheet you mentioned. Here what you can do is keep the Background-color of the UIView as white and then keep its alpha = 0.5.
When you dismiss the UIActionsheet you can removeFromSuperview the UIView added behind too.
Instead of presentViewController use [UIView transitionWithView] to define your custom animation.
Start by having your view controller retained somewhere. (If you are using ARC have it as property on the parent view controller).
Add the view controller's view as a subview to the current VC's view. Set the frame to be CGRectMake(0, screenBottom, screenWidth, screenHeight / 2);
Animate by changing the frame so that it slides onto the screen.
[UIView transitionWithView:<#(UIView *)view#> duration:0.5 options:nil
animations:^
{
view.frame = CGRectMake(0, screenHeight / 2, screenWidth, screenHeight/ 2);
} completion:nil];

Custom transition between UIViewControllers

I've just changed my app from being TabView driven to CollectionView driven, as there are too many sections of my app to be feasible for a TabView. When you start the app you are presented with several items in a CollectionView and selecting any of these items will take you to the relevant section of the app.
In XCode, the collection view lives in its own storyboard and each section of the app has its own storyboard.
In the CollectionView's didSelectItemAtIndexPath, I launch the relevant starboard as follows;
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"relevant_storyboard" bundle:nil];
UIViewController* vc = [storyboard instantiateInitialViewController];
[self presentViewController:vc animated:YES completion:nil];
Now, none of the built-in transition animations really suit launching from a CollectionView, so I'd really like a custom effect, such as zoom in. However, I'm struggling to find any decent examples that work for me to create any kind of custom transition. I've tried [UIView transitionFromView], but I don't think that suits transitioning between UIViewControllers. I've tried transitionFromViewController:toViewController: but don't think I have the view hierarchy set up correctly. I've also tried using CATransition without success.
I've thought about doing it with a custom segue but, as my CollectionView is in it's own storyboard and have separate storyboards for each section of my app, I can't see how I can do this. At least not without having all sections of the app inside one storyboard, which would make the storyboard huge and difficult to manage.
So, can anyone give me any code examples or pointers on how I can solve this?
In my app I used a similar effect to zoom in from a thumbnail in a collection view cell to a child view controller that took up the entire screen. You could conceivably do the same thing for a navigation controller push as well.
In my code, I had a scoreView property on the cell subclass that I wanted to zoom up into the full screen. In your case, you may want to use a UIImageView with a screenshot of your new view. Alternatively, you could present the new view controller with a screenshot of the old view controller and then animate from there.
//Instantiate the view controller to be animated in...
//If the cell is not completely inside the collection view's frame, a dissolve animation might be more graceful.
BOOL dissolveAnimation = !CGRectContainsRect(CGRectMake(0, 0, self.collectionView.frame.size.width, self.collectionView.frame.size.height), cellRect);
//Get the frame of the cell in self.view coordinates, then the frame of the thumbnail view
CGRect cellRect = [self.collectionView layoutAttributesForItemAtIndexPath:indexPath].frame;
cellRect = CGRectOffset(cellRect, 0.0, -self.collectionView.contentOffset.y);
VSScoreCell *scoreCell = (VSScoreCell *)[self.collectionView cellForItemAtIndexPath:indexPath];
CGRect scoreRect = dissolveAnimation ? CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height) : CGRectMake(cellRect.origin.x + scoreCell.scoreView.frame.origin.x, cellRect.origin.y + scoreCell.scoreView.frame.origin.y, scoreCell.scoreView.frame.size.width, scoreCell.scoreView.frame.size.height);
VSScoreView *scoreView = [[VSScoreView alloc] initWithFrame:scoreRect];
//Initialize the view that will be animated up (in this case scoreView)...
if (dissolveAnimation)
scoreView.alpha = 0.0;
[self.view addSubview:scoreView];
[UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
if (dissolveAnimation)
scoreView.alpha = 1.0;
else
scoreView.frame = CGRectMake(0.0, 0.0, self.view.frame.size.width, self.view.frame.size.height);
} completion:^(BOOL finished) {
if (finished)
{
//Add scoreDisplayController as a child view controller or present it without animation
[scoreView removeFromSuperview];
}
}];
Of course, the new iOS might make this easier (my lips are sealed), but I hope this is somewhat helpful for your situation!
Have you tried the UIView animation block?
[UIView animationWithDuration:1.0 animation^ {
// do custom animation with the view
}completion:^(BOOL finished) {
if(finished) {
NSLog(#"Finished");
}
}];
It allows you to do custom animations when dealing with UIView(s), and even with UIViewControllers. I use it alot when dealing with custom animation actions.
EDIT:
for example, if you'd like to make the view of the current controller to move up the screen, and the second view controller to slide down in place of it, just do
[UIView animationWithDuration:1.0 animation^ {
// do custom animation with the view
// make sure CoreGraphics.framework is imported
// sliding current view to the top of the screen
self.view.transform = CGAffineTransformMakeTranslation(0,0);
// sliding 2nd view down..
// uncomment the following line, and one of the options for translation
//SecondView *sv = [[SecondView alloc] init];
// just edit the x,y in CGAffineTransformMakeTranslation to set where it will go
//sv.view.transform = CGAffineTransformMakeTranslation(320, 480) // iphone 4
//sv.view.transform = CGAffineTransformMakeTranslation(768, 1024) // ipad 1
}completion:^(BOOL finished) {
if(finished) {
NSLog(#"Finished");
}
}];
Hope this helps!

Resources