在iOS 7中,一个重大的改变就是随处可见的虚化,这在通知中心和控制中心表现得尤为抢眼:
然而,当开发人员们着手去将类似的模糊效果增加自己的App的时候,他们会发现有相当严重的障碍。
那时苹果所界定的设备可用范围相当简单,并不强大到足以支持在第三方应用中实现实时模糊。
并声称开发人员们非常可能在App里滥用虚化从而严重影响用户体验。
只是,精明又狡猾的程序猿们非常快的创造了自己基于模糊静态图片方法来破解实时模糊的算法。
大部分解决方式都效果卓越。只是,之后的iOS 8在开发人员工具箱中加入了官方的模糊效果。不仅相当高效,并且其使用的简单程度让人惊叹。
提示:想知道怎样使用静态模糊图片来模拟实时模糊的话能够參考这篇博文。
模糊化扫盲
想要使模糊效果显得美观而又高效须要一定技巧,在这一节你将会了解到最常见的模糊算法以及怎样使用模糊效果来提升你App的用户体验。
怎么做到模糊
模糊的对象是图片。想要实现模糊。你须要对图片中的每个像素使用模糊算法,这样会得到一个对原图进行了均匀模糊后的图片。
模糊算法能够在模糊的风格和模糊的复杂度上有非常多变化,只是在这个教程里你将会运用到一个最为常见并且颇为出名的算法——高斯模糊。
模糊算法一般会检索图片的每个像素点并基于它周围的像素点来计算该像素在模糊后的灰度值。
比方,我们想象一张例如以下所看到的网格图:
每个小格子代表了一个独立的像素,每个像素点有一个介于1和10之间的值。如果我们要对中心的像素点进行模糊化,那就须要计算四周八个像素中的值的算术平均数,并将这个数作为中心像素的值插入进去。
结果例如以下图:
接着对原图的每个像素点都反复相同的操作(编者按:原图中每个像素的新值应该插入到一张新图片对应位置的像素中去以免出现错误,原图的像素值依然不变。原作者并未提示这一点)。
上面的模糊样例只用每一个方向上的一个像素单位来进行计算新图片的像素值,你能够扩大模糊所要採用的像素半径来提升图片的模糊效果,例如以下图所演示的这样:
提示:一般说来,使用的模糊半径越大则处理图片时候的计算量会越多。iOS会将大部分图像处理工作交给GPU来处理以确保主线程不会被卡死。
关于模糊化的设计
人总是会情不自禁的被那些对焦准确的部分而忽视掉被虚化的部分。
无论你信不信。这是大自然的道理,由于人眼就是这么工作的。眼球的对焦机制好像一个调节器一样捕捉那些离你忽远忽近的物体,这样才干让你感受到周围一切事物的深度和距离。
App设计师实际上通过模糊掉那些无关紧要的内容来引导用户的目光关注那些没有被模糊掉的要素,比方时下流行的Twitterclient就是一个非常好的演示样例:
上图中背景里的用户界面可以勉强识别,因而为用户提供了一个情景意识来让他们知道正处于导航层中的哪个位置。
在这个样例中用户仅仅须要选择一个账户登入,就行退回到没有被模糊的背景图层里去。
提示:尽管模糊能带给人很清新的视觉体验,只是也切忌在你的App中过度使用。由于过度使用或者使用不当都会分散用户的注意力或者惹恼用户。
遵照标准的模糊设计方案来让用户关注到你想要给出的事物。这样你就不easy弄糟。
你能够在苹果iOS开发人员中心的iOS Human Interface Guidelines文档中的Designing for iOS章节了解到很多其它内容。
開始
为了理解怎样实现模糊,你须要尝试在一款以新格林童话故事为蓝本的App上加入合适的模糊效果,这款App叫做Grimm。
该应用为用户提供了一系列的童话故事,当用户点开某个童话时,它就会在屏幕上显示完整的故事内容。用户能够自己定义显示的字体、文本对齐,以及适用于日间或夜晚阅读的颜色主题。
如今開始你须要下载一个初始project,在Xcode中打开Grimm.xcodeproj,然后打开Grimm.storyboard看一下App中的视图控制器,像以下这样:
你可略过上图中最前面的那个视图控制器。由于它在App中仅仅只是是个简单的底层导航控制器。
你须要关注的是后面有编号的视图控制器:
1.第一个控制器是StoryListController。是用于显示数据库中全部童话故事的列表。
2.当你点击一个童话故事时就会切换到这个视图控制器StoryViewController。它会显示选中童话的标题和文本内容。
3.最后的OptionsController是包括在StoryViewController中的,会列出一些可用的字体、对齐、颜色选项。仅仅须要在StoryViewController中轻击设置图标就能显示它。
构建并执行,你就会看到例如以下所看到的的一个初始界面:
你能够体验一下这个应用。选好童话之后,点击省略号唤出选项视图来切换不同的字体和阅读模式。这样能够了解用户界面的基本功能。
提示:你能够在模拟器或者除了iPad 2之外的iOS 8设备上执行这个应用。
出于性能上的考虑苹果限制了在iPad 2上显示模糊效果,App本身的确能非常好的执行在iPad 2上,仅仅只是你会看不到不论什么满意的模糊效果而已。
手动模糊技巧
眼尖的同学可能会发如今这个project里面还残留有Objective-C代码。
为此焦虑大可不必,这一段Objective-C代码在非常多应用project里面都实用到。并且还相当坚挺。它的作用是在你的全部Swift文件里接入Grimm-Bridging-Header.h头文件,由于我们在这里没有必要再单独为Swift重写一个。
提示:Swift被设计得可以良好的兼容Objective-C,这种话包含苹果自己的开发人员在内的开发人员可以直接在project里加入Swift代码而免去重构代码的麻烦。连接了头文件之后你就行在你的Swift文件里写进Objective-C代码了。
在项目资源管理器中打开GrimmCategoriesUIImage+ImageEffects.m文件,略过前面全部的凝视来看看形如applyBlurWithRadius:tintColor:saturationDeltaFactor:maskImage:的代码段,本教程从头到尾都不会覆盖或是改动这些代码,可是读一读有助于你理解当中包含哪些基本功能。
在iOS 7公布的时候苹果还提供了UIImage类来演示怎样怎样对图片应用静态模糊。这充分的发挥了Accelerate框架在使用向量和矩阵运算上的优势。使得在图像处理上使用这些计算时变得更为方便。
applyBlurWithRadius:tintColor:saturationDeltaFactor:maskImage:这里的參数有模糊半径、饱和度、以及可选的掩盖图片。该方法会运用大量的数学运算生成一张处理后的新图片。
获取快照
在你使用你的模糊效果前你须要获取一张快照,今天你的大部分力气将会花在StoryViewController视图底部的绘制选择上。
打开StoryViewController.swift文件并找到setOptionsHidden方法。在这里你会先获取整个StoryViewController控制器的截图。然后在将其模糊化之后作为选项界面的背景图片。
把以下这种方法加入到setOptionsHidden方法前面:
1
2
3
4
5
6
7
8
9
10
11
|
func updateBlur() { //为了避免在截图的时候截到选项界面,因此先要确保选项界面必须是隐藏状态。 optionsContainerView.hidden = true //创建一个新的ImageContext来绘制截图,你没有必要去渲染一个完整分辨率的高清截图,使用ImageContext能够节约掉不少的计算量 UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, true , 1) //将StoryViewController中的界面绘制到ImageContext中去。由于你须要确保选项界面是隐藏状态因此你须要等待屏幕刷新后才干绘制 self.view.drawViewHierarchyInRect(self.view.bounds, afterScreenUpdates: true ) //将ImageContext放入一个UIImage内然后清理掉这个ImageContext let screenshot = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() } |
在点击省略号之后你须要调用一个updateBlur方法来模糊截图,这样你须要在setOptionsHidden方法的一開始加入例如以下代码:
1
2
3
|
if !hidden { updateBlur() } |
更进一步之前,你应该检查一下你是否截到你想截的那张图。
在你的上一步加入的updateBlur方法源代码中找到UIGraphicsEndImageContext()这一行并加入一个断点,然后构建并执行。选择一个童话故事并打开它。
一旦童话打开就点击省略号来触发断点。
在调试栏里展开screenshot变量然后选中例如以下嵌套在当中的some变量:
敲击空格键来打开Quick Look。你应该会看到一张故事栏的非高清截图。例如以下所看到的:
请注意在截图中并未包含UINavigationController中的不论什么元素。由于故事列表的视图是作为UINavigationController的背景图存在的,导航控制器则位于截图的区域之外了。
如今你已经能截到一张正确的快照了。你能够使用我们之前提到的UIImage类来对你的截图開始进行模糊化。
模糊掉你的快照
仍旧打开StoryViewController.swift文件,找到你刚刚更改过的updateBlur方法,在最后一行UIGraphicsEndImageContext()的以下加入这行代码:
1
|
let blur = screenshot.applyLightEffect() |
移动你刚刚加在文件中的断点。像这样:
提示:你能够在滚动槽里面拖着断点上下移动。
构建并执行,打开一则童话故事,点击导航器里面的省略号,然后在调试栏里面找到blur变量并使用空格打开Quick Look。
稍等……blur里面好像什么都没有?去哪了?
你没有看到不论什么东西是由于你的断点恰好放在了blur变量设置的那一行,这样Xcode会停在这一行运行之前的一步。
想要运行下图中高亮的那一行你能够敲击F6或者如图中所看到的点击运行下一步:
如今你能够展开blur变量了,选择底下的那个some变量然后敲击空格键唤出Quick Look查看你模糊化后的图片:
提示:LLDB(Xcode的调试器)有时候并非非常适宜用于Swift,所以你可能会须要点两次运行下一步才会显示一个some变量。
你如今能够获取一张快照而且运行模糊化了。接下来要做的就是在App中增加这张模糊后的图片了。
在视图中显示模糊图片
打开StoryViewController.swift文件在属性定义的那堆代码的開始增加以下这行:
1
|
var blurView = UIImageView() |
这里能够为每一个StoryViewController实例初始化一个UIImageView。
找到viewDidLoad方法并在这个它的最后加上这样一段:
1
|
optionsContainerView.subviews[0].insertSubview(blurView, atIndex:0) |
在Grimm.storyboard中把OptionsController放进了一个视图容器以方便用户点击省略号时候就显示出来。由于你无需直接使用OptionsController所在图层,你要做的就是获取这个容器的subview,在这样的情况下这层view仅仅是恰好属于OptionsController。最后你须要把那个模糊的blurview作为subview加入到视图堆栈的最底部,保证它处于其它全部视图的下方。
在StoryViewController.swift文件里找到updateBlur方法在最后加入例如以下代码:
1
2
3
|
blurView.frame = optionsContainerView.bounds blurView.image = blur optionsContainerView.hidden = false |
由于blurView在Storyboard中并没有被设置过。所以它会有一帧CGRectZero的图片,除非你有手动设置过。当然你也能够设置你刚刚模糊生成的那张图片的属性。
这里还要注意的是你在截图之前以前把optionsContainerView设置为不可见的隐藏状态。一定要记得在虚化方法完毕的最后将optionsContainerView设置为可见。
取消你之前设定的断点,构建并执行。在选择了一则童话之后点击设置选项,注意看着它范围内的模糊效果,例如以下:
这一个虚化看上去还是有点猥琐。由于它好像跟后面的文本并非非常搭配?
在默认情况下。UIImageView会重置图片的大小以确保和视图中的画面适应。也就是说那张大一些的虚化图片已经被压缩小了。所以就产生了这种效果。
为了修正这一错误,你须要把UIImageView的contentMode属性改为除了默认的UIViewContentMode.ScaleToFill外的其他值。
在updateBlur中设置blurView那一行的以下贴上这些代码:
1
|
blurView.contentMode = .Bottom |
UIViewContentMode.Bottom表示强制让图片保持原有大小。而不是仅有仅仅有UIImageView原图本身的中下部那么大。
构建并执行。如今看看虚化的效果怎样了?
在你的静态模糊准备拿去使用之前你还须要多考虑一个事,旋转你的设备或者虚拟机(command+左/右方向键)。你能够看到视图的大小并没有被重置。
由于你的全部文本採用了自己主动布局。所以之前的截图不再实用了,你须要在旋转之后又一次截图快照而且更新一下blurView。
这个非常easy就能够实现。在StoryViewController.swift重写一下以下这种方法:
1
2
3
4
5
6
7
8
|
override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { // animateAlongsideTransition方法能够使你旋转屏幕的时候的变化更为动感而且在旋转完毕后作一些清理,你只须要的是后者。由于你还须要截下optionsViewController旋转之后的一帧图。 coordinator.animateAlongsideTransition(nil, completion: { context in // 在旋转后更新一下blurView,这样就会使用新的布局了 self.updateBlur() }) } |
构建并执行之后试着改变一下设备或者模拟器的角度,会发现有新的布局了:
模糊范围的大小正确无误。只是还不够。滑动后面的文本区你会发现虚化部分没有发生不论什么改变。
依据上面的经验你也应该知道该怎么改动。而之后的iOS 8提供了动态生成虚化的工具。应用中採用实时模糊效果这一事从开发人员们在iOS 7上开辟的解决方式以来那是说来话长了。
iOS 8上的模糊效果
iOS 8 提供了一套完整有用的虚化工具。UIVisualEffect的子类UIBlurEffect正是我们所感兴趣的。UIBlurEffect提供了你在导航栏、通知中心和控制中心里看到的那些美丽的虚化,你也能够在你的App中使用这个效果。
加入UIBlurEffect
打开StoryViewController.swift文件之后找到setOptionsHidden方法,假设你之前在第一个if条件分支里面写入过updateBlur,那就将它凝视掉。改动后例如以下:
尽管你做完了,可是你不能全然保证blurview没有被加入到场景中去,凝视掉以下这一行:
1
|
optionsContainerView.subviews[0].insertSubview(blurView, atIndex:0) |
提示:不要仅仅是简单的删除掉那些代码。你仅仅需凝视掉就好,这样也便于你在回想的时候发现有什么不同。假设你对你手动加入的模糊代码没有不论什么想法,那你也能够删掉它们而非凝视。
构建并执行之后你会发现除了你的虚化不见了而外剩下的部分都能正常执行。
打开Grimm.storyboard然后找到Options Controller Scene,选择view。展开Attributes Inspector然后更改view的background为Clear color。例如以下:
打开OptionsController.swift文件在viewDidLoad方法中加入以下代码,位置就在你之前加入过的optionsView的后面:
1
2
3
4
5
6
7
|
// 创建一个样式为UIBlurEffectStyle.Light的UIBlurEffect,定了要应用的效果,其它的效果样式还有UIBlurEffectStyle.ExtraLight和UIBlurEffectStyle.Dark let blurEffect = UIBlurEffect(style: .Light) // 创建一个UIVisualEffectView并为其设置须要使用的效果。 let blurView = UIVisualEffectView(effect: blurEffect) // 解除blurView自适应遮罩限制大小的变化,过会儿你也能够手动加入限制。然后将它至于视图堆栈里的最以下。 blurView.setTranslatesAutoresizingMaskIntoConstraints( false ) view.insertSubview(blurView, atIndex: 0) |
如今你须要确保你的blurView可以适宜的布局。
仍然是在viewDidLoad中,在addConstraints的调用之前写入以下代码:
1
2
3
4
5
6
|
constraints.append(NSLayoutConstraint(item: blurView, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Height, multiplier: 1, constant: 0)) constraints.append(NSLayoutConstraint(item: blurView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0)) |
这些參数限制会使得blurView的画面总是与OptionsController相适应。
构建并执行。
打开童话故事点击省略号。然后滑动后面的文本,会发现虚化部分可以实时变化了:
如今你就拥有一个可以动态渲染虚化的App了,不单仅仅是看上去好看,你还是採用了iOS核心功能实现的。
加入Vibrancy
虚化的效果相当棒——只是苹果像曾经一样对其进行了提升。结合使用UIVibrancyEffect与UIVisualEffectView能够调整文本的颜色使得App看上去更加艳丽。
以下这张图展示了Vibrancy在背景图片全然同样的情况下怎样让你的标签和图标在屏幕上显得更为舒适:
左边的显示的是通常情况下的标签和button,而右边的显示的是应用了Vibrancy之后的效果。
提示:UIVibrancyEffect必须加入到已经用UIBlurEffect配置过的UIVisualEffectView中去,否则就不会有不论什么的虚化图片会应用Vibrancy效果。
在OptionsController.swift文件里找到viewDidLoad,在自己主动布局限制条件之前加入以下代码:
1
2
3
4
5
6
7
8
9
|
// 使用你之前设置过的blurEffect来构建UIVibrancyEffect。UIVibrancyEffect是UIVisualEffect还有一个子类。 let vibrancyEffect = UIVibrancyEffect(forBlurEffect: blurEffect) // 创建UIVisualEffectView来应用Vibrancy效果,这个过程恰巧跟生成模糊图一样。 let vibrancyView = UIVisualEffectView(effect: vibrancyEffect) vibrancyView.setTranslatesAutoresizingMaskIntoConstraints( false ) // 将optionsView加入入vibrancyView的contentView属性里。这样就能确保全部的控制视图都会应用Vibrancy效果 vibrancyView.contentView.addSubview(optionsView) // 最后你须要在blurView的contentView里增加vibrancyView来完毕效果 blurView.contentView.addSubview(vibrancyView) |
最后一件事就是为Vibrancy视图设置自己主动布局的限制,这样就能够与你的控制器视图保持一直的高宽。
把以下的限制增加viewDidLoad方法的最后:
1
2
3
4
5
6
7
8
|
constraints.append(NSLayoutConstraint(item: vibrancyView, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Height, multiplier: 1, constant: 0)) constraints.append(NSLayoutConstraint(item: vibrancyView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1, constant: 0)) |
构建并执行。唤出设置选项来看看你的Vibrancy效果。
除非你的眼睛也是高分屏的,不然真的非常难看清标签和控制器,那么到底发生了什么?
这个情况其实是这种,由于你blurView使用的样式是UIBlurEffectStyle.Light。所以导致它是白色的。
这种话就不能产生意料之中的Vibrancy效果了。
在viewDidLoad方法中把blurEffect的初始化改为以下这样:
1
|
let blurEffect = UIBlurEffect(style: .Dark) |
这样就改变并且添加了模糊视图与背景之间的颜色反差。
构建并执行之后你就能看到一个称心如意的Vibrancy效果了。
很多其它
你能够在这里下载到完毕后的project。
至此你已经知道怎样手动模糊一张图片了。也学会了怎样进行实时的模糊渲染,也会在你的App上简单使用UIVisualEffectViews。
你所能使用的模糊技巧仅限于静态图片。所以图片不会非常生动并且不能实时的更新。
只是使用UIBlurEffect却能够进行实时更新。这样你就能够借助这个效果做一些奇异的事,比方说做动画。
同一时候你也可能会企图把全部的东西都来模糊一下——请想想我们在教程的最初就提到过的事——使用虚化要适当并且有克制。当然对于Vibrancy也是这样。
(按:这是一篇来自于Ray Wenderlich上有关于iOS 8 虚化效果的教程,在这里不过一个概览式的介绍,假设想了解很多其它,可自行參阅该站点的iOS 8 Feast专题)