大部分iOS应用通常都可以同时支持纵屏(长而窄)和横屏(短而宽)两种运行模式,而且应用程序会随着不同的运行模式自动调整界面,保证应用程序可以同时在两种运行模式下良好的运行,这种机制被称为自动旋转机制。
指定视图控制器支持的方向通过重写如下方法来控制:
(3)、- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation;重写该方法指定该视图控制器初始显示时默认的屏幕方向;
除此之外,视图控制器提供了如下属性来判断当前的屏幕方向。
interfaceOrientations:设置一个只读属性,该属性返回一个UIInterfaceOrientations枚举值,用于代表当前的屏幕方向。枚举值如下:
(1)、当iOS视图控制器初始显示出来时,系统会自动调用该视图控制器的如下方法,并根据该方法的返回值决定屏幕方向;
例如://貌似不运行
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return UIInterfaceOrientationPortrait;
}
(2)、当用户旋转iPhone时,系统会自动调用该视图控制器的如下方法,如果该方法的返回值中包含了当前设备希望的方向,那么该视图控制器将会旋转为对应的显示方式;
例如:
- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskAll;
}
(3)、在屏幕旋转时调整应用界面,当视图控制器显示的屏幕方向发生改变时,程序将会依次触发如下方法:
③、- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation NS_DEPRECATED_IOS(2_0,8_0);当屏幕完成旋转到指定显示方向时,系统会自动调用视图控制器的这个方法。该方法的第一个参数代表将要旋转到的显示方向。
提示:如果程序要在显示方向改变时对程序界面进行调整,都会重写- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;方法,并在该方法中对应用界面进行调整。调整方式有两种:旋转时重构用户界面;为不同的显示方式提供不同的界面设计文件,然后在显示方向旋转时切换视图。
思路:重写视图控制器的- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;方法,在该方法中检测当前的屏幕方向,并根据屏幕方向来计算用户界面上各控件的大小和位置。
例如:
- (void)viewDidLoad {
[super viewDidLoad];
for (int i = 0; i < 2; i ++) {
for (int j = 0; j < 3; j ++) {
UIImageView* imageView = [[UIImageView alloc]initWithFrame:CGRectMake(i * 140 + 40, j * 160 + 84, 100, 120)];
imageView.image = [UIImage imageNamed:[NSString stringWithFormat:@"%d.png",i * 3 + j + 1]];
[self.view addSubview:imageView];
imageView.tag = i * 3 + j + 100;
NSLog(@"%ld",imageView.tag);
[self.view addSubview:imageView];
}
}
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
return UIInterfaceOrientationPortrait;
}
- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskAllButUpsideDown;
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
if (UIInterfaceOrientationIsLandscape(toInterfaceOrientation)) {
//如果当前处于横屏状态
for (int i = 0; i < 3; i ++) {
for (int j = 0; j < 2; j ++) {
UIImageView* imageView = (UIImageView *)[self.view viewWithTag:i * 2 + j + 100];
NSLog(@"%ld",imageView.tag);
imageView.frame = CGRectMake(i * 160 + 40, j * 140 + 44, 100, 120);
}
}
}else{
//如果当前处于竖屏状态
for (int i = 0; i < 2; i ++) {
for (int j = 0; j < 3; j ++) {
UIImageView* imageView = (UIImageView *)[self.view viewWithTag:i * 3 + j + 100];
imageView.frame = CGRectMake(i * 140 + 40, j * 160 + 84, 100, 120);
}
}
}
}
例如:不太常用
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
switch (toInterfaceOrientation) {
case UIInterfaceOrientationPortrait:
self.view.transform = CGAffineTransformIdentity;
self.view.bounds = CGRectMake(0, 0, screenSize.width, screenSize.height);
for (int i = 0; i < 2; i ++) {
for (int j = 0; j < 3; j ++) {
UIImageView* imageView = (UIImageView *)[self.view viewWithTag:i * 3 + j + 100];
imageView.transform = CGAffineTransformIdentity;
}
}
break;
case UIInterfaceOrientationPortraitUpsideDown:
self.view.transform = CGAffineTransformMakeRotation(M_PI);
self.view.bounds = CGRectMake(0, 0, screenSize.width, screenSize.height);
for (int i = 0; i < 2; i ++) {
for (int j = 0; j < 3; j ++) {
UIImageView* imageView = (UIImageView *)[self.view viewWithTag:i * 3 + j + 100];
imageView.transform = CGAffineTransformMakeRotation(M_PI);
}
}
break;
case UIInterfaceOrientationLandscapeLeft:
self.view.transform = CGAffineTransformMakeRotation(M_PI* (-90)/180);
self.view.bounds = CGRectMake(0, 0, screenSize.width, screenSize.height);
for (int i = 0; i < 2; i ++) {
for (int j = 0; j < 3; j ++) {
UIImageView* imageView = (UIImageView *)[self.view viewWithTag:i * 3 + j + 100];
imageView.transform = CGAffineTransformMakeRotation(M_PI* (90)/180);
}
}
break;
case UIInterfaceOrientationLandscapeRight:
self.view.transform = CGAffineTransformMakeRotation(M_PI * 90/180);
self.view.bounds = CGRectMake(0, 0, screenSize.width, screenSize.height);
for (int i = 0; i < 2; i ++) {
for (int j = 0; j < 3; j ++) {
UIImageView* imageView = (UIImageView *)[self.view viewWithTag:i * 3 + j + 100];
imageView.transform = CGAffineTransformMakeRotation(M_PI * -90/180);
}
}
break;
default:
break;
}
}
对于iOS应用而言,必须区分应用程序在前台运行和后台运行的差异,因为iOS设备的系统资源是有限的,应用程序在后台和前台必须以不同的行为运行。iOS系统也会限制应用程序在后台的运行,从而提高电池寿命,并“让出”更多系统给前台的应用程序,从而提高用户的体验。
大部分的时候,应用程序的状态改变都会激发应用程序委托类(AppDelegate)对应的方法,AppDelegate中各方法的回调实时机如下:
(1)、当应用启动时,将从Not running状态进入Active状态时,在进入Active状态时的中间会先短暂进入Inactive状态。在应用启动时,系统会创建一个进程和一个主线程,并且在主线程中调用main()函数。Xcode在创建iOS项目时会自动生成main()函数,main()函数负责初始化UIApplication对象,并未UIApplication设置应用代理类等。当应用初始化并准备进到前台运行之间的大部分工作都由main()函数负责完成。
用户点击应用图标—>执行main()函数—>UIApplicationMain()—>加载主界面设计文件—>第一步初始化—>Restore UI state—>最后初始化—>激活应用—>Event Loop—>切换其他应用。
(2)、如果应用程序启动后直接进入Background状态,则对应的启动过程与进入Active有区别,主要区别在于:直接进入Background的应用启动后会转入Background状态处理事件,并将很快进入Suspended状态。当启动进入后台时,系统依然会加载应用的用户界面设计文件,但不会显示在应用程序窗口上。
用户点击应用图标—>执行main()函数—>UIApplicationMain()—>加载主界面设计文件—>第一步初始化—>Restore UI state—>最后初始化—>Enter Background—>是否需要继续运行—>Monitor events—>(不在处理事件时休眠)应用休眠。
提示:可在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;方法中加入如下代码即可检测应用程序将要进入的状态
UIApplicationState state = [UIApplication sharedApplication] .applicationState; 枚举值(UIApplicationState)包括: UIApplicationStateActive; UIApplicationStateInactive; UIApplicationStateBackground。
在iOS应用开发中,main函数的功能被最小化了,它的主要工作都交给了UIKit框架去完成。函数代码通常如下:
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
当一个警告框方式的中断(比如电话来电)发生时,应用会暂时从Active状态切换到Inactive状态,从而让用户决定如何处理。在用户决定如何处理该中断之前,应用将一直处于Inactive状态。在用户做出选择后,当前应用可以返回Active状态继续运行,也可切换到Background状态,从而让其他应用开发运行。
中断发生时,程序应该做什么:
警告框方式的中断会导致用户暂时失去对应用的控制。虽然应用依然在前台运行,但是不在接受任何触碰事件(应用知识不在接受触碰事件,它依然会接受通知和其他类型的事件,如accelerometer事件),为了应对这些变化,应用需要在applicationWillResignActive:方法中完成以下工作。
①、停止timers并终止其他周期性的任务;
②、停止任何正在运行的元数据查询;
③、不在初始化任何新的任务;
④、暂停电影播放(在AirPlay上播放的除外);
⑤、游戏进入暂停状态;
⑥、减少OpenGL ES帧率;
⑦、暂停所有执行非关键代码的调度队列和操作队列(即使处于Inactive状态,依然可以继续处理网络请求和其他耗时的后台任务)。
当应用恢复Active状态时,需要在applicationDidBecomeActive:方法中恢复刚刚上述方法中做的所有工作。比如:重启timers,恢复所有的分发队列,恢复OpenGL ES帧率,游戏回到暂停状态。
当用户按下“锁屏”键时,如果该设备设置了密码保护,该设备内的所有文件将处于被保护状态,任何尝试访问这些文件的行为都会导致失败。因此,如果应用持有这些被保护文件的引用,应该在applicationWillResignActive:方法中关闭所有对这些文件的引用,并在applicationDidBecomeActive:方法中重新打开对这些文件的引用。
在通话过程中调整应用的UI
当用户正在通电话,并且返回应用继续保持通话时,状态栏的高度将会增加以反映用户正在通话。类似的,当用户结束通话时,状态栏的高度将会缩减恢复到常规的高度。处理状态栏高度变化的最好方法是使用视图控制器去管理应用程序界面。当状态栏的大小发生改变时,视图控制器可以自动调整它管理的所有内部视图。
如果应用由于某些原因二没有使用视图控制器,则应该手动响应状态栏大小的变化,即通过注册UIApplicationDidChangeStatusBarFrameNotification通知来实现。该通知的处理函数应该获取状态栏的高度,并且使用这些数据来适度调整当前应用的用户界面的高度。
当用户按下Home键、锁屏键,或者系统启动另一个应用时,前台应用首先切换到Inactive状态,然后切换到Background状态,系统将会自动调用应用委托的applicationWillResignActive:和applicationDidEnterBackground:方法。当applicationDidEnterBackground方法执行完成后,大部分应用将会在不久之后转入Suspended状态。对于请求特定后台任务的应用(如播放音乐)或者那些请求需要额外执行时间的应用,可能会继续执行更长的一段时间。
用户切换另一个程序—>应用程序失去活动—>进入后台—>允许执行—>监视事件—>后台程序睡眠—>切换到其他程序。
转入后台时应该做什么
后台应用的内存使用
当应用转入后台时,该应用应该尽可能多的释放它所占用的内存。当系统内存紧张时,系统会优先终止那些占用大量内存且处于后台的应用。
事实上,应用程序应该的对象如果不在使用了,那就应该近况的去掉强应用,这样编辑器可以回收这些内存。如果你想缓存一些对象提升程序的性能,你可以在进入后台时,把这些对象去掉强引用。
与此同时,系统为了减少应用程序所占用的内存,会自动执行如下操作释放内存:
如果应用曾被转入后台,相应地任务被停止,则可以在返回前台时重启任务继续执行。应用的applicationWillEnterForeground:方法应该恢复所有在applicationDidEnterBackground:方法所做的工作。同时,applicationDidBecomeActive:方法应该执行在应用启动时所做的操作。
应用从后台进入前台的程序流程如下图:
切换回本应用—>唤醒应用—>激活应用—>Event Loop。
当应用处于Suspended状态时,它不能执行任何代码。因此,它不能处理在Suspended期间发过来的通知,比如方向的改变、时间的改变、设置的改变,以及其他影响程序展现的或状态的通知。在程序返回后台或前台时,都要正确处理这些通知。
程序在以下情况下进入后台或者挂起状态会终止:
如果应用将被终止时正在前台或者后台运行,系统将会调用应用委托对象的applicationWillTerminate:方法,从而保证应用程序可以正常的回收资源。通常来说,开发者可以在该方法中保存用户数据或应用状态信息,保证应用重新启动可以恢复之前的运行状态。该方法的运行事件被限制为5秒,超过该时间后,该进程会被自动结束并移除内存。
Main Run Loop负责处理用户相关的事件。UIApplication对象在程序启动时启动main run Loop,它处理事件和更新视图的界面。看Main Run Loop就知道,它是运行在程序的主线程上的。这样保证了接收到用户相关操作的事件是按顺序处理的。
用户操作设备,相关的操作事件被系统生成并通过UIKit的指定端口分发。事件在内部排成队列,一个个的分发到Main run loop 去做处理。UIApplication对象是第一个接收到时间的对象,它决定事件如何被处理。触摸事件分发到主窗口,窗口再分发到对应出发触摸事件的View。其他的事件通过其他途径分发给其他对象变量做处理。
大部分的事件可以在你的应用里分发,类似于触摸事件,远程操控事件(线控耳机等)都是由app的 responder objects 对象处理的。Responder objects 在你的app里到处都是,比如:UIApplication 对象。view对象,view controller 对象,都是resopnder objects。大部分事件的目标都指定了resopnder object,不过事件也可以传递给其他对象。比如,如果view对象不处理事件,可以传给父类view或者view controller。