Я пытаюсь понять, как и когда вызываются инициализаторы .sheet и .fullScreenCover. Ниже приведен минимальный воспроизводимый пример, где первый экран имеет 3 цветных прямоугольника, а SecondView (показанный через .fullScreenCover) имеет 1 прямоугольник, который меняет цвет в зависимости от выбранного цвета на первом экране.
Итак, мне интересно, почему эта настройка НЕ работает при начальной загрузке, но работает со 2-й или 3-й попытки?
Примечание. Я понимаю, что это можно решить, изменив «let selectedColor» на переменную @Binding, это не то, о чем я прошу.
Код:
import SwiftUI
struct SegueTest: View {
@State var showSheet: Bool = false
@State var color: Color = .gray
var body: some View {
HStack {
RoundedRectangle(cornerRadius: 25)
.fill(Color.red)
.frame(width: 100, height: 100)
.onTapGesture {
color = .red
showSheet.toggle()
}
RoundedRectangle(cornerRadius: 25)
.fill(Color.green)
.frame(width: 100, height: 100)
.onTapGesture {
color = .green
showSheet.toggle()
}
RoundedRectangle(cornerRadius: 25)
.fill(Color.orange)
.frame(width: 100, height: 100)
.onTapGesture {
color = .orange
showSheet.toggle()
}
}
.fullScreenCover(isPresented: $showSheet, content: {
SecondView(selectedColor: color)
})
}
}
struct SecondView: View {
@Environment(\.presentationMode) var presentationMode
let selectedColor: Color // Should change to @Binding
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
RoundedRectangle(cornerRadius: 25)
.fill(selectedColor)
.frame(width: 300, height: 300)
}
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
}
}
struct SegueTest_Previews: PreviewProvider {
static var previews: some View {
SegueTest()
}
}
Смотрите комментарии и print
заявления. Особенно red
import SwiftUI
struct SegueTest: View {
@State var showSheet: Bool = false{
didSet{
print("showSheet :: didSet")
}
willSet{
print("showSheet :: willSet")
}
}
@State var color: Color = .gray{
didSet{
print("color :: didSet :: \(color.description)")
}
willSet{
print("color :: willSet :: \(color.description)")
}
}
@State var refresh: Bool = false
init(){
print("SegueTest " + #function)
}
var body: some View {
print(#function)
return HStack {
//Just to see what happens when you recreate the View
//Text(refresh.description)
Text(color.description)
RoundedRectangle(cornerRadius: 25)
.fill(Color.red)
.frame(width: 100, height: 100)
.onTapGesture {
print("SegueTest :: onTapGesture :: red")
//Changing the color
color = .red
//Refreshed SegueTest reloads function
//refresh.toggle()
showSheet.toggle()
}
RoundedRectangle(cornerRadius: 25)
.fill(Color.green)
.frame(width: 100, height: 100)
.onTapGesture {
print("SegueTest :: onTapGesture :: green")
//Changing the color
color = .green
showSheet.toggle()
}
RoundedRectangle(cornerRadius: 25)
.fill(Color.orange)
.frame(width: 100, height: 100)
.onTapGesture {
print("SegueTest :: onTapGesture :: orange")
//Changing the color
color = .orange
showSheet.toggle()
}
}
//This part is likely created when SegueTest is created and since a struct is immutable it keeps the original value
.fullScreenCover(isPresented: $showSheet, content: {
SecondView(selectedColor: color)
})
}
}
struct SecondView: View {
@Environment(\.presentationMode) var presentationMode
//struct is immutable
let selectedColor: Color // Should change to @Binding
init(selectedColor: Color){
print("SecondView " + #function)
self.selectedColor = selectedColor
print("SecondView :: struct :: selectedColor = \(self.selectedColor.description)" )
print("SecondView :: parameter :: selectedColor = \(selectedColor.description)" )
}
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
RoundedRectangle(cornerRadius: 25)
.fill(selectedColor)
.frame(width: 300, height: 300)
}
.onTapGesture {
presentationMode.wrappedValue.dismiss()
}
}
}
struct SegueTest_Previews: PreviewProvider {
static var previews: some View {
SegueTest()
}
}
Это было бы, если бы это было упомянуто в SegueTest
, измените Text(refresh.description)
на Text(color.description)
и закомментируйте refresh.toggle()
и @State var refresh: Bool = false
. Я просто использовал обновление для объяснения/теста или, может быть, привычки. Поставьте print
в body
. Посмотреть код
Ооо теперь вижу. Поэтому, если я также закомментирую Text(), представление не будет перерисовываться, и цвет не изменится. Но добавив этот Text(), представление вынуждено повторно отображать перед переходом... верно?
Да, именно так это работает. @Binding
вызывает рендеринг в SecondView
, чтобы он работал каждый раз и не зависел от какого-либо другого обновления.
Именно то, что я пытался выяснить. ... так что, если я закомментирую Text(), он не будет повторно отображаться... но со 2-й/3-й попытки он будет повторно отображаться. Любая идея, что вызывает это?
Вероятно, поведение предварительной загрузки SwiftUI. SwiftUI дает много прогнозов. Это определенно было бы чем-то, чтобы попросить Apple получить внутреннюю работу (во время WWDC, чтобы вы не потеряли возможность задать конкретный вопрос по коду или, возможно, на форумах Apple). Он также не меняется, если вы снова и снова нажимаете один и тот же цвет. Он учится только после того, как вы нажмете второй цвет.
Да, я видел это, ха-ха. Еще раз спасибо!
Проблема в том, что вы не используете свой цвет @State внутри SegueView, который не перезагрузит ваше представление при изменении состояния. Просто включите свой @State где-нибудь в код, что заставит его повторно отображать, а затем обновить лист с правильным цветом.
RoundedRectangle(cornerRadius: 25)
.fill(color == .red ? Color.red : Color.red) //<< just use dump color variable here
Если вы не укажете свое состояние цвета где-то в коде, он не будет повторно отображать ваш SegueView, поэтому вы все равно передадите старый серый цвет вашему SecondView
.
Или даже передать свои цвета как Binding your Second View..
SecondView(selectedColor: $color)
@Binding var selectedColor: Color
Это действительно полезно - спасибо. Почему refresh.toggle() перезагружает SegueTest, но не меняет цвет? Оба являются переменными @State. Есть ли способ печати при перезагрузке SegueTest?