UISplitViewController简单入门

在邮件这个App里, 它在iPad里划分了两个区域, 左边是一个邮件列表, 右边则是对应的邮件详细内容. Apple为咱们建立了一个很是方便的ViewController, 它的名字叫作UISplitViewController. 在这个教程中, 咱们将学习如何去使用它, 还有一个事情就是, 在iOS 8开始, UISplitViewController就能够在iPadiPhone上运行.git

在本教程中, 咱们将会从头开始建立一个通用的App, 它使用UISplitViewController来显示水果的列表, 咱们将使用UISplitViewController来处理iPadiPhone 11Navigation和显示的问题.程序员

在此以前, 你应该掌握iOS开发的一些基础知识, 好比AutoLayoutStoryboard的使用等等.github

图片

在开始以前, 咱们须要下载本教程的一些课件, 这里的课件共有两个, 一个是已经完成了的, 一个是准备让你去完成的.swift

注意: 这里使用的是Xcode 11, iOS 13Swift 5, 如需转载, 请联系做者, 侵权必究.数组

开始

点击File ▸ New ▸ Project, 在Xcode中建立一个新的项目, 选择iOS ▸ Application ▸ Single View App模板ide

图片

将项目命名为Fruit, 将开发语言设置为Swift, 而后将用户界面设置为Storyboard, 若是底下的勾选框勾选了的话, 则要取消掉.布局

图片

虽然咱们能够选择使用Master-Detail App这个模板, 可是为了更好的了解UISplitViewController的工做原理, 因此咱们将使用Single View App模板. 这对咱们在未来的项目使用UISplitViewController时会更有帮助.学习

如今咱们来建立App的主体UI, 打开Main.storyboard, 这里咱们须要把系统自带的ViewController删除, 同时也要将项目中的ViewController.swift文件也删掉.优化

而后在Main.storyboard中找到Split View Controller, 而后拖出来:ui

图片

图片

这里会让Storyboard添加几个元素:

  • Split View Controller: 第一个确定是UISplitViewController, 它将是这个App的根控制器.

  • Navigation Controller: 其次是UINavigationController, 它将是主控制器的根视图, 在iPad或者是比较大尺寸的iPhone横屏时, 它将会显示在左侧.

    仔细点查看, 在UISplitViewController中, 这个具备UINavigationController的控制器, 是它的Master View Controller, 这将容许咱们在主视图控制器中建立整个Navigation的层次结果, 而后又不影响到Detail View Controller.

  • View Controller: 这里将会显示全部水果的详细信息, 若是你仔细点查看UISplitViewController, 你会发现ViewController是它的Detail View Controller.

图片

  • Table View Controller: 这是UINavigationController的根视图, 它将会显示水果列表.

注意: 这个时候因为咱们没有Cell的重用标识, 因此Xcode会有个警告, 这个前往别忘记了.

还有一点要注意的是, 因为咱们把自带的ViewController给删除了, 因此咱们须要告诉Storyboard, 咱们但愿将UISplitViewController设置为初始化ViewController.

这个时候咱们选择UISplitViewController, 而后在右侧的"属性"栏, 勾选Is Initial View Controller:

图片

勾选了以后, 咱们就会在UISplitViewController的左侧看到一个箭头, 这就是这个Storyboard的初始化控制器.

这个时候, 咱们选择iPad模拟器, 而后将模拟器横向后, 你就会看到下面这个空白的UISplitViewController了:

图片

以前也说了, 自从iOS 8以后, 咱们也能够运行在iPhone上, 只要它的尺寸够大就能够了, 这里咱们选择iPhone 8 Plus模拟器, 而后就会看到效果:

图片

在横向的大尺寸iPhone会和iPad显示的效果同样以外, UISplitViewController将会和常规的操做同样, 会有UINavigationControllerPushPop, 这都是系统帮咱们实现的, 不须要咱们而外再去操做,

建立自定义ViewController

如今咱们已经有了Storyboard主要的控制器结构, 如今咱们须要的是在代码上添加数据源, 而后将数据显示出来.

如今咱们建立一个名为MasterViewControllerUITableViewController子类, 在建立的过程里, 咱们须要把Also create XIB file给去掉, 由于在Storyboard里已经有了, 而后开发语言为Swift, 而后就一直下一步, 到最后完成建立便可.

建立完成了以后, 咱们打开MasterViewController.swift, 删除一些不须要的代码, 而后添加一些咱们须要的代码:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 10
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "FruitCell", for: indexPath)
    return cell
}
复制代码

在运行以前, 咱们须要在Storyboard中设置一些UITableViewControllerUITableViewCell相关的东西.

  • 首先设置RootViewControllerCustom Class设置为MasterViewController:

图片

  • 其次设置UITableViewCellIdentifyFruitCell, 就和我上们面的代码一致, 而后再设置StyleBasic:

图片

如今咱们能够运行一下, 这个时候你会发现总共有十行, 每行的标题都是同样的, 并且每当咱们点击任何的一行都不会发生任何事情.

图片

这是由于咱们尚未建立DetailViewController, 如今咱们来建立对应的DetailViewController, 和上面同样, 这个过程就忽略掉, 只是名称为DetailViewController.

建立完成以后, 咱们按照一样的方式, 在Main.storyboard中, 给DetailViewController设置Custom ClassDetailViewController.

为了让咱们的DetailViewController可以有东西显示, 这里加一个UILabel而后, 写上全世界程序员第一句学的代码Hello Wirkd!, 而后在iPad模拟器上运行:

图片

如今每当咱们点击一下UITableViewCell, 在DetailViewController中就会显示一个Hello World!.

制做数据模型

基础知识讲完了, 接下来是来制做咱们须要显示的数据模型, 因为如今咱们是在演示, 因此这里的数据模型不会很复杂, 更加不会使用到数据持久化.

首先咱们须要建立一个名为Fruit类, 这里使用的模板是:

图片

咱们建立的这个类, 其中包含了水果的图片, 名称, 介绍等等:

import UIKit

struct Fruit {
    let name: String
    let description: String
    let iconName: String
    
    init(name: String, description: String, iconName: String) {
        self.name = name
        self.description = description
        self.iconName = iconName
    }
    
    var icon: UIImage? {
        return UIImage(named: iconName)
    }
}
复制代码

如今回到咱们的MasterViewController.

显示水果列表

打开MasterViewController.swift以后, 咱们须要新增一个let的属性:

let fruits = [
    Fruit(name: "Apple", description: "这是苹果", iconName: "Apple"),
    Fruit(name: "Banana", description: "这是香蕉", iconName: "Banana"),
    Fruit(name: "Blackberry", description: "这是黑莓", iconName: "Blackberry"),
    Fruit(name: "Cherries", description: "这是樱桃", iconName: "Cherries"),
    Fruit(name: "Coconut", description: "这是椰子", iconName: "Coconut"),
    Fruit(name: "Grapes", description: "这是葡萄", iconName: "Grapes")
]
复制代码

这是咱们用来显示的具体数据数组.

而后咱们找到tableView(_:numberOfRowsInSection:)并将return语句替换为如下内容:

return fruits.count
复制代码

接下来, 咱们须要将名称显示到UITableViewCell中, 找到tableView(_:cellForRowAtIndexPath:), 而且在return的语句以前添加下面的代码:

let fruti = fruits[indexPath.row]
cell.textLabel?.text = fruti.name
复制代码

这将会把水果的名称显示到UITableViewCell中, 如今咱们运行一下项目:

图片

如今咱们成功的将水果名称显示在UITableViewCell中了.

更改MasterViewController的标题

为了让MasterViewController的标题看起来更加的贴合, 咱们能够修改一下它的标题, 修改标题有两种方式, 第一种是直接在Storyboard中修改:

图片

第二种是在代码上修改:

override func viewDidLoad() {
    super.viewDidLoad()

    title = "Fruit List"
}
复制代码

不管哪一种均可以, 但若是你所在的公司有固定的代码规范, 那就按照公司的规范来.

显示水果的详细内容

如今在TableView中咱们已经显示了水果的名称, 如今咱们是时候来完善每当点击Cell的时候, DetailViewController则会显示对应的内容了.

打开Main.storyboard, 将原来咱们添加到DetailViewController里面的内容删掉, 而后再添加咱们所须要展现的内容:

图片

下面是咱们须要添加的内容:

  • 最左边是用来显示水果样子的UIImageView, 它的尺寸是128x128
  • 右边最上面的是用来显示水果名称的UILabel, 它用的是系统粗体, 字号为28
  • 右边最下面的是用来显示水果详情的UILabel, 它用的是系统常规, 字号为17
  • 这里使用了两个UIStackView, 第一个是用在两个UILabel里, 排序方式是竖向的, 而且设置它们之间的间距为10, 第二个则是用在UIImageView和第一个UIStackView, 它的排序方式是横向的, 而且设置它们之间的间距为15.

这里使用UIStackView能够帮助咱们省下不少使用AutoLayout的布局问题, 如今布局完成了, 咱们来将UIKit控件和DetailViewController关联.

打开DetailViewController.swift, 而后将下面的代码添加进去:

@IBOutlet weak var imageView: UIImageView!

@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var descriptionLabel: UILabel!

var furti: Fruit? {
    didSet {
        refreshUI()
    }
}

private func refreshUI() {
  loadViewIfNeeded()
  nameLabel.text = furti?.name
  descriptionLabel.text = furti?.description
  imageView.image = furti?.icon
}
复制代码

接下来, 咱们则须要在Storyboard中, 去关联这里的属性:

图片

如今, 咱们准备就绪了, 只差将数据显示就能够了, 打开SceneDelegate.swift, 而后将下面的代码替换scene(_:willConnectTo:options :)的内部实现:

guard
  let splitViewController = window?.rootViewController as? UISplitViewController,
  let leftNavController = splitViewController.viewControllers.first as? UINavigationController,
  let masterViewController = leftNavController.viewControllers.first as? MasterViewController,
  let detailViewController = (splitViewController.viewControllers.last as? UINavigationController)?.topViewController as? DetailViewController
  else { fatalError() }

let firstMonster = masterViewController.fruits.first
detailViewController.furti = firstMonster
复制代码

UISplitViewController中, 它有一个名为ViewControllers的属性, 其中是包含了MasterViewControllerDetailViewController, 在咱们这个状况下, MasterViewController实际上就是NavigationController, 因此咱们若是要获取真正的MasterViewController实例, 就须要得到NavigationController中的一个ViewController.

要获取DetailViewController也是使用一样的方式, 只不过是获取UISplitViewController中的ViewControllers最后一个ViewController.

如今咱们运行项目, 就能够看到有关于水果的详情信息:

图片

但如今咱们又面临了一个问题, 不管咱们点击哪一个UITableViewCell, 都只会显示苹果的信息, 接下来咱们就须要解决这个问题.

使用delegate完善DetailViewController的显示内容

关于两个控制器之间的通讯方式有不少种, 在Master-Detail App的模板中, MasterViewController有着对DetailViewController的引用, 这就意味着MasterViewController能够在DetailViewController上设置属性了.

这若是只是在一个简单的ViewController中则能够直接使用, 但在咱们这种状况, 仍是遵循UISplitViewController类引用中的建议方法来处理, 那就是添加delegate.

打开MasterViewController.swift, 而且在这个类的上面添加下面的代码:

protocol FruitSelectionDelegate: class {
    func furitSelected(_ newFurit: Fruit)
}
复制代码

这定义了一个带有名为furitSelected方法的协议, 咱们将会在DetailViewController中实现这个方法, 而且在MasterViewController中将用户选择的水果发送过去.

接下来, 咱们须要定义一个delegate属性:

weak var delegate: FruitSelectionDelegate?
复制代码

这就意味着delegate属性须要一个实现了furitSelected(_:)方法的对象, 因为咱们但愿DetailViewController在用户点了不一样的水果时就更新内容, 因此咱们须要在DetailViewController中实现这个代理方法:

extension DetailViewController: FruitSelectionDelegate {
    func furitSelected(_ newFurit: Fruit) {
        self.furti = newFurit
    }
}
复制代码

这里咱们使用extension可让代码看起来更加的明确, 如今咱们在DetailViewController中实现了这个代理方法, 那么接下来, 咱们还须要在MasterViewController里, 实现这个传递的细节:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let selectedFruits = fruits[indexPath.row]

    delegate?.furitSelected(selectedFruits)
}
复制代码

最后, 咱们还须要在SceneDelegate.swiftscene(_:willConnectTo:options:)方法中, 将DetailViewController设置为MasterViewController的代理:

masterViewController.delegate = detailViewController
复制代码

如今咱们已经完成了MasterViewControllerDetailViewController之间的通讯了.

图片

如今看上去一切都好像很是的完美, 但若是咱们须要在iPhone上运行它, 那么在MasterViewController选择水果时就不会显示DetailViewController了, 这里咱们须要进行一丢丢的优化, 确保在iPhone上也能够正常运行.

打开MasterViewController.swift, 找到tableView(_:didSelectRowAt:)方法, 而后将下面的内容添加到内部代码的最后面:

if let detailViewController = delegate as? DetailViewController {
    splitViewController?.showDetailViewController(detailNavigationController, sender: nil)
}
复制代码

首先, 咱们确保咱们的Delegate对象是DetailViewController实例, 而后在UISplitViewController上调用showDetailViewController(_: sender:)时, 将DetailViewController传递进去, 这里有一点要说明, UIViewController自己是有一个叫作splitViewController的属性, 它将会引用已经存在的ViewController.

通过这个简单的改动, 在iPhone上它就能够正常运行了, 只是添加了几行代码, 咱们就能够在iPadiPhone上使用完整的UISplitViewController了.

图片

完善iPad的纵向显示

在横向显示的时候, iPad会自动在左边显示菜单栏, 可是在纵向时, 只能经过手势从左往右的滑动才会显示, 在点击菜单栏之外的位置, 它就会自动隐藏掉.

虽然这种滑动显示的方式很高大上, 但若是咱们要像iPhone那样, 在左上方有一个显示菜单的按钮该怎么作呢? 这个时候咱们只须要对App进行一丢丢的优化就能够了.

首先咱们打开Main.storyboard, 给DetailViewController添加一个UINavigationController, 这里有两种添加的方式.

  • 第一种

图片

  • 第二种

图片

不管哪一种其实都是能够的, 没有任何的区别, 下面是完成了添加的storyboard:

图片

如今咱们打开MasterViewController, 找到tableView(_:didSelectRowAt:), 修改一下咱们以前调用showDetailViewController(_:sender:)的小细节:

if let detailViewController = delegate as? DetailViewController,
    let detailNavigationController = detailViewController.navigationController {
    splitViewController?.showDetailViewController(detailNavigationController, sender: nil)
}
复制代码

这里咱们将显示DetailViewController修改为显示DetailViewControllerNavigationController, 但不管怎么改, 这个NavigationController的根视图依然是DetailViewController, 和咱们以前看到的内容依然是同样的.

接下来, 咱们须要在SceneDelegate.swiftscene(_willConnectTo:options:)中, 修改初始化detailViewController的代码:

let detailViewController = (splitViewController.viewControllers.last as? UINavigationController)?.topViewController as? DetailViewController
复制代码

由于DetailViewController是存在于UINavigationController中, 因此咱们这里须要经过访问两层来获取到它的实例.

最后, 咱们在方法结束以前, 添加下面的两行代码:

detailViewController.navigationItem.leftItemsSupplementBackButton = true
detailViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem
复制代码

这两行代码是用来告诉DetailViewController的左上角按钮是用来显示UISplitViewController的, 在iPhone不会有任何的改变, 可是在iPad上会有一个按钮用来显示菜单的UITableView.

下面就是咱们运行的效果:

图片

总结

经过简单的事例, 咱们学习了UISplitViewController的使用, 虽然这个例子比较简单, 可是能够经过该例子慢慢的延展出更多的使用场景, 谢谢你们的阅读.

相关文章
相关标签/搜索