The Dev World, Yuxiao

Swift 3 - 用 SnapKit 添加 SafariViewController 为 childViewController

介绍

本文章尝试添加 SafariViewController 作为子 viewcontroller。我是通过 SnapKit 来对所有的对象进行 layout 以及 Swift 3 来编译。

同时,我也在最后介绍了一些 SnapKit 的高级用法,divide 和 multiply,这些在官方的档案中并没有过多的提及。

下载完整的项目

English Version: Add SafariViewController as childViewController with SnapKit

开发者:开发这个世界.

SnapKit

SnapKit 是一款让 iOS 和 OS X 自动布局变得方便的 DSL。

为什么要用?

当我在开发 Info It 时,在 Xcode 中给对象添加限制时,Xcode 每一次都会给我崩溃并且无法显示想要的任何界面。在尝试许久并资讯了他人后,不得不觉得是 Xcode 本身的问题。在安装了 SnapKit 以及使用后,发觉通过 Snapkit 来进行编程化的自动布局会自动的解决这个方法,而 SnapKit 所带来的简单方便的理解,更加让布局变得方便许多。

安装

SnapKit 官方文档

通过 CocoaPods,每个人都可以十分快的安装。我不会在本文章中相机说明安装 CocoaPods 的细节和方法,我会假定你已经安装了 CocoaPods 并且直接开始安装 SnapKKits。

选择 Swift 语言创建一个新的项目,不关闭 Xcode 的情况下打开终端(我这里用的是 iTerm),接下来 ‘cd’ 到你项目所在的文件夹并且创建一个新的 podfile。

> pod init

在完成这条指令之后,打开你的 podfile,并且讲下面一行指令加入进去,和我图中所加入的位置一致。

pod 'SnapKit', '~> 3.0'

接下类在终端中输入安装 pod 的指令:

> pod install

在安装 SnapKit 期间,不要关闭你所创建的项目!在如图中成功安装后,关闭项目并且打开 .xcworkspace 结尾的文件。


SafariViewController

接下来我会用 “svc” 来代替 SafariViewController

当你想用 svc 时,你需要在 class 的开头继承 SafariViewControllerDelegate,然后添加两个 String 作为之后当做链接打开所使用:

1
2
3
4
5
class ViewController: UIViewController, SFSafariViewControllerDelegate {
// Insert two web link to test the child view
let main: String = "http://o1xhack.com/2017/02/21/infoit/"
let update: String = "http://o1xhack.com"

在 storyboard 中创建一个按钮以及一个界面(button,view)。

并且将 view 和 button 连接到你的 controller 文件中。

addChildViewController

在 viewDidLoad 中添加如下的代码来实现自动布局和添加子视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
override func viewDidLoad() {
super.viewDidLoad()
showView.snp.makeConstraints{ (make) -> Void in
make.edges.equalTo(self.view).inset(UIEdgeInsetsMake(0,0,60,0))
}
let svc = SFSafariViewController(url: URL(string: main)!)
svc.delegate = self
self.addChildViewController(svc)
self.showView.addSubview(svc.view)
svc.didMove(toParentViewController: self)
svc.view.snp.makeConstraints{ (make) -> Void in
make.edges.equalTo(self.showView)
}
}

第 4-6 行:

给 showView 进行布局限定(这个视图将会是 svc 的父视图),这里的代码运用 SnapKit 将 showView 限制于 view(主视图),上左右分别和 view 没有任何举例,下方空出 60 的距离留给 button。

第 8-12 行:

第 8 行添加了 SafariViewController 给 svc,用到了我之前添加的链接。第 9 行设定了 svc 的 delegate。
第 10 行将 svc 添加为子视图,然后在第 11 行将 svc 视图添加为 showView 的 subview。
第 12 行告诉 controller 我们的 svc 已经成功的添加到了父级视图中,也就是我们的 showView。

第 14-16 行:

这里基本和前面的 4-6 行一致,我们给 svc 设定了布局限制,让它正正好好填满了整个 showView,相对的来讲也就是和 showView 一样的(0,0,60,0),但因为 svc 是一个子视图,所以我们要限制子视图相对于父级视图的布局。

结果

在添加了这些代码之后,运行整个项目然后我们会看到如图中的模拟器视图,并且发现我们的 button 和 svc 互相遮挡,这是因为我们还没有给 button 进行任何的布局。、


再遇 SnapKit

现在,我们知道 svc 距离底部是 60,因此我们可以相应依据 showView 来给 button 进行限制。

1
2
3
4
change.snp.makeConstraints { (make) -> Void in
make.centerX.equalTo(showView)
make.top.equalTo(showView.snp.bottom).offset(20)
}

关于 change.snp.makeConstraints 的代码,我们需要把它紧接着 showView.snp.makeConstraints 添加,因为这两个的限制是有关联的。

第 2 行,我们对 button 的中心进行了限定,让它的横轴中心和 showView 一致,如果你曾经用过 Xcode 的限制,那么你一定会熟悉这行代码的意思。
第 3 行,我们设定 button 的顶部和 showView 的底部距离为 20。因为我们知道 showView 一定距离 view 的底部是 60, 因此 20 这个距离是一定因为分辨率问题被影响。(在之后我会用 divide 的方法来设定布局,一个更加精美的方法。)

运行项目,然后你会发现 button 正好在 svc 的下面并且在整个手机的中心。


更新 SVC

现在我们需要写一个新的 function 用作点按 button 后更新 svc 链接。这一点都不难,只需要将 button
拖拽到 viewcontroller 中并且选择创建一个动作即可。

我们尝试添加和 viewDidLoad 中一样的添加 svc 的代码并且运行程序,然后我们发现在点击 button 后,svc 并没有任何变化。

这是我们添加的代码:那为什么会这样呢?(记住你不能直接复制我的代码,因为这需要连接 button 才可以)

1
2
3
4
5
6
7
8
@IBAction func changeWebsite(_ sender: UIButton) {
let svc = SFSafariViewController(url: URL(string: update)!)
svc.delegate = self
self.addChildViewController(svc)
self.showView.addSubview(svc.view)
svc.didMove(toParentViewController: self)
}

点击 button 不更新是因为我们没有新新建的 svc 进行任何的布局限制,因此 viewcontroller 根本不知道这个新建的 svc 应该显示到哪里,因此,我们需要用 viewDidLoad 中一样的 svc.snp.makeConstraints 来对 svc 进行限制。

1
2
3
4
5
6
7
8
9
10
11
12
@IBAction func changeWebsite(_ sender: UIButton) {
let svc = SFSafariViewController(url: URL(string: update)!)
svc.delegate = self
self.addChildViewController(svc)
self.showView.addSubview(svc.view)
svc.didMove(toParentViewController: self)
svc.view.snp.makeConstraints{ (make) -> Void in
make.edges.equalTo(self.showView)
}
}

添加完成后,运行程序并且点击按钮,我们发现的确可以更改 svc 的连接了:(第一个是文章内容,第二个是目录)


SnapKit Divide / Multiply

到这里,这个项目其实已经完成了,我们成功用 SnapKit 来限制了每个对象,并且将 svc 嵌入到 view 中,和 button 可以同时存在,并且可以更新链接。但是用具体的数字来限制布局的距离其实并不那么好,会有一丢丢的缺点,而这里我们就要通过 SnapKit 所拥有的分割和倍数来决定每个对象所占用的具体比例而达到完美的决绝,而我们之前用具体数字所形成的布局在白色空白处其实是不相等的,因为我们没有考虑 button 自身的高度。

Divideby 以及 Multiplyby 是 SnapKit 中的两个高级使用方法,在文档中也没有涉及过多,是通过对对象进行比例化的分割来完美达到布局效果。

首先我们考虑了一下发现我们仅仅需要很少的空间给 button,假设我们将整个 view 的高度分成了 10 份,svc 占用 9 份。但当我们在讲 view 除以 10 分取其 9 份的时候,我们不能用 divide,而应用 multiply 以及系数 0.9 因为使用 devide 来进行 9/10 的分割并没有简单的乘以系数方便。

1
2
3
4
5
6
showView.snp.makeConstraints{ (make) -> Void in
make.top.equalTo(self.view).offset(0)
make.right.equalTo(self.view).offset(0)
make.left.equalTo(self.view).offset(0)
make.height.equalTo(self.view).multipliedBy(0.9)
}

我们讲 viewShow 的整体限制更改为单方向的限制。第 2-4 行让 showView 的顶部,左部以及右部和 view 没有任何距离。第 5 行将 viewShow 的高度设定为整个 view 的 90%,也就是我们上面分析的乘以 0.9。运行项目,你会发现这样做是完全对的:

接下来我们需要考虑 button 的分割和占比问题了。我们需要考虑到 button 本身的身高以及相对应的到 showView 底部的空白和到 view 底部的空白,这三个部分需要进行一样的分割和比例。我们需要将 view 剩余的部分(1-0.9=0.1)分成三部分。因此我们将乘以一个系数 0.1*0.3=0.03,让我们这个试试:

1
2
3
4
5
change.snp.makeConstraints { (make) -> Void in
make.centerX.equalTo(showView)
make.top.equalTo(showView.snp.bottom).multipliedBy(0.03)
make.height.equalTo(self.view).multipliedBy(0.03)
}

Woops! 我们得到的是非常奇怪的布局:

为什么会这样呢?因为我们思考的方式错了~

实际上,我们是想要把剩下的 10% 分成三份,应该是 0.1/3=0.0333… 无限循环,所以我们就将 button 的高度设为 0.034,这样上面和下面的空白就都是 0.033。但是需要让计算机理解我们的限定我们不应该针对 showView 进行 offset 的距离限定,而是根据我么一直计算的 view 来限定,也就是 0.967 的整个 view 距离,并且要限定给底部的举例,将代码改成下面这样:

1
2
3
4
5
change.snp.makeConstraints { (make) -> Void in
make.centerX.equalTo(showView)
make.bottom.equalTo(self.view).multipliedBy(0.967)
make.height.equalTo(self.view).multipliedBy(0.034)
}

哈!成功啦~

为什么用?

我们可以发现 divide 和 multiply 很容易让整个布局变得比较迷惑,尤其是当对象越来越多时,更加需要深思熟虑每一个比例的确定以及试验,然而一旦我们确定了这个比例,除非我们需要在添加一个新的对象,就算添加,也将会比用具体数字要方便美观容易确定的多。