UIView Resize Animation Glitches - iphone

I've been struggling with a seemingly minor glitch in the way my UIView subclass animates. I have the view set up like so:
self.contentMode = UIViewContentModeRedraw;
self.contentStretch = CGRectMake(0.5f, 0.5f, 0.0f, 0.0f);
The view is basically a rectangle that's got a 3-pixel stroke. Because I want the view to not distort during resize, I have the contentStretch property indicating the middle of the view. Problem is, distortion happens while it's growing, but it's smooth when shrinking.
Seems like my animation block is pretty straightforward; I have separate ones for growing and shrinking. Here's the growing block:
[UIView animateWithDuration:0.5 animations:^{
CGRect newRect = CGRectMake(20, 100, 200, 70);
[titleView setFrame:newRect];
}];
This is pretty hard to describe in a post, so I assembled a test case project that isolates only the parts that are relevant to this. If anyone wants to take a look?
http://dl.dropbox.com/u/2798577/ViewResizeTest2.0.zip
You can see when you hit the "Grow" button, the view's left side "glitches" before animating. While clearly visible here, it's a tad worse in my real project.
I'm not sure if this is a bug in my code or a problem with the framework? Hoping it's the former...
Thanks,
Aaron.
Update
Having received Jacob's generous feedback, I've gone back to my app and removed the contentMode setting as UIViewContentModeRedraw, noting as he does that it will run drawRect every time it animates, and perhaps that initial frame is part of the glitch. So now the animation is smooth, but it animates to the wrong position, squishing the sides. I've updated the linked project to something that more-accurately reflects what's happening in my app.
If I had to guess, it would appear that the contentStretch property isn't doing its job. It's resizing the whole thing and not a CGRect region inside as per my instruction. Can anyone confirm if I'm using that correctly?

I've got this figured out now; here's the answer for anyone else who runs into this issue.
The documentation for UIView's contentStretch property doesn't tell the full story. In my case, I understood the purpose of contentStretch to describe a "sample" of an area that should be altered in size. Instead, contentStretch needs to describe the entire region that would be subject to resizing. That makes better sense now as I write this, but it wasn't obvious to me before.
Therefore, my original declaration:
self.contentStretch = CGRectMake(0.5f, 0.5f, 0.0f, 0.0f);
described a rect that had no width or height! So it effectively did nothing. Instead, I am using a rect that includes most of the view's bounds, except for the ends, which I didn't want distorted by the size change:
self.contentStretch = CGRectMake(0.2, 0, 0.7, 1);
Note that it's a rect that starts 20% into the x axis, and spans 70% of the width, at full height. In the sizes that my rect needed to be, this covered it beautifully.
Thanks to Jacob for his advice to remove the contentMode property; that was totally unnecessary in this circumstance.
Cheers,
Aaron.

Get rid of the following line in TitleView.m:
self.contentMode = UIViewContentModeRedraw;
The reason why you don't want to use UIViewContentModeRedraw is because it redisplays the view’s content when its bounds change. Hence the jagged first frame in the animation.
The default contentMode is UIViewContentModeScaleToFill, which is what you want.

Related

Animate UIView grow/shrink

I have a UILabel I want to animate growing and shrinking. While the size changes I need the bottom left corner to remain static so that it always appears directly above a bottom toolbar. I am using the following code to make the label grow:
[UIView animateWithDuration:kAnimationDuration delay:0.0 options:UIViewAnimationCurveEaseInOut
animations:^{
CGFloat lblHeight = 42.0f;
[label setFrame:CGRectMake(0.0,
CGRectGetMaxY(self.view.bounds) - kBottomBarHeight - lblHeight,
CGRectGetMaxX(self.view.bounds),
lblHeight)];
} completion:^(BOOL finished) { }];
and to make it shrink I use the same logic except that lblHeight is set to 17.0f
The view correctly grows but when I try to shrink it the frame change animation is not animated. It blips into the new size and then animates into the new origin/location. I need the frame change to be animated. Can anyone see what I'm doing wrong?
After some tinkering I've managed to get the desired behavior by doing the following.
In the expand method, I use the UIView animation to alter the frame.
In the shrink method, I use the UIView animation to alter the bounds and center.
I'm a little baffled as to why this works but trying to shrink with the frame does not. If anyone can share some insight into this that would be great.
You should not really use frames to animate, rather you should use the transform property of your label.
However, since you want one corner to remain static, I think its best you use Core Animation. CALayer has a property called anchorPoint that determines which point a layer will rotate with respect to, and I'm pretty sure it is also valid for grow/shrink effects.

Scrolling a patterned background on UIView

I have a simple UIView which I'm filling with a patterned background, like this
self.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"backgroundPattern"]];
I want to be able to manipulate the origin of the view by modifying its bounds property and have the background move with the change in origin (as if the background is fixed to the content, not floating behind it). Is this possible? Currently the background seems fixed to the view's frame instead of its bounds and the pattern doesn't scroll when the content scrolls.
To change the origin I'm using a block animation like this
self.bounds = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
[UIView animateWithDuration:1. delay:0. options:UIViewAnimationOptionRepeat animations:^{
self.bounds = CGRectMake(0, 100, self.bounds.size.width, self.bounds.size.height);
} completion:nil];
With that code, my child views repeatedly scroll and reset, but the background stays fixed in place. Is it possible to specify a background which scrolls with the view instead?
I haven't been able to find anything on making a background pattern "stick" to the view's origin system. My solution for my app has been to make a child view and set the background pattern on that. Then the child view will move with the origin change and the background pattern will follow it. The problem with that solution is that the child view has to be large enough to cover the range of potential origins, which can be difficult to handle if the content area might be arbitrarily large.
You could generate an instance of your view again in the direction the user pans and add it to an NSMutableArray to be accessed and moved based on the offset of the world.

Problem animating size change in a custom-drawn view

I have a custom UIView that implements drawRect to render itself with a gradient background and a 1-pixel black border around the edge, and some text.
In my parent view controller, I am attempting to animate my custom view from its original small size to full-screen, using this code:
[UIView beginAnimations:nil context:NULL];
CGRect screenRect = [[UIScreen mainScreen] applicationFrame];
CGRect detailRect = CGRectOffset(screenRect, 0, -screenRect.origin.y);
[dayDetail.view setFrame:detailRect];
[UIView commitAnimations];
This works, but drawRect in my custom view is only called once when the view is initially created. As a result the resizing just takes the original small image and blows it up to full screen, so the border of the full-size view is now many pixels wide instead of one, and the text gets expanded in size as well.
Is there any way to set this up so that drawRect is called continuously as the view is being animated from small to full-screen? Basically, I want my custom rendering code to handle the appearance of the view no matter what size it is at any point in time.
I've had the exact same problem and this is how I solved it:
dayDetail.view.contentStretch = CGRectMake(0.5, 0.5, 0.0, 0.0);
That will stretch the middle pixel only. Read the documentation to learn how to modify that behavior. You could certainly ask me, but reading the documentation is probably quicker. Besides, I would probably just quote the documentation.

How do I get a UIView which displays text to behave like a UILabel when bounds change?

I have a UIView in which I need to draw text in drawRect:
- (void)drawRect:(CGRect)rect {
...
[#"some text" drawAtPoint:somePoint withFont:someFont];
...
}
Because the text requires special formatting, I cannot just use a UILabel.
It looks fine until I rotate the device. Then the size of my custom UIView changes (in the parent view's layoutSubviews method), and the text becomes stretched in one direction and squished in the other.
When I replace my view with a UILabel, the text always looks great, even when the bounds of the view changes.
How can I get my view to exhibit the same behavior as UILabel?
Some things I have looked into, but have yet to have success with:
Set the view's layer's needsDisplayOnBoundsChange to YES.
Set the view's contentStretch to CGRectZero.
Call setNeedsDisplay in my view's layoutSubviews.
Maybe I'm not doing one of these things right. Has anyone else run into this?
Update: As recommended in James Huddleston's answer, I set the contentMode property of the view to UIViewContentModeRedraw, which got me part of the way there. The text now appears correct at the conclusion of the animation. However, at the start of the animation the text gets squished/stretched to fit the end dimensions and then gets unsquished/unstretched over the course of the animation. This is not the case with UILabel.
Try setting the contentMode property of your view to UIViewContentModeRedraw.
This seems to work OK:
self.contentMode = UIViewContentModeRedraw;
self.contentStretch = CGRectMake(1, 1, 0.5, 0.5);
And then ensure that the bottom-right pixel is the background color. To do that, I put one pixel of padding around the contents of the view. It seems like UILabel doesn't have the one pixel border restriction, so it must be doing something different. But as far as I can tell, this has the same effect.
Use a UIWebView
Sounds a bit overkill but it seems the recommended way to get formatted text that's more complicated than a UILabel can cope with.
There is some source code created by Kevin Ballard called FontLabel. (code.google.com)
The good thing about this, it subclasses UILabel and you can use your own Font ;)
//EDIT: ok ok, as told I will update my answer to recommend subclassing UILabel "to get all the UILabel goodness" ^^

don't allow subviews to scale vwith view?

i am scaling UIView using
[UIView setTransform:CGAffineTransformMakeScale(2.0*scale.value, 2.0*scale.value)];
its works fine for me but all subviews are also scale with the UIView, but i don't want to scale all subviews of that UIView.
i tried following this to stop scale subviews.
[UIView setAutoresizesSubviews:NO];
UIView.autoresizingMask = UIViewAutoresizingNone;
but still it scale subviews.
please help me.
Transformation works on the whole view as it is. There is no way to take things out of it.
Using transform matrices don't alter the size of your views directly, it just determines how the whole thing thing is then rendered on the screen.
If you just scale it by a factor of two, you won't gain or lose any level of detail either. Maybe set its frame instead.