MVVM 实现记录文本

1. MVVM 框架说明:

  Model     - 数据层

  View      - 视图层

  ViewModel - 管理模型的视图

2. 资源文件

  2.1 启动图标:

AppIconhttps://img-blog.csdnimg.cn/8fa1031489f544ef9757b6b3ab0eddbe.png

  2.2 Display Name: Do Stuff

  2.2 颜色图:

MVVM 实现记录文本_第1张图片    MVVM 实现记录文本_第2张图片

  2.3 项目结构图: 

MVVM 实现记录文本_第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. 效果图

MVVM 实现记录文本_第4张图片     MVVM 实现记录文本_第5张图片     MVVM 实现记录文本_第6张图片

MVVM 实现记录文本_第7张图片     MVVM 实现记录文本_第8张图片     MVVM 实现记录文本_第9张图片

你可能感兴趣的:(Swift,MVVM,iOS,Swift,MVVM)