SwiftUI學(xué)習(xí)100天(Day87 - 項(xiàng)目 17,第二部分)

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

Cory House 曾經(jīng)說(shuō)過(guò),“代碼就像幽默。當(dāng)你不得不解釋它時(shí),這很糟糕?!?我之前已經(jīng)談到過(guò)類似的事情——需要編寫清晰的代碼來(lái)有效地傳達(dá)我們的意圖是良好編程的標(biāo)志,它將在未來(lái)節(jié)省許多維護(hù)和測(cè)試時(shí)間。
今天你將學(xué)習(xí)如何使用 Apple 的 Combine 框架監(jiān)控通知,你會(huì)發(fā)現(xiàn)代碼非常簡(jiǎn)單,幾乎不需要任何解釋——盡管它讓我們可以對(duì)系統(tǒng)事件進(jìn)行各種監(jiān)控。
這并非偶然發(fā)生:Apple 花費(fèi)大量時(shí)間進(jìn)行API 審查,這是跨職能的開發(fā)人員團(tuán)隊(duì)聚在一起討論我們所說(shuō)的API表面區(qū)域——它對(duì)我們最終用戶開發(fā)人員的看法根據(jù)它們采用的參數(shù)、返回的內(nèi)容、命名方式、是否拋出錯(cuò)誤以及它們?nèi)绾卧谏舷挛闹薪M合在一起。API 審查比你想象的要難,但最終結(jié)果是我們用非常少的 Swift 和 SwiftUI 代碼獲得了強(qiáng)大的功能,所以這對(duì)我們來(lái)說(shuō)是一個(gè)巨大的勝利!
今天你完成三個(gè)主題,你將在其中了解 Combine 框架、Timer
閱讀特定的輔助功能設(shè)置。

使用計(jì)時(shí)器重復(fù)觸發(fā)事件
iOS 帶有一個(gè)內(nèi)置Timer
類,可以讓我們定期運(yùn)行代碼。這使用了一個(gè)來(lái)自名為 Combine 的 Apple 框架的發(fā)布者系統(tǒng)。實(shí)際上,我們已經(jīng)在本系列的許多應(yīng)用程序中使用了 Combine 的某些部分,盡管你不太可能注意到它。例如,@Published
屬性包裝器和ObservableObject
協(xié)議都來(lái)自 Combine,但我們不需要知道這一點(diǎn),因?yàn)楫?dāng)你導(dǎo)入 SwiftUI 時(shí),我們也會(huì)隱式導(dǎo)入 Combine 的一部分。
Apple 的核心系統(tǒng)庫(kù)稱為 Foundation,它為我們提供了Data
,?Date
,?SortDescriptor
,UserDefaults
等等。它還為我們提供了Timer
類,該類旨在在一定秒數(shù)后運(yùn)行一個(gè)函數(shù),但它也可以重復(fù)運(yùn)行代碼。Combine 對(duì)此添加了一個(gè)擴(kuò)展,以便計(jì)時(shí)器可以成為發(fā)布者,這是當(dāng)它們的值發(fā)生變化時(shí)宣布的事情。這是@Published
屬性包裝器的名稱來(lái)源,計(jì)時(shí)器發(fā)布者的工作方式相同:達(dá)到你的時(shí)間間隔時(shí),Combine 將發(fā)出包含當(dāng)前日期和時(shí)間的公告。
創(chuàng)建計(jì)時(shí)器發(fā)布者的代碼如下所示:
這會(huì)同時(shí)做幾件事:
它要求計(jì)時(shí)器每 1 秒觸發(fā)一次。
它說(shuō)計(jì)時(shí)器應(yīng)該在主線程上運(yùn)行。
它說(shuō)計(jì)時(shí)器應(yīng)該在公共運(yùn)行循環(huán)上運(yùn)行,這是你大部分時(shí)間都想使用的循環(huán)。(運(yùn)行循環(huán)讓 iOS 在用戶主動(dòng)做某事時(shí)處理正在運(yùn)行的代碼,例如在列表中滾動(dòng)。)
它立即連接定時(shí)器,這意味著它將開始計(jì)時(shí)。
它將整個(gè)事物分配給常量
timer
,
以便它保持活力。
如果你還記得的話,在項(xiàng)目 7 中我說(shuō)過(guò)“@Published
或多或少是一半@State
”——它發(fā)送其他東西可以監(jiān)控的變更公告。對(duì)于像這樣的常規(guī)發(fā)布者,我們需要使用一種名為onReceive()
.?這接受一個(gè)發(fā)布者作為它的第一個(gè)參數(shù)和一個(gè)運(yùn)行的函數(shù)作為它的第二個(gè)參數(shù),并且它將確保每當(dāng)發(fā)布者發(fā)送其更改通知時(shí)調(diào)用該函數(shù)。
對(duì)于我們的計(jì)時(shí)器示例,我們可以像這樣接收它的通知:
這將每秒打印一次時(shí)間,直到計(jì)時(shí)器最終停止。
說(shuō)到停止計(jì)時(shí)器,需要一點(diǎn)點(diǎn)挖掘才能停止我們創(chuàng)建的計(jì)時(shí)器。你看,我們做的這個(gè)timer
屬性是一個(gè)autoconnected publisher(自動(dòng)連接的發(fā)布者),所以我們需要去它的upstream publisher(上游發(fā)布者)去尋找timer定時(shí)器本身。從那里我們可以連接到計(jì)時(shí)器發(fā)布者,并要求它取消自己。老實(shí)說(shuō),如果不是為了代碼完成,這將很難找到,但這是它在代碼中的樣子:
例如,我們可以更新現(xiàn)有示例,使其僅觸發(fā)計(jì)時(shí)器五次,如下所示:
在我們完成之前,我想向你展示一個(gè)更重要的計(jì)時(shí)器概念:如果你不介意你的計(jì)時(shí)器有一點(diǎn)浮動(dòng),你可以指定一些容差。這允許 iOS 執(zhí)行重要的能量?jī)?yōu)化,因?yàn)樗梢栽谄溆?jì)劃的觸發(fā)時(shí)間和其計(jì)劃的觸發(fā)時(shí)間加上你指定的容差之間的任何時(shí)間點(diǎn)觸發(fā)計(jì)時(shí)器。在實(shí)踐中,這意味著系統(tǒng)可以執(zhí)行計(jì)時(shí)器合并:它可以將你的計(jì)時(shí)器推遲一點(diǎn),以便它與一個(gè)或多個(gè)其他計(jì)時(shí)器同時(shí)觸發(fā),這意味著它可以使 CPU 保持空閑狀態(tài)并節(jié)省電池電量。
例如,這為我們的計(jì)時(shí)器增加了半秒的容差:
如果你需要嚴(yán)格計(jì)時(shí),則不使用該tolerance
參數(shù)將使你的計(jì)時(shí)器盡可能準(zhǔn)確,但請(qǐng)注意,即使沒(méi)有任何容差,該類Timer
仍然是“盡力而為”——系統(tǒng)不保證它會(huì)準(zhǔn)確執(zhí)行。



當(dāng)你的 SwiftUI 應(yīng)用移至后臺(tái)時(shí)如何收到提示
SwiftUI 可以檢測(cè)你的應(yīng)用程序何時(shí)移至后臺(tái)(即,用戶何時(shí)返回主屏幕),以及何時(shí)返回前臺(tái),如果你將這兩者放在一起,我們就可以確保我們的應(yīng)用程序暫停和恢復(fù)工作取決于用戶是否可以立即看到它。
這是通過(guò)三個(gè)步驟完成的:
添加一個(gè)新屬性來(lái)監(jiān)視名為
scenePhase
的環(huán)境值
。用于
onChange()
觀察場(chǎng)景相位變化。以某種方式響應(yīng)新場(chǎng)景階段。
你可能想知道為什么它被稱為場(chǎng)景階段而不是與你當(dāng)前的應(yīng)用程序狀態(tài)有關(guān),但請(qǐng)記住,在 iPad 上用戶可以同時(shí)運(yùn)行你的應(yīng)用程序的多個(gè)實(shí)例 - 他們可以有多個(gè)窗口,稱為場(chǎng)景,每個(gè)處于不同的狀態(tài)。
要查看實(shí)際的各個(gè)場(chǎng)景階段,請(qǐng)嘗試以下代碼:
當(dāng)你返回時(shí),嘗試轉(zhuǎn)到模擬器的主屏幕、鎖定虛擬設(shè)備和其他常見活動(dòng),以查看場(chǎng)景相位如何變化。
可以看到,我們需要關(guān)心三個(gè)場(chǎng)景階段:
活動(dòng)場(chǎng)景正在運(yùn)行,這在 iOS 上意味著它們對(duì)用戶可見。在 macOS 上,一個(gè)應(yīng)用程序的窗口可能會(huì)被另一個(gè)應(yīng)用程序的窗口完全隱藏,但這沒(méi)關(guān)系——它仍然被認(rèn)為是活動(dòng)的。
非活動(dòng)場(chǎng)景正在運(yùn)行并且可能對(duì)用戶可見,但用戶無(wú)法訪問(wèn)它們。例如,如果你向下滑動(dòng)以部分顯示控制中心,那么下方的應(yīng)用程序?qū)⒈灰暈樘幱诜腔顒?dòng)狀態(tài)。
背景場(chǎng)景對(duì)用戶不可見,這在 iOS 上意味著它們可能會(huì)在將來(lái)的某個(gè)時(shí)候終止。



使用 SwiftUI 支持特定的可訪問(wèn)性需求
SwiftUI 為我們提供了許多描述用戶自定義輔助功能設(shè)置的環(huán)境屬性,值得花時(shí)間閱讀并遵守這些設(shè)置。
回到項(xiàng)目 15,我們查看了可訪問(wèn)性標(biāo)簽和提示、特征、組等,但這些設(shè)置是不同的,因?yàn)樗鼈兪峭ㄟ^(guò)環(huán)境提供的。這意味著 SwiftUI 會(huì)自動(dòng)監(jiān)視它們的變化,并會(huì)body
在其中一個(gè)發(fā)生變化時(shí)重新調(diào)用我們的屬性。
例如,輔助功能選項(xiàng)之一是“不區(qū)分顏色”,這對(duì) 12 分之一的色盲男性很有幫助。啟用此設(shè)置后,應(yīng)用程序應(yīng)嘗試使用形狀、圖標(biāo)和紋理而不是顏色來(lái)使其 UI 更清晰。
要使用它,只需添加一個(gè)像這樣的環(huán)境屬性:
這將是真或假,你可以相應(yīng)地調(diào)整你的 UI。例如,在下面的代碼中,我們?yōu)槌R?guī)布局使用簡(jiǎn)單的綠色背景,但是當(dāng)啟用“無(wú)色區(qū)分”時(shí),我們使用黑色背景并添加一個(gè)復(fù)選標(biāo)記:
你可以在模擬器中進(jìn)行測(cè)試,方法是轉(zhuǎn)到“設(shè)置”應(yīng)用并選擇“輔助功能”>“顯示和文本大小”>“無(wú)色區(qū)分”。
另一個(gè)常見選項(xiàng)是 Reduce Motion,它在模擬器中的 Accessibility > Motion > Reduce Motion 下再次可用。啟用此功能后,應(yīng)用程序應(yīng)限制導(dǎo)致屏幕移動(dòng)的動(dòng)畫量。例如,iOS 應(yīng)用程序切換器使視圖淡入和淡出,而不是按比例放大和縮小。
withAnimation()
對(duì)于 SwiftUI,這意味著我們應(yīng)該在涉及移動(dòng)時(shí)限制使用,如下所示:
我不了解你,但我發(fā)現(xiàn)它使用起來(lái)很煩人。幸運(yùn)的是,我們可以添加一個(gè)withAnimation()
直接使用 UIKit數(shù)據(jù)的包裝函數(shù)UIAccessibility
,讓我們自動(dòng)繞過(guò)動(dòng)畫:
因此,當(dāng) Reduce Motion Enabled 為真時(shí),傳入的閉包代碼會(huì)立即執(zhí)行,否則會(huì)使用withAnimation()
傳遞
.?整個(gè)throws
/rethrows
過(guò)程是更高級(jí)的 Swift 操作,但它是withAnimation()
函數(shù)簽名的直接副本,因此
兩者可以互換使用。
像這樣使用它:
使用這種方法,你不需要每次都重復(fù)你的動(dòng)畫代碼。
你應(yīng)該考慮支持的最后一個(gè)選項(xiàng)是減少透明度,當(dāng)啟用該選項(xiàng)時(shí),應(yīng)用程序應(yīng)該減少其設(shè)計(jì)中使用的模糊和半透明的數(shù)量,以加倍確保一切都清晰。
例如,此代碼在啟用“減少透明度”時(shí)使用純黑色背景,否則使用 50% 的透明度:
這是我希望你在構(gòu)建真實(shí)項(xiàng)目之前學(xué)習(xí)的最后一項(xiàng)技術(shù),因此請(qǐng)將你的項(xiàng)目重置回其原始狀態(tài),以便我們可以從頭開始。


