Я новичок в 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, то изменение запускается в свойстве pageOne вашего Book. Вы можете создать Page и @Observable, а затем передать book.pageOne в DisplayView вместо book.
@State предназначен только для значений или структур, для классов — @StateObject@workingdogsupportUkraine Я только что проверил. Явление то же самое.
@Paulw11 Спасибо, я так и предполагал. Но мне просто нужен кто-то, кто подтвердит мои мысли.





Во-первых, вам также следует сделать 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 не вызывается.
Большое спасибо за ваш ответ. Это объясняет все. Причина, по которой я привел этот пример, заключается в том, что когда я его изучал, мне было любопытно, как этот механизм реализован внутри и какого уровня функциональности он может достичь. Для нас, новичков, иногда документация недостаточно подробная, и мы не можем видеть внутреннюю реализацию, что может сильно расстраивать, когда мы сталкиваемся с некоторыми странными явлениями.
Что произойдет, если вы поставите
@Observable class Pageилиstruct Page?