iOS从零开始,使用Swift:导航控制器和视图控制器层次结构

在iOS上,导航控制器是用于呈现多个屏幕内容的主要工具之一。 本文通过创建用于浏览图书馆书籍的应用程序,教您如何使用导航控制器。

介绍

在上一教程中 ,您了解到UIKit的表视图类非常适合呈现表格或列式数据。 但是,当内容需要分布在多个屏幕上时,导航控制器通常是首选工具。 UINavigationController类实现了这种功能。

就像任何其他UIViewController子类一样,导航控制器管理视图,即UIView类的实例。 导航控制器的视图管理多个子视图,包括顶部的导航栏,包含自定义内容的视图以及底部的可选工具栏。 使导航控制器与众不同的是,它创建和管理视图控制器的层次结构,通常称为导航堆栈

在本文中,我们将创建一个新的iOS应用程序以熟悉UINavigationController类。 您将了解到,导航控制器和(表)视图控制器堆栈的组合是一种用于展示嵌套数据集的优雅而强大的解决方案。

除了UINavigationController ,您还将在本教程中遇到另一个UIViewController子类UITableViewControllerUITableViewController管理UITableView实例而不是UIView实例。 表格视图控制器会自动采用UITableViewDataSourceUITableViewDelegate协议,这将为我们节省一些时间。

另一个项目

我们将要创建的应用程序名为Library 。 使用此应用程序,用户可以浏览作者列表并查看他们写的书。 作者列表显示在表格视图中。

如果用户点击作者的姓名,则该作者撰写的书籍清单会带有动画效果。 类似地,当用户从书籍列表中选择标题时,另一个视图会变成动画,显示书籍封面的图像。 让我们创建一个新的Xcode项目以使我们开始。

创建项目

打开Xcode,通过从“ 文件”菜单中选择“ 新建”>“项目...”来创建一个新项目,然后从“ iOS”>“应用程序模板”列表中选择“ Single View Application”模板。

选择项目模板

为项目命名,并分配组织名称和标识符。 将语言设置为Swift ,将设备设置iPhone 。 告诉Xcode您要将项目保存到哪里,然后单击Create

配置项目

Single View Application模板包含一个应用程序委托类AppDelegate ,一个Storyboard Main.storyboard以及一个UIViewController子类ViewController 。 打开AppDelegate.swift并查看application(_:didFinishLaunchingWithOptions:) 。 它的实现很简短,现在应该看起来很熟悉。

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    return true
}

添加资源

本教程源文件包括我们将要使用的数据。 您可以在名为Resources的文件夹中找到它们。 它包括一个属性列表Books.plist ,其中包含有关作者,他们所写的书的信息,有关每本书的一些信息以及该属性列表中包含的每本书的图像。

Resources文件夹拖到您的项目中,以将它们添加到项目中。 将文件夹添加到项目时,Xcode将为您显示一些选项。 确保根据需要选中“ 复制项目 ”复选框并且不要忘记将文件添加到“ 库”目标中。

向项目添加资源
将项目复制到组的目标文件夹中

物业清单

在继续之前,我想花一点时间来讨论财产清单及其含义。 属性列表仅是对象图的表示。 如本系列前面所述,对象图是一组对象,它们通过彼此共享的连接或引用形成网络。

从磁盘读写属性列表很容易,这使其非常适合存储少量数据。 使用属性列表时,请务必记住,只有某些类型的数据可以存储在属性列表中,例如字符串,数字,日期,数组,字典和二进制数据。

Xcode使浏览属性列表非常容易。 从添加到项目的Resources文件夹中选择Books.plist ,然后使用Xcode的内置属性列表编辑器浏览其内容。 当我们开始使用Books.plist的内容时,这将是本文稍后的有用工具。

浏览Xcode中的属性列表

子类化UITableViewController

在开始使用存储在Books.plist中的数据之前 ,我们首先需要打基础。 这包括创建一个用于管理表视图的视图控制器,并将显示属性列表中列出的作者。

上一篇文章中 ,我们创建了UIViewController子类,并将表视图添加到视图控制器的视图中以向用户呈现数据。 在本教程中,我们通过子类化UITableViewController来获得快捷方式。

首先从项目中删除ViewController.swift 。 通过从“ 文件”菜单中选择“ 新建”>“文件...”来创建一个新类。 在iOS>源模板的列表中选择Cocoa Touch Class模板。

创建一个新的可可接触类

将新类AuthorsViewController并将其AuthorsViewController UITableViewController 。 无需选中“ 同时为用户界面创建XIB文件 ”复选框,因为我们将使用情节提要来创建应用程序的用户界面。

配置新的可可接触类

打开Main.storyboard ,将情节提要中的视图控制器替换为表格视图控制器。 在情节提要中选择视图控制器,按Delete键,然后拖动UITableViewController   右侧对象库中的实例。 选择新的视图控制器,打开右侧的Identity Inspector ,并将其类设置为AuthorsViewController

添加表视图控制器

在上一篇文章中,我们利用原型单元填充了表格视图。 在本教程中,我将向您展示另一种方法。 在工作空间中或从左侧的对象列表中选择表视图对象,在右侧打开Attributes Inspector ,然后将Prototype Cells设置为0

删除原型单元

每个情节提要板都需要一个初始视图控制器。 这是加载情节提要时实例化的视图控制器。 我们可以通过选择左侧的Authors View Controller对象,打开右侧的Attributes Inspector并选中复选框Is Initial View Controller来将authors视图控制器标记为初始视图控制器

填充表格视图

打开AuthorsViewController.swift并检查文件的内容。 由于AuthorsViewControllerUITableViewController的子类,因此AuthorsViewController已经符合UITableViewDataSourceUITableViewDelegate协议。

在表格视图中显示数据之前,我们需要显示数据。 如前所述, Books.plist中包含的数据用作表视图的数据源。 要使用此数据,我们首先需要将其加载到一个对象(精确地说是一个数组)中。

我们声明一个变量属性authors并将其初始值设置为[AnyObject]类型的空数组。 请记住, AnyObject类型可以表示任何类或结构。

视图控制器的viewDidLoad()方法是将Books.plist中的数据加载到视图控制器的authors属性中的好地方。 我们通过调用NSArray类的init(contentsOfFile:)初始化程序来实现。 我们将结果对象转换为[AnyObject]类型的实例。

authors = NSArray(contentsOfFile: path) as! [AnyObject]

该方法接受一个文件路径,这意味着我们需要弄清楚Books.plist的文件路径是什么Books.plist文件位于应用程序捆绑包中,这是包含该应用程序可执行文件和应用程序资源(例如图像和声音)的目录的惯用词。

要获取Books.plist的文件路径,我们首先需要通过在NSBundle类上调用mainBundle()来引用应用程序的主捆绑包。 下一步是向应用程序的捆绑包询问其资源之一Books.plist的路径。 我们在应用程序的主捆绑包上调用pathForResource(_:ofType:) ,传入我们感兴趣的文件的名称和类型(扩展名)。我们将文件路径存储在常量filePath

let filePath = NSBundle.mainBundle().pathForResource("Books", ofType: "plist")

因为我们可能需要的是应用程序包中不存在的资源,所以pathForResource(_:ofType:)返回一个可选的。 通常,如果方法可以返回nil ,则应使用可选方法。 filePath常量的类型为String? 。 为了安全地打开可选包,我们使用了可选绑定,我们在本系列的前面已经对此进行了讨论。

如果将这两部分放在一起,我们将得到以下viewDidLoad() 。 我还添加了一条打印语句,以将authors属性的内容打印到控制台。 这让我们看一下它的内容。

override func viewDidLoad() {
    super.viewDidLoad()
    
    let filePath = NSBundle.mainBundle().pathForResource("Books", ofType: "plist")
    
    if let path = filePath {
        authors = NSArray(contentsOfFile: path) as! [AnyObject]
        print(authors)
    }
}

如果您已经阅读了本系列的上一篇文章,那么填充表视图应该很简单。 由于表视图仅包含一个部分,因此numberOfSectionsInTableView(_:)非常简单。 AuthorsViewController继承自UITableViewController ,后者已经符合并实现了UITableViewDataSource协议。 这就是为什么我们需要使用override关键字的原因。 我们将覆盖由父类实现的方法。

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}

表格视图的唯一部分中的行数等于authors数组中的authors数,因此我们要做的就是计算数组的项目。

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return authors.count
}

tableView(_:cellForRowAtIndexPath:)的实现类似于我们在上一篇文章中看到的实现。 主要区别在于我们如何获取在表格视图单元格中显示的数据。

作者数组包含字典的有序列表,每个字典包含两个键值对。 名为Author的键的对象的类型为String ,而Books的键的对象是一个字典数组,每个字典代表作者编写的书。 如果不清楚,请在Xcode中打开Books.plist来检查数据源的结构。

在实现tableView(_:cellForRowAtIndexPath:) ,我们需要注意两点。 首先,我们为将要使用的单元重用标识符声明一个常量。

import UIKit

class AuthorsViewController: UITableViewController {

    let CellIdentifier = "Cell Identifier"
    
    var authors = [AnyObject]()
    
    ...

}

其次,我们在表视图上调用registerClass(_:forCellReuseIdentifier:) ,传入UITableViewCell.classForCoder()和单元重用标识符。 我们在viewDidLoad()调用此方法以确保仅调用一次。

override func viewDidLoad() {
    super.viewDidLoad()
    
    let filePath = NSBundle.mainBundle().pathForResource("Books", ofType: "plist")
    
    if let path = filePath {
        authors = NSArray(contentsOfFile: path) as! [AnyObject]
        print(authors)
    }
    
    tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier)
}

完成上述操作后, tableView(_:cellForRowAtIndexPath:)变得非常简短。

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    // Dequeue Resuable Cell
    let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath)
    
    if let author = authors[indexPath.row] as? [String: AnyObject], let name = author["Author"] as? String {
        // Configure Cell
        cell.textLabel?.text = name
    }
    
    return cell;
}

请注意,我们用逗号链接可选绑定。 这是避免嵌套if语句的便捷方法。 我们在indexPath.row要求authors提供该元素,并将其下标为[String: AnyObject] 。 因为我们需要作者的姓名,所以我们要求author提供键"Author"的值,并将结果向下转换为String

添加导航控制器

使用情节提要板可以轻松添加导航控制器。 但是,在添加导航控制器之前,了解导航控制器在iOS上的工作方式非常重要。

就像任何其他UIViewController子类一样,导航控制器管理UIView实例。 导航控制器的视图管理多个子视图,包括顶部的导航栏,包含自定义内容的视图以及底部的可选工具栏。 使导航控制器与众不同的原因是它管理视图控制器的堆栈。

导航堆栈

术语堆栈几乎可以从字面上理解。 初始化导航控制器后,会为导航控制器提供一个根视图控制器。 根视图控制器是导航堆栈底部的视图控制器。

通过另一个视图控制器推到导航堆栈上 ,根视图控制器的视图将替换为新视图控制器的视图。 使用导航控制器时,可见视图始终是导航堆栈最顶层视图控制器的视图。

当视图控制器从导航堆栈中删除或弹出时 ,位于其下方的视图控制器的视图再次变为可见。 通过将视图控制器推入和弹出导航控制器的导航堆栈,可以创建视图层次结构,从而可以将嵌套数据集呈现给用户。 让我们看看所有这些推动和弹出在实际中是如何工作的。

再次访问项目的故事板( Main.storyboard ),然后选择视图控制器。 要将导航控制器添加到混音中,请从“ 编辑器”菜单中选择“ 嵌入”>“导航控制器 ”。 一些变化:

  • 导航控制器成为情节提要的初始视图控制器
  • 添加了一个名为Navigation Controller Scene的新场景
  • 将导航栏添加到导航和作者视图控制器中
  • 导航控制器和作者视图控制器通过segue连接
添加导航控制器

Segues在情节提要中很常见,我们将在本系列的后面部分中详细了解它们。 有多种顺序,连接导航控制器和作者视图控制器的顺序是关系顺序

每个导航控制器都有一个根视图控制器,该视图控制器位于导航堆栈的底部。 无法将其从导航堆栈中弹出,因为导航控制器始终需要视图控制器才能显示给用户。 导航控制器和作者视图控制器之间的关系顺序象征着后者是导航控制器的根视图控制器。

使用导航控制器时,可以免费获得导航控制器和作者视图控制器顶部的导航栏。 它是UINavigationBar的实例,有助于导航导航堆栈。

即使导航控制器是情节提要的初始视图控制器,作者的视图控制器还是启动应用程序时看到的第一个视图控制器。 正如我之前提到的,导航控制器不过是一个包装器,可以帮助在视图控制器的层次结构之间进行导航。 其视图由其导航堆栈中的视图控制器的视图填充。

要将标题添加到导航栏,请将以下行添加到AuthorsViewController类的viewDidLoad()方法。

// Set Title
title = "Authors"

每个视图控制器都有一个title属性,该属性在不同的地方使用。 导航栏就是其中之一。 运行应用程序以查看此微小更改的结果。

推和弹出

现在,让我们添加一个功能,当用户点击作者的姓名时,便可以查看书籍列表。 这意味着我们需要捕获选择(作者的姓名),然后基于该选择实例化一个新的视图控制器,然后将新的视图控制器推入导航堆栈。 这听起来复杂吗? 不是。 让我给你演示。

另一个表视图控制器

为什么不在另一个表格视图中显示书籍列表。 创建UITableViewController的新子类,并将其命名为BooksViewController

创建BooksViewController类

如我们先前所见,加载书籍列表很容易,但是书籍视图控制器如何知道用户点击了哪个作者? 有几种方法可以告知新的视图控制器用户的选择,但是Apple推荐的方法称为“ 通过引用传递” 。 这是如何运作的?

图书视图控制器声明了author属性,我们可以设置该属性来配置图书视图控制器。 书籍视图控制器使用author属性显示所选作者的书籍。 打开BooksViewController.swift并添加[String: AnyObject]!类型的变量属性[String: AnyObject]! 并命名为author

import UIKit

class BooksViewController: UIViewController {

    var author: [String: AnyObject]!
    
    ...
    
}

为什么我们不将author声明为[String: AnyObject]? 还是[String: AnyObject] ? 因为变量需要具有初始值,所以我们不能将author声明为[String: AnyObject] 。 我们可以使用[String: AnyObject]? ,但这意味着每次要访问其可选值时,我们都必须解开该可选项。

在Swift中,如果知道属性具有值,更重要的是,如果它需要一个值才能使应用程序按预期工作,则通常会使用强制展开的可选对象。 如果author属性没有值,则books view控制器对我们几乎没有用,因为它无法显示任何数据。

为了更轻松地访问作者的书,我们还声明了一个计算属性。 顾名思义,计算属性不存储值。 它定义了一个用于获取和设置另一个属性值的获取器和/或设置器。 看看下面的books计算属性。

var books: [AnyObject] {
    get {
        if let books = author["Books"] as? [AnyObject] {
            return books
        } else {
            return [AnyObject]()
        }
    }
}

books取决于价值author 。 我们检查作者是否具有键"Books"的值,并将该值向下转换为AnyObject对象的数组。 如果作者没有"Books"的值,我们将创建一个[AnyObject]类型的空数组。 因为books计算属性仅定义了一个吸气剂,所以我们可以像这样简化实现:

var books: [AnyObject] {
    if let books = author["Books"] as? [AnyObject] {
        return books
    } else {
        return [AnyObject]()
    }
}

BooksViewController类的其余部分很简单。 看一下下面显示的三个UITableViewDataSource协议方法的实现。

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return books.count
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    // Dequeue Resuable Cell
    let cell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier, forIndexPath: indexPath)
    
    if let book = books[indexPath.row] as? [String: String], let title = book["Title"] {
        // Configure Cell
        cell.textLabel?.text = title
    }
    
    return cell;
}

这也意味着我们需要为单元重用标识符声明一个常量属性,并在viewDidLoad()注册一个类以供单元重用。 这不是什么新鲜事。

import UIKit

class BooksViewController: UITableViewController {

    let CellIdentifier = "Cell Identifier"
    
    ...
    
}
override func viewDidLoad() {
    super.viewDidLoad()
    
    tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier)
}

推送视图控制器

当用户在作者视图控制器中点击作者的姓名时,应用程序应显示该作者撰写的书籍列表。 这意味着我们需要实例化BooksViewController类的实例,告诉该实例用户选择了哪个作者,然后将新的视图控制器推入导航堆栈。

故事板将帮助我们解决此问题。 打开Main.storyboard ,拖动另一个 对象库中的 UITableViewController实例,并在Identity Inspector中将其类设置为BooksViewController

将Books View Controller添加到情节提要

在新视图控制器中选择表格视图,然后在“ 属性”检查器中将“ 原型单元”的数量设置为0 。 要将书本视图控制器推到导航控制器的导航堆栈上,我们需要创建另一个segue。 但这一次,我们创建了一个手动搜索 ,精确地显示节目

在情节提要中选择作者视图控制器,按住Control键,然后从作者视图控制器拖到书本视图控制器。 从出现的菜单中选择“ 手动Segue”>“显示 ”以创建从作者视图控制器到书籍视图控制器的序列。

创建手册显示区

回到books view controller的实现之前,我们还需要做另外一件事。 选择我们创建的segue,打开右侧的Attributes Inspector ,然后将segue的Identifier设置为BooksViewController 。 通过给segue命名,我们可以在以后的代码中引用它。

设置Segue标识符

要使用此序列,我们需要在作者视图控制器中实现tableView(_:didSelectRowAtIndexPath:) 。 正如我们在上一篇关于表视图的文章中看到的那样,该方法在UITableViewDelegate协议中定义。 在此方法中,我们调用performSegueWithIdentifier(_:sender:)来执行我们在情节提要中创建的segue。 performSegueWithIdentifier(_:sender:)方法采用两个参数,即segue的标识符和消息的发送者。 现在应该清楚为什么我们要在故事板上为segue赋予一个标识符? 还要注意,我们执行segue 之后重置选择。

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    // Perform Segue
    performSegueWithIdentifier(SegueBooksViewController, sender: self)
    
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
}

SegueBooksViewController常量是AuthorsViewController类的另一个常量属性。

import UIKit

class AuthorsViewController: UITableViewController {

    let CellIdentifier = "Cell Identifier"
    let SegueBooksViewController = "BooksViewController"
    
    ...

}

在执行segue之前,视图控制器有机会在prepareForSegue(_:sender:)为segue做准备。 在这种方法中,视图控制器可以配置目标视图控制器,书本视图控制器。 让我们实现prepareForSegue(_:sender:)看看它是如何工作的。

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == SegueBooksViewController {
        if let indexPath = tableView.indexPathForSelectedRow, let author = authors[indexPath.row] as? [String: AnyObject]  {
            let destinationViewController = segue.destinationViewController as! BooksViewController
            destinationViewController.author = author
        }
    }
}

每当执行segue时,都会调用此方法。 我们首先检查segue的标识符是否等于SegueBooksViewController 。 然后,我们使用可选绑定向表视图询问当前选择的索引路径。 如果选择了一行,我们会要求authors提供与此选择相对应的作者。

if语句中,我们获得对books视图控制器(segue的目标视图控制器)的引用,并将其author属性设置为表视图中当前选定的作者。

您可能想知道我们何时或在何处初始化books视图控制器? 我们没有显式实例化books视图控制器的实例。 故事板知道要实例化哪个类,并为我们初始化BooksViewController的实例。

在运行应用程序之前,请打开BooksViewController.swift并将视图控制器的标题设置为作者的名称,以更新导航栏的标题。

override func viewDidLoad() {
    super.viewDidLoad()
    
    if let name = author["Author"] as? String {
        title = name
    }
    
    tableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: CellIdentifier)
}

运行应用程序。 在表格视图中点击作者的姓名,观察新的BooksViewController实例如何推入导航堆栈并显示给用户。 您是否注意到使用导航控制器时,我们还会免费提供一个后退按钮。 前一个视图控制器的标题用作后退按钮的标题。

添加书套

当用户在“书籍”视图控制器中点击一本书时,应用程序应显示该书籍的封面。 我们不会为此使用表视图控制器。 相反,我们使用普通的UIViewController子类,并在UIImageView类的实例中显示书的封面。 UIImageView是专门用于显示图像的UIView子类。

创建UIViewController的新子类(而不是UITableViewController ,并将其命名为BookCoverViewController

创建BookCoverViewController类

我们需要在新的视图控制器中声明两个存储的属性。 第一个存储的属性是对将用来显示书的封面的图像视图的引用。 @IBOutlet关键字指示我们将在情节@IBOutlet建立连接。 第二个存储的属性Book[String: String]!类型[String: String]! 。 此属性表示在书的封面视图控制器中显示的书。

import UIKit

class BookCoverViewController: UIViewController {

    @IBOutlet var bookCoverView: UIImageView!
    
    var book: [String: String]!
    
    ...

}

打开Main.storyboard创建书本视图控制器的用户界面。 将UIViewController实例从“ 对象库BookCoverViewController工作区, BookCoverViewControllerIdentity Inspector中将其类设置为BookCoverViewController

将视图控制器添加到工作区

UIImageView实例从“ 对象库”拖动到视图控制器的视图,并使其覆盖视图控制器的整个视图。 在Connections Inspector中 ,将其与视图控制器的bookCoverView插座连接。

添加图像视图

为确保图像视图正确显示在每个设备上,我们需要应用必要的布局约束,如下所示。

向图像视图添加布局约束

在实现视图控制器之前,请在书籍视图控制器和书籍封面视图控制器之间创建一个推式序列 。 选择序列并将其标识符设置为BookCoverViewController 属性检查器

创建书套视图控制器的Segue

在里面 BooksViewController类,为segue标识符声明一个常量属性。

import UIKit

class BooksViewController: UITableViewController {

    let CellIdentifier = "Cell Identifier"
    let SegueBookCoverViewController = "BookCoverViewController"
    
    ...

}

我们在tableView(_:didSelectRowAtIndexPath:)使用此属性来执行在情节提要中创建的segue。 执行完Segue 之后,不要忘记取消选择该行。

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    // Perform Segue
    performSegueWithIdentifier(SegueBookCoverViewController, sender: self)
    
    tableView.deselectRowAtIndexPath(indexPath, animated: true)
}

prepareForSegue(_:sender:)看起来与BooksViewController类的实现非常相似。 我们检查segue的标识符是否等于SegueBookCoverViewController然后向表视图询问当前选定行的索引路径。 我们要求books的书,与用户的选择相对应,并设置book目的地视图控制器的一个实例的属性BookCoverViewController

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == SegueBookCoverViewController {
        if let indexPath = tableView.indexPathForSelectedRow, let book = books[indexPath.row] as? [String: String]  {
            let destinationViewController = segue.destinationViewController as! BookCoverViewController
            destinationViewController.book = book
        }
    }
}

我们在其viewDidLoad()方法中配置BookCoverViewController类的图像视图。 我们向book请求键"Cover"的值,并通过调用init(named:)初始化程序并传入文件名来实例化UIImage对象。 我们将UIImage对象分配给bookCoverViewimage属性。

override func viewDidLoad() {
    super.viewDidLoad()
    
    if let fileName = book["Cover"] {
        bookCoverView.image = UIImage(named: fileName)
        bookCoverView.contentMode = .ScaleAspectFit
    }
}

viewDidLoad() ,我们还将图像视图的内容模式设置为ScaleAspectFitcontentMode属性的类型为UIViewContentMode (枚举)。 我们分配的值ScaleAspectFit告诉图像视图在尊重宽高比的同时尽可能地拉伸图像。

运行该应用程序,然后试一下。 现在,您应该能够浏览存储在Books.plist中的书籍。

它在哪里弹出?

在本文的前面,我解释了可以将视图控制器推入导航堆栈并从中弹出。 到目前为止,我们仅将视图控制器推送到导航堆栈上。 当用户点击导航栏的后退按钮时,就会从导航堆栈中弹出视图控制器。 这是我们免费获得的另一功能。

但是,在某些时候,您将遇到手动需要从导航堆栈弹出视图控制器的情况。 您可以通过在导航视图控制器上调用popViewControllerAnimated(_:)来实现。 这将从导航堆栈中删除最顶层的视图控制器。

或者,您可以通过在导航控制器上调用popToRootViewControllerAnimated(_:)从导航堆栈中弹出所有视图控制器(根视图控制器除外)。

您如何访问视图控制器的导航控制器? UIViewController类声明了UINavigationController?类型的计算属性navigationController UINavigationController? 。 如果视图控制器在导航堆栈上,则此属性引用导航堆栈所属的导航控制器。

结论

我希望您同意导航控制器没有那么复杂。 这篇文章本来可以短很多,但是我希望您在此过程中学到了更多东西。 在下一篇文章中,我们看一下标签栏控制器。 即使标签栏控制器也管理视图控制器的集合,它们与导航控制器还是有很大不同的。

如果您有任何问题或意见,可以将其留在下面的评论中,或通过Twitter与我联系。

翻译自: https://code.tutsplus.com/tutorials/ios-from-scratch-with-swift-navigation-controllers-and-view-controller-hierarchies--cms-25462

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值