I want to use Storyboards to design content for a slider, and it seems like an easy way to design offscreen content is to use a childViewController. So I've done this
myViewController = [[UIStoryboard storyboardWithName:#"ipad" bundle:NULL] instantiateViewControllerWithIdentifier:#"keyPadOffScreen"];
[self addChildViewController:myViewController];
[myViewController didMoveToParentViewController:self];
newView = myViewController.view;
[self.view addSubview:newView];
And that adds the entire view controller over top of my root view. The problem is, I only want one of the subviews to show up, not the whole view. I can handle the animation, as long as I know how to add the root view. I tried this to just add the subview (sliderView is the name of the subview I want) instead of the whole view, but that did nothing
newView = myViewController.sliderView;
[self.view addSubview:newView];
Should I be using a different strategy?
EDIT: this DOES work, but it seems silly - setting the views size to just be the size of the subview.
newView.frame = CGRectMake(newView.frame.origin.x, newView.frame.origin.y, newView.frame.size.width, **myViewController.sliderView.frame.size.height**);
It does seem a bit overkill for just a view. Once you start doing a lot of custom view/animation/transition stuff it's often easier to implement in code, or at least it is for me since I've been doing it that way for a long time.
But maybe you want to stick with Storyboards. I respect that. And if you have a few developers working on this then it's important to keep some uniformity to how you set up your UI.
Instead of keeping it in a separate view controller and adding it when you need it to animate on-screen, simply add it to your existing view controller and either set it to hidden, or set it's alpha to 0.0 in IB. Then your animation can undo that and make it visible.
you can use custom segue here, for instance:
#implementation FRPresentEnteringPopupSegue
- (void)perform
{
FirstVC *toVC = self.destinationViewController;
SecondNavigationController *fromVC = self.sourceViewController;
toVC.view.frame = CGRectMake(0.0, 0.0, 300.0, 135.0);
toVC.view.center = CGPointMake(fromVC.view.bounds.size.width/2, fromVC.view.bounds.size.height + toVC.view.bounds.size.height/2);
[fromVC.view addSubview:toVC.view];
[toVC viewWillAppear:YES];
[UIView animateWithDuration:0.5
delay:0.0
usingSpringWithDamping:0.7
initialSpringVelocity:0.5
options:UIViewAnimationOptionBeginFromCurrentState
animations:^{
toVC.view.center = CGPointMake(fromVC.view.bounds.size.width/2, fromVC.view.bounds.size.height/2);
}completion:^(BOOL finished) {
[toVC viewDidAppear:YES];
}];
}
#end
make your UIStoryboardSegue subclass
override - (void)perform method with your custom view appearance code
use segue usual way
Related
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];
When I change my UIBarButtonItems they change abruptly, unlike the default which gives a nice but speedy fade animation. You can see this when segueing between view controllers, for instance, the back button will fade in and out. How can I simulate the same effect?
Update - Based on this answer - https://stackoverflow.com/a/10939684/2649021
It looks like you would have to do something like this to make the button itself fade out.
[self.navigationItem setRightBarButtonItem:nil animated:YES];
And do something like this to make it fade in
[self.navigationItem setRightBarButtonItem:myButton animated:YES];
Otherwise if you want more control over animation properties you would have to create a custom view I believe.
EDIT: I just confirmed that you can fade a UIBarButtonItem custom view using this.
As a test I created a simple project and dropped a UIBarButtonItem onto the navigation bar. I created an outlet to the view controller. In viewDidLoad on the view controller I setup a custom view
-(void)viewDidLoad{
[super viewDidLoad];
UILabel *lbl = [[UILabel alloc]initWithFrame:CGRectMake(0,0,40,40)];
lbl.text = #"test";
UIView *customView = [[UIView alloc]initWithFrame:CGRectMake(0,0,40,40)];
[customView addSubview:lbl];
self.barButtonItem.customView = customView;
}
As a test in viewDidAppear I animated it
-(void)viewDidAppear:(BOOL)animated
{
[UIView animateWithDuration:3.0
delay:3.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
self.barButtonItem.customView.alpha = 0;
}
completion:^(BOOL finished) {
NSLog(#"animation complete");
}];
EDIT: Here's a link to Apples Documentation for a full explanation of UIView animations.
https://developer.apple.com/library/ios/documentation/windowsviews/conceptual/viewpg_iphoneos/animatingviews/animatingviews.html
You might have to create a custom bar button item using a view or an image, and then animate the properties of the view as digitalHound shows.
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!
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!
In fading a view controller in from black, I am doing the following within viewDidLoad:
Creating a UIView with a black background;
Giving the UIView an alpha value of 1.0f;
Adding the UIView as a subview of [self view];
Fading the black UIView out via animateWithDuration by changing its alpha value to 0.0f; and
Removing the black UIView from [[self view] subviews]
More often than not, this works as planned. Occasionally, however, I see a glimpse of the view controller I want initially hidden, just before the black UIView is drawn.
Is there a way to avoid this? Is there a better method to place this code in than viewDidLoad?
Many thanks
Yes, add the view in the loadView method and do the actual animation in viewDidLoad or viewDidAppear. Or do as the above commentor said and simply use the view alpha.
I would create the UIView that I want hidden in UIViewController's nib file, then link that to via an IBOutlet
#interface SomeViewController: UIViewController
{
IBOutlet UIView *blackView;
}
then in UIViewController's -(void) viewDidLoad; method, I would do the following
- (void)viewDidLoad
{
[super viewDidLoad];
// Fade the opacity of blackView over 1 second,
// then remove it from the view controller.
[UIView animateWithDuration:1
delay:0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
blackView.layer.opacity = 0;
}
completion:^(BOOL finished) {
// This line prevents the flash
blackView.layer.opacity = 0;
[blackView removeFromSuperview];
}];
}