I'm writing a custom UITableView with custom UITableViewCells. An unfortunate side effect of this, as many devs have found out, is that when the table starts editing, the content (UILabel in my case) isn't automatically moved. So, here's the code I've written to deal with it located in the UITableViewCell custom subclass:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
if (animated) {
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGRect titleFrame = self.titleLabel.frame;
if (editing) {
[self.titleLabel setFrame:CGRectMake(15, titleFrame.origin.y, titleFrame.size.width, titleFrame.size.height)];
} else {
[self.titleLabel setFrame:CGRectMake(25, titleFrame.origin.y, titleFrame.size.width, titleFrame.size.height)];
}
}completion:nil];
}
Unfortunately, I can't seem to figure out the correct options to use as no combination I've tried has worked right. Without UIViewAnimationOptionBeginFromCurrentState the UILabel shifts to the far left before beginning the animation, which is not desirable. And without UIViewAnimationOptionBeginFromCurrentState the UILabel performs the animation fine, but then has a small rubberband effect on the end.
Any ideas as to what's going on?
Thanks.
EDIT:
I've figured out whats happening! Not sure how to fix it, but now I now exactly what the question is!
Firstly, I had to uncheck 'use Autolayout' to get the cell to move automatically.
The labels initial x value is 25 when not editing. When editing, if the animation takes effect without moving the label myself, it moves to 25 points right of the left accessory item. The reason the first line there says 15:
if (editing) {
[self.titleLabel setFrame:CGRectMake(15 ...
is because that 25 point gap is pretty wide, and doesn't look right. So the animation, with the code in place, moves the label to the 25 mark, then back to the 15 mark, creating that rubber banding effect.
In trying to remedy this problem, I've tried all sorts of things like moving the [super setEditing:...] call below the code, always setting the animated argument of that call to NO... on and on and no luck. The solution is to somehow make the label animate over a shorter distance than the x value, or (as I was originally attempting to do) create the animation locally to send it to a specific x value and have it stay there.
My instincts tell me that it's a problem with the chain reaction of [setEditing ] calls that happens with the UITableView where the animation of the contentView (possibly) is happening before my code block, thus moving the label over and then my code executes, moving it back.
Note: the behavior occurs whether or not the label is a subview of contentView.
You haven't shown it in the question but it looks like you're adding your titleLabel directly to the cell.
Add your custom content to the cell's contentView instead, with appropriate resizing masks, and it will resize for you during the normal transition to editing mode with no need to override any methods. That's how it works with the standard label and image view. The content view is resized to allow the editing accessories to be shown, and the content view's subviews resize automatically.
try following code
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
[self beginUpdates];
if (animated) {
[UIView animateWithDuration:0.5 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGRect titleFrame = self.titleLabel.frame;
if (editing) {
[self.titleLabel setFrame:CGRectMake(15, titleFrame.origin.y, titleFrame.size.width, titleFrame.size.height)];
} else {
[self.titleLabel setFrame:CGRectMake(25, titleFrame.origin.y, titleFrame.size.width, titleFrame.size.height)];
}
}completion:nil];
[self endUpdates];
}
Related
I use a UICollectionView with UICollectionViewFlowLayout. I change the height of a UICollectionViewCell if a user touches it. To achieve that I use performBatchUpdates:to animate the height change. This works perfectly and the change gets animated with a standard grow and shrink animation.
But I would like to change this standard animation. How would I do that? I can't seem to find any hint on how to influence the animation type used.
Thanks to the hint here https://stackoverflow.com/a/15068865/956433 it is possible to wrap the performBatchUpdates: into a UIView animation block. So to change the standard grow and shrink animation to a more fancy spring animation you could implement the following:
[UIView animateWithDuration:0.5
delay:0.0
usingSpringWithDamping:0.5
initialSpringVelocity:0.0
options:0
animations:^{
[collectionView performBatchUpdates:^{
// your changes to the data
} completion:nil];
} completion:nil];
I've read many questions that state in order to animate constraint changes, we just need to...
self.someConstraint.constant = 0.f;
[UIView animateWithDuration:0.5f animations:^{
[self layoutIfNeeded];
}];
Recently, I've read that custom view subclasses should use the -(void)updateConstraints method on a UIView to perform constraint updates in case there are many property changes that occur that would cause you to do the above code block in multiple places, which would lead to many layout passes that are unneeded.
UIView -(void)updateConstraints documentation
I actually like this approach where we update all constraints. Some simple code of...
- (void)updateConstraints
{
if(someCondition)
{
self.someConstraint.constant = 0.f;
}
//Maybe some other conditions or constant changes go
here based on the current state of your view.
[super updateConstraints];
}
At this point, my original constraint animation code now only becomes
[self setNeedsUpdateConstraints];
The only problem with this is I've now lost the ability to animate these changes.
I've looked over the view documentation to find a way to animate these changes to the constraints, but I can't seem to find an appropriate method to override to call [self layoutIfNeeded] in an animation block. Even in this case, I feel it should be unnecessary to call that because a layout is going to be performed, and I feel I shouldn't have to force it right then and there.
Does anyone know HOW to cause these constraint changes to animate?
You need to call [self updateConstraintsIfNeeded]. So your code would look like this...
[self updateConstraintsIfNeeded];
[UIView animateWithDuration:0.25 animations:^{
[self layoutIfNeeded];
}];
I'm using a custom UICollectionview Layout that displays the items in a circle. On a swipe up I reset the constraints on my collectionview and animate those constraint changes. I would like my items to animate with the constraint change so it's one smooth experience.
I get them to animate but only after the constraint change (I think), it looks messy and strange, check the screencast out here: http://cl.ly/ZmA0
This is the code in the swipe gesture method:
- (void)foo:(UISwipeGestureRecognizer *)gesture {
[self.view layoutIfNeeded];
[self.collectionView mas_remakeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(self.view.mas_width);
make.height.equalTo(self.collectionView.mas_width);
make.top.equalTo(self.view.mas_top).offset(50);
}];
[UIView animateWithDuration:1.0 animations:^{
[self.view layoutIfNeeded];
}];
[self.collectionView performBatchUpdates:^{
CircleLayout *layout = (CircleLayout *)self.collectionView.collectionViewLayout;
} completion:nil];
}
Any suggestions?
I'm not sure what MASConstraintMaker is (and the video is not working).
But within the performBatchUpdates block you are getting the collection view layout. What are you doing with it? it unused. Maybe that can point you to the right solution.
I'm trying to move a simple uiview 30 points up. However, no matter what I do I can't move it.
I'm developing in iOS 7.1
Now, I finally found out that I have auto layout on, however I don't have any constrains for this view. It was a simple drag from the object library and place in a view controller. I've tried various things to get it to move. I can animate properties, such as alpha and what have you but I can't for the life of me move it.
Below is what I've tried:
[UIView animateWithDuration:1.0f animations:^{
CGREct oldFrame = self.viewWantingToMove.frame;
self.viewWantingToMove.frame = CGRectMake(self.viewWantingToMove.frame.origin.x, self.viewWantingToMove.origin.y, self.viewWantingToMove.size.width, self.viewWantingToMove.width.height);
});
This doesn't work
[UIView animateWithDuration:1.0f animation:^{
self.viewWantingToMove.center = CGPoint(self.viewWantingToMove.frame.origin.x, self.viewWantingToMove.frame.origin.y-30
}];
Again nadda...
After I figured it might have something with auto layout, mind you I can't turn it off because I have to use it, I then tried this.
[self.viewWantingToMove layoutIfNeeded];
[UIView animateWithDuration:1.0f animation:^{
self.viewWantingToMove.center = CGPoint(self.viewWantingToMove.frame.origin.x, self.viewWantingToMove.frame.origin.y-30
[self.viewWantingToMove layoutIfNeeded];
}];
This gives me animation...... BUT backwards. it starts off 30 points up and then moves back to it's original position. Can anyone help me with this? Its getting to one of those moments where you feel like biting the monitor.
When you're using auto layout, you shouldn't be setting any frames. To move or resize views, you should modify the constraints instead. The fact that you didn't add any constraints does mean they're not there. The system adds default constraints for you. You should set the constraints you want, so you know what they are (try ones to the top of the view, to the left side, and ones for height and width). Make an IBOutlet to the constraint to the top of the view (lets call it topCon), then you can modify that one in code to animate it,
[UIView animateWithDuration:1.0f animation:^{
self.topCon.constant -= 30;
[self.view layoutIfNeeded];
}];
It's kind the weird I'm facing right now, I know that adding constraints in an AutoLayout IB is simple. But I really couldn't figure-out why my simple constraints are not working when I'm resizing (ZoomIn Animation) a UIView.
The View I'm targeting to zoomIn works fine, but the subviews within it is not being positioned well. It seems that the constraints are not working.
From this setup, where in the Yellow Boxes are UIViews and the Green Boxes are UIImageViews.
When I tapped on a Yellow Box, it should zoomIn, as what is shown on the photo below it.
These are the constraints:
This should be the expected resulting upon zoomIn AnimationBut I got no luck, The yellow box zoomIn but the Green Box is let on its old position and did not changed size.
I put a leading, trailing, topSpace, and bottomSpace from Yellow Box to Green Box already.
My code for resizing the yellow box is:
- (void) resizeViewWithZoomInAnimation: (UIView*)view duration:(float)secs option:(UIViewAnimationOptions) option {
//sets the new width and height of deal's view
float newWidth = view.superview.frame.size.width - 20;
float newHeight = view.superview.frame.size.height - 20;
[UIView animateWithDuration:secs delay:0.0 options:option
animations: ^{
switch ([view tag]) {
case 1:
[view setFrame:CGRectMake(view.frame.origin.x, view.frame.origin.y, newWidth, newHeight)];
break;
case 2:
[view setFrame:CGRectMake(CGRectGetMaxX(view.frame),CGRectGetMinY(view.frame), -newWidth , newHeight)];
break;
case 3:
[view setFrame:CGRectMake(CGRectGetMinX(view.frame),CGRectGetMaxY(view.frame), newWidth , -newHeight)];
break;
case 4:
[view setFrame:CGRectMake(CGRectGetMaxX(view.frame),CGRectGetMaxY(view.frame), -newWidth, -newHeight)];
break;
}
}
completion:nil];
}
This should be the expected resulting upon zoomIn Animation:
When trying to animate with Auto Layout, you should not change view's frames directly. Instead, you should animate changes in constraints.
To access constraints from storyboard you can simply create IBOutlets for them.
[containerView layoutIfNeeded]; // complete pending layout operations (optional)
[UIView animateWithDuration:1.0 animations:^{
// Update constraints
self.viewConstraint1.constant = NEW_VALUE;
self.viewConstraint2.constant = NEW_VALUE;
// Animate the changes
[containerView layoutIfNeeded];
}];
Note that you usually want to call layoutIfNeeded on the super view(or higher) of the view you are trying to animate.
I recommend reading more on Auto Layout in Apple's official guide.
A tweak to the above answer, I used from within a UIViewController
[self.view layoutIfNeeded];
[UIView animateWithDuration:.5
animations:^{
myConstraint.constant = 64;
[self.view layoutIfNeeded];
}
completion:^(BOOL finished) {
// whatever
}];
Changes of note: Apple docs recommend using layoutIfNeeded, and calling this both before and within the animation block. This is so that you can be sure the animation stack is cleared before beginning.
Also, you are to call layoutIfNeeded on the parent view of the constraint you will be animating, not on the view who owns the constraint. Thanks to #nkukushkin for the initial answer.
It's also important to have your constraints set up correctly. The 'fix constraints' tool in IB won't necessary set them up in a way that works correctly for animating. For me I had to go through them and think through how I wanted the dependencies to work, which is of course expected but might not be obvious to the beginner (and results in errors).
When you are using constrains, pre-constrains are generated. When you act on position and size, remove constrains while animating the view or do following:
yourView.translatesAutoresizingMaskIntoConstraints = YES;