UIViewController changes dimension after modal presentation - ios

I have a UIViewController which is presented using a custom transition, and by design it only fills 90% of the screen's height.
This appears fine, and I've never had any issues with it. Let's call it View A. Now I am trying to present a full screen modal view on top of this, let's call that View B. This appearance works, but when View B is dismissed, View A reappears, but has been expanded to fill the entire bounds of the screen.
Here's the presentation code I'm using:
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
...
// Presentation
const CGFloat viewHeight = (screenBounds.size.height * 0.9);
const CGRect beginFrame = CGRectMake(0, screenBounds.size.height, screenBounds.size.width, viewHeight);
const CGRect finalFrame = CGRectMake(0, (screenBounds.size.height - viewHeight), screenBounds.size.width, viewHeight);
// Dim
self.dimmedView.alpha = 0.0;
[transitionContext.containerView addSubview:self.dimmedView];
[transitionContext.containerView addConstraints:[NSLayoutConstraint allConstraintsFromViewToSuperview:self.dimmedView inset:UIOffsetZero]];
// Prepare
UIView * const toView = toVC.view;
toView.frame = beginFrame;
[transitionContext.containerView addSubview:toView];
// Animate
[UIView animateWithDuration:kAnimationDuration delay:0.0 usingSpringWithDamping:0.8 initialSpringVelocity:0.25 options:0 animations:^{
toView.frame = finalFrame;
self.dimmedView.alpha = 0.6;
self.tabBarController.view.layer.cornerRadius = 8.0;
self.tabBarController.view.transform = CGAffineTransformMakeScale(0.95, 0.95);
} completion:^(BOOL finished) {
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
[offshootView removeFromSuperview];
}];
...
}
Has anyone seen this before, and know how to stop the system from resizing View A?

I think the problem is that you modify the size of the view controller's root view, although it is handled by the view controller. The documentation for UIViewController says:
A view controller’s root view is always sized to fit its assigned
space.
Why not add another (fully transparent) view as a child to the root view, where you place all your content? Doing so let's you keep the root view at 100% while changing the new view's size to 90% when you want to. If I understand you correctly this will accomplish the same thing without touching the root view.
For this to work you should set the view controllers Presentation property in the Storyboard attribute inspector to Over Full Screen. If you want to set it by code you set the view controller's .modalPresentationStyle = UIModalPresentationOverFullScreen. By setting this the underlying view controller will stay on screen after you have presented your view controller and continue to be visible where you have transparency in your view.
Documentation for UIViewController
Documentation for UIModalPresentationOverFullScreen

Related

iOS Action app extension with transparent background?

I’m trying to make an iOS Action app extension with minimal UI. Basically it would just show a progress indicator until the action completed. I just want to be able to animate the view so that it slides down from the top & then slides back up when the action has completed. If anyone is familiar with Instapaper’s Share extension, then that’s the kind of basic UI I’m looking for.
The problem is that when I try to duplicate this functionality - I just have a small UIView that animates down from the top - I get a black background behind my view. I can’t figure out how to make that background transparent so that the stuff behind my view is still visible. Does anyone know how to do this?
As a starting point I’m just using the default Action Extension template that’s created by Xcode...
Create a new iOS app project in Xcode.
Add a new target -> Action Extension.
In the ActionViewController.m file add a viewWillAppear method to animate the view (using a 1 second animation so that the black background is easily seen):
Code:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGRect currFrame = self.view.frame;
CGRect newFrame = currFrame;
newFrame.origin.y -= newFrame.size.height;
self.view.frame = newFrame;
[UIView animateWithDuration:1.0 animations:^{
self.view.frame = currFrame;
}];
}
When this is run the view is animated sliding down from the top. However rather than seeing the UI of the calling App all you see is a black background.
I’ve tried a number of things - changing the modalPresentationStyle (doesn’t seem to do anything), setting the entire view to hidden (this just makes the whole screen black), etc.
For reference this is using iOS 9.3.2 and Xcode 7.3.1.
From what I understand from Apple docs
In iOS, an Action extension:
Helps users view the current document in a different way
Always appears in an action sheet or full-screen modal view
Receives selected content only if explicitly provided by the host app
The fact that the Action extension always appear in a full-screen view on an iPhone might mean that there's no way of having a transparent background.
I am certain that a Share extension can be animated (I've done it myself) how you want it and have a transparent background. That's why Instapaper's Share extension works nicely.
You are facing two problems:
1. When you present a view controller, the default behavior is the controller is full screen context. That is the reason you see a black screen.
2. You are trying to change self.view.frame when the controller is presented on full screen.
Yet there is a way to achieve this kind of behavior you are looking for, in one of three ways:
A. Specify "modalPresentationStyle" to "UIModalPresentationOverCurrentContext" and set the presenting controller to "definesPresentationContext".
That way, when you present the controller, the presenting controller will be behind the presented controller.
And insted of changing self.view.frame you will set self.view background color to clear, and add a subview and use it as a background view:
//Presenting view controller:
-(void) presentPopUpViewController {
self.definesPresentationContext = YES; //self is presenting view controller
self.presentedVC.modalPresentationStyle = UIModalPresentationOverCurrentContext;
[self presentViewController:self.presentedVC animated:NO completion:nil];
}
//Presented view controller:
-(void) viewDidLoad {
[super viewDidLoad];
CGRect currFrame = self.view.frame;
CGRect newFrame = currFrame;
newFrame.origin.y -= newFrame.size.height;
self.myBackroundView = [[UIView alloc] init];
self.myBackroundView.backgroundColor = [UIColor yellowColor];
self.myBackroundView.frame = newFrame;
[self.view addSubview:self.myBackroundView];
self.view.backgroundColor = [UIColor clearColor];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[UIView animateWithDuration:5.0 animations:^{
self.myBackroundView.frame = self.view.frame;
}];
}
B. Add the presented view controller as a Child view controller. that way the life cycle stays the same, but you can add I'ts view as a subview, and change I'ts frame.
//Presenting view controller:
-(void) presentChildViewController {
[self addChildViewController:self.presentedVC];
[self.view addSubview:self.presentedVC.view];
[self.presentedVC didMoveToParentViewController:self];
}
//Presented view controller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGRect currFrame = self.view.frame;
CGRect newFrame = currFrame;
newFrame.origin.y -= newFrame.size.height;
self.view.frame = newFrame;
[UIView animateWithDuration:1.0 animations:^{
self.view.frame = currFrame;
}];
}
C. Don't use UIViewController, use UIView. Use a "Decorator" object, that you pass it the ViewController you would like the view to disaply on, and the "Decorator" will add the view as subview, and deal with the animation. No need for an example for this scenario.
It is wrong to start animations when your view hasn't yet appeared. Can you, please, try the same code in viewDidAppear.
Also animating main view controller's view will make underlying layers visible, so you've got to use another view on top of view controller's main view, like this:
UIView *progressView = [[UIView alloc] init];
progressView.frame = self.view.bounds;
progressView.backgroundColor = [UIColor redColor];
[self.view addSubView:progressView];

How to bounce child view controller's UICollectionView when the child view controller is presented?

I want to present a child view controller by dropping it from top to the bottom. The child view controller is a UICollectionViewController with several cells in it. I can use the iOS7 UIViewControllerContextTransitioning for the dropping down view controller transition. But if I want only the collection view to bounce (like a ball hit on the ground) when the child view controller is presented, how should I do?
I have try to use UIKit Dynamics and create some UIAnimatorBehavior on the UICollectionView after the transition, like UIGravityBehavior and UIPushBehavior. But they don't seem to work. Maybe I am using them in the wrong way. Is there anyone can give me some hints?
Update
After tried several solutions, I finally came out a solution which is pretty close to what I want. This video shows the result: http://youtu.be/tueXDBMsdt0
But I think there should be a better solution for that. And here is my solution's steps:
Create a UIViewControllerAnimatedTransitioning object, which animate the view controller transition from top to bottom.
The child view controller is a UICollectionViewController. At the end of transition animation, I set child view controller's scrollview content offset to (0, -30), and then complete the transition.
In child view controller's viewDidAppear, animate the content offset back to (0, 0).
Besides, I also follow the instructions in the article: http://www.teehanlax.com/blog/implementing-a-bouncy-uicollectionviewlayout-with-uikit-dynamics/ to set UIKit dynamics animator in cells. When the content offset is changed, then the cells will look like bouncing.
The transition animation code looks like this:
- (void) animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
CGRect frame = [[transitionContext containerView] frame];
CGRect startFrame = frame;
startFrame.origin.y -= CGRectGetHeight(transitionContext.containerView.frame);
[transitionContext.containerView addSubview:fromViewController.view];
[transitionContext.containerView addSubview:toViewController.view];
toViewController.view.frame = startFrame;
[UIView animateWithDuration:0.4 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
toViewController.view.frame = frame;
}
completion:^(BOOL finished) {
((UICollectionViewController*)toViewController).contentOffset = CGPointMake(0, -30);
[transitionContext completeTransition:YES];
}];
}
And in child view controller viewDidAppear:
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.collectionView setContentOffset:CGPointMake(0, 0) animated:YES];
}
But I would still want the cell to bounce more naturally. Any other better solutions?
Important: this can now be done in iOS in one simple line of code:
https://stackoverflow.com/a/23514653/294884
Detailed answer when fuller control is needed:
If you want the same effect between the Screen lock and the Camera on the iPhone, you can use UIViewControllerContextTransitioning
There are a good tutorial here http://www.objc.io/issue-5/view-controller-transitions.html
and here http://www.teehanlax.com/blog/custom-uiviewcontroller-transitions/
if you have a Apple developer account, there are a video about View controller transition : https://developer.apple.com/tech-talks/videos/
The video is named "Architecting Modern Apps, Part 1"
This way work only on iOS7!

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!

self.view.frame is no longer animating when in storyboard's initial view controller

This is a strange problem. I have an iPhone view controller for creating a new user with some text fields that require animation to avoid the keyboard when it is shown, i.e. the view animates up and down to keep a minimum clearance between the top of the keyboard and the lower edge of the text field. That worked fine when it was presented modally from the login existing user screen, the initial view controller in the storyboard.
Then I decided to change the app a bit so that the login/create user views would belong to the same view controller. They transition like so:
if(accountCreateView == nil) {
UINib *nib = [UINib nibWithNibName:#"NewAccountView" bundle:nil];
NSArray *nibViews = [nib instantiateWithOwner:self options:nil];
accountCreateView = [nibViews objectAtIndex:0];
}
[UIView transitionFromView:(displayingLoginView ? loginView : accountCreateView)
toView:(displayingLoginView ? accountCreateView : loginView)
duration:0.6
options:(displayingLoginView ?
UIViewAnimationOptionTransitionFlipFromRight :
UIViewAnimationOptionTransitionFlipFromLeft)
completion:^(BOOL finished) {
if (finished) {
displayingLoginView = !displayingLoginView;
}
}
];
This works fine too. However the code that does animation of the views does not. When a text field becomes active the code to check proximity of the text field to the keyboard is called but nothing happens when the animation block executes.
So I checked:
if([UIView areAnimationsEnabled])
NSLog(#"Animations are enabled");
else
NSLog(#"Animations are disabled");
Animations are certainly enabled.
So I moved it back further to viewDidLoad, trying a simple animation to see what works, before anything else:
CGRect viewFrame = loginNameTextField.frame;
viewFrame.origin.y = 400.f;
[UIView animateWithDuration:1.f
delay:0.f
options:UIViewAnimationOptionCurveEaseInOut
animations:^ {
loginNameTextField.frame = viewFrame;
}
completion:NULL];
This works fine! Text field moves slowly down the window.
This does nothing, and that is the confusing part:
CGRect viewFrame = self.view.frame;
viewFrame.origin.y = 400.f;
[UIView animateWithDuration:1.f
delay:0.f
options:UIViewAnimationOptionCurveEaseInOut
animations:^ {
self.view.frame = viewFrame;
}
completion:NULL];
viewFrame when set from self.view.frame has origin 0,20 and size 320,460 as expected.
I tried setting the frame property directly:
CGRect viewFrame = self.view.frame;
viewFrame.origin.y = 400.f;
self.view.frame = viewFrame;
Looks like I can change the frame of subviews of self.view but not of self.view. A fix might be to leave the initial view controller as a blank view in the storyboard and create xibs for both the login view and create user view and load them in viewDidLoad - but why?
edit: Yes, switching the storyboard version of the view controller to a plain empty view and adding the view containing the actual UI from a xib as a subview does make animations work again. Still don't know why though.
If all you need to do is keep your text fields above your keyboard, why not drop the UIViewController and change to a UITableViewController and use static cells? You place one text field per cell (creating enough cells to meet your needs) and style it how you like. When you don't have a first responder and there are few enough cells to fit on the screen, it won't scroll. It there are more than what will fit, then it will scroll to reach the bottom/top cells. Yet no matter how many cells you have, if you select a text field that will be covered up by the keyboard, the device will automatically scroll the cell to a viewable position. It's all built in and I use this feature all the time. It works with text fields and text views. Just remember to comment out the number of sections, number of rows per section, and the cell for index path methods so the static cells will work correctly. It saves a lot of coding.
HTH
Rob

How to do "pushviewcontroller" animation WITHOUT using pushviewcontroller?

I want to do animation like NavigationController pushviewcontroller's animation.
but I don't have a NavigationController, I don't want to make it.
So I want to ask is it possible to do it's animation in UIViewController? thanks!
oh forgot to say, I'm trying to switch view after clicking button.
Using presentModalViewController now, but I don't like it's animation..
You could animate the origin property of your sub view, make it decreasing along the x axis just after adding it to the main view.
EDIT :
Use something like this :
// retrieve the screen bounds
CGRect sBounds = [[UIScreen mainScreen] bounds];
// the origin point is just on the right of the screen
CGRect newFrame = CGRectMake(sBounds.size.width,
0.0,
sBounds.size.width,
sBounds.size.height);
// set your view frame
[mySecondView setFrame:newFrame];
// add it to the main view
[mainView addSubview:mySecondView];
// then animate your view
[UIView animateWithDuration:0.5 // set the interval you want
animations:^{
mySecondView.frame.origin = sBounds.origin;
}
];
I would use a navigation controller and hide the navigation bar, but as you don't want to do that you can switch between views using [UIView beginAnimantion: #"myAnimation" withContext: nil]; and changing the frame or the origin.

Resources