I'm creating a custom selected-animation for my UITableView.
What I've got is a standard UIViewController that displays a UITableView, which upon didSelectRow:AtIndexPath: instantiates another VC from storyboard, prepares it and displays it modally using UIModalPresentationOverCurrentContext.
Then the second VC updates a constraint from the top layout guide to a UIImageView, hence placing it exactly where the cell was located on screen. This coordinate is passed from the first VC.
Next, this UIImageView slides up, creating the illusion that the cell slides up. However, this produces a flicker for a split second just before sliding the image quite often (not always, like 3/4 times).
What's a better way to position the UIImageView initially, before it's animated?
Here's my code doing this:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:YES];
self.imageViewTopConstraint.constant = self.cellFrame.origin.y;
[self.view layoutIfNeeded];
self.imageView.image = self.image;
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:YES];
/* animate the opening */
[UIView animateWithDuration:0.45 delay:0 usingSpringWithDamping:1.0f initialSpringVelocity:1.2f options:UIViewAnimationOptionTransitionNone animations:^{
/* slide image */
self.imageViewTopConstraint.constant = 0.0f;
[self.view layoutIfNeeded];
}completion:^(BOOL finished) {
}];
}
Related
I have UIViewController, there is some animation in these UIViewController. UIView that animates on this UIViewController has different start and end position. I also have segue, that will push new UIViewController to the scene. All works okay, but if I return to my first UIViewController, position of my UIVIew changes to start position.
How can I fix this?
- (void)viewDidLoad
{
[super viewDidLoad];
[UIView animateWithDuration:0.3 animations:^{
self.myView.center = CGPointMake(self.myView.center.x,self.ahotherView.center.y);
}completion:^(BOOL complete){
}];
}
- (void)viewWillLayoutSubviews {
[super viewWillLayoutSubviews];
self.myView.center = CGPointMake(self.myView.center.x, self.ahotherView.center.y);
}
Autolayout is causing it to return to the beginning position. You can either turn it off or write a bit of code to override the constraints.
You'll have to first add constraints in Storyboard, which is an entirely different question in itself. I recommend doing some research. Once you've added the proper constraints and created IBOutlets for them, you'll be changing the values of the constraint constants, then calling [self.myView layoutIfNeeded] inside your animation block. Instead of changing the center property. Might look something like this:
//update constraint values
self.topConstraint.constant = 70;
self.bottomConstraint.constant = 20;
//animate
[UIView animateWithDuration:0.5f animations:^{
[self.myView layoutIfNeeded];
} completion:^(BOOL finished) {
}];
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
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!
I have a custom subclass of UITableViewCell. On my storyboard, it basically has a containerView (UIView *), that has some labels on it, and a UISwitch. I add a gesture to slide my tableViewCell to the right to show some detail controls underneath the containerView, and then another gesture to slide the original view back. The animation code looks like this:
- (void)showCustomControls:(UISwipeGestureRecognizer *)gesture {
[UIView animateWithDuration:0.3 animations:^{
[self.containerView setFrame:CGRectMake(self.frame.size.width - kTableViewCellSwipedWidth, 0, self.frame.size.width - kTableViewCellSwipedWidth, kOverviewTableViewCellHeight)];
}completion:^(BOOL finished) {
self.isReveal = YES;
self.selectionStyle = UITableViewCellEditingStyleNone;
}];
}
- (void)hideCustomControls:(UISwipeGestureRecognizer *)gesture {
[UIView animateWithDuration:0.3 animations:^{
[self.containerView setFrame:CGRectMake(-kTableViewCellBounceWidth, 0, self.frame.size.width, kOverviewTableViewCellHeight)];
}completion:^(BOOL finished) {
[UIView animateWithDuration:0.1 animations:^{
[self.containerView setFrame:CGRectMake(-kTableViewCellBounceWidth / 2, 0, self.frame.size.width, kOverviewTableViewCellHeight)];
}completion:^(BOOL finished) {
[self.containerView setFrame:CGRectMake(0, 0, self.frame.size.width, kOverviewTableViewCellHeight)];
}];
self.isReveal = NO;
self.selectionStyle = UITableViewCellSelectionStyleBlue;
}];
}
I noticed if I left the selectionStyle on, with the row open, it looked kind of funny. So I wanted to turn the selectionstyle off. So the code in there is to turn on and off the selection style when the row is open or closed. It works, however the weird thing is, when hideCustomControls gets called, and my original row slides back into place, the labels on the container view all go away. I have a little edge of the original row that shows, and when that is showing, the labels are there. But the second that the row slides all the way in place, all the labels disappear. The UISwitch which is also on the containerView never has problems though. It is always present. Is there a reason why this may be happening? When I remove the selectionStyle code in both methods, this problem goes away. Thanks in advance.
Edit:
I put in the correct code to animate using NSLayoutConstraint. Now the problem is the UIView subclasses I have on the view underneath show through to the top view when my row slides back into place. Not sure why this happens...
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];
}];
}