英文:
SwiftUI ForEach loop does not reflect changes when the list changes state?
问题
我仍在努力理解如何使用@State
、@ObservedObject
、@Published
等属性来实现我想要做的事情。我尝试了多种不同的方式,但问题没有得到解决。
所以现在,我的主视图中有两个@State
变量,以及一个嵌套在主视图中的for
循环。该循环显示存储在eventViews
列表中的视图,视图的框架和位置由eventBounds
列表中的值指定。
struct CalendarDayView: View {
@State var eventViews: [CalendarEventView] = []
@State var eventBounds: [CGRect] = []
var body: some View {
ZStack {
ForEach(eventViews.indices, id: \.self) { index in
let eventView = eventViews[index]
let bounds = eventBounds[index]
eventView
.frame(width: bounds.width,
height: bounds.height,
alignment: .center)
.position(x: bounds.midX, y: bounds.midY)
}
}
}
}
当用户更改事件或其边界时,我调用一个名为calculateEventBounds()
的函数,该函数会遍历所有事件并计算我希望它们渲染的边界,根据它们的位置等。一切都运行良好,除了当我调用calculateEventBounds()
时,所做的更改只有在我切换应用程序选项卡并返回到CalendarDayView
后才会立即更新到UI中。我需要在调用calculateEventBounds()
后立即看到更改。如何使这个for
循环无效,以便可以立即在UI中看到eventViews
和eventBounds
的更改?
这是calculateEventBounds()
的简化代码:
func calculateEventBounds() {
self.eventViews = []
self.eventBounds = []
let events = eventsManager.getEventsOnDay(day: Int(day))
for event in events { // 执行一些复杂的计算以确定事件的边界
self.eventViews.append(CalendarEventView(event: event))
self.eventBounds.append(bounds) // bounds是在for循环内定义的CGRect
}
}
我的CalendarEventView
结构体是一个视图,包含了一个CalendarEvent
结构体,其中包含有关CalendarEvent的信息,如名称、开始时间等。
我尝试过使用一个单独的类,符合ObservableObject
协议,并将eventViews
和eventBounds
作为@Published
属性,同时将该类存储为CalendarDayView
中的@ObservedObject
,然后在循环中从另一个类的getter函数中获取视图,但这也没有解决我的问题。
英文:
I am still trying to understand exactly how @State, @ObservedObject, @Published, etc properties can help accomplish what I am trying to do. I have tried a variety of these in different ways with no change in my issue.
So right now I have two @State variables along with this for loop within my main view. The loop displays the views stored within the eventViews
list with the frame and position specified in the eventBounds
list.
struct CalendarDayView: View {
@State var eventViews: [CalendarEventView] = []
@State var eventBounds: [CGRect] = []
var body: some View {
ZStack {
ForEach(eventViews.indices, id: \.self) { index in
let eventView = eventViews[index]
let bounds = eventBounds[index]
eventView
.frame(width: bounds.width,
height: bounds.height,
alignment: .center)
.position(x: bounds.midX, y: bounds.midY)
}
}
}
}
When the user makes changes to the events or their bounds I call a function calculateEventBounds()
which goes through all the events and calculates the bounds I want them to be rendered with according to their positioning, etc. Everything works great, except when I call calculateEventBounds()
, the changes made are only updated on the UI after I switch tabs on my app and go back to the CalendarDayView. I need the changes to be seen immediately after calling calculateEventBounds()
.
How can I invalidate this for loop so that the changes to eventViews
and eventBounds
are seen immediately in the UI?
Here is simplified code for calculateEventBounds()
:
func calculateEventBounds() {
self.eventViews = []
self.eventBounds = []
let events = eventsManager.getEventsOnDay(day: Int(day))
for event in events { //do fancy stuff to calculate the event's bounds
self.eventViews.append(CalendarEventView(event: event))
self.eventBounds.append(bounds) //bounds is a CGRect defined within the for loop
}
}
My struct CalendarEventView
is a view that holds a CalendarEvent
struct with the info of the CalendarEvent like it's name, start time, etc.
I've tried using a separate class that conforms to ObservableObject
and holds the eventViews
and eventBounds
as @Published properties while storing that class as an @ObservedObject
in my CalendarDayView
, and the for loop would get the views from a getter function of the other class but that didn't solve my issue either.
答案1
得分: 0
有两个主要的不良实践,我甚至称它们为错误:
- 多个数组作为数据源
- SwiftUI 视图作为数据源的一部分。
一个事件应该是一个包含bounds
的Event
结构体,还有一个用于事件数据的init
方法和用于计算边界的方法(使用网格或其他智能集合视图是否更好?)。
struct Event: Identifiable {
let id = UUID()
var bounds: CGRect = .zero
init(data: ...) { ... }
func calculateBounds() { ... }
}
然后使用一个视图模型,一个符合ObservableObject
协议的类,其中包含事件的@Published
属性,以及一个获取事件并创建Event
实例的方法。
class EventManager: ObservableObject {
@Published var events = [Event]()
func getEventsOnDay(day: Int) {
let eventData = // 获取数据
events = eventData.map { data in
let event = Event(data: data)
event.calculateBounds()
return event
}
}
}
在视图中创建一个EventManager
的实例作为@StateObject
,然后从中获取事件。
struct CalendarDayView: View {
@StateObject var manager = EventManager()
var body: some View {
ZStack {
ForEach(manager.events) { event in
let bounds = event.bounds
CalendarEventView(event: event)
.frame(width: bounds.width,
height: bounds.height,
alignment: .center)
.position(x: bounds.midX, y: bounds.midY)
}
}
}
}
在onAppear
(或异步task
)中,您可以调用获取数据的方法。
对events
数组的任何更改都会触发视图刷新。
英文:
There are two major bad practices, I even call them mistakes:
- Multiple arrays as data source
- SwiftUI view as part of the data source.
An event should be an Event
struct containing the bounds
, an init
method for the event data and the method to calculate the bounds (is a grid or another intelligent collection view not a better choice?).
struct Event: Identifiable {
let id = UUID()
var bounds: CGRect = .zero
init(data: ...) { ... }
calculateBounds() { ... }
}
Then use a View Model, a class conforming to ObservableObject
– as you already tried – with a @Published
property containing the events, and also a method to get the events and create Event
instances
class EventManager: ObservableObject {
@Published var events = [Event]()
func getEventsOnDay(day: Int) {
let eventData = // get the data
events = eventData.map { data in
let event = Event(data: data)
event.calculateBounds()
return event
}
}
}
In the view create an instance of EventManager
as @StateObject
and get the events from there.
struct CalendarDayView: View {
@StateObject var manager = EventManager()
var body: some View {
ZStack {
ForEach(manager.events) { event in
let bounds = event.bounds
CalendarEventView(event: event)
.frame(width: bounds.width,
height: bounds.height,
alignment: .center)
.position(x: bounds.midX, y: bounds.midY)
}
}
}
}
In onAppear
(or in asynchronous task
) you can call the method to get the data.
Any change in the events
array will trigger the view to refresh.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论