[xcode6.4,ios8.4]第一个IOS
[xcode6.4,ios8.4]第一个IOS
第一个iOS程序
先打开Xcode—Create a new Xcode project—Single View Application--输入项目名称,同时选择使用Objective-C语言,设备选择iPhone--接下来系统默认生成一个IOS项目模板。项目目录结构如下:
此时什么也不用做,直接运行看一下,没错我们看到了一个iOS应用程序:
程序的运行过程
在几乎所有的程序开发中程序一般都是从main函数开始运行的,那么IOS程序也不例外,在上图中我们可以看到Xcode为我们生成了一个main.m文件:
// // main.m // firstios // // Created by zhoujianqiang on 15/8/27. // Copyright (c) 2015年 zhoujianqiang. All rights reserved. // #import <UIKit/UIKit.h> #import "AppDelegate.h" int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
这个默认的iOS程序就是从main函数开始执行的,但是在main函数中我们其实只能看到一个方法,这个方法内部是一个消息循环(相当于一个死循 环),因此运行到这个方法UIApplicationMain之后程序不会自动退出,而只有当用户手动关闭程序这个循环才结束。这个方法有四个参数:
- 第一个参数和第二个参数其实就是main函数的参数,分别代表:参数个数、参数内容;
- 第三个参数代表UIApplication类(或子类)字符串,这个参数默认为nil则代表默认为UIApplication类,用户 可以自定义一个类继承于这个类;如果为nil则等价于NSStringFromClass([UIApplication class]),大家可以自己试验,效果完全一样;UIApplication是单例模式,一个应用程序只有一个UIApplication对象或子对 象;
- 第四个参数是UIApplication的代理类字符串,默认生成的是AppDelegate类,这个类主要用于监听整个应用程序生命 周期的各个事件(其实类似于之前我们文章中提到的事件监听代理),当UIApplication运行过程中引发了某个事件之后会调用代理中对应的方法.
小技巧:
其实在Xcode中如果要看一些系统方法的解释或者参数说明,可以直接鼠标放到这个方法上,在Xcode右侧面板中就会给出帮助提示,如下图当我们放到UIApplicationMain上之后:
也就是说当执行UIApplicationMain方法后这个方法会根据第三个参数创建对应的UIApplication对象,这个对象会根据第四个参数AppDelegate创建并指定此对象为UIApplication的代理;同时UIApplication会开启一个消息循环不断监听应用程序 的各个活动,当应用程序生命周期发生改变UIApplication就会调用代理对应的方法。
既然应用程序UIApplication是通过代理和外部交互的,那么我们就有必要清楚AppDelegate的操作细节,下面是UIApplication详细的代码:
AppDelegate.h
// // AppDelegate.h // firstios // // Created by zhoujianqiang on 15/8/27. // Copyright (c) 2015年 zhoujianqiang. All rights reserved. // #import <UIKit/UIKit.h> @interface AppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @end
AppDelegate.m
// // AppDelegate.m // firstios // // Created by zhoujianqiang on 15/8/27. // Copyright (c) 2015年 zhoujianqiang. All rights reserved. // #import "AppDelegate.h" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. return YES; } - (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - (void)applicationWillEnterForeground:(UIApplication *)application { // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - (void)applicationDidBecomeActive:(UIApplication *)application { // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - (void)applicationWillTerminate:(UIApplication *)application { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } @end
这个类中定义了应用程序生命周期中各个事件的执行方法:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;程序启动之后执行,只有在第一次程序启动后才执行,以后不再执行;
- (void)applicationWillResignActive:(UIApplication *)application;程序将失去焦点;
- (void)applicationDidEnterBackground:(UIApplication *)application;程序进入后台后执行,注意进入后台时会先失去焦点再进入后台;
- (void)applicationWillEnterForeground:(UIApplication *)application;程序将要进入前台时执行;
- (void)applicationDidBecomeActive:(UIApplication *)application;程序被激活(获得焦点)后执行,注意程序被激活时会先进入前台再被激活;
- (void)applicationWillTerminate:(UIApplication *)application;程序在终止时执行,包括正常终止或异常终止,例如说一个应用程序在后太运行(例如音乐播放软件、社交软件等)占用太多内存这时会意外终止调用此方法;
为了演示程序的生命周期,不妨在每个事件中都输出一段内容,简单调整上面的代码:
AppDelegate.m
// // AppDelegate.m // firstios // // Created by zhoujianqiang on 15/8/27. // Copyright (c) 2015年 zhoujianqiang. All rights reserved. // #import "AppDelegate.h" @interface AppDelegate () @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSLog(@"程序已经启动..."); // Override point for customization after application launch. return YES; } - (void)applicationWillResignActive:(UIApplication *)application { NSLog(@"程序将要失去焦点..."); // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. } - (void)applicationDidEnterBackground:(UIApplication *)application { NSLog(@"程序已经进入后台..."); // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. } - (void)applicationWillEnterForeground:(UIApplication *)application { NSLog(@"程序将要进入前台..."); // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. } - (void)applicationDidBecomeActive:(UIApplication *)application { NSLog(@"程序已经获得焦点..."); // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. } - (void)applicationWillTerminate:(UIApplication *)application { NSLog(@"程序将要终止..."); // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } @end
下面是各个不同操作的运行结果:
相信通过上面运行过程大家会对整个运行周期有个大概了解。比较容易混淆的地方就是应用程序进入前台、激活、失去焦点、进入后台,这几个方法大家要清 楚。如果一个应用程序失去焦点那么意味着用户当前无法进行交互操作,因此一般会先失去焦点再进入后台防止进入后台过程中用户误操作;如果一个应用程序进入 前台也是类似的,会先进入前台再获得焦点,这样进入前台过程中未完全准备好的情况下用户无法操作。另外一般如果应用程序要保存用户数据会在注销激活中进行 (而不是在进入后台方法中进行),因为如果用户双击Home不会进入后台只会注销激活;如果用户恢复应用状态一般在进入激活状态时处理(而不是在进入前台 方法中进行),因为用户可能是从任务栏直接返回应用,此时不会执行进入前台操作。
当然,上面的事件并不是所有AppDelegate事件,而是最常用的一些事件,其他事件大家可以查阅官方文档,例如 -(void)applicationDidReceiveMemoryWarning:(UIApplication *)application;用于在内存占用过多发出内存警告时调用并通知对应的ViewController调用其内存回收方法。这里简单以图形方式描 述一下应用程序的调用过程:
文件结构
这里对于Xcode默认为我们生产的项目结果文件做一下简单介绍:
- AppDelegate(.h/.m):应用程序代理,主要用于监听整个应用程序生命周期中各个阶段的事件;
- ViewController(.h/.m):视图控制器,主要负责管理UIView的生命周期、负责UIView之间的切换、对UIView事件进行监听等;
- Main.storyboard:界面布局文件,承载对应UIView的视图控件;
- Images.xcassets:应用程序图像资源文件;
- Info.plist:应用程序配置文件;
- main.m:应用程序入口函数文件;
- xxx-prefix.pch:项目公共头文件,此文件中的导入语句在编译时会应用到所有的类文件中,相当于公共引入文件(注意在Xcode6中没有提供此文件)
Images.xcassets
关于AppDelegate、main.m前面已经介绍过了,ViewController和Main.storyboard在后面介绍,这里先说 一下Image.xcassets文件。在Xcode中打开这个文件会发现里面有个设置项:AppIcon
AppIcon
在AppIcon中可以看到三个图标设置,当我们勾选了右侧ios7.0或者其他选项这个图标会自动增多,也就是说可以设计的图标跟应用程序准备支持的设备系统有关,这里我们就以默认的ios7.0,8.0为例(现在基本上设备都升级到ios7了):
a.iPhone Spotlight-iOS5,6 Settings-iOS 5-7 29pt:用于设置iOS5或者iOS6的搜索图标以及iOS5、iOS6、iOS7的设置图标,大小是58*58。
iOS搜索图标:
iOS设置图标:
b.iPhone Spootlight iOS 7 40pt:设置iOS7的搜索图标,大小是80*80。具体参见上图。
c.iPhone App iOS7 60pt:设置iOS7的应用图标,大小是120*120。
iOS应用图标:
Info.plist
Info.plist文件记录了应用程序的配置信息,如下图:
其实这些信息我们可以在项目属性中进行配置,效果和编辑这个文档是一样的,大家可以对照查看:
Storyboard
到目前为止我们还没有解释我们的程序是如何显示默认视图界面的。做过WinForm程序的朋友都知道每个Window窗口界面都有一个设计器(对应 一个设计文件),其实在IOS中也可以通过设计工具设计界面不用编写代码,这个工具就是Interface Builder。用Interface Builder编辑的文件在iOS5之前是一个“.xib”文件,从IOS5开始进行了改进,使用“.storyboard”文件进行设计。其实在上面我 们已经看到这个文件,这里重点说明一下Storyboard文件的使用。
首先我们打开Main.storyboard,此时可以看到一个Interface Builder界面设计器出现在我们眼前:
在这个界面中整个核心就是右侧视图控制器ViewController,在ViewController中有一个视图UIView,这个视图用来放 置其他用户操作控件。视图控制器左侧的箭头表示这个视图控制器是个主视图控制器,程序启动之后默认就会直接显示这个视图控制器的视图。我们可以在项目属性 中通过修改“Main Interface”属性来修改主视图控制器。
这里我们不妨从Xcode右侧工具栏Object Library中拖放一些组件在上面简单完成一个登录布局。
要实现这个登录,那么接下来就是事件和属性绑定的问题,大家应该可以猜到登录的逻辑代码肯定在ViewController.m中编写,那么 storyboard文件是如何关联到这个类的呢?如果我们在storyboard界面选中ViewController在Xcode右侧切换到 Identity Inspector视图就会发现里面当前设置的是ViewController类,通过这个设置Main.storyboard和 ViewController关联在一起。
那么如何在代码中读取两个TextField的值并通过点击按钮触发相关事件验证登录合法性呢?要想在代码中使用UITextField,并且添加按钮点击事件,则必须在ViewController.h中定义两个UITextField属性和一个登录方法。
// // ViewController.h // firstios // // Created by zhoujianqiang on 15/8/27. // Copyright (c) 2015年 zhoujianqiang. All rights reserved. // #import <UIKit/UIKit.h> @interface ViewController : UIViewController #pragma mark 手机号码 @property (nonatomic,strong) IBOutlet UITextField *phoneNumber; #pragma mark 密码 @property (nonatomic,strong) IBOutlet UITextField *password; #pragma mark 点击事件 -(IBAction)login:(UIButton *)btn; @end
上面代码我们需要解释一下IBOutlet、IBAction,其余代码和我们之前写的ObjC没有任何区别。
- IBOutlet:IBOutlet没有做任何操作,它的唯一作用就是告诉Interface Builder这个属性可以被关联到其中某个控件(在代码中可以看到代码前面多了空心圆点)。
- IBAction:其实就是void,只是当你定义为IBAction在Interface Builder中可以关联到某个控件的事件方法(后面的关联操作将会看到,而且我们在代码中也可以看到代码前面多了空心圆点);
下面看一下storyboard中的控件和代码中定义的属性和事件如何关联。Xcode为我们提供了几种方式来实现代码和storyboard控件的关联:
1.拖拽控件到代码中(首先点击Xcode右上方“Show the Assistant editor”切换对应视图,然后在代码中打开.h文件,同时打开Interface Builder,按住ctrl键拖拽控件到代码中相应的位置进行关联),关联后属性或方法前的空心圆变成实心表示已经关联到具体控件(注意:事实上,从控 件拖拽到代码时如果代码中没有定义对应的属性和方法Interface Builder会自动生成代码)。
2.从控件场景中拖拽控件到代码,关联后属性或方法前的空心圆变成实心表示已经关联到具体控件。(在Interface Builder中点击左下角“Show Document outline”显示控件结构树,选中相应的控件按住Ctrl键拖拽到代码中的属性或方法上)
3. 在控件上右键找到对应的属性或方法关联到代码中对应的属性或方法即可,关联后属性或方法前的空心圆变成实心表示已经关联到具体控件。
4.与方法3类似,不再截图,只是通过View Control Scene中的控件右键来关联,关联后属性或方法前的空心圆变成实心表示已经关联到具体控件。(在Interface Builder中点击左下角Show Document outline显示控件结构树,选中相应的控件右键拖拽到代码中的属性或方法上)
5.对于前面几种方法其实我们还可以直接从代码拖拽到控件上面,这里简单演示一种,其他情况大家可以自己试验。
如果要删除关联,可以采用第三、第四种方式在控件上右键,在关联菜单中找到对应的关联删除即可。此外需要注意对于一个控件而言可能有多个事件,当我们使用 第三种或第四种方式直接选择具体某个事件关联到login:方法自然没有问题,但是第一、第二、第五种方式没有提示我们关联到哪个事件而是使用一个控件的 默认事件(对于UIButton就是Touch Up Inside事件)。
既然控件和代码属性或方法已经做了关联,这里我们看一下具体效果,这里简单修改一下ViewController.h和ViewController.m
ViewController.h
// // ViewController.h // firstios // // Created by zhoujianqiang on 15/8/27. // Copyright (c) 2015年 zhoujianqiang. All rights reserved. // #import <UIKit/UIKit.h> @interface ViewController : UIViewController<UITextFieldDelegate> #pragma mark 手机号码 @property (nonatomic,strong) IBOutlet UITextField *phoneNumber; #pragma mark 密码 @property (nonatomic,strong) IBOutlet UITextField *password; #pragma mark 点击事件 -(IBAction)login:(UIButton *)btn; @end
说明:由于用到了文本输入框,则必须引入UITextFieldDelegate协议 ,用于文本输入完毕后关闭软键盘!
ViewController.m
// // ViewController.m // firstios // // Created by zhoujianqiang on 15/8/27. // Copyright (c) 2015年 zhoujianqiang. All rights reserved. // #import "ViewController.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _phoneNumber.delegate=self;//设置文本输入框控件的委托代理为self _password.delegate=self;//设置文本输入框控件的委托代理为self // Do any additional setup after loading the view, typically from a nib. } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } - (void)login:(UIButton *)btn{ if ([_phoneNumber.text isEqual:@"123"]&&[_password.text isEqual:@"456"]) { NSLog(@"登录成功!"); }else{ NSLog(@"登录失败..."); } } #pragma mark UITextFieldDelegate - (BOOL)textFieldShouldReturn:(UITextField *)textField{ [textField resignFirstResponder]; return YES; } @end
这里实现了login:方法模拟登录的过程,可以发现当在手机号码中输入“123”,在密码中输入“456”点击登录会输出”登录成功!“。