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: String
let title: String
let isCompleted: Bool
init(id: String = UUID().uuidString, title: String, isCompleted: Bool) {
self.id = id
self.title = title
self.isCompleted = isCompleted
}
// 修改值
func updateCompleted() -> ItemModel {
return ItemModel(id: id, title: title, isCompleted: !isCompleted)
}
}
4. ViewModel 层实现,ListViewModel.swift
import Foundation
/*
CRUD FUNCTIONS
Create
Read
Update
Delete
*/
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)
// 获取数据, 解码转换数据
guard
let 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)
}
// 删除 Item
func deleteItem(indexSet: IndexSet){
items.remove(atOffsets: indexSet)
}
// 添加 Item
func 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 = false
var body: some View {
// 滚动 View
ScrollView {
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 in
ListRowView(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. 效果图