iPad for iPhone Developers 101 in iOS 6: UISplitView Tutorial

跟隨本文,你將使用三種最常用的基本功能從頭至尾做一個iPad應用。首先,應用會以split view的方式顯示來自於one of Ray’s Cocos2D games 的一列表怪物。然後你可以使用popover view改變標籤的顏色。最後,你可以使用自定義輸入視圖改變怪獸的武器。

讀完此文,你將可以很好的瞭解iPad相關開發的一些重要特性,你會感覺超級棒。

Adding a Split View

由於iPad的屏幕要比iPhone大很多,在iPad開發中,像iPhone開發那樣一次使用整個視圖作為UITableView將很少有意義。為了更好的利用屏幕空間,我們使用UISplitViewContoller。

Split View可以让我们把iPad屏幕分为两部分,每部一个view controller。通常左边导航,右边细节。

Screenshot of finished UISplitViewController

好了,让我们一起出发吧!

Starting From Scratch

我们完全可以使用Master-Detail Application模板作为开始,但为了更好的理解 UISplitView是怎样工作的,我们还是使用Empty View Application模板从头开始吧。即使将来为了节省时间我们选择使用Master-Detail template,这些理解在我们未来开发中也将非常有宜。

Select Empty Application template

新建一个工程,选择iOSApplicationEmpty Application模板,命名工程为MathMonsters.在产品下拉框选择iPad – Universal Apps我们在后来的课程中另行介绍。最后,确认 Use Automatic Reference Counting 文本框被选中,其它框框未选,然后创建工程。好了现在我们可以编译与运行程序了,不过此时你将看到的只是一个空白的屏幕。接下来,我们将创建一个Storyboard文件,点击FileNewFile… ,选中iOSUser InterfaceStoryboard 模板。

Add A Storyboard File


Device Family选择iPad,命名这个Storyboard为MainStoryboard_iPad.storyboard.

接下来,在Project Navigator选择我们的项目,滚动至 iPad Deployment Info 部分。然后在Main Storyboard输入框写下我们Storyboard名字:MainStoryboard_iPad.

Set Storyboard as the Main Storyboard for the iPad


打开MainStoryboard_iPad.storyboard ,拖拽 SplitViewController至这个空storyboard。

Adding a split view controller to the storyboard

这里将在我们的storyboard中添加一些元素

  • A Split View Controller. 这是我们应用的根视图– split view 将包含应用的整个部分。注:在 Attributes Inspector设置模拟器度量为横向方向,你讲看到如下图所示这样的 SplitViewController。

    Simulated UISplitViewController in Storyboard

  • A Navigation Controller. 这将是我们主视图控制器的根视图控制器的UINavigationController(即,左窗格的SplitView)。如果细看 Split View Controller,你会发现NavigationController与master view controller之间存在Segue关系。这可以让我们在Master View Controller创建一个完整的导航层次结构,而根本不需要影响 Detail View Controller。
  • A Table View Controller Root View Controller. A Table View Controller Root View Controller. 这是UINavigationController由Navigation Controller代表的rootViewController 。这最终将是包含怪物列表的那个 view controller。
  • A View Controller. A View Controller.这最终显示怪物们的所有细节。如果细看Split View Controller,你会发现View Controller与detail view controller存在Segue关系。

在你可以看到程序正常运行之前,还有最后一步。打开AppDelegate.m,用以下方法取代pplication:didFinishLaunchingWithOptions:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    return YES;
}

这里你删除了由模板用来创建一个空UIWindow的占位符代码。因为我们使用Storyboard配置我们的英用,我们不需要这些代码了。

Build and run, and rotate your simulator to landscape. You should see an empty split view controller:

编译并运行,旋转模拟器至风景模式。我们将看到一个如下图所示的空split view controller。

Empty split view controller

在这里我们想有我们自己的view controllers而不是这些默认的,所以,来吧,让我们一起开始创建吧。

Creating Custom View Controllers

我们将创建两个view controller placeholders用来放置我们的split view –左边一个table view controller,右边一个“plain” view controller。让我们以table view controller开始创建。打开 FileNewFile… 选择 iOSCocoa TouchObjective-C class 模板. 命名类 LeftViewController, 让它继承类 UITableViewController,确认所有的 checkboxes 被选中. 点击 Next 创建.

打开 LeftViewController.m,找到 numberOfSectionsInTableView:方法,按如下方法实现:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

然后找到 tableView:numberOfRowsInSection:方法,按如下方法实现:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 10;
}

现在,如果测试工程,我们可以看到10行空数据。
打開MainStoryboard_iPad.storyboard,選擇Table View Controller,在Identity Inspector(第三個標籤)改變under CustomClass下面的類為 LeftViewController。

Changing Custom Class for LeftViewController

此外,我们需要确保Table View中的原型cell被赋为重用标识符,否则当故事版加载时会造成程序崩溃。在左视图控制器内,选择原型cell,然后改变它的Identifier为“cell”(它在默认下设置为UITableViewController的子类),改变cell的style为“Basic”。

Setting Up The Prototype Cell

现在我们将在右部分创建一个View Controller。打开 FileNewFile… 选择 iOSCocoaTouchObjective-C class 模板. 命名类名为 RightViewController, 让它继承 UIViewController, 确认所有的checkboxes被选中.点击 Next 然后创建.

打开 MainStoryboard_iPad.storyboard, 在generic View Controller,选择View Controller对象,更改CustomClass下面的Class为RightViewController: 

Right view controller

拖拽一个label至屏幕中间,使用AutoLayout固定它与容器的水平与垂直中心。

Centering with Auto Layout

更改label文字为“Hello,World”,或者其它文字,以便稍后我们测试的时候可以确定它运行正常。注意,如果AutoLayout定位固定宽带截断了你所添加的控件,你可以更改约束的优先级,使之小于1000,然后你可以删除该约束。

编译并运行,现在,我们可以就可以看到自定义的view controllers了。

Making Your Model

下一步,我们需要定义一个想要的时间模型。我们不想把事情复杂化,所以我们用一个没有数据持久化的简单模型。

首先,创建一个代表我们想要显示的怪兽类。打开 FileNewFile… 选择 Cocoa TouchObjective-C Class 模板. 命名类名 Monster,使之继承 NSObject. 点击 Next 创建.

接下来我们只需要一个简单的类,使之包含我们想要显示的每只怪兽的一些实例变量和属性。然后创建一些方便的方法用来创建一些新的怪兽以及用来获得每只小怪兽的武器图片。用以下内容更改Monster.h 文件。

#import <Foundation/Foundation.h>
 
typedef enum {
    Blowgun = 0,
    NinjaStar,
    Fire,
    Sword,
    Smoke,
} Weapon;
 
@interface Monster : NSObject
 
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *description;
@property (nonatomic, strong) NSString *iconName;
@property (nonatomic, assign) Weapon weapon;
 
//Factory class method to create new monsters
+(Monster *)newMonsterWithName:(NSString *)name description:(NSString *)description 
  iconName:(NSString *)iconName weapon:(Weapon)weapon;
 
//Convenience instance method to get the UIImage representing the monster's weapon. 
-(UIImage *)weaponImage;
@end

And Monster.m with the following:

#import "Monster.h"
 
@implementation Monster
 
+(Monster *)newMonsterWithName:(NSString *)name description:(NSString *)description 
  iconName:(NSString *)iconName weapon:(Weapon)weapon {
 
    Monster *monster = [[Monster alloc] init];
    monster.name = name;
    monster.description = description;
    monster.iconName = iconName;
    monster.weapon = weapon;
 
    return monster;
}
 
-(UIImage *)weaponImage {
 
    switch (self.weapon) {
        case Blowgun:
            return [UIImage imageNamed:@"blowgun.png"];
            break;
        case Fire:
            return [UIImage imageNamed:@"fire.png"];
            break;
        case NinjaStar:
            return [UIImage imageNamed:@"ninjastar.png"];
            break;
        case Smoke:
            return [UIImage imageNamed:@"smoke.png"];
            break;
        case Sword:
            return [UIImage imageNamed:@"sword.png"];
        default:
            //Anything not named in the enum.
            return nil;
            break;
    }
}
 
@end

这就是用来定义模型的方法 – 让我们把它放在左侧视图吧!

Displaying the Monster List

打开LeftViewController.h文件,给怪兽数组添加一个新的实体变量以及属性。

#import <UIKit/UIKit.h>
 
@interface LeftViewController : UITableViewController
 
@property (nonatomic, strong) NSMutableArray *monsters;
@end

然后开始使用这个新数组给 LeftViewController.m添加一些调整:

// At top, under #import
#import "Monster.h"
  
// In numberOfRowsInSection, replace return 10 with:
return [_monsters count];
 
// In cellForRowAtIndexPath, after "Configure the cell..."
Monster *monster = _monsters[indexPath.row];
cell.textLabel.text = monster.name;

以上为表视图服务。现在,让我们填充一些默认怪兽吧。


首先,从one of Ray’s Cocos2D games.下载一些美术资源 some art 。拖拽包含这些图片的文件夹到Xcode中MathMonsters 目录下。确认 Copy items into destination group’s folder (if needed) 选中,点击Add按钮。

添加那些图片后,打开LeftViewController.m 文件,用initWithCoder:方法 取代 initWithStyle:方法,设置怪兽数组。注意:你用initWithCoder:方法 取代了 initWithStyle:方法。因为这个类是从Storyboard加载的。

-(id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super initWithCoder:aDecoder]) {
        //Initialize the array of monsters for display.
        _monsters = [NSMutableArray array];
 
        //Create monster objects then add them to the array.
        [_monsters addObject:[Monster newMonsterWithName:@"Cat-Bot" description:@"MEE-OW" 
             iconName:@"meetcatbot.png" weapon:Sword]];
        [_monsters addObject:[Monster newMonsterWithName:@"Dog-Bot" description:@"BOW-WOW" 
             iconName:@"meetdogbot.png" weapon:Blowgun]];
        [_monsters addObject:[Monster newMonsterWithName:@"Explode-Bot" 
             description:@"Tick, tick, BOOM!" iconName:@"meetexplodebot.png" weapon:Smoke]];
        [_monsters addObject:[Monster newMonsterWithName:@"Fire-Bot" 
             description:@"Will Make You Steamed" iconName:@"meetfirebot.png" weapon:NinjaStar]];
        [_monsters addObject:[Monster newMonsterWithName:@"Ice-Bot" 
             description:@"Has A Chilling Effect" iconName:@"meeticebot.png" weapon:Fire]];
        [_monsters addObject:[Monster newMonsterWithName:@"Mini-Tomato-Bot" 
             description:@"Extremely Handsome" iconName:@"meetminitomatobot.png" weapon:NinjaStar]];        
    }
 
    return self;
}

编译与运行程序,如果一切顺利我们可以在视图左边看到小怪兽列表。

Bots on left hand side of split view

Displaying Bot Details

接下来,为了可以看到特定小怪兽的细节信息,让我们设置右边视图。

打开 MainStoryboard_iPad.storyboard, 到右边视图控制器中删除我们早前放置的占位符label。

以下图为参考,拖拽如下controls到RightViewController的视图。

RightViewController Layout

     1.A 95×95   UIImageView的左上角显示怪物的形象。

     2. UILabel与UIIimageView等高对齐,高度45,字体黑体加粗,大小36.

     3. A 70×70 IImageView用来显示武器图像,与“Preferred way to Kill” 标签后缘对齐。

当我们构建这个视图时应当小心设置AutoLayout约束。

  • 确保 ImageViews的宽度和高度都固定,确保它们可以伸展旋转。
  • 确保怪物旁边的图像视图UILabels的宽度是不固定的 - 否则你的文字会被截断。如果必要,高度固定会 好些。

  •  尝试尽可能使用边缘对齐,这样的视图的位置互相依赖,而不是依赖父视图的位置。

使用适当的约束获得AutoLayout是很重要的,因为iPad应用需要处理正确旋转至任何方向。

注: Auto Layout是把双刃剑!如果您有困惑,我强烈建议您看看我们的Auto Layout系列教程,这是链接 Beginning Auto Layout。

根据您视图的不同设置,您得到的结果可能会略有不同。以下是我创建的一个测试可行的约束例子。

Autolayout Sample Constraints

好了,以上就是Autolayout。现在,让我们给这些视图设置一些属性。像下面这样,在RightViewController.h 添加一些IBOutlets。

#import <UIKit/UIKit.h>
 
@class Monster;
@interface RightViewController : UIViewController
 
@property (nonatomic, strong) Monster *monster;
@property (nonatomic, weak) IBOutlet UILabel *nameLabel;
@property (nonatomic, weak) IBOutlet UILabel *descriptionLabel;
@property (nonatomic, weak) IBOutlet UIImageView *iconImageView;
@property (nonatomic, weak) IBOutlet UIImageView *weaponImageView;
 
@end

这里,我们添加的各种UI元素的属性需要动态改变。我们还为在这个视图控制器中将要显示的怪兽对象增加了一个属性。需要注意的是IBOutlets保留为weakly - 他们也被嵌入在故事板的xib所retained。

然后,更新 LeftViewController.h,为符合委托协议的对象添加一个属性:

#import "RightViewController.h"
#import "Monster.h"
 
@implementation RightViewController
 
- (void)viewDidLoad
{
    [self refreshUI];
    [super viewDidLoad];
}
 
-(void)setMonster:(Monster *)monster
{
    //Make sure you're not setting up the same monster.
    if (_monster != monster) {
        _monster = monster;
 
        //Update the UI to reflect the new monster on the iPad.
        [self refreshUI];
    }
}
 
-(void)refreshUI
{
    _nameLabel.text = _monster.name;
    _iconImageView.image = [UIImage imageNamed:_monster.iconName];
    _descriptionLabel.text = _monster.description;
    _weaponImageView.image = [_monster weaponImage];
}
 
@end

现在,返回MainStoryboard_iPad.storyboard, 右击Right View Controller对象显示IBOutlets列表,然后从每个子项右边的小圆圈拖动至视图,一一连接与它们对应的IBOutlet。

Hooking up IBOutlets to views

好了,现在我们完全可以测试了!返回AppDelegate.m ,做如下调整。

//[Up top, under #import]
#import "LeftViewController.h"
#import "RightViewController.h"
 
//[Inside application:didFinishLaunchingWithOptions:
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;    
UINavigationController *leftNavController = [splitViewController.viewControllers objectAtIndex:0];
LeftViewController *leftViewController = (LeftViewController *)[leftNavController topViewController];
RightViewController *rightViewController = [splitViewController.viewControllers objectAtIndex:1];
 
Monster *firstMonster = [[leftViewController monsters] objectAtIndex:0];
[rightViewController setMonster:firstMonster];


分割视图控制器有一个属性名唤viewControllers数组,该数组里面包含左视图控制器以及右视图控制器。我们这里的左视图控制器是导航控制器,我们从该导航控制器的顶部视图controller来获得我们的左视图controller类。

编译并运行程序,如果一切正常,我们将在屏幕右部看到一些怪兽的具体信息。

Screenshot of finished UISplitViewController

注:选中右边这些怪兽没反应,不要急,接下来我们就实现这个功能。

Hooking Up The Left With the Right

让两个视图控制器之间最好的通信可以有许多不同的策略。在主从应用程序模板,苹果给出了一个指针用以从左视图控制器指向右视图控制器,当行被选中,左视图控制器设置右视图控制器上的一个属性。当属性更新时,右视图控制器覆盖原有属性来更新视图。
当我们的小应用右边只有一个视图控制器时,一切工作很好,但在更复杂应用引用的UISplitViewController,你要遵循建议的方法,并使用委托。

其基本思路是用一个单一的方法 - “selectedMonster:”定义一个协议。我们的右手边将实施这种方法,我们左手边,将接受相关对象的委托。

因此,让我们看看在代码中怎样实现。首先,为MonsterSelectionDelegate创建文件。FileNewFile… 选择iOSCocoa TouchObjective-C protocol 模板。输入MonsterSelectionDelegate协议,单击“下一步”,然后创建。

以如下内容更新MonsterSelectionDelegate.h :

@class Monster;
@protocol MonsterSelectionDelegate <NSObject>
@required
-(void)selectedMonster:(Monster *)newMonster;
@end

然后,在RightViewController.m添加如下代码以此显示怪物的信息:

#import "MonsterSelectionDelegate.h"
 
@interface LeftViewController : UITableViewController
 
@property (nonatomic, strong) NSMutableArray *monsters;
@property (nonatomic, assign) id<MonsterSelectionDelegate> delegate;
 
@end

基本上,这意味着该委托属性是必需的是一个对象,该对象具有selectedMonster:实施方法。该对象将负责处理小怪兽被选中后视图上面的变化。

现在,因为我们想要实现当怪兽被选中时更新RightViewController,我们需要进入RightViewController,并添加一些内容使之工作。首先,在RightViewController.h文件的顶部Monster类定义之前声明RightViewController协议:

#import "MonsterSelectionDelegate.h"
 
@interface RightViewController : UIViewController <MonsterSelectionDelegate>

然后,在RightViewController.m 之后添加委托方法:

-(void)selectedMonster:(Monster *)newMonster
{
    [self setMonster:newMonster];
}

现在,我们需要进入LeftViewController.m 文件,更新 didSelectRowAtIndexPath以通知新怪兽的selection委托。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    Monster *selectedMonster = [_monsters objectAtIndex:indexPath.row];
    if (_delegate) {
        [_delegate selectedMonster:selectedMonster];
    }
}

最后,进入 AppDelegate.m.在 didFinishLaunchingWithOptions 方法中你所添加代码的最后一行添加:

//Set the RightViewController as the left's delegate.
leftViewController.delegate = rightViewController;

好了!编译并运行程序,我们现在可以像下面那样在怪兽间来回选择。

SplitView Selection Working

如果你想知道使用分解成一个单独的文件的委托究竟有什么好处,注意左侧视图右侧视图之间不存在任何进口。因为你已经很清楚每一个期望,所以这意味着在其它工程中可以更容易的重用这些视图,或者用其它视图替换这些视图。

目前为止,我们已经很好的了解了Split View ,但还有一个问题- 如果你旋转模拟器为垂直方向,你就看不到怪物的列表了,所以你没有办法在它们之间进行切换。幸运的是,苹果公司已经给了我们一个简单的方法来解决这个问题 - 拆分视图控制器委托。

Setting the Split View Controller Delegate

像iOS中其它类一样,只要你注册你的类为委托,当有趣的事情发生时,Split View控制器将会通知你。

为了达到这些目的,打开RightViewController.h 并标记类实现 UISplitViewControllerDelegate:

@interface RightViewController : UIViewController <MonsterSelectionDelegate, UISplitViewControllerDelegate>

然后切换到 RightViewController.m,为一些委托添加一些细节实现,以使它们可以正常运行。

-(void)splitViewController:(UISplitViewController *)svc 
    willHideViewController:(UIViewController *)aViewController 
    withBarButtonItem:(UIBarButtonItem *)barButtonItem  
    forPopoverController:(UIPopoverController *)pc
{
    NSLog(@"Will hide left side");
}
 
-(void)splitViewController:(UISplitViewController *)svc 
    willShowViewController:(UIViewController *)aViewController 
    invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
    NSLog(@"Will show left side");
}

最后,切换至AppDelegate.m ,添加如下一行:

splitViewController.delegate = rightViewController;

编译并运行,横竖屏旋转模拟器几次,我们将在控制台看到如下一些日志信息。

2013-02-07 11:25:36.734 MathMonsters[24551:c07] Will hide left side
2013-02-07 11:25:45.979 MathMonsters[24551:c07] Will show left side

既然现在我们已经获得了split view 控制器显示与隐藏左视图的通知,我们只需要再加些代码在工具栏添加一个按钮就可以控制左视图。

Adding a Toolbar and Popover List

您可能已经注意到,委托方法给我们一个方便的UIBarButtonItem,我们可以添加它到工具栏中,以显示在popover左侧。实现这些,我们只需要把这个按钮添加到工具栏或导航栏。

因此,让我们尝试一下吧。打开MainStoryboard_iPad.storyboard,找到右视图控制器,拖动UINavigationBar到视图顶部。你可能要把所用控件都向下移动一些 - 你应该能够保留大部分现有的限制。

固定navigation bar 和 iconImageView的垂直间距,以确保其它视图旋转的时候总是低于工具栏。

然后添加两个属性至RightViewController.h:

@property (nonatomic, weak) IBOutlet UINavigationItem *navBarItem;
@property (nonatomic, strong) UIPopoverController *popover;

在 RightViewController.m’s selectedMonster:方法添加如下代码:

//Dismisses the popover if it's showing.
if (_popover != nil) {
     [_popover dismissPopoverAnimated:YES];
}

现在在RightViewController.m中MonsterSelectionDelegate方法下添加UISplitViewDelegate的协议方法响应显示和隐藏LeftViewController :

#pragma mark - UISplitViewDelegate methods
-(void)splitViewController:(UISplitViewController *)svc 
    willHideViewController:(UIViewController *)aViewController 
    withBarButtonItem:(UIBarButtonItem *)barButtonItem  
    forPopoverController:(UIPopoverController *)pc
{
    //Grab a reference to the popover
    self.popover = pc;
 
    //Set the title of the bar button item
    barButtonItem.title = @"Monsters";
 
    //Set the bar button item as the Nav Bar's leftBarButtonItem
    [_navBarItem setLeftBarButtonItem:barButtonItem animated:YES];
}
 
-(void)splitViewController:(UISplitViewController *)svc 
    willShowViewController:(UIViewController *)aViewController 
    invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
    //Remove the barButtonItem.
    [_navBarItem setLeftBarButtonItem:nil animated:YES];
 
 
    //Nil out the pointer to the popover.
    _popover = nil;
}

最后,回到右视图控制器的Storyboard,连接navBarItem IBOutlet到UINavigationBar NavigationItem的对象(当你在那里,你也可以删除NavigationItem的标题“Title”)。就是它了!编译并运行该项目,现在,当你旋转,工具栏上会出现一个按钮,我们可以点击弹出一个popover,示例如下:

UISplitViewDelegate Popover

注:如果你想要在模拟器操作时按钮可以显示与隐藏侧边栏进入和退出框架,你可以去“调试”菜单,选择“切换缓慢的动画”,你可以看的视图按钮自动进行显示和隐藏。

Normal UIPopoverViewController type

Standard UIPopover.

注意popover显示一个UISplitViewController有点独特 - 它像一个抽屉滑出。大多数你处理的其它popovers会更像本教程的第2部分所述,它们有一个边框和一个箭头指向一个按钮。

Using a UINavigationController Instead

在这个项目中,我们在右手一侧使用一个含静态UINavigationBar的自定义视图。然而,在我们的项目中,我们可能想在右侧使用UINavigationController,来允许我们进一步浏览右边的视图。我们怎样设置才能让bar button item显示在UINavigationController的toolbar上面呢?

为了实现这,我们只需要获得UIViewController的navigationItem 属性的引用,并在willHideViewController方法下像当前版本那样设置 left bar button item :

UINavigationItem *navItem = [self navigationItem];
[navItem setLeftBarButtonItem:barButtonItem animated:YES];

注意,如果UIViewController不在UINavigationController的视图控制器栈内,navigationItem将为空。

Where To Go From Here?

这里 example project 有一个包含我们目前所有已书写的代码的示例项目。

好了- 这就是我们如何最基础的使用UISplitViewController。注意在实践中,为了节省时间,我们很可能只使用主详细模板 - 但现在我们知道它是如何工作的感觉总是要好很多。

好了,这节就到这里了。下文我们将学习怎样在iPad下使用popovers.how to use popovers on the iPad!

谢谢观看!希望对您有所帮助。

第一次翻译,水平所限,难免有误,还望诸位看官多多指点,扶正。

原文链接:http://www.raywenderlich.com/29469/ipad-for-iphone-developers-101-in-ios-6-uisplitview-tutorial

原文地址:https://www.cnblogs.com/bbsno1/p/3279737.html