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

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

今天你將以 Swift?Result
類型的形式處理一個(gè)棘手的概念,但為了平衡事情,我們還將涵蓋兩個(gè)更簡單的概念,希望你今天不會(huì)發(fā)現(xiàn)太多工作。
Swift 的Result
類型旨在解決當(dāng)你知道事情 A 可能為真或事情 B 可能為真時(shí)的問題,但在任何給定時(shí)間只有一個(gè)可以為真。如果你將它們想象成布爾屬性,那么每個(gè)屬性都有兩種狀態(tài)(真和假),但它們加起來有四種狀態(tài):
A是假的,B是假的
A為真,B為假
A為假,B為真
A為真,B為真
如果你確定選項(xiàng) 1 和 4 永遠(yuǎn)不可能——A 或 B 必須為真但它們不能都為真——那么你可以立即將邏輯的復(fù)雜性減半。
美國作家厄休拉·K·勒金曾說過:“唯一使生活成為可能的是永久的、無法忍受的不確定性;不知道接下來會(huì)發(fā)生什么。”?優(yōu)秀軟件的情況恰恰相反:我們可以執(zhí)行的確定性越強(qiáng),我們可以應(yīng)用的約束越多,我們的代碼就越安全,Swift 編譯器可以為我們做的工作就越多。
因此,盡管Result
需要你考慮將轉(zhuǎn)義閉包作為參數(shù)傳入,但回報(bào)更智能、更簡單、更安全——完全值得。
今天你要完成三個(gè)主題,你將在其中學(xué)習(xí)Result
、objectWillChange
和圖像插值。

手動(dòng)發(fā)布 ObservableObject 變化
符合ObservableObject
協(xié)議的類可以使用 SwiftUI 的@Published
屬性包裝器來自動(dòng)宣布對(duì)屬性的更改,以便使用該對(duì)象的任何視圖都可以重新調(diào)用其body
屬性并與其數(shù)據(jù)保持同步。這在很多時(shí)候都非常有效,但有時(shí)你想要更多的控制,SwiftUI 的解決方案被稱為objectWillChange
.
每個(gè)符合ObservableObject
的類都會(huì)
自動(dòng)獲得一個(gè)名為objectWillChange
.?這是一個(gè)publisher,這意味著它與屬性包裝器@Published
做同樣的工作
:它通知任何正在觀察該對(duì)象的視圖一些重要的東西已經(jīng)改變。顧名思義,應(yīng)該在我們進(jìn)行更改之前立即觸發(fā)此發(fā)布者,這允許 SwiftUI 檢查我們的 UI 狀態(tài)并為動(dòng)畫更改做準(zhǔn)備。
為了證明這一點(diǎn),我們將構(gòu)建一個(gè)自我更新 10 次的ObservableObject
類。我們將使用一個(gè)名為 DispatchQueue.main.asyncAfter()
的方法
,它允許我們?cè)谶x擇的延遲后運(yùn)行附加的閉包,這意味著我們可以說“1 秒后執(zhí)行此工作”而不是“現(xiàn)在執(zhí)行此工作”。
在此測(cè)試用例中,我們將在asyncAfter()
從 1 到 10 的循環(huán)中使用,因此我們將整數(shù)值遞增 10。該整數(shù)將被包裝使用@Published
以便將更改公告發(fā)送到正在觀看它的任何視圖。
在你的代碼中的某處添加此類:
要使用它,我們只需要在 ContentView
中添加一個(gè)
,然后在我們的主體中顯示該值,如下所示:@StateObject
屬性
當(dāng)你運(yùn)行該代碼時(shí),你會(huì)看到該值一直向上計(jì)數(shù),直到達(dá)到 10,這正是你所期望的。
現(xiàn)在,如果你刪除@Published
屬性包裝器,你將看到 UI 不再發(fā)生變化。在幕后,所有asyncAfter()
工作仍在進(jìn)行,但不會(huì)再導(dǎo)致 UI 刷新,因?yàn)椴粫?huì)發(fā)送任何更改通知。
我們可以通過使用我之前提到的objectWillChange
屬性手動(dòng)發(fā)送更改通知來解決這個(gè)問題。這讓我們可以隨時(shí)發(fā)送更改通知,而不是依賴@Published
自動(dòng)執(zhí)行。
嘗試將value
屬性更改為此:
現(xiàn)在你將再次獲得舊行為 - 用戶界面將像以前一樣計(jì)數(shù)到 10。除了這一次,我們有機(jī)會(huì)在該觀察者willSet
中添加額外的功能
。也許你想記錄一些東西,也許你想調(diào)用另一個(gè)方法,或者你想把整數(shù)限制在value
里面,
這樣它就不會(huì)超出范圍——現(xiàn)在一切都在我們的控制之下。



理解 Swift 的Result類型
Swift 提供了一種特殊類型Result
,它允許我們將成功的值或某種錯(cuò)誤類型封裝在一個(gè)單獨(dú)的數(shù)據(jù)片段中。因此,就像一個(gè)可選項(xiàng)可能包含一個(gè)字符串或可能什么都不包含一樣,例如,Result
可能包含一個(gè)字符串或可能包含一個(gè)錯(cuò)誤。起初使用它的語法有點(diǎn)奇怪,但它確實(shí)在我們的項(xiàng)目中扮演著重要的角色。
要查看Result
實(shí)際效果,我們可以從編寫一個(gè)方法開始,該方法從服務(wù)器下載一組數(shù)據(jù)讀數(shù),如下所示:
該代碼工作得很好,但它沒有給我們很大的靈活性——如果我們想把工作藏在某個(gè)地方并在它運(yùn)行時(shí)做其他事情怎么辦?如果我們想在未來的某個(gè)時(shí)候讀取它的結(jié)果,也許完全在其他地方處理任何錯(cuò)誤怎么辦?或者如果我們只是因?yàn)椴辉傩枰肴∠趺崔k?
好吧,我們可以通過使用 Result
獲得所有這些
,并且實(shí)際上可以通過你之前遇到過的 API 獲得它:Task
。我們可以將上面的代碼重寫為:
我們之前使用過Task
來啟動(dòng)工作,但在這里我們給Task
對(duì)象起了一個(gè)名字fetchTask
——這給了我們額外的靈活性來傳遞它,或者在需要時(shí)取消它。并注意我們的Task
閉包現(xiàn)在如何返回一個(gè)值?該值存儲(chǔ)在我們的Task
實(shí)例中,以便我們將來準(zhǔn)備好時(shí)可以讀取它。
更重要的是,Task
如果拋出網(wǎng)絡(luò)獲取失敗,或者數(shù)據(jù)解碼失敗,這可能會(huì)引發(fā)錯(cuò)誤,這就是Result
進(jìn)來的地方:我們?nèi)蝿?wù)的結(jié)果可能是一個(gè)字符串,上面寫著“找到 10000 個(gè)讀數(shù)”,但它也可能包含一個(gè)錯(cuò)誤。找出答案的唯一方法是查看內(nèi)部——它與可選項(xiàng)非常相似。
要從 一個(gè)Task
中讀取結(jié)果
,請(qǐng)像這樣讀取其result
屬性:
注意到我們沒有使用try
來
讀出Result
嗎?那是因?yàn)?strong>Result
將它保存在自身內(nèi)部——可能會(huì)拋出一個(gè)錯(cuò)誤,但除非我們?cè)敢?,否則我們現(xiàn)在不必?fù)?dān)心它。
如果你查看result
的類型,你會(huì)發(fā)現(xiàn)它是一個(gè)Result<String, Error>
– 如果它成功,它將包含一個(gè)字符串,但它也可能失敗并包含一個(gè)錯(cuò)誤。
你可以直接從 if 中讀取成功值Result
,但你需要確保并適當(dāng)?shù)靥幚礤e(cuò)誤,如下所示:
或者,你可以在switch
中用
Result
,
編寫代碼來檢查成功和失敗情況。這些情況中的每一個(gè)都有它們的值(成功的字符串和失敗的錯(cuò)誤),因此 Swift 讓我們使用特制的匹配來讀取這些值case
:
無論你如何處理它,Result
的優(yōu)點(diǎn)
是它可以讓我們將某些工作的全部成功或失敗存儲(chǔ)在一個(gè)值中,將其傳遞到我們需要的任何地方,并且僅在我們準(zhǔn)備好時(shí)才讀取錯(cuò)誤。



在 SwiftUI 中控制圖像插值
如果你創(chuàng)建一個(gè) SwiftUI?Image
視圖,將其內(nèi)容拉伸到大于其原始大小,會(huì)發(fā)生什么情況?默認(rèn)情況下,我們得到圖像插值,這是 iOS 如此平滑地混合像素的地方,你甚至可能根本沒有意識(shí)到它們已經(jīng)被拉伸了。這當(dāng)然會(huì)帶來性能成本,但大多數(shù)時(shí)候不值得擔(dān)心。
但是,圖像插值在一個(gè)地方會(huì)導(dǎo)致問題,那就是當(dāng)你處理精確像素時(shí)。例如,GitHub 上這個(gè)項(xiàng)目的文件包含一個(gè)名為 example@3x.png 的小卡通外星人圖像——它取自https://kenney.nl/assets/platformer-art-deluxe上的 Kenney Platform Art Deluxe 捆綁包和在公共領(lǐng)域下可用。
繼續(xù)將該圖形添加到你的資產(chǎn)目錄,然后將你的ContentView
結(jié)構(gòu)更改為:
這會(huì)在黑色背景下呈現(xiàn)外星人角色,使其更容易看到,并且由于它的大小可調(diào)整,SwiftUI 會(huì)將其拉伸以填充所有可用空間。
仔細(xì)觀察顏色的邊緣:它們看起來參差不齊,但也很模糊。鋸齒狀部分來自原始圖像,因?yàn)樗拇笮H為 66x92 像素,但模糊部分是 SwiftUI 在像素被拉伸時(shí)試圖混合像素以使拉伸不那么明顯的地方。
通常這種混合效果很好,但在這里很難,因?yàn)樵磮D片很?。ㄒ虼诵枰罅炕旌喜拍芤晕覀兿胍拇笮★@示),而且因?yàn)閳D像有很多純色所以混合像素很突出很明顯。
對(duì)于這種情況,SwiftUI 為我們提供了interpolation()
修改器,讓我們可以控制像素混合的應(yīng)用方式。這有多個(gè)層次,但實(shí)際上我們只關(guān)心一個(gè):.none
。這將完全關(guān)閉圖像插值,因此它們不會(huì)混合像素,而是會(huì)按比例放大并帶有銳利的邊緣。
因此,將你的圖像修改為:
現(xiàn)在你會(huì)看到外星人角色保留了它的像素化外觀,這不僅在復(fù)古游戲中特別受歡迎,而且對(duì)于線條藝術(shù)也很重要,因?yàn)榫€條藝術(shù)在模糊時(shí)看起來會(huì)很糟糕。


