I'm making a layout editor for a comic book app. The page of the comic book is divided into rows and then each row has at least one column. When the user performs a vertical swipe over a column, it's supposed to divide into two separate columns. This part already works fine, but I wanted to add a simple curl-up transition for that using +transitionWithView:. Everything still works fine (the column divides nicely) but there's no animation. Here's the code:
- (void)divideColumnView:(CCLayoutColumnView *)columnView atPoint:(CGPoint)_divisionPoint {
// divides a single column into two smaller ones
// define frames for new columns
CGRect frameLeft = columnView.frame;
CGRect frameRight = frameLeft;
CGFloat width = _divisionPoint.x - frameLeft.origin.x;
frameLeft.size.width = width;
frameRight.size.width -= width;
frameRight.origin.x += width;
// set up the container for transition
UIView *rowView = [columnView superview];
UIView *containerView = [[UIView alloc] initWithFrame:columnView.frame];
[rowView addSubview:containerView];
// move the column to the container
columnView.frame = [containerView convertRect:columnView.frame fromView:rowView];
[containerView addSubview:columnView];
// set up columns that will be inserted to the container during transition
CCLayoutColumnView *leftColumnView = [[CCLayoutColumnView alloc] initWithFrame:[containerView convertRect:frameLeft fromView:rowView]];
CCLayoutColumnView *rightColumnView = [[CCLayoutColumnView alloc] initWithFrame:[containerView convertRect:frameRight fromView:rowView]];
[UIView transitionWithView:containerView
duration:0.5
options:UIViewAnimationOptionTransitionCurlUp
animations:^{
[columnView removeFromSuperview];
[containerView addSubview:leftColumnView];
[containerView addSubview:rightColumnView];
}
completion:^(BOOL finished){
// add columns to their row
leftColumnView.frame = frameLeft;
rightColumnView.frame = frameRight;
[rowView addSubview:leftColumnView];
[rowView addSubview:rightColumnView];
// clean up
[containerView removeFromSuperview];
[containerView release];
[leftColumnView release];
[rightColumnView release];
}];
}
I already checked the geometry of all views and it seems ok. Also, the completion block is executed properly.
What may be the problem?
I was also stuck at animating views with transitionWithView.
I don't know the hierarchy of your project, but if it is possible for you to create a separate class for the view you want to animate with this, it would be easy:
[UIView transitionWithView:self.view duration:1.0 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{} completion:^(BOOL f){}];
works..i.e. this function works properly with self.view.
I hope it helps.
Related
I'm using the following code to attempt to flip a UIView. It's a playing card. There's a container view, with two children, front and back. I've read in similar questions here that this should be the correct way to do it, but the actual flip itself is not being performed.
I'm intending for this to animate moving the card to the centre of the screen, and flip it from its back to front. The movement to centre is fine, but the flip never occurs.
If I change the View: argument to self, it flips the entire view controller, and if I leave it on the cardContainer, it does nothing at all.
Puzzled :(
UIView *cardContainer = [[UIView alloc] initWithFrame:CGRectMake(20, self.view.frame.size.height + 20, [Card size].width, [Card size].height)];
[self.view addSubview:cardContainer];
Card *card = _playerOneCards[_playerOneNextCardIndex];
card.frame = CGRectMake(0, 0, [Card size].width, [Card size].height);
card.delegate = self;
[cardContainer addSubview:card];
CardBack *back = [[CardBack alloc] initWithFrame:CGRectMake(0, 0, [Card size].width, [Card size].height)];
[cardContainer addSubview:back];
[UIView transitionWithView:cardContainer
duration:duration
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
back.alpha = 0.0f;
cardContainer.center = self.center;
}
completion:nil];
Try this. The container view has to be placed in the hierarchy before the animation starts.
//Add container views and subviews
[CATransaction flush];
[UIView transitionWithView:cardContainer
duration:duration
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
back.alpha = 0.0f;
cardContainer.center = self.center;
}
completion:nil];
You can also wrap your function up in a dispatch_after call.
For more information check : http://andrewmarinov.com/working-with-uiviews-transition-animations/
Setup: have a containerView on screen inside a UIViewController (for the sake of simplicity let's say containerView takes up the whole screen).
Problem: create and add an overlayView as a subView to containerView and animate the appearance so that it would animate into position from the right as a response to a user action.
UIView *overlayView = [[UIView alloc] initWithFrame:containerView.frame];
overlayView.translatesAutoresizingMaskIntoConstraints = NO;
[containerView addSubview:overlayView]; // Can't add constraints before inserting into view hierarchy
Approach: tie the leading edge of the overlayView to the leading edge of the containerView with a constraint that I'll call overlayLeadingConstraint. Set overlayLeadingConstraint.constant to the width of containerView so that it would be initially positioned just off screen to the right.
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|[overlayView(width)]" options:0 metrics:metrics views:views];
[containerView addConstraints:constraints];
NSLayoutConstraint *overlayViewLeadingConstraint = [constraints objectAtIndex:0];
overlayViewLeadingConstraint.constant = containerView.frame.size.width;
// Height constraint not shown for simplicity
Animation: now onto the real problem; animate overlayView into position from the right. First approach is something like this:
overlayViewLeadingConstraint.constant = 0.0;
[UIView animateWithDuration:1.0 animations:^{
[containerView layoutIfNeeded];
}];
But this does not work as all the code above would execute on the same run loop and so only the end result would be shown.
Second approach: try to defer the animation to a future run loop until after the initial layout after addSubview: has already taken place.
dispatch_async(dispatch_get_main_queue(), ^{
overlayViewLeadingConstraint.constant = 0.0;
[UIView animateWithDuration:1.0 animations:^{
[containerView layoutIfNeeded];
}];
});
This does not work either and it hints that addSubview: and the setup of constraints can take up multiple run loops.
Third approach: try to delay the animation even further: several run loops into the future.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
overlayViewLeadingConstraint.constant = 0.0;
[UIView animateWithDuration:1.0 animations:^{
[containerView layoutIfNeeded];
}];
}];
The time delay is small, but it allows several run loops to complete before the animation and this seems to achieve the desired effect.
Questions:
The above approach seems to be a workaround and not the real solution. So I'm wondering if there is a better approach to this. I have thought about using the hosting viewController's viewDidLayoutSubviews method to get to know when overlayView is in place and it's ok to fire up the animation, but the documentation explicitly advises against this:
However, this method being called does not indicate that the individual layouts
of the view's subviews have been adjusted. Each subview is responsible for
adjusting its own layout.
I'm starting to think that Apple's idea was to have all subViews added at initialization time and just hide those that you don't need immediately. So that when the time comes to animate a subView it would already be a member of the view hierarchy tied up properly with constraints.
How would you do it? Any input is much appreciated.
I'm not sure what you're doing with some of the things you don't show, but this code, worked fine for me. I made a view in IB (containerView) that was 200 x 200, and used this code,
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIView *overlayView = [UIView new];
overlayView.backgroundColor = [UIColor redColor];
overlayView.translatesAutoresizingMaskIntoConstraints = NO;
[containerView addSubview:overlayView];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|[overlayView]|" options:0 metrics:nil views:#{#"overlayView":overlayView}];
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[overlayView]|" options:0 metrics:nil views:#{#"overlayView":overlayView}];
[containerView addConstraints:constraints];
[containerView addConstraints:constraints2];
NSLayoutConstraint *overlayViewLeadingConstraint = [constraints objectAtIndex:0];
overlayViewLeadingConstraint.constant = containerView.frame.size.width;
[containerView layoutIfNeeded];
overlayViewLeadingConstraint.constant = 0.0;
[UIView animateWithDuration:1.0 animations:^{
[containerView layoutIfNeeded];
}];
}
This animated correctly. Notice that I put in a call to layoutIfNeeded before the animation block. That may or may not be necessary in your case, depending on some aspects of your code that you didn't show.
I don't know if using [constraints objectAtIndex:0] to get the constraint you want to modify is safe; it worked in this case, but I don't know if the order of the constraints set up with the visual format is guaranteed.
You have not added anything to the animations block that will animate. The animations block is generally considered to be the "I want my UI to end up in this state; animate stuff to make that happen" block.
[UIView animateWithDuration:1.0 animations:^{
overlayViewLeadingConstraint.constant = 0.0;
}];
I have a readonly UITextView, and I'm updating the text.
When the new text appears, I want it to animate as follows: New text slides in from the right, as the old text slides offscreen toward the left side.
Is it possible to do this with transitionWithView? If not, what's the best way to do it? I can make it do a CrossDissolve, but that's not the animation I'm looking for:
[UIView transitionWithView:self.storyTextView
duration:0.5
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^{
[self.storyTextView setText:withStory.storyText];
}
completion:nil];
The reason for insisting on the right to left animation is because eventually I want the user to be able to trigger it by swiping toward the left on the UITextView.
CATransition will allow you to do this, with a transition type of 'push' and a subtype of 'from right'. It's very straightforward to use:
CATransition *transition = [CATransition new];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
// Make any view changes
self.textView.text = newText;
// Add the transition
[self.textView.layer addAnimation:transition forKey:#"transition"];
Note that this, while it's what you've asked for, won't fit nicely with a gesture without some extra tinkering - to tie it to a gesture you'd need to do something like set the layer speed to 0 and the manually update the progress of the animation to match your gesture.
What about just using UIView animateWithDuration and slide the one textView to the right while sliding a new textView from the left. Here is an example just have to work out the positions. Let me know if this is what you were looking to do.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.textView2.frame = CGRectMake(1000.0,self.textView1.frame.origin.y,self.textView2.frame.size.width,self.textView2.frame.size.height);
}
- (IBAction)animate:(id)sender {
[UIView animateWithDuration:0.5 animations:^{
self.textView1.frame = CGRectMake(-200.0,self.textView1.frame.origin.y,self.textView1.frame.size.width,self.textView1.frame.size.height);
[UIView animateWithDuration:0.5 animations:^{
self.textView2.center = (CGPointMake(200, 200));
}];
}];
}
You can also try something like this.
You can create a containerView for your text view and then change the coordinates using UIView animations to give it a slide from right to left.
Please see code below.
First declare a container view and the make the required text view its subview.
In .h
#property (strong, nonatomic)IBOutlet UIView *containerView;
#property (strong, nonatomic)IBOutlet UITextView *childTextView; //I have set it as subview in xib
In .m make sure to set the following in viewDidLoad or in Xib
self.containerView.clipsToBounds = YES;
Gestures
UISwipeGestureRecognizer *recog = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(changeText)];
recog.direction = UISwipeGestureRecognizerDirectionLeft;
[self.containerView addGestureRecognizer:recog];
Beautification (for this you would need QuartzCore/QuartzCore.h)
self.containerView.layer.borderWidth = 1.0f;
self.containerView.layer.borderColor = [UIColor lightGrayColor].CGColor;
self.containerView.layer.cornerRadius = 5.0f;
To the Point
- (void)changeText
{
[UIView animateWithDuration:0.3f animations:^(void){
CGRect endPos = self.childTextView.frame;
endPos.origin.x -= endPos.size.width; //Move it out of the view's frame to the left
self.childTextView.frame = endPos;
} completion:^(BOOL done){
CGRect startPos = self.childTextView.frame;
startPos.origin.x += (2*startPos.size.width);// this will take it to the right.
self.childTextView.frame = startPos;
self.childTextView.text = #"Changed text";
[UIView animateWithDuration:0.2f animations:^(void){
CGRect displayPos = self.childTextView.frame;
displayPos.origin.x -= displayPos.size.width; // To compensate for the double +ve on top
self.childTextView.frame = displayPos;
}];
}];
}
I have a screen which contains several subviews. On the user's interaction, I want to add another subview, change the positions of the other subviews, and make them disappear at the same time. My code is:
[self.view addSubview:imageView];
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
for (UIView *currentItemView in self.currentItemViews) {
currentItemView.center = someCenter;
currentItemView.alpha = 0;
}
} completion:^(BOOL finished) {
}];
Before I added the code with the new subview everything worked fine, but now the only thing is animated is the setting of alpha. The subviews don't move.
Any idea why and how to solve this?
Thanks in advance!
EDIT:
The code for creating the currentItemViews array:
self.currentItemViews = [[NSMutableArray alloc] initWithCapacity:6];
for (Item *item in arrayOfItems) {
ItemView *itemView = [[NSBundle mainBundle] loadNibNamed:#"ItemView" owner:self options:nil].lastObject;
itemView.item = item;
[self.currentItemViews addObject:itemView];
[self.view addSubview:itemView];
}
The ViewController I'm in, is a childViewController to another one, so it's view is added as subview too. Could that be the problem?
I wrote a custom UITableViewCell - image view, button and three labels now I am trying to add some animations to it. So once I tap the button, it fades away and the spinner replaces the button. After two seconds the cell is overlaid with a red color, as a subview of cell fades in, then the indicator is removed and and red overlay starts fading back out. As well the button I previously removed fades back in.
(I could not phrase it a better way :P )
The method is :
-(void)rentButtonPressed:(id)sender
{
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[indicator startAnimating];
indicator.center = self.rentButton.center;
[UIView animateWithDuration:0.2
animations:^{self.rentButton.alpha = 0.0;}
completion:^(BOOL finished){
[self.rentButton removeFromSuperview];
[self addSubview:indicator];
}
];
UIView *overlay = [[UIView alloc] initWithFrame:self.backgroundImage.frame];
overlay.alpha = 0.0;
overlay.backgroundColor = [UIColor redColor];
[self.contentView addSubview:overlay];
[UIView animateWithDuration:0.4
delay:2.0
options:UIViewAnimationCurveEaseInOut
animations:^{
[indicator removeFromSuperview];
overlay.alpha = 0.4;
}
completion:^(BOOL finished){
[UIView animateWithDuration:0.4
animations:^{ overlay.alpha = 0.0; }
completion:^(BOOL finished)
{
[overlay removeFromSuperview];
}
];
[self.contentView addSubview:self.rentButton];
[UIView animateWithDuration:0.4 animations:^{ self.rentButton.alpha = 1.0;}];
[self.delegate didTryToRentMovieAtCell:self];
}
];
}
So the code does fade out the button, replace it with spinner and fades in the red overlay. The problem is, the red overlay does not fade away, but disappears same with the button, instead of fading in, it just appears.
During your animation, you are changing the view hierarchy by adding and removing subviews. The UIView class method animateWithDuration:animations:completion is intended only animating property changes in a view, and not for changing the view hierarchy.
Try using the UIView class method transitionWithView:duration:options:animations:completion: instead, and use the cell's content view as the "container."
This documentation is helpful in distinguishing between animating view property changes and animating view transitions, specifically the section "Changing the Subviews of a View":
http://developer.apple.com/library/ios/#documentation/windowsviews/conceptual/viewpg_iphoneos/animatingviews/animatingviews.html