When you change a view’s frame, you update
its size (i.e., its width and height) and its location. For example,
you might move a frame as follows. This code creates a subview located
at (0,0) and then moves it down 30 pixels to (0,30).
CGRect initialRect = CGRectMake(0.0f, 0.0f, 320.0f, 50.0f);
myView = [[UIView alloc] initWithFrame:initialRect];
[topView addSubview:myView];
myView.frame = CGRectMake(0.0f, 30.0f, 320.0f, 50.0f);
This
approach is fairly uncommon. The iPhone SDK does not expect you to move
a view by changing its frame. Instead, it provides you with a way to
update a view’s position. The preferred way to do this is by setting
the view’s center. Center is a built-in view property, which you can access directly:
myView.center = CGPointMake(160.0f, 55.0f);
Although
you’d expect the SDK to offer a way to move a view by updating its
origin, no such option exists. It’s easy enough to build your own class
extension. Retrieve the view frame, set the origin to the requested
point, and then update the frame with change. This snippet creates a
new origin property letting you retrieve and change the view’s origin.
@interface UIView (ViewFrameGeometry)
@property CGPoint origin;
@end
@implementation UIView (ViewFrameGeometry)
- (CGPoint) origin
{
return self.frame.origin;
}
- (void) setOrigin: (CGPoint) aPoint
{
CGRect newframe = self.frame;
newframe.origin = aPoint;
self.frame = newframe;
}
@end
When
you move a view, you don’t need to worry about things such as
rectangular sections that have been exposed or hidden. The iPhone takes
care of the redrawing. This lets you treat your views like tangible
objects and delegate rendering issues to Cocoa Touch.
1. Adjusting Sizes
A
view’s frame and bounds control its size. Frames, as you’ve already
seen, define the location of a view in its parent’s coordinate system.
If the frame’s origin is set to (0, 30), the view appears in the
superview flush with the left side of the view and offset 30 pixels
from the top. Bounds define a view within its own coordinate system.
That means the origin for a view’s bounds, that is, myView.bounds, is always (0,0), and its size matches its normal extent, that is, the frame’s size property.
Change
a view’s size onscreen by adjusting either its frame or its bounds. In
practical terms, you’re updating the size component of those
structures. As with moving origins, it’s simple to create your own
utility method to do this directly.
- (void) setSize: (CGSize) aSize
{
CGRect newframe = self.frame;
newframe.size = aSize;
self.frame = newframe;
}
When
a view’s size changes, the view itself updates live onscreen. Depending
how the elements within the view are defined and the class of the view
itself, subviews may shrink to fit or they may get cropped. There’s no
single rule that covers all circumstances. Interface Builder’s size
inspector offers interactive resizing options that define how subviews
respond to changes in a superview’s frame.
Sometimes,
you need to resize a view before adding it to a new parent. For
example, you might have an image view to place into an alert. To fit
that view into place without changing its aspect ratio, you might use a
method like this to ensure that both the height and width scale
appropriately.
- (void) fitInSize: (CGSize) aSize
{
CGFloat scale;
CGRect newframe = self.frame;
if (newframe.size.height > aSize.height)
{
scale = aSize.height / newframe.size.height;
newframe.size.width *= scale;
newframe.size.height *= scale;
}
if (newframe.size.width >= aSize.width)
{
scale = aSize.width / newframe.size.width;
newframe.size.width *= scale;
newframe.size.height *= scale;
}
self.frame = newframe;
}
2. CGRects and Centers
As
you’ve seen, UIViews use CGRect structures composed of an origin and a
size to define their frames. This structure contains no references to a
center point. At the same time, UIViews depend on their center
property to update a view’s position when you move a view to a new
point. Unfortunately Core Graphics doesn’t use centers as a primary
rectangle concept. As far as centers are concerned, Core Graphics’
built-in utilities are limited to recovering a rectangle’s midpoint
along the X- or Y-axis.
You can bridge this gap by constructing functions that coordinate between the origin-based CGRect struct and center-based UIView
objects. This function retrieves the center from a rectangle by
building a point from the X- and Y- midpoints. It takes one argument, a
rectangle, and returns its center point.
CGPoint CGRectGetCenter(CGRect rect)
{
CGPoint pt;
pt.x = CGRectGetMidX(rect);
pt.y = CGRectGetMidY(rect);
return pt;
}
Moving
a rectangle by its center point is another function that may prove
helpful, and one that mimics the way UIViews work. Say you need to move
a view to a new position but need to keep it inside its parent’s frame.
To test before you move, you’d use a function like this to offset the
view frame to a new center. You could then test that offset frame
against the parent (use CGRectContainsRect()) and ensure that the view won’t stray outside its container.
CGRect CGRectMoveToCenter(CGRect rect, CGPoint center)
{
CGRect newrect = CGRectZero;
newrect.origin.x = center.x-CGRectGetMidX(rect);
newrect.origin.y = center.y-CGRectGetMidY(rect);
newrect.size = rect.size;
return newrect;
}