Swift及SwiftUI學(xué)習(xí)筆記

持續(xù)更新中......

swift官方文檔


swift官方文檔(英文)

協(xié)議


swift主要基于協(xié)議編程的,所以協(xié)議無(wú)處不在。
先看看這篇文章:Swift學(xué)習(xí):協(xié)議

官方的一些常用協(xié)議:
1. Equtable
在Swift中可以通過(guò)實(shí)現(xiàn)Equatable協(xié)議使自定義類(lèi)型支持==以及!=這兩種運(yùn)算符。

2. Comparable
Comparable協(xié)議繼承于Equatable,實(shí)現(xiàn)Comparable協(xié)議可以在Equatable的基礎(chǔ)上使類(lèi)型支持>,>=,<,<=四種運(yùn)算符。

3. CustomStringConvertible
當(dāng)類(lèi)實(shí)現(xiàn)這個(gè)協(xié)議,可使用print打印類(lèi)的自定義信息。

4. CustomDebugStringConvertible
當(dāng)類(lèi)實(shí)現(xiàn)這個(gè)協(xié)議,可使用debugPrint打印類(lèi)的自定義信息。

5. Hashable
繼承于Equatable。
一個(gè)類(lèi)型為了存儲(chǔ)在集合中,該類(lèi)型必須是可哈希化的-該類(lèi)型必須提供一種方法計(jì)算它的哈希值,一個(gè)哈希值為Int類(lèi)型,相等的對(duì)象哈希值必須相同。
Swift的所有基本類(lèi)型(形如String,Int,Double,Bool)默認(rèn)是可哈?;?,可以作為集合的值的類(lèi)型或者字典的鍵的類(lèi)型。沒(méi)有關(guān)聯(lián)值的枚舉成員值默認(rèn)也是可哈希化的。

用我的話(huà)來(lái)說(shuō):當(dāng)想把自定義對(duì)象當(dāng)做key存入set和dict中時(shí),那么這個(gè)key必須是哈希的,并且要重載等號(hào)來(lái)對(duì)比key之間的不同。

官方的文檔說(shuō)的比較清楚,這里有篇中文翻譯:
Swift之Hashable協(xié)議

6. Codable
Codable = Decodable & Encodable
顧名思義,即編解碼序列化。
具體參看以下文章:
Swift 4.0: Codable
Swift 4 踩坑之 Codable 協(xié)議

這里的“&”符號(hào)的意思是協(xié)議合成:
協(xié)議合成并不會(huì)生成新的、永久的協(xié)議類(lèi)型,而是將多個(gè)協(xié)議中的要求合成到一個(gè)只在局部作用域有效的臨時(shí)協(xié)議中。
協(xié)議合成的意思是都羅列的協(xié)議都需要遵循。
其實(shí)不僅僅是協(xié)議可以合成,類(lèi)也可以跟協(xié)議一起使用“&”符號(hào)合成。

7. CaseIterable
一句話(huà):CaseIterable被用于合成簡(jiǎn)單枚舉類(lèi)型的 allCases 靜態(tài)屬性。
Swift 4.2 新特性詳解 CaseIterable.allCases
Swift--enum枚舉,協(xié)議CaseIterable

8. Identifiable
這個(gè)協(xié)議只需要你定義一個(gè) id 屬性,這個(gè)屬性必須是一個(gè) Hashable 類(lèi)型。

協(xié)議和泛型


加粗的這句話(huà)非常重要:

針對(duì)Class和Function,都是通過(guò)<Type>來(lái)定義。而當(dāng)我們需要給協(xié)議實(shí)現(xiàn)一個(gè)泛型的時(shí)候,需要使用associatedtype關(guān)鍵字來(lái)定義泛型:這里需要注意,泛型協(xié)議并不能像普通協(xié)議那樣作為一個(gè)類(lèi)型使用,這是因?yàn)?GenericProtocol 表示一組類(lèi)型,并不是一個(gè)單一類(lèi)型。比如你有一個(gè)關(guān)于 GenericProtocol 的隨機(jī)數(shù)組,并不能確定每個(gè)元素的 magic() 方法返回的類(lèi)型到底是什么,因?yàn)閿?shù)組中每個(gè)元素可能都是不同的。

泛型最終在使用時(shí)都要被推斷出來(lái),一種情況是實(shí)現(xiàn)的時(shí)候指定了類(lèi)型,另一種在調(diào)用時(shí)明確類(lèi)型。

參考:

  1. swift的泛型協(xié)議為什么不用<T>語(yǔ)法
  2. 為什么 Swift 關(guān)聯(lián)類(lèi)型的協(xié)議需要做為泛型約束使用(譯)

元類(lèi)型.Type 與 .self


一開(kāi)始看到官方例子中有一行這樣的代碼:

func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
...
}
//調(diào)用方法
let landmarkData: [Landmark] = load("landmarkData.json")

對(duì)于as type: T.Type = T.self這一部分不理解,搞懂這個(gè)需要先學(xué)習(xí)元類(lèi)型。

元類(lèi)型就是類(lèi)型的類(lèi)型。

let n : Int = 5

變量n的類(lèi)型是Int類(lèi)型,這個(gè)很容易理解。
那么Int的類(lèi)型又是什么呢?
Int的類(lèi)型就是:Int.Type,類(lèi)型的類(lèi)型,即元類(lèi)型。
說(shuō)完類(lèi)型來(lái)說(shuō)值:
n的類(lèi)型是Int,值是5。

let m: Int.Type = Int.self

那么變量m的類(lèi)型是元類(lèi)型Int.Type,m的值是Int.self。

到這兒我們?cè)倩仡^來(lái)看上面的代碼:

func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
...
}

as在這兒是標(biāo)簽名字,type是參數(shù)名,它的類(lèi)型是:T.Type元類(lèi)型,默認(rèn)值是T.self。
所以我們也可以這樣調(diào)用方法,跟不寫(xiě)第2個(gè)參數(shù)是等價(jià)的:

let landmarkData: [Landmark] = load("landmarkData.json", as: [Landmark].self)

class和static的區(qū)別


class和static用于修飾方法和計(jì)算屬性使其成為靜態(tài)方法或者靜態(tài)屬性。

最大的區(qū)別:

  1. class只能在類(lèi)中使用,static可以在類(lèi)和結(jié)構(gòu)中使用。
  2. class修飾的方法可以被子類(lèi)重寫(xiě),而static不能被重寫(xiě)。

其他細(xì)節(jié)的區(qū)別參看:
swift3.0 中class和static

ForEach和\.self


官方例子:

static var previews: some View {
        ForEach(["iPhone SE", "iPhone XS Max"], id: \.self) { deviceName in
            LandmarkList()
                .previewDevice(PreviewDevice(rawValue: deviceName))
                .previewDisplayName(deviceName)
        }
        .environmentObject(UserData())
    }

需要先學(xué)習(xí)這篇文章:
SwiftUI 和 Swift 5.1 新特性(3) Key Path Member Lookup

enum: if case,guard case,for case


模式匹配第四彈:if case,guard case,for case

關(guān)鍵字


associatedtype:
從字面上來(lái)理解,就是相關(guān)類(lèi)型。意思也就是被associatedtype關(guān)鍵字修飾的變量,相當(dāng)于一個(gè)占位符,而不能表示具體的類(lèi)型。具體的類(lèi)型需要讓實(shí)現(xiàn)的類(lèi)來(lái)指定。

typealias:
用來(lái)為已經(jīng)存在的類(lèi)型重新定義名字的,通過(guò)命名,可以使代碼變得更加清晰。使用的語(yǔ)法也很簡(jiǎn)單,使用typealias 關(guān)鍵字像使用普通的賦值語(yǔ)句一樣,可以將某個(gè)已經(jīng)存在的類(lèi)型賦值為新的名字。

Combine


Combine可以看作是簡(jiǎn)化版的RxSwift,Combine目前學(xué)習(xí)資料不多,可以先學(xué)RxSwift了解基本概念,再使用下面的對(duì)照速查表快速學(xué)習(xí)Combine。
RxSwift中文文檔
RxSwift 使用詳解系列
Swift Combine 入門(mén)導(dǎo)讀
RxSwift到Apple的Combine速查表(Cheat Sheet)

Apple Combine 的開(kāi)源實(shí)現(xiàn):
有空可以看看,對(duì)于加深Combine的理解有好處。
有兩個(gè)項(xiàng)目:

  1. OpenCombine
    項(xiàng)目地址:broadwaylamb/OpenCombine

  2. Apple Combine 的開(kāi)源實(shí)現(xiàn) CombineX 的第一個(gè) beta 發(fā)布啦!
    項(xiàng)目地址:luoxiu/CombineX

SwiftUI學(xué)習(xí)資料


  1. 這個(gè)是官方Demo的中文翻譯文檔
    建議先從這里開(kāi)始學(xué),結(jié)合源碼學(xué)的更快。
    WillieWangWei/SwiftUI-Tutorials

  2. 基本控件、狀態(tài)流、手勢(shì)等知識(shí)的簡(jiǎn)單說(shuō)明和示范
    Jinxiansen/SwiftUI

3.SwiftUI之屬性包裝
講解了以下屬性包裝:

@State
ObservableObject
@Published
@ObservedObject
@EnvironmentObject
@Environment
@Binding
@GestureState

學(xué)習(xí)SwiftUI遇到的問(wèn)題和bug


  1. SwiftUI update navigation bar title color
  2. 滾動(dòng)ScrollView到指定位置:
    SwiftUI ScrollView: How to modify .content.offset aka Paging?
  3. 鍵盤(pán)彈出/隱藏時(shí)修改布局
    How to show complete List when keyboard is showing up in SwiftUI
  4. SecureField
  • 只要在頁(yè)面中使用了SecureField那么該頁(yè)面的第三方輸入法就被禁用,在其他TextField上也不能調(diào)出。
    在切換到另外頁(yè)面中可以調(diào)出第三方鍵盤(pán),不過(guò)因?yàn)樵诖隧?yè)面中已經(jīng)被切換到系統(tǒng)輸入法,所以在別的頁(yè)面需要手動(dòng)切換第三方鍵盤(pán)。
  • 運(yùn)行時(shí)焦點(diǎn)切換到SecureField會(huì)打印以下log:

[AutoFill] Cannot show Automatic Strong Passwords for app bundleID: xxx.xxx due to error: Cannot save passwords for this app. Make sure you have set up Associated Domains for your app and AutoFill Passwords is enabled in Settings

可以忽略不管。
參看此篇文章:WWDC18 iOS 自動(dòng)生成強(qiáng)密碼和自動(dòng)填充驗(yàn)證碼/密碼

  1. 去掉導(dǎo)航頭
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.navigationBarTitle("注冊(cè)", displayMode:.inline)//tmd,必須設(shè)置這個(gè)才能真使.navigationBarHidden(true)有效
  1. xcode的一個(gè)bug
    在一個(gè)文件中SwiftUI預(yù)覽時(shí)右邊工具欄的屬性欄始終出不來(lái),另一個(gè)文件可以看到屬性欄,折騰了半天把文件名一改屬性欄居然出來(lái)了,再把文件名改回去也一切正常。
  2. 去掉List或者Form的左邊空白
    加在List/Form的子級(jí)中:
.listRowInsets(EdgeInsets())

以上還會(huì)留一點(diǎn)點(diǎn)空白,使用以下把空白完全去除:

.listRowInsets(EdgeInsets(top: 0, leading: -8, bottom: 0, trailing: 0))

或者

.padding(.horizontal, -24)

以上的-8,-24是自己對(duì)著界面調(diào)出來(lái)的,不一定通用(可能在不同平臺(tái)有不同表現(xiàn))。一般問(wèn)題不用太精確,湊合用著可以了。

  1. 不要隨便用Button
    在Form的Section底下用了Button,當(dāng)點(diǎn)擊一欄中不被其他控件比如TextField完全填充的空白區(qū)域時(shí)都會(huì)激活Button的action事件,把Button換成Image并且使用onTapGesture事件就正常了。
    Image點(diǎn)擊事件代碼例子:
 Image(systemName: "xmark.circle.fill")
                                    .foregroundColor(.gray).opacity(0.5)
                                    .imageScale(.small)
                                    .onTapGesture {
                                           print("image click")
                                    }
  1. TextField在系統(tǒng)自帶的簡(jiǎn)體拼音輸入法時(shí)有bug
    首先是輸入時(shí)鍵盤(pán)上的候選區(qū)閃動(dòng),另外輸入會(huì)莫明其妙的被自動(dòng)刪除或者多出字符,總之該輸入法在TextField中基本不能用。
    然后我換成UITextField后就沒(méi)問(wèn)題了,shit!
    目前iOS13.2.3依然有此bug。

  2. TextField和SecureField回車(chē)后無(wú)法自動(dòng)切換到下一個(gè)輸入框。

  3. 根據(jù)上面第9和第10條,TextField和SecureField最好還是別用,用UIKit版封裝一下替代。

  4. 最好不要自定義previewDevice

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ForEach("iPhone SE","iPhone XS Max", "iPhone 11 ", "iPhone 11 Pro"], id: \.self) { deviceName in
            ContentView()
                .previewDevice(PreviewDevice(rawValue: deviceName))
                .previewDisplayName(deviceName)
        }
        .environmentObject(UserData())
    }
}
  • 以上"iPhone 11 "在我機(jī)器上一定要加后面那個(gè)空格,否則preview時(shí)一直出不來(lái)。
  • 我實(shí)際上只使用一個(gè)設(shè)備比如"iPhone XS Max"或者"iPhone 11 Pro",當(dāng)使用Xcode一段時(shí)間后,Xcode的子進(jìn)程:SourceKitService會(huì)瘋狂占用內(nèi)存,占用的內(nèi)存多達(dá)幾十G,機(jī)器變得非常慢,最后只好強(qiáng)制殺死SourceKitService進(jìn)程。
    不使用以上自定義previewDevice,只使用以下默認(rèn)代碼則一切正常:
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
  1. 改變?nèi)令伾⊿afeArea區(qū)域
//  Color實(shí)際是個(gè)View,所以可以使用edgesIgnoringSafeArea來(lái)忽略安全區(qū)達(dá)到全屏效果
.background(Color(red: 0.9, green: 0.9, blue:0.9).edgesIgnoringSafeArea(.all))
  1. 當(dāng)在xcode中打開(kāi)多個(gè)Tab窗口時(shí),只在一個(gè)Tab中開(kāi)啟Preview。如果在多個(gè)Tab中開(kāi)啟Preview的話(huà)可能會(huì)預(yù)覽失敗。

Font Icon和SF Symbols


Font Icon:
開(kāi)源庫(kù)好幾個(gè),我先選擇了這個(gè):
ranesr/SwiftIcons
不過(guò)后來(lái)發(fā)現(xiàn)蘋(píng)果官方有SF Symbols。

SF Symbols:
ios13以上系統(tǒng)自帶SF Symbols,應(yīng)該也是Font Icon,可以直接調(diào)用。
官方文檔說(shuō)明:
SF Symbols
要想知道所有符號(hào)名字需要下載上面頁(yè)面提供的一個(gè)mac軟件,不過(guò)直接下載很慢,使用迅雷快很多。

所以使用SwiftUI開(kāi)發(fā)APP的話(huà)可以不使用第三方FontIcon直接使用SF Symbols
調(diào)用例子:

 Image(systemName: "arkit").foregroundColor(.blue)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容