英文:
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't display them, because they are definitely not the problem.
}
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) // 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: "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)
}
// 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)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论