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

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];

}



评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值