您的位置:首页 > 产品设计 > UI/UE

UINavigationController

2015-12-16 16:07 531 查看


UITabBarController可以实现多页的效果,但如何实现多个相互有关系的页面呢?

你可以使用UINavigationController来添加一个drill-down interface,深入形的多页面。



UINavigationController可以用于显示多页面,每个页面对应一个view controller, 它本身维护一个controller的堆栈;

初始化一个UINavigationController,你需要先提供一个UIViewController,它会作为navigation controller的root view controller。

这个根节点会一直处于栈的底部,更多的view controller可以加入栈中。

view controller的加入或者删除都会显示动画。

图10.3中显示有2个controller,只有处于栈上层的controller会显示在页面中。



UINavigationController有一个viewController的数组(起到栈的作用?)  同时也有一个topViewController的属性用于指向最顶层的controller

UINavigationController的基类是UIViewController,所以它有一个view的数据,它有2个子对象,一个是UINavigationBar和

topViewController的view。你通过设置window的rootViewController来显示UINavigationController.(它本身除了navigationBar并无其他的子视图)。



在这一章中,你要在UINavigationController中添加2个controller,一个是BNRItemsViewController,一个是用于显示并编辑BNRItem的controller,

把它们都加入UINavigationController的堆栈中,当用户点击了UITableView中的一行,就可以进入另外一个controller中。



这个view图看起来很复杂,其实你只要把每一个controller都当成一个独立的整体来对待即可。 

在application:didFinishLaunchingWithOptions设置window的root view controller为UINavigationController.

- (BOOL)application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

self.window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]];

// Override point for customization after application launch

BNRItemsViewController *itemsViewController

= [[BNRItemsViewController alloc] init];

// Create an instance of a UINavigationController

// its stack contains only itemsViewController

UINavigationController *navController = [[UINavigationController alloc]

initWithRootViewController:itemsViewController];

// Place navigation controller's view in the window hierarchy

self.window.rootViewController = navController;

self.window.backgroundColor = [UIColor whiteColor];

[self.window makeKeyAndVisible];

return YES;

}

Build and run the application. Homepwner will look the same as it did before – except now it has a

UINavigationBar at the top of the screen (Figure 10.6). Notice how BNRItemsViewController’s view

was resized to fit the screen with a navigation bar. UINavigationController did this for you.



设置window的rootViewController 为UINavigationController ,初始化UINavigationController 的rootViewController 为BNRItemsViewController。

创建另外一个view controller用于显示BNRItem的详细细节。



In BNRDetailViewController.m, delete all of the code between the @implementation and @end

directives so that the file looks like this:

#import "BNRDetailViewController.h"

@interface BNRDetailViewController ()

@end

@implementation BNRDetailViewController

@end

In Homepwner, you want the user to be able to tap an item to get another screen with editable

text fields for each property of that BNRItem. This view will be controlled by an instance of

BNRDetailViewController.

创建一个BNRDetailViewController用于编辑BNRItem的属性。

The detail view needs four subviews – one for each instance variable of a BNRItem instance. And

because you need to be able to access these subviews during runtime, BNRDetailViewController

needs outlets for these subviews. The plan is to add four new outlets to BNRDetailViewController,

drag the subviews onto the view in the XIB file, and then make the connections.

使用xib创建BNRDetailViewController的布局,它有4个子view。

In previous exercises, these were three distinct steps: you added the outlets in the interface file, then

you configured the interface in the XIB file, and then you made connections. You can combine these

steps using a shortcut in Xcode. First, open BNRDetailViewController.xib by selecting it in the

project navigator.

你可以把添加outlet,在xib文件中编辑界面,建立Outlet和xib之间的连接,简化成一步操作。首先打开xib文件。

Now, Option-click on BNRDetailViewController.m in the project navigator. This shortcut opens the

file in the assistant editor, right next to BNRDetailViewController.xib. (You can toggle the assistant

editor by clicking the middle button from the Editor control at the top of the workspace; the shortcut to

display the assistant editor is Command-Option-Return; to return to the standard editor, use Command-

Return.)

在assistant editor界面中打开BNRDetailViewController.m,你也可以使用快捷键打开或者关闭这个界面

You will also need the object library available so that you can drag the subviews onto the view.

Show the utility area by clicking the right button in the View control at the top of the workspace (or

Command-Option-0).

显示utility area 界面,用于拖动子view到视图中

Your window is now sufficiently cluttered. Let’s make some temporary space. Hide the navigator

area by clicking the left button in the View control at the top of the workspace (the shortcut for this is

Command-0). Then, change the dock in Interface Builder to show the icon view by clicking the toggle

button in the lower left corner of the editor. Your workspace should now look like Figure 10.8.

你可以通过隐藏navigator区域来增大显示空间。



然后拖动4个UILable和3个UITextFiled到canvas区域



要考虑到视图会在NavigationBar下方显示,所以要模拟一个navigation bar,在根view的属性中,可以设置top bar是Translucent Navigation Bar.

直接从视图连接到代码中,就可以自动为你创建属性或者Action;这里的属性为weak因为它并不是根节点视图。



将鼠标放在nameFiled上可以看到和视图的连接关系;

After making the connections, BNRDetailViewController.m should look like this:

#import "BNRDetailViewController.h"

@interface BNRDetailViewController ()

@property (weak, nonatomic) IBOutlet UITextField *nameField;

@property (weak, nonatomic) IBOutlet UITextField *serialNumberField;

@property (weak, nonatomic) IBOutlet UITextField *valueField;

@property (weak, nonatomic) IBOutlet UILabel *dateLabel;

@end

@implementation BNRDetailViewController

@end

连接之后,就可以看到生成的代码了。

If your file looks different, then your outlets are not connected right.

Fix any disparities between your file and the code shown above in three steps: First, go through

the Control-drag process and make connections again until you have the four lines shown above in

your BNRDetailViewController.m. Second, remove any wrong code (like non-property method

declarations or instance variables) that got created. Finally, check for any bad connections in the XIB

file. In BNRDetailViewController.xib, Control-click on the File's Owner. If there are yellow warning

signs next to any connection, click the x icon next to those connections to disconnect them.

如果你最后的结果和上述的不一致,那就要看下哪里出了问题。 可以重新拖线建立连接,接着删除所有

错误的代码,最后到file owner中查看是否有黄色的提醒标记。

It is important to ensure there are no bad connections in a XIB file. A bad connection typically happens

when you change the name of an instance variable but do not update the connection in the XIB file. Or,

you completely remove an instance variable but do not remove it from the XIB file. Either way, a bad

connection will cause your application to crash when the XIB file is loaded.

要注意有没有建立错误的连接,比如你改变了变量的名字但却没有更新和xib之间的连接关系,

以及在代码中删除了变量却没有从xib删除相关的view, 这样在加载xib文件的时候就会出现crash

Now let’s make more connections. For each instance of UITextField in the XIB file, connect the

delegate property to the File's Owner. (Remember, Control-drag from the UITextField to the File's

Owner and select delegate from the list.)

接着在xib UITextField控件 的delegate属性和文件的owner建立连接。

Now that this project has a good number of source files, you will be switching between them fairly

regularly. One way to speed up switching between commonly accessed files is to use Xcode tabs. If

you double-click on a file in the project navigator, the file will open in a new tab. You can also open

up a blank tab with the shortcut Command-T. The keyboard shortcuts for cycling through tabs are

Command-Shift-} and Command-Shift-{. (You can see the other shortcuts for project organization by

selecting the General tab from Xcode’s preferences.)

可以使用快捷键快速切换xcode中的文件

Navigating with UINavigationController

Now you have a navigation controller and two view controller subclasses. Time to put the pieces

together. The user should be able to tap a row in BNRItemsViewController’s table view and have the

BNRDetailViewController’s view slide onto the screen and display the properties of the selected

BNRItem instance.

现在你已经有了navigation controller以及2个view controller,现在要把它们连接在一起;当你点击

BNRItemsViewController中table view的某个item的时候,就需要显示另外一个view controller用于

显示BNRItem的内容

Pushing view controllers

Of course, you need to create an instance of BNRDetailViewController. Where should this object be

created? Think back to previous exercises where you instantiated all of your controllers in the method

application:didFinishLaunchingWithOptions:. For example, in Chapter 6, you created both view

controllers and immediately added them to the tab bar controller’s viewControllers array.

那么你什么时候创建BNRDetailViewController的实例比较好呢? 回顾之前的章节,是在application:didFinishLaunchingWithOptions:.

就把所有的controller都创建好,并加入到一个viewControllers 的数组中,现在是不是也要这么做呢?

However, when using a UINavigationController, you cannot simply store all of the possible view

controllers in its stack. The viewControllers array of a navigation controller is dynamic – you start

with a root view controller and push view controllers depending on user input. Therefore, some object

other than the navigation controller needs to create the instance of BNRDetailViewController and be

responsible for adding it to the stack.

但是在UINavigationController中,你不能简单把所需要所有的view controller加入到它的栈中,

navigation controller的viewController是动态,你应该从root view controller开始,并根据用户的输入

添加新的view controller。因此,应该有个新的对象,专门负责创建BNRDetailViewController 实例,

并负责把它加到UINavigationController的栈中。

This object must meet two requirements: it needs to know when to push a BNRDetailViewController

onto the stack, and it needs a pointer to the navigation controller to send the navigation controller

messages, namely, pushViewController:animated:.

这个对象需要知道什么时候把BNRDetailViewController压人到栈中,它也需要一个navigation controller的指针,

用于给navigation controller发送消息,这个消息的名字可以是pushViewController:animated:.

BNRItemsViewController fills both requirements. First, it knows when a row is tapped in a table view

because, as the table view’s delegate, it receives the message tableView:didSelectRowAtIndexPath:

when this event occurs. Second, any view controller in a navigation controller’s stack can get a pointer

to that navigation controller by sending itself the message navigationController. As the root view

controller, BNRItemsViewController is always in the navigation controller’s stack and thus can always

access it.

BNRItemsViewController 可以满足以上的需求,首先它知道在table view中何时被点击以及点击的是哪一个,

另外所有在navigation controller栈中的view controller都可以通过发送navigationController消息获取navigationController的引用。

因为作为navigation controller的根view controller,它始终处于栈中。

Therefore, BNRItemsViewController will be responsible for creating the instance of

BNRDetailViewController and adding it to the stack. At the top of BNRItemsViewController.m,

import the header file for BNRDetailViewController.

#import "BNRDetailViewController.h"

@interface BNRItemsViewController : UITableViewController

When a row is tapped in a table view, its delegate is sent tableView:didSelectRowAtIndexPath:,

which contains the index path of the selected row. In BNRItemsViewController.m, implement this

method to create a BNRDetailViewController and then push it on top of the navigation controller’s stack.

这样你可以在BNRItemsViewController 在用户点击的时候,创建BNRDetailViewController 实例并加入到

navigation controller的栈中。用户点击table view的时候,会发送tableView:didSelectRowAtIndexPath:消息,这个

消息在BNRItemsViewController会收到。(这样是否符合低耦合?)

@implementation BNRItemsViewController

- (void)tableView:(UITableView *)tableView

didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{

BNRDetailViewController *detailViewController =

[[BNRDetailViewController alloc] init];

// Push it onto the top of the navigation controller's stack

[self.navigationController pushViewController:detailViewController

animated:YES];

}

Build and run the application. Create a new item and select that row from the UITableView. Not only

are you taken to BNRDetailViewController’s view, but you also get a free animation and a back

button in the UINavigationBar. Tap this button to get back to BNRItemsViewController.

这样当你点击UITableView的一行时候,不仅创建了一个BNRDetailViewController的视图,也

有一个动画以及在UINavigationBar上显示一个回退的按钮。

Since the UINavigationController’s stack is an array, it will take ownership of any view controller

added to it. Thus, the BNRDetailViewController is owned only by the UINavigationController

after tableView:didSelectRowAtIndexPath: finishes. When the stack is popped, the

BNRDetailViewController is destroyed. The next time a row is tapped, a new instance of

BNRDetailViewController is created.

因为UINavigationController管理了一个View controller的堆栈,在调用了tableView:didSelectRowAtIndexPath:之后

你压人了BNRDetailViewController,当你点击后退的时候就会弹出BNRDetailViewController ,

系统也会回收这个对象。

Having a view controller push the next view controller is a common pattern. The root view controller

typically creates the next view controller, and the next view controller creates the one after that, and so

on. Some applications may have view controllers that can push different view controllers depending

on user input. For example, the Photos app pushes a video view controller or an image view controller

onto the navigation stack depending on what type of media was selected.

使用一个 view controller压入另外一个 view controller是通用的编程模式。

(The iPad-only class UISplitViewController calls for a different pattern. The iPad’s larger screen

size allows two view controllers in a drill-down interface to appear on screen simultaneously instead of

being pushed onto the same stack. You will learn more about UISplitViewController in Chapter 22.)

在 iPad-中可以同时显示2个不同的view controller;你可以使用UISplitViewController 来实现。

Passing data between view controllers

Of course, the text fields on the screen are currently empty. To fill these fields, you need a way to pass

the selected BNRItem from the BNRItemsViewController to the BNRDetailViewController.

To pull this off, you will give BNRDetailViewController a property to hold a BNRItem. When a

row is tapped, BNRItemsViewController will give the corresponding BNRItem to the instance of

BNRDetailViewController that is being pushed onto the stack. The BNRDetailViewController

will populate its text fields with the properties of that BNRItem. Editing the text in the text fields on

BNRDetailViewController’s view will change the properties of that BNRItem.

显示BNRDetailViewController 需要有BNRItem的数据,你需要在创建它的时候也传送相关的数据。

In BNRDetailViewController.h, add this property. Also, at the top of this file, forward declare

BNRItem.

#import <UIKit/UIKit.h>

@class BNRItem;

@interface BNRDetailViewController : UIViewController

@property (nonatomic, strong) BNRItem *item;

@end

创建一个item的属性。

In BNRDetailViewController.m, import BNRItem’s header file.

#import "BNRItem.h"

When the BNRDetailViewController’s view appears on the screen, it needs to set up its subviews

to show the properties of the item. In BNRDetailViewController.m, override viewWillAppear: to

transfer the item’s properties to the various instances of UITextField.

在BNRDetailViewController.m中,重载viewWillAppear用于初始化视图。

- (void)viewWillAppear:(BOOL)animated

{

[super viewWillAppear:animated];

BNRItem *item = self.item;

self.nameField.text = item.itemName;

self.serialNumberField.text = item.serialNumber;

self.valueField.text = [NSString stringWithFormat:@"%d", item.valueInDollars];

// You need an NSDateFormatter that will turn a date into a simple date string

static NSDateFormatter *dateFormatter = nil;

if (!dateFormatter) {

dateFormatter = [[NSDateFormatter alloc] init];

dateFormatter.dateStyle = NSDateFormatterMediumStyle;

dateFormatter.timeStyle = NSDateFormatterNoStyle;

}

// Use filtered NSDate object to set dateLabel contents

self.dateLabel.text = [dateFormatter stringFromDate:item.dateCreated];

}

In BNRItemsViewController.m, add the following code to tableView:didSelectRowAtIndexPath: so

that BNRDetailViewController has its item before viewWillAppear: gets called.

- (void)tableView:(UITableView *)tableView

didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{

BNRDetailViewController *detailViewController =

[[BNRDetailViewController alloc] init];

NSArray *items = [[BNRItemStore sharedStore] allItems];

BNRItem *selectedItem = items[indexPath.row];

// Give detail view controller a pointer to the item object in row

detailViewController.item = selectedItem;

[self.navigationController pushViewController:detailViewController

animated:YES];

}

Many programmers new to iOS struggle with how data is passed between view controllers. Having all

of the data in the root view controller and passing subsets of that data to the next UIViewController

(like you just did) is a clean and efficient way of performing this task.

Build and run your application. Create a new item and select that row in the UITableView. The view

that appears will contain the information for the selected BNRItem. While you can edit this data, the

UITableView will not reflect those changes when you return to it. To fix this problem, you need to

implement code to update the properties of the BNRItem being edited. In the next section, you will see

when to do this.

这样你就可以在BNRDetailViewController的视图显示BNRItem相关的内容了,你可以在这里编辑内容,

但你返回的时候,tableview并不会更新数据,你可以在下一节中进行相关的操作。

Appearing and disappearing views

Whenever a UINavigationController is about to swap views, it sends out two messages:

viewWillDisappear: and viewWillAppear:. The UIViewController that is about to be popped off the

stack is sent the message viewWillDisappear:. The UIViewController that will then be on top of the

stack is sent viewWillAppear:.

在UINavigationController切换的view controller的时候,被移除的会收到viewWillDisappear:.,要显示的

会显示viewWillAppear:.

When a BNRDetailViewController is popped off the stack, you will set the properties of its

item to the contents of the text fields. When implementing these methods for views appearing

and disappearing, it is important to call the superclass’s implementation – it might have some

work to do and needs to be given the chance to do it. In BNRDetailViewController.m, implement

viewWillDisappear:.

当BNRDetailViewController 从栈中移除的时候,你需要保存text filed的值到BNRItem中。

当你调用appearing或者disappearing,要先调用父类的方法。

- (void)viewWillDisappear:(BOOL)animated

{

[super viewWillDisappear:animated];

// Clear first responder

[self.view endEditing:YES];

// "Save" changes to item

BNRItem *item = self.item;

item.itemName = self.nameField.text;

item.serialNumber = self.serialNumberField.text;

item.valueInDollars = [self.valueField.text intValue];

}

Notice the use of endEditing:. When the message endEditing: is sent to a view, if it or any of its

subviews is currently the first responder, it will resign its first responder status, and the keyboard will

be dismissed. (The argument passed determines whether the first responder should be forced into

retirement. Some first responders might refuse to resign, and passing YES ignores that refusal.)

注意到endEditing:这个方法,当发出这个消息的时候,这个view的所有子view如果是一个first responder,都应该释放,

并且隐藏键盘,这个方法还有一个YES参数,是指需要强制释放。

Now the values of the BNRItem will be updated when the user taps the Back button on the

UINavigationBar. When BNRItemsViewController appears back on the screen, it is sent the message

viewWillAppear:. Take this opportunity to reload the UITableView so the user can immediately see

the changes. In BNRItemsViewController.m, override viewWillAppear:.

这样BNRItem的数据就被更新了,你还需要在BNRItemsViewController中重载viewWillAppear:.,以刷新tableview的视图。

- (void)viewWillAppear:(BOOL)animated

{

[super viewWillAppear:animated];

[self.tableView reloadData];

}

Build and run your application now. Now you can move back and forth between the view controllers

that you created and change the data with ease.

这样你就可以看到所想要得结果了。

UINavigationBar

The UINavigationBar is not very interesting right now. A UINavigationBar should display a

descriptive title for the UIViewController that is currently on top of the UINavigationController’s

stack.



现在让我们事先UINavigationBar,  每一个UIViewController都会有一个类型为UINavigationItem名为navigationItem的属性,它不是

UIView的子类,而只是一个数据模型,它为UINavigationBar的显示提供数据,比如可以提供title,当这个数值被设置的时候,

就可以显示UINavigationBar



你可以在UINavigationBar左边和右边添加一个UIBarButton,同样你需要为它提供一个UIBarButtonItem类型的数据,并设置它的

rightBarButtonItem和leftBarButtonItem属性。

UINavigationItem的titleView既可以是一个title也可以是任何的一个UIView;

有xib文件的情况下,你可以通过连线的方法建立连接,但如果是用代码的方法,那就需要手动传输target和action参数了。

In BNRItemsViewController.m, create a UIBarButtonItem instance and give it its target and action.

- (instancetype)init

{

self = [super initWithStyle:UITableViewStylePlain];

if (self) {

UINavigationItem *navItem = self.navigationItem;

navItem.title = @"Homepwner";

// Create a new bar button item that will send

// addNewItem: to BNRItemsViewController

UIBarButtonItem *bbi = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemAdd

target:self

action:@selector(addNewItem:)];

// Set this bar button item as the right item in the navigationItem

navItem.rightBarButtonItem = bbi;

}

return self;

}

The action is passed as a value of type SEL. Recall that the SEL data type is a pointer to a selector and

that a selector is the entire message name including any colons. Note that @selector() does not care

about the return type, argument types, or names of arguments.

action的类型是一个SEL(可以看作是函数指针),你可以使用@selector(name)来创建一个SEL对象。

Build and run the application. Tap the + button, and a new row will appear in the table. (Note that

this is not the only way to set up a bar button item; check the documentation for other initialization

messages that you can use to create an instance of UIBarButtonItem.)

这样你就可以使用点击+按钮来添加一个BNRItem了。

Now let’s add another UIBarButtonItem to replace the Edit button in the table view header. In

BNRItemsViewController.m, edit the init method.

- (instancetype)init

{

self = [super initWithStyle:UITableViewStylePlain];

if (self) {

UINavigationItem *navItem = self.navigationItem;

navItem.title = @"Homepwner";

// Create a new bar button item that will send

// addNewItem: to BNRItemsViewController

UIBarButtonItem *bbi = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemAdd

target:self

action:@selector(addNewItem:)];

// Set this bar button item as the right item in the navigationItem

navItem.rightBarButtonItem = bbi;

navItem.leftBarButtonItem = self.editButtonItem;

}

return self;

}

同样的方法,你在UINavigationBar上添加一个编辑按钮。

Surprisingly, that is all the code you need to get an edit button on the navigation bar. Build and run, tap

the Edit button, and watch the UITableView enter editing mode! Where does editButtonItem come

from? UIViewController has an editButtonItem property, and when sent editButtonItem, the view



Bronze Challenge: Displaying a Number Pad

The keyboard for the UITextField that displays a BNRItem’s valueInDollars is a QWERTY

keyboard. It would be better if it was a number pad. Change the Keyboard Type of that UITextField to

the Number Pad. (Hint: you can do this in the XIB file using the attributes inspector.)

Silver Challenge: Dismissing a Number Pad

After completing the bronze challenge, you may notice that there is no return key on the number pad.

Devise a way for the user to dismiss the number pad from the screen.

Gold Challenge: Pushing More View Controllers

Right now, instances of BNRItem cannot have their dateCreated property changed. Change BNRItem

so that they can, and then add a button underneath the dateLabel in BNRDetailViewController

with the title Change Date. When this button is tapped, push another view controller instance onto

the navigation stack. This view controller should have a UIDatePicker instance that modifies the

dateCreated property of the selected BNRItem.

加入多个view controller到navigation controller中,在第二级页面添加一个可以修改时间的页面。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: