极简第一版
极简版教学:SwiftUI:从零点五开始的APP开发之路 https://www.bilibili.com/video/BV1Kg4y1i7dd?p=1
原项目代码为自己寒假在跟学时的代码,由于版本不同所以和原视频的代码在通知推送方面有一些出入
运行效果
已实现功能
该APP已经是个完整的独立APP,已实现功能如下:
1、自定义添加、收藏和取消收藏、删除事项,并同时支持单选和多选统一操作
2、在事项结束时间到达时向用户发送通知和提醒,在首次打开APP前询问用户许可发送通知权限
待完善
1、日期格式并没有进行设置和规范化
2、在进行事项按时间排序时,对id进行了修改,在swiftUI中很容易会导致一些奇怪的问题
3、已完成数据的增删改而没有查找功能
二次开发
日期格式
原版的日期格式是这个样子的:
这非常乱码,那么我们只需要在初始化的时候,规范一下日期格式
为了方便使用,我们定义一个Date转为规范模式后的String(因为我们输出的时候时以字符串的方式)的方法:
//日期 -> 字符串
func date2String(_ date:Date, dateFormat:String = "yyyy-MM-dd HH:mm:ss") -> String {
let formatter = DateFormatter()
formatter.locale = Locale.init(identifier: "zh_CN")
formatter.dateFormat = dateFormat
let date = formatter.string(from: date)
return date
}
然后在SingleCardView中,更改显示日期部分的代码
Text(date2String(self.UsrData.ToDoList[index].duedate)) //原代码为:self.UsrDate.ToDoList[index].duedate.description,即仅仅将date转换为string
.font(.subheadline)
.foregroundColor(Color.gray)
完成后新的效果如下:
id的维护
在第一版中,我们在sort排序后,对id进行了更新和修改。在swiftUI中尽量不要修改元素的id,因为id是区分不同View的方式,所以直接更改id会出现一些问题(尤其是动画方面)
对此进行进一步完善主要涉及到UserData里的sort()和ContentView中的SingleCardView
sort()
对于sort()的修改方式及其简单暴力——直接把id更新删掉即可
//第一版:
func sort() {
self.ToDoList.sort(by: {(data1, data2) in
return(data1.duedate.timeIntervalSince1970 < data2.duedate.timeIntervalSince1970)
})
for i in 0..<self.count {
self.ToDoList[i].id = i
}
}
//第二版:
func sort() {
self.ToDoList.sort(by: {(data1, data2) in
return(data1.duedate.timeIntervalSince1970 < data2.duedate.timeIntervalSince1970)
})
}
SingleCardView
由于sort方法没有更改id,因此元素的id不再与其在数组ToDoList中的个数(index)相同;因此我们使用一个计算属性index来找到ToDoList中对应的singleToDo是在数组中的哪一个;
我们在SingleCardView中以前的index改为以下代码
var singleData: SingleToDo
var index: Int{
self.UsrData.ToDoList.firstIndex(where: {data in data.id == self.singleData.id})!
}
并在Content()调用SIngleCardView时将传入的index改为singleData
查找(搜索)功能
与uIKit不同的是,SwiftUI没有内置的搜索栏控制,但是搭建一个并不很难
搜索栏UI
IOS中的标准搜索栏,它实际上由一个文本字段和一个取消按钮组成。
首先,我们声明了两个变量:一个是搜索文本的绑定,另一个是存储搜索字段状态的变量(编辑与否)。并且,只有当用户点击搜索字段时,才会显示取消按钮
import SwiftUI
struct Search: View {
@Binding var text: String
@State private var isEditing = false
var body: some View {
HStack {
TextField("Search...", text: self.$text)
.padding(7)
.padding(.horizontal, 25)
.background(Color(.systemGray6))
.cornerRadius(8)
.overlay( //文本字段上覆盖两个系统图像
HStack {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray)
.frame(minWidth:0, maxWidth: .infinity, alignment: .leading)
.padding(.leading, 8)
if isEditing {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.gray)
.padding(.trailing, 8)
}
}
}
)
.padding(.horizontal, 10)
.onTapGesture {
self.isEditing = true
}
if isEditing {
Button(action: {
self.isEditing = false
self.text = ""
}) {
Text("Cancel")
}
.padding(.trailing, 10)
.transition(.move(edge: .trailing))
.animation(.default)
}
}
}
}
struct Search_Previews: PreviewProvider {
static var previews: some View {
Search(text: .constant(""))
.padding(.leading)
}
}
效果如下:
使用搜索栏进行数据过滤
切换到ContentView.swift并将搜索栏添加到列表视图中
在body中添加
Search(text: self.$searchText)
.padding(.top, 11.0)
视图如下:
然后在显示的时候再加一个判断当前是否在搜索,如果不在就都显示,如果在搜索就只显示title中包含搜索词的事项卡片
if searchText.isEmpty {
ForEach (self.UsrData.ToDoList) { item in
if item.deleted == false {
if !self.showFavouriteOnly || item.isFavourite {
SingleCardView(singleData : item, editingMode: self.$editingMode, selected: self.$selected)
.environmentObject(self.UsrData)
.padding()
.animation(.spring())
.transition(.slide)
}
}
}
} else {
ForEach (self.UsrData.ToDoList) { item in
if item.deleted == false {
if item.title.contains(self.searchText) {
SingleCardView(singleData : item, editingMode: self.$editingMode, selected: self.$selected)
.environmentObject(self.UsrData)
.padding()
.animation(.spring())
.transition(.slide)
}
}
}
}