I know that it works if I change the view's frame via the changing of a layer's transform (in this case it simply takes a view with all the subviews and works with it as if it is a single image). It is not good enough for me to use it because:
It is too hard to calculate the appropriate position;
I use HMLauncherView which has some logic based on frames not
transforms.
I tried a lot of ways described here but it seems they are all for autolayout because none of them works. Depending on the type of the autoresizing mask some elements jump to their destination position and some of them are not affected at all or affected via strange ways.
How to solve such issue? Are there ways to solve this via an autoresizing mask?
EDITED
Animation code (the same result is for setting of center/bounds):
[UIView animateWithDuration:0.3 animations:^{
lastLauncherIcon.layer.transform = CATransform3DIdentity;
CGRect r = self.bounds;
r.origin.y = MAX(0, MIN(self.scrollView.contentOffset.y, self.scrollView.contentSize.height - self.bounds.size.height));
launcherIcon.frame = r;
} completion:^(BOOL finished) {
...
}];
The code of layoutSubviews seems to be extra because I have already tried to subclass UILabel which always jumps to the animation destination position. The investigation showed that the frame of this label is set when I set the view's frame/bounds only. In other cases label's setCenter, setFrame and setBounds are called when you call them directly.
This question is very similar but the suggested solution doesn't work for my case. I also found out I had broken something so the view with transformation doesn't appear on iOS 7.1 (maybe 7.x) but works on 8.3.
Related
I'm simply trying to move two views upwards in the simplest manner, and I can't figure out how to make it work on iOS8 (but its works just fine on iOS7).
I've read that changes happened but I can't make it work anyway...
Here is the code i'm using :
CGRect txFrame; //the initial frame of my textview
CGRect btFrame; //the initial frame of my button
- (void)kbWillShow{
[UIView animateWithDuration:0.45 animations:^{
//Remembering the initial frames here
txFrame = _txReason.frame;
btFrame = _btSend.frame;
_lbTitleReason.alpha = 0.3;
//Animating
_txReason.frame = CGRectMake(txFrame.origin.x, txFrame.origin.y-55, txFrame.size.width, txFrame.size.height);
_btSend.frame = CGRectMake(btFrame.origin.x, btFrame.origin.y-75, btFrame.size.width, btFrame.size.height);
}completion:nil];
}
- (void)kbWillHide{
[UIView animateWithDuration:0.45 animations:^{
//Putting them back to their original positions.
_txReason.frame = txFrame;
_btSend.frame = btFrame;
_lbTitleReason.alpha = 1;
}completion:nil];
}
I've tried putting the "result" position in the completion block but I just does a very abrupt and weird movement, and after the duraton the view teleports to the intented position. (Which is better than doing an abrupt movement and ending up at the exact same position as it started).
This should be simple, what am I doing wrong?
I have come across this and the answer is to do with the layout constraints. You need to change the constant properties of the layout constraints that affect the view you want to change.
E.g. to change the width of a view with a width NSLayoutConstraint you would call constraint.constant = newWidth; then go ahead with your animation code. This is how I'm doing it and it works fine.
EDIT: your animation code is fine. You don't need to play with the centre property.
I have an animation which is kicked off when a gesture recogniser (double tap) fires:
[UIView animateWithDuration:0.3 animations:^{
_scrollView.contentOffset = CGPointMake(x, y);
_scrollViewContentView.frame = someFrame;
_scrollViewContentView.layer.transform =
CATransform3DMakeScale(1.f/_zoomScale, 1.f/_zoomScale, 1.f);
}];
It's working nice except in one case : if scrollViewWillBeginDecelerating delegate method is not called before animation execution (just by dragging hardly my scrollview).
I just have scrollViewDidEndDragging method called.I can wait 20 sec and then play my animation. It'll play correctly except for my contentOffset.
The delegate methods themselves do nothing, they were only added to see where the problem might be.
I have no idea why.
Edit : here's a video of my problem. Phase 1 : scroll with deceleration and phase 2 without. Look a the final position. Phase 1 is correct but not phase
try:
[_scrollView setContentOffSet:CGPointMake(x, y) animated:YES];
outside of the animation block. I don't believe contentOffSet is animatable through UIView's animation block.
Edit:
Instead of:
_scrollview.contentOffSet = CGPointMake(x, y);
try:
_scrollview.bounds = CGRectMake(x, y, CGRectGetWidth(scrollview.bounds), CGRectGetHeight(scrollview.bounds));
I suspect that x in this line may be causing the effect that you see:
_scrollView.contentOffset = CGPointMake(x, y);
What's it's value? I think you should try replacing that by CGPointMake(0, y) and see if that causes the white margin to appear every time.
My reasoning is the following:
1) to create bouncing effect that doesn't go outside the scope of the image (doesn't show white background), you must have set _scrollView's contentSize.width less than the actual image size.
2) to allow some scrolling you have set the contentSize.width greater than the width of the _scrollView
3) you have centered the image within the _scrollView relatively to the size of it's contentSize
4) when animating you are probably setting frame with pre-calculated x coordinate to position the image on the left size because without that CATransform3DMakeScale would just leave the image in the center (by the way, why use CATransform3DMakeScale, when you could just change the size in the frame?)
5) in the video, when running the animation the first time, you have contentOffset.x to it's maximum value (because you drag to the left therefore increasing the content offset and scrollview bounces back to it's max content offset that doesn't go beyond content size limits)
6) in the video when running the animation for the second time, you have contentOffset.x with a smaller value because you drag to the right by decreasing it
7) when animation is finished, you still have the same content size for your scrollView and therefore the same amount of scroll available. If you have the contentOffset.x at it's maximum value then image will be more on the left, if you have contentOffset.x with less value - image will be more on the right
8) if the x in _scrollView.contentOffset = CGPointMake(x, y); is related to actual content offset prior to animation then that makes sense that the image appears more on the left in the first case and more on the right in the second case.
If I'm right then this is how you could solve it:
a) If you want the image always to appear on specific x position, make sure to set constant contentOffset in the animation.
b) Adjust the code of calculating frame (someFrame) origin according to the contentOffset that you'll be animating to (looks like now it works only with the maximum contentOffset.x).
c) If after animation you don't want the content to be scrollable, make sure to set _scrollView.contentSize = _scrollview.bounds.size; in the animation block.
I'm not sure I completely understand your problem (Maybe you can try to explain what you want to accomplish in this animation?). Any way, maybe try this instead of using setContentOffset:
CGRect *rect = CGRectMake(x, y, 0, 0);
[_scrollView scrollRectToVisible:rect animated:YES];
Perhaps try using core animation rather than UIView animations. It won't make a difference if the reason you're running into trouble is because all animations are being removed from the layer, but if it's due to -[removeAnimationForKey:#"bounds"], this might be a winner.
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"bounds"];
animation.fromValue = [NSValue valueWithCGRect:_scrollView.layer.bounds];
animation.toValue = [NSValue valueWithCGRect:(CGRect){ .origin = CGPointMake(x,y), .size = scrollView.layer.bounds.size }];
[scrollView.layer addAnimation:animation forKey:#"myCustomScroll"];
scrollView.layer.bounds = (CGRect){ .origin = CGPointMake(x,y), .size = scrollView.layer.bounds.size };
P.S. I haven't tested the code above it's just off my head, so apologies for any silly errors.
In my app that I've been working on for about 6 months now, I've seen a very weird bug in a few places relating to drawing of views. I've built 20+ ios apps, and this is the only project where I've seen anything like this. I must say it has me scratching my head.
Basically a couple of views do not draw until the frame changes, or for a tableview I have, until I scroll it off screen. These views tend to be outside the bounds of their superview that is eventually resized to reveal them (though not always). When the superview is resized the subview is not redrawn.
This happens even with just a simple UITextView in a nib. However, the text view shows up as soon as I scroll the superview which is a UIScrollView (causing a redraw). The bounds/frames are correct and I've checked all views are not hidden and their alpha is 1.0
Is this expected behaviour? Calling setNeedsDisplay and setNeedsLayout does nothing as this just marks the view as needing to be redrawn.
I put together this absolutely terrible hack to force the drawing, and fortunately I only need to use it in about 2 minor places, but I would really love to know what is going on.
This is probably overkill, but I can't waste anymore time on a bug I believe exists in UIView or CALayer.
//Don't use this unless a view really really really really won't draw when it should
- (void)twForceDraw
{
CGAffineTransform transform = self.transform;
CGRect frame = self.frame;
CGRect bounds = self.bounds;
self.transform = CGAffineTransformIdentity;
self.frame = CGRectZero;
self.bounds = CGRectZero;
self.frame = frame;
self.bounds = bounds;
self.transform = transform;
[self setNeedsLayout];
[self setNeedsDisplay];
}
Any thoughts what might be causing this?
I'm trying to make an exact "translation" of this UIView block-based animation code:
[UIView animateWithDuration:0.5
delay:0.0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
someView.frame = CGRect(0, 100, 200, 200);
}
completion:nil];
using CABasicAnimation instead.
I'm totally aware that the frame property is actually a combination of position, bounds and anchorPoint of the underlying layer, as it is described here: http://developer.apple.com/library/mac/#qa/qa1620/_index.html
... and I already made a solution like that, using two CABasicAnimations one setting the position, one for bounds and it works for that one view.
The problem is however that I have subviews inside my view. someView has a subview of type UIScrollView in which I place still another subview of type UIImageView. UIScrollView subview has autoresizingMask set to UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight. That all works perfectly if I use the UIView block-based version, however when I try using CABasicAnimations the subviews start behaving unexpectedly(i.e. get resized to incorrect widths). So it seems autoresizingMask is not working correctly when using CABasicAnimations. I noticed also that subviews don't receive a call to setFrame:, although the frame property of the parent view does change after changes to layer position and bounds are made.
That's why I would like to know what would be the correct code to replicate with CABasicAnimation that what is happening when one uses UIView's animateWithDuration method.
I'm totally aware that the frame property is actually a combination of position, bounds and anchorPoint of the underlying layer
Good, but it's important also to be aware that frame is not an animatable property for layers. If you want to animate with CABasicAnimation you must use properties that are animatable for layers. The CALayer documentation marks every such property as explicitly "animatable". The idea of using bounds and position is correct.
Thus, this code does essentially what you were doing before:
[CATransaction setDisableActions:YES];
// set final bounds and position
v.layer.bounds = CGRectMake(0,0,200,200);
v.layer.position = CGPointMake(100,200);
// cause those changes to be animated
CABasicAnimation* a1 = [CABasicAnimation animationWithKeyPath:#"bounds"];
a1.duration = 0.5;
CABasicAnimation* a2 = [CABasicAnimation animationWithKeyPath:#"position"];
a2.duration = 0.5;
[v.layer addAnimation:a1 forKey:nil];
[v.layer addAnimation:a2 forKey:nil];
However, that code has no effect on the size of any sublayers of v.layer. (A subview of v is drawn by a sublayer of v.layer.) That, unfortunately, is the problem you are trying to solve. I believe that by dropping down to the level of layers and direct explicit core animation, you have given up autoresizing, which happens at the view level. Thus you will need to animate the sublayers as well. That is what view animation was doing for you.
This is an unfortunate feature of iOS. Mac OS X has layer constraints (CAConstraint) that do at the layer level what autoresizing does at the view level (and more). But iOS is missing that feature.
I'm trying to move and resize a label, but what happens is the label resizes immediately then moves into position. I first tried simply the commented out lbl.frame line. Next I found this question:
How to animate while resizing UIView
And added all the other code except for the contentMode. This did what I wanted, but the Label's font did not adjust downwards as the label shrunk. ( I tick adjust to fit in xib ). Finally adding the contentMode line gave me the same result as my original frame line - shrink immediately first them animate the move.
lbl.contentMode = UIViewContentModeRedraw;
[UIView animateWithDuration:1.0 delay:0.0
options:(UIViewAnimationOptionAllowUserInteraction)
animations:^{
//lbl.frame = CGRectMake(x, mStartingLine.frame.origin.y+mStartingLine.frame.size.height, 100, 100);
CGRect theBounds = lbl.bounds;
CGPoint theCenter = lbl.center;
theBounds.size.height = 100;
theBounds.size.width = 100;
theCenter.y = mStartingLine.frame.origin.y+mStartingLine.frame.size.height+50;
theCenter.x = x;
lbl.bounds = theBounds;
lbl.center = theCenter;
}
completion:nil
];
I suspect that the auto text resizing feature doesn't work with Core Animation.
What I would suggest doing is to set the label to it's final size (using it's bounds) then apply a transform to it that shrinks it back down to it's starting size. The end effect of those things is that it should stay at the same apparent size.
Finally, use animateWithDuration:animations: to set the view's transform back to the identity transform, so it grows to it's new size.
I don't know what drawing artifacts this will cause - you'll have to try it.