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