MVVM 实现记录文本

news2024/11/27 10:39:16

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. 效果图

          

          

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/753258.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2.8 Bootstrap 图片

文章目录 Bootstrap 图片\<img> 类响应式图片 Bootstrap 图片 在本章中&#xff0c;我们将学习 Bootstrap 对图片的支持。Bootstrap 提供了三个可对图片应用简单样式的 class&#xff1a; .img-rounded&#xff1a;添加 border-radius:6px 来获得图片圆角。.img-circle&…

探索uni-app:构建跨平台应用的神奇工具

文章目录 &#x1f4da;1. 视图容器类组件⚡ <view>&#xff1a;视图容器&#xff0c;类似于div元素⚡<scroll-view>&#xff1a;可滚动的视图容器 &#x1f4da;2. 基础内容类组件⚡<text>&#xff1a;文本内容&#xff0c;类似于span元素⚡<icon>&am…

阿里大佬都在偷偷肝的 Java 程序优化笔记,程序性能提高了 5 倍!

前言 此笔记从软件设计、编码和 JVM 等维度阐述性能优化的方法和技巧&#xff0c;分享资深架构师 Java 程序性能优化的宝贵经验&#xff0c;专注于 Java 应用程序的优化方法、技巧和思想&#xff0c;并深度剖析 JDK 部分的实现。具有较强的层次性和连贯性&#xff0c;深入剖析…

wps插入图片显示不全、混乱

问题如下&#xff1a; 原因&#xff1a; 格式混乱 解决办法&#xff1a; 1、统一格式&#xff0c;使用格式刷统一文档的格式 2、Ctrl A 全选&#xff0c;重新选择行距 3、重新粘贴图片&#xff08;选择嵌入型&#xff09;

什么是事件循环 Event Loop

一、什么是事件循环 事件循环&#xff08;Event Loop&#xff09;是单线程的JavaScript在处理异步事件时进行的一种循环过程&#xff0c;具体来讲&#xff0c;对于异步事件它会先加入到事件队列中挂起&#xff0c;等主线程空闲时会去执行事件队列&#xff08;Event Queue&…

(黑客)网络安全自学?你不要命啦!

前言 网络安全&#xff0c;顾名思义&#xff0c;无安全&#xff0c;不网络。现如今&#xff0c;安全行业飞速发展&#xff0c;我们呼吁专业化的 就职人员与大学生 &#xff0c;而你&#xff0c;认为自己有资格当黑客吗&#xff1f; 本文面向所有信息安全领域的初学者和从业人员…

RPC和HTTP区别是什么?

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;RPC和HTTP区别是什么&#xff1f; ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的…

二、遥感物理基础(3)大气对太阳辐射的影响

前言 本文内容较为枯燥&#xff0c;是遥感的物理原理&#xff0c;作者已经尽量去帮助读者理解了&#xff0c;无论是精细的阅读还是走马观花&#xff0c;长期下来都能提高读者对专业知识的理解&#xff1b;作者非物理专业&#xff0c;对某些知识点的总结仅是个人理解&#xff0c…

【Java】JVM执行流程、类加载过程和垃圾回收机制

JVM执行流程执行引擎本地方法接口运行时数据区方法区堆虚拟机栈(线程私有)本地方法栈(线程私有)程序计数器(线程私有) 堆溢出问题类加载类加载的过程加载连接验证准备解析 初始化 双亲委派机制 垃圾回收死亡对象的判断算法引用计数算法可达性分析算法 垃圾回收的过程标记-清除算…

Win11 锁屏、开机画面使用window聚焦 壁纸失效解决方案

1、设置>个性化>锁屏界面>个性化锁屏界面 切换为图片。 2、打开文件资源管理器&#xff0c;导航栏中点击查看>显示>勾选隐藏的项目。 3、打开C&#xff1a;\用户\你的用户\AppData\Local\Packages\Microsoft.Windows.ContentDeliveryManager_xxxxxxx\LocalSta…

Docker安装Rabbitmq超详细教程

&#x1f680; Docker安装Rabbitmq保姆级教程 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1…

检测到目标网站存在Bash Shell历史记录文件

详细描述 Bash Shell历史记录文件.bash_history包含了用户在bash shell运行的命令的历史记录.可能包含有与目标机器相关的敏感信息。这些敏感信息可以帮助攻击者针对站点发起进一步的攻击&#xff0c;降低攻击的难度。 解决办法 如果不需要该文件&#xff0c;建议删除对应的…

ARM Coresight 系列文章 5 - ARM Coresight APB Interconnect(APBIC)

文章目录 APBIC 介绍APBIC ROM TableError response软件访问的控制 APBIC 介绍 下图是基于SOC-400的 DAP 架构图&#xff0c;从图中可以看到 DAP 口出来的DAP-APB 会接入 APBIC 的 Slave 口&#xff0c;系统总线也接入了 APBIC 的 APB Slave port(图中并没有直接标出slave por…

FastDVDnet Towards Real-Time Deep Video Denoising Without Flow

FastDVDnet: Towards Real-Time Deep Video Denoising Without Flow Estimation 原文&#xff1a; https://ieeexplore.ieee.org/document/9156652 由于视频有着较强的时间相关性&#xff0c;那么一个好的视频去噪算法必将要充分利用这一特点。利用时间相关性主要体现为两个方面…

Linux:企业级服务器嵌入式系统优势与应用

Linux在企业级服务器领域具有广泛应用。作为一种强大的操作系统&#xff0c;Linux可以用于构建企业的WWW服务器、数据库服务器、负载均衡服务器、邮件服务器、DNS服务器、代理服务器、路由器等。通过采用Linux系统&#xff0c;企业不仅可以降低运营成本&#xff0c;还能获得高稳…

Linux5.14 ELK企业级日志分析系统

文章目录 计算机系统5G云计算第四章 LINUX ELK 企业级日志分析系统一、ELK 概述1.ELK 简介1&#xff09;ElasticSearch2&#xff09;Kiabana3&#xff09;Logstash4&#xff09;可以添加的其它组件&#xff1a;Filebeat5&#xff09;缓存/消息队列&#xff08;redis、kafka、Ra…

【Shell学习】

Shell学习 Shell介绍 Shell是一种用于操作系统的命令行解释器&#xff0c;它提供了与操作系统内核进行交互的接口。它允许用户通过键入命令来执行各种操作&#xff0c;包括文件管理、进程控制、网络通信等。   下面是一些关于Shell的介绍&#xff1a; Shell是一种解释性语言…

使用php数组实现双色球的随机选号

一、双色球彩票介绍 双色球是中国福利彩票的一种常见玩法&#xff0c;也是全国彩民最爱的彩种之一。玩法规则是在33个红色球中选择6个数字&#xff0c;在16个蓝色球中选择1个数字&#xff0c;红色球号码区间为1-33&#xff0c;蓝色球号码区间为1-16。可以单式投注或者复式投注…

【数据结构】初识

&#x1f341; 博客主页:江池俊的博客_CSDN博客-C语言——探索高效编程的基石领域博主 &#x1f341; 专栏&#xff1a;https://blog.csdn.net/2201_75743654/category_12348274.html &#x1f341; 如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏&#x1f…

在UI中使用SpriteMask裁减任意shader的粒子效果

前言 由于我们需要在Mask中对粒子效果进行裁减。但是我们的的特效同事不愿意每个shader都去添加Stencil。所以使用SpriteMask方式进行裁减。 使用步骤 1. 添加SpriteMask Component 更具你需要的Mask形状设置精灵图片。又因为实际是精灵&#xff0c;并不属于UI系统&#xff…