SwiftUI @Binding更新不会刷新视图

时间:2019-12-12 06:59:18

标签: binding swiftui

我觉得我缺少一些非常基本的东西,但是此示例SwiftUI代码在单击按钮时不会修改视图(尽管进行了绑定更新)

Tutorials我读过建议这是使用绑定的正确方法,并且视图应自动刷新

// Add observer
webView.addObserver(self, forKeyPath: "URL", options: .new, context: nil)

// Observe value
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if let key = change?[NSKeyValueChangeKey.newKey] {
        print("observeValue \(key)") // url value
    }
}

6 个答案:

答案 0 :(得分:2)

您没有误解任何东西。当基础@State更改时,使用@Binding的View将更新,但是 @State必须在视图层次结构中定义。 (否则您可以绑定到发布者)

下面,我将您的ContentView的名称更改为OriginalContentView,然后在包含您原始内容视图的新ContentView中定义了@State。

import SwiftUI

struct OriginalContentView: View {
    @Binding var isSelected: Bool

    var body: some View {
        Button(action: {
            self.isSelected.toggle()
        }) {
            Text(isSelected ? "Selected" : "Not Selected")
        }
    }
}

struct ContentView: View {
    @State private var selected = false

    var body: some View {
       OriginalContentView(isSelected: $selected)
    }
}



struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

答案 1 :(得分:1)

在SwiftUI的最高级别中,除非手动添加@state或其他刷新触发器,否则@Binding无法刷新View层次结构。

          struct ContentView: View {
        @Binding var isSelected : Bool
        @State var hiddenTrigger = false

        var body: some View {
            VStack{
            Text("\(hiddenTrigger ? "" : "")")
            Button(action: {
                self.isSelected.toggle()
                self.hiddenTrigger = self.isSelected
            }) {
                Text(self.isSelected? "Selected" : "not Selected")
            }
            }
        }
    }

    struct ContentView_Previews: PreviewProvider {

        static var selected: Bool = false

        static var previews: some View {
            ContentView(isSelected: Binding<Bool>(get: {selected}, set: { newValue in
                selected = newValue}))
        }
    }

答案 2 :(得分:1)

SwiftUI视图会影响@Binding。 @State影响SwiftUI视图。 @State var影响视图,但是要影响另一个@State,必须通过在值名称中添加前导$来将其用作绑定,并且只能在SwiftUI中使用。

要从外部触发SwiftUI更改,即传递/更新Image,请使用如下所示的Publisher:

// Declare publisher in Swift (outside SwiftUI)    
public let imagePublisher = PassthroughSubject<Image, Never>()

// And within SwiftUI it must be handled:
struct ContentView: View {
// declare @State that updates View:
    @State var image: Image = Image(systemName: "photo")
    var body: some View {
// Use @State image declaration
                image
// Subscribe this value to publisher "imagePublisher"
                    .onReceive(imagePublisher, perform: { (output: Image) in
// Whenever publisher sends new value, old one to be replaced
                        self.image = output
                    })
}
}

// And this is how to send value to update SwiftUI from Swift:
imagePublisher.send(Image(systemName: "photo"))

答案 3 :(得分:0)

您需要使用@State而不是@Binding。

  • 如果UI应该在其值更改时更新,则可以将变量指定为 @State变量。这是真理的源头。

  • 当视图不拥有此数据且它不是真相的来源时,请使用@Binding而不是@State。

这是您的变量:

@State var isSelected: Bool

答案 4 :(得分:0)

我有一个类似的问题,我创建了一个带有清除按钮和onChange属性的自定义文本字段(因为我们需要支持iOS 13,所以无法使用内置的)。

基本上,我使用带有getter / setter的绑定来触发onChange。由于某种原因,该按钮不会触发绑定的设置器。但是通常可以输入文字。

有人知道为什么会这样吗?

struct ParentView: View {
    @State var text: String = ""
    
    var body: some View {
        CustomTextField(text: $text) { text in
            print("\(text)")
        }
    }
}

struct CustomTextField: View {
    @Binding var text: String
    var onChange: ((String) -> Void)?
    
    var body: some View {
        HStack {
            // entering text normally into this text field does trigger the binding setter below
            TextField(
                placeholder: "Enter Text...", 
                text: Binding(
                    get: {text}, 
                    set{
                        text = $0
                        onChange?($0)
                    }
                )
            )
            Button {
                text = "" // This does not trigger the above binding setter (so my onChange does not get called), but it does clear the text field as expected...
            } label: {
               Image(systemName: "xmark.circle.fill")
            }
        }
    } 

}

答案 5 :(得分:-1)

进一步研究一下,我想我了解发生了什么事。

在这种情况下,我想在构建自定义控件时使用@Binding(例如SwiftUI的本机Toggle,它也绑定到Bool

问题在于,ContentView_Previews中的静态状态(即行@State static var selected: Bool = false)在状态更改时不会触发预览的重新渲染,即使所选状态已更改由于与控件的交互,控件(ContentView_Previews的子级)不会重新呈现自身

这使得很难在SwiftUI预览中单独测试控件,但是将状态移动到虚拟ObservableObject实例中可以正常运行。这是代码:

import SwiftUI
import Combine

class SomeData: ObservableObject {
    @Published var isOn: Bool = false
}

struct MyButton: View {
    @Binding var isSelected: Bool

    var body: some View {
        Button(action: {
            self.isSelected.toggle()
        }) {
            Text(isSelected ? "Selected" : "Not Selected")
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var data: SomeData

    var body: some View {
        MyButton(isSelected: $data.isOn)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environmentObject(SomeData())
    }
}

@State static var中的更改似乎不会触发预览重新渲染。在上面的代码中,我的@Binding示例被移到MyButton中,内容视图的虚拟环境实例被绑定到其isSelected属性。轻按该按钮即可在SwiftUI预览中按预期更新视图。