SwiftUI學(xué)習(xí)100天(Day96 - 項(xiàng)目 19,第一部分)

盡管到目前為止我們所有的項(xiàng)目都在 iPad 上能夠運(yùn)行,但我們并沒有真正花時(shí)間停下來深入研究它。好吧,這在我們的新項(xiàng)目中發(fā)生了變化,因?yàn)槲覀儗?gòu)建一個(gè)應(yīng)用程序,利用 iPad 提供的所有額外空間,甚至利用 Max-sized iPhone 橫向提供的空間。
盡管 Apple 在 2019 年將 iOS 分叉為 iPadOS,但 iPad 和 iPhone 在軟件方面幾乎完全相同。這意味著我們可以編寫同時(shí)在兩個(gè)平臺(tái)上運(yùn)行的代碼,只需進(jìn)行少量更改即可真正充分利用每個(gè)環(huán)境。
史蒂夫·喬布斯 (Steve Jobs) 在 2010 年推出第一款 iPad 時(shí)說,“因?yàn)槲覀円呀?jīng)出貨了超過 7500 萬部 iPhone,所以已經(jīng)有 7500 萬用戶知道如何使用 iPad?!?這意味著用戶也可以從兩個(gè)平臺(tái)的相似性中受益,因?yàn)樗麄兛梢粤⒓粗廊绾卧?iPad 上使用我們的應(yīng)用程序,這要?dú)w功于 iPhone 上的現(xiàn)有用法。
盡管自定義用戶界面看起來和感覺起來都很棒,但永遠(yuǎn)不要低估這種內(nèi)置知識(shí)的力量!
今天你有四個(gè)主題要完成,在這些主題中你將學(xué)習(xí)拆分視圖控制器、將彈窗綁定到可選類型以及使用組進(jìn)行靈活布局。

SnowSeeker:簡介
在這個(gè)項(xiàng)目中,我們將創(chuàng)建 SnowSeeker:一款讓用戶瀏覽世界各地滑雪勝地的應(yīng)用程序,幫助他們找到適合下一個(gè)假期的滑雪勝地。
這將是我們專門致力于通過并排顯示兩個(gè)視圖來制作在 iPad 上運(yùn)行良好的應(yīng)用程序的第一款應(yīng)用程序,但你還將深入解決有問題的布局,學(xué)習(xí)一種顯示工作表和彈窗的新方法等等.
與往常一樣,在進(jìn)入主項(xiàng)目之前我們需要介紹一些技術(shù),因此請(qǐng)使用 App 模板創(chuàng)建一個(gè)新的 iOS 項(xiàng)目,將其命名為 SnowSeeker。
我們走!



在 SwiftUI 中使用兩個(gè)并排視圖
你可能沒有意識(shí)到,但我們的應(yīng)用程序適應(yīng)不同屏幕尺寸的最聰明、最簡單的方法之一實(shí)際上已經(jīng)融入到NavigationView
.
你已經(jīng)熟悉 NavigationView
的基本用法
,這使我們能夠創(chuàng)建這樣的視圖:
但是,你在運(yùn)行時(shí)看到的內(nèi)容取決于幾個(gè)因素。如果你正在縱向使用 iPhone,那么你會(huì)看到你所期望的布局:頂部有一個(gè)大的“主要”標(biāo)題,然后是一個(gè)小的“Hello,world!”?以下方空間為中心。
如果你將同一部手機(jī)旋轉(zhuǎn)到橫向,那么你會(huì)看到以下兩種情況之一:在大多數(shù) iPhone 上,導(dǎo)航標(biāo)題會(huì)縮小為小文本,以便占用更少的空間,但在最大尺寸的 iPhone 上,例如在 iPhone 13 Pro Max 上,你會(huì)看到我們的標(biāo)題在左上角變成了一個(gè)藍(lán)色按鈕,使屏幕的其余部分保持清晰。點(diǎn)擊那個(gè)按鈕就會(huì)出現(xiàn)“你好,世界!”?文本從前緣滑入,你還會(huì)在頂部看到“主要”標(biāo)題。
在 iPad 上,事情變得更加聰明,因?yàn)橄到y(tǒng)會(huì)根據(jù)設(shè)備的大小和可用的屏幕空間從三種不同的布局中進(jìn)行選擇。例如,如果我們橫向使用 12.9 英寸的 iPad Pro,那么:
如果我們的應(yīng)用程序擁有整個(gè)屏幕,你將看到“Hello, world!”?視圖在左側(cè)可見,右側(cè)沒有任何內(nèi)容。
如果應(yīng)用程序的空間非常小,它看起來就像一個(gè)縱向的長 iPhone。
對(duì)于其他大小,你可能會(huì)看到“主要”按鈕可見,這會(huì)導(dǎo)致“Hello, world!”?按下時(shí)滑入的文本。
你在這里看到的稱為自適應(yīng)布局,它用于 Apple 的許多應(yīng)用程序,例如 Notes、Mail 等。它允許 iOS 充分利用可用的屏幕空間,而無需我們參與。
這里實(shí)際發(fā)生的是 iOS 給了我們一個(gè)主要/次要布局:一個(gè)主要視圖充當(dāng)導(dǎo)航,例如從我們讀過的書籍列表或阿波羅任務(wù)列表中選擇,以及一個(gè)次要視圖作為導(dǎo)航更多信息,例如有關(guān)在主視圖中選擇的一本書或阿波羅任務(wù)的更多詳細(xì)信息。
在我們簡單的代碼示例中,SwiftUI 將NavigationView內(nèi)部的單個(gè)視圖解釋
為此主要/次要布局中的主要視圖。但是,如果我們確實(shí)提供了兩個(gè)視圖,那么我們會(huì)立即獲得一些非常有用的行為。嘗試將你的視圖更改為:
當(dāng)你啟動(dòng)該應(yīng)用程序時(shí),你再次看到的內(nèi)容取決于你的設(shè)備和方向,但在 Max-sized 手機(jī)和 iPad 上,你會(huì)看到“Secondary”,而 Primary 工具欄按鈕會(huì)顯示“Hello, world!”?看法。
SwiftUI 自動(dòng)鏈接主視圖和輔助視圖,這意味著如果你在主視圖中有一個(gè)NavigationLink
,它將自動(dòng)在輔助視圖中加載其內(nèi)容:
然而,至少現(xiàn)在,所有這些魔法都有一些缺點(diǎn),我希望在未來的 SwiftUI 更新中得到修復(fù):
無論你是否想要,詳細(xì)視圖總是會(huì)得到一個(gè)導(dǎo)航欄,因此你需要使用
navigationBarHidden(true)
來隱藏它。即使空間足夠大,也無法使主視圖保持可見。
默認(rèn)情況下,你不能使主視圖顯示為橫向;SwiftUI 總是選擇細(xì)節(jié)。
提示:你甚至可以向 NavigationView
中添加第三個(gè)視圖
,這樣你就可以創(chuàng)建邊欄。你會(huì)在 Notes 等應(yīng)用程序中看到這些內(nèi)容,你可以在其中從筆記列表向上導(dǎo)航以瀏覽筆記文件夾。因此,第一個(gè)視圖中的導(dǎo)航鏈接控制第二個(gè)視圖,第二個(gè)視圖中的導(dǎo)航鏈接控制第三個(gè)視圖——當(dāng)你需要時(shí),這是一個(gè)額外的組織級(jí)別。



將 alert() 和 sheet() 與可選項(xiàng)一起使用
SwiftUI 有兩種創(chuàng)建彈窗和工作表的方法,到目前為止我們主要只使用了一種:綁定到布爾值,當(dāng)布爾值變?yōu)檎鏁r(shí)顯示彈窗或工作表。
第二個(gè)選項(xiàng)允許我們將一個(gè)可選項(xiàng)綁定到彈窗或工作表,我們在展示地圖圖釘時(shí)簡單地使用了它。如果你還記得的話,關(guān)鍵是我們使用一個(gè)可選項(xiàng)Identifiable
對(duì)象作為顯示工作表的條件,閉包會(huì)為你提供用于該條件的非可選值,因此你可以安全地使用它。
為了證明這一點(diǎn),我們可以創(chuàng)建一個(gè)User
符合Identifiable
協(xié)議的簡單結(jié)構(gòu):
然后我們可以在ContentView
中創(chuàng)建一個(gè)屬性
來跟蹤選擇了哪個(gè)用戶,nil
默認(rèn)設(shè)置為:
現(xiàn)在我們可以更改ContentView
中的
body
,使其selectedUser
在點(diǎn)擊其文本視圖時(shí)設(shè)置為一個(gè)值,然后在selectedUser
給定一個(gè)值時(shí)顯示一個(gè)工作表:
使用這個(gè)簡單的代碼,每當(dāng)你點(diǎn)擊“Hello, World!”?一張寫著“Taylor Swift”的表格出現(xiàn)了。關(guān)閉工作表后,SwiftUI 會(huì)立即設(shè)置selectedUser
回nil
.
這可能看起來像是一個(gè)簡單的功能,但它比替代方案更簡單、更安全。如果我們使用 .sheet(isPresented:)
修飾符重寫上面的代碼,
它將如下所示:
這是另一個(gè)屬性,另一個(gè)要在 onTapGesture()
中設(shè)置的值
,以及在sheet()
修飾符中傳遞可選的額外代碼
——如果你能避免那些你應(yīng)該避免的事情。
彈窗具有類似的功能,盡管你需要同時(shí)傳遞布爾值和可選值Identifiable
。這使你可以在需要時(shí)顯示彈窗,而且還可以從我們對(duì)工作表使用的相同可選展開行為中受益:
有了這些,你現(xiàn)在幾乎了解了有關(guān)工作表和彈窗的所有知識(shí),但是我想偷偷做最后一件事來完善你的知識(shí)。
你看,到目前為止,我們已經(jīng)寫了很多像這樣的彈窗:
那個(gè) OK 按鈕之所以有效,是因?yàn)樗胁僮髟谒鼈儽稽c(diǎn)擊時(shí)都會(huì)關(guān)閉它們所屬的彈窗,到目前為止我們一直在使用這種方法,因?yàn)樗梢宰屇憔毩?xí)創(chuàng)建彈窗和按鈕。
但是,我想向你展示一個(gè)簡潔的快捷方式。試試這個(gè)代碼:
當(dāng)它運(yùn)行時(shí),你會(huì)看到一些有趣的東西:盡管沒有專用的 OK 按鈕,但結(jié)果與以前完全相同。SwiftUI 發(fā)現(xiàn)我們在彈窗中沒有任何操作,因此它為我們添加了一個(gè)標(biāo)題為“確定”的默認(rèn)操作,并在點(diǎn)擊時(shí)關(guān)閉彈窗。
顯然,這在你需要其他按鈕以及 OK 的情況下不起作用,但對(duì)于簡單的彈窗來說,它是完美的。



使用組作為透明布局容器
SwiftUI 的Group
視圖通常用于解決 10 個(gè)子視圖的限制,但它還有另一個(gè)重要的行為:它充當(dāng)透明布局容器。這意味著該組實(shí)際上根本不會(huì)影響我們的布局,但仍然使我們能夠根據(jù)需要添加 SwiftUI 修飾符,或者不使用@ViewBuilder
返回多個(gè)視圖。
例如,這個(gè)UserView
有一個(gè)Group
包含三個(gè)文本視圖:
該組不包含任何布局信息,因此我們不知道這三個(gè)文本字段是垂直堆疊、水平堆疊還是按深度堆疊。這就是 Group
的透明布局行為
變得重要的地方:無論父級(jí)放置什么,UserView
都
可以決定其文本視圖的排列方式。
例如,我們可以創(chuàng)建一個(gè)這樣的ContentView
:
每次點(diǎn)擊該組時(shí),它都會(huì)在垂直和水平布局之間翻轉(zhuǎn),你會(huì)再次看到,使用Group
讓我們可以立即將點(diǎn)擊手勢附加到所有內(nèi)容。
你可能想知道你需要多久使用一次這樣的替代布局,但答案可能會(huì)讓你感到驚訝:這真的很常見!你看,這正是你在嘗試編寫適用于多種設(shè)備尺寸的代碼時(shí)想要發(fā)生的事情——如果我們希望在水平空間受限時(shí)進(jìn)行垂直布局,否則水平布局。Apple 提供了一個(gè)非常簡單的解決方案,稱為size classes,這是一種非常模糊的方式來告訴我們我們的視圖有多少空間。
當(dāng)我說“完全模糊”時(shí),我的意思是:我們只有水平和垂直兩個(gè)尺寸類別,稱為“緊湊型”和“常規(guī)型”。就是這樣——涵蓋所有屏幕尺寸,從最大的橫向 iPad Pro 到最小的縱向 iPhone。這并不意味著它沒有用——遠(yuǎn)非如此!– 只是它只能讓我們以最廣泛的方式推理我們的用戶界面。
為了演示實(shí)際的尺寸等級(jí),我們可以創(chuàng)建一個(gè)視圖,該視圖具有一個(gè)屬性來跟蹤當(dāng)前尺寸等級(jí),以便我們可以在VStack
和 HStack
之間
自動(dòng)切換:
提示:在這種情況下,堆棧中只有一個(gè)視圖并且它不接受任何參數(shù),你可以將視圖的初始化程序直接傳遞給 VStack
以
縮短代碼:
我知道短代碼不是一切,但是當(dāng)你使用這種方法進(jìn)行分組視圖布局時(shí),這種技術(shù)非常簡潔。
該代碼運(yùn)行時(shí)你看到的內(nèi)容取決于你使用的設(shè)備。例如,iPhone 13 Pro 在縱向和橫向上都有一個(gè)緊湊的水平尺寸類別,而 iPhone 13 Pro Max在橫向上有一個(gè)常規(guī)的水平尺寸類別。
不管我們是使用大小類還是點(diǎn)擊手勢來切換我們的布局,關(guān)鍵是UserView
并不關(guān)心——Group
只是將文本視圖組合在一起而根本不影響它們的布局,所以UserView布局安排完全
取決于如何它的使用方式了。



使 SwiftUI 視圖可搜索
iOS 可以使用修飾符向我們的視圖添加一個(gè)搜索欄searchable()
,我們可以將一個(gè)字符串屬性綁定到它以在用戶輸入時(shí)過濾我們的數(shù)據(jù)。
要了解這是如何工作的,請(qǐng)嘗試這個(gè)簡單的示例:
重要提示:你需要確保你的視圖在 NavigationView
中
,否則 iOS 將無處放置搜索框。
當(dāng)你運(yùn)行該代碼示例時(shí),你應(yīng)該會(huì)看到一個(gè)可以輸入的搜索欄,你輸入的任何內(nèi)容都會(huì)顯示在下面的視圖中。
在實(shí)踐中,searchable()
最好與某種數(shù)據(jù)過濾一起使用。請(qǐng)記住,SwiftUI 會(huì)在屬性更改時(shí)重新調(diào)用你的 body 屬性@State
,因此你可以使用計(jì)算屬性來處理實(shí)際的過濾:
當(dāng)你運(yùn)行它時(shí),iOS 會(huì)自動(dòng)將搜索欄隱藏在列表的最頂部——你需要輕輕向下拉才能顯示它,這與其他 iOS 應(yīng)用程序的工作方式相匹配。iOS 不要求我們讓我們的列表可搜索,但它確實(shí)對(duì)用戶產(chǎn)生了巨大的影響!
提示:與其在這里使用contains()
,不如使用另一個(gè)名稱更長的方法:localizedCaseInsensitiveContains()
。這讓我們可以檢查搜索字符串的任何部分,而不用擔(dān)心大寫或小寫字母。


