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






4.71/5 (14投票s)
一个 Objective-C 类,继承自 UIViewController,可以轻松地在 iPad 应用程序中实现分屏视图。
介绍
本项目实现了一个 iPad 的分屏视图控制器。它非常类似于 iOS 库中的UISplitViewController
类,但更通用且易于使用。在本文中,我们将开发的分屏视图控制器简称为分屏视图控制器,而 iOS 库中的则称为 UISplitViewController
。该分屏视图控制器兼容 iOS 4.2 及以上版本。 分屏视图控制器将两个视图控制器并排显示:屏幕左侧的侧视图控制器,以及右侧显示稍大的主视图控制器。它支持这两个视图控制器支持的所有用户界面方向。在本文中,我们将侧视图控制器和主视图控制器统称为子视图控制器。
以下是此分屏视图控制器与 iOS 库中的 UISplitViewController
之间的一些区别:
- 该分屏视图控制器有一个标志,允许客户端选择在 iPad 处于纵向时是否必须隐藏侧视图控制器。
UISplitViewController
在此方向上始终隐藏侧视图控制器。 - 与
UISplitViewController
不同,该分屏视图控制器不必是应用程序的根控制器。UISplitViewController
不能被推送到UINavigationController
的堆栈上,也不能以模态方式呈现。该分屏视图控制器没有这些限制。 - 该分屏视图控制器的列表按钮可以显示在工具栏或导航控制器的导航栏中。我们称之为列表按钮,因为它在弹出视图控制器中显示侧视图控制器的视图,而侧视图控制器通常是一个
UITableViewController
,用于显示项目列表(情况不一定如此;侧视图控制器可以是任意UIViewController
对象)。 - 可以通过编程方式轻松设置该分屏视图控制器。事实上,这就是我们在 源代码 的示例项目中使用的方式。
- 该分屏视图控制器的视图分隔器不必是一条 1 像素宽的黑线。它可以是任意
UIView
对象。 - 该分屏视图控制器有一个属性,用于控制侧视图显示在主视图的左侧还是右侧。
下面的截图显示了在 示例项目 的导航控制器中,分屏视图控制器在横向方向上的外观。
这是在纵向方向上,按下列表按钮后,使用弹出视图样式的外观:
这是使用侧滑样式的外观
实施
该分屏视图控制器由 SplitViewController
类实现,该类是 UIViewController
类的子类。SplitViewController
通过 initWithNibName:bundle:
方法从 XIB 文件初始化。标准的 XIB 文件 *SplitView.xib*,如图所示,包含一个根 UIView
,带有一个子视图。我们称这个子视图为视图分隔器。在此 XIB 文件中,它是一条垂直的黑线(具体来说,它是一个纯 UIView
,带有黑色背景,宽度为 1,高度与其父视图高度相同),其目的是分隔侧视图控制器和主视图控制器的视图。
视图分隔器的 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
的子类。这个类简单地重写了 UIView
的 touchesEnded:withEvent:
方法,在该方法中,它调用 SplitViewController
的一个选择器,并将 UITouch
对象集作为参数。
我们已经描述了 SplitViewController
在作为 UINavigationViewController
堆栈中的视图控制器之一时的工作方式。这并非 SplitViewController
的唯一用法。它可以在没有顶部栏的情况下使用。当分屏视图控制器不隐藏侧视图时(即,如果 hideSideViewInPortraitOrientation
设置为 NO
),这很有意义,但如果它隐藏了侧视图,则需要在某处显示列表按钮。另一种选择是使用 SplitViewController
和 UIToolbar
。在这种情况下,您需要创建一个自定义 XIB 文件,其中包含一个 UIToolbar
和一个 UIToolbarButtonItem
,后者将用作列表按钮。示例代码中的 *ToolbarSplitView.xib* 文件就是一个自定义 XIB 文件的示例。您必须将 toolbar
和 toolbarListButton
IBOutlet
分别连接到 XIB 中的 UIToolbar
和 UIToolbarButtonItem
。只有当分屏视图控制器的 listDisplayStyle
属性设置为 ListDisplayStylePopover
时,才需要 UIToolbar
的引用,以便在按下列表按钮时,能够计算弹出视图控制器的箭头位置,使箭头直接显示在列表按钮的正下方。当然,当分屏视图控制器处于横向方向时,列表按钮需要隐藏。SplitViewController
通过将列表按钮的宽度设置为 0.1 来隐藏它。当列表按钮需要再次可见时,其宽度会恢复到原始值。
使用代码
要将 SplitViewController
添加到 Xcode 项目以供使用,需要将 zip 文件中的 *SplitViewControllerTestProject/SplitViewController* 文件夹中的源文件添加到项目中(下载源代码 - 72.7 KB)。
zip 文件中的 Xcode 项目试图演示 SplitViewController
的所有用法,我将在以下段落中简要说明。
- 要将
SplitViewController
设置为应用程序的根控制器,只需对应用程序的委托类(实现UIApplicationDelegate
协议的类)进行一些小改动。我们将SplitViewController
分配给应用程序UIWindow
的rootViewController
属性。然后SplitViewController
由UIWindow
保留,因此我们可以释放它。
我们在 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
SplitViewController
的 setMainViewController:
或 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
属性。它是在侧视图控制器隐藏且列表按钮需要显示时模拟两个按钮的图像,如下图所示。
要创建图像,请在运行 iOS 5.0+ 的设备上(或在运行 iOS 5.0+ 的模拟器上)运行应用程序,截屏,然后剪切出按钮。
UIToolbar
中显示列表按钮,我们必须创建自己的 XIB 来与 SplitViewController
一起使用。请参阅示例代码中的 *ToolbarSplitView.xib* 文件作为示例。我们创建一个 iPad XIB,添加一个分屏视图,然后添加一个包含 UIToolbarButton
的 UIToolbar
(如果需要,我们还可以向 UIToolbar
添加许多其他 UIBarItem
)。将 File's Owner 的类设置为 SplitViewController
,并将 viewSplitter
、toolbar
和 toolbarListButton
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
属性进行控制。 - 请注意,
hideSideViewControllerInPortraitOrientation
和sideViewControllerHiddenLeftButtonsImage
属性已被弃用,并可能很快从SplitViewController
类中移除。如有必要,请更新您的代码以使用hideSideViewInPortraitOrientation
和sideViewHiddenLeftButtonsImage
。 - 2013/01/20
直到现在,分屏视图控制器才真正变得多功能! - 已添加新的列表显示样式。
ListDisplayStyleSlideIn
样式会导致列表显示滑入,推开主视图。 hideSideViewInPortraitOrientation
属性已被新的sideViewDisplayOption
属性取代。此属性提供三个选项:始终显示侧视图(默认),在纵向方向上隐藏侧视图,或默认隐藏侧视图(侧视图仅在按下列表按钮时显示)。- 新的
sideViewPosition
属性控制侧视图显示在主视图的左侧还是右侧。