Upload
others
View
4
Download
0
Embed Size (px)
Citation preview
第 3 章┃
iPhone SDK 开发基础
The heart of the discerning acquires knowledge; the ears of
the wise seek it out.
聪明人的心得知识,智慧人的耳求知识。
——《旧约·箴言》
3.12 iPhone 程序框架
总的来说 iPhone 程序有两类框架,一类是游戏框架,另一类是非游戏框架,这里介
绍的是非游戏框架,即基于 iPhone 用户界面标准控件的程序框架。
典型的 iPhone 程序包含一个 Window 和几个 UIViewController,每个 UIViewController
管理多个 UIView(可能是 UITableView、UIWebView、UIImageView 等),如图 3-24 所示。
这些 UIView 之间如何进行层次迭放、显示、隐藏、旋转、移动等都由 UIViewController
进行管理,而 UIViewController 之间的切换,通常情况是通过 UINavigationController、
UITabBarController 或 UISplitViewController 进行切换。接下来笔者会逐一介绍如何使用这
三种 Controller 来切换你的 UIViewController,以及在 UIViewController 中如何组织和管理
你的各种 UIView。
图 3-24 iPhone 程序框架示意图
┃第 3 章 iPhone SDK 开发基础┃
3.12.1 使用 UINavigationController 组织和管理 UIView
当你的程序具有层次化的工作流时,就比较适合使用 UINavigationController 来管理
UIViewController,即用户可以从上一层界面进入下一层界面,在下一层界面处理完以后
又可以简单地返回到上一层界面,UINavigationController 使用堆栈的方式来管理
UIViewController,进入下一层界面的代码如下。
[self.navigationController
pushViewController:nextController animated:YES];
返回上一层界面的代码如下。
[self.navigationController
popViewControllerAnimated:YES];
如图 3-25 所示,屏幕左上方的“Animal List”按钮是
返回按钮,注意这个返回按钮是 UINavigationController 自
动添加的,不需要编写任何代码在界面上添加按钮或者实
现按钮操作,当程序使用 pushViewController()函数将
ViewController 添加进 UINavigation Controller 的时候,
UINavigationController 就自动显示这个返回按钮,用户单
击这个“Animal List”按钮就可以回到原先的界面,
UINavigationController 的这种运行机制产生这样的效果,
用户可以一层一层地进入更深的界面层次,然后又可以一
层一层的按顺序返回,使用这样的方式来组织用户界面非
常方便。
本节相关的完整 Xcode 工程源代码文件请参考本书附
带的光盘中的 Zoo 实例。
3.12.2 使用 UITabBarController 组织和管理 UIView
当你的程序分为几个相对比较独立的部分时,就比较适合使用 UITabBarController 来
组织用户界面,如图 3-26 所示。
图 3-25 UINavigationController
程序框架实例界面
图 3-26 UITabBarController 程序框架实例界面
在屏幕的下方包含 UITabBarController 的三个按钮,用户单击不同的按钮即可以进入
不同的界面,每个界面相对来说在整个系统中比较独立,也就是程序分成了三个相对比较
独立的不同部分,在每个相对独立的部分你也可以使用 UINavigationController 等容器类组
织你的界面。这样组织使程序逻辑非常清晰,当然你也可以组织很多个 Tab 而不只是三个,
以下代码演示如何创建 UITabBarController 对象,并为其添加多个 Tab。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//Create the navigation Controller
UINavigationController *localNavigationController;
//Create UINavigationController
tabBarController = [[UITabBarController alloc] init];
tabBarController.delegate = self;
// Create the array that will contain all the View controlelr
NSMutableArray *localControllersArray = [[NSMutableArray alloc] init
WithCapacity:3];
┃第 3 章 iPhone SDK 开发基础┃
// Create the view controller attached to the first item in the TabBar
aViewController *firstViewController;
firstViewController = [aViewController alloc];
localNavigationController = [[UINavigationController alloc] initWithRoot
ViewController:firstViewController];
localNavigationController.navigationBar.barStyle = UIBarStyleBlackOpaque;
[localNavigationController.tabBarItem initWithTitle:@"Outlines"
image:[UIImage imageNamed:@"webcast.png"] tag:1];
firstViewController.navigationItem.title = @"Outlines";
[localControllersArray addObject:localNavigationController];
[localNavigationController release];
[firstViewController release];
// Create the view controller attached to the second item in the TabBar
anotherViewController *secondViewController;
secondViewController = [[anotherViewController alloc] initWithStyle:
UITableViewStyleGrouped ];
localNavigationController = [[UINavigationController alloc] initWithRoot
ViewController:secondViewController];
[localNavigationController.tabBarItem initWithTitle:@"Q & A"
image:[UIImage imageNamed:@"book.png"] tag:2];
secondViewController.navigationItem.title=@"Q & A";
[localControllersArray addObject:localNavigationController];
[localNavigationController release];
[secondViewController release];
miscViewController *thirdViewController;
thirdViewController = [[miscViewController alloc] initWithStyle:UITable
ViewStyleGrouped ];
localNavigationController = [[UINavigationController alloc] initWithRoot
ViewController:thirdViewController];
[localNavigationController.tabBarItem initWithTitle:@"Misc"
image:[UIImage imageNamed:@"favorites.png"] tag:3];
thirdViewController.navigationItem.title=@"Misc";
[localControllersArray addObject:localNavigationController];
[localNavigationController release];
[thirdViewController release];
// load up our tab bar controller with the view controllers
tabBarController.viewControllers = localControllersArray;
// release the array because the tab bar controller now has it
[localControllersArray release];
// add the tabBarController as a subview in the window
[window addSubview:tabBarController.view];
// need this last line to display the window (and tab bar controller)
[window makeKeyAndVisible];
return YES;
}
捕获 Tab 切换事件,获取当前活动的 Tab 索引和 UIViewController 对象,代码如下。
- (void)tabBarController:(UITabBarController *)barController didSelectView
Controller:(UIViewController *)viewController{
NSLog(@"currentController index:%d",viewController,
tabBarController.selectedIndex);
UIViewController *currentController = tabBarController.selectedView
Controller;
NSLog(@"currentController: %@",currentController);
}
切换不同的 Tab 时,只需要设置 UITabBarController 的 selectedIndex 属性即可,代码
如下。
tabBarController.selectedIndex = 2;
本节相关的完整 Xcode 工程源代码文件请参考本书附带的光盘中的 Lessons2 实例。
3.12.3 使用 UISplitViewController 组织和管理 UIView
UISplitViewController 属于 iPad 特有的界面控件,适合用于主从界面的情况(Master
view→Detail view),Detail view 跟随 Master view 进行更新,如图 3-27 所示,屏幕左边
(Master View)是主菜单,单击每个菜单则屏幕右边(Detail View)就进行刷新,屏幕右边
┃第 3 章 iPhone SDK 开发基础┃
的界面内容又可以通过 UINavigationController 进行组织,以便用户进入 Detail View 进行更
多操作,用户界面以这样的方式进行组织,使得程序内容清晰,非常有条理,是组织用户
界面导航很好的方式,有关 UISplitViewController 的具体使用,将在后面的章节进行介绍。
图 3-27 UISplitViewController 程序框架实例界面
重要提示:UIView 的 tag 属性在 iPhone 程序开发中非常重要,因为几乎所有
的 View 之间的管理都是依靠 tag 来进行索引和查找。
iPhone 程序框架小结:
(1)使用 UIViewController 组织和管理你的 UIView;
(2)使用.plist 文件、Core Data 或者自定义数据持久方式管理你的缓存和持久数据;
(3)使用 UINavigationController、UITabBarController,或者 UISplitViewController 管
理你的 UIViewController。
练习:
(1)通过 View 的 tag 属性对 UIView 进行管理。
(2)通过 UINavigationController 对 UIViewController 进行管理。
(3)通过 UITabBar 对 UIViewController 进行管理。
(4)通过 UISegmentControl 对 UIViewController 进行管理。
3.29 OpenFlow 编程
当用户界面需要按页面显示图片时,使用 OpenFlow 库提供的功能,将要显示的用户
界面图片分页进行显示会使编程工作变得非常快捷。该库提供了与 OS X 桌面 Finder 程序
相同的视觉效果,如图 3-46 所示就是一个使用 OpenFlow 库逐页进行图片显示的程序,用
户按下屏幕即可进行左右滚动显示,双击图片即可对当前显示的图片进行选取操作。
图 3-46 OpenFlow 编程实例界面
程序在视图控制器的viewDidAppear()中使用 refreshCoverFlow()函数初始化OpenFlow
库,通过 setNumberOfImages()函数设置图片数量,代码如下。
┃第 3 章 iPhone SDK 开发基础┃
// RootViewController.m
- (void)refreshCoverFlow{
CGRect bounds = [[UIScreen mainScreen] bounds];
AFOpenFlowView *coverFlowView = (AFOpenFlowView*)[self.view
viewWithTag:kTagCoverflow];
if(coverFlowView != nil)
[coverFlowView removeFromSuperview];
coverFlowView = [[AFOpenFlowView alloc] initWithFrame:CGRectMake(0, -30,
bounds.size.width, COVERFLOWHEIGHT)];
coverFlowView.dataSource = self;
coverFlowView.viewDelegate = self;
coverFlowView.defaultImage = [self defaultImage];
coverFlowView.tag = kTagCoverflow;
[self.view addSubview:coverFlowView];
NSInteger count = [self numberOfAnimals];
[coverFlowView setNumberOfImages:count];
//...
[coverFlowView release];
}
并在 loadView()中初始化图片,将图片从资源中加载并保存在一个 NSMutableArray
类型的变量 imageArray 中,代码如下。
- (BOOL)doAddAnimal:(NSString *)name Image:(NSString *)imageName{
UIImage *image = [UIImage imageNamed: imageName];
if(image == nil) return FALSE;
CGSize size = CGSizeMake(179, 208);
[imageArray addObject:[self resizeImage:image scaledToSize:size]];
return TRUE;
}
在 OpenFlow 库的 requestImageForIndex delegate 方法中直接通过 NSMutableArray 的
索引作为 OpenFlow 库的图片索引,并通过该索引设置和获取具体图片,代码如下。
// PageViewController.m
- (void)openFlowView:(AFOpenFlowView *)openFlowView requestImageForIndex:
(int)index{
UIImage *image = [imageArray objectAtIndex:index];
[openFlowView setImage:image forIndex:index];
}
笔者在 OpenFlow 库 AFOpenFlowView.m 文件的 touchesEnded()函数中增加了双击回
调接口,以便在用户双击图片时通知库的调用者,代码如下。
//AFOpenFlowView.m
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if(((UITouch *)[touches anyObject]).tapCount == 2){
if ([self.viewDelegate respondsToSelector:@selector(openFlowView:
coverViewDoubleClick:)])
[self.viewDelegate openFlowView:self coverViewDoubleClick:
selectedCoverView.number];
}
[super touchesEnded:touches withEvent:event];
}
库的调用者 RootViewController 类通过接口函数 coverViewDoubleClick()即可处理用
户双击事件,代码如下。
- (void)openFlowView:(AFOpenFlowView *)openFlowView
coverViewDoubleClick:(int)index{
NSLog(@"coverViewDoubleClick called!");
[self showPaintingViewController];
}
本节相关的完整 Xcode 工程源代码文件请参考本书附带的光盘中的 OpenFlow 工程。
3.30 UIPageControl 编程
当用户界面需要按页面进行显示时,使用 iOS 提供的 UIPageControl 控件将要显示的
用户界面内容分页进行显示会使编程工作变得非常快捷,如图 3-47 所示就是一个使用
UIPageControl 控件逐页进行图片显示的程序,用户按下屏幕即可进行左右滚动显示,在
屏幕的正上方使用白色的点显示当前滚动到的页面位置。
┃第 3 章 iPhone SDK 开发基础┃
图 3-47 UIPageControl 编程实例界面
程序自定义一个 SwipeView 类,该类通过子类化 UIView 类并重载其 touchesMoved()
方法捕获用户滚动的方向,类的定义如下。
// SwipeView.h
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
@interface SwipeView : UIView {
CGPoint startTouchPosition;
NSString *dirString;
UIViewController *host;
}
- (void) setHost: (UIViewController *) aHost;
@end
// SwipeView.m
#import "SwipeView.h"
@implementation SwipeView
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Initialization code
}
return self;
}
- (void) setHost: (UIViewController *) aHost
{
host = aHost;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
startTouchPosition = [touch locationInView:self];
dirString = NULL;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = touches.anyObject;
CGPoint currentTouchPosition = [touch locationInView:self];
#define HORIZ_SWIPE_DRAG_MIN 12
#define VERT_SWIPE_DRAG_MAX 4
if (fabsf(startTouchPosition.x - currentTouchPosition.x) >=
HORIZ_SWIPE_DRAG_MIN &&
fabsf(startTouchPosition.y - currentTouchPosition.y) <=
VERT_SWIPE_DRAG_MAX) {
// Horizontal Swipe
if (startTouchPosition.x < currentTouchPosition.x) {
dirString = kCATransitionFromLeft;
}
else
dirString = kCATransitionFromRight;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (dirString) [host swipeTo:dirString];
}
@end
在捕获用户滚动的方向后,SwipeView 类通过用户设置的 host 成员变量回调其
┃第 3 章 iPhone SDK 开发基础┃
swipeTo()方法,host 成员变量在类中定义为 UIViewController,在编译时编译器会产生警
告,这里不用管它,只需要 SwipeView 类的使用者设置 host 成员变量并实现 swipeTo()方
法即可。
SwipeView 类的使用者为 PageViewController 类,该类实现程序的主界面,在这个自
定义的 UIViewController 类中实现 swipeTo()方法,代码如下。
// PageViewController.m
- (void) swipeTo: (NSString *) aDirection{
UIPageControl *pageControl = [[[contentView superview] subviews] lastObject];
if ([aDirection isEqualToString:kCATransitionFromRight])
{
if (currentPage == 5) return;
[pageControl setCurrentPage:currentPage + 1];
} else {
if (currentPage == 0) return;
[pageControl setCurrentPage:currentPage - 1];
}
[self pageTurn:pageControl];
}
在该回调方法中根据用户滚动的方向来设置 UIPageControl 的 currentPage 属性,如果
是向右方滚动则页面计数加一,如果用户滚动的方向是向左,则页面计数减一。设置
UIPageControl 的 currentPage 属性以后,PageViewController 对象再调用其 pageTurn()方法
交换页面显示内容,并将图片显示出来,代码如下。
- (void) pageTurn: (UIPageControl *) pageControl{
CATransition *transition;
int secondPage = [pageControl currentPage];
if ((secondPage - currentPage) > 0)
transition = [self getAnimation:@"fromRight"];
else
transition = [self getAnimation:@"fromLeft"];
UIImageView *newView = (UIImageView *)[[contentView subviews] objectAtIndex:0];
[newView setImage:[UIImage imageNamed:[NSString stringWithFormat:@"ipad_
wallpaper%02d.jpg", secondPage + 1]]];
[contentView exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
[[contentView layer] addAnimation:transition forKey:@"transitionView
Animation"];
currentPage = [pageControl currentPage];
}
在主 pageTurn()方法实现中,PageViewController 类通过 UIView 的 exchangeSubview
AtIndex()方法实现页面内容的切换。
本节相关的完整Xcode工程源代码文件请参考本书附带的光盘中的PageControl工程。
3.31 自定义仪表控件
在 iOS 开发中,因为程序的需要,有时要自行绘制 iPhone SDK 没有提供的界面控件,
通常使用 QuartzCore.framework 即可画出你所需要的各种图形,在这里我们实现一个圆形
的“仪表盘”控件,控件的外观如图 3-48 所示,用户可以通过旋转仪表控件的指针来设
置程序需要的各种系统参数。
┃第 3 章 iPhone SDK 开发基础┃
图 3-48 “仪表盘”控件
控件使用两个 UIView 来实现仪表控件,并通过 CGAffineTransform 类来实现仪表指
针的旋转,控件在 UIDialView 类中实现,UIDialView 类的定义如下。
// UIDialView.h
#import <UIKit/UIKit.h>
@protocol UIDialViewDelegate
@optional
- (void)dialValue:(int)tag Value:(float)value;
@end
@interface UIDialView : UIView {
id<UIDialViewDelegate> delegate;
NSTimer *timer;
UIImageView *iv;
float maxValue,minValue;
CGAffineTransform initialTransform ;
float currentValue;
}
@property(nonatomic,assign)id<UIDialViewDelegate>delegate;
@property CGAffineTransform initialTransform;
@property float currentValue;
@end
在 UIDialView 类的实现文件中,通过 init()方法读取图片文件初始化控件背景和指针,
代码如下。
// UIDialView.m
#import "UIDialView.h"
@interface UIDialView()
-(void)spin:(NSTimer *)timer;
-(float) goodDegrees:(float)degrees;
@end
#define degreesToRadians(degrees) (M_PI * degrees / 180.0)
#define radiansToDegrees(radians) (radians * 180 / M_PI)
static CGPoint delta;
static float deltaAngle;
static float currentAngle;
@implementation UIDialView
@synthesize initialTransform,currentValue;
- (void)dealloc {
[iv release];
[super dealloc];
}
@synthesize delegate;
- (id)init{
if ((self = [super init])) {
self.userInteractionEnabled = YES;
iv =[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"knob.
png"]];
UIImage *backgroundTile = [UIImage imageNamed: @"clock.png"];
UIColor *backgroundPattern = [[UIColor alloc] initWithPatternImage:
backgroundTile];
self.contentMode = UIViewContentModeCenter;
[self setBackgroundColor:backgroundPattern];
[backgroundPattern release];
iv.backgroundColor = [UIColor clearColor];
iv.autoresizesSubviews= YES;
self.frame = CGRectMake(0, 0, iv.frame.size.width, iv.frame.size.
height);
[self addSubview:iv];
[self bringSubviewToFront:iv];
[iv release];
currentValue = 0;
currentAngle = 0;
deltaAngle = 0.0;
}
return self;
}
┃第 3 章 iPhone SDK 开发基础┃
在 UIView 的 touchesBegan()方法中捕获用户 Touch 点的位置,并根据此位置使用
atan2()函数计算出控件的初始化角度,代码如下。
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *thisTouch = [touches anyObject];
delta = [thisTouch locationInView:self];
float dx = delta.x - iv.center.x;
float dy = delta.y - iv.center.y;
deltaAngle = atan2(dy,dx);
initialTransform = iv.transform;
}
在用户的旋转过程中通过设置指针UIView对象的 transform属性实现仪表控件指针伴
随用户手指的旋转而旋转,代码如下。
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint pt = [touch locationInView:self];
float dx = pt.x - iv.center.x;
float dy = pt.y - iv.center.y;
float ang = atan2(dy,dx);
if (deltaAngle == 0.0) {
deltaAngle = ang;
initialTransform = iv.transform;
}else{
float angleDif = deltaAngle - ang;
CGAffineTransform newTrans = CGAffineTransformRotate(initialTransform,
-angleDif);
iv.transform = newTrans;
float diffValue = [self goodDegrees:radiansToDegrees(angleDif)];
currentValue = maxValue - ((maxValue - minValue)/300)*diffValue;
if(currentValue > 100) currentValue = 100;
}
if (delegate != nil) {
[delegate dialValue:self.tag Value:currentValue];
}
}
客户通过实现 UIDialViewDelegate 接口协议的 dialValue()方法而得到控件的通知消
息,代码如下。
// DialViewController.h
#import <UIKit/UIKit.h>
#import "UIDialView.h"
@interface DialViewController : UIViewController< UIDialViewDelegate> {
UIDialView *dialView;
UILabel *myLabel;
}
- (void)dialValue:(int)tag Value:(float)value{
NSString *str = [NSString stringWithFormat:@"%.1f",v*100];
[myLabel performSelector:@selector(setText:) withObject:str];
}
本节相关的完整Xcode工程源代码文件请参考本书附带的光盘中的DialControl工程。
3.32 本章总结
本章介绍使用 iPhone SDK 进行软件开发所涉及的主要基础知识,包括 SDK 的主要框
架、系统结构,使用 Interface Builder 进行用户界面开发,iOS 的事件响应机制,用户界面
核心类 UIView 解析,商业 iOS 软件的主要框架类型分析,动画实现以及 SDK 主要用户
界面控件的实例剖析。这些知识涵盖了使用 iPhone SDK 进行企业应用开发所需要的主要
基础技术和编程要点,每个技术要点都包含实际应用的商业代码,掌握这些基础技术以及
在这些实例的基础上修改和扩展功能,将在实际的 iOS 商业应用中极大地减少开发和测试
时间,重复利用这些已实现的功能代码将节约您宝贵的项目开发时间。