2.4 UIView的外观
2.4.1 外观定制
继承UIView的子类中,可以拥有自己独特的各种子元素。但是当UIView通过其frame属性任意改变其位置以及尺寸的情况下,其子元素的位置往往也会出现变化,甚至出现混乱。问题是,我们到底有没有办法避免这种混乱情况的出现呢?
首先我们能想到的是,提前编写些调整UIView外观的方法,当frame属性发生改变后及时调用这些方法。只要不忘调用这些方法,就可以实现我们的目的。实际上,UIView中已经提供了这种调整外观用的方法。而且是当UIView的尺寸发生变化,需要重新调整外观的时候会自动调用此方法。这个外观自动调整的方法为layoutSubviews。在UIView的子类中,只需要重新调用layoutSubviews方法,就可以实现外观的自动调整。以下是重写调用layoutSubviews方法的实例代码。
// 定义UIView的子类 // 子类中拥有child1_以及child2_两个标签子元素 @interface LayoutTest :UILabel { @private UILabel* child1_; UILabel* child2_; } @end // 实现LayoutTest类 @implementation LayoutTest // 释放处理 -(void)dealloc { [child1_ release]; [child2_ release]; [super dealloc]; } // 初始化处理 -(id)init { if((self = [super init])){ // 调整自己的大小,追加两个标签 self.frame = CGRectMake(0,0,320,320); child1_ = [[UILabel alloc] initWithFrame:CGRectZero]; child1_.text = @"CHILD 1"; [child1_ sizeToFit]; child1_.backgroundColor = [UIColor redColor]; child1_.textColor = [UIColor whiteColor]; child2_ = [[UILabel alloc] initWithFrame:CGRectZero]; child2_.text = @"CHILD 2"; [child2_ sizeToFit]; child2_.backgroundColor = [UIColor blueColor]; child2_.textColor = [UIColor whiteColor]; child2_.center = CGPointMake(child2_.center.x,child2_.center.y + 30); [self addSubview:child1_]; [self addSubview:child2_]; } return self; } // 外观调整方法 -(void)layoutSubviews { // 调用父类中的相同方法 [super layoutSubviews]; // 再显示子元素 // 将child1_移动到左下 CGRect newRect = child1_.frame; newRect.origin.x = 0; newRect.origin.y = self.frame.size.height - child1_.frame.size.height; child1_.frame = newRect; // 将child2_移动到右上 newRect = child2_.frame; newRect.origin.x = self.frame.size.width - child2_.frame.size.width; newRect.origin.y = 0; child2_.frame = newRect; } @end
在上述实例中定义UIView的子类LayoutTest,其中拥有两个标签子元素。这里我们重新编写了layoutSubviews方法,其中完成了将一个标签放置在左下角显示,另一个标签放置在右上角显示的外观调整。将此LayoutTest追加到画面中后,将显示如图2-14所示的外观效果。
图2-14 由layoutSubviews方法实现的外观调整
这里我们直接改变标签子元素的位置与尺寸后,可以看外观是否会自动调整。将图2-14中的“CHILD1”标签的frame属性作一下改变后,图2-15显示外观并没有作出自动调整。
图2-15 直接修改子元素的frame属性后不进行外观自动调整
像上述这样直接改变子元素的位置与尺寸时,layoutSubviews方法并没有被调用。如果要改变这种状况,我们需要调用setNeedsLayout方法。当UIView中发生某些改变后,再调用setNeedsLayout方法时,将会通知UIView需要调用其layoutSubviews方法,这样一来就完成了外观的自动调整。具体的代码如下所示。
// 取得child1_并使之尺寸变大 UIView* child1 =(UIView*)[label_.subviews objectAtIndex:0]; child1.frame = CGRectMake(0,0,160,160); child1.center = label_.center; // 此后,调用setNeedsLayout方法 // layoutSubviews方法将会被自动调用 [label_ setNeedsLayout];
经过上述修改后,layoutSubviews方法将会被自动调用,最终效果如图2-16所示,“CHILD1”标签将会移动到左下的位置。
另外,如果想在调用setNeedsLayout 方法后不用等待而自动调用layoutSubviews方法,我们可以调用layoutIfNeeded方法,以强制调用layoutSubviews方法。调用layoutIfNeeded方法后,如果当UIView处于必须重新布局的情况下,将直接调用layoutSubviews方法。
图2-16 使用 setNeedsLayout 方法后的效果
2.4.2 子元素的自动尺寸调整
当UIView中包含有子元素的情况下,如果其autoresizeSubviews属性为YES时,当UIView发生尺寸调整时,其子元素将自动调整到合适的位置。autoresizeSubviews属性的默认值就是YES,但是UIView中还存在一个必须设置的项目,就是子元素的autoresizingMask属性。autoresizingMask属性默认为UIViewAutoresizingNone,并不直接进行自动尺寸调整。autoresizingMask属性中可以设置6种不同的标志,可以使用OR逻辑运算符将6种标志都设置上。表2-7中罗列了autoresizingMask属性设置不同值时自动尺寸调整的变化情况。
表2-7 autoresizingMask属性值及其变化效果
续表
续表
关于autoresizingMask属性的实例在第4.2.2节有进一步的介绍,有兴趣的读者可以先行学习。
2.4.3 坐标变换
使用坐标时,有时取以特定对象为参照的相对坐标会更方便一些。例如,在[标签1右侧50像素处配置标签2]的情况下。以标签1的本地坐标为参照,标签2的x坐标为[标签1的宽度+50],这样坐标的计算将变得更简单。如果以父元素为参照,标签2的x坐标将为[标签1的x坐标+标签1的宽度+50]。
用于坐标系变换的方法有:covertPoint:toView:方法、convertPoint:fromView:方法、convertRect:toView:方法、convertRect:fromView:方法四个。作用分别如表2-8所示。
表2-8 坐标系变换方法列表
下面看一个具体的使用实例。如图2-17所示,画面中有“CHILD1”及“CHILD2”两个标签,分别以“CHILD1”及“CHILD2”的本地坐标系变换对方的坐标。
图2-17 两个标签的相对坐标(长宽均为100像素)
首先,将“CHILD1”的本地坐标系中“CHILD1”的右下(100,100)的坐标,以及“CHILD1”的矩形(0,0,100,100)变换到“CHILD2”的本地坐标系的坐标变换代码如下。
CGPoint newPoint = [child1_ convertPoint:CGPointMake(100,100)toView:child2_]; CGRect newFrame = [child1_ convertRect:CGRectMake(0,0,100,100)toView:child2_];
结果为,newPoint的坐标为(0,0),newFrame的坐标为(-100,-100,100,100)。
接着,将“CHILD2”的本地坐标系中“CHILD2”的左上(0,0)的坐标,以及“CHILD2”的矩形(0,0,100,100)变换到“CHILD1”的本地坐标系的坐标变换代码如下。
CGPoint newPoint = [child1_ convertPoint:CGPointMake(0,0)fromView:child2_]; CGRect newFrame = [child1_ convertRect:CGRectMake(0,0,100,100)fromView:child2_];
变换结果为,newPoint的坐标为(100,100),newFrame的坐标为(100,100,100,100)。