SwiftUI 宣告式程式设计的前端IOS编程语言
工程结构 ContentView.swift为入口文件
Assets存放静态资源
HabeetApp为启动入口
ContentView 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import  SwiftUIstruct  ContentView : View  {    var  body: some  View  {         VStack  {             Image (systemName: "globe" )                 .imageScale(.large)                 .foregroundColor(.accentColor)             Text ("Hello, world!" )                          }         .padding()     } } struct  ContentView_Previews : PreviewProvider  {    static  var  previews: some  View  {         ContentView ()       		  .previewDevice(PreviewDevice (rawValue: "iPhone 12 Pro" ))              .previewDisplayName("iPhone 12 Pro" )	       			.previewInterfaceOrientation(.landscapeLeft)          HomeView ()     } } 
 
HabeetApp 1 2 3 4 5 6 7 8 9 10 11 import  SwiftUI@main struct  HabeetApp : App  {    var  body: some  Scene  {         WindowGroup  {             ContentView ()             HomeView ()         }     } } 
 
视图(View) @ViewBuilder可以解决优化视图之后无返回值的问题
Text 1 2 3 4 5 6 7 Text ("Stay Hungry. Stay Foolish." )    .fontWeight(.bold)     .font(.title)	 		.font(.system(size: 50 ))	 		.minimumScaleFactor(0.7 )	     .lineLimit(1 )	 		.foregroundColor(.indigo)	 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 			 Button (role: .none, action: {     }, label: {        Text ("开始" ) }) .padding(.top,30 )   .foregroundColor(.white) .background(Color .purple) .cornerRadius(20 )  .buttonStyle(.borderedProminent)   .buttonStyle(.plain)   Button {     }label: {      } 
 
Image 1 2 3 Image ("user1" )    .resizable() 		.scaledToFit() 
 
Vertical Stack(VStack 垂直堆叠视图) 作用为把子视图排列成一个垂直的堆栈(默认不可见,相当于css里的display:block)
1 2 3 4 5 VStack (spacing: 20 ) {  .   .   . } 
 
Horizontal Stack(HStack 水平堆叠视图) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 HStack  {  .   .   . } .padding(.horizontal, 20 )  HStack (alignment: .bottom, spacing: 10 ) {  .   .   . } 
 
ZStack(Z轴堆叠视图) 越靠近下面的,在z轴上越高
1 2 3 4 5 6 ZStack {     Text ("最底部" )   Text ("中间" )   Text ("最顶部" ) } 
 
Rectangle Rectangle() 是 SwiftUI 中的一个视图类型,用于创建一个矩形形状的视图
注意在overlay与Rectangle等视图联系时,在overlay里使用foreach等类似语句,不会报相应的foreach里参数使用错误,而是会报错:Type ‘() -> ()’ cannot conform to ‘ShapeStyle’,会导致我们找错报错的方向(卡了半个多小时,QAQ)
1 2 3 4 Rectangle ()    .frame(width: 100 , height: 50 )     .foregroundColor(Color .blue) 
 
Circle 1 2 3 Circle ()	.fill(Color .blue)		 	.frame(width: 100 , height: 100 )  
 
Spacer 「留白」(Spacer )的 SwiftUI 特殊元件,留白視圖是一個沒有內容的視圖,它在堆疊視圖中占用儘可能多的空間。例如:當你將留白視圖放置在垂直佈局中,它會在堆疊允許的範圍內垂直擴展。
1 2 Spacer ()	.layoutPriority(1 ) 
 
TextField 输入框
1 2 3 4 5 6 7 8 TextField ("请输入备注" , text: $textInput )    .font(Font .system(size: 16 , weight: .bold))      .padding(EdgeInsets (top: 15 , leading: 15 , bottom: 15 , trailing: 15 ))      .background(Color (UIColor (red: 250 / 255 , green: 250 / 255 , blue: 255 / 255 , alpha: 1 )))      .cornerRadius(22.5 )      .textFieldStyle(PlainTextFieldStyle ())  
 
TabView 轮播图
注意 这个currentIndex = (currentIndex + 1) % items.count里面的 items.count不能为0否则报错(卡了我半小时,主要是swiftui项目崩溃的日志实在是又长又没有重点😡)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @State  private  var  currentIndex =  0 let  timer =  Timer .publish(every: 3 , on: .main, in: .common).autoconnect()let  items =  [        ("loginNavLogo1" , "兑换\n 商店积分" ),         ("loginNavLogo2" , "发现\n 自我进步" ),         ("loginNavLogo3" , "建立\n 计时标签" ),         ("loginNavLogo4" , "建立\n 你的目标" ) ] TabView (selection: $currentIndex ) {       	     ForEach (items.indices, id: \.self ) { index in          VStack (spacing: 10 ) {             Image (items[index].0 )                 .resizable()                 .scaledToFit()                 .frame(width: 400 , height: 380 )             HStack  {                 Text (items[index].1 )                     .font(.title)                     .multilineTextAlignment(.leading)                 Spacer ()             }         }                           .tag(index)     } } .tabViewStyle(PageTabViewStyle (indexDisplayMode: .automatic)) .indexViewStyle(PageIndexViewStyle (backgroundDisplayMode: .always)) .onReceive(timer) {_  in           print ("Timer triggered" )          currentIndex =  (currentIndex +  1 ) %  items.count } 
 
1 2 3 4 5 6 7 ScrollView (.vertical, showsIndicators: false ) {    ForEach (tagWithTime.indices, id: \.self ) { index in           TagItemView (tagTimeIndex:tagTimeIndex)             .frame(maxWidth: .infinity)     } } 
 
Picker 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 .sheet(isPresented: $showScorePicker ) {     Picker ("分数" , selection: $selectedScore ) {         ForEach (1 ... 8 , id: \.self ) { score in              Text ("\(score)  Point" )         }     }     .pickerStyle(WheelPickerStyle ())   	     .presentationDetents([.fraction(0.4 ),.medium,.large])     .edgesIgnoringSafeArea(.all)          Button  {         showScorePicker.toggle()     }label: {         Text ("完成" )             .foregroundColor(Color .white)     }         .frame(width: 100 ,height: 40 )         .background(Color .indigo)         .cornerRadius(12 )         .padding(.top,30 ) } 
 
DatePicker 关键在于.datePickerStyle(GraphicalDatePickerStyle()),通过这个修饰符出来的样式是好看的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 .sheet(isPresented: $showDatePicker ) {     VStack  {         DatePicker (             selection: $selectedDate ,             in: Date ()...          ) {             Text ("选择时间" )         }       	         .datePickerStyle(GraphicalDatePickerStyle ())         .labelsHidden()         .presentationDetents([.fraction(0.6 ),.large])             .edgesIgnoringSafeArea(.all)                  Button  {             showDatePicker.toggle()         }label: {             Text ("完成" )                 .foregroundColor(Color .white)         }         .frame(width: 100 ,height: 40 )         .background(Color .indigo)         .cornerRadius(12 )         .padding(.top,30 )              }     .padding()     .background(Color .white)     .cornerRadius(15 ) } 
 
视图修饰符(View Modifier) 本质上就是一个苹果为我们提供好的实例里的方法
注意每次使用视图修饰符时,SwiftUI都会在后台创建一个新的原始视图的修饰版本,所以视图修饰符的前后位置也很关键,视图修饰符不同的位置会改变其样式,同时在使用视图修饰符后返回的值也都有所不同(肯返回some View或者text),而部分视图修饰符又要求了它接受的值,所以顺序不同可能会造成类型的bug
background 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 .background {     Image ("background" )         .resizable()         .ignoresSafeArea() } .background( 		LinearGradient (         gradient: Gradient (             colors: [                 Color (red: 142 / 255 , green: 150 / 255 , blue: 255 / 255 ),                 Color (red: 108 / 255 , green: 93 / 255 , blue: 211 / 255 )             ]         ),       	         startPoint: .trailing,         endPoint: .leading     ) ) 
 
padding padding设置在frame前面可以达到不改变设定宽高,只是内部改变padding
1 2 .padding(.horizontal, 20 )  .padding(.top,30 )   
 
frame frame只是把一个视图限制在一定的宽度和高度之中,并不会改变视图本身的大小,但是可以利用子视图来撑大父视图,使其大小改变
1 2 3 .frame(width: 200 )  .frame(maxWidth: .infinity)	 .frame(width: 100 ,alignment: .leading)	 
 
boder 1 .border(Color .red,width: 2 ) 
 
opacity  
multilineTextAlignment 文字对齐方式(主要返回的some View)
1 2 3 .multilineTextAlignment(.leading)	 .multilineTextAlignment(.center)	 .multilineTextAlignment(.trailing)	 
 
lineSpacing 行间距,增加文字行与行之间点距离
 
front 1 2 3 4 .font(.title) .font(.title2) .font(.title3) .font(.footnote) 
 
不同字体在默认状态下的大小(单位为px)
 
kerning 文本里文字之间的间距
1 2 .kerning(2.0 )   .kerning(- 1.0 )   
 
fontWeight 文字字重
1 2 .fontWeight(.black)   .fontWeight(.bold) 
 
alert 按钮下的方法
注意这个$alertIsVisible如果在foreeach里的视图组件使用的话,不要把它作为binding参数传进来,否则alertIsVisible为true后会同时唤出多个alert,导致alert里的参数传递会出错,应该把alertIsVisible作为视图组件的private变量,确保在foreach循环里,每个视图组件的变量alertIsVisible都私有(卡了我一晚上,本来是玩博德之门3的啊啊啊啊啊啊啊啊QAQ) 还有要注意的是,如果想在一个视图里使用多个alert,靠增加.alert的数量是没用的,应该通过返回多个Alert达到增加alert数量的结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 .alert(isPresented: $isShowingAlert ) {     Alert (         title: Text ("确定要删除吗?" ),          message: Text ("删除后将会从您的标签移除数据" ),          primaryButton: .default(Text ("确定" ), action: {                          deleteTag(tagName: tagName,tagTimeIndex:tagTimeIndex)                          }),         secondaryButton: .cancel(Text ("取消" ))      ) } .alert(isPresented: $time ) {     if  timeStop {         return  Alert (             title: Text ("确定要放弃吗?" ),              message: Text ("本次计时将不会得到任何分数" ),              primaryButton: .default(Text ("确定" ), action: {                                  resetCountdown()               	                 time =  false              }),             secondaryButton: .cancel(Text ("取消" ))          )     } else  {         return  Alert (             title: Text ("计时结束" ),              message: Text ("本次计时获得\(tagWithTime[selectedTagIndex].tagPoint! ) Points" ),              primaryButton: .default(Text ("确定" ), action: {                                  resetCountdown()               	                 time =  false              }),             secondaryButton: .cancel(Text ("取消" ))          )     } } 
 
animation 如果在大的动画里有部分动画不满意,可以直接在那个部分里再加一份动画,或者动画为none,也可以加上不同的id来是SwiftUI意识到这是不同的View,还有在View里加上.transition可以控制不同的动画效果,也可以用.withAnimation精确控制动画出现的时机
1 2 					 .animation(.easeInOut, value: ifShowTargetMenu) 
 
controlSize 任何视图都可以使用,但是只有苹果给的预设
 
layoutPriority 排版的优先顺序,默认都为0
 
transition 1 .transition(.move(edge: .top ).combined(with: .opacity))	 
 
cornerRadius  
toggle 翻转Bool变量的值
 
ignoresSafeArea 在进行背景颜色修改时,手机底部和顶部无法正确上色,可以使用这个Modifier
 
onTapGesture 在视图的顶端点击后的动作
1 2 3 .onTapGesture { 	ifShowMenu.toggle()		 } 
 
colorMultiply 将整个图片的颜色变暗为黑色
1 2 Image ("targetBefore" )    .colorMultiply(.black) 
 
shadow 增加阴影效果
1 .shadow(color: Color .black.opacity(0.2 ), radius: 10 , x: 0 , y: 5 )  
 
overlay alignment:可以控制8个角的放置的位置,下面的示例固定在右下角
注意在overlay与Rectangle等视图联系时,在overlay里使用foreach等类似语句,不会报相应的foreach里参数使用错误,而是会报错:Type ‘() -> ()’ cannot conform to ‘ShapeStyle’,会导致我们找错报错的方向(卡了半个多小时,QAQ)
1 2 3 4 5 VStack {    Text ("被重叠的主视图" )               }.overlay(alignment:.bottomTrailing) {     Text ("覆盖的内容" ) } 
 
onAppear 注意子组件里的onAppear在父组件是无效的,里面的内容不会执行
1 2 3 4 .onAppear {      self .onDateSelected? (self .selectedDate) } 
 
sheet 通过presentationDetents控制大小(注意presentationDetents要放在sheet里面才有效,以及该修饰符IOS16才适配)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 .sheet(isPresented: $showScorePicker ) {     Picker ("分数" , selection: $selectedScore ) {         ForEach (1 ... 8 , id: \.self ) { score in              Text ("\(score)  Point" )         }     }     .pickerStyle(WheelPickerStyle ())   	     .presentationDetents([.fraction(0.4 ),.medium,.large])     .edgesIgnoringSafeArea(.all)          Button  {         showScorePicker.toggle()     }label: {         Text ("完成" )             .foregroundColor(Color .white)     }         .frame(width: 100 ,height: 40 )         .background(Color .indigo)         .cornerRadius(12 )         .padding(.top,30 ) } 
 
实践 毛玻璃效果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 if  ifShowMenu {  	 		 		 		     VisualEffectView (effect: UIBlurEffect (style: .light))         .ignoresSafeArea()                 Color .white.opacity(0.3 )          .ignoresSafeArea() } struct  VisualEffectView : UIViewRepresentable  {    var  effect: UIVisualEffect ?     func  makeUIView (context : UIViewRepresentableContext <Self >) -> UIVisualEffectView  {         UIVisualEffectView ()     }     func  updateUIView (_  uiView : UIVisualEffectView , context : UIViewRepresentableContext <Self >) {         uiView.effect =  effect     } } 
 
视图宽度固定为设备的一半,并置于左侧 难点在于直接使用frame固定尺寸的话,是达不到置于左侧的效果的
同时下面的例子还使得图片固定在左上角
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 if  ifShowMenu {    DrawerMenu (isDrawerOpen: $ifShowMenu ) } struct  DrawerMenu : View  {    @Binding  var  isDrawerOpen: Bool      var  body: some  View  {       	         GeometryReader  { geometry in              VStack (alignment: .leading) {                 Image ("Avatar" )                     .resizable()                     .scaledToFit()                     .frame(width: 40 , height: 40 )                     .alignmentGuide(HorizontalAlignment .leading) { _  in                          geometry.size.width /  2                       }                     .padding(.leading,30 ).padding(.bottom,10 )               	Spacer ()             }             .frame(width: geometry.size.width /  2 , height: geometry.size.height+ 40 )              .background(Color .white)         }     } } 
 
两种导航方式 NavigationStack 这种方法会在左上角留下back的返回字样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 NavigationStack {  ZStack {       Button {           ifShowTarget= true        }label: {                      HStack {               Image ("targetBefore" )                   .resizable()                   .scaledToFit()                   .frame(width: 30 , height: 30 )                   .padding(.leading,5 )               Text ("目标" )                   .frame(width: 120 ,alignment: .leading)           }       }          NavigationLink ("" , destination: TargetView (), isActive: $ifShowTarget )   } } NavigationStack {  ZStack {          NavigationLink (destination: TargetView ()){ 			Text ("点击跳转" )	     }   } } 
 
fullScreenCover 这种方法是在当前页面直接开一个新的视图,比较符合常规的导航
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 Button {    ifShowTarget= true  }label: {          HStack {         Image ("targetBefore" )             .resizable()             .scaledToFit()             .frame(width: 30 , height: 30 )             .padding(.leading,5 )         Text ("目标" )             .frame(width: 120 ,alignment: .leading)     } } if  ifShowTarget{    NullView ()         .fullScreenCover(isPresented: $ifShowTarget , content: {                          TargetView ()         }) } 
 
API网络请求 POST请求 第一步是建立好结构体接收传回来的参数,可以通过postman获取到传回来的JSON数据,根据数据进行书写
第二步建立urlRequest,并给出链接的设定,包括请求方式(httpMethod),请求头(header),请求参数(httpBody),其中请求参数这里需要转化为Data类型的数据,如果是直接传入String则使用userEmail.data(using: .utf8)转化,如果是json数据则进行转化:
1、let requestData = [“userEmail”: userEmail]
2、let jsonData = try JSONSerialization.data(withJSONObject: requestData)
第三步是创建 URLSession 数据任务,传回来的数据也要通过解码:
JSONDecoder().decode(ResponseData.self, from: data) 最后还有处理各种出错情况
注意下面的例子只是将类型粗糙的分类,具体怎么请求最主要要是要看后端的api的需求
例子1 在大体上该例子常用于获取数组类型的数据
JSON格式 该格式里的data里最外围是[ ]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 {     "code" :  "00000" ,      "message" :  "一切 ok" ,      "data" :  [          {              "id" :  null ,              "userId" :  null ,              "userEmail" :  null ,              "targetName" :  "测试1" ,              "targetDescribe" :  "hhh" ,              "targetColor" :  null ,              "targetPoint" :  "3" ,              "deadline" :  null ,              "status" :  "0" ,              "deadlineString" :  null ,              "ifPoints" :  null ,              "ifTargetNull" :  null ,              "ifTargetUpdate" :  null ,              "targetId" :  "1692785063700615169"          } ,          {              "id" :  null ,              "userId" :  null ,              "userEmail" :  null ,              "targetName" :  "测试2" ,              "targetDescribe" :  "hhh" ,              "targetColor" :  null ,              "targetPoint" :  "7" ,              "deadline" :  null ,              "status" :  "0" ,              "deadlineString" :  null ,              "ifPoints" :  null ,              "ifTargetNull" :  null ,              "ifTargetUpdate" :  null ,              "targetId" :  "1692785123469447170"          }      ] ,      "ok" :  true  } 
 
代码 在细节上该例子讲参数写死,并返回值,且传给后端的数据为text( let jsonData = userEmail.data(using: .utf8) )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 import  Foundationstruct  ResponseData : Decodable  {    let  data: [TagWithTime ]   } struct  TagWithTime : Decodable , Identifiable  {    let  id: String        let  tagName: String        let  tagDescribe: String        let  tagHour: String        let  tagMinute: String        let  tagPoint: String         } class  TagDataManager  {         func  fetchTagData (completion : @escaping  ([TagWithTime ]? , Error ?) -> Void ) {                  guard  let  url =  URL (string: "https://tengenchang.top/tag/get" ) else  {             completion(nil , nil )               return          }                           let  userEmail =  "3489044730@qq.com"                   do  {                          let  jsonData =  userEmail.data(using: .utf8)                                       var  request =  URLRequest (url: url)             request.httpMethod =  "POST"                request.setValue("application/json" , forHTTPHeaderField: "Content-Type" )             request.httpBody =  jsonData                                         URLSession .shared.dataTask(with: request) { data, response, error in                  if  let  data =  data {                     do  {                                                  let  decodedResponse =  try  JSONDecoder ().decode(ResponseData .self , from: data)                         completion(decodedResponse.data, nil )                       } catch  {                         print ("JSON decoding error: \(error) " )                         completion(nil , error)                       }                 } else  if  let  error =  error {                     completion(nil , error)                   }             }.resume()           } catch  {             completion(nil , error)           }     } } import  SwiftUIstruct  TagView : View  {    @State  private  var  ifShowMenu:Bool = false      @State  private  var  ifShowTarget:Bool = false      @State  private  var  showWhichView:Int = 2      @State  private  var  ifDelete:Bool = false           @State  private  var  tagWithTime: [TagWithTime ] =  []     @State  private  var  ifshowTagDetailBNull =  false            let  tagDataManager =  TagDataManager ()     var  body: some  View  {         ZStack {                          VStack {                                  NavView (ifShowMenu: $ifShowMenu ,showWhichView:$showWhichView ,ifDelete:$ifDelete )               	                 ForEach (tagWithTime, id: \.id) { tag in    TagItemView (ifDelete:$ifDelete ,isShowingAlert:$isShowingAlert ,tagName:tag.tagName,tagDescribe:tag.tagDescribe)                 }                 Spacer ()             }                    }.onAppear {              fetchTagData()         }     }     func  fetchTagData () {             tagDataManager.fetchTagData { fetchedData, error in                  if  let  fetchedData =  fetchedData {                     ifshowTagDetailBNull =  fetchedData.isEmpty                     tagWithTime =  fetchedData                 } else  {                     print ("Error fetching data: \(error? .localizedDescription ??  "Unknown error" ) " )                 }             }         } } 
 
例子2 在大体上该例子常用于删除数据
JSON格式 该格式就是后端返回的值全是null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 {     "code" : "00000" ,     "message" : "一切 ok" ,     "data" : {         "id" : null,         "userId" : null,         "picUrl" : null,         "tagName" : null,         "tagDescribe" : null,         "tagColor" : null,         "tagPoint" : null,         "tagHour" : null,         "tagMinute" : null,         "creatTime" : null,         "userEmail" : null,         "ifRepeat" : null,         "ifTagNull" : null,         "ifTagUpdate" : null,         "tagId" : null     },     "ok" : true  } 
 
代码 在细节上该例子不返回任何参数,这里data明明由{ }包裹,但是却使用[TagWithTime]类型解密,还没有报错的原因在于,该方法的并不需要访问里面的数据,completion也不返回[TagWithTime],,所以可以这么使用,不能使用[TagWithTime]类型解密的例子为例子4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 import  Foundationstruct  TagResponseData : Decodable  {    let  data: [TagWithTime ] } struct  TagWithTime : Decodable , Identifiable  {    let  id: String ?       let  tagName: String ?     let  tagDescribe: String ?     let  tagHour: String ?     let  tagMinute: String ?     let  tagPoint: String ?     let  ifTagNull:String ? } class  TagDataManager  {         func  deleteTag (tagName : String , completion : @escaping  (Error ?) -> Void ) {                  guard  let  url =  URL (string: "https://tengenchang.top/tag/delete" ) else  {             completion(nil )              return          }                  let  tagName =  tagName                  let  jsonData =  tagName.data(using: .utf8)                  var  request =  URLRequest (url: url)              request.httpMethod =  "POST"           request.setValue("application/json" , forHTTPHeaderField: "Content-Type" )         request.httpBody =  jsonData                            URLSession .shared.dataTask(with: request) { _ , _ , error in              if  let  error =  error {                 completion(error)             } else  {                 completion(nil )             }         }.resume()      } } import  SwiftUIstruct  TagItemView : View  {    let  tagDataManager =  TagDataManager ()     @Binding  var  tagWithTime:[TagWithTime ]     let  tagTimeIndex:Int           var  body: some  View  {         HStack {             if  ifDelete{                 Button {                     isShowingAlert =  true                   }label: {                     Image ("x" )                         .resizable()                         .scaledToFit()                         .frame(width: 12 ,height: 12 )                 }.alert(isPresented: $isShowingAlert ) {                     Alert (                         title: Text ("确定要删除吗?" ),                          message: Text ("删除后将会从您的标签移除数据" ),                          primaryButton: .default(Text ("确定" ), action: {                                                          deleteTag(tagName: tagName,tagTimeIndex:tagTimeIndex)                         }),                         secondaryButton: .cancel(Text ("取消" ))                      )                 }             }else {                            }         }     }     func  deleteTag (tagName :String ,tagTimeIndex :Int ){         tagDataManager.deleteTag(tagName: tagName) { error in              if  error ==  nil  {                 deleteSuccess= true                  tagNum-= 1                  if  tagNum== 0 {                     ifshowTagDetailBNull= true                  }                 tagWithTime.remove(at: tagTimeIndex)                 print ("删除标签成功" )             } else  {                 print ("删除标签失败" )             }         }     } } 
 
例子3 在大体上该例子用于检验用户是否注册,利用completion返回Int,方便用户直接跳转到登录界面或者注册界面
JSON格式 该格式就是后端的代码就是return null
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 {     "code" :  "00000" ,      "message" :  "一切 ok" ,      "data" :  {          "userId" :  null ,          "userName" :  null ,          "picData" :  null ,          "picUrl" :  null ,          "userEmail" :  "3489044730@qq.com" ,          "userPassword" :  null ,          "userCode" :  null ,          "completeTarget" :  null ,          "point" :  null ,          "ifUpdate" :  null ,          "openId" :  null      } ,      "ok" :  true  } 
 
代码 在细节上该例子传入的参数是动态的,返回简单参数如(Int),利用completion返回起来简单,且使用时直接调用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 import  Foundationstruct  UserResponseData : Decodable  {    let  code: String ? } class  UserDataManager {    func  checkEmail (email : String , completion : @escaping  (Int ) -> Void ) {         guard  let  url =  URL (string: "https://tengenchang.top/user/home" )         else  {             completion(12 )              return          }                  let  userEmail =  email                  let  jsonData =   userEmail.data(using: .utf8)                  var  request =  URLRequest (url: url)                      request.httpMethod =  "POST"           request.setValue("application/json" , forHTTPHeaderField: "Content-Type" )         request.httpBody =  jsonData                   URLSession .shared.dataTask(with: request) { data, response, error in              if  let  data =  data {                 do  {                     let  decodedData =  try  JSONDecoder ().decode(UserResponseData .self , from: data)                     if  let  code =  decodedData.code {                         DispatchQueue .main.async {                             if  code ==  "nil"  {                                 completion(12 )                             } else  {                                 completion(11 )                             }                         }                     }                 } catch  {                     print (error)                     completion(12 )                  }             }         }.resume()              } } import  SwiftUIstruct  HomeView : View  {    @EnvironmentObject  private  var  userData: UserData    	let  userDataManager =  UserDataManager ()      var  body: some  View  {         VStack (alignment: .leading,spacing: 20 ){             Button {               	                 let  isValidQQEmail =  isValidQQEmailFormat(email: userData.userEmail)                 if  isValidQQEmail{                   	                   	                     userDataManager.checkEmail(email: userData.userEmail) { retrurnShowWhichView in                          showWhichView =  retrurnShowWhichView                         timerTriggered =  true                      }                 }else {                     ifshowTextAlert= true                  }             }label: {                 HStack (){                     Text ("继续" )                 }             }         }.padding(15 )             .overlay {                 if  ifshowTextAlert{                     TextAlertView (textContant:$textContant ,ifshowTextAlert:$ifshowTextAlert )                                          }             }     } } 
 
例子4 大体上该例子作用于获取该用户的账户信息,一般不是数组
JSON格式 注意json返回的数据不是数组,且要获取里面的数据时时,不要为结构体或者类加上[]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 {     "code" : "00000" ,     "message" : "一切 ok" ,     "data" : {         "id" : null,         "userId" : null,         "point" : null,         "pointType" : null,         "pointName" : null,         "pointDescribe" : null,         "pointDate" : null,         "userEmail" : "3489044730@qq.com" ,         "userTimeP" : "过去一天" ,         "pointAll" : "0" ,         "progress" : "0" ,         "pointInsistence" : "0" ,         "pointAverage" : "0.0" ,         "completeTarget" : "0" ,         "completeTargetRate" : "0.0" ,         "ifProgress" : null     },     "ok" : true  } 
 
代码 在细节上该例子解析出来的数据放在类里面,而不是数组,注意拆包和初始化时候的使用可选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 import  Foundationstruct  PointRecordResponseData : Decodable  {    let  data: PointRecordData  } class  PointRecordData :Decodable {    let  userTimeP, pointAll, progress: String      let  pointInsistence, pointAverage, completeTarget, completeTargetRate: String       } class  UserDataManager {         func  fetchPointRecordData (userTimeP :String ,completion : @escaping  (PointRecordData ?, Error ?) -> Void ) {                  guard  let  url =  URL (string: "https://tengenchang.top/pointRecord/get" ) else  {             completion(nil , nil )              return          }                  let  userEmail =  "3489044730@qq.com"          let  userTimeP= userTimeP                           let  parameters: [String : Any ] =  ["userEmail" : userEmail, "userTimeP" : userTimeP]                  let  jsonData =  try?  JSONSerialization .data(withJSONObject: parameters)                               var  request =  URLRequest (url: url)                      request.httpMethod =  "POST"           request.setValue("application/json" , forHTTPHeaderField: "Content-Type" )         request.httpBody =  jsonData                                URLSession .shared.dataTask(with: request) { data, response, error in              if  let  data =  data {                 do  {                                          let  decodedResponse =  try  JSONDecoder ().decode(PointRecordResponseData .self , from: data)                         completion(decodedResponse.data, nil )                 } catch  {                     print ("JSON decoding error: \(error) " )                     completion(nil , error)                 }             } else  if  let  error =  error {                 completion(nil , error)             }         }.resume()      } } import  SwiftUIstruct  UserView : View  {    let  userDataManager =  UserDataManager ()           @State  private  var  userTimeP:String = "过去一周"           @State  private  var  pointRecordData:PointRecordData ?     let  pointAll:String = "10"      var  body: some  View  {         ZStack {                          VStack {                 ZStack {                     VStack (alignment: .leading){                         Text ("获取分数" )                         HStack {                             Text ((pointRecordData? .pointAll) ??  "0" )                         }                         Text ("努力的\(String(userTimeP.suffix(2 ))) !" )                     }                 }                 ZStack {                     VStack (alignment: .leading){                         Text ("进步" )                         HStack {                             Text ((pointRecordData? .progress) ??  "0" )                         }                     }                 }                                  HStack {                     VStack {                         VStack (alignment: .leading){                             HStack {                                 Text ("\((pointRecordData? .pointInsistence) ??  "0" ) \n 连续得分" )                             }                         }                         VStack (alignment: .leading){                             HStack {                                 VStack {                                     Text ("\((pointRecordData? .completeTargetRate) ??  "0.0" ) %" )                                 }                             }                         }                     }                     VStack {                         VStack (alignment: .leading){                             HStack {                                 VStack {                                     Text ((pointRecordData? .pointAverage) ??  "0.0" )                                 }                             }                         }                                                  VStack (alignment: .leading){                             HStack {                                 VStack {                                     Text ("\((pointRecordData? .completeTarget) ??  "0" ) 个目标" )                                 }                             }                         }                     }                 }             }             if   ifShowUserMenu{                                  if  userTimeP== "过去一周" {                     VStack {                         Button {                             userTimeP= "过去一天"                              fetchPointRecordData()                         }label: {                             Text ("过去一天" )                         }                         Button {                             userTimeP= "过去一月"                              fetchPointRecordData()                         }label: {                             Text ("过去一月" )                         }                     }                 }             }         }     }     func  fetchPointRecordData () {         userDataManager.fetchPointRecordData(userTimeP: userTimeP) { fetchedData, error in              if  let  fetchedData =  fetchedData {                 pointRecordData= fetchedData                 print ((pointRecordData? .pointAll)! )             } else  {                 print ("Error fetching data: \(error? .localizedDescription ??  "Unknown error" ) " )             }         }     } } 
 
自定义文字弹窗提示 因为swiftui中只有alert,且这个视图在官方的规定下是必定要有按钮的,所以为了满足项目的需求,我进行了自定义的文字弹窗提示,该弹窗还会在几秒后自动消失
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 struct  TextAlertView : View  {         @State  private  var  isTextVisible =  true           @Binding  var  textContant:String           @Binding  var  ifshowTextAlert:Bool      var  body: some  View  {         VStack  {             if  isTextVisible {                 Text (textContant)                     .foregroundColor(Color .white)                     .padding([.top,.bottom],10 )                     .padding([.leading,.trailing],15 )                     .background(Color .secondary)                     .cornerRadius(10 )                     .onAppear {                         DispatchQueue .main.asyncAfter(deadline: .now() +  0.6 ) {                             withAnimation {                                 isTextVisible =  false                              }                             ifshowTextAlert= false                          }                     }             }         }     } } 
 
foreach两种方式获得index 1、利用Array包装数组获取到index,该方法获取元素使用类似target.targetName的方式
1 2 3 4 5 6 7 8 9 ForEach (Array (targetNoTime.enumerated()), id: \.element.id) { (index, target) in     TargetItemView (targetName: target.targetName! ,                    targetDescribe: target.targetDescribe! ,                    targetId: target.targetId! ,                    targetPoint:target.targetPoint!  ,                    targetStatus: target.status! ,                    targetTimeIndex:index     ) } 
 
2、使用数组方法中的.indices,获取到index,该方法获取元素使用类似tagWithTime[index].tagName的方式
1 2 3 4 5 6 ForEach (tagWithTime.indices, id: \.self ) { index in     TagItemView (tagName: tagWithTime[index].tagName! ,                 tagDescribe:tagWithTime[index].tagDescribe! ,                 tagWithTime:$tagWithTime ,                 tagTimeIndex:index) } 
 
自定义横向日期显示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 import  SwiftUIstruct  TargetNav1View : View  {    private  let  calendar =  Calendar .current     private  let  dateFormatter: DateFormatter  =  {         let  formatter =  DateFormatter ()         formatter.dateFormat =  "E"           return  formatter     }()          @State  private  var  selectedDayIndex =  0            var  body: some  View  {         ScrollView (.horizontal, showsIndicators: false ) {             HStack (spacing: 18 ) {                 ForEach (0 ..< 30 ) { index in                      VStack  {                         Text (self .dayName(for: index))                             .foregroundColor(self .selectedDayIndex ==  index ?  .black : .secondary)                             .font(.system(size: 12 ))                          Text (self .dayNumber(for: index))                             .frame(width: 36 , height: 36 )                             .foregroundColor(Color (rgba: (207 , 200 , 255 , 1 )))                             .background(                                 LinearGradient (                                     gradient: Gradient (                                         colors: self .gradientColors(for: index)                                     ),                                     startPoint: .top,                                     endPoint: .bottom                                 )                             )                             .cornerRadius(22.5 )                             .onTapGesture {                                  self .selectedDayIndex =  index                              }                     }                     .cornerRadius(22.5 )                  }             }             .padding(.top, 20 )          }.padding([.leading,.trailing],20 )     }          private  func  dayName (for  index : Int ) -> String  {         let  currentDate =  calendar.date(byAdding: .day, value: index, to: Date ()) ??  Date ()         return  dateFormatter.string(from: currentDate)      }          private  func  dayNumber (for  index : Int ) -> String  {         let  currentDate =  calendar.date(byAdding: .day, value: index, to: Date ()) ??  Date ()         let  day =  calendar.component(.day, from: currentDate)          return  "\(day) "       }          private  func  gradientColors (for  index : Int ) -> [Color ] {         if  self .selectedDayIndex ==  index {             return  [                 Color (red: 142 / 255 , green: 150 / 255 , blue: 255 / 255 ),                 Color (red: 108 / 255 , green: 93 / 255 , blue: 211 / 255 )             ]          } else  {             return  [                 Color .clear,                 Color .clear             ]          }     } } 
 
自定义日期比较器 难点有两个,第一点是仅关注日期部分,忽略时间的影响,解决方法是把传进来的时间设置为0小时、0分钟、0秒,来忽略小时和分钟对日期差的干扰,并且用calendar.dateComponents(_:from:to:)来计算目标日期和选定日期的月份和日子的差异,第二点在于进入目标页面时间差的初值问题,解决方法是在获取数据时,直接调用求时间差的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 import  SwiftUIstruct  TargetNav1View : View  {    let  selectedDate: Date = Date ()           private  let  calendar =  Calendar .current     private  let  dateFormatter: DateFormatter  =  {         let  formatter =  DateFormatter ()         formatter.dateFormat =  "E"           return  formatter     }()          @State  private  var  selectedDayIndex =  0            var  onDateSelected: ((Date ) -> Void )?                 var  body: some  View  {         ScrollView (.horizontal, showsIndicators: false ) {             HStack (spacing: 18 ) {                 ForEach (0 ..< 30 ) { index in                      VStack  {                         Text (self .dayName(for: index))                             .foregroundColor(                                 self .selectedDayIndex ==  index ?  .black : .secondary                             )                             .font(.system(size: 12 ))                          Text (self .dayNumber(for: index))                             .frame(width: 36 , height: 36 )                             .foregroundColor(Color (rgba: (207 , 200 , 255 , 1 )))                             .background(                                 LinearGradient (                                     gradient: Gradient (                                         colors: self .gradientColors(for: index)                                     ),                                     startPoint: .top,                                     endPoint: .bottom                                 )                             )                             .cornerRadius(22.5 )                                                                                   .onTapGesture {                                 self .selectedDayIndex =  index                                  if  let  selectedDate =  self .calendar.date(byAdding: .day, value: index, to: Date ()) {                                     self .onDateSelected? (selectedDate)                                  }                                                              }                     }                     .cornerRadius(22.5 )                  }             }             .padding(.top, 20 )          }.padding([.leading,.trailing],20 )     }          private  func  dayName (for  index : Int ) -> String  {         let  currentDate =  calendar.date(byAdding: .day, value: index, to: Date ()) ??  Date ()         return  dateFormatter.string(from: currentDate)      }          private  func  dayNumber (for  index : Int ) -> String  {         let  currentDate =  calendar.date(byAdding: .day, value: index, to: Date ()) ??  Date ()         let  day =  calendar.component(.day, from: currentDate)          return  "\(day) "       }          private  func  gradientColors (for  index : Int ) -> [Color ] {         if  self .selectedDayIndex ==  index {             return  [                 Color (red: 142 / 255 , green: 150 / 255 , blue: 255 / 255 ),                 Color (red: 108 / 255 , green: 93 / 255 , blue: 211 / 255 )             ]          } else  {             return  [                 Color .clear,                 Color .clear             ]          }     } } TargetNav1View (onDateSelected: { selectedDate in     for  _  in  0 ..< targetWithTime.count {         targetDateInfo.append(TargetDateInfo (dayDifference: 0 , timeString: "" ))     }          let  calendar =  Calendar .current          for  index in  0 ..< self .targetWithTime.count {         print (index)         let  dateFormatter =  DateFormatter ()                  dateFormatter.dateFormat =  "yyyy-MM-dd'T'HH:mm:ss"                            if  let  deadlineDate =  dateFormatter.date(from: self .targetWithTime[index].deadline! ),             let  startDate =  calendar.date(bySettingHour: 0 , minute: 0 , second: 0 , of: selectedDate) {                          print ("startDate:" ,startDate)                                       let  deadlineComponents =  calendar.dateComponents([.month, .day], from: deadlineDate)                                       let  selectedComponents =  calendar.dateComponents([.month, .day], from: startDate)                                         if  let  dayDifference =  calendar.dateComponents([.day], from: selectedComponents, to: deadlineComponents).day {                 self .dayDifference =  dayDifference                 print ("index:" ,index)                 targetDateInfo[index].dayDifference =  dayDifference                 targetDateInfo[index].timeString =  ""                                   if  dayDifference <  0  {                                          let  monthDayFormatter =  DateFormatter ()                     monthDayFormatter.dateFormat =  "MM.dd"                      let  monthDayString =  monthDayFormatter.string(from: deadlineDate)                     self .monthDayString =  monthDayString                     targetDateInfo[index].timeString =  monthDayString                     print ("Month and Day: \(monthDayString) " )                 } else  if  dayDifference ==  0  {                                          let  timeFormatter =  DateFormatter ()                     timeFormatter.dateFormat =  "HH:mm"                      let  timeString =  timeFormatter.string(from: deadlineDate)                     self .timeString =  timeString                     targetDateInfo[index].timeString =  timeString                     print ("Time: \(timeString) " )                 } else  if  dayDifference >  0  {                     print ("Day difference: \(dayDifference) " )                 }             }         }     } }) 
 
三种Picker 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 .sheet(isPresented: $showScorePicker ) {     Picker ("分数" , selection: $selectedScore ) {         ForEach (1 ... 8 , id: \.self ) { score in              Text ("\(score)  Point" )         }     }     .pickerStyle(WheelPickerStyle ())   	     .presentationDetents([.fraction(0.4 ),.medium,.large])     .edgesIgnoringSafeArea(.all)          Button  {         showScorePicker.toggle()     }label: {         Text ("完成" )             .foregroundColor(Color .white)     }         .frame(width: 100 ,height: 40 )         .background(Color .indigo)         .cornerRadius(12 )         .padding(.top,30 ) } .sheet(isPresented: $showDatePicker ) {     VStack  {         DatePicker (             selection: $selectedDate ,             in: Date ()...          ) {             Text ("选择时间" )         }         .datePickerStyle(GraphicalDatePickerStyle ())         .labelsHidden()         .presentationDetents([.fraction(0.6 ), .large])         .edgesIgnoringSafeArea(.all)                  Button  {             showDatePicker.toggle()             deadlineString =  dateFormatter.string(from: selectedDate)         } label: {             Text ("完成" )                 .foregroundColor(Color .white)         }         .frame(width: 100 , height: 40 )         .background(Color .indigo)         .cornerRadius(12 )         .padding(.top, 30 )     }     .padding()     .background(Color .white)     .cornerRadius(15 ) } .sheet(isPresented: $showDatePicker ) {     VStack  {         DatePicker (             selection: $selectedDate ,             in: Date ()...          ) {             Text ("选择时间" )         }       	         .datePickerStyle(GraphicalDatePickerStyle ())         .labelsHidden()         .presentationDetents([.fraction(0.6 ),.large])             .edgesIgnoringSafeArea(.all)                  Button  {             showDatePicker.toggle()         }label: {             Text ("完成" )                 .foregroundColor(Color .white)         }         .frame(width: 100 ,height: 40 )         .background(Color .indigo)         .cornerRadius(12 )         .padding(.top,30 )              }     .padding()     .background(Color .white)     .cornerRadius(15 ) } 
 
崩溃总结 因为swiftui的崩溃日志实在是难以读懂,所以根据经验总结了一下崩溃的原因
首先是数组越界问题,比如说常见的remove之后,还在用index去访问数组的元素,就会导致数组越界,所以看来还是避免使用index下标去访问数组元素,还要注意获取数据和使用数据的先后
其次是拆包问题,常见于处理后端数据的时候,后端传进来的数据可能为空,但是我们并没有使用可选型去接收这个参数,就会导致崩溃 的产生
点击更换样式 利用onTapGesture通过点击更换index,再使用三元运算符判断更改样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 struct  TargetNav1View : View  {    let  selectedDate: Date = Date ()           private  let  calendar =  Calendar .current     private  let  dateFormatter: DateFormatter  =  {         let  formatter =  DateFormatter ()         formatter.dateFormat =  "E"           return  formatter     }()          @State  private  var  selectedDayIndex =  0            var  onDateSelected: ((Date ) -> Void )?                 var  body: some  View  {         ScrollView (.horizontal, showsIndicators: false ) {             HStack (spacing: 18 ) {                 ForEach (0 ..< 30 ) { index in                      VStack  {                         Text (self .dayName(for: index))                             .foregroundColor(                                 self .selectedDayIndex ==  index ?  .black : .secondary                             )                             .font(.system(size: 12 ))                          Text (self .dayNumber(for: index))                             .frame(width: 36 , height: 36 )                             .foregroundColor(Color (rgba: (207 , 200 , 255 , 1 )))                             .background(                                 LinearGradient (                                     gradient: Gradient (                                         colors: self .gradientColors(for: index)                                     ),                                     startPoint: .top,                                     endPoint: .bottom                                 )                             )                             .cornerRadius(22.5 )                                                                                   .onTapGesture {                                 self .selectedDayIndex =  index                                  if  let  selectedDate =  self .calendar.date(byAdding: .day, value: index, to: Date ()) {                                     self .onDateSelected? (selectedDate)                                  }                                                              }                     }                     .cornerRadius(22.5 )                  }             }             .padding(.top, 20 )          }.padding([.leading,.trailing],20 )     }          private  func  dayName (for  index : Int ) -> String  {         let  currentDate =  calendar.date(byAdding: .day, value: index, to: Date ()) ??  Date ()         return  dateFormatter.string(from: currentDate)      }          private  func  dayNumber (for  index : Int ) -> String  {         let  currentDate =  calendar.date(byAdding: .day, value: index, to: Date ()) ??  Date ()         let  day =  calendar.component(.day, from: currentDate)          return  "\(day) "       }          private  func  gradientColors (for  index : Int ) -> [Color ] {         if  self .selectedDayIndex ==  index {             return  [                 Color (red: 142 / 255 , green: 150 / 255 , blue: 255 / 255 ),                 Color (red: 108 / 255 , green: 93 / 255 , blue: 211 / 255 )             ]          } else  {             return  [                 Color .clear,                 Color .clear             ]          }     } } 
 
获取到数据后再显示页面 这个例子运用的场景,在页面一加载就需要显示的数据,那么这个时候异步执行的网络请求方法,还没有获取到数据,这时候视图上获取数据就会获取不到,或者直接数据越界(因为我们一般数据设置为空数组[ ]),
有三种方法解决,第一种比如说为数据赋初值,这样项目就不会崩溃了,但是就像前面说的这时候还没有获取到数据,那么页面就会一闪而过一个奇怪的数据,
所以使用第二种方法,在异步执行的网络请求方法完成后,在方法里赋值isDataLoaded代表数据加载完成,并把isDataLoaded作为if的条件,为true再显示页面,虽然其实页面会显示的慢一点,但是这点时间用户看不出来,是比较好的选择,
还有第三种方法,比如说之前我在微信小程序里,通过在该页面的前一个页面,提前获取到数据,然后再传递给该页面,就可以到达一样的效果,唯一比较麻烦的是一般这样出现在登录界面,就需要进行多种数据的获取,会导致一定的卡顿
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 if  isDataLoaded{   } func  fetchTagData () {    tagDataManager.fetchTagData { fetchedData, error in          if  let  fetchedData =  fetchedData {             tagWithTime =  fetchedData             tagNum= tagWithTime.count             if  tagWithTime[0 ].ifTagNull== "1" {                 ifshowTagDetailBNull= true              }             print (tagWithTime)             isDataLoaded =  true                         } else  {             print ("Error fetching data: \(error? .localizedDescription ??  "Unknown error" ) " )         }     } } 
 
自定义倒计时器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 func  startCountdown () {         let  hoursInSeconds =  (Int (tagWithTime[selectedTagIndex].tagHour! ) ??  1 ) *  3600      let  minutesInSeconds =  (Int (tagWithTime[selectedTagIndex].tagMinute! ) ??  30 ) *  60      remainingTimeInSeconds =  TimeInterval (hoursInSeconds +  minutesInSeconds)          countdownTimer =  Timer .scheduledTimer(withTimeInterval: 1 , repeats: true ) { timer in          if  remainingTimeInSeconds >  0  {             remainingTimeInSeconds -=  1          } else  {             timer.invalidate()                           time= true              timeStop= false              finishTag(tagName: tagWithTime[selectedTagIndex].tagName! )             userData.point+= Int (tagWithTime[selectedTagIndex].tagPoint! )!          }     } } 
 
swiftui基础 数组 可以通过[结构体]的方式,来定义数组
注意在通过targetDateInfo[index].dayDifference去添加数组的时候,需要先初始化好targetDateInfo,不然index会使targetDateInfo越界,之前在Vue里面经常直接使用push的方法,所以忘记了这点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 struct  TargetDateInfo  {     var  dayDifference: Int       var  timeString: String   } @State  private  var  targetDateInfo:[TargetDateInfo ]= []for  _  in  0 ..< targetWithTime.count {    targetDateInfo.append(TargetDateInfo (dayDifference: 0 , timeString: "" )) } for  index in  0 ..< self .targetWithTime.count {                         let  dateFormatter =  DateFormatter ()          dateFormatter.dateFormat =  "yyyy-MM-dd'T'HH:mm:ss"                if  let  deadlineDate =  dateFormatter.date(from: self .targetWithTime[index].deadline! ),                                  let  startDate =  Calendar .current.date(bySettingHour: 0 , minute: 0 , second: 0 , of: selectedDate) {                  print ("startDate:" ,startDate)         let  calendar =  Calendar .current                                    let  components =  calendar.dateComponents([.day], from: startDate, to: deadlineDate)                           if  let  dayDifference =  components.day {             self .dayDifference= dayDifference             print ("index:" ,index)             targetDateInfo[index].dayDifference= dayDifference             targetDateInfo[index].timeString= ""              if  dayDifference <=  0  {                                  let  timeFormatter =  DateFormatter ()                 timeFormatter.dateFormat =  "HH:mm"                  let  timeString =  timeFormatter.string(from: deadlineDate)                 self .timeString= timeString                 targetDateInfo[index].timeString= timeString                 print ("Time: \(timeString) " )             } else  {                 print ("Day difference: \(dayDifference) " )             }         }     } } 
 
状态(@State、@StateObject) @StateObject在跟踪类/结构体内的属性变化时使用
SwiftUI的状态能够使body在状态改变时重新渲染(相当于Vue里)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct  ContentView : View  {    @State  private  var  alertIsVisible:Bool = false      var  body: some  View  {         VStack  {             Button (action: {                 self .alertIsVisible= true              }) {                 Text ("点我" )             }           		           	 .alert(isPresented: $alertIsVisible  , content: {                 return  Alert (title: Text ("你好" ),message:                               Text ("这是弹窗" ),dismissButton: .                              default (Text ("好好好 " )))             })         }         .padding()     } } 
 
绑定(@Binding) 其实就是绑定传入的值
1 2 3 4 5 6 7 8 DrawerMenu (isDrawerOpen: $ifShowMenu , ifShowTarget: $ifShowTarget )struct  DrawerMenu : View  {    @Binding  var  isDrawerOpen: Bool      @Binding  var  ifShowTarget:Bool      var  body: some  View  {            } } 
 
全局变量(@EnvironmentObject) 定义一个实现ObservableObject协议的类,需要跟踪的数值使用@Published 修饰
1 2 3 4 class  UserData : ObservableObject  {    @Published  var  userEmail: String  =  ""  } 
 
通过.envrionmentObject(对象)将一个对象放置到环境中
1 2 3 4 5 6 7 8 9 10 11 12 @main struct  HabeetApp : App  {    @StateObject  private  var  userData =  UserData ()     var  body: some  Scene  {         WindowGroup  {             ContentView ()                 .environmentObject(userData)         }     } } 
 
在view中使用这个对象,@EnvironmentObject修饰对象
1 2 3 4 5 6 7 8 9 struct  HomeView : View  {  @EnvironmentObject  private  var  userData: UserData    var  body: some  View  {     TextField ("请输入邮箱" , text: $userData .userEmail)   } } 
 
注意,如果预览没有.envrionmentObject()设置环境中的对象,程序就会崩溃,包括导航到需要用的视图
1 2 3 4 5 6 7 8 9 10 11 12 struct  ContentView_Previews : PreviewProvider  {    static  var  previews: some  View  {         ContentView ().environmentObject(UserData ())     } } struct  HomeView_Previews : PreviewProvider  {    static  var  previews: some  View  {         HomeView ().environmentObject(UserData ())     } } 
 
单元测试(Unit Text) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import  XCTest@testable  import  Habeetfinal  class  HabeetTests : XCTestCase  {         var  game:Game !     override  func  setUpWithError () throws  {         game= Game ()              }     override  func  tearDownWithError () throws  {         game= nil               }     func  testExample () throws  {       	         XCTAssertEqual (game.points(sliderValue: 50 ), 999 )                                                  } 		     func  testPerformanceExample () throws  {                  self .measure {                      }     } } 
 
项目结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ProjectRoot ├── Views │   ├── ContentView.swift │   ├── MainContent.swift │   ├── DrawerMenu.swift │   ├── TargetView.swift │   └── ...other view  files ├── Extensions │   ├── Color+ Extensions.swift │   ├── View + Extensions.swift ├──   └── ...other extension files ├── Helpers │   ├── Constants.swift │   └── ...other helper files └── Models     ├── Target.swift     └── ...other model files 
 
快捷键 建立新的 Swift 文件:command+N
将视图包含进 VStack、HStack、ZStack:选中视图+command(Embed in xxx)
要將存放代码的 VStack、HStack、ZStack 提取出来(新的stuck):选中视图+command  (Extract Subview)
插入不同视图:command+Shift+L(选中后可以用鼠标拖拽到不同位置,同时不同位置也可以达成自动创建Stack的效果)
移动代码到 上一行/下一行:option+command+[ / ]
实机调试:command+R
刷新预览:option+command+P
查看视图内属性的详情:option+点击
只构建项目不调试:command+B
进行单元测试:command+U