SwiftUI學習100天(Day44 - 項目 9,第二部分)

原創(chuàng)鏈接:https://www.hackingwithswift.com/100/swiftui
以下內容僅供學習參考:

今天,我們將通過一點點創(chuàng)意繼續(xù)我們對 SwiftUI 繪圖系統(tǒng)的了解——我想你會驚訝地發(fā)現(xiàn),只需將你所知道的大部分知識與一些新技術相結合,就可以如此輕松地制作出令人著迷的東西。
今天,你還將遇到drawingGroup()
修改器,它讓我們可以將視圖渲染結合到由 Apple 的高性能圖形 API Metal 提供支持的單一過程中。過去很多人問我是否打算寫一本關于 Metal 的書,答案是否定的——不僅已經(jīng)有一本非常好的書了,而且從 API 中獲得任何好的東西也非常困難.
那不是因為 Metal 不好——相信我,它太棒了!– 而是因為 Apple 最優(yōu)秀的工程師在使用 Metal 時努力使 SwiftUI 盡可能高效,而且,坦率地說,我不太可能做得更好。
你會發(fā)現(xiàn),切換 Metal 并不是你經(jīng)常需要的事情,盡管這很容易做到。著名的軟件開發(fā)人員 Kent Beck 曾經(jīng)說過,我們的流程應該是“讓它工作、讓它正確、讓它快速”,他說得很對。但是,如果你發(fā)現(xiàn)你的繪圖在不切換到 Metal 的情況下工作速度很快,那么通常最好保持原樣。
不管怎樣,聊夠了——我說我們會做一些令人著迷的事情,所以讓我們開始吧!
今天,你要完成三個主題,在這些主題中你將了解CGAffineTransform
、ImagePaint
、drawingGroup()
等等。

使用 CGAffineTransform 和奇偶填充變換形狀
當你超越簡單的形狀和路徑時,SwiftUI 的兩個有用功能將結合在一起,以非常少的工作創(chuàng)建一些美麗的效果。第一個是CGAffineTransform
,它描述了路徑或視圖應該如何旋轉、縮放或剪切;第二個是(even-odd fills)奇偶填充,它允許我們控制重疊形狀的渲染方式。
為了演示這兩者,我們將用幾個旋轉的橢圓花瓣創(chuàng)建一個花的形狀,每個橢圓圍繞一個圓定位。這背后的數(shù)學原理相對簡單,有一個問題:CGAffineTransform
用弧度而不是度數(shù)來測量角度。如果你上學已經(jīng)有一段時間了,你至少需要知道的是:3.141 弧度等于 180 度,所以 3.141 弧度乘以 2 等于 360 度。3.141 并非巧合:實際值是數(shù)學常數(shù) pi。
所以,我們要做的是:
創(chuàng)建一個新的空路徑。
從 0 數(shù)到 pi 乘以 2(以弧度表示的 360 度),每次以 pi 的八分之一計數(shù)——這將給我們 16 個花瓣。
創(chuàng)建等于當前數(shù)字的旋轉變換。
添加到該旋轉等于我們繪制空間寬度和高度的一半的運動,因此每個花瓣都以我們的形狀為中心。
為花瓣創(chuàng)建一條新路徑,等于特定大小的橢圓。
將我們的變換應用于該橢圓,使其移動到位。
將該花瓣的路徑添加到我們的主路徑中。
一旦你看到代碼正在運行,這將更有意義,但首先我想添加三件小事:
旋轉然后移動它不會產(chǎn)生與移動然后旋轉相同的結果,因為當你先旋轉它時它移動的方向將與它不旋轉時不同。
為了真正幫助你了解正在發(fā)生的事情,我們將使我們的花瓣橢圓使用一些我們可以從外部傳入的屬性。
如果你想一次數(shù)一個數(shù),那么范圍
1...5
非常好,但是如果你想以 2 秒為單位進行計數(shù),或者在我們的例子中以“pi/8”為單位進行計數(shù),則應該改用stride(from:to:by:)
。
好了,廢話不多說了——現(xiàn)在將這個形狀添加到你的項目中:
我意識到這是相當多的代碼,但希望當你嘗試它時它會變得更加清晰。將你的ContentView
修改
為:
現(xiàn)在試試看。一旦開始拖動偏移量和寬度滑塊,你應該能夠準確地看到代碼是如何工作的——它只是一系列旋轉的橢圓,以圓形形式放置。
這本身就很有趣,但是通過一個小的改變,我們可以從有趣變成崇高。如果你看一下我們畫橢圓的方式,它們經(jīng)常重疊——有時一個橢圓畫在另一個橢圓上,有時畫在其他幾個橢圓上。
如果我們使用純色填充我們的路徑,我們會得到一個相當不起眼的結果。試試這樣:
但作為替代方案,我們可以使用奇偶規(guī)則填充形狀,該規(guī)則決定是否應根據(jù)路徑包含的重疊部分對路徑的一部分進行著色。它是這樣工作的:
如果路徑?jīng)]有重疊,它將被填充。
如果另一條路徑與其重疊,則重疊部分將不會被填充。
如果第三條路徑與前兩條路徑重疊,則它將被填充。
…等等。
只有實際重疊的部分受此規(guī)則影響,它會產(chǎn)生一些非常漂亮的結果。更好的是,Swift UI 使它的使用變得簡單,因為每當我們在
一個形狀上調用fill()
時,我們都可以傳遞一個FillStyle
結構,要求啟用奇偶規(guī)則。
試試這個:
現(xiàn)在運行程序并開始游戲——老實說,考慮到我們所做的工作如此之少,結果非常令人著迷!



使用 ImagePaint 的創(chuàng)意邊框和填充
SwiftUI 嚴重依賴協(xié)議,這在處理繪圖時可能會有點混亂。例如,我們可以用Color
作視圖,但它也符合ShapeStyle?
- 用于填充、描邊和邊框的不同協(xié)議。
實際上,這意味著我們可以修改默認文本視圖,使其具有紅色背景:
或紅色邊框:
相反,我們可以使用圖像作為背景:
但是使用相同的圖像作為邊框是行不通的:
如果你考慮一下,這是有道理的——除非圖像的尺寸完全正確,否則你幾乎無法控制它的外觀。
為了解決這個問題,SwiftUI 為我們提供了一種專用類型,它以一種我們可以完全控制圖像呈現(xiàn)方式的方式包裝圖像,這反過來意味著我們可以毫無問題地將它們用于邊框和填充。
該類型稱為ImagePaint
,它是使用一到三個參數(shù)創(chuàng)建的。至少你需要給它一個Image
作為它的第一個參數(shù)的工作,但你也可以在該圖像中提供一個矩形以用作在 0 到 1 范圍內指定的繪圖源(第二個參數(shù)),以及該圖像的比例(第三個參數(shù))。第二個和第三個參數(shù)具有合理的默認值“整個圖像”和“100% 比例”,因此你有時可以忽略它們。
例如,我們可以使用 0.2 的比例渲染示例圖像,這意味著它以正常尺寸的 1/5 顯示:
如果你想嘗試使用sourceRect
參數(shù),請確保你傳入了CGRect
相對大小和位置的參數(shù):0 表示“開始”,1 表示“結束”。例如,這將顯示示例圖像的整個寬度,但只顯示中間的一半:
值得添加的是,ImagePaint
可用于視圖背景和形狀筆觸。例如,我們可以創(chuàng)建一個膠囊,我們的示例圖像平鋪為其筆畫:
ImagePaint
將自動繼續(xù)平鋪其圖像,直到填滿其區(qū)域——它可以處理任何大小的背景、筆觸、邊框和填充。



使用 drawingGroup() 啟用高性能 Metal 渲染
SwiftUI 默認使用 Core Animation 進行渲染,它提供了開箱即用的出色性能。然而,對于復雜的渲染,你可能會發(fā)現(xiàn)你的代碼開始變慢——任何低于每秒 60 幀 (FPS) 的東西都是一個問題,但實際上你應該瞄準更高的目標,因為許多 iOS 設備現(xiàn)在以 120fps 渲染。
為了證明這一點,讓我們看一些示例代碼。我們將創(chuàng)建一個顏色循環(huán)視圖,以一系列顏色呈現(xiàn)同心圓。結果看起來像徑向漸變,但我們將添加兩個屬性以使其更具可定制性:一個用于控制應繪制多少個圓圈,另一個用于控制顏色循環(huán)——它將能夠移動漸變開始和結束顏色。
我們可以通過使用Color(hue:saturation:brightness:)
初始化器獲得顏色循環(huán)效果
:色調是一個從 0 到 1 的值,控制著我們看到的顏色種類——紅色既是 0 又是 1,所有其他色調都介于兩者之間。為了計算出特定圓圈的色調,我們可以用圓圈數(shù)(例如 25)除以圓圈數(shù)(例如 100),然后加上我們的顏色循環(huán)量(例如 0.5)。所以,如果我們是 100 的第 25 圈,循環(huán)量為 0.5,我們的色調將為 0.75。
這里的一個小復雜性是色調在我們達到 1.0 后不會自動換行,這意味著 1.0 的色調等于 0.0 的色調,但 1.2 的色調不等于 0.2 的色調。因此,我們將手動包裝色調:如果它超過 1.0,我們將減去 1.0,以確保它始終位于 0.0 到 1.0 的范圍內。
這是代碼:
我們現(xiàn)在可以在布局中使用它,將其顏色循環(huán)綁定到由滑塊控制的本地屬性:
如果你運行該應用程序,你會看到我們有一個整潔的彩色波浪效果,完全通過在滑塊周圍拖動來控制,而且效果非常流暢。
你現(xiàn)在看到的是由 Core Animation 提供支持的,這意味著它將把我們的 100 個圓圈變成 100 個單獨的視圖繪制到屏幕上。這在計算上是昂貴的,但正如你所見,它運行良好——我們獲得了平穩(wěn)的性能。
然而,如果我們稍微增加復雜性,我們會發(fā)現(xiàn)事情并不那么樂觀。用這個strokeBorder()
替換現(xiàn)有的
修改器:
現(xiàn)在呈現(xiàn)出柔和的漸變,在圓圈頂部顯示明亮的顏色,在底部顯示較暗的顏色?,F(xiàn)在,當你運行該應用程序時,你會發(fā)現(xiàn)它的運行速度要慢得多——SwiftUI 正在努力渲染 100 個漸變作為 100 個獨立視圖的一部分。
我們可以通過應用一個名為drawingGroup()
.?這告訴 SwiftUI 它應該將視圖的內容渲染到屏幕外圖像中,然后再將其作為單個渲染輸出放回到屏幕上,這樣速度要快得多。在幕后,這是由 Metal 提供支持的,Metal 是 Apple 的框架,用于直接與 GPU 一起工作以獲得極快的圖形。
因此,將ColorCyclingCircle
正文修改為:
現(xiàn)在再次運行它 - 有了這個微小的添加,您現(xiàn)在會發(fā)現(xiàn)我們得到了正確渲染的所有內容,并且即使使用漸變,我們也可以全速返回。
重要提示:了解修飾符并保存在你的drawingGroup()
武器庫中有助于解決性能問題,但你不應該經(jīng)常使用它。添加離屏渲染通道可能會降低 SwiftUI 的簡單繪圖速度,因此你應該等到遇到實際性能問題后再嘗試引入drawingGroup()
.


