天天看點

使用iOS8的虛化效果

在iOS 7中,一個重大的改變就是随處可見的虛化,這在通知中心和控制中心表現得尤為搶眼:

使用iOS8的虛化效果

然而,當開發者們着手去将類似的模糊效果加入自己的App的時候,他們會發現有相當嚴重的障礙。那時蘋果所界定的裝置可用範圍相當簡單,并不強大到足以支援在第三方應用中實作實時模糊。并聲稱開發者們很可能在App裡濫用虛化進而嚴重影響使用者體驗。

不過,精明又狡猾的程式員們很快的創造了自己基于模糊靜态圖檔方法來破解實時模糊的算法。

大部分解決方案都效果卓越。不過,之後的iOS 8在開發者工具箱中添加了官方的模糊效果,不僅相當高效,而且其使用的簡單程度讓人驚歎。

提示:想知道如何使用靜态模糊圖檔來模拟實時模糊的話可以參考這篇博文。

模糊化掃盲

想要使模糊效果顯得美觀而又高效需要一定技巧,在這一節你将會了解到最常見的模糊算法以及如何使用模糊效果來提升你App的使用者體驗。

怎麼做到模糊

模糊的對象是圖檔,想要實作模糊,你需要對圖檔中的每一個像素使用模糊算法,這樣會得到一個對原圖進行了均勻模糊後的圖檔。模糊算法可以在模糊的風格和模糊的複雜度上有很多變化,不過在這個教程裡你将會運用到一個最為常見而且頗為出名的算法——高斯模糊。

模糊算法通常會檢索圖檔的每一個像素點并基于它周圍的像素點來計算該像素在模糊後的灰階值。比如,我們想象一張如下所示網格圖:

使用iOS8的虛化效果
每一個小格子代表了一個獨立的像素,每個像素點有一個介于1和10之間的值。假設我們要對中心的像素點進行模糊化,那就需要計算四周八個像素中的值的算術平均數,并将這個數作為中心像素的值插入進去。結果如下圖:
使用iOS8的虛化效果

接着對原圖的每一個像素點都重複同樣的操作(編者按:原圖中每一個像素的新值應該插入到一張新圖檔相應位置的像素中去以免出現錯誤,原圖的像素值依舊不變,原作者并未提示這一點)。

上面的模糊例子僅僅用每個方向上的一個像素機關來進行計算新圖檔的像素值,你可以擴大模糊所要采用的像素半徑來提升圖檔的模糊效果,如下圖所示範的這樣:

使用iOS8的虛化效果

提示:一般說來,使用的模糊半徑越大則處理圖檔時候的計算量會越多。iOS會将大部分圖像處理工作交給GPU來處理以確定主線程不會被卡死。

關于模糊化的設計

人總是會不由自主的被那些對焦準确的部分而忽視掉被虛化的部分。不管你信不信,這是大自然的道理,因為人眼就是這麼工作的。眼球的對焦機制好像一個調節器一樣捕捉那些離你忽遠忽近的物體,這樣才能讓你感受到周圍一切事物的深度和距離。

App設計師實際上通過模糊掉那些無關緊要的内容來引導使用者的目光關注那些沒有被模糊掉的要素,比如時下流行的Twitter用戶端就是一個很好的示例:

使用iOS8的虛化效果

上圖中背景裡的使用者界面能夠勉強識别,因而為使用者提供了一個情景意識來讓他們知道正處于導航層中的哪個位置。在這個例子中使用者隻需要選擇一個賬戶登入,就可以退回到沒有被模糊的背景圖層裡去。

提示:雖然模糊能帶給人非常清新的視覺體驗,不過也切忌在你的App中過度使用,因為過度使用或者使用不當都會分散使用者的注意力或者惹惱使用者。

遵照标準的模糊設計方案來讓使用者關注到你想要給出的事物,這樣你就不容易弄糟。你可以在蘋果iOS開發者中心的iOS Human Interface Guidelines文檔中的Designing for iOS章節了解到更多内容。

開始

為了了解如何實作模糊,你需要嘗試在一款以新格林童話故事為藍本的App上添加合适的模糊效果,這款App叫做Grimm。

該應用為使用者提供了一系列的童話故事,當使用者點開某個童話時,它就會在螢幕上顯示完整的故事内容。使用者可以自定義顯示的字型、文本對齊,以及适用于日間或夜晚閱讀的顔色主題。

現在開始你需要下載下傳一個初始工程,在Xcode中打開Grimm.xcodeproj,然後打開Grimm.storyboard看一下App中的視圖控制器,像下面這樣:

使用iOS8的虛化效果

你可略過上圖中最前面的那個視圖控制器,因為它在App中隻不過是個簡單的底層導航控制器。你需要關注的是後面有編号的視圖控制器:

1.第一個控制器是StoryListController,是用于顯示資料庫中所有童話故事的清單。

2.當你點選一個童話故事時就會切換到這個視圖控制器StoryViewController,它會顯示選中童話的标題和文本内容。

3.最後的OptionsController是包含在StoryViewController中的,會列出一些可用的字型、對齊、顔色選項。隻需要在StoryViewController中輕擊設定圖示就能顯示它。

建構并運作,你就會看到如下所示的一個初始界面:

使用iOS8的虛化效果

你可以體驗一下這個應用,選好童話之後,點選省略号喚出選項視圖來切換不同的字型和閱讀模式,這樣可以了解使用者界面的基本功能。

提示:你可以在模拟器或者除了iPad 2之外的iOS 8裝置上運作這個應用。出于性能上的考慮蘋果限制了在iPad 2上顯示模糊效果,App本身的确能很好的運作在iPad 2上,隻不過你會看不到任何惬意的模糊效果而已。

手動模糊技巧

眼尖的同學可能會發現在這個工程裡面還殘留有Objective-C代碼。

使用iOS8的虛化效果

為此焦慮大可不必,這一段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變量:

使用iOS8的虛化效果

敲擊空格鍵來打開Quick Look,你應該會看到一張故事欄的非高清截圖。如下所示:

使用iOS8的虛化效果

請注意在截圖中并未包括UINavigationController中的任何元素,因為故事清單的視圖是作為UINavigationController的背景圖存在的,導航控制器則位于截圖的區域之外了。

現在你已經能截到一張正确的快照了。你可以使用我們之前提到的UIImage類來對你的截圖開始進行模糊化。

模糊掉你的快照

仍舊打開StoryViewController.swift檔案,找到你剛剛更改過的updateBlur方法,在最後一行UIGraphicsEndImageContext()的下面添加這行代碼:

let blur = screenshot.applyLightEffect()      
使用iOS8的虛化效果

提示:你可以在滾動槽裡面拖着斷點上下移動。

建構并運作,打開一則童話故事,點選導航器裡面的省略号,然後在調試欄裡面找到blur變量并使用空格打開Quick Look。

稍等……blur裡面好像什麼都沒有?去哪了?

你沒有看到任何東西是因為你的斷點恰好放在了blur變量設定的那一行,這樣Xcode會停在這一行執行之前的一步。

想要執行下圖中高亮的那一行你可以敲擊F6或者如圖中所示點選執行下一步:

使用iOS8的虛化效果

現在你可以展開blur變量了,選擇底下的那個some變量然後敲擊空格鍵喚出Quick Look檢視你模糊化後的圖檔:

使用iOS8的虛化效果

提示: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.p_w_picpath = blur
optionsContainerView.hidden = false      

因為blurView在Storyboard中并沒有被設定過,是以它會有一幀CGRectZero的圖檔,除非你有手動設定過。當然你也可以設定你剛剛模糊生成的那張圖檔的屬性。

這裡還要注意的是你在截圖之前曾經把optionsContainerView設定為不可見的隐藏狀态,一定要記得在虛化方法完成的最後将optionsContainerView設定為可見。

取消你之前設定的斷點,建構并運作,在選擇了一則童話之後點選設定選項,注意看着它範圍内的模糊效果,如下:

使用iOS8的虛化效果

這一個虛化看上去還是有點猥瑣,因為它好像跟後面的文本并不是很搭配?

在預設情況下,UIImageView會重置圖檔的大小以確定和視圖中的畫面适應,也就是說那張大一些的虛化圖檔已經被壓縮小了。是以就産生了這樣的效果。

為了修正這一錯誤,你需要把UIImageView的contentMode屬性改為除了預設的UIViewContentMode.ScaleToFill外的其它值。

在updateBlur中設定blurView那一行的下面貼上這些代碼:

blurView.contentMode = .Bottom      

UIViewContentMode.Bottom表示強制讓圖檔保持原有大小,而不是僅有隻有UIImageView原圖本身的中下部那麼大。

建構并運作,現在看看虛化的效果如何了?

使用iOS8的虛化效果

在你的靜态模糊準備拿去使用之前你還需要多考慮一個事,旋轉你的裝置或者虛拟機(command+左/右方向鍵),你可以看到視圖的大小并沒有被重置。

因為你的所有文本采用了自動布局,是以之前的截圖不再有用了,你需要在旋轉之後重新截圖快照并且更新一下blurView。

這個很簡單就可以實作,在StoryViewController.swift重寫一下下面這個方法:

override func viewWillTransitionToSize(size: CGSize,
  withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
  // animateAlongsideTransition方法可以使你旋轉螢幕的時候的變化更為動感并且在旋轉完成後作一些清理,你僅僅需要的是後者,因為你還需要截下optionsViewController旋轉之後的一幀圖。
  coordinator.animateAlongsideTransition(nil, completion: { context in
    // 在旋轉後更新一下blurView,這樣就會使用新的布局了
    self.updateBlur()
  })
}      

建構并運作之後試着改變一下裝置或者模拟器的角度,會發現有新的布局了:

使用iOS8的虛化效果

模糊範圍的大小正确無誤,不過還不夠。滑動後面的文本區你會發現虛化部分沒有發生任何改變。

根據上面的經驗你也應該知道該怎麼修改。而之後的iOS 8提供了動态生成虛化的工具。應用中采用實時模糊效果這一事從開發者們在iOS 7上開辟的解決方案以來那是說來話長了。

iOS 8上的模糊效果

iOS 8 提供了一套完整實用的虛化工具。UIVisualEffect的子類UIBlurEffect正是我們所感興趣的。UIBlurEffect提供了你在導航欄、通知中心和控制中心裡看到的那些漂亮的虛化,你也可以在你的App中使用這個效果。

添加UIBlurEffect

打開StoryViewController.swift檔案之後找到setOptionsHidden方法,如果你之前在第一個if條件分支裡面寫入過updateBlur,那就将它注釋掉。修改後如下:

使用iOS8的虛化效果

雖然你做完了,但是你不能完全保證blurview沒有被添加到場景中去,注釋掉下面這一行:

optionsContainerView.subviews[0].insertSubview(blurView, atIndex:0)      

提示:不要隻是簡單的删除掉那些代碼,你隻需注釋掉就好,這樣也便于你在回顧的時候發現有什麼不同。如果你對你手動添加的模糊代碼沒有任何想法,那你也可以删掉它們而非注釋。

建構并運作之後你會發現除了你的虛化不見了而外剩下的部分都能正常運作。

打開Grimm.storyboard然後找到Options Controller Scene,選擇view,展開Attributes Inspector然後更改view的background為Clear color,如下:

使用iOS8的虛化效果

打開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相适應。

建構并運作。打開童話故事點選省略号,然後滑動後面的文本,會發現虛化部分能夠實時變化了:

使用iOS8的虛化效果

現在你就擁有一個能夠動态渲染虛化的App了,不單隻是看上去好看,你還是采用了iOS核心功能實作的。

添加Vibrancy

虛化的效果相當棒——不過蘋果像以前一樣對其進行了提升。結合使用UIVibrancyEffect與UIVisualEffectView可以調整文本的顔色使得App看上去更加豔麗。

下面這張圖展示了Vibrancy在背景圖檔完全相同的情況下如何讓你的标簽和圖示在螢幕上顯得更為舒适:

使用iOS8的虛化效果

左邊的顯示的是通常情況下的标簽和按鈕,而右邊的顯示的是應用了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效果。

使用iOS8的虛化效果

除非你的眼睛也是高分屏的,不然真的很難看清标簽和控制器,那麼究竟發生了什麼?

這個情況事實上是這樣的,因為你blurView使用的樣式是UIBlurEffectStyle.Light,是以導緻它是白色的。這樣的話就不能産生意料之中的Vibrancy效果了。

在viewDidLoad方法中把blurEffect的初始化改為下面這樣:

let blurEffect = UIBlurEffect(style: .Dark)      

這樣就改變而且增加了模糊視圖與背景之間的顔色反差。

建構并運作之後你就能看到一個稱心如意的Vibrancy效果了。

使用iOS8的虛化效果

更多

你可以在這裡下載下傳到完成後的工程。

至此你已經知道如何手動模糊一張圖檔了,也學會了如何進行實時的模糊渲染,也會在你的App上簡單使用UIVisualEffectViews。

你所能使用的模糊技巧僅限于靜态圖檔,是以圖檔不會很生動而且不能實時的更新。不過使用UIBlurEffect卻可以進行實時更新,這樣你就可以借助這個效果做一些奇妙的事,比如說做動畫。

繼續閱讀