通过前面的介绍我们已经了解了iOS开发的基本方法和步骤实际上只要掌握了这些基本的方法和步骤我们就可以对iOS应用进行更多的控制。前面介绍的开发方式是使用xib或Storyboard文件设计iOS应用界面这也是iOS开发的最主要方式在某些极端的情况下我们也有可能不使用任何界面设计文件直接通过代码来开发UI界面。
9.5.1 不使用界面布局文件开发UI界面
如果打算使用纯代码来开发UI界面则不需要设计任何界面布局文件没有界面布局文件也就不再需要使用自定义的视图控制器。这样程序可以直接在应用程序委托对象的application: didFinishLaunchingWithOptions:方法中创建UIWindow和应用程序界面——所有这些对象的创建都使用objective-C代码来完成。
实例无界面布局文件开发iOS应用
首先创建一个iOS的Empty Application应用。在创建iOS应用时选择“Empty Application”项即可如图9.35所示。
图9.35 创建iOS的EmptyApplication应用
对于“Empty Application”类型的iOS应用Xcode只生成应用程序委托类不会生成任何界面设计文件也不会生成任何控制器类。
对于打算使用纯代码开发UI界面的开发方式来说我们的应用并不需要任何界面设计文件也不需要任何控制器。程序只要修改应用程序委托的application:didFinishLaunchingWithOptions:方法并在该方法中创建UI控件然后利用这些UI控件搭建应用程序界面即可。下面是修改过的application:didFinishLaunchingWithOptions:方法代码。
程序清单codes/09/9.5/CodeUI/ CodeUI/FKAppDelegate.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
|
// 应用程序加载完成后将会自动回调该方法
- (
BOOL
)application:(UIApplication *)application didFinishLaunchingWithOptions: (NSDictionary *)launchOptions
{
// 创建UIWindow对象并将该UIWindow初始化为与屏幕相同大小
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// 设置UIWindow的背景色
self.window.backgroundColor = [UIColor whiteColor];
// 创建一个UIViewController对象
UIViewController* controller = [[UIViewController alloc] init];
// 让该程序的窗口加载并显示viewController视图控制器关联的用户界面
self.window.rootViewController = controller;
// 创建一个UIView对象
UIView* rootView = [[UIView alloc] initWithFrame
:[[UIScreen mainScreen] bounds]];
// 设置controller显示rootView控件
controller.view = rootView;
// 创建一个圆角按钮
UIButton* button = [UIButton buttonWithType: UIButtonTypeRoundedRect];
// 设置按钮的大小
button.frame = CGRectMake(120, 100, 80, 40);
// 为按钮设置文本
[button setTitle:@
"确定"
forState:UIControlStateNormal];
// 将按钮添加到rootView控件中
[rootView addSubview:button];
// 创建一个UILabel对象
self.show = [[UILabel alloc] initWithFrame
:CGRectMake(60 , 40 , 180 , 30)];
// 将UILabel添加到rootView控件中
[rootView addSubview:self.show];
// 设置UILabel默认显示的文本
self.show.text = @
"初始文本"
;
self.show.backgroundColor = [UIColor grayColor];
// 为圆角按钮的触碰事件绑定事件处理方法
[button addTarget:self action:@selector(clickHandler:)
forControlEvents:UIControlEventTouchUpInside];
// 将该UIWindow对象设为主窗口并显示出来
[self.window makeKeyAndVisible];
return
YES;
}
|
上面的代码中首先创建了一个UIWindow作为应用程序的窗口接下来创建一个UIView作为UIWindow显示的根视图需要借助一个UIViewController对象。
一旦程序中有了UIView作为容器接下来代码归纳起来相当于只有三行此处的三行是一种归纳说法并非实际只有三行。
创建UI控件比如创建UILabel丶创建UIButton等。
调用addSubView:方法将UI控件添加到其他容器中。
多次调用UI控件的setter方法来设置UI控件的外观丶行为。
上面代码中为按钮的触碰事件绑定了clickHandler:事件处理方法因此程序还需要在应用程序委托类中定义该方法。方法代码如下
程序清单codes/09/9.5/CodeUI/ CodeUI/FKAppDelegate.m
1
2
3
4
|
- (
void
) clickHandler:(id)sender
{
self.show.text = @
"开始学习iOS吧"
;
}
|
上面的代码只是简单地修改show控件的文本内容这样即可实现当用户触碰按钮时动态改变show控件的文本内容。
运行该程序单击程序中的按钮即可看到如图9.36所示的效果。
通过上面的开发过程可以发现不管是通过纯代码来创建UI控件再将这些UI控件搭建成程序界面还是使用界面设计文件来搭建程序界面其本质是相同的。它们的本质都是把UI控件当成小的积木块然后将这些“积木块”按自己的意愿组合在一起就可以做成iOS应用的程序界面了。
需要指出的是使用纯代码方式来开发iOS应用并不是最好的开发方式这种方式不仅开发步骤异常烦琐而且所有创建程序界面的代码都由应用程序委托对象的方法负责完成这并不符合MVC设计原则因此不利于程序组件的解耦。通过学习这种开发方式我们可以更好地理解iOS应用中应用程序委托的作用同时也能更好地理解iOS程序界面的底层实现原理。
下面介绍一种更实用的代码方式开发UI界面。
9.5.2 使用代码创建UI界面
更实际的情况是在程序运行开始时程序已经具有一个初始的程序界面初始界面可能只包含一个UIView在程序运行过程中程序需要根据用户交互来动态添加丶删除UI控件。
在这种需求下我们可以通过Interface Builder来设计程序的初始界面接下来在程序运行过程中可以通过代码创建UI控件再将UI控件添加到相应的父控件中即可。
实例动态添加丶删除标签
首先创建一个iOS的Single View Application应用创建完成后该应用将自带一个Main.storyboard界面设计文件但我们并不打算修改该界面设计文件而是直接在程序代码中创建整个UI界面程序只使用该界面文件中的UIView作为容器即可。
接下来修改控制器类在控制器类的实现部分创建整个程序界面绑定事件处理方法。下面是控制类的实现部分代码。
程序清单codes/09/9.5/DynaLabel/DynaLabel/FKViewController.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
|
#import "FKViewController.h"
// 定义FKViewController的扩展
@interface FKViewController ()
// 定义一个属性来记录所有动态添加的UILabel控件
@property (nonatomic, strong) NSMutableArray* labels;
@end
@implementation FKViewController
// 定义一个变量来记录下一个将要添加的UILabel的位置
int
nextY = 80;
- (
void
)viewDidLoad
{
[super viewDidLoad];
// 设置该view的背景色
self.view.backgroundColor = [UIColor grayColor];
// 初始化labels数组
self.labels = [NSMutableArray array];
// 创建UIButtonTypeRoundedRect类型的UIButton对象
UIButton* addBn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
// 设置addBn的大小和位置
addBn.frame = CGRectMake(30, 30, 60, 40);
// 为UIButton设置按钮文本
[addBn setTitle:@
"添加"
forState:UIControlStateNormal];
// 为addBn的Touch Up Inside事件绑定事件处理方法
[addBn addTarget:self action:@selector(add:)
forControlEvents:UIControlEventTouchUpInside];
// 创建UIButtonTypeRoundedRect类型的UIButton对象
UIButton* removeBn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
// 设置removeBn的大小和位置
removeBn.frame = CGRectMake(230, 30, 60, 40);
// 为UIButton设置按钮文本
[removeBn setTitle:@
"删除"
forState:UIControlStateNormal];
// 为removeBn的Touch Up Inside事件绑定事件处理方法
[removeBn addTarget:self action:@selector(
remove
:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:addBn];
[self.view addSubview:removeBn];
}
- (
void
)add:(id)sender {
// 创建一个UILabel控件
UILabel* label = [[UILabel alloc] initWithFrame:
CGRectMake(80, nextY, 160, 30)];
label.text = @
"疯狂iOS讲义"
;
// 设置该UILabel显示的文本
[self.labels addObject: label];
// 将该UILabel添加到labels数组中
[self.view addSubview:label];
// 将UILabel控件添加到view父控件内
nextY += 50;
// 控制nextY的值加50
}
- (
void
)
remove
:(id)sender {
// 如果labels数组中元素个数大于0表明有UILabel可删除
if
([self.labels count] > 0)
{
// 将最后一个UILabel从界面上删除
[[self.labels lastObject] removeFromSuperview];
[self.labels removeLastObject];
// 从labels数组中删除最后一个元素
nextY -= 50;
// 控制nextY的值减50
}
}
@end
|
上面的代码中第一段粗体字代码创建了应用的初始界面该初始界面只包含两个按钮并且程序还为这两个按钮绑定了事件处理方法。
该应用的关键就是实现add:和remove:两个方法其中add:方法中粗体字代码负责创建一个UILabel控件每次创建的UILabel的Y坐标并不相同并将这个UILabel控件添加到该控制器关联的UIView内。这样即可实现每次用户触碰该按钮程序界面就会添加一个UILabel控件而remove:方法中粗体字代码则负责把labels数组的最后一个元素UILabel控件从父控件中删除并从该数组中删除该元素。
通过上面的程序即可实现通过用户交互来动态添加丶删除程序界面控件。编译丶运行该程序并多次触碰添加丶删除按钮后可能看到如图9.37所示的动态界面。
9.5.3 自定义UI控件
UIView控件只是一个矩形的空白区域并没有任何内容。iOS应用的其他UI控件都继承了UIView这些UI控件都是在UIView提供的空白区域上绘制外观。
基于UI控件的实现原理开发者完全可以开发出项目定制的控件——当iOS系统提供的UI控件不足以满足项目需要时开发者可以通过继承UIView来派生自定义控件。
当开发者打算派生自己的UI控件时首先定义一个继承View基类的子类然后重写View类的一个或多个方法通常可以被用户重写的方法如下。
initWithFrame:前面已经见到程序创建UI控件时常常会调用该方法执行初始化因此如果你需要对UI控件执行一些额外的初始化即可通过重写该方法来实现。
initWithCoder:程序通过在nib文件中加载完该控件后会自动调用该方法。因此如果程序需要在nib文件中加载该控件后执行自定义初始化则可通过重写该方法来实现。
drawRect:如果程序需要自行绘制该控件的内容则可通过重写该方法来实现。
layoutSubviews如果程序需要对该控件所包含的子控件布局进行更精确的控制可通过重写该方法来实现。
didAddSubview:当该控件添加子控件完成时将会激发该方法。
willRemoveSubview:当该控件将要删除子控件时将会激发该方法。
willMoveToSuperview:当该控件将要添加到其父控件中时将会激发该方法。
didMoveToSuperview当把该控件添加到父控件完成时将会激发该方法。
willMoveToWindow: 当该控件将要添加到窗口中时将会激发该方法。
didMoveToWindow当把该控件添加到窗口完成时将会激发该方法。
touchesBegan:withEvent:当用户手指开始触碰该控件时将会激发该方法。
touchesMoved:withEvent:当用户手指在该控件上移动时将会激发该方法。
touchesEnded:withEvent:当用户手指结束触碰该控件时将会激发该方法。
touchesCancelled:withEvent:用户取消触碰该控件时将会激发该方法。
当需要开发自定义View时开发者并不需要重写上面列出的所有方法而是根据业务需要重写上面的部分方法。例如下面的跟随手指运动的小球示例程序就只重写drawRect:方法。
实例跟随手指运动的小球
为了实现一个跟随手指运动的小球示例我们考虑开发自定义的UI控件这个UI控件将会在指定位置绘制一个小球这个位置可以动态改变。当用户通过手指在屏幕上拖动时程序监听到这个手指动作并把手指动作的位置传入自定义UI控件然后通知该控件重绘即可。
首先创建一个Single View Application然后通过该应用的项目导航面板打开Main.storyboard文件选中Dock区内唯一场景内的View Controller节点或选中界面布局文件中的根UI控件UIView也就是界面中大块的丶右上角有个电池图标的白色矩形区域然后按下键盘上的command+option+3快捷键打开Xcode的身份检查器通过身份检查器可以看到该界面布局文件的根UI控件的实现类是UIView如图9.38所示。
该应用并不打算使用默认的UIView作为根控件因此将图9.38所示对话框中Class文本框内的实现类改为FKCustomView这表明程序将使用FKCustomView作为界面设计的根控件。
接下来程序需要开发自定义的FKCustomView类其步骤如下。
①用鼠标右键单击项目文件夹然后单击“New File”菜单项Xcode弹出如图9.39所示的对话框。
图9.38 通过身份检查器面板管理UI控件的实现类
图9.39 创建objective-C类
②在图9.39所示对话框的左边选中iOS分类下的Cocoa Touch然后在对话框右边选中“objective-Cclass”列表项后单击“Next”按钮系统显示如图9.40所示的对话框。
图9.40 确定类名和父类
③在图9.40所示的对话框中输入类名选择父类之后单击“Next”按钮Xcode将会显示一个保存文件夹用于确定新创建文件的存储路径。选择合适的路径后单击“Create”按钮即可创建一个新的objective-C类。
下面是自定义控件类实现部分的代码接口部分仅仅只是继承UIView即可。
程序清单codes/09/9.5/CustomView/CustomView/FKCustomView.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
|
#import "FKCustomView.h"
@implementation FKCustomView
// 定义两个变量记录当前触碰点的坐标
int
curX;
int
curY;
- (
void
) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// 获取触碰事件的UITouch事件
UITouch *touch = [touches anyObject];
// 得到触碰事件在当前组件上的触碰点
CGPoint lastTouch = [touch locationInView:self];
// 获取触碰点的坐标
curX = lastTouch.x;
curY = lastTouch.y;
// 通知该组件重绘
[self setNeedsDisplay];
}
// 重写该方法来绘制该UI控件
- (
void
)drawRect:(CGRect)rect
{
// 获取绘图上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 设置填充颜色
CGContextSetFillColorWithColor(ctx, [[UIColor redColor] CGColor]);
// 以触碰点为圆心绘制一个圆形
CGContextFillEllipseInRect(ctx, CGRectMake(curX - 10, curY - 10, 20, 20));
}
@end
|
上面的程序自定义了UIView的子类FKCustomView该子类重写了drawRect:方法该方法的逻辑很简单它仅仅只是以触碰点为圆心绘制一个圆形。除此之外该自定义UIView子类还重写了touchesMoved方法每当用户触碰该组件时程序就会将触碰点的坐标赋给curX丶curY两个变量并通知该控件调用drawRect:方法来重绘自身。这样即可保证每当用户触碰该控件时该控件总会在触碰点绘制一个红色圆形。
编译丶运行该程序即可看到如图9.41所示的效果。
——本文节选自《疯狂ios讲义上》
本文转自 fkJava李刚 51CTO博客,原文链接:http://blog.51cto.com/javaligang/1353339 ,如需转载请自行联系原作者