这篇的内容是切换Views,也是上一篇中提到的第三种当iphone发生旋转后改变布局的方式,先回顾一下上一篇中提到的三种方式
1、使用Autosizing
2、写code
3、重新弄个View,替换原先的View
切换View,顾名思义就是在两个不同的View中间进行切换,那么我们至少需要有2个View,一个View展现当竖着(Portrait)拿iphone时的界面,另一个View展现当横着(Landscape)拿iphone是的界面,当我们旋转iphone时,就在这2个View之间进行切换,给用户的感觉好像是用一个界面,其实我们是用2个View在进行替换。这样做的好处是不必处理复杂的控件重新布局问题,但是坏处是因为是2个不同的View,我们必须用2套控件,然后当一个控件进行改变时,在另一个View中的“相同”控件也应进行改变(例如在一个View中被隐藏了,那在另一个View中也应该被隐藏,因为是同一个界面嘛,这点很重要。)
好,废话少说,开始这篇的学习。
1)创建一个新的Single View项目,并命名为Swap
2)添加2个button
添加2个button,分别命名为Foo和Bar,长宽都为125,并像下图一样进行布局
3)添加另一个View(landscape view)
由于这个view是当前view(portrait view)的横向版本,其界面上的控件类型、个数、功能应该和protrait view一样,只是在布局上有些不同,因此最简便的方法便是先复制一个portrait view,然后对界面上的控件位置大小从新布局。
选中BIDViewController.xib,在xib的editor dock中找到View
按住键盘上的option键,鼠标选中View并拖动鼠标,有一个绿色的加号出现,然后在View的同一层的下面放开鼠标,这样一个View就复制好了。
(可能2个View重叠在一起,用鼠标移动上面的一个View,就会看到有2个View了)
在editor dock中选中新加的View,然后切换到Attributes inspector,找到Simulated Mertrics栏中的Orientation,将其属性改成Landscape,这样View就横过来了
但是另一个button不见了,因为button位置的原因,另一个button没有显示在View中,我们现在editor dock中选中看不见的那个button
然后在Size inspector中将其起始点设成10,10
看不见的那个button出现了
重新对其布局
4)创建View的Outlet
因为我们要切换View,因此必须指定View的Outlet,这样我们就可以在代码中对View进行操作了,创建View的Outlet的方法和创建其他控件的Outlet的方法一样,按下control键,鼠标选中View,拖动到BIDViewController.h中释放,并命名即可。我们首先添加Portrait View的Outlet,命名为portrait
添加Landscape View的Outlet,命名为landscape
5)创建button的Outlet Collection
和以往的略微有些不同,由于我们有两个View,但是这两个View中的按钮的作用是一样的,所以我们在创建按钮的Outlet时,可以使用Outlet集合,也就是Outlet Collection,Outlet Collection和Outlet的区别是,Outlet只能对应一个控件,Outlet Collection则可以对应多个控件,其实Outlet Collection就是一个Outlet的数组,里面可以存放任意多个Outlet,然后对其一一进行遍历。有了Outlet Collection后,我们在写Action的时候,只需要便利Outlet Collection,就可以其中包含的每个控件进行操作,会方便很多(否则你需要对每个控件声明一个Outlet,然后一一操作,这个不仅增加代码的复杂度,而且还很容易遗漏控件)。
添加Outlet Collection的方法和添加一般的Outlet方法一样,选中Portrait View中的button Foo,按住control键,鼠标拖动到BIDViewController.h,释放鼠标,在填出的框中改变类型Connection的类型,改成“Outlet Collection”,并命名为foos,单击Connect完成添加。
添加完成后,切换到Landscape View,选中Foo按钮,control + 鼠标拖动到已添加的Outlet Collection foos上,这样Landscape View中Foo按钮也加入到了foos集合中。
使用同样的方法为两个Bar按钮添加Outlet Collection,并命名为bars。完成后的BIDViewController.h文件如下
#import <UIKit/UIKit.h> @interface BIDViewController : UIViewController @property (strong, nonatomic) IBOutlet UIView *portrait; @property (strong, nonatomic) IBOutlet UIView *landscape; @property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *foos; @property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *bars; @end
6)添加Action
为4个按钮添加Action buttonTapped,只要添加一个Action,其他几个按钮连接到这个Action即可,完整的BIDViewController.h文件如下
@interface BIDViewController : UIViewController @property (strong, nonatomic) IBOutlet UIView *portrait; @property (strong, nonatomic) IBOutlet UIView *landscape; @property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *foos; @property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *bars; - (IBAction)buttonTaped:(id)sender; @end
7)实现View的切换
首先打开BIDViewController.m文件,然后添加一个宏定义在最上面(#import的下面)
#define degreesToRadians(x) (M_PI * (x) / 180.0)
这段宏的意思是将角度转成弧度,在iphone旋转时会用到,因为iphone的旋转角度是根据弧度来计算的,并不是角度,因此我们需要进行一个简单的转换。M_PI是一个预定义的值,就是3.14159265358979323846264338327950288
重载willAnimateRotationToInterfaceOrientation方法,添加在最后一个@synthesize的后面,如下
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { if(toInterfaceOrientation == UIInterfaceOrientationPortrait) { self.view = self.portrait; self.view.transform = CGAffineTransformIdentity; self.view.transform = CGAffineTransformMakeRotation(degreesToRadians(0)); self.view.bounds = CGRectMake(0.0, 0.0, 320.0, 460.0); } else if(toInterfaceOrientation == UIInterfaceOrientationLandscapeLeft) { self.view = self.landscape; self.view.transform = CGAffineTransformIdentity; self.view.transform = CGAffineTransformMakeRotation(degreesToRadians(-90)); self.view.bounds = CGRectMake(0.0, 0.0, 480.0, 300.0); } else if(toInterfaceOrientation == UIInterfaceOrientationLandscapeRight) { self.view = self.landscape; self.view.transform = CGAffineTransformIdentity; self.view.transform = CGAffineTransformMakeRotation(degreesToRadians(90)); self.view.bounds = CGRectMake(0.0, 0.0, 480.0, 300.0); } }
willAnimateRotationToInterfaceOrientation方法发生在旋转开始之后但是还未真正旋转之前,即旋转这个命令已经发出了,但是还没有开始旋转这个动作。
上面的这段code,有几个地方需要说明一下,我们拿一个if语句块进行说明
self.view = self.landscape;
根据iphone的选择方向,选择显示哪个View
self.view.transform = CGAffineTransformIdentity;
貌似是将view的旋转状态设置到默认状态,即初始化一下,这个不太了解,网上查到的说法是:线性代数里面讲的矩阵变换,这个是恒等变换
当 你改变一个view.transform属性的时候需要先恢复默认状态,然后再进行改变。
self.view.transform = CGAffineTransformMakeRotation(degreesToRadians(-90));
view的旋转弧度,将角度换算成弧度,然后进行旋转。
self.view.bounds = CGRectMake(0.0, 0.0, 480.0, 300.0);
CGRectMake在上一篇已经讲解过,即设置起始点和大小,bounds属性是第一次遇到,它和frame有些类似,但是不同的是,frame控件相对于父视图的位置,而bounds则是控件自身的位置,即没有相对于父视图的概念,因为我们旋转的都是view,因此其起始点自然都是(0.0 , 0.0)。
上面的这个旋转方法可以当做模板来使用,每当遇到切换view的时候,就可以直接复制粘贴该方法。
(额外说明一个问题,仔细观察上面的这个方法中CGRectMake中最后一个参数,最后一个参数是表面view的高度,但是是不是发现它少了20?原因是状态栏,iphone顶部的状态栏的高度是20,因此view的高度会减少20。)
8)实现buttonTappedAction
在BIDViewController.m中找到buttonTapped,添加代码如下
- (IBAction)buttonTaped:(id)sender { NSString *message = nil; if([self.foos containsObject:sender]) message = @"Foo button pressed"; else message = @"Bar button pressed"; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:message message:nil delegate:nil cancelButtonTitle:@"ok" otherButtonTitles:nil]; [alert show]; }
这段代码需要注意的就只有这一行
if([self.foos containsObject:sender])
foos是Outlet Collection对象,是一个NSArray,里面有一个containObject方法,查看是否存在某个对象,上面的if语句的意思就是判断foos中是否包含触发buttonTappedAction的对象,即判断该Action是不是由2个Foo按钮触发的,如果不是,那么即使2个Bar按钮触发的。
9)编译运行
点击Foo,一个警告框弹出,告诉你Foo按钮被点击了
旋转iphone,点击Bar,同样警告框填出,告诉你Bar按钮被点击了
10)更新buttonTapped
在开头的时候,我们说过,因为是2个View进行切换,因此在一个View中发生的变化也要体现在另一个View中,我们在这里举一个例子,当在一个View中点击一个按钮的时候,隐藏该按钮,那么在另一个View中也要把对应的按钮隐藏,将buttonTapped方法改成如下样子
- (IBAction)buttonTaped:(id)sender { /* NSString *message = nil; if([self.foos containsObject:sender]) message = @"Foo button pressed"; else message = @"Bar button pressed"; UIAlertView *alert = [[UIAlertView alloc] initWithTitle:message message:nil delegate:nil cancelButtonTitle:@"ok" otherButtonTitles:nil]; [alert show]; */ if([self.foos containsObject:sender]) { for (UIButton *oneFoo in foos) { oneFoo.hidden = YES; } } else { for (UIButton *oneBar in bars) { oneBar.hidden = YES; } } }
这里的for语句和C#中的foreach一样,都是遍历某个集合中的对象,首先判断是那个按钮触发了buttonTapped,然后就将该按钮所在的Outlet Collection中的所有对象隐藏,这样当然也就隐藏了另一个View中的按钮。
编译运行,点击bar按钮,bar按钮被隐藏
旋转iphone,另一个View中的bar按钮也被隐藏了