iPhone UIKit详解
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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)。