iOS学习
我们经常听到,frame的修改是相对的,bounds是绝对的。
具体来说是什么意思呢?我们来用一个小demo演示一下:
#import "ViewController.h"
@interface ViewController ()
// Views for the demo
@property (nonatomic, strong) UIView *containerView;
@property (nonatomic, strong) UIView *childView;
@property (nonatomic, strong) UILabel *infoLabel;
@property (nonatomic, assign) CGAffineTransform initialTransform;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
self.containerView = [[UIView alloc] initWithFrame:CGRectMake(50, 120, 280, 280)];
self.containerView.backgroundColor = [UIColor lightGrayColor];
self.containerView.layer.borderWidth = 2.0;
self.containerView.layer.borderColor = [UIColor blackColor].CGColor;
self.containerView.clipsToBounds = YES;
[self.view addSubview:self.containerView];
self.initialTransform = self.containerView.transform; // Save initial transform state
self.childView = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 80, 80)];
self.childView.backgroundColor = [UIColor colorWithRed:1.0 green:0.2 blue:0.2 alpha:0.9];
[self.containerView addSubview:self.childView];
self.infoLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, self.view.bounds.size.height - 320, self.view.bounds.size.width - 40, 150)];
self.infoLabel.numberOfLines = 0;
self.infoLabel.font = [UIFont monospacedSystemFontOfSize:12 weight:UIFontWeightRegular];
self.infoLabel.text = @"点击下方按钮查看效果。";
[self.view addSubview:self.infoLabel];
[self setupButtons];
[self updateInfoLabel:@"初始状态"];
- (void)setupButtons {
CGFloat buttonWidth = 160;
CGFloat buttonHeight = 40;
CGFloat startY = self.view.bounds.size.height - 160;
UIButton *frameButton = [self createButtonWithTitle:@"改变子视图Frame" frame:CGRectMake(30, startY, buttonWidth, buttonHeight) action:@selector(changeChildFrame)];
[self.view addSubview:frameButton];
UIButton *boundsButton = [self createButtonWithTitle:@"改变容器Bounds" frame:CGRectMake(self.view.bounds.size.width - buttonWidth - 30, startY, buttonWidth, buttonHeight) action:@selector(changeContainerBounds)];
[self.view addSubview:boundsButton];
UIButton *rotateButton = [self createButtonWithTitle:@"旋转容器" frame:CGRectMake(30, startY + 50, buttonWidth, buttonHeight) action:@selector(rotateContainer)];
[self.view addSubview:rotateButton];
UIButton *resetButton = [self createButtonWithTitle:@"全部重置" frame:CGRectMake(self.view.bounds.size.width - buttonWidth - 30, startY + 50, buttonWidth, buttonHeight) action:@selector(resetAll)];
[self.view addSubview:resetButton];
}
- (UIButton *)createButtonWithTitle:(NSString *)title frame:(CGRect)frame action:(SEL)action {
UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
button.frame = frame;
[button setTitle:title forState:UIControlStateNormal];
[button addTarget:self action:action forControlEvents:UIControlEventTouchUpInside];
button.titleLabel.font = [UIFont systemFontOfSize:14];
button.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
button.layer.cornerRadius = 8;
return button;
}
#pragma mark - Demo Actions
- (void)changeChildFrame {
[UIView animateWithDuration:0.5 animations:^{
CGRect currentFrame = self.childView.frame;
if (currentFrame.origin.x == 10) {
self.childView.frame = CGRectMake(100, 100, currentFrame.size.width, currentFrame.size.height);
} else {
self.childView.frame = CGRectMake(10, 10, currentFrame.size.width, currentFrame.size.height);
}
} completion:^(BOOL finished) {
[self updateInfoLabel:@"已改变子视图Frame"];
}];
}
- (void)changeContainerBounds {
[UIView animateWithDuration:0.5 animations:^{
CGRect currentBounds = self.containerView.bounds;
if (currentBounds.origin.x == 0) {
self.containerView.bounds = CGRectMake(50, 50, currentBounds.size.width, currentBounds.size.height);
} else {
self.containerView.bounds = CGRectMake(0, 0, currentBounds.size.width, currentBounds.size.height);
}
} completion:^(BOOL finished) {
[self updateInfoLabel:@"已改变容器Bounds"];
}];
}
- (void)rotateContainer {
[UIView animateWithDuration:0.5 animations:^{
if (CGAffineTransformIsIdentity(self.containerView.transform)) {
self.containerView.transform = CGAffineTransformMakeRotation(M_PI_4); // 45 degrees
} else {
self.containerView.transform = CGAffineTransformIdentity;
}
} completion:^(BOOL finished) {
[self updateInfoLabel:@"已旋转容器"];
}];
}
- (void)resetAll {
[UIView animateWithDuration:0.5 animations:^{
self.containerView.transform = self.initialTransform;
self.containerView.bounds = CGRectMake(0, 0, 280, 280);
self.childView.frame = CGRectMake(10, 10, 80, 80);
} completion:^(BOOL finished) {
[self updateInfoLabel:@"已重置"];
}];
}
- (void)updateInfoLabel:(NSString *)action {
NSString *info = [NSString stringWithFormat:
@"操作: %@\n\n"
@"容器 Frame: %@\n"
@"容器 Bounds: %@\n\n"
@"子视图 Frame: %@\n"
@"子视图 Bounds: %@",
action,
NSStringFromCGRect(self.containerView.frame),
NSStringFromCGRect(self.containerView.bounds),
NSStringFromCGRect(self.childView.frame),
NSStringFromCGRect(self.childView.bounds)];
self.infoLabel.text = info;
NSLog(@"--- %@ ---", action);
NSLog(@"Container Frame: %@", NSStringFromCGRect(self.containerView.frame));
NSLog(@"Container Bounds: %@", NSStringFromCGRect(self.containerView.bounds));
NSLog(@"Child Frame: %@", NSStringFromCGRect(self.childView.frame));
NSLog(@"Child Bounds: %@", NSStringFromCGRect(self.childView.bounds));
}
@end
运行后我们可以得到下面的效果:
再解释效果之前,我们先来看一下iOS的坐标系:
iOS中的坐标系是以左上角为原点,向右为x轴正方向,向下为y轴正方向。
frame是每一个view必备的属性,表示view在父view中的大小和位置,参照点是父视图的坐标系统。
bounds也是每个view都有的属性,这个属性我们一般不进行设置,表示view在本地坐标系统中的位置和大小。参照点是本地坐标系统。
对他们进行修改的效果如下:
- frame
- frame的位置是以父视图的坐标系作为参考,从而确定当前视图在父视图中的位置。
- frame的大小改变时,当前视图的左上角位置不会改变,改变的只是大小。
- frame的位置改变时,是根据父视图的原点进行改变的。
bounds
- 更改bounds中的位置:对于当前视图没有影响,相当于更改了当前视图的坐标系,对于子视图来说当前视图的左上角已经不再是(0,0),而是改变后的坐标,坐标改了以后,所有子视图的位置也会改变。
- 更改bounds中的大小:bounds的大小代表当前视图的长和宽,修改长和宽之后,中心点继续保持不变,长和宽改变,通过bounds修改长宽看起来就像是以中心点为基准点对长宽两边同时进行缩放。
在知道以上内容后,我们可以解释效果图了:
frame的修改较好理解,对size修改就是放大缩小,对原点修改就是相对父视图原点移动。
但是当bounds的原点修改时,子视图会跟着移动。此时可以将bounds看作一个取景器,移动原点位置就是移动这个取景器,当对原点进行50 50的修改时,相当于取景器向右下移动,那么视图此时就会向左上移动。从而就是demo中的效果。