65.9K
CodeProject 正在变化。 阅读更多。
Home

多功能、对程序员友好的 iOS 分屏视图控制器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (14投票s)

2011 年 12 月 30 日

CPOL

10分钟阅读

viewsIcon

92895

downloadIcon

1867

一个 Objective-C 类,继承自 UIViewController,可以轻松地在 iPad 应用程序中实现分屏视图。

介绍  

本项目实现了一个 iPad 的分屏视图控制器。它非常类似于 iOS 库中的 UISplitViewController 类,但更通用且易于使用。在本文中,我们将开发的分屏视图控制器简称为分屏视图控制器,而 iOS 库中的则称为 UISplitViewController。该分屏视图控制器兼容 iOS 4.2 及以上版本。 

分屏视图控制器将两个视图控制器并排显示:屏幕左侧的侧视图控制器,以及右侧显示稍大的主视图控制器。它支持这两个视图控制器支持的所有用户界面方向。在本文中,我们将侧视图控制器和主视图控制器统称为子视图控制器

以下是此分屏视图控制器与 iOS 库中的 UISplitViewController 之间的一些区别:

  • 该分屏视图控制器有一个标志,允许客户端选择在 iPad 处于纵向时是否必须隐藏侧视图控制器。UISplitViewController 在此方向上始终隐藏侧视图控制器。
  • UISplitViewController 不同,该分屏视图控制器不必是应用程序的根控制器。UISplitViewController 不能被推送到 UINavigationController 的堆栈上,也不能以模态方式呈现。该分屏视图控制器没有这些限制。
  • 该分屏视图控制器的列表按钮可以显示在工具栏或导航控制器的导航栏中。我们称之为列表按钮,因为它在弹出视图控制器中显示侧视图控制器的视图,而侧视图控制器通常是一个 UITableViewController,用于显示项目列表(情况不一定如此;侧视图控制器可以是任意 UIViewController 对象)。
  • 可以通过编程方式轻松设置该分屏视图控制器。事实上,这就是我们在 源代码 的示例项目中使用的方式。 
  • 该分屏视图控制器的视图分隔器不必是一条 1 像素宽的黑线。它可以是任意 UIView 对象。
  • 该分屏视图控制器有一个属性,用于控制侧视图显示在主视图的左侧还是右侧。

下面的截图显示了在 示例项目 的导航控制器中,分屏视图控制器在横向方向上的外观。

splitviewcontroller/photo-2.png

这是在纵向方向上,按下列表按钮后,使用弹出视图样式的外观: 

splitviewcontroller/photo-1.png

这是使用侧滑样式的外观

实施

该分屏视图控制器由 SplitViewController 类实现,该类是 UIViewController 类的子类。SplitViewController 通过 initWithNibName:bundle: 方法从 XIB 文件初始化。标准的 XIB 文件 *SplitView.xib*,如图所示,包含一个根 UIView,带有一个子视图。我们称这个子视图为视图分隔器。在此 XIB 文件中,它是一条垂直的黑线(具体来说,它是一个纯 UIView,带有黑色背景,宽度为 1,高度与其父视图高度相同),其目的是分隔侧视图控制器和主视图控制器的视图。 

splitviewcontroller/SplitView.xib.png

视图分隔器的 x 坐标定义了子视图控制器的(垂直)分割点,尽管分屏视图控制器的客户端可以通过 splitPoint 属性覆盖此分割点(此属性设置视图分隔器中心的 x 坐标)。出于美学考虑,*SplitView.xib* 中视图分隔器的 x 坐标被选择为当分屏视图控制器占据 iPad 在横向方向上的全部屏幕时,黄金分割成立(1004/620 = 1.61935...;620/384 = 1.61458...)。分屏视图控制器基本上工作方式如下:当将侧视图控制器和主视图控制器分配给分屏视图控制器时,分屏视图控制器会将这些视图控制器的视图添加为自身主视图的子视图,并使用分屏视图的框架正确设置视图控制器视图的位置和尺寸。当界面方向旋转时,子视图控制器的视图框架会相应地进行漂亮的动画调整。  

客户端通过设置 sideViewDisplayOption 属性的值(默认值为 SideViewDisplayOptionAlwaysDisplay)来选择侧视图控制器在纵向方向上是否应隐藏。如果此属性设置为 SideViewDisplayOptionHideInPortraitOrientation,并且方向从横向变为纵向,则侧视图控制器的视图将从分屏视图控制器的视图中移除,并在导航控制器的导航栏左侧添加一个列表按钮(如果存在)。列表按钮使用的文本是 listButtonTitle 属性的值。  

当需要支持 iOS 4.2 或 iOS 4.3 时,这里有一个问题需要解决:在这些 iOS 版本中,UINavigationItem 只能有一个左按钮。这意味着,如果已经有一个后退按钮或其他左侧按钮,我们就不能添加列表按钮。SplitViewController 提供的变通方法是显示一个看起来像两个按钮的单个按钮。这是一种有些“hack”的解决方案,但它有效。该按钮是一个 UIBarButtonItem,带有自定义视图,显示一个 UIImage。按下按钮时,我们通过检查触摸的位置来确定实际按下了哪个按钮。为了有一个 UITouch 对象可用以获取位置,我们实现了 MyImageView 类,它是 UIImageView 的子类。这个类简单地重写了 UIViewtouchesEnded:withEvent: 方法,在该方法中,它调用 SplitViewController 的一个选择器,并将 UITouch 对象集作为参数。 

我们已经描述了 SplitViewController 在作为 UINavigationViewController 堆栈中的视图控制器之一时的工作方式。这并非 SplitViewController 的唯一用法。它可以在没有顶部栏的情况下使用。当分屏视图控制器不隐藏侧视图时(即,如果 hideSideViewInPortraitOrientation 设置为 NO),这很有意义,但如果它隐藏了侧视图,则需要在某处显示列表按钮。另一种选择是使用 SplitViewControllerUIToolbar。在这种情况下,您需要创建一个自定义 XIB 文件,其中包含一个 UIToolbar 和一个 UIToolbarButtonItem,后者将用作列表按钮。示例代码中的 *ToolbarSplitView.xib* 文件就是一个自定义 XIB 文件的示例。您必须将 toolbartoolbarListButton IBOutlet 分别连接到 XIB 中的 UIToolbarUIToolbarButtonItem。只有当分屏视图控制器的 listDisplayStyle 属性设置为 ListDisplayStylePopover 时,才需要 UIToolbar 的引用,以便在按下列表按钮时,能够计算弹出视图控制器的箭头位置,使箭头直接显示在列表按钮的正下方。当然,当分屏视图控制器处于横向方向时,列表按钮需要隐藏。SplitViewController 通过将列表按钮的宽度设置为 0.1 来隐藏它。当列表按钮需要再次可见时,其宽度会恢复到原始值。

使用代码 

要将 SplitViewController 添加到 Xcode 项目以供使用,需要将 zip 文件中的 *SplitViewControllerTestProject/SplitViewController* 文件夹中的源文件添加到项目中(下载源代码 - 72.7 KB)。   

zip 文件中的 Xcode 项目试图演示 SplitViewController 的所有用法,我将在以下段落中简要说明。

  • 要将 SplitViewController 设置为应用程序的根控制器,只需对应用程序的委托类(实现 UIApplicationDelegate 协议的类)进行一些小改动。我们将 SplitViewController 分配给应用程序 UIWindowrootViewController 属性。然后 SplitViewControllerUIWindow 保留,因此我们可以释放它。 
  • 我们在 application:didFinishLaunchingWithOptions: 方法中创建并显示 SplitViewController,如下所示: 

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
        self.window.backgroundColor = [UIColor whiteColor];
        
        topViewController = [[SplitViewController alloc] init];
        UIViewController* mainViewController = // Code to allocate and init main view controller.
        topViewController.mainViewController = mainViewController;
        UIViewController* sideViewController = // Code to allocate and init side view controller.
        topViewController.sideViewController = sideViewController;
        [sideViewController release];
        [mainViewController release];
        // Temporary fix for the 20px shift after launching app:
        CGRect initialFrame = topViewController.view.frame;
        initialFrame.origin.y+=20;
        topViewController.view.frame = initialFrame;
        self.window.rootViewController = topViewController; 
        [topViewController release];
    
        [self.window makeKeyAndVisible];
        return YES;
    }    

    请注意,当设备方向发生变化时,SplitViewController 会首先询问侧视图和主视图控制器是否应自动旋转到新的界面方向。只有当两个子视图控制器都返回 YES 时,它才会返回 YES 以进行自动旋转。因此,请确保在两个子视图控制器的 shouldAutorotateToInterfaceOrientation: 方法中,为要支持的每个界面方向返回 YES。自 iOS 6.0 起,将调用新的 supportedInterfaceOrientations 方法。

    如何让侧视图控制器和主视图控制器相互通信取决于您。很容易在侧视图控制器或主视图控制器中获取对 SplitViewController 的引用 - 只需添加一个类型为 SplitViewController* 且名为 splitViewController 的属性,如

    @interface MySideViewController : UITableViewController
    {
        SplitViewController* splitViewController;
    }
    
    @property (nonatomic, assign) SplitViewController* splitViewController; 
    
    @end  

    SplitViewControllersetMainViewController:setSideViewController: 方法将找到此属性并将其分配给 SplitViewController

  • 这是将一个 SplitViewController(在纵向方向上隐藏其侧视图控制器)推送到导航控制器堆栈的代码。
  • SplitViewController* nextSplitViewController = [[SplitViewController alloc] init];
    nextSplitViewController.title = @"Split view controller's title text";
    nextSplitViewController.sideViewDisplayOption = 
        SideViewDisplayOptionHideInPortraitOrientation;
    nextSplitViewController.listButtonTitle = @"Items";
    nextSplitViewController.sideViewHiddenLeftButtonsImage = [UIImage imageNamed:@"splitter_items.png"];
    UIViewController* mainViewController = // Code to allocate and init main view controller.
    nextSplitViewController.mainViewController = mainViewController;
    UIViewController* sideViewController = // Code to allocate and init side view controller.
    nextSplitViewController.sideViewController = sideViewController;
    sideViewController.contentSizeForViewInPopover = CGSizeMake(320,320);
    [mainViewController release];
    [sideViewController release];
    
    [myTopViewController.navigationController pushViewController:nextSplitViewController animated:YES];
    [nextSplitViewController release];      

    仅当应用程序将在 iOS 4.2 或 iOS 4.3 上运行时,才需要设置 sideViewHiddenLeftButtonsImage 属性。它是在侧视图控制器隐藏且列表按钮需要显示时模拟两个按钮的图像,如下图所示。

    splitviewcontroller/backsplitter_items.png

    要创建图像,请在运行 iOS 5.0+ 的设备上(或在运行 iOS 5.0+ 的模拟器上)运行应用程序,截屏,然后剪切出按钮。

  • 要在 UIToolbar 中显示列表按钮,我们必须创建自己的 XIB 来与 SplitViewController 一起使用。请参阅示例代码中的 *ToolbarSplitView.xib* 文件作为示例。我们创建一个 iPad XIB,添加一个分屏视图,然后添加一个包含 UIToolbarButtonUIToolbar(如果需要,我们还可以向 UIToolbar 添加许多其他 UIBarItem)。将 File's Owner 的类设置为 SplitViewController,并将 viewSplittertoolbartoolbarListButton IBOutlet 连接起来。如果我们想绑定 UIToolbar 中任何 UIToolbarButton 的操作(列表按钮除外),我们将需要以编程方式完成,如下面的示例所示。
  • SplitViewController* nextSplitViewController = 
        [[SplitViewController alloc] initWithNibName:@"MyToolbarSplitView" bundle:nil];
    nextSplitViewController.title = @"Split view controller's title text";
    nextSplitViewController.sideViewDisplayOption = 
        SideViewDisplayOptionHideInPortraitOrientation;
    nextSplitViewController.listDisplayStyle = ListDisplayStyleSlideIn;
    UIViewController* mainViewController = // Code to allocate and init main view controller.
    nextSplitViewController.mainViewController = mainViewController;
    UIViewController* sideViewController = // Code to allocate and init side view controller.
    nextSplitViewController.sideViewController = sideViewController;
    [mainViewController release];
    [sideViewController release];
    
    for (UIBarButtonItem* nextItem in ((UIToolbar*) [nextSplitViewController.view viewWithTag:15]).items)
    {
        if (nextItem.tag==15)
        {
            // Found back-button.
            nextItem.target = self;
            nextItem.action = @selector(dismissModalView);
        }
        else if (nextItem.tag==16)
        {
            // Found greet button.
            nextItem.target = mainViewController;
            nextItem.action = @selector(sayHi);
        }
    }
    
    [myTopViewController presentModalViewController:nextSplitViewController animated:YES];
    [nextSplitViewController release];   

    在该示例中,MyToolbarSplitView 是我们创建的自定义 XIB 文件的名称。
    listDisplayStyle 属性控制按下列表按钮时侧视图的显示方式。侧视图可以显示在弹出视图中(默认),或者可以滑入覆盖主视图,或者可以滑入并推开主视图。

历史

  • 2011/12/28 
    • 初始版本。 
  • 2012/10/29  
    • 在测试项目中,分屏视图控制器现在已显式设置为主窗口的根视图控制器。通过此更改,测试项目现在在 iOS 6.0 上也能正常工作。文章中的示例代码已更新以包含此改进。
  • 2012/12/18
    • splitPoint 以编程方式设置时,在从纵向更改为横向方向时,视图分隔器未能重新出现。此 bug 已修复。 
    • 修复了在某些 UI 配置中出现的方向更改动画中的 bug,该 bug 导致侧视图瞬时出现或消失。 
    • SplitViewController 绑定到自定义 xib 时,视图分隔器的宽度不再假定为 1。它现在可以具有任意宽度。 
  • 2012/12/24 
    • 当在纵向方向上按下列表按钮时,侧视图现在可以显示为弹出视图(如之前),或者可以滑入覆盖主视图,类似于“邮件”应用程序的工作方式。此行为通过新的 listDisplayStyle 属性进行控制。
    • 请注意,hideSideViewControllerInPortraitOrientationsideViewControllerHiddenLeftButtonsImage 属性已被弃用,并可能很快从 SplitViewController 类中移除。如有必要,请更新您的代码以使用 hideSideViewInPortraitOrientationsideViewHiddenLeftButtonsImage
  • 2013/01/20
    直到现在,分屏视图控制器才真正变得多功能!
    • 已添加新的列表显示样式。ListDisplayStyleSlideIn 样式会导致列表显示滑入,推开主视图。
    • hideSideViewInPortraitOrientation 属性已被新的 sideViewDisplayOption 属性取代。此属性提供三个选项:始终显示侧视图(默认),在纵向方向上隐藏侧视图,或默认隐藏侧视图(侧视图仅在按下列表按钮时显示)。
    • 新的 sideViewPosition 属性控制侧视图显示在主视图的左侧还是右侧。 
© . All rights reserved.