如何使用iOS 8的虚化效果

兄弟,打扰一下,如何使用iOS 8的虚化效果
最新回答
薄荷凉我心

2024-11-28 09:56:42

  
  为了理解如何实现模糊,你需要尝试在一款以新格林童话故事为蓝本的App上添加合适的模糊效果,这款App叫做Grimm。

  该应用为用户提供了一系列的童话故事,当用户点开某个童话时,它就会在屏幕上显示完整的故事内容。用户可以自定义显示的字体、文本对齐,以及适用于日间或夜晚阅读的颜色主题。

  现在开始你需要下载一个初始工程,在Xcode中打开Grimm.xcodeproj,然后打开Grimm.storyboard看一下App中的视图控制器,像下面这样:

  storyboard.png

  你可略过上图中最前面的那个视图控制器,因为它在App中只不过是个简单的底层导航控制器。你需要关注的是后面有编号的视图控制器:

  1.第一个控制器是StoryListController,是用于显示数据库中所有童话故事的列表。

  2.当你点击一个童话故事时就会切换到这个视图控制器StoryViewController,它会显示选中童话的标题和文本内容。

  3.最后的OptionsController是包含在StoryViewController中的,会列出一些可用的字体、对齐、颜色选项。只需要在StoryViewController中轻击设置图标就能显示它。

  构建并运行,你就会看到如下所示的一个初始界面:

  firstrun.png

  你可以体验一下这个应用,选好童话之后,点击省略号唤出选项视图来切换不同的字体和阅读模式,这样可以了解用户界面的基本功能。

  提示:你可以在模拟器或者除了iPad 2之外的iOS 8设备上运行这个应用。出于性能上的考虑苹果限制了在iPad 2上显示模糊效果,App本身的确能很好的运行在iPad 2上,只不过你会看不到任何惬意的模糊效果而已。

  手动模糊技巧

  眼尖的同学可能会发现在这个工程里面还残留有Objective-C代码。

  objcnswift.png

  为此焦虑大可不必,这一段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方法的一开始添加如下代码:

  1
  2
  3
  if !hidden {
  updateBlur()
  }
  更进一步之前,你应该检查一下你是否截到你想截的那张图。

  在你的上一步添加的updateBlur方法源码中找到UIGraphicsEndImageContext()这一行并添加一个断点,然后构建并运行,选择一个童话故事并打开它。

  一旦童话打开就点击省略号来触发断点。在调试栏里展开screenshot变量然后选中如下嵌套在其中的some变量:

  debug1.png

  敲击空格键来打开Quick Look,你应该会看到一张故事栏的非高清截图。如下所示:

  debug2.png

  请注意在截图中并未包括UINavigationController中的任何元素,因为故事列表的视图是作为UINavigationController的背景图存在的,导航控制器则位于截图的区域之外了。

  现在你已经能截到一张正确的快照了。你可以使用我们之前提到的UIImage类来对你的截图开始进行模糊化。

  模糊掉你的快照

  仍旧打开StoryViewController.swift文件,找到你刚刚更改过的updateBlur方法,在最后一行UIGraphicsEndImageContext()的下面添加这行代码:

  1
  let blur = screenshot.applyLightEffect()
  移动你刚刚加在文件里的断点,像这样:

  debug3.png

  提示:你可以在滚动槽里面拖着断点上下移动。

  构建并运行,打开一则童话故事,点击导航器里面的省略号,然后在调试栏里面找到blur变量并使用空格打开Quick Look。

  稍等……blur里面好像什么都没有?去哪了?

  你没有看到任何东西是因为你的断点恰好放在了blur变量设置的那一行,这样Xcode会停在这一行执行之前的一步。

  想要执行下图中高亮的那一行你可以敲击F6或者如图中所示点击执行下一步:

  debug4.png

  现在你可以展开blur变量了,选择底下的那个some变量然后敲击空格键唤出Quick Look查看你模糊化后的图片:

  debug5.png

  提示: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设置为可见。

  取消你之前设定的断点,构建并运行,在选择了一则童话之后点击设置选项,注意看着它范围内的模糊效果,如下:

  manualblur1.png

  这一个虚化看上去还是有点猥琐,因为它好像跟后面的文本并不是很搭配?

  在默认情况下,UIImageView会重置图片的大小以确保和视图中的画面适应,也就是说那张大一些的虚化图片已经被压缩小了。所以就产生了这样的效果。

  为了修正这一错误,你需要把UIImageView的contentMode属性改为除了默认的UIViewContentMode.ScaleToFill外的其它值。

  在updateBlur中设置blurView那一行的下面贴上这些代码:

  1
  blurView.contentMode = .Bottom
  UIViewContentMode.Bottom表示强制让图片保持原有大小,而不是仅有只有UIImageView原图本身的中下部那么大。

  构建并运行,现在看看虚化的效果如何了?

  manualblur2.png

  在你的静态模糊准备拿去使用之前你还需要多考虑一个事,旋转你的设备或者虚拟机(command+左/右方向键),你可以看到视图的大小并没有被重置。

  因为你的所有文本采用了自动布局,所以之前的截图不再有用了,你需要在旋转之后重新截图快照并且更新一下blurView。

  这个很简单就可以实现,在StoryViewController.swift重写一下下面这个方法:
  
  override func viewWillTransitionToSize(size: CGSize,
  withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
  // animateAlongsideTransition方法可以使你旋转屏幕的时候的变化更为动感并且在旋转完成后作一些清理,你仅仅需要的是后者,因为你还需要截下optionsViewController旋转之后的一帧图。
  coordinator.animateAlongsideTransition(nil, completion: { context in
  // 在旋转后更新一下blurView,这样就会使用新的布局了
  self.updateBlur()
  })
  }
  构建并运行之后试着改变一下设备或者模拟器的角度,会发现有新的布局了:

  manualblur3.png

  模糊范围的大小正确无误,不过还不够。滑动后面的文本区你会发现虚化部分没有发生任何改变。

  根据上面的经验你也应该知道该怎么修改。而之后的iOS 8提供了动态生成虚化的工具。应用中采用实时模糊效果这一事从开发者们在iOS 7上开辟的解决方案以来那是说来话长了。

  iOS 8上的模糊效果

  iOS 8 提供了一套完整实用的虚化工具。UIVisualEffect的子类UIBlurEffect正是我们所感兴趣的。UIBlurEffect提供了你在导航栏、通知中心和控制中心里看到的那些漂亮的虚化,你也可以在你的App中使用这个效果。

  添加UIBlurEffect

  打开StoryViewController.swift文件之后找到setOptionsHidden方法,如果你之前在第一个if条件分支里面写入过updateBlur,那就将它注释掉。修改后如下:

  newblur1.png

  虽然你做完了,但是你不能完全保证blurview没有被添加到场景中去,注释掉下面这一行:

  1
  optionsContainerView.subviews[0].insertSubview(blurView, atIndex:0)
  提示:不要只是简单的删除掉那些代码,你只需注释掉就好,这样也便于你在回顾的时候发现有什么不同。如果你对你手动添加的模糊代码没有任何想法,那你也可以删掉它们而非注释。

  构建并运行之后你会发现除了你的虚化不见了而外剩下的部分都能正常运行。

  打开Grimm.storyboard然后找到Options Controller Scene,选择view,展开Attributes Inspector然后更改view的background为Clear color,如下:

  newblur2.png

  打开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能够适宜的布局。

  仍然是在viewDidLoad中,在addConstraints的调用之前写入下面代码:

  
  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相适应。

  构建并运行。打开童话故事点击省略号,然后滑动后面的文本,会发现虚化部分能够实时变化了:

  newblur3.png

  现在你就拥有一个能够动态渲染虚化的App了,不单只是看上去好看,你还是采用了iOS核心功能实现的。

  添加Vibrancy

  虚化的效果相当棒——不过苹果像以前一样对其进行了提升。结合使用UIVibrancyEffect与UIVisualEffectView可以调整文本的颜色使得App看上去更加艳丽。

  下面这张图展示了Vibrancy在背景图片完全相同的情况下如何让你的标签和图标在屏幕上显得更为舒适:

  vibrancy.png

  左边的显示的是通常情况下的标签和按钮,而右边的显示的是应用了Vibrancy之后的效果。

  提示:UIVibrancyEffect必须添加到已经用UIBlurEffect配置过的UIVisualEffectView中去,否则就不会有任何的虚化图片会应用Vibrancy效果。

  在OptionsController.swift文件中找到viewDidLoad,在自动布局限制条件之前添加下面代码:

  
  // 使用你之前设置过的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效果。

  effect1.png

  除非你的眼睛也是高分屏的,不然真的很难看清标签和控制器,那么究竟发生了什么?

  这个情况事实上是这样的,因为你blurView使用的样式是UIBlurEffectStyle.Light,所以导致它是白色的。这样的话就不能产生意料之中的Vibrancy效果了。

  在viewDidLoad方法中把blurEffect的初始化改为下面这样:

  1
  let blurEffect = UIBlurEffect(style: .Dark)
  这样就改变而且增加了模糊视图与背景之间的颜色反差。

  构建并运行之后你就能看到一个称心如意的Vibrancy效果了。