這篇文章將介紹如何使用UIVisualEffectView
實現 iOS 系統中各種模糊效果(blur effect)、鮮豔度效果(vibrancy effect),以及模糊效果的原理。
自 iOS 7 設計風格改變後,模糊效果在 app 中越來越常見。合理運用可以改善 app 可用性和外觀。
iOS 系統中很多地方使用了模糊效果,最爲明顯的有控制中心、通知中心。控制中心的模糊顯示了當前活躍的app,而非控制中心自身。通知中心沒有給整個背景添加模糊,而是爲每條通知添加了模糊。這樣不僅看起來更美,也讓元素更爲突出。
使用UIVisualEffectView
可以實現上述模糊效果。
1. 模糊的原理
模糊起始於圖像。要實現模糊,需對每一像素執行模糊算法。最終,得到原始圖像的均勻模糊版本。模糊算法的樣式、複雜度各不相同,這裏介紹一種常見的模糊算法:高斯模糊(gaussian blur)。
高斯模糊將正態分佈用於圖像處理。本質上,它是一種數據平滑技術(data smoothing),適用於多個場合,圖像處理恰好提供了一個直觀的應用實例。
1.1 高斯模糊原理
模糊可以理解成每一個像素都取周邊像素的平均值。
上圖中,2是中間點,周邊都是1。中間點取周圍點的平均值,就會變成1。如下圖所示:
在數值上,這是一種平滑化。在圖像上,就相當於產生模糊效果,中間點失去細節。
計算平均值時,取值範圍越大,模糊效果越強烈。
上面分別是原圖、模糊半徑3像素、10像素的效果。模糊半徑越大,圖像就越模糊。從數值角度看,就是數值越平滑。
通常,模糊半徑越大,處理圖片所需資源越多。iOS 爲了避免主線程擁堵,會將盡可能多的任務移交給 GPU 處理。
既然每個點都要取周邊像素的平均值,那應如何分配權重呢?如果簡單平均,顯然不盡合理。因爲圖像都是連續的,越靠近的點關係越緊密;反之則疏遠。因此,加權平均更合理,距離越近的點權重越大,距離越遠的點權重越小。
正態分佈就是一種可取的權重分配模式。
正態分佈是一維的,圖像是二維的,因此我們需要二維的正態分佈。
正態分佈的密度函數叫做高斯函數,可以根據高斯函數計算每個點的權重,然後根據每個點的權重計算高斯模糊值。對所有點重複這個過程,就得到了高斯模糊後的圖像。如果原圖是彩色圖像,可以對RGB三個通道分別做高斯模糊。
2. 模糊效果
這裏使用WebKit瀏覽網頁,點擊皇冠時在底部彈出一個視圖,爲彈出的視圖添加模糊效果。這樣後續滑動 webview 時可以看到滾動的模糊效果。
2.1 UIBlurEffect
UIBlurEffect
繼承自UIVisualEffect
,導航欄、通知中心、控制中心的模糊效果都由UIBlurEffect
提供。
下面爲bottomView
添加模糊效果:
// 選用 dark 類型 blur
let blurEffect = UIBlurEffect(style: .dark)
// blur effect 需要添加到 UIVisualEffectView
let blurView = UIVisualEffectView(effect: blurEffect)
bottomView.addSubview(blurView)
blurView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
blurView.heightAnchor.constraint(equalTo: bottomView.heightAnchor),
blurView.widthAnchor.constraint(equalTo: bottomView.widthAnchor),
])
// 子視圖不能直接添加到blurView,只能添加到contentView。
blurView.contentView.addSubview(houseLabel)
houseLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
houseLabel.centerXAnchor.constraint(equalTo: blurView.centerXAnchor),
houseLabel.centerYAnchor.constraint(equalTo: blurView.centerYAnchor),
])
UIBlurEffect.Style
有以下這些類型:
- extraLight:視圖比底層視圖顏色淺。
- light:視圖與底層視圖亮度類似。
- dark:視圖比底層視圖顏色深。
- extraDark:視圖比底層視圖顏色更深。
- regular:自適應用戶界面。
- prominent:根據背景色自動使內容更加突出。
此外,iOS 13 中還添加了一些自適應淺色、深色的模糊效果,如systemUltraThinMaterialLight、systemChromeMaterialDark等。
UIVisualEffectView
繼承自UIView
,需將UIVisualEffectView
添加到要添加模糊效果的視圖,視圖層級中其它子視圖需添加到UIVisualEffectView
的contentView
中,直接添加到UIVisualEffectView
會導致閃退。
運行後效果如下:
2.2 UIVibrancyEffect
UIVibrancyEffect
繼承自UIVisualEffect
。UIVibrancyEffect
與UIVisualEffectView
組合使用,可以讓內容顏色更鮮豔。
下圖顯示了UIVibrancyEffect
讓文本、圖標更易識別:
使用以下代碼添加UIVibrancyEffect
效果:
// 使用之前的blurEffect創建UIVibrancyEffect
let vibrancyEffect = UIVibrancyEffect(blurEffect: blurEffect)
let vibrancyView = UIVisualEffectView(effect: vibrancyEffect)
// UIVibrancyEffect 必須添加到配置了UIBlurEffect的UIVisualEffectView的contentView上,否則不會有效果。
blurView.contentView.addSubview(vibrancyView)
vibrancyView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
vibrancyView.heightAnchor.constraint(equalTo: blurView.heightAnchor),
vibrancyView.widthAnchor.constraint(equalTo: blurView.widthAnchor),
vibrancyView.centerXAnchor.constraint(equalTo: blurView.centerXAnchor),
vibrancyView.centerYAnchor.constraint(equalTo: blurView.centerYAnchor),
])
// 將文本添加到 UIVibrancyEffect 視圖的contentView上。添加到vibrancyView上的所有控件都會具有 vibrancy effect。
vibrancyView.contentView.addSubview(houseLabel)
houseLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
houseLabel.centerXAnchor.constraint(equalTo: vibrancyView.centerXAnchor),
houseLabel.centerYAnchor.constraint(equalTo: vibrancyView.centerYAnchor),
])
將houseLabel
添加到vibrancyView
前,需先註釋掉將其添加到blurView
的代碼:
blurView.contentView.addSubview(houseLabel)
houseLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
houseLabel.centerXAnchor.constraint(equalTo: blurView.centerXAnchor),
houseLabel.centerYAnchor.constraint(equalTo: blurView.centerYAnchor),
])
運行後如下:
2.3 UIVisualEffectView
使用UIVisualEffectView
時,不能直接向其添加子視圖,子視圖需添加到其contentView
。
2.3.1 alpha
應避免設置alpha
小於1。半透明視圖會導致視圖、子視圖離屏渲染,進而影響性能。UIVisualEffectView
或其父視圖alpha小於1會導致模糊效果顯示錯誤,甚至沒有模糊效果。
2.3.2 mask
爲UIVisualEffectView
設置mask後,visual effect view 會將mask
轉發給它的內部視圖,包括contentView
。也可以直接爲contentView
添加 mask。爲UIVisualEffectView
的父視圖設置 mask 會導致特效失效,且會拋出異常。
2.3.3 截屏
UIVisualEffectView
的許多特效都需要包含該視圖的 window 支持。嘗試只截取UIVisualEffectView
會導致截屏不包含任何特效。截取包含UIVisualEffectView
特效的視圖層級時,必須截取包含特效的整個UIWindow
或UIScreen
。
3. 降低透明度
使用模糊效果時,需考慮用戶在系統設置中開啓了「降低透明度」時怎麼辦?
進入系統設置 > 輔助功能 > 顯示與文字大小 > 降低透明度,開啓降低透明度。返回app後,再次彈出底部視圖,效果如下:
開啓「降低透明度」後,模糊效果會消失。使用UIAccessibility.isReduceTransparencyEnabled
方法可以查看當前是否開啓了「降低透明度」。
在設置透明效果前添加以下代碼,即如果開啓了「降低透明度」,就不使用模糊效果。
// 系統開啓了「降低透明度」
guard !UIAccessibility.isReduceTransparencyEnabled else {
bottomView.backgroundColor = .lightGray
bottomView.addSubview(houseLabel)
houseLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
houseLabel.centerXAnchor.constraint(equalTo: bottomView.centerXAnchor),
houseLabel.centerYAnchor.constraint(equalTo: bottomView.centerYAnchor),
])
return
}
運行app後,開啓「降低透明度」後app也可以正常顯示了。
Demo名稱:VisualEffectView
源碼地址:https://github.com/pro648/BasicDemos-iOS/tree/master/VisualEffectView
參考資料:
歡迎更多指正:https://github.com/pro648/tips
本文地址:https://github.com/pro648/tips/blob/master/sources/高斯模糊原理、以及如何使用UIVisualEffectView實現模糊效果.md