SwiftUI 可观测数据模型的使用

SwiftUI 可观测数据模型的使用

简介

在 SwiftUI 中,App 使用变量 eventData(事件数据)储存数据。eventData 通过@StateObject(状态对象)属性包装器1进行定义,以创建可观测对象2 EventData 的实例。因为这个对象是可观测的,所以 SwiftUl 会监视它,来跟踪它的值的更改。每当数据发生变化,SwiftUl就会自动更新使用(或观测)该对象的视图。

数据传入

接着,代码会将 eventData 传入 EventList(事件列表)视图。对于下面这段代码而言,EventList 是App启动时显示的第一个视图:

import SwiftUI

@main
struct DatePlannerApp: App {
    @StateObject private var eventData = EventData()
    
    var body: some Scene {
        WindowGroup {
            EventList(eventData: eventData)
//            将 eventData 传入 EventList。
        }
    }
}

可识别数据整理

为了整理数据,App会使用一系列 Event 对象,每个对象代表一个特定事件,如露营之旅或生日派对。为了符合 Identifiable(可识别)协议,请确保在你创建事件列表时,SwiftUl 可以识别和更新每一行(如下图)。

图片[1]-SwiftUI 可观测数据模型的使用-梦闯の天下

Event 类型可包含信息

Event 类型包含填充事件需要的所有信息,包括符号、颜色、标题、日期和任务集。每个任务都是特定于事件的待办事项,如“准备一个露营灯”。(如下图)

图片[2]-SwiftUI 可观测数据模型的使用-梦闯の天下
import SwiftUI

struct Event: Identifiable, Hashable, Codable {
    var 1d = UUID()
    var symbol: String = EventSymbols. randomName ()
    var color: RGBAColor = ColorOptions. random() .rgbaColor
    var title = ""
    var tasks = [EventTask(text: "") ]
    var date = Date.now

//    Other Code...

下面这是 Event 的计算属性列表。这些属性可帮助用户根据待完成任务的日期和数量整理列表小节。

var period: Period {
        if date < Date.now{
            return .past
            
        } else if date < Date.now.sevenDaysOut {
            return .nextSevenDays
            
        } else if date < Date.now.thirtyDaysOut {
            return .nextThirtyDays
            
        } else {
            return .future
        }
    }
    
    var remainingTaskCount: Int {
        tasks.filter { !$0.isCompleted && !$0.text.isEmpty }.count
    }
    
    var isComplete: Bool {
        tasks.allSatisfy { $0.isCompleted || $0.text.isEmpty }
    }
//    Other Code...

EvenTask 标记

一个 EventTask(事件任务)代表事件一系列待办事项中的一个。和 Event 一样,EventTask 也是 Identifiable,可让 SwiftUl 管理和更新它在列表中的外观。

EventTask 包含文本和完成状态的属性,以及 isNew(新的)属性。当用户将任务标记为已完成时,你需要将 isCompleted(已完成)设为 true,以允许 App 跟踪每个事件剩下的任务:

import Foundation

struct EventTask: Identifiable, Hashable, Codable {
    var id = UUID()
    var text: String
    var isCompleted = false
    var isNew = false
}

EventData 包含一个叫做 events 的属性,储存了Event 值的预填充数组,如游戏之夜或就诊预约。定义这个属性时,通过使用 @Published(发布)属性包装器,可指示 SwiftUI 在events 数组发生变化时通知所有观测器,并更新视图。这样可让你在数组中添加和删除事件,以及立即在 UI 中查看更改。

import SwiftUI

class EventData: ObservableObject {
    @Published var events: [Event] = [
        Event(symbol: "gift.fill",
              color: Color.red.rgbaColor,
              title: "Maya's Birthday",
              tasks: [EventTask(text: "Guava kombucha"),
                      EventTask(text: "Paper cups and plates"),
                      EventTask(text: "Cheese plate"),
                      EventTask(text: "Party poppers"),
                     ],
//            Other code...

将可观测数据传入

通过 @ObservedObject 属性包装器将 eventData 可观测对象传入 EventList。这个属性包装器会在值发生任何变化时让 SwiftUI 更新视图。

import SwiftUI

struct EventList: View {
    @ObservedObject var eventData: EventData    //ObservedObject
    @State private var isAddingNewEvent = false
    @State private var newEvent = Event()
    
    @State private var selection: Event?
    
    var body: some View {
//    Other code...

导航拆分视图

App 使用 NavigationSplitView(导航拆分视图)在 App 内的不同视图间导航。在更宽的 App 配置中,如 iPad 上的全屏幕横屏模式,SwiftUI 会将 NavigationSplitView 内容显示为多个相邻的列,而不是单个堆叠。在这个布局中,EventList 显示在边栏列中,它的目的内容显示在主面板中。当有人在列表中选择了一个事件时,第二列会显示 EventEditor(事件编辑器)视图;否则,它会显示占位符 Text(文本)视图。(如下两图)若要创建列表,请创建 List 视图,并使用 ForEach 循环迭代所有时间段(nextSevenDays, nextThirtyDays, futurepast)。

当小节中包含事件时,请创建 Section(小节)视图,并使用 ForEach 迭代该时间段中的所有事件。在EventData 中,使用 sortedEventsperiod:)方法,以返回特定于小节时间范围的事件。在 ForEach 循环内,为时间段中的每个事件创建 EventRow(事件行)视图。

若要删除事件,在事件行上添加修饰符 .swipeActions(轻扫操作),并定义从 EventDataevents 数组中移除事件的按钮。你现在可以在事件上向左轻扫,然后轻点“删除”按钮来移除当前事件。

图片[3]-SwiftUI 可观测数据模型的使用-梦闯の天下
图片[4]-SwiftUI 可观测数据模型的使用-梦闯の天下

图中完整示例代码如下:

import SwiftUI

struct EventList: View {
    @ObservedObject var eventData: EventData
    @State private var isAddingNewEvent = false
    @State private var newEvent = Event()
    
    @State private var selection: Event?
    
    var body: some View {
        NavigationSplitView {
            List(selection: $selection) {
                ForEach(Period.allCases) { period in
                    Section(content: {
                        ForEach(eventData.sortedEvents(period: period)) { $event in
                            EventRow(event: event)
                                .tag(event)
                                .swipeActions {
                                    Button(role: .destructive) {
                                        selection = nil
                                        eventData.remove(event)
                                    } label: {
                                        Label("Delete", systemImage: "trash")
                                    }
                                }
                        }
                    }, header: {
                        Text(period.name)
                            .font(.callout)
                            .foregroundColor(.secondary)
                            .fontWeight(.bold)
                    })
                    .disabled(eventData.sortedEvents(period: period).isEmpty)
                }
            }
            .navigationTitle("Date Planner")
            .toolbar {
                ToolbarItem {
                    Button {
                        newEvent = Event()
                        isAddingNewEvent = true
                    } label: {
                        Image(systemName: "plus")
                    }
                }
            }
            .sheet(isPresented: $isAddingNewEvent) {
                NavigationStack {
                    EventEditor(event: $newEvent, isNew: true)
                        .toolbar {
                               ToolbarItem(placement: .cancellationAction) {
                                   Button("Cancel") {
                                       isAddingNewEvent = false
                                   }
                               }
                               ToolbarItem {
                                   Button {
                                       eventData.add(newEvent)
                                       isAddingNewEvent = false
                                   } label: {
                                       Text("Add" )
                                   }
                                   .disabled(newEvent.title.isEmpty)
                               }
                            }
                }
            }
        } detail: {
            ZStack {
                if let event = selection, let eventBinding = eventData.getBindingToEvent(event) {
                    EventEditor(event: eventBinding)
                } else {
                    Text("Select an Event")
                        .foregroundStyle(.secondary)
                }
            }
        }
    }
}

struct EventList_Previews: PreviewProvider {
    static var previews: some View {
        EventList(eventData: EventData())
    }
}
  1. 属性包装器是一种将常用行为模式应用到属性的简单方法。这个包装器是对属性的一种标注方式,定义了读取时它如何被储存或计算。
    例如,CState 属性包装器告诉 SwiftUl 管理值的储存空间,并在值发生更改时,更新视图中任何使用了这个值的部分。 ↩︎
  2. 当类符合 ObservableObject 协议时,对它的发布值进行的任何更改都会导致使用这些值的所有视图自动更新,以反映变化。 ↩︎
    © 版权声明
    THE END
    分享和支持
    点赞7 分享
    评论 抢沙发
    头像
    留下评论,见证当下。
    提交
    头像

    昵称

    取消
    昵称表情代码快捷回复

      请登录后查看评论内容