Я новичок в SwiftUI. Извините, если это глупый вопрос.
@Observable
class Book {
var title = "A Sample Book"
var pageOne = Page()
}
class Page {
var pageTitle = "pageTitle"
var isAvailable = true
}
struct ContentView: View {
@State private var book = Book()
var body: some View {
VStack{
ChangeView(book: book)
DisplayView(book: book)
}
}
}
struct ChangeView: View{
@Bindable var book: Book
var body: some View{
Toggle(book.pageOne.isAvailable ? "page available" : "page not available",
isOn: $book.pageOne.isAvailable
)
}
}
struct DisplayView: View {
var book: Book
var body: some View {
Text(book.pageOne.pageTitle)
}
}
ChangeView
читает и смотрит book.pageOne.isAvailable
. Каждый раз, когда я нажимаю переключатель, тело ChangeView
перестраивается. Это имеет смысл.
Но я обнаружил, что тело DisplayView
также перестраивается каждый раз, когда я нажимаю переключатель, даже если оно вообще не заботится о isAvailable
. Итак, мой вопрос: зачем SwiftUI перестраивать DisplayView
, если переменная не имеет значения? Кажется, это отличается от того, что сказано в документе. Это потому, что isAvaialble
является подсвойством данных Observable?
Во-первых, вам также следует сделать Page
@Observable
. В противном случае SwiftUI не сможет должным образом отслеживать свои изменения.
@Observable
class Page {
var pageTitle = "pageTitle"
var isAvailable = true
}
Однако одного этого недостаточно, чтобы решить проблему. Чтобы это исправить, вы можете либо поставить Bindable
вместо Page
(в этом случае book
не обязательно должно быть @Bindable
),
@Bindable var page = book.pageOne
Toggle(book.pageOne.isAvailable ? "page available" : "page not available",
isOn: $page.isAvailable
)
или сделайте:
Toggle(book.pageOne.isAvailable ? "page available" : "page not available",
isOn: $book[dynamicMember: \.pageOne.isAvailable]
)
Объяснение:
Когда вы это делаете $book.pageOne.isAvailable
, он снижается (т. е. эквивалентно) до:
$book[dynamicMember: \.pageOne][dynamicMember: \.isAvailable]
Первая пара []
— это вызов Bindable.subscript . Это создает Binding<Page>
. Вторая пара []
— это вызов Binding.subscript, создающий Bindable<Bool>
.
Если вы понятия не имеете, о чем идет речь, посмотрите SE-0195 и SE-0252.
Проблема в этом промежуточном Binding<Page>
. Когда последний Binding<Bool>
изменяется переключателем, очевидно, вызывается установщик isAvailable
, но, по моим наблюдениям, также вызывается установщик pageOne
.
Поскольку Page
является ссылочным типом, этот вызов сеттера не нужен. Возможно, это потому, что Binding<Bool>
производится из Binding<Page>
. В конце концов, Binding
изначально предназначены для работы со структурами, и если бы Page
была структурой, абсолютно необходимо вызвать установщик pageOne
.
В любом случае, поскольку вызывается установщик pageOne
, когда isAvailable
изменяется, SwiftUI думает, что pageOne
тоже меняется. Поскольку DisplayView.body
вызывает метод получения pageOne
, SwiftUI будет вызывать DisplayView.body
снова, когда isAvailable
изменится.
В обоих решениях я исключил промежуточный Binding<Page>
, поэтому установщик pageOne
не вызывается.