1. MVVM 框架说明:
Model - 数据层
View - 视图层
ViewModel - 管理模型的视图
2. 资源文件
2.1 启动图标:
AppIconhttps://img-blog.csdnimg.cn/8fa1031489f544ef9757b6b3ab0eddbe.png
2.2 Display Name: Do Stuff
2.2 颜色图:
2.3 项目结构图:
3. Model 层实现,ItemModel.swift
import Foundation// 不可变结构
struct ItemModel: Identifiable, Codable{var id: Stringlet title: Stringlet isCompleted: Boolinit(id: String = UUID().uuidString, title: String, isCompleted: Bool) {self.id = idself.title = titleself.isCompleted = isCompleted}// 修改值func updateCompleted() -> ItemModel {return ItemModel(id: id, title: title, isCompleted: !isCompleted)}
}
4. ViewModel 层实现,ListViewModel.swift
import Foundation/*CRUD FUNCTIONSCreateReadUpdateDelete*/class ListViewModel: ObservableObject{@Published var items:[ItemModel] = []{didSet{saveItems()}}let itemsKey: String = "items_list"init() {getItems()}func getItems(){// let newItems = [// ItemModel(title: "This is the first title", isCompleted: false),// ItemModel(title: "This is the second", isCompleted: true),// ItemModel(title: "Third", isCompleted: false)// ]// items.append(contentsOf: newItems)// 获取数据, 解码转换数据guardlet data = UserDefaults.standard.data(forKey: itemsKey),let saveItems = try? JSONDecoder().decode([ItemModel].self, from: data)else{ return }self.items = saveItems}// 移动func moveItem(from: IndexSet, to: Int){items.move(fromOffsets: from, toOffset: to)}// 删除 Itemfunc deleteItem(indexSet: IndexSet){items.remove(atOffsets: indexSet)}// 添加 Itemfunc addItem(title: String) {let newItem = ItemModel(title: title, isCompleted: false)items.append(newItem)}// 更新func updateItem(item: ItemModel){// 判断 id 是否一样
// if let index = items.firstIndex { existingItem in
// return existingItem.id == item.id
// }{
// // run this code
// }if let index = items.firstIndex(where: { $0.id == item.id}){items[index] = item.updateCompleted()}}// 保存 Items 模型转换为 JSON 数据,然后对其进行,存储func saveItems(){if let encodedData = try? JSONEncoder().encode(items){UserDefaults.standard.set(encodedData, forKey: itemsKey)}}
}
5. View 层实现
5.1 添加数据页实现,AddView.swift
struct AddView: View {@Environment(\.presentationMode) var presentationMode@EnvironmentObject var listViewModel: ListViewModel@State var textFieldText: String = ""@State var alertTitle: String = ""@State var showAlert: Bool = falsevar body: some View {// 滚动 ViewScrollView {VStack {TextField("Type something here...", text: $textFieldText).padding(.horizontal).frame(height: 55).background(Color(UIColor.secondarySystemBackground)).cornerRadius(10)Button(action: saveButtonPressed) {Text("Save").foregroundColor(.white).font(.headline).frame(height: 55).frame(maxWidth: .infinity).background(Color.accentColor).cornerRadius(10)}}.padding(13)}.navigationTitle("Add an Item 🖋️").alert(isPresented: $showAlert, content: getAlert)}// 单机保存按钮func saveButtonPressed() {if textIsAppropriate() {listViewModel.addItem(title: textFieldText)presentationMode.wrappedValue.dismiss()}}// 检查文本合法性func textIsAppropriate() -> Bool{if textFieldText.count < 3 {alertTitle = "Your new todo item must be at least 3 characters long!!! 😨😰😱"showAlert.toggle()return false}return true}// 获取提示框func getAlert() -> Alert {return Alert(title: Text(alertTitle))}
}struct AddView_Previews: PreviewProvider {static var previews: some View {NavigationView {AddView()}//.preferredColorScheme(.dark).environmentObject(ListViewModel())}
}
5.2 无数据页实现,NoItemsView.swift
struct NoItemsView: View {// 添加动画@State var animate: Bool = false// 定义颜色let secondaryAccentColor = Color("SecondaryAccentColor")var body: some View {ScrollView{VStack(spacing: 10){Text("There are no items!").font(.title).fontWeight(.semibold)Text("Are you a productive person? I think you should click the add button and a bunch of items to your todo list!").padding(.bottom, 20)NavigationLink(destination: AddView()) {Text("Add Something 🥳").foregroundColor(.white).font(.headline).frame(height: 55).frame(maxWidth: .infinity).background(animate ? secondaryAccentColor : Color.accentColor).cornerRadius(10)}.padding(.horizontal, animate ? 30 : 50).shadow(color: animate ? secondaryAccentColor.opacity(0.7) : Color.accentColor.opacity(0.7),radius: animate ? 30 : 10,x: 0,y: animate ? 50 : 30).scaleEffect(animate ? 1.1 : 1.0).offset(y: animate ? -7 : 0)}.frame(maxWidth: 400).multilineTextAlignment(.center).padding(40).onAppear(perform: addAnimation)}.frame(maxWidth: .infinity,maxHeight: .infinity)}// 添加动画func addAnimation(){guard !animate else { return }DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {withAnimation(Animation.easeInOut(duration: 2.0).repeatForever()){animate.toggle()}}}
}struct NoItemsView_Previews: PreviewProvider {static var previews: some View {NavigationView {NoItemsView().navigationTitle("Title")}}
}
5.3 列表行 View 实现,ListRowView.swift
struct ListRowView: View {let item: ItemModel;var body: some View {HStack {Image(systemName: item.isCompleted ? "checkmark.circle":"circle").foregroundColor(item.isCompleted ? .green : .red)Text(item.title)Spacer()}.font(.title2).padding(.vertical, 8)}
}struct ListRowView_Previews: PreviewProvider {static var item1: ItemModel = ItemModel(title: "First Item!", isCompleted: false)static var item2: ItemModel = ItemModel(title: "Second Item!", isCompleted: true)static var previews: some View {Group {ListRowView(item: item1)ListRowView(item: item2)}.previewLayout(.sizeThatFits)}
}
5.4 列表页实现,ListView.swift
struct ListView: View {// @State var items:[ItemModel] = [
// ItemModel(title: "This is the first title", isCompleted: false),
// ItemModel(title: "This is the second", isCompleted: true),
// ItemModel(title: "Third", isCompleted: false)
// ]@EnvironmentObject var listViewModel: ListViewModel;var body: some View {ZStack {if listViewModel.items.isEmpty{// 加载 View 时,添加动画NoItemsView().transition((AnyTransition.opacity.animation(.easeIn)))}else{List {ForEach(listViewModel.items) { item inListRowView(item: item).onTapGesture {withAnimation(.linear){listViewModel.updateItem(item: item)}}}.onDelete(perform: listViewModel.deleteItem).onMove(perform: listViewModel.moveItem)}.listStyle(.plain)}}.navigationTitle("Todo List 📝").navigationBarItems(leading: EditButton(),trailing: NavigationLink("Add", destination: AddView()))}
}struct ListView_Previews: PreviewProvider {static var previews: some View {NavigationView {ListView()}.environmentObject(ListViewModel())}
}
5.5 在 App 文件中添加 ListView 与 ViewModel,TodoListApp.swift
/*MVVM 框架Model - 数据层View - 视图层ViewModel - 管理视图的模型*/@main
struct TodoListApp: App {/// ViewModel@StateObject var listViewModel: ListViewModel = ListViewModel()var body: some Scene {WindowGroup {NavigationView {ListView()}// 适配 iPad 导航栏.navigationViewStyle(StackNavigationViewStyle()).environmentObject(listViewModel)}}
}
6. 效果图