SwiftUI入門到精通

SwiftUI要求

iOS13.0+

快捷鍵

control + option + 點(diǎn)擊:出現(xiàn)屬性編輯器

command + 點(diǎn)擊:出現(xiàn)快捷菜單

command + shift + L:Show Library彈窗

布局

VStack- 垂直布局

HStack- 水平布局

Spacer- 間距

Text- 文本

Image- 圖片

Divider- 分割線

Group- 組

ScrollView- 滾動(dòng)視圖

Path- 路徑

Shaper- 形狀

Form猾漫、Section- 表單

Color.red- 填充顏色

ForEach- 循環(huán)

LinearGradient(線性漸變)、RadialGradient(徑向漸變)、AngularGradient(角度漸變)

代碼解析

structContentView:View{varbody:someView{Text("Hello, world!").font(.title).foregroundColor(.yellow).bold().italic()}}structContentView_Previews:PreviewProvider{staticvarpreviews:someView{ContentView()}}

ContentView為布局,ContentView_Previews為預(yù)覽布局

body為計(jì)算屬性匠楚,類型為不透明類型的View良哲,不透明類型使用some修飾

Swift語法,只有一行代碼系任,return可以省略

some修飾,表示不透明類型虐块,總會返回某一個(gè)特定的類型俩滥,但是不知道是哪一種

可以返回關(guān)聯(lián)類型的協(xié)議類型

安全性:不對外暴露具體的返回類型

用來解決SwiftUI繁瑣的不確定返回類型問題

使用技巧

可以在右上角+里拖動(dòng)空間到代碼中

使用import導(dǎo)入所需的庫

可以新建SwiftUI View

ignoresSafeArea忽略safeArea的邊距,用在feame前

布局group組件可增加padding

VStack可添加font贺奠、foregroundColor等屬性霜旧,對所有包含的元素起效

串聯(lián)屬性,每一個(gè)點(diǎn)語法屬性敞嗡,返回當(dāng)前對象

Text("Hello world!").font(.title).foregroundColor(.purple)A modifier returns a view that applies a new behavior or visual change. You can chain multiple modifiers to achieve the effects you need.

使用previewLayout可以定義預(yù)覽的窗口的大小颁糟,也可以使用Group同時(shí)預(yù)覽多個(gè)窗口,通用屬性可以提取到外面

structLandmarkRow_Previews:PreviewProvider{staticvarpreviews:some View{Group{LandmarkRow(landmark:landmarks[0])LandmarkRow(landmark:landmarks[1])}.previewLayout(.fixed(width:300,height:70))}}

Identifiable:作為唯一標(biāo)識

遍歷需要唯一標(biāo)識來遍歷喉悴,如下:

List(landmarks,id:\.id){landmark inNavigationLink(destination:LandmarkDetail()){LandmarkRow(landmark:landmark)}}

如果讓列表中元素遵守Identifiable協(xié)議棱貌,遍歷處即可省略id參數(shù),model中需要有名稱為id的屬性

structLandmark:Hashable,Codable,Identifiable{varid:Intvarname:String

頁面跳轉(zhuǎn)使用NavigationLink箕肃,destination為要跳轉(zhuǎn)的頁面

NavigationLink(destination: LandmarkDetail()) {? ? LandmarkRow(landmark: landmark)}

使用NavigationView為頁面田健導(dǎo)航欄婚脱,可設(shè)置navigationTitle等

NavigationView{List(landmarks){landmark inNavigationLink(destination:LandmarkDetail()){LandmarkRow(landmark:landmark)}}.navigationTitle("Landmarks")}

預(yù)覽窗口按鈕作用:

第一個(gè)按鈕:Live Preview和Debug Preview,未打開只能查看頁面勺像,不能點(diǎn)擊等障贸,打開之后可以點(diǎn)擊跳轉(zhuǎn)頁面等交互操作

第二個(gè)按鈕:Preview On Device,連上真機(jī)點(diǎn)擊之后吟宦,預(yù)覽可以同步在真機(jī)上展示

第三個(gè)按鈕:Inspect Preview篮洁,可以打開窗口屬性窗口,可以設(shè)置預(yù)覽窗口屬性

第四個(gè)按鈕:Duplicate Preview殃姓,可以復(fù)制創(chuàng)建多個(gè)預(yù)覽窗口

代碼控制預(yù)覽的機(jī)型

structLandmarkList_Previews:PreviewProvider{staticvarpreviews:someView{LandmarkList().previewDevice(PreviewDevice(rawValue:"iPhone SE (2nd generation)"))}}// 多設(shè)備同時(shí)預(yù)覽structLandmarkList_Previews:PreviewProvider{staticvarpreviews:someView{ForEach(["iPhone 8","iPhone 12 Pro Max"],id:\.self){deviceNameinLandmarkList().previewDevice(PreviewDevice(rawValue:deviceName)).previewDisplayName(deviceName)}}}

組合靜態(tài)的View和動(dòng)態(tài)的view到List里袁波,可使用List + ForEach:

List(filteredLandmarks){landmark inNavigationLink(destination:LandmarkDetail(landmark:landmark)){LandmarkRow(landmark:landmark)}}替換為List{Toggle(isOn:$showFavoriteOnly){Text("Favorites only")}ForEach(filteredLandmarks){landmark inNavigationLink(destination:LandmarkDetail(landmark:landmark)){LandmarkRow(landmark:landmark)}}}

如果遍歷的對象沒有實(shí)現(xiàn)Identifiable協(xié)議瓦阐,則需要傳id

List{ForEach(modelData.categories.keys.sorted(),id:\.self){key inText(key)}}

ObservableObject協(xié)議

當(dāng)遵守ObservableObject協(xié)議的數(shù)據(jù)更新時(shí),綁定數(shù)據(jù)的view會自動(dòng)更新

finalclassModelData:ObservableObject{@Publishedvarlandmarks:[Landmark]=load("landmarkData.json")}

@Published

使用@Published修飾監(jiān)聽對象的屬性篷牌,表示該對象的屬性需要把屬性值的改變更新進(jìn)去

finalclassModelData:ObservableObject{@Publishedvarlandmarks:[Landmark]=load("landmarkData.json")}

@StateObject

使用@StateObject初始化一個(gè)監(jiān)聽對象的數(shù)據(jù)睡蟋,

使用.environmentObject把數(shù)據(jù)設(shè)置到環(huán)境對象里,

在需要的地方去取環(huán)境對象@EnvironmentObject var modelData: ModelData進(jìn)行使用

@mainstructMySwiftUIApp:App{// 定義@StateObjectprivatevarmodelData=ModelData()varbody:someScene{WindowGroup{ContentView()// 設(shè)置.environmentObject(modelData)}}}// 取@EnvironmentObjectvarmodelData:ModelData// 使用modelData.landmarks

Bool的toggle()方法:在true和false之前切換

@EnvironmentObject屬性用于在下級view中接收傳遞過來的參數(shù)

environmentObject(_:)方法用于向下級傳遞參數(shù)

@Binding:綁定修飾符用于修飾一個(gè)值枷颊,這個(gè)值用來改變另外一個(gè)值

綁定:@BindingvarisSet:Bool修改:FavoriteButton(isSet:$modelData.landmarks[landmarkIndex].isFavorite)

定義有狀態(tài)的字段戳杀,使用@State修飾,定義為private夭苗,并且賦初始值

@State private var showFavoritesOnly = false

@State:

使用@State屬性為應(yīng)用程序中的數(shù)據(jù)建立一個(gè)真實(shí)來源信卡,您可以從多個(gè)視圖中修改這些數(shù)據(jù)。SwiftUI管理底層存儲并根據(jù)值自動(dòng)更新視圖题造。

使用Path直接繪制坐求,可以當(dāng)做View來使用

View動(dòng)畫,包括顏色晌梨、透明度、旋轉(zhuǎn)须妻、大小和其他屬性等仔蝌,可以使用withAnimation來包裹狀態(tài)State實(shí)現(xiàn)動(dòng)畫

withAnimation{self.showDetail.toggle()}withAnimation(.easeInOut(duration:4)){self.showDetail.toggle()}

調(diào)用View的transition可以為View添加動(dòng)畫

HikeDetail(hike: hike).transition(.slide)

自定義transition可自定義動(dòng)畫

extensionAnyTransition{staticvarmoveAndFade:AnyTransition{AnyTransition.slide}}extensionAnyTransition{staticvarmoveAndFade:AnyTransition{AnyTransition.move(edge:.trailing)}}extensionAnyTransition{staticvarmoveAndFade:AnyTransition{letinsertion=AnyTransition.move(edge:.trailing).combined(with:.opacity)letremoval=AnyTransition.scale.combined(with:.opacity)return.asymmetric(insertion:insertion,removal:removal)}}

ListRowInsets用來調(diào)整一個(gè)view在list中的上下左右間距

CaseIterable,用在enum中用來獲取allCases方法

@Environment荒吏,SwiftUI提供了在環(huán)境中值的存儲敛惊,使用@Environment可以訪問值,并可以讀寫

@Environment(\.editMode)vareditMode

@State绰更、@ObservableObject瞧挤、@Binding、@EnvironmentObject區(qū)別

@State和@ObservableObject之間有一些細(xì)微的差異儡湾。這些都是很重要的特恬,因?yàn)樗鼈兌加胁煌挠猛尽?/p>

@State在視圖本地。值或數(shù)據(jù)在視圖中本地保存徐钠。它由框架管理癌刽,由于它存儲在本地,因此它是一個(gè)值類型尝丐。

使用@State來存儲不斷變化的數(shù)據(jù)显拜。記住,我們所有的SwiftUI視圖都是結(jié)構(gòu)體爹袁,這意味著沒有@State之類的東西就無法更改它們远荠。

@ObservableObject在視圖外部,并且不存儲在視圖中失息。它是一種引用類型譬淳,因?yàn)樗辉诒镜卮鎯Φ抵罚皇蔷哂袑υ撝档囊谩_@不是由框架自動(dòng)管理的瘦赫,而是開發(fā)人員的責(zé)任辰晕。這最適用于外部數(shù)據(jù),例如數(shù)據(jù)庫或由代碼管理的模型确虱。

@Binding也在視圖內(nèi)含友,但是與@State區(qū)別在于@Binding用于不通視圖之間的參數(shù)傳遞。@Binding和@ObservedObject一樣都是傳遞引用校辩。

@EnvironmentObject可以理解為全局變量

ObservableObject和@Published

遵循ObservableObject協(xié)議的類可以使用SwiftUI的@Published屬性包裝器來自動(dòng)發(fā)布屬性的變化窘问,以便使用該類的實(shí)例的任何視圖能夠自動(dòng)重新調(diào)用body屬性,保持界面與數(shù)據(jù)的一致宜咒。

@Published var profile = Profile.default

界面中使用@Binding來綁定UI

``

使用UIViewRepresentable來將UIKit中已有的View在SwiftUI中使用

使用UIViewControllerRepresentable來UIKit中的UIViewController在SwiftUI中使用

UIViewRepresentable

使用方法如下:

importSwiftUIimportUIKitstructPageControl:UIViewRepresentable{varnumberOfPages:Int@BindingvarcurrentPage:IntfuncmakeUIView(context:Context)->UIPageControl{letcontrol=UIPageControl()control.numberOfPages=numberOfPagesreturncontrol}funcupdateUIView(_uiView:UIPageControl,context:Context){uiView.currentPage=currentPage}}

SwiftUI畫布的Resume快捷鍵:Option + Command + P

Form表單惠赫、Section分段、Group分組

SwiftUI限制Section和Group包含不能超過10個(gè)故黑,Section可設(shè)置header和footer

Form {? ? ? ? ? ? Section(header: Text("Section 1 header").bold(), footer: Text("Section 1 footer")) {? ? ? ? ? ? ? ? Text("Placeholder 1")? ? ? ? ? ? ? ? Text("Placeholder 2")? ? ? ? ? ? ? ? Text("Placeholder 3")? ? ? ? ? ? ? ? Group() {? ? ? ? ? ? ? ? ? ? Text("Placeholder 1")? ? ? ? ? ? ? ? ? ? Text("Placeholder 2")? ? ? ? ? ? ? ? ? ? Text("Placeholder 3")? ? ? ? ? ? ? ? ? ? Text("Placeholder 4")? ? ? ? ? ? ? ? ? ? Text("Placeholder 5")? ? ? ? ? ? ? ? ? ? Text("Placeholder 6")? ? ? ? ? ? ? ? ? ? Text("Placeholder 7")? ? ? ? ? ? ? ? ? ? Text("Placeholder 8")? ? ? ? ? ? ? ? ? ? Text("Placeholder 9")? ? ? ? ? ? ? ? ? ? Text("Placeholder 10")? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? ? ? Group() {? ? ? ? ? ? ? ? Text("Placeholder 1")? ? ? ? ? ? ? ? Text("Placeholder 2")? ? ? ? ? ? }? ? ? ? }

WX20210127-160605@2x.png

添加導(dǎo)航欄使用navigationBarTitle儿咱,displayMode設(shè)置顯示樣式

NavigationView{Form{Section{Text("Hello World")}}.navigationBarTitle("SwiftUI",displayMode:.inline)}

就像SwiftUI的其他視圖一樣,VStack最多可以有10個(gè)子節(jié)點(diǎn)——如果您想添加更多子節(jié)點(diǎn)场晶,應(yīng)該將它們包裝在一個(gè)Group中混埠。

Color.red本身就是一個(gè)視圖,這就是為什么它可以像形狀和文本一樣使用诗轻。它會自動(dòng)占用所有可用空間钳宪。

Color.primary是SwiftUI中文本的默認(rèn)顏色,根據(jù)用戶的設(shè)備是在亮模式還是在暗模式下運(yùn)行扳炬,它將是黑色還是白色吏颖。還有Color.secondary,它也可以是黑色或白色恨樟,這取決于設(shè)備半醉,但現(xiàn)在它有輕微的透明度,以便后面的一點(diǎn)顏色可以穿透厌杜。

如果要將內(nèi)容置于安全區(qū)域之下奉呛,可以使用edgesIgnoringSafeArea()修飾符指定要運(yùn)行到的屏幕邊緣。

Color.red.ignoresSafeArea()Color.red.edgesIgnoringSafeArea(.all)

漸變色

VStack{// 線性漸變 LinearGradient 沿一個(gè)方向運(yùn)行夯尽,因此我們?yōu)槠涮峁┝艘粋€(gè)起點(diǎn)和終點(diǎn)LinearGradient(gradient:Gradient(colors:[.white,.black]),startPoint:.leading,endPoint:.trailing)// 徑向漸變 RadialGradient 以圓形向外移動(dòng)瞧壮,因此,我們沒有指定方向匙握,而是指定了起點(diǎn)和終點(diǎn)半徑——顏色應(yīng)從圓心到圓心的距離開始和停止變化RadialGradient(gradient:Gradient(colors:[.blue,.black]),center:.center,startRadius:20,endRadius:200)// 角度漸變 AngularGradient咆槽,盡管您可能聽說過其他地方將其稱為圓錐形或圓錐形漸變。這使顏色圍繞一個(gè)圓圈循環(huán)而不是向外輻射AngularGradient(gradient:Gradient(colors:[.red,.yellow,.green,.blue,.purple,.red]),center:.center)}

image.png

如果您發(fā)現(xiàn)圖像已被某種顏色填充圈纺,例如顯示為純藍(lán)色而不是實(shí)際圖片秦忿,則可能是SwiftUI為它們著色以顯示它們是可點(diǎn)擊的麦射。要解決此問題,請使用renderingMode(.original)修飾符強(qiáng)制SwiftUI顯示原始圖像灯谣,而不是重新著色的版本潜秋。

Image("Image Name").renderingMode(.original)

Alert的使用

structContentView:View{@StateprivatevarshowAlert=falsevarbody:someView{VStack{Button(action:{showAlert=true}){Text("按鈕")}.alert(isPresented:$showAlert,content:{Alert(title:Text("標(biāo)題"),message:Text("文本內(nèi)容"),primaryButton:.cancel{print("點(diǎn)擊取消")},secondaryButton:.default(Text("確定")){print("點(diǎn)擊確定")})})}}}

image.png

Swift內(nèi)置形狀:

矩形Rectangle胎许、圓角矩形RoundedRectangle峻呛、圓形Circle、膠囊Capsule和橢圓Ellipse

使用方法:.clipShape(Capsule())

切割辜窑、描邊钩述、陰影

Image("xixi")// 邊緣形狀.clipShape(Circle())// 描邊.overlay(Circle().stroke(Color.yellow,lineWidth:2))// 陰影.shadow(color:.blue,radius:20)

image.png

SwiftUI為什么使用結(jié)構(gòu)體而不是用類?

首先穆碎,有一個(gè)性能因素:結(jié)構(gòu)體比類更簡單牙勘,更快。我之所以說性能因素所禀,是因?yàn)楹芏嗳苏J(rèn)為這是SwiftUI使用結(jié)構(gòu)體的主要原因方面,而實(shí)際上這只是更大范圍的一部分。

在UIKit中色徘,每個(gè)視圖都來自一個(gè)名為UIView的類葡幸,該類具有許多屬性和方法:背景色,確定其放置方式的約束贺氓,用于將其內(nèi)容呈現(xiàn)到其中的圖層等等。其中有很多床蜘,每個(gè)UIView和UIView子類都必須具有它們辙培,因?yàn)槔^承是這樣工作的。

視圖作為結(jié)構(gòu)體還是有很多更重要的事情:它迫使我們考慮以一種干凈的方式隔離狀態(tài)邢锯。您會發(fā)現(xiàn)扬蕊,類能夠自由更改其值,這可能導(dǎo)致代碼混亂丹擎。

通過生成不會隨時(shí)間變化的視圖尾抑,SwiftUI鼓勵(lì)我們轉(zhuǎn)向更具功能性的設(shè)計(jì)方法:在將數(shù)據(jù)轉(zhuǎn)換為UI時(shí),我們的視圖變成簡單的蒂培,惰性的東西再愈,而不是會失去控制的智能化的東西。

當(dāng)您查看可以作為視圖的事物時(shí)护戳,可以看到這一點(diǎn)翎冲。我們已經(jīng)使用了Color.red和LinearGradient作為視圖——包含很少數(shù)據(jù)的簡單類型。實(shí)際上媳荒,您不能找到比使用Color.red作為視圖的更好的主意:除了“用紅色填充我的空間”之外抗悍,它不包含任何信息驹饺。

相比之下,Apple的UIView文檔列出了UIView擁有的約200種屬性和方法缴渊,無論是否需要它們赏壹,所有這些屬性和方法都將傳遞給其子類。

提示:如果您在視圖中使用類衔沼,則可能會發(fā)現(xiàn)代碼無法編譯或在運(yùn)行時(shí)崩潰蝌借。

SwiftUI應(yīng)用點(diǎn)語法修改視圖,返回的也是視圖類型俐巴。每次我們修改視圖時(shí)骨望,SwiftUI都會使用以下泛型來應(yīng)用該修飾符:ModifiedContent<OurThing, OurModifier>。

為什么SwiftUI使用some View作為視圖類型欣舵?

返回some View與僅返回View相比有兩個(gè)重要區(qū)別:

1擎鸠、我們必須始終返回相同類型的視圖。

2缘圈、即使我們不知道返回的視圖類型劣光,編譯器也同樣不知道。

這種代碼是不允許的:

varbody:some View{ifself.useRedText{returnText("Hello World")}else{returnText("Hello World").background(Color.red)}}

some View意味著“將返回一種特定類型的視圖糟把,但我們不想說它是什么绢涡。”由于SwiftUI使用通用的ModifiedContent包裝器創(chuàng)建新視圖的方式遣疯,Text(…)和Text(…).background(Color.red)是不同的底層類型雄可,這與some View不兼容。

SwiftUI使用ModifiedContent構(gòu)建數(shù)據(jù)的方式缠犀。

SwiftUI是如何處理VStack這樣的東西的——它符合View協(xié)議数苫,如果您創(chuàng)建了一個(gè)包含兩個(gè)文本視圖的VStack,那么SwiftUI會無聲地創(chuàng)建一個(gè)TupleView來包含這兩個(gè)視圖辨液。TupleView的一個(gè)版本可以跟蹤十種不同的內(nèi)容:

TupleView<(C0,C1,C2,C3,C4,C5,C6,C7,C8,C9)>

這就是為什么SwiftUI在一個(gè)父級中不允許超過10個(gè)視圖的原因:他們編寫了TupleView的版本虐急,可以處理2到10個(gè)視圖,但不能超過10個(gè)滔迈。

自定義修飾符止吁,使用ViewModifier

importSwiftUIstructMyViewModifier:View{varbody:someView{VStack{Text("Hello, World!")// 使用修飾符.modifyTitle()Text("Hello, World!")// 使用修飾符.modifySubTitle(text:"前綴")}}}structMyViewModifier_Previews:PreviewProvider{staticvarpreviews:someView{MyViewModifier()}}// 自定義修飾符structTitle:ViewModifier{funcbody(content:Content)->someView{content.font(.title).foregroundColor(.white).padding().background(Color.red).clipShape(RoundedRectangle(cornerRadius:5.0))}}// 自定義修飾符,并重新構(gòu)建視圖structSubTitle:ViewModifier{vartext:Stringfuncbody(content:Content)->someView{VStack{content.font(.subheadline).foregroundColor(.gray).padding().background(Color.green).clipShape(RoundedRectangle(cornerRadius:5.0))Text(text).font(.subheadline).foregroundColor(.blue)}}}// 擴(kuò)展修飾符extensionView{funcmodifyTitle()->someView{self.modifier(Title())}funcmodifySubTitle(text:String)->someView{self.modifier(SubTitle(text:text))}}

LazyVGrid和LazyHGrid使用(iOS14新增)

lettext=(1...10).map{"Hello\($0)"}// 以最小寬度160斤可能在一行放入gridletcolumns=[GridItem(.adaptive(minimum:80))]// 每行三個(gè)grids,大小靈活分配letcolumnsFixed=[GridItem(.flexible()),GridItem(.flexible()),GridItem(.flexible()),]// 第一個(gè)100固定燎悍,第二個(gè)盡量填滿letcolumnsFixed100=[GridItem(.fixed(100)),GridItem(.flexible()),]varrows:[GridItem]=Array(repeating:.init(.fixed(20)),count:2)varbody:someView{ScrollView{Section(header:Text("最小160")){LazyVGrid(columns:columns,spacing:20){ForEach(text,id:\.self){iteminText(item)}}}Section(header:Text("每行三個(gè)Grid")){LazyVGrid(columns:columnsFixed,spacing:20){ForEach(text,id:\.self){iteminText(item)}}}Section(header:Text("第一個(gè)固定100")){LazyVGrid(columns:columnsFixed100,spacing:20){ForEach(text,id:\.self){iteminButton(item){print("itme pressed")}}}}ScrollView(.horizontal){LazyHGrid(rows:rows,alignment:.top){ForEach(0...79,id:\.self){letcodepoint=$0+0x1F600letcodepointString=String(format:"%02X",codepoint)Text("\(codepointString)").font(.footnote)letemoji=String(Character(UnicodeScalar(codepoint)!))Text("\(emoji)").font(.largeTitle)}}}}}

image.png

ForEach使用區(qū)別:

letagents=["Cyril","Lana","Pam","Sterling"]VStack{ForEach(0..<agents.count){Text(self.agents[$0])}}

我們回到Swift如何識別數(shù)組中的值敬惦。當(dāng)我們使用0..<5或0..<agents.count這樣的范圍時(shí),Swift確信每個(gè)項(xiàng)目都是唯一的谈山,因?yàn)樗鼘⑹褂梅秶械臄?shù)字——每個(gè)數(shù)字在循環(huán)中只使用一次仁热,所以它肯定是唯一的。

但是當(dāng)使用字符串時(shí),不會標(biāo)識為唯一抗蠢,導(dǎo)致body被調(diào)用時(shí)會被重建举哟。因此可以使用id來標(biāo)識,如下:

VStack{ForEach(agents,id:\.self){Text($0)}}

另外迅矛,為了標(biāo)識視圖的唯一妨猩,可以用Identifiable協(xié)議來實(shí)現(xiàn):

定義:

@available(macOS10.15,iOS13,tvOS13,watchOS6,*)publicprotocolIdentifiable{/// A type representing the stable identity of the entity associated with/// an instance.associatedtypeID:Hashable/// The stable identity of the entity associated with this instance.varid:Self.ID{get}}

使用:

structModelData:Identifiable{varid:Int}

使用定制綁定Binding

簡單使用:

structContentView:View{@Stateprivatevarselection=0varbody:someView{letbinding=Binding(get:{self.selection},set:{self.selection=$0})returnVStack{Picker("Select",selection:binding){ForEach(0..<3){Text("Item\($0)")}}.pickerStyle(SegmentedPickerStyle())Text("\(selection)")}}}

因此,該綁定實(shí)際上只是作為一個(gè)傳遞——它本身不存儲或計(jì)算任何數(shù)據(jù)秽褒,而是作為UI和被操縱的底層狀態(tài)值之間的一個(gè)填充壶硅。

但是,請注意销斟,選擇器現(xiàn)在是使用selection:binding進(jìn)行的庐椒,不需要美元符號。我們不需要在這里明確要求雙向綁定蚂踊,因?yàn)樗呀?jīng)是了约谈。

高級使用:

structContentView:View{@StateprivatevaragreedToTerms=false@StateprivatevaragreedToPrivacyPolicy=false@StateprivatevaragreedToEmails=falsevarbody:someView{letagreeToAll=Binding<Bool>(get:{self.agreedToTerms&&self.agreedToPrivacyPolicy&&self.agreedToEmails},set:{self.agreedToTerms=$0self.agreedToPrivacyPolicy=$0self.agreedToEmails=$0})returnVStack{Toggle(isOn:$agreedToTerms){Text("agreedToTerms")}Toggle(isOn:$agreedToPrivacyPolicy){Text("agreedToPrivacyPolicy")}Toggle(isOn:$agreedToEmails){Text("agreedToEmails")}Toggle(isOn:agreeToAll){Text("agreeToAll")}}.padding()}}

image.png

double轉(zhuǎn)String保留幾位小數(shù)

// 保留2位小數(shù)Text("\(sleepAmount, specifier: "%.2f") 小時(shí)")// 保留2位小數(shù),并自動(dòng)刪除末尾不需要的0Text("\(sleepAmount, specifier: "%.2g") 小時(shí)")

image.png

DatePicker使用

struct DatePickerView:View{@StateprivatevarwakeUp=Date()varbody:some View{VStack{// 有標(biāo)題DatePicker("Please enter a date",selection:$wakeUp)// 無標(biāo)題DatePicker("Please enter a date",selection:$wakeUp).labelsHidden()// 無標(biāo)題犁钟,有時(shí)間范圍DatePicker("Please",selection:$wakeUp,in:Date()...Date().addingTimeInterval(86400)).labelsHidden()DatePicker("Please",selection:$wakeUp,in:Date()...).labelsHidden()}}}

image.png

DateComponents和DateFormatter使用

// hour棱诱、minute通過DateComponents生成DatevardateComponents=DateComponents()dateComponents.hour=8letdate=Calendar.current.date(from:dateComponents)// Date通過DateComonents獲取hour、minuteletsomeDate=Date()letcomponents=Calendar.current.dateComponents([.hour,.minute],from:someDate)lethour=components.hour??0letminute=components.minute??0// Date轉(zhuǎn)StringletdateFormatter=DateFormatter()dateFormatter.dateStyle=.shortletdateString=dateFormatter.string(from:Date())

SwiftUI讀取本地項(xiàng)目文件

ifletstartWordsUrl=Bundle.main.url(forResource:"start",withExtension:"txt"){ifletstartWords=try?String(contentsOf:startWordsUrl){letallWords=startWords.components(separatedBy:"\n")rootWord=allWords.randomElement()??"silkworm"return}}

顯示視圖時(shí)調(diào)用的閉包onAppear

NavigationView {? ? ? ? ? ? VStack {? ? ? ? ? ? ? ? TextField("輸入單詞", text: $newWord, onCommit: addNewWord)? ? ? ? ? ? ? ? .textFieldStyle(RoundedBorderTextFieldStyle())? ? ? ? ? ? ? ? .padding()? ? ? ? ? ? ? ? ? ? .autocapitalization(.none)? ? ? ? ? ? ? ? List(usedWords, id: \.self) {? ? ? ? ? ? ? ? ? ? Image(systemName: "\($0.count).circle")? ? ? ? ? ? ? ? ? ? Text($0)? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? ? ? .navigationTitle(rootWord)? ? ? ? ? ? .onAppear(perform: startGame)? ? ? ? }

使用多個(gè).animation對不同的動(dòng)畫涝动,進(jìn)行分別處理

structContentView:View{@Stateprivatevarenabled=falsevarbody:someView{VStack{Button("Tap Me"){self.enabled.toggle()}.frame(width:200,height:200).background(enabled?Color.blue:Color.red).animation(.default)// 針對顏色的動(dòng)畫.foregroundColor(.white).clipShape(RoundedRectangle(cornerRadius:enabled?60:0)).animation(.interpolatingSpring(stiffness:10,damping:1))// 針對形狀的動(dòng)畫}}}

禁用動(dòng)畫:.animation(nil)

手勢動(dòng)畫

structContentView:View{@StateprivatevardragAmount=CGSize.zerovarbody:someView{LinearGradient(gradient:Gradient(colors:[.yellow,.red]),startPoint:.topLeading,endPoint:.bottomTrailing).frame(width:300,height:200).clipShape(RoundedRectangle(cornerRadius:10))// 視圖位置改變.offset(dragAmount)// 添加拖動(dòng)手勢.gesture(DragGesture().onChanged{// 實(shí)時(shí)根據(jù)手勢位置改變視圖的位置self.dragAmount=$0.translation}.onEnded{_in// 彈性動(dòng)畫歸0withAnimation(.spring()){self.dragAmount=.zero}})}}

結(jié)構(gòu)體和類的使用區(qū)別

結(jié)構(gòu)體一直擁有唯一的所有者迈勋,而對于類,多個(gè)對象可以指向同一個(gè)值醋粟。

類不需要在更改其屬性的方法之前使用mutating關(guān)鍵字靡菇,因?yàn)槟梢愿某A款惖膶傩浴?/p>

實(shí)際上,這意味著米愿,如果我們有兩個(gè)SwiftUI視圖镰官,并且我們將同一個(gè)結(jié)構(gòu)體發(fā)送給它們,那么它們實(shí)際上都有該結(jié)構(gòu)體的唯一副本吗货;如果其中一個(gè)更改了它,那么另一個(gè)將看不到該更改狈网。另一方面宙搬,如果我們創(chuàng)建一個(gè)類的實(shí)例并將其發(fā)送到兩個(gè)視圖,它們將共享更改拓哺。

對于SwiftUI開發(fā)人員來說勇垛,這意味著如果我們想要在多個(gè)視圖之間共享數(shù)據(jù)——如果我們想要兩個(gè)或多個(gè)視圖指向同一個(gè)數(shù)據(jù),以便在一個(gè)更改時(shí)它們都得到這些更改——我們需要使用類而不是結(jié)構(gòu)體士鸥。

為什么使用@ObservedObject

如果使用類的同時(shí)闲孤,使用@State來讓SwiftUI監(jiān)聽值的改變,雖然值改變了烤礁,但是SwiftUI監(jiān)聽不到類中值的改變讼积,不會對body進(jìn)行銷毀和重建肥照,所以需要使用@OvervedObject來處理該問題。

當(dāng)我們使用@State時(shí)勤众,我們要求SwiftUI監(jiān)視屬性的更改舆绎。因此,如果我們更改字符串们颜、翻轉(zhuǎn)布爾值吕朵、添加到數(shù)組等,則屬性已更改窥突,SwiftUI將重新調(diào)用視圖的body屬性努溃。

當(dāng)User是一個(gè)結(jié)構(gòu)體時(shí),每次我們修改該結(jié)構(gòu)體的屬性時(shí)阻问,Swift實(shí)際上是在創(chuàng)建該結(jié)構(gòu)的新實(shí)例梧税。@State能夠發(fā)現(xiàn)這個(gè)變化,并自動(dòng)重新加載我們的視圖≡蚩剑現(xiàn)在我們有了一個(gè)類贡蓖,這種行為不再發(fā)生:Swift可以直接修改值。

還記得我們?nèi)绾螢樾薷膶傩缘慕Y(jié)構(gòu)體方法使用mutating關(guān)鍵字嗎煌茬?這是因?yàn)槌馄蹋绻覀儗⒔Y(jié)構(gòu)體的屬性創(chuàng)建為變量,但結(jié)構(gòu)體本身是常量坛善,則無法更改屬性——Swift需要能夠在屬性更改時(shí)銷毀和重新創(chuàng)建整個(gè)結(jié)構(gòu)體晾蜘,而常量結(jié)構(gòu)則不可能這樣做。類不需要mutating關(guān)鍵字眠屎,因?yàn)榧词诡悓?shí)例標(biāo)記為常量Swift剔交,仍然可以修改變量屬性。

我知道這聽起來非常理論化改衩,但這里有個(gè)問題:既然User是一個(gè)類岖常,那么屬性本身不會改變,所以@State不會注意到任何事情葫督,也無法重新加載視圖竭鞍。是的,類中的值正在更改橄镜,但是@State不監(jiān)視這些值偎快,所以實(shí)際上發(fā)生的情況是,類中的值正在更改洽胶,但視圖不會重新加載以反映該更改晒夹。

為了解決這個(gè)問題,是時(shí)候拋棄@State了。相反丐怯,我們需要一個(gè)更強(qiáng)大的屬性包裝器喷好,名為@ObservedObject。

@ObservedObject响逢、@Published的使用

如果需要在多個(gè)視圖之間共享數(shù)據(jù)的話绒窑,可使用@ObservedObject和@EnvironmentObject。

@Published用于類中舔亭,通知關(guān)注類的所有視圖在類發(fā)生改變時(shí)些膨,去重新加載。

classUser{// @Published通知關(guān)注類的所有視圖在類發(fā)生改變時(shí)钦铺,去重新加載@PublishedvarfirstName="zhiying"@PublishedvarlastName="yuan"}

@ObservedObject用于獲知知道哪些類在改變時(shí)能通知視圖订雾,它告訴SwiftUI監(jiān)視類中的任何的更改公告。

@ObservedObject屬性包裝器只能用于符合ObservableObject協(xié)議的類型矛洞。該協(xié)議沒有任何要求洼哎,實(shí)際上意味著“我們希望其他事物能夠監(jiān)視此更改”。

// 類遵守ObservableObject協(xié)議classUser:ObservableObject{// @Published 通知關(guān)注類的所有視圖在類發(fā)生改變時(shí)沼本,去重新加載@PublishedvarfirstName="zhiying"@PublishedvarlastName="yuan"}struct ContentView:View{// @ObservedObject 用于標(biāo)記哪些類在改變時(shí)通知視圖加載視圖@ObservedObjectvaruser=User()varbody:some View{VStack{Text("名字是\(user.firstName)\(user.lastName)")TextField("firstName",text:$user.firstName)TextField("lastName",text:$user.lastName)}}}

三個(gè)步驟:

創(chuàng)建一個(gè)符合ObservableObject協(xié)議的類噩峦。

用@Published標(biāo)記一些屬性,以便使用該類的所有視圖在更改時(shí)都得到更新抽兆。

使用@ObservedObject屬性包裝器創(chuàng)建我們的類的實(shí)例识补。

彈出模態(tài)視圖,并通過獲取全局變量來關(guān)閉模態(tài)視圖

彈出

structContentView:View{@StateprivatevarshowSheet=falsevarbody:someView{VStack{Button("show sheet"){self.showSheet.toggle()}.sheet(isPresented:$showSheet,content:{SecondView()})}}}

關(guān)閉

structSecondView:View{// 獲取全局環(huán)境變量 presentationMode@Environment(\.presentationMode)varsecondPresentationModevarbody:someView{Button("關(guān)閉"){// 通過獲取到的全局環(huán)境變量辫红,來關(guān)閉模態(tài)視圖self.secondPresentationMode.wrappedValue.dismiss()}}}

SwiftUI中\(zhòng).self是什么凭涂?

[SwiftUI 100天] Core Data ForEach .self 的工作機(jī)制

struct ContentView:View{@Stateprivatevarnumbers=[Int]()varbody:some View{VStack{List{ForEach(numbers,id:\.self){Text("\($0)")}}}}}

之前我們了解了使用ForEach來創(chuàng)建動(dòng)態(tài)視圖的不同方式,它們都有一個(gè)共同點(diǎn):SwiftUI 需要知道如何唯一識別動(dòng)態(tài)視圖的每一項(xiàng)贴妻,以便正確地動(dòng)畫化改變切油。

如果一個(gè)對象遵循Identifiable協(xié)議,那么 SwiftUI 會自動(dòng)使用它的id屬性作為唯一標(biāo)識名惩。如果我們不使用Identifiable澎胡,那就需要指定一個(gè)我們知道是唯一的屬性的 key path,比如圖書的 ISBN 號娩鹉。但假如我們不遵循Identifiable也沒有唯一的 key path攻谁,我們通常會使用.self。

我們對原始數(shù)據(jù)類型底循,例如Int和String使用過.self,就像下面這樣:

List {

ForEach([2, 4, 6, 8, 10], id: .self) {

Text("($0) is even")

}

}

對于 Core Data 為我們生成托管類槐瑞,我們也使用.self熙涤,當(dāng)時(shí)我沒有解釋這是如何工作的祠挫,或者說它究竟是如何與我們的ForEach關(guān)聯(lián)的蝶柿。不過今天我要再來討論這個(gè)問題戒洼,因?yàn)槲矣X得這會給你提供一些有助益的洞察怎茫。

當(dāng)我們使用.self作為標(biāo)識符時(shí)掉伏,我們指的是“整個(gè)對象”,但實(shí)踐上這個(gè)指代并沒有包含太多的含義 —— 一個(gè)結(jié)構(gòu)體就是結(jié)構(gòu)體供常,除了內(nèi)容之外摊聋,它并沒有包含任何特定的標(biāo)識信息。因此栈暇,實(shí)際發(fā)生的事情是麻裁,Swift 計(jì)算了結(jié)構(gòu)體的哈希值 —— 一種以固定長度的值表示復(fù)雜數(shù)據(jù)的方法 —— 然后以哈希值作為標(biāo)識符。

哈希值可以以很多種方法生成源祈,但所有方法的背后的概念是一致的:

無論輸入的數(shù)據(jù)多大煎源,輸出總是固定大小。

對同一個(gè)對象計(jì)算兩次哈希值新博,應(yīng)該返回相同的值薪夕。

這兩點(diǎn)聽起來很簡單,但你思考一下:假設(shè)我們獲取 “Hello World” 的哈希值和莎士比亞全集的哈希值赫悄,兩者都將是相同的大小原献。這意味著我們是無法從哈希值還原出原始數(shù)據(jù)的 —— 我們無法從 40 個(gè)看起來完全隨機(jī)的十六進(jìn)制數(shù)字轉(zhuǎn)換出莎士比亞全集。

哈希常見于數(shù)據(jù)校驗(yàn)埂淮。舉個(gè)例子姑隅,假如你下載了一個(gè) 8GB 的 zip 文件,你可以通過對比你本地的哈希值和服務(wù)器上的哈希值來確認(rèn)文件是正確的 —— 如果兩者匹配倔撞,說明 zip 文件是一致的讲仰。哈希還被用于字典的鍵和值,這是為什么字典查詢速度很快的原因痪蝇。

上面說的這些很重要鄙陡,因?yàn)?Xcode 為我們生成托管對象的類時(shí),它會讓這些類遵循Hashable躏啰,這是一個(gè)表示 Swift 可以從中生成哈希值的協(xié)議趁矾,也就是說,我們可以用它的.self作為標(biāo)識符给僵。這也是為什么String和Int可以用.self的原因:它們也遵循Hashable毫捣。

Hashable有點(diǎn)類似Codable:如果我們想讓一個(gè)自定義類型遵循Hashable,那么只要它包含的所有東西也遵循Hashable帝际,那我們就不必做額外的工作蔓同。為了說明這一點(diǎn),我們可以創(chuàng)建一個(gè)自定義結(jié)構(gòu)體蹲诀,讓它遵循Hashable而不是Identifiable斑粱,然后使用.self來標(biāo)識它:

struct Student: Hashable {

let name: String

}

struct ContentView: View {

let students = [Student(name: "Harry Potter"), Student(name: "Hermione Granger")]

varbody:some View{List(students,id:\.self){student inText(student.name)}}

}

我們可以讓Student遵循Hashable,因?yàn)樗械膶傩远家呀?jīng)遵循Hashable脯爪,因此 Swift 會計(jì)算每個(gè)屬性的哈希值则北,然后結(jié)合這些值產(chǎn)生一個(gè)代表整個(gè)對象的哈希值蹋宦。當(dāng)然,如果我們遇到兩個(gè)同名的學(xué)生咒锻,那我們可能會遇到問題,這就像我們擁有一個(gè)包含兩個(gè)相同字符串的字符串?dāng)?shù)組一樣守屉。

現(xiàn)在惑艇,你可能想,這樣會導(dǎo)致問題吧:如果我們用相同的值創(chuàng)建了兩個(gè) Core Data 對象拇泛,它們會生成相同的哈希值滨巴,這樣我們就遇到問題了。不過俺叭,其實(shí) Core Data 是一種很聰明的方式來工作:它為我們創(chuàng)建的對象實(shí)際上有一組我們定義數(shù)據(jù)模型時(shí)定義的屬性之外的其他屬性恭取,包括一個(gè)叫 ID 的對象 —— 這是一個(gè)可以唯一標(biāo)識對象的標(biāo)識符,不管對象包含的屬性是什么熄守。這些 ID 類似于 UUID蜈垮,在我們創(chuàng)建對象時(shí),Core Data 順序產(chǎn)生它們裕照。

因此攒发,.self適用于所有遵循Hashable的類,因?yàn)?Swift 會為其生成哈希值并用該值作為對象的唯一標(biāo)識晋南。這對于 Core Data 的對象同樣適用惠猿,因?yàn)樗鼈円呀?jīng)遵循了Hashable。

警告: 雖然給一個(gè)對象計(jì)算兩次哈希值應(yīng)該返回相同的值负间,但在兩次應(yīng)用運(yùn)行期間計(jì)算它們 —— 比如說偶妖,計(jì)算哈希值,退出應(yīng)用政溃,重啟趾访,然后再計(jì)算哈希值 —— 是有可能返回不同的值的撒桨。

onDelete()的使用

單個(gè)左滑刪除

struct ContentView:View{@Stateprivatevarnumbers=[Int]()@StateprivatevarcurrentNumber=1varbody:some View{VStack{List{ForEach(numbers,id:\.self){Text("\($0)")}// onDelete只能添加在ForEach上.onDelete(perform:{indexSetin// ForEach是由numbers數(shù)組創(chuàng)建的工育,可以直接將索引集直接傳給numbers數(shù)組numbers.remove(atOffsets:indexSet)})}Button("添加"){numbers.append(currentNumber)currentNumber+=1}}}}

image.png

多個(gè)點(diǎn)擊刪除

struct ContentView:View{@Stateprivatevarnumbers=[Int]()@StateprivatevarcurrentNumber=1varbody:some View{NavigationView{VStack{List{ForEach(numbers,id:\.self){Text("\($0)")}.onDelete(perform:{indexSetinnumbers.remove(atOffsets:indexSet)})}Button("添加"){numbers.append(currentNumber)currentNumber+=1}}.navigationBarItems(leading:EditButton())}}}

image.png

本地?cái)?shù)據(jù)存儲UserDefaults

存UserDefaults.standard.setValue(self.tapCount,forKey:"tapCount")取(未設(shè)置有默認(rèn)值)UserDefaults.standard.integer(forKey:"tapCount")

調(diào)整圖片大小机久,以適應(yīng)屏幕

structContentView:View{varbody:some View{NavigationView{VStack{// GeometryReader 確保圖像填充其容器視圖的整個(gè)寬度GeometryReader(content:{geometryinImage("WX20210226-120815").resizable().aspectRatio(contentMode:.fit).frame(width:geometry.size.width)})VStack{Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")}}.navigationTitle(Text("我是標(biāo)題"))}}}

image.png

structContentView:View{varbody:some View{NavigationView{VStack{// GeometryReader 確保圖像填充其容器視圖的整個(gè)寬度GeometryReader(content:{geometryinImage("WX20210226-120815").resizable().aspectRatio(contentMode:.fit).frame(width:geometry.size.width)})VStack{Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")}VStack{Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")}VStack{Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")}VStack{Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")}VStack{Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")Text("哈哈哈哈哈")}}.navigationTitle(Text("我是標(biāo)題"))}}}

image.png

List + ForEach與ScrollView + ForEach區(qū)別

List + ForEach會在可見的時(shí)候才創(chuàng)建

ScrollView + ForEach會一次性創(chuàng)建所有視圖

List + ForEach

struct ContentView:View{varbody:some View{NavigationView{// List + ForEach 會在可見的時(shí)候才創(chuàng)建List{ForEach(0..<100){CustomText("\($0) _")}}.navigationTitle(Text("標(biāo)題"))}}}

ScrollView + ForEach

structContentView:View{varbody:someView{NavigationView{ScrollView{ForEach(0..<100){CustomText("\($0)_")}.frame(maxWidth:.infinity)}.navigationTitle(Text("標(biāo)題"))}}}

布局優(yōu)先級layoutPriority

所有視圖的默認(rèn)優(yōu)先級均為0

structContentView:View{varbody:someView{HStack(spacing:16){Text("Hello")Text("World")// 布局優(yōu)先級layoutPriority空扎,所有視圖的默認(rèn)優(yōu)先級均為0Text("哈哈哈哈哈哈哈").layoutPriority(1)}.font(.largeTitle).lineLimit(1)}}

image.png

Path繪制線

structContentView:View{var body:some View{Path({pathinpath.move(to:CGPoint(x:200,y:100))path.addLine(to:CGPoint(x:100,y:300))path.addLine(to:CGPoint(x:300,y:300))path.addLine(to:CGPoint(x:200,y:100))})// style - StrokeStyle用來控制每條線的連接方式.stroke(Color.blue.opacity(0.5),style:StrokeStyle(lineWidth:5,lineCap:.round,lineJoin:.round,miterLimit:20,dash:[15],dashPhase:55))}}

image.png

stride使用

從起始值以指定值步幅到結(jié)束值的序列

從0度到360度藏鹊,每22.5度一步生成一個(gè)序列stride(from:0,to:CGFloat.pi*2,by:CGFloat.pi/8)

stroke繪制邊框

Circle().stroke(Color.blue,lineWidth:4).padding(100)

image.png

循環(huán)繪制形狀

structContentView:View{@StateprivatevarpetalOffset=-20.0@StateprivatevarpetalWidth=100.0varbody:someView{VStack{Flower(petalOffset:petalOffset,petalWidth:petalWidth).fill(// 填充漸變色AngularGradient(gradient:Gradient(colors:[.red,.yellow,.green,.blue,.purple,.red]),center:.center),// eoFill: 奇偶填充style:FillStyle(eoFill:true))Text("Offset")Slider(value:$petalOffset,in:-40...40).padding([.horizontal,.bottom])Text("Width")Slider(value:$petalWidth,in:0...100).padding(.horizontal)}.padding(20)}}structFlower:Shape{// 花瓣移離中心多少距離varpetalOffset:Double=-20// 每片花瓣的寬度varpetalWidth:Double=100funcpath(inrect:CGRect)->Path{// 容納所有花瓣的路徑varpath=Path()// 從0向上計(jì)數(shù)到 pi * 2,每次移動(dòng) pi / 8fornumberinstride(from:0,to:CGFloat.pi*2,by:CGFloat.pi/8){// 根據(jù)循環(huán)旋轉(zhuǎn)當(dāng)前的花瓣letrotation=CGAffineTransform(rotationAngle:number)// 將花瓣移到我們視野的中心letposition=rotation.concatenating(CGAffineTransform(translationX:rect.width/2,y:rect.height/2))// 使用我們的屬性以及固定的Y和高度為該花瓣創(chuàng)建路徑letoriginalPetal=Path(ellipseIn:CGRect(x:CGFloat(petalOffset),y:0,width:CGFloat(petalWidth),height:rect.width/2))// 將我們的旋轉(zhuǎn)/位置變換應(yīng)用于花瓣letrotatedPetal=originalPetal.applying(position)// 將其添加到我們的主路徑path.addPath(rotatedPetal)}// 現(xiàn)在將主徑 returnreturnpath}}

image.png

ImagePaint 制作邊框和填充

structContentView:View{varbody:someView{VStack{// sourceRect 相對大小和位置的CGRect 0表示“開始”转锈,1表示“結(jié)束”// scale 使用比例尺繪制示例圖像盘寡,0.2表示該圖像的顯示尺寸為正常尺寸的1/5Text("1111").frame(width:180,height:180,alignment:.center).border(ImagePaint(image:Image("WX20210310-163132"),sourceRect:CGRect(x:0,y:0.25,width:1,height:0.5),scale:0.1),width:20)}}}

image.png

啟用Metal高性能渲染

SwiftUI默認(rèn)使用CoreAnimation來進(jìn)行渲染,但是遇到復(fù)雜的渲染撮慨,可以啟用高性能渲染Metal竿痰。

structContentView:View{@StateprivatevarcolorCycle=0.0varbody:someView{VStack{ColorCyclingCircle(amount:self.colorCycle).frame(width:300,height:300)Slider(value:$colorCycle)}}}structColorCyclingCircle:View{varamount=0.0varsteps=100varbody:someView{ZStack{ForEach(0..<steps){valueinCircle().inset(by:CGFloat(value)).strokeBorder(LinearGradient(gradient:Gradient(colors:[self.color(for:value,brightness:1),self.color(for:value,brightness:0.5)]),startPoint:.top,endPoint:.bottom),lineWidth:2)}}.drawingGroup()}funccolor(forvalue:Int,brightness:Double)->Color{vartargetHue=Double(value)/Double(self.steps)+self.amountiftargetHue>1{targetHue-=1}returnColor(hue:targetHue,saturation:1,brightness:brightness)}}

image.png

通過應(yīng)用一個(gè)稱為drawingGroup()的新修改器來解決此問題脆粥。這告訴SwiftUI,在將視圖內(nèi)容作為單個(gè)呈現(xiàn)的輸出放回到屏幕上之前影涉,應(yīng)將視圖的內(nèi)容呈現(xiàn)到屏幕外的圖像中(離屏渲染)变隔,這要快得多。在幕后蟹倾,該功能由Metal提供支持匣缘,Metal是Apple的框架,可直接與GPU協(xié)同工作以實(shí)現(xiàn)極快的圖形鲜棠。

重要提示:drawingGroup()修飾符有助于了解和保留您的武器庫肌厨,這是解決性能問題的一種方法,但是您不應(yīng)該經(jīng)常使用它豁陆。添加屏幕外渲染過程可能會降低SwiftUI進(jìn)行簡單繪圖的速度柑爸,因此,在嘗試引入drawingGroup()之前盒音,應(yīng)等到遇到實(shí)際性能問題后再進(jìn)行操作表鳍。

74、實(shí)現(xiàn)實(shí)施模糊祥诽、混合模式进胯、飽和度調(diào)整等效果

SwiftUI使我們能夠出色地控制視圖的呈現(xiàn)方式,包括應(yīng)用實(shí)時(shí)模糊原押,混合模式胁镐,飽和度調(diào)整等功能。

混合模式使我們可以控制一個(gè)視圖在另一個(gè)視圖上的渲染方式诸衔。默認(rèn)模式是.normal盯漂,它只是將新視圖中的像素繪制到后面的任何東西上,但是有很多選項(xiàng)可以控制顏色和不透明度笨农。

structContentView:View{@StateprivatevarcolorCycle=0.0varbody:someView{VStack{ZStack{Image("demo1")Rectangle().fill(Color.blue)// blendMode圖像混合模式 默認(rèn)normal.blendMode(.softLight).frame(width:500,height:500,alignment:.center)}Image("demo1").colorMultiply(.yellow)}}}

image.png

之所以命名為“Multiply”就缆,是因?yàn)樗鼘⒚總€(gè)源像素顏色與目標(biāo)像素顏色相乘——在我們的示例中,是圖像的每個(gè)像素和頂部的矩形的每個(gè)像素谒亦。每個(gè)像素具有RGBA的顏色值竭宰,范圍從0(沒有該顏色)到1(所有顏色),因此所得的最高顏色為1x1份招,最低的顏色為0x0切揭。

對純色使用乘法會產(chǎn)生一種非常常見的色調(diào)效果:黑色保持黑色(因?yàn)樗鼈兊念伾禐?,所以無論您將頂部乘以0都將產(chǎn)生0)锁摔,而較淺的顏色會變成各種陰影著色廓旬。

混合模式screen,它的作用與乘法相反:將顏色反轉(zhuǎn)谐腰,執(zhí)行乘法孕豹,然后再次反轉(zhuǎn)顏色涩盾,從而產(chǎn)生較亮的圖像而不是較暗的圖像。

常用用法:.colorMultiply(Color.red)

structContentView:View{@Stateprivatevaramount:CGFloat=0.0varbody:some View{VStack{ZStack{Circle()//? ? ? ? ? ? ? ? ? ? .fill(Color.red).fill(Color(red:1,green:0,blue:0)).frame(width:200*amount).offset(x:-50,y:-80).blendMode(.screen)Circle()//? ? ? ? ? ? ? ? ? ? .fill(Color.green).fill(Color(red:0,green:1,blue:0)).frame(width:200*amount).offset(x:50,y:-80).blendMode(.screen)Circle()//? ? ? ? ? ? ? ? ? ? .fill(Color.blue).fill(Color(red:0,green:0,blue:1)).frame(width:200*amount).blendMode(.screen)}.frame(width:300,height:300)Slider(value:$amount).padding()}.frame(maxWidth:.infinity,maxHeight:.infinity).background(Color.black).edgesIgnoringSafeArea(.all)}}

image.png

模糊效果

structContentView:View{@Stateprivatevaramount:CGFloat=0.0varbody:some View{VStack{// 模糊效果Image("demo1").resizable().scaledToFit().frame(width:200,height:200).saturation(Double(amount))// 飽和度励背,用于調(diào)整顏色的數(shù)量.blur(radius:(1-amount)*20)Slider(value:$amount).padding()}}}

image.png

75春霍、edgesIgnoringSafeArea邊界忽略safeArea安全區(qū)域

76、Shape形狀設(shè)置動(dòng)畫(單個(gè)動(dòng)畫變量)

structContentView:View{@StateprivatevarinsetAmount:CGFloat=50@Stateprivatevarrows=4@Stateprivatevarcolumns=4varbody:someView{Trapezoid(insetAmount:insetAmount).frame(width:200,height:100).onTapGesture{// 添加動(dòng)畫withAnimation(.linear(duration:1)){self.insetAmount=CGFloat.random(in:10...90)}}}}structTrapezoid:Shape{varinsetAmount:CGFloat// 使用 animatableData 給形狀設(shè)置動(dòng)畫varanimatableData:CGFloat{get{insetAmount}set{self.insetAmount=newValue}}funcpath(inrect:CGRect)->Path{varpath=Path()path.move(to:CGPoint(x:0,y:rect.maxY))path.addLine(to:CGPoint(x:insetAmount,y:rect.minY))path.addLine(to:CGPoint(x:rect.maxX-insetAmount,y:rect.minY))path.addLine(to:CGPoint(x:rect.maxX,y:rect.maxY))path.addLine(to:CGPoint(x:0,y:rect.maxY))returnpath}}

這里發(fā)生的事情非常復(fù)雜:當(dāng)我們使用withAnimation()時(shí)叶眉,SwiftUI會立即將狀態(tài)屬性更改為其新值终畅,但在幕后,隨著動(dòng)畫的進(jìn)行竟闪,它還在跟蹤隨時(shí)間的值變化。隨著動(dòng)畫的進(jìn)行杖狼,SwiftUI會將 Shape 的animatableData屬性設(shè)置為最新值炼蛤,這取決于我們來決定這意味著什么——在本例中,我們將其直接分配給insetAmount蝶涩,因?yàn)檫@就是我們要進(jìn)行動(dòng)畫處理的東西理朋。

記住,SwiftUI在應(yīng)用動(dòng)畫之前先評估視圖狀態(tài)绿聘,然后再應(yīng)用動(dòng)畫嗽上。可以看到我們最初有評估為Trapezoid(insetAmount:50)的代碼熄攘,但是在選擇了一個(gè)隨機(jī)數(shù)之后兽愤,我們最終得到了(例如)Trapezoid(insetAmount:62)。因此挪圾,它將在動(dòng)畫的整個(gè)長度內(nèi)插值50到62浅萧,每次將形狀的animatableData屬性設(shè)置為最新的插值:51、52哲思、53洼畅,依此類推,直到達(dá)到62棚赔。

77帝簇、Shape形狀設(shè)置動(dòng)畫(多個(gè)動(dòng)畫變量)

structContentView:View{@Stateprivatevarrows=4@Stateprivatevarcolumns=4varbody:someView{Checkerboard(rows:rows,columns:columns).onTapGesture{// 添加動(dòng)畫withAnimation(.linear(duration:1)){self.rows=8self.columns=16}}}}structCheckerboard:Shape{varrows:Intvarcolumns:IntpublicvaranimatableData:AnimatablePair<Double,Double>{get{AnimatablePair(Double(rows),Double(columns))}set{self.rows=Int(newValue.first)self.columns=Int(newValue.second)}}funcpath(inrect:CGRect)->Path{varpath=Path()// 計(jì)算每行/列需要多大letrowSize=rect.height/CGFloat(rows)letcolumnSize=rect.width/CGFloat(columns)// 循環(huán)遍歷所有行和列,從而使交替的正方形變?yōu)椴噬玣orrowin0..<rows{forcolumnin0..<columns{if(row+column).isMultiple(of:2){// 這個(gè)正方形應(yīng)該是彩色的靠益;在此處添加一個(gè)矩形letstartX=columnSize*CGFloat(column)letstartY=rowSize*CGFloat(row)letrect=CGRect(x:startX,y:startY,width:columnSize,height:rowSize)path.addRect(rect)}}}returnpath}}

78丧肴、為@Published包裝器添加Codable支持

使用:

importFoundationclassUser:ObservableObject,Codable{@Publishedvarname="xixi"enumCodingKeys:CodingKey{casename}requiredinit(from decoder:Decoder)throws{// decoder包含了所有的數(shù)據(jù)letcontainer=trydecoder.container(keyedBy:CodingKeys.self)name=trycontainer.decode(String.self,forKey:.name)}funcencode(to encoder:Encoder)throws{varcontainer=encoder.container(keyedBy:CodingKeys.self)trycontainer.encode(name,forKey:.name)}}

原因:使用@Published包裝后的屬性,被包裝在了另外一個(gè)類型中胧后,這個(gè)類型包含一些其他的功能闪湾。比如Published<String>,是一個(gè)包含字符串的可發(fā)布的對象绩卤。

79途样、使用URLSession和URLRequest請求數(shù)據(jù)

structContentView:View{@Stateprivatevarresults=[Result]()varbody:someView{List(results,id:\.trackId){iteminVStack(alignment:.leading){Text(item.trackName).font(.headline)Text(item.collectionName)}}.onAppear(perform:loadData)}funcloadData(){guardleturl=URL(string:"https://itunes.apple.com/search?term=taylor+swift&entity=song")else{print("Invalid URL")return}letrequest=URLRequest(url:url)URLSession.shared.dataTask(with:request){(data,response,error)inifletdata=data{ifletresponseData=try?JSONDecoder().decode(Response.self,from:data){DispatchQueue.main.async{self.results=responseData.results}}}}.resume()}}structContentView_Previews:PreviewProvider{staticvarpreviews:someView{ContentView()}}structResponse:Codable{varresults:[Result]}structResult:Codable{vartrackId:IntvartrackName:StringvarcollectionName:String}

80江醇、disabled來控制控件是否可用

struct ContentView:View{@Stateprivatevarusername=""@Stateprivatevaremail=""varbody:some View{Form{Section{TextField("Username",text:$username)TextField("Email",text:$email)}Section{Button("Create account"){print("Creating account…")}.disabled(username.isEmpty||email.isEmpty)}}}}

image.png

81、使用@Binding創(chuàng)建自定義視圖何暇,實(shí)現(xiàn)雙向綁定

structPushButton:View{lettitle:String@BindingvarisOn:BoolvaronColors=[Color.red,Color.yellow]varoffColors=[Color(white:0.6),Color(white:0.4)]varbody:someView{Button(title){self.isOn.toggle()}.padding().background(LinearGradient(gradient:Gradient(colors:isOn?onColors:offColors),startPoint:.leading,endPoint:.trailing)).foregroundColor(.white).clipShape(Capsule()).shadow(radius:10)}}

struct ContentView:View{@StateprivatevarrememberMe=falsevarbody:some View{NavigationView{List{PushButton(title:"Remember Me",isOn:$rememberMe)Text(rememberMe?"開啟":"關(guān)閉")}.navigationBarTitle("Cupcake Corner")}}}

如果不使用@Binding陶夜,外部頁面中,使用外部頁面的屬性創(chuàng)建自定義頁面裆站,只是傳入自定義頁面參數(shù)条辟,傳入后,里面值的改變并不會傳遞到外面宏胯。

自定義頁面中羽嫡,將需要綁定的屬性使用@Binding修飾符,綁定外部頁面的屬性肩袍,將自定義頁面中的值的改變傳遞到外部頁面中杭棵,同步改變。

82氛赐、使用CoreData來增刪改查數(shù)據(jù)

相關(guān)文章:SwiftUI CoreData入門概念和基礎(chǔ)大全

創(chuàng)建持久化控制器單例PersistenceController魂爪,并創(chuàng)建初始化持久化容器NSPersistentContainer

importCoreDatastructPersistenceController{staticletshared=PersistenceController()staticvarpreview:PersistenceController={letresult=PersistenceController(inMemory:true)letviewContext=result.container.viewContextfor_in0..<10{letnewItem=Item(context:viewContext)newItem.timestamp=Date()}do{tryviewContext.save()}catch{// Replace this implementation with code to handle the error appropriately.// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.letnsError=errorasNSErrorfatalError("Unresolved error\(nsError),\(nsError.userInfo)")}returnresult}()letcontainer:NSPersistentContainerinit(inMemory:Bool=false){container=NSPersistentContainer(name:"CoreDataSwiftUIDemo")ifinMemory{container.persistentStoreDescriptions.first!.url=URL(fileURLWithPath:"/dev/null")}container.loadPersistentStores(completionHandler:{(storeDescription,error)inifleterror=errorasNSError?{// Replace this implementation with code to handle the error appropriately.// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development./*

? ? ? ? ? ? ? ? Typical reasons for an error here include:

? ? ? ? ? ? ? ? * The parent directory does not exist, cannot be created, or disallows writing.

? ? ? ? ? ? ? ? * The persistent store is not accessible, due to permissions or data protection when the device is locked.

? ? ? ? ? ? ? ? * The device is out of space.

? ? ? ? ? ? ? ? * The store could not be migrated to the current model version.

? ? ? ? ? ? ? ? Check the error message to determine what the actual problem was.

? ? ? ? ? ? ? ? */fatalError("Unresolved error\(error),\(error.userInfo)")}})}}

在app中創(chuàng)建全局持久化控制器,并將持久化控制器的持久化容器的上下文注入全局環(huán)境變量中

importSwiftUI@mainstructCoreDataSwiftUIDemoApp:App{letpersistenceController=PersistenceController.sharedvarbody:someScene{WindowGroup{ContentView().environment(\.managedObjectContext,persistenceController.container.viewContext)}}}

創(chuàng)建項(xiàng)目同名的DataModel文件艰管,后綴為.xcdatamodelId滓侍,創(chuàng)建entity

image.png

從全局環(huán)境變量中取出上下文

@Environment(\.managedObjectContext) private var viewContext

使用@FetchRequest裝飾器,從數(shù)據(jù)庫中讀取指定entity的數(shù)據(jù)列表

@FetchRequest(sortDescriptors:[NSSortDescriptor(keyPath:\Item.timestamp,ascending:true)],animation:.default)privatevaritems:FetchedResults<Item>

往coredata添加數(shù)據(jù)牲芋,通過從全局環(huán)境變量中獲取到的上下文撩笆,創(chuàng)建對象

privatefuncaddItem(){withAnimation{letnewItem=Item(context:viewContext)newItem.timestamp=Date()do{tryviewContext.save()}catch{// Replace this implementation with code to handle the error appropriately.// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.letnsError=errorasNSErrorfatalError("Unresolved error\(nsError),\(nsError.userInfo)")}}}

刪除coredata數(shù)據(jù)

privatefuncdeleteItems(offsets:IndexSet){withAnimation{offsets.map{items[$0]}.forEach(viewContext.delete)do{tryviewContext.save()}catch{// Replace this implementation with code to handle the error appropriately.// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.letnsError=errorasNSErrorfatalError("Unresolved error\(nsError),\(nsError.userInfo)")}}}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市缸浦,隨后出現(xiàn)的幾起案子浇衬,更是在濱河造成了極大的恐慌,老刑警劉巖餐济,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耘擂,死亡現(xiàn)場離奇詭異,居然都是意外死亡絮姆,警方通過查閱死者的電腦和手機(jī)醉冤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來篙悯,“玉大人蚁阳,你說我怎么就攤上這事「胝眨” “怎么了螺捐?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我定血,道長赔癌,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任澜沟,我火速辦了婚禮灾票,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘茫虽。我一直安慰自己刊苍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布濒析。 她就那樣靜靜地躺著正什,像睡著了一般。 火紅的嫁衣襯著肌膚如雪号杏。 梳的紋絲不亂的頭發(fā)上婴氮,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天,我揣著相機(jī)與錄音馒索,去河邊找鬼。 笑死名船,一個(gè)胖子當(dāng)著我的面吹牛绰上,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播渠驼,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼蜈块,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了迷扇?” 一聲冷哼從身側(cè)響起百揭,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜓席,沒想到半個(gè)月后器一,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡厨内,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年祈秕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片雏胃。...
    茶點(diǎn)故事閱讀 37,989評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡请毛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出瞭亮,到底是詐尸還是另有隱情方仿,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站仙蚜,受9級特大地震影響此洲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜鳍征,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一黍翎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧艳丛,春花似錦匣掸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至戴差,卻和暖如春送爸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背暖释。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工袭厂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人球匕。 一個(gè)月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓纹磺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親亮曹。 傳聞我的和親對象是個(gè)殘疾皇子橄杨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容