为什么我的视图没有更新,即使使用了@State?

huangapple go评论93阅读模式
英文:

Why my view isn't updating, event with @State?

问题

我有这个EventDetailsView,其中包含许多TextFields、Pickers等。以前它运行得非常好,但现在,我不知道为什么,我的视图对任何更改都没有响应。我只能删除它,TextFields和Pickers都不起作用。

如果你想看其他文件的代码,请写下来。

以下是EventDetailsView的代码:

import SwiftUI

struct EventDetailsView: View {
    
    @EnvironmentObject var calendarViewModel: CalendarViewModel
    
    @Environment(\.dismiss) var dismiss
    
    @State private var event: Event // 我传递给这个视图的事件
    
    var body: some View {
        NavigationStack {
            GeometryReader { geo in
                VStack {
                    // 所有的Pickers和TextFields都在这里,它们与事件绑定,所以我不显示它们,因为它们肯定不是问题所在。
                }
                
                Button("Delete Event", role: .destructive) {
                    calendarViewModel.showingDeleteEventDialog = true
                }
            }
            .navigationTitle("Event Details")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    Button {
                        dismiss()
                    } label: {
                        Label("Back", systemImage: "chevron.left")
                            .labelStyle(.titleAndIcon)
                    }
                }
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("Save") {
                        calendarViewModel.changeEvent(event)
                        dismiss()
                    }
                }
            }
            .alert("Failed to set a Notification.", isPresented: $calendarViewModel.showingAlertErrorAlert) {
                Button("Cancel", role: .cancel) {
                    event.alert = .noAlerts
                    calendarViewModel.setNoAlerts()
                }
                Button("Settings") {
                    event.alert = .noAlerts
                    calendarViewModel.setNoAlerts()
                    calendarViewModel.openSettings()
                }
            } message: {
                Text("Please, enable notifications for this app in settings.")
            }
            
            .confirmationDialog("", isPresented: $calendarViewModel.showingDeleteEventDialog) {
                Button("Delete", role: .destructive) {
                    calendarViewModel.deleteEvent(event)
                }
                Button("Cancel", role: .cancel) { }
            } message: {
                Text("Are you sure you want to delete this event?")
            }
        }
    }
    
    init(event: Event) {
        _event = State(wrappedValue: event) // 在这里传递事件并创建新的State对象
    }
}

struct EventDetailsView_Previews: PreviewProvider {
    static var previews: some View {
        EventDetailsView(event: Event.example)
            .environmentObject(CalendarViewModel())
    }
}

还有Event结构体:

struct Event: Identifiable, Codable, Hashable, Comparable {
    
    var id = UUID()
    var title: String
    var description: String
    var tag: EventTags
    var startDate: Date
    var endDate: Date
    var repeatEvent: RepeatEvents
    var alert: Alerts
    var isCritical: Bool
    var sound: String
    var soundVolume: Double
    var color: EventColors.RawValue
    var background: Backgrounds
    var backgroundLocations: BackgroundLocation
    
    static let example = Event(title: "Workout", description: "We need to get strong", tag: .workout, startDate: Date(), endDate: Date().addingTimeInterval(86400), repeatEvent: .everyDay, alert: .noAlerts, isCritical: false, sound: "", soundVolume: 0.5, color: "darkGray", background: .color, backgroundLocations: BackgroundLocation.example)
    
    var stringStartDate: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM d, HH:mm a"
        return formatter.string(from: startDate)
    }
    
    var stringEndDate: String {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMM d, HH:mm a"
        return formatter.string(from: endDate)
    }
    
    var stringNotificationTime: String {
        var formattedStart = ""
        var formattedEnd = ""
        
        if Calendar.current.isDate(startDate, inSameDayAs: endDate) {
            formattedStart = startDate.formatted(date: .omitted, time: .shortened)
            formattedEnd = endDate.formatted(date: .omitted, time: .shortened)
        } else {
            let formatter = DateFormatter()
            formatter.dateFormat = "MMM d, h:mm a"
            
            formattedStart = formatter.string(from: startDate)
            formattedEnd = formatter.string(from: endDate)
        }
        
        return "\(formattedStart) - \(formattedEnd)"
    }
    
    var currentTime: Double {
        let hour = Double(Calendar.current.component(.hour, from: startDate))
        
        let time = hour / 24
        return time
    }
    
    // Sorting
    static func <(lhs: Event, rhs: Event) -> Bool {
        lhs.startDate < rhs.startDate
    }
    
    static func == (lhs: Event, rhs: Event) -> Bool {
        lhs.id == rhs.id
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }

    // 这里只是更多的枚举
}

当viewModel中的selectedEvent不为nil时,Event会从viewModel传递过来。

.fullScreenCover(item: $calendarViewModel.selectedEvent) { event in
    EventDetailsView(event: event)
}
英文:

I have this EventDetailsView, with many TextFields, Pickers, etc. Previously it was working fantastically, but now, I don't know why, my view isn't responding to any changes. I can only delete it, TextFields, and Pickers are not working.

If you want to see some other file code, right down)

Here is EventDetailsView:


import SwiftUI
struct EventDetailsView: View {
@EnvironmentObject var calendarViewModel: CalendarViewModel
@Environment(\.dismiss) var dismiss
@State private var event: Event // My event that I am passing to this view
var body: some View {
NavigationStack {
GeometryReader { geo in
VStack {
// All the Pickers and TextFields Are here, they are bound to               the event, so I don&#39;t display them, because they are definitely not the problem.
}
Button(&quot;Delete Event&quot;, role: .destructive) {
calendarViewModel.showingDeleteEventDialog = true
}
}
.navigationTitle(&quot;Event Details&quot;)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
dismiss()
} label: {
Label(&quot;Back&quot;, systemImage: &quot;chevron.left&quot;)
.labelStyle(.titleAndIcon)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(&quot;Save&quot;) {
calendarViewModel.changeEvent(event)
dismiss()
}
}
}
.alert(&quot;Failed to set a Notification.&quot;, isPresented: $calendarViewModel.showingAlertErrorAlert) {
Button(&quot;Cancel&quot;, role: .cancel) {
event.alert = .noAlerts
calendarViewModel.setNoAlerts()
}
Button(&quot;Settings&quot;) {
event.alert = .noAlerts
calendarViewModel.setNoAlerts()
calendarViewModel.openSettings()
}
} message: {
Text(&quot;Please, enable notifications for this app in settings.&quot;)
}
.confirmationDialog(&quot;&quot;, isPresented: $calendarViewModel.showingDeleteEventDialog) {
Button(&quot;Delete&quot;, role: .destructive) {
calendarViewModel.deleteEvent(event)
}
Button(&quot;Cancel&quot;, role: .cancel) { }
} message: {
Text(&quot;Are you sure you want to delete this event?&quot;)
}
}
}
}
init(event: Event) {
_event = State(wrappedValue: event) // Here I passing the event and creating the new State object
}
}
struct EventDetailsView_Previews: PreviewProvider {
static var previews: some View {
EventDetailsView(event: Event.example)
.environmentObject(CalendarViewModel())
}
}

Also Here is Event struct:

struct Event: Identifiable, Codable, Hashable, Comparable {
var id = UUID()
var title: String
var description: String
var tag: EventTags
var startDate: Date
var endDate: Date
var repeatEvent: RepeatEvents
var alert: Alerts
var isCritical: Bool
var sound: String
var soundVolume: Double
var color: EventColors.RawValue
var background: Backgrounds
var backgroundLocations: BackgroundLocation
static let example = Event(title: &quot;Workout&quot;, description: &quot;We need to get strong&quot;, tag: .workout, startDate: Date(), endDate: Date().addingTimeInterval(86400), repeatEvent: .everyDay, alert: .noAlerts, isCritical: false, sound: &quot;&quot;, soundVolume: 0.5, color: &quot;darkGray&quot;, background: .color, backgroundLocations: BackgroundLocation.example)
var stringStartDate: String {
let formatter = DateFormatter()
formatter.dateFormat = &quot;MMM d, HH:mm a&quot;
return formatter.string(from: startDate)
}
var stringEndDate: String {
let formatter = DateFormatter()
formatter.dateFormat = &quot;MMM d, HH:mm a&quot;
return formatter.string(from: endDate)
}
var stringNotificationTime: String {
var formattedStart = &quot;&quot;
var formattedEnd = &quot;&quot;
if Calendar.current.isDate(startDate, inSameDayAs: endDate) {
formattedStart = startDate.formatted(date: .omitted, time: .shortened)
formattedEnd = endDate.formatted(date: .omitted, time: .shortened)
} else {
let formatter = DateFormatter()
formatter.dateFormat = &quot;MMM d, h:mm a&quot;
formattedStart = formatter.string(from: startDate)
formattedEnd = formatter.string(from: endDate)
}
return &quot;\(formattedStart) - \(formattedEnd)&quot;
}
var currentTime: Double {
let hour = Double(Calendar.current.component(.hour, from: startDate))
let time = hour / 24
return time
}
// Sorting
static func &lt;(lhs: Event, rhs: Event) -&gt; Bool {
lhs.startDate &lt; rhs.startDate
}
static func == (lhs: Event, rhs: Event) -&gt; Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
// Here is just more enums
}

Event got passed from my viewModel when it isn't nil.

.fullScreenCover(item: $calendarViewModel.selectedEvent) { event in
EventDetailsView(event: event)
}

答案1

得分: 2

当将数据传递给子视图结构体时,你有两个选项。使用let进行只读访问,或者使用@Binding var进行读写访问,例如:

struct EventDetailsView: View {
    @Binding var event: Event // 读写访问
    // let event: Event // 只读访问
}

然而,在设计视图结构体时,你应该问自己:这个视图需要哪些数据来完成它的工作?因此,与其传递整个Event对象,最好只传递实际使用的数据。例如,在你的情况下,详细视图并不需要Event结构体的所有参数。这将提高变化检测的性能(因为只有当实际使用的Event属性发生变化时,才会调用body,而不是任何属性发生变化时),并且更容易创建预览(因为你不需要创建一个完整的测试Event对象)。代码示例如下:

struct MyDetailView: View {
    let title: String
    @Binding var startDate: Date
}

使用方式如下:

MyDetailView(title: event.title, startDate: $event.startDate)
英文:

When passing data down to a child View struct you have 2 options. let for read access or @Binding var for write access, e.g.

struct EventDetailsView: View {
@Binding var event: Event // for read/write access
// let event: Event // if only need read access

However, when designing your View structs, you should ask yourself - what data does this View need to do its job? Thus instead of passing in the whole Event it would be better to only pass in the data it actually uses. I.e. in your case the detail view doesn't need all those params of the Event struct. This will improve performance of the change detection (since body will only be called when the properties of the Event actually being used change rather than any property changing) and make it easier to create Previews (since you don't need to create a whole test Event). This could look like this:

struct MyDetailView: View {
let title: String
@Binding var startDate: Date

And use it like:

MyDetailView(title: event.title, startDate: $event.startDate)

huangapple
  • 本文由 发表于 2023年8月8日 23:56:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76861263.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定