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

iOS 从零开始使用 Swift系列:
探索 iOS SDK
探索 Foundation 框架
使用 UIKit 的第一步
自动布局基础
表格视图基础
导航控制器和视图控制器层次结构
iOS 上的数据持久性和沙盒
构建购物清单应用程序 1
构建购物清单应用程序 2
下一步该去哪里

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

介绍

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

就像任何其他 UIViewController 子类一样,导航控制器管理一个视图,一个UIView类的实例。导航控制器的视图管理多个子视图,包括顶部的导航栏、包含自定义内容的视图和底部的可选工具栏。导航控制器的特别之处在于它创建和管理视图控制器的层次结构,通常称为 导航堆栈

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

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

另一个项目

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

如果用户点击作者的姓名,则该作者所写的书籍列表会以动画形式出现在视图中。类似地,当用户从书籍列表中选择一个标题时,另一个视图动画进入视图,显示书籍封面的图像。让我们创建一个新的 Xcode 项目来开始。

创建项目

 打开 Xcode,通过从 File 菜单中 选择New > Project…创建一个新项目 ,然后从iOS > Application模板 列表中 选择Single View Application 模板。

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

视图应用程序 模板包含一个应用程序委托类 、 AppDelegate一个故事板Main.storyboard和一个 UIViewController 子类ViewController。打开 AppDelegate.swift 并查看 application(_:didFinishLaunchingWithOptions:). 它的实现很短,现在看起来应该很熟悉。

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

添加资源

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

将 Resources 文件夹拖到您的项目中以将它们添加到项目中。当您将文件夹添加到项目中时,Xcode 会显示一些选项。如果需要,请确保选中复选框 复制项目, 并且不要忘记将文件添加到  目标。

Property Lists

在继续之前,我想花点时间谈谈属性列表以及它们是什么。属性列表只不过是对象图的表示。正如我们在本系列前面所见,对象图是一组对象,通过它们彼此共享的连接或引用形成网络。

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

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

子类化 UITableViewController

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

上一篇文章中,我们创建了一个 UIViewController 子类,并在视图控制器的视图中添加了一个表格视图,以将数据呈现给用户。在本教程中,我们通过子类化来走捷径UITableViewController

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

命名新类AuthorsViewController并使其成为 UITableViewController. 无需选中复选框 Also create XIB file for user interface,因为我们将使用情节提要来创建应用程序的用户界面。

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

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

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

填充表视图

打开 AuthorsViewController.swift 并检查文件的内容。因为 AuthorsViewController 是 的子类 UITableViewController, AuthorsViewController已经符合 UITableViewDataSource 和 UITableViewDelegate 协议。

在我们可以在表格视图中显示数据之前,我们需要显示数据。正如我前面提到的,  Books.plist中包含 的数据作为表格视图的数据源。要使用这些数据,我们首先需要将其加载到一个对象中,准确地说是一个数组。

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

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

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

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

要获取 Books.plist的文件路径,我们首先需要通过调用类来引用应用程序的主 mainBundle() 包NSBundle。下一步是向应用程序的包询问其资源之一 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 数组中的作者数,所以我们需要做的就是计算数组的项目数。

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

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

作者数组包含一个有序的字典列表,每个字典包含两个键值对。名为 Author的键的对象 是 type 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语句的便捷方法。我们要求authors元素 atindexPath.row并将其向下转换为[String: AnyObject]. 因为我们需要作者的名字,所以我们要求 authorkey 的值"Author",将结果向下转换为String

添加导航控制器

使用故事板很容易添加导航控制器。但是,在我们添加导航控制器之前,了解导航控制器在 iOS 上的工作方式很重要。

就像任何其他 UIViewController 子类一样,导航控制器管理一个 UIView 实例。导航控制器的视图管理多个子视图,包括顶部的导航栏、包含自定义内容的视图和底部的可选工具栏。导航控制器的独特之处在于它管理着一堆视图控制器。

术语 堆栈 几乎可以从字面上理解。当一个导航控制器被初始化时,导航控制器被赋予一个根视图控制器。根视图控制器是导航堆栈底部的视图控制器 。

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

当视图控制器从导航堆栈中移除或 弹出 时,它下面的视图控制器的视图将再次可见。通过将视图控制器推入和弹出导航控制器的导航堆栈,创建视图层次结构,结果,可以将嵌套数据集呈现给用户。让我们看看所有这些推送和弹出在实践中是如何工作的。

重新访问项目的故事板(Main.storyboard)并选择视图控制器。要将导航控制器添加到组合中,请  从 编辑器 菜单中选择嵌入 > 导航控制器。有几点改变:

  • 导航控制器成为故事板的初始视图控制器
  •  添加了一个名为 Navigation Controller Scene的新场景
  • 导航栏添加到导航和作者视图控制器
  • 导航控制器和作者视图控制器通过 segue 连接

Segue 在故事板中很常见,我们将在本系列的后面部分了解更多关于它们的信息。segue 有很多种,连接导航控制器和作者视图控制器的 segue 是一种关系 segue

每个导航控制器都有一个根视图控制器,即位于导航堆栈底部的视图控制器。它不能从导航堆栈中弹出,因为导航控制器总是需要一个视图控制器来显示给用户。导航控制器和作者视图控制器之间的关系segue象征着后者是导航控制器的根视图控制器。

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

尽管导航控制器是故事板的初始视图控制器,但作者视图控制器是我们在启动应用程序时将看到的第一个视图控制器。正如我之前提到的,导航控制器只不过是一个帮助在视图控制器层次结构之间导航的包装器。它的视图由其导航堆栈中的视图控制器的视图填充。

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

// Set Title
title = "Authors"

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

推动和弹出

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

另一个表视图控制器

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

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

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

import UIKit
 
class BooksViewController: UIViewController {
 
    var author: [String: AnyObject]!
     
    ...
     
}

为什么我们不声明author[String: AnyObject]?or  [String: AnyObject]?因为变量需要有一个初始值,所以我们不能声明author为 [String: AnyObject]. 我们可以使用[String: AnyObject]?,但这意味着我们每次想要访问它的值时都必须解包可选。

在 Swift 中,如果您知道属性具有值,并且更重要的是,如果它需要具有值以使您的应用程序按预期工作,则通常会使用强制解包选项。如果该author属性没有值,那么书籍视图控制器对我们来说几乎没有用处,因为它无法显示任何数据。

为了更容易访问作者的书籍,我们还声明了一个计算属性。顾名思义,计算属性不存储值。它定义了一个 getter 和/或 setter 来获取和设置另一个属性的值。看看books下面的计算属性。

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

的值books取决于 的值author。我们检查 author 是否有 key的值并将该值向下转换为对象"Books"数组。AnyObject如果 author 没有值 for"Books"我们创建一个空数组 type  [AnyObject]。因为books计算属性只定义了一个getter,我们可以像这样简化实现:

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对象库中 的实例 ,并BooksViewController 在 Identity Inspector中将其类设置为 。

在新视图控制器中选择表格视图,并  在 Attributes Inspector中将Prototype Cells的数量设置 为 0。要将书籍视图控制器推送到导航控制器的导航堆栈上,我们需要创建另一个 segue。然而,这一次,我们创建了一个手动转场, 准确地说是一场表演赛。

在故事板中选择作者视图控制器,按住 Control 键,然后从作者视图控制器拖动到书籍视图控制器。从出现的菜单中选择Manual Segue > Show 以创建从 authors 视图控制器到 books 视图控制器的 segue。

在返回书籍视图控制器的实现之前,我们还需要做一件事。选择我们创建的 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:). 在这种方法中,视图控制器可以配置目标视图控制器,即书籍视图控制器。让我们实现 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语句中,我们获得了对书籍视图控制器(segue 的目标视图控制器)的引用,并将其author属性设置为表视图中当前选定的作者。

您可能想知道我们何时何地初始化书籍视图控制器?我们没有显式地实例化书籍视图控制器的实例。故事板知道它需要实例化什么类并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-not  – 子类UITableViewController并将其命名为 BookCoverViewController

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

import UIKit
 
class BookCoverViewController: UIViewController {
 
    @IBOutlet var bookCoverView: UIImageView!
     
    var book: [String: String]!
     
    ...
 
}

打开 Main.storyboard 以创建书本视图控制器的用户界面。将一个 UIViewController 实例从 Object LibraryBookCoverViewController拖到工作区,并 在 Identity Inspector中将其类设置为 。

将一个 UIImageView 实例从 Object Library 拖到 视图控制器的视图中,并使其覆盖视图控制器的整个视图。在Connections Inspector中,将其与 bookCoverView 视图控制器的出口连接。

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

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

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

import UIKit
 
class BooksViewController: UITableViewController {
 
    let CellIdentifier = "Cell Identifier"
    let SegueBookCoverViewController = "BookCoverViewController"
     
    ...
 
}

我们使用这个属性 tableView(_:didSelectRowAtIndexPath:) 来执行我们在故事板中创建的转场。不要忘记 在执行 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
        }
    }
}

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

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

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

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

它在哪里流行?

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

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

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

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

结论

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

ios-from-scratch-with-swift-navigation-controllers-and-view-controller-hierarchies–cms-25462