在邮件这个App里, 它在iPad里划分了两个区域, 左边是一个邮件列表, 右边则是对应的邮件详细内容. Apple为咱们建立了一个很是方便的ViewController, 它的名字叫作UISplitViewController. 在这个教程中, 咱们将学习如何去使用它, 还有一个事情就是, 在iOS 8开始, UISplitViewController就能够在iPad和iPhone上运行.git
在本教程中, 咱们将会从头开始建立一个通用的App, 它使用UISplitViewController来显示水果的列表, 咱们将使用UISplitViewController来处理iPad和iPhone 11的Navigation和显示的问题.程序员
在此以前, 你应该掌握iOS开发的一些基础知识, 好比AutoLayout和Storyboard的使用等等.github
在开始以前, 咱们须要下载本教程的一些课件, 这里的课件共有两个, 一个是已经完成了的, 一个是准备让你去完成的.swift
注意: 这里使用的是Xcode 11, iOS 13和Swift 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.
注意: 这个时候因为咱们没有Cell的重用标识, 因此Xcode会有个警告, 这个前往别忘记了.
还有一点要注意的是, 因为咱们把自带的ViewController给删除了, 因此咱们须要告诉Storyboard, 咱们但愿将UISplitViewController设置为初始化ViewController.
这个时候咱们选择UISplitViewController, 而后在右侧的"属性"栏, 勾选Is Initial View Controller:
勾选了以后, 咱们就会在UISplitViewController的左侧看到一个箭头, 这就是这个Storyboard的初始化控制器.
这个时候, 咱们选择iPad模拟器, 而后将模拟器横向后, 你就会看到下面这个空白的UISplitViewController了:
以前也说了, 自从iOS 8以后, 咱们也能够运行在iPhone上, 只要它的尺寸够大就能够了, 这里咱们选择iPhone 8 Plus模拟器, 而后就会看到效果:
在横向的大尺寸iPhone会和iPad显示的效果同样以外, UISplitViewController将会和常规的操做同样, 会有UINavigationController的Push和Pop, 这都是系统帮咱们实现的, 不须要咱们而外再去操做,
如今咱们已经有了Storyboard主要的控制器结构, 如今咱们须要的是在代码上添加数据源, 而后将数据显示出来.
如今咱们建立一个名为MasterViewController的UITableViewController子类, 在建立的过程里, 咱们须要把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中设置一些UITableViewController与UITableViewCell相关的东西.
如今咱们能够运行一下, 这个时候你会发现总共有十行, 每行的标题都是同样的, 并且每当咱们点击任何的一行都不会发生任何事情.
这是由于咱们尚未建立DetailViewController, 如今咱们来建立对应的DetailViewController, 和上面同样, 这个过程就忽略掉, 只是名称为DetailViewController.
建立完成以后, 咱们按照一样的方式, 在Main.storyboard中, 给DetailViewController设置Custom Class为DetailViewController.
为了让咱们的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的标题看起来更加的贴合, 咱们能够修改一下它的标题, 修改标题有两种方式, 第一种是直接在Storyboard中修改:
第二种是在代码上修改:
override func viewDidLoad() {
super.viewDidLoad()
title = "Fruit List"
}
复制代码
不管哪一种均可以, 但若是你所在的公司有固定的代码规范, 那就按照公司的规范来.
如今在TableView中咱们已经显示了水果的名称, 如今咱们是时候来完善每当点击Cell的时候, DetailViewController则会显示对应的内容了.
打开Main.storyboard, 将原来咱们添加到DetailViewController里面的内容删掉, 而后再添加咱们所须要展现的内容:
下面是咱们须要添加的内容:
这里使用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的属性, 其中是包含了MasterViewController和DetailViewController, 在咱们这个状况下, MasterViewController实际上就是NavigationController, 因此咱们若是要获取真正的MasterViewController实例, 就须要得到NavigationController中的一个ViewController.
要获取DetailViewController也是使用一样的方式, 只不过是获取UISplitViewController中的ViewControllers最后一个ViewController.
如今咱们运行项目, 就能够看到有关于水果的详情信息:
但如今咱们又面临了一个问题, 不管咱们点击哪一个UITableViewCell, 都只会显示苹果的信息, 接下来咱们就须要解决这个问题.
关于两个控制器之间的通讯方式有不少种, 在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.swift的scene(_:willConnectTo:options:)
方法中, 将DetailViewController设置为MasterViewController的代理:
masterViewController.delegate = detailViewController
复制代码
如今咱们已经完成了MasterViewController和DetailViewController之间的通讯了.
如今看上去一切都好像很是的完美, 但若是咱们须要在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上它就能够正常运行了, 只是添加了几行代码, 咱们就能够在iPad和iPhone上使用完整的UISplitViewController了.
在横向显示的时候, 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修改为显示DetailViewController的NavigationController, 但不管怎么改, 这个NavigationController的根视图依然是DetailViewController, 和咱们以前看到的内容依然是同样的.
接下来, 咱们须要在SceneDelegate.swift的scene(_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的使用, 虽然这个例子比较简单, 可是能够经过该例子慢慢的延展出更多的使用场景, 谢谢你们的阅读.