您的位置:首页 > 其它

Split View Controller在应用的中的若干问题及解决

2012-02-11 15:16 417 查看
Split View Controller是iPad中最具特色的视图控制器之一。它充分利用iPad横竖屏转换时的屏幕空间变化,提供了以左右分栏或popover来进行导航的界面视图。但它在使用上的复杂程度远不是TableView Controller之类的控制器所能相比。而且由于其本身所具有的限制,我们无法象使用其他控制器组件一样任意使用它。本文总结了iPad应用中SplitView Controller的一些问题及适用的解决方法,希望能起到抛砖引玉的效果。
一、从IB构建SplitView Controller
对于Split View Controller来说,通过代码来使用是比较简单的,因此不用多讲,相信大家并不陌生。但在IB中构建SplitView Controller,尚未有人介绍。虽然在笔者另一篇博文“iPad开发:UISplitViewController应用”曾有过介绍,但那是在Xcode3.2下实现的,随着Xcode已经升级至4.2,笔者觉得有必要再次罗嗦一番。
新建3个View Controller类:iPadHelpVC、iPadHelpIndexVC、iPadHelpContentVC,注意勾选“WithNib…”。
1、iPadHelpVC
这是一个普通的View Controller,但我们在其中拖入了一个SpliteView Controller组件:



View对象是一个空白UIView。它不包含实质的内容。我们在使用iPadHelpVC类时,主要是为了使用它的Xib文件中的SplitView Controller对象,因此这个View对象只是个摆设。
提示:你不能删除View对象,因为它会导致一个IB对象连接错误——因为View Controller的view属性必须连接到一个UIView。
重要的是Split View Controller对象。它下面会自动包含一些子对象:一个NavigationController、一个View Controller。在Navigation Controller下面又包括一个Navigation Bar和一个TableView Controller。
下面我们要对这些对象进行连接。
将Table View Controller的Identity Class修改为iPadHelpIndexVC,待会我们要用它来提供SplitView Controller左边的导航列表。
将View Controller得Identifty Class修改为iPadHelpContentVC,待会我们用它来提供SplitView Controller右边的内容视图。
在iPadHelpVC类中声明一个出口:
@property (nonatomic, retain) IBOutlet UISplitViewController *splitVC;
⋯⋯
@synthesize splitVC;
将Split View Controller对象和这个splitVC出口连接起来,便于我们在Xcode中引用。
在iPadHelpVC的viewDidLoad方法中:
// Split ViewController 只能作为window的根视图控制器
SplitDemoDelegate*app=(SplitDemoDelegate*)[[UIApplication sharedApplication]delegate];
app.stubVC=app.window.rootViewController;
app.window.rootViewController=splitVC;

SplitDemoDelegate是我们这个示例程序的应用程序委托类。我们在这个类中定义了一个顶层对象stubVC:
@property(retain,nonatomic)UIViewController* stubVC;
⋯⋯
@synthesize stubVC;
提示:关于顶层对象,简单地说就是app 全局对象。参考另一篇博文: 单例,应用程序委托和顶层数据。
我们需要先在stubVC中保存一份window.rootViewController的引用。因为SplitView Controller只能在window对象的rootViewController上应用。如果我们不想让app从头至尾只使用一个Split ViewController的话,我们需要保持住导航到Split View Controller之前的那个视图控制器(也许是一个View Controller,也许是一个NavigationController)。这样我们可以从Split View Controller再次导航回前面的View Controller。
上面的工作做完后,在IB的Objects面板显示如下:


最后,我们需要将Split View Controller的delegate和I PadHelp IndexVC进行连接。这样可以在iPadHelpIndexVC类中加入一些代码,定制Split View Controller的行为。
选择Split View Controller对象,在Connections面板中将delegate右边的圆圈拖到I Pad Help IndexVC对象:


2、iPadHelpIndexVC
这个类提供了左边栏的导航列表。一般来说,它应该是UITableView子类。它会自带一个TableView对象,并实现UITableViewDataSouce和UITableViewDelegate协议。用于Split View Controller的delegate是iPadHelpIndexVC,因此还需要声明实现UISplitViewControllerDelegate协议:
@interface iPadHelpIndexVC :UITableViewController
<UISplitViewControllerDelegate,UITableViewDelegate,UITableViewDataSource>{
说明一个数组作为table view对象的数据模型:
NSArray*model;
声明一个出口,用于和Split View Controller进行连接:
@property (nonatomic, assign) IBOutlet UISplitViewController*splitViewController;
⋯⋯
@synthesize popoverController;
然后回到iPadHelpVC.xib,将出口splitViewController和SplitView Controller对象连接在一起:



在iPadHelpIndexVC的viewDidiLoad中,我们初始化model并在model中添加一些数据:
model=[[NSArray alloc]initWithObjects:@"文档1",@"文档2", nil];
接下来,我们先实现Table View的DataSource 方法:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}

- (NSInteger)tableView:(UITableView *)tableViewnumberOfRowsInSection:(NSInteger)section
{
return model.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableViewcellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"CellIdentifier";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
cell.textLabel.text = [model objectAtIndex:indexPath.row];
return cell;
}
当用户点击左边栏导航列表中的条目,我们修改右边栏的内容显示:
- (void)tableView:(UITableView *)tableViewdidSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
iPadHelpContentVC <DetailViewController>*detailViewController = nil;
detailViewController= [[iPadHelpContentVC alloc] initWithNibName:@"iPadHelpContentVC" bundle:nil];
// 修改 split view controller的viewControllers属性.
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailViewController,nil];
splitViewController.viewControllers =viewControllers;
detailViewController.lbTitle.text=[model objectAtIndex:indexPath.row];
[viewControllersrelease];

// 如果popover窗口在弹出中,解散
if (popoverController!= nil) {
[popoverController dismissPopoverAnimated:YES];
}
// 重新设置右边栏的popover按钮
if (rootPopoverButtonItem!= nil) {
[detailViewController showRootPopoverButtonItem:self.rootPopoverButtonItem];
}
[detailViewControllerrelease];
}
这个方法中用到的两个属性: popoverController 和 rootPopoverButtonItem声明如下:
@property (nonatomic, retain) UIPopoverController*popoverController;
@property (nonatomic, retain) UIBarButtonItem*rootPopoverButtonItem;
⋯⋯
@synthesize popoverController;
@synthesize rootPopoverButtonItem;
协议DetailViewController 声明了两个必须由iPadHelpContentVC实现的方法:
@protocol DetailViewController
- (void)showRootPopoverButtonItem:(UIBarButtonItem*)barButtonItem;
- (void)invalidateRootPopoverButtonItem:(UIBarButtonItem*)barButtonItem;
@end
这两个方法在iPad方向转变为竖屏和横屏时调用。
最后,我们需要在iPadHelpIndexVC中实现Split ViewController的委托方法。
// 本方法用于竖屏时弹出popover
- (void)splitViewController:(UISplitViewController*)svcwillHideViewController:(UIViewController *)aViewControllerwithBarButtonItem:(UIBarButtonItem*)barButtonItemforPopoverController:(UIPopoverController*)pc {

// 从参数获得按钮和popover controller的引用.
barButtonItem.title = @"文档";
self.popoverController =pc;
self.rootPopoverButtonItem = barButtonItem;
// 获取右边栏,在右边栏中显示按钮
UIViewController<DetailViewController> *detailViewController = [splitViewController.viewControllers objectAtIndex:1];
[detailViewControllershowRootPopoverButtonItem:rootPopoverButtonItem];
}
// 本方法用于横屏时显示左边栏并消除popover按钮
- (void)splitViewController:(UISplitViewController*)svcwillShowViewController:(UIViewController *)aViewControllerinvalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem {
// splite view controller的viewControllers属性管理了两个View Controller:左边栏、
// 右边栏,它们分别用索引0和1访问。
UIViewController<DetailViewController> *detailViewController =[splitViewController.viewControllers objectAtIndex:1];
// 清除popover按钮(根据DetailViewController协议)
[detailViewControllerinvalidateRootPopoverButtonItem:rootPopoverButtonItem];
// 释放
self.popoverController =nil;
self.rootPopoverButtonItem = nil;
}
3、iPadHelpContentVC
这个类,很简单,我们也不准备实现实质性的功能,仅仅是在工具栏的Label上显示菜单的标题。因此它仅包含了一个ToolBar和一个Label对象:



这两个对象都需要相应出口进行连接:
@property (nonatomic, retain) IBOutlet UIToolbar *toolbar;
@property (nonatomic,retain)IBOutlet UILabel* lbTitle;
⋯⋯
@synthesize toolbar;
@synthesize lbTitle;

然后我们把它们连接在一起:



根据iPadHelpIndexVC中的介绍,iPadHelpContentVC类是需要实现DetailViewController协议的:
@interface iPadHelpContentVC : UIViewController
<DetailViewController>
⋯⋯
#pragma markDetailViewController 协议实现

- (void)showRootPopoverButtonItem:(UIBarButtonItem *)barButtonItem {

// 在工具栏上加一个popover按钮,用于弹出导航列表
NSMutableArray*itemsArray = [toolbar.items mutableCopy];
[itemsArray insertObject:barButtonItem atIndex:0];
[toolbar setItems:itemsArray animated:NO];
[itemsArray release];
}
- (void)invalidateRootPopoverButtonItem:(UIBarButtonItem*)barButtonItem {

// 横屏显示时,将popover按钮移除
NSMutableArray*itemsArray = [toolbar.items mutableCopy];
[itemsArray removeObject:barButtonItem];
[toolbar setItems:itemsArray animated:NO];
[itemsArray release];
}


二、在RootViewController中调用SplitViewController
假设我们的程序并不是一来就显示Split View Controller,那么我们需要将window的rootViewController设置为SplitView Controller对象。这个工作其实已经在iPadHelpVC类的viewDidLoad中做了,因此我们只需要把iPadHelpVC当做普通的ViewController来显示就可以了。你可以用presentModalView或者pushViewController显示SplitView Controller:
iPadHelpVC* helpVC=[[iPadHelpVC alloc]initWithNibName:@"iPadHelpVC"
bundle:nil];
[self.navigationController pushViewController:helpVC animated:YES];
注意:由于viewDidLoad只会在initWithNibName方法中调用,因此每次显示Split View Controller时你必须调用initWithNibName方法重新初始化helpVC,否则SplitView Controller不能显示(这跟Tab Bar Controller是一样的)。
三、从SplitViewController返回
我们的app存在多个View Controller(起码两个,一个Split ViewController和一个其他的View Controller),并且Split View Controller并不是第一个控制器,因此我们必须考虑如何从SplitView Controller返回第一个视图的问题。
我们首先决定在Split View Controller的右边栏加一个返回按钮。原因很简单,因为左边栏在竖屏时不显示,而右边栏无论横屏竖屏总是显示。
打开iPadHelpContentVC.xib,在工具栏上放一个Bar ButtonItem,并让它和相应的IBAction连接:
-(IBAction)backAction;
⋯⋯
-(void)backAction{
DLTAppDelegate* app=(DLTAppDelegate*)[[UIApplication sharedApplication]delegate];
app.window.rootViewController=app.stubVC;
UINavigationController* nc=(UINavigationController*)app.stubVC;
[nc popViewControllerAnimated:YES];
}
这里,我们重新把window的rootViewController设置回原来的Controller。
提示:你可能奇怪这个stubVC是什么时候保存的。 这是在iPadHelpVC的viewDidLoad方法中:
SplitDemoDelegate* app=(SplitDemoDelegate*)[[UIApplication sharedApplication]delegate];
app.stubVC=app.window.rootViewController;

此外,最后一句“[nc popViewControllerAnimated:YES];”稍微显得有些奇怪。因为iPadHelpVC本身还是一个ViewController(它还有一个无用的view属性),当你pushViewController时,实际上把这个带有空白View的iPadHelpVC压入navigationController的栈中了。当你恢复rootViewController时,自然将压入栈顶的空白View显示出来了。如果你去掉最后的这句,当从SplitView Controller返回原根视图时,会返回iPadHelpVC的这个View界面(空白窗体,但带一个Navigation Bar)。而此时你必须点击NavigationBar上的“返回”按钮才能返回根视图。
四、一个Bug
当你运行程序,你会发现如下Bug:







Bug描述:每当你弹出一次popover菜单并选择其中一项,则popover按钮(即文档按钮)会往右边移动一点位置。对比第1张和第3张截图,你会发现popover按钮的位置往右移动了约一个BarItem的距离。重复上述动作,popover按钮会不断右移,直到不可见。
这个问题在竖屏时出现。在iPadHelpContentVC中,我在工具栏中放入了一个FlexibleSpace Bar Button Item和一个Bar Button Item,以便在右边栏中显示退出按钮:


这就会导致上面的Bug出现。
暂时想到的解决办法是不要在xib中放入任何Bar Button Item,而改用代码动态生成所有Bar Button Item:
- (void)showRootPopoverButtonItem:(UIBarButtonItem *)barButtonItem {

// Add the popover button to thetoolbar.
NSMutableArray *itemsArray = [[NSMutableArray alloc]init];
[itemsArray addObject:barButtonItem];

UIBarButtonItem* item=[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil action:nil];
[itemsArray addObject:item];
[item release];

item=[[UIBarButtonItem alloc]
initWithTitle:@"返回"
style:UIBarButtonItemStyleBordered
target:self action:@selector(backAction)];
[itemsArray addObject:item];
[item release];

[toolbar setItems:itemsArray animated:NO];
[itemsArray release];
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐