iOS极速入门教程(二)从iOS的HelloWorld谈iOS应用基本原理,再写两个小工程练手

简介

本文通过在XCode10.0创建iOS的HelloWorld工程,介绍iOS应用的基本原理和编程思想。对比Storyboard、xib两种方式创建的HelloWorld,介绍这两种创建布局方式基本使用。最后通过写一个iOS登陆界面和一个show question&answer的小应用来练手。

iOS之HelloWorld(storyboard版)

使用XCode新建iOS下的Single View App工程:

可以看到有如下文件:

可以看到一共两个.storyboard文件,这表示一共两个界面。其中LaunchScreen.storyboard表示应用启动页,类似Android里的LaunchActivity。而Main.storyboard就是MainActivity程序主界面。这样按下CMD+R能看到程序先打开了LaunchScreen.storyboard然后自动进到了Main.storyboard。为了简化,我们先把启动页拿掉,进到应用配置页在Targets/App Icons and Launch Images/Launch Screen File选择Main即可:

然后按如下方法拖进来一个Label,然后双击修改内容为任意字符:

这样我们就看到了HelloWorld界面如下所示:

为了简化代码,实际上我们现在可以直接把LaunchScreen.storyboard删掉就好了!

iOS之HelloWorld(xib版)

上面介绍了storyboard版的helloworld,stotyboard实际上再xib的基础上发展而来。虽然功能强大,而且苹果一直也在力推,但是实际工程中用的很少。最大问题是它就是个二进制,不利于版本管理维护,还会有其他兼容性问题。而且初学就用storyborad很多原理都被掩埋了。所以退而求其次,我们用xib来写界面。xib发音为zib,xib文件在编译的过程中仍会被编译为.nib文文件,类似android的xml,但是比xml强大一点。下面演示基于xib的helloworld。

我们新建一个类TestViewController,该类继承自UIViewController,同时勾选创建xib文件,如下图所示:

同时我们直接把老的ViewController.hViewController.mmain.storyboard都删掉。
同时把target/deployment info/main interface改为我们新建的TestViewController.xib.

然后再TestViewController.xib里跟上面一样拖进去一个Label并修改信息。然后在AppDelegate.mdidFinishLaunchingWithOptions增加以下代码:

1
2
3
4
5
6
7
8
9
10
11
#import "TestViewController.h"
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
self.window = [[UIWindow alloc]initWithFrame: [[UIScreen mainScreen] bounds]];
TestViewController * controller = [[TestViewController alloc]initWithNibName:@"TestViewController" bundle:nil];
self.window.rootViewController = controller;
[self.window makeKeyAndVisible];
return YES;
}

上面代码用于将我们自己创建的TestViewController和应用程序的window对象关联起来。
之后按cmd+r运行将会遇到以下错误:

1
libc++abi.dylib: terminating with uncaught exception of type NSException

我们找到Info.plist文件删掉其中的main nib file base namelaunch screen interface file name两行:

之后就可以正常运行了!

iOS之HelloWorld(xib版)运行原理

每一个应用程序都有且仅有一个属于自己的UIWindow(它包含状态栏),UIWindow继承自UIView。某一时刻在某个界面上看到的就是一个大的UIView,UIView面临三个问题:

  1. 谁来控制UIView之间的切换
  2. 谁来控制UIView的生命周期,即UIView的创建销毁
  3. 谁给UIView提供的数据源并显示的
  4. 谁来监听处理UIView的事件?

上面三个问题答案都是控制器UIViewController,而UIViewController就相当于Android里的Activity,一个控制器默认有一个UIView,同一时刻只能显示一个控制器的UIView。控制器被销毁,它里面的UIView也会被销毁。

  1. 第一个界面就UiWindow的根控制器。
    总之一句话,iOS的UiViewController(包含其子类)就相当于Android里的Activity

然后我们看下iOS应用程序的运行过程:

  1. 首先程序从main.m进入:
1
2
3
4
5
6
7
8
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

在这里调用了UIApplicationMain这个函数来创建一个UIApplication对象,一个UIApplication就表示一个应用程序,且为单例,是应用程序的象征。这个类就等于Android里的Application
该函数共四个参数。其中前两个参数来自main这个不用研究,第三个参数表示UIApplication的类名,如果传nil意味着创建默认的UIApplication,等于Android里的Application。在Android里如果自定义Application就要配到androidManifest.xml,这里也是如此。第四个参数表示UIApplication的代理对象的类名,用来接收UIApplication关于应用生命周期的回调。所谓的delegate就像等于Android里Application的回调。

  1. iOS生命周期
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//第一次点击图标进入
didFinishLaunchingWithOptions(加载完毕)
applicationDidBecomeActive(获得焦点 )
//点home按键退到后台:
applicationWillResignActive(失去焦点)
applicationDidEnterBackground(进到后台)
//第二次点击图标
applicationWillEnterForeground
applicationDidBecomeActive
//进到多任务
applicationWillResignActive
//多任务再进入程序
applicationDidBecomeActive
//多任务杀掉
applicationDidEnterBackground
applicationWillTerminate

所以我们选择在didFinishLaunchingWithOptions里去创建关联ViewController

  1. 需要注意的是,UiApplication对象创建完毕后和android类似会开启一个消息循环(main looper)之后才创建的是代理对象。

最后再总结下iOS应用程序运行过程:

  1. 执行main函数,创建UIApplication对象,然后开启消息循环;
  2. 根据传参创建代理对象AppDelegate,这样应用程序的生命周期变化时会通知到代理对象;
  3. 应用程序创建完毕后进到didFinishLaunchingWithOptions,在这里初始化一个窗口,再创建一个控制器。之后设置控制器是窗口的根控制器,最后让他成为主窗口且可见。

一个QQ登陆界面实现(xib版)

废话不说先来看最后实现的效果:

下面介绍实现过程:

  1. 先用拖的方法把想要的View效果拖出来;
  2. TestViewController里增加一个方法- (IBAction)login;,然后在xib里右键单击File's owner将这个方法拖到登陆按钮上,记得方法选择Touch Up Inside也就是单击的意思:
  3. 创建两个UITextField的成员变量,在TestViewController.h里新增:
1
2
@property (nonatomic, assign) IBOutlet UITextField *name;
@property (nonatomic, assign) IBOutlet UITextField *pswd;

之后连线,连线之后这两个变量就和UIView上的控件关联起来了:

  1. 经过上述操作我们就可以在login函数里操作两个控件了:
1
2
3
4
5
6
7
- (IBAction)login{
NSLog(@"click...");
NSString *name = self.name.text;
NSString *pswd = self.pswd.text;
NSLog(@"name = %@, pswd = %@", name, pswd);
}

这里仅仅是示意,把用户名和密码打印出来。

要点如下:

  1. IBAction就是Void但是它可以让一个函数显示到File's owner的右击方法列表中;
  2. UI元素不用管内存,本来要用retain的用assign,原因是他们都在随UIViewController生命周期已经被管理了。

    一问一答小demo

    接下来实现一个《iOS编程(第四版)》里第一章的一个小例子,一个界面上两个UILabel两个UIButton,点击后触发不同的显示逻辑。界面如下:

新建一个QuizViewController,在m文件里实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#import "QuizViewController.h"
@interface QuizViewController ()
@property (nonatomic, assign) IBOutlet UILabel * questionLabel;
@property (nonatomic, assign) IBOutlet UILabel * answerLabel;
@property (nonatomic, copy) NSArray * questions;
@property (nonatomic, copy) NSArray * answers;
@property (nonatomic, assign) int currIndex;
- (IBAction)showQuestion;
- (IBAction)showAnswer;
@end
@implementation QuizViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil{
self = [super initWithNibName: nibNameOrNil bundle:nibBundleOrNil];
if(self){
self.questions = @[
@"你几岁了?",
@"你来自哪?",
@"几点了?"
];
self.answers = @[
@"100",
@"地球",
@"12:30"
];
self.currIndex = -1;
}
return self;
}
- (IBAction)showQuestion{
self.currIndex = (self.currIndex + 1) % [self.questions count];
NSString *question = self.questions[self.currIndex];
self.questionLabel.text = question;
self.answerLabel.text = @"???";
}
- (IBAction)showAnswer{
NSString *answer = self.answers[self.currIndex];
self.answerLabel.text = answer;
}
@end

这个例子就省略自动连线等过程了。

显示 Gitment 评论