SwiftUI學習100天(Day86 - 項目 17,第一部分)

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

當 Apple 推出 iPhone X 時,他們放棄了 iPhone 早期就存在的東西:主頁按鈕。這個簡單的硬件自最初發(fā)布以來就一直存在,作為一種幫助用戶返回主屏幕的方式,無論他們在做什么和使用什么應用程序——它讓整個設備變得不那么可怕。
但隨著我們習慣于使用越來越大的玻璃面板,Apple 開始更加依賴手勢:我們獲得了手勢識別器、滑動以終止應用程序的能力、下拉和上拉系統(tǒng)功能的菜單等等。
但在 iPhone X 上,蘋果真的把事情提升到了一個新的水平,因為沒有主頁按鈕,幾乎所有的東西都變成了一個手勢。Apple 甚至在 WWDC18 上發(fā)表演講,鼓勵開發(fā)人員更多地思考手勢,Apple 人機界面設計團隊的 Chan Karunamuni 說了一些關于手勢非常重要的事情:“當感覺非常好時,有時人們甚至會說感覺很自然,或者很神奇”
你想構建感覺自然的應用程序嗎?你當然。我們正在構建的這個新應用程序將在很大程度上依賴于手勢,在使用它幾秒鐘后,你將以光速使用手勢。這正是我們的目標:感覺如此自然以至于你很難想象它們以其他方式工作的手勢。
今天你有四個主題需要完成,你將在其中學習手勢、觸覺、命中測試等。

Flashzilla:簡介
在這個項目中,我們將構建一個應用程序,幫助用戶使用抽認卡學習事物——卡片的一面寫著一個東西,比如“購買”,另一面寫著另一個東西,比如“comprar”。當然,這是一個數(shù)字應用程序,所以我們不需要擔心“另一面”,而是可以讓閃存卡的詳細信息在點擊時出現(xiàn)。
這個項目的名稱實際上是我的第一個 iOS 應用程序的名稱——我很久以前發(fā)布的一個應用程序是為 iPhoneOS 編寫的,因為 iPad 還沒有發(fā)布。Apple 在應用程序審查期間實際上拒絕了該應用程序,因為它的產品名稱中有“Flash”,而當時 Apple 真的很想在他們的 App Store 附近安裝 Flash!時代變了……
無論如何,我們在這個項目中有很多有趣的東西要學習,包括手勢、觸覺、計時器等等,所以請使用 App 模板創(chuàng)建一個新的 iOS 項目,并將其命名為 Flashzilla。與往常一樣,在我們開始構建真實的東西之前,我們有一些技術需要介紹,所以讓我們開始吧……

如何在 SwiftUI 中使用手勢
SwiftUI 為我們提供了很多處理視圖的手勢,并且很好地消除了大部分艱苦的工作,因此我們可以專注于重要的部分。最常見的是我們的朋友onTapGesture()
,但還有其他幾個,還有一些有趣的手勢組合方式值得一試。
我將跳過onTapGesture()
簡單的部分,
因為我們之前已經介紹過很多次,但在我們嘗試更大的事情之前,我想補充一點,你可以將一個count
參數(shù)傳遞給它們,讓它們處理雙擊、三次點擊,以及更多,像這樣:
好的,讓我們看看比簡單的點擊更有趣的東西。要處理長按,你可以使用onLongPressGesture()
,如下所示:
與點擊手勢一樣,長按手勢也可以自定義。例如,你可以指定按下的最短持續(xù)時間,這樣你的操作關閉只會在經過特定秒數(shù)后觸發(fā)。例如,這只會在兩秒后觸發(fā):
你甚至可以添加第二個閉包,只要手勢狀態(tài)發(fā)生變化就會觸發(fā)。這將被賦予一個布爾參數(shù)作為輸入,它將像這樣工作:
只要你按下 change 閉包,它的參數(shù)設置為 true 就會被調用。
如果你在手勢被識別之前釋放(因此,如果你在使用 2 秒識別器時在 1 秒后釋放),將調用更改閉包并將其參數(shù)設置為 false。
如果你按住識別器的整個長度,那么 change 閉包將被調用,其參數(shù)設置為 false(因為手勢不再處于飛行狀態(tài)),你的完成閉包也會被調用。
使用這樣的代碼親自嘗試一下:
對于更高級的手勢,可以在
gesture
結構體使用gesture()
修飾符:?DragGesture
、LongPressGesture
、MagnificationGesture
、RotationGesture
和TapGesture
。這些都有特殊的修飾符,onEnded()
和
onChanged()
也經常
如此,你可以使用它們在手勢處于飛行中(對于onChanged()
)或完成(對于onEnded()
)時采取行動。
例如,我們可以將放大手勢附加到視圖,以便放大和縮小視圖來放大和縮小視圖。這可以通過創(chuàng)建兩個@State
屬性來存儲縮放量,在scaleEffect()
修改器中使用它,然后在手勢中設置這些值來完成,如下所示:
使用 RotationGesture
旋轉視圖可以采用完全相同的方法
,除了現(xiàn)在我們使用rotationEffect()
修飾符
:
事情開始變得更有趣的地方是手勢沖突時——當你有兩個或更多手勢可能同時被識別時,例如,如果你將一個手勢附加到視圖,并將相同的手勢附加到其父項。
例如,這將一個附加onTapGesture()
到文本視圖及其父視圖:
在這種情況下,SwiftUI 將始終優(yōu)先考慮子視圖的手勢,這意味著當你點擊上面的文本視圖時,你會看到“Text taped”。但是,如果你想改變它,你可以使用修飾符highPriorityGesture()
來強制觸發(fā)父母的手勢,如下所示:
或者,你可以使用修飾符simultaneousGesture()
告訴 SwiftUI 你希望父手勢和子手勢同時觸發(fā),如下所示:
這將同時打印“Text tapped”和“VStack tapped”。
最后,SwiftUI 允許我們創(chuàng)建手勢序列,其中一個手勢只有在另一個手勢首先成功時才會激活。這需要更多思考,因為手勢需要能夠相互引用,所以你不能直接將它們附加到視圖。
這是一個顯示手勢序列的示例,你可以在其中拖動一個圓圈,但前提是你先長按它:
手勢是制作流暢、有趣的用戶界面的絕佳方式,但請確保向用戶展示手勢是如何工作的,否則他們只會感到困惑!



使用 UINotificationFeedbackGenerator 和 Core Haptics 產生振動
盡管 SwiftUI 沒有內置任何觸覺功能,但我們可以很容易地使用 UIKit 和 Core Haptics 添加觸覺功能——這兩個框架直接內置到系統(tǒng)中,并且在所有現(xiàn)代 iPhone 上都可用。如果你以前沒有聽說過這個術語,“觸覺”涉及設備中的小型電機,以產生輕敲和振動等感覺。
UIKit 有一個非常簡單的觸覺實現(xiàn),但這并不意味著你應該排除它:它可以很簡單,因為它專注于內置的觸覺,例如“成功”或“失敗”,這意味著用戶可以來學習某些事情的感覺。也就是說,如果你使用成功觸覺,那么一些用戶——尤其是那些更依賴觸覺的用戶,例如盲人用戶——將能夠知道操作的結果,而無需你的應用程序的任何進一步輸出。
要試用 UIKit 的觸覺,請將此方法添加到ContentView
:
你可以用一個簡單的onTapGesture()
來觸發(fā)它
,比如這個:
嘗試用.success
或者.error
替換.warning
,看看你是否能分辨出它們的區(qū)別——?我認為.success
和.warning
相似但不同。
對于更高級的觸覺,Apple 為我們提供了一個稱為 Core Haptics 的完整框架。這件事感覺像是背后的 Apple 團隊真正的愛心勞動,我認為它是 iOS 13 中引入的真正隱藏的寶石之一——當然,我一看到發(fā)行說明就撲向了它!
Core Haptics 讓我們可以通過組合輕擊、連續(xù)振動、參數(shù)曲線等來創(chuàng)建高度可定制的觸覺。我不想在這里深入探討,因為這有點偏離主題,但我至少想給你一個例子,這樣你就可以自己嘗試一下。
首先在 ContentView.swift 的頂部附近添加這個新導入:
接下來,我們需要創(chuàng)建一個CHHapticEngine
作為屬性的實例——這是負責創(chuàng)建振動的實際對象,因此我們需要在需要觸覺效果之前先創(chuàng)建它。
因此,將此屬性添加到ContentView
:
我們將在主視圖出現(xiàn)后立即創(chuàng)建它。創(chuàng)建引擎時,你可以附加處理程序以幫助在活動停止時恢復活動,例如當應用程序移至后臺時,但在這里我們將保持簡單:如果當前設備支持觸覺,我們將啟動引擎,如果失敗則打印錯誤。
將此方法添加到ContentView
:
現(xiàn)在是有趣的部分:我們可以配置參數(shù)來控制觸覺應該有多強 (?.hapticIntensity
) 以及它有多“銳利” (?.hapticSharpness
),然后將它們放入具有相對時間偏移的觸覺事件中?!颁J度”是一個奇怪的術語,但一旦你嘗試過它就會更有意義——銳度值為 0 與值為 1 相比確實感覺單調。至于相對時間,這讓我們可以創(chuàng)建很多單個序列中的觸覺事件。
現(xiàn)在將此方法添加到ContentView
:
要嘗試我們的自定義觸覺,請將 ContentView
的
為:body
屬性修改
這確保了觸覺系統(tǒng)已啟動,因此點擊手勢可以正常工作。
如果你想進一步試驗觸覺,請將let intensity
、let sharpness
和let event
行替換為你想要的任何觸覺。例如,如果你將這三行代碼替換為下一個代碼,你將獲得幾次先增加然后降低強度和清晰度的點擊:



使用 allowsHitTesting() 禁用用戶交互
SwiftUI 有一個先進的命中測試算法,它使用視圖的框架,通常也使用它的內容。例如,如果你將點擊手勢添加到文本視圖,那么文本視圖的所有部分都是可點擊的——如果你恰好按下了空格所在的位置,則無法點擊文本。另一方面,如果你將相同的手勢附加到一個圓上,那么 SwiftUI將忽略圓的透明部分。
為了證明這一點,下面是一個使用一個ZStack
與矩形重疊的圓
,兩者都帶有onTapGesture()
修飾符:
如果你嘗試這樣做,你會發(fā)現(xiàn)在圓圈內點擊會打印“Circle tapped”,但在圓圈后面的矩形上會打印“Rectangle tapped”——即使圓圈實際上與矩形具有相同的框架。
SwiftUI 讓我們以兩種有用的方式控制用戶交互,第一種是修飾符allowsHitTesting()
。當它附加到一個參數(shù)設置為 false 的視圖時,該視圖甚至不被認為是可點擊的。不過,這并不意味著它是惰性的,只是它不會捕捉到任何點擊——視圖后面的東西將被點擊。
嘗試像這樣將它添加到我們的圓圈中:
現(xiàn)在點擊圓圈將始終打印“Rectangle tapped!”,因為圓圈將拒絕響應點擊。
控制用戶交互性的另一種有用方法是使用修飾符contentShape()
,它讓我們可以為某些東西指定可點擊的形狀。默認情況下,圓的可點擊形狀是相同大小的圓,但你可以指定不同的形狀,如下所示:
contentShape()
修飾符真正有用的地方是當你點擊附加到帶有間隔符的堆棧的動作時,因為默認情況下,SwiftUI 不會在點擊堆棧間隔符時觸發(fā)動作。
這是你可以嘗試的示例:
如果你運行它,你會發(fā)現(xiàn)你可以點擊“Hello”標簽和“World”標簽,但不能點擊它們之間的空間。但是,如果我們contentShape(Rectangle())
在 VStack
上使用
,那么堆棧的整個區(qū)域都可以點擊,包括間隔:


