Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/sharepoint/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
SwiftUI:在@Binding值更改时收到通知_Binding_Swiftui - Fatal编程技术网

SwiftUI:在@Binding值更改时收到通知

SwiftUI:在@Binding值更改时收到通知,binding,swiftui,Binding,Swiftui,我写了一个视图来在SwiftUI中创建打字机效果——当我第一次传入绑定变量时,它工作正常,例如:TypewriterTextView($textString) 但是,textString值的任何后续更改都将不起作用(因为绑定值不会直接放置在正文中)。我对如何在视图中更改@Binding变量时手动通知感兴趣 struct TypewriterTextView: View { @Binding var textString:String @State private var typ

我写了一个视图来在SwiftUI中创建打字机效果——当我第一次传入绑定变量时,它工作正常,例如:TypewriterTextView($textString)

但是,textString值的任何后续更改都将不起作用(因为绑定值不会直接放置在正文中)。我对如何在视图中更改@Binding变量时手动通知感兴趣

struct TypewriterTextView: View {

    @Binding var textString:String
    @State private var typingInterval = 0.3
    @State private var typedString = ""

    var body: some View {
        Text(typedString).onAppear() {
            Timer.scheduledTimer(withTimeInterval: self.typingInterval, repeats: true, block: { timer in

                if self.typedString.length < self.textString.length {
                    self.typedString = self.typedString + self.textString[self.typedString.length]
                }
                else { timer.invalidate() }
            })
        }
    }
}
struct TypewriterTextView:View{
@绑定变量textString:String
@国家私有变量typingInterval=0.3
@国家私有变量typedString=“”
var body:一些观点{
Text(typedString).onAppear(){
scheduledTimer(withTimeInterval:self.typingInterval,repeats:true,block:{Timer in
如果self.typedString.length
您可以使用所谓的发布者来实现以下目的:

public let subject = PassthroughSubject<String, Never>()
通常,您希望上述代码位于SwiftUI声明之外

现在,在您的SwiftUI代码中,您需要接收以下信息:

Text(typedString)
    .onReceive(<...>.subject)
    { (string) in
        self.typedString = string
    }
我知道当
类型字符串
@状态
时,上述操作应该有效:

 .onReceive((UIApplication.shared.delegate as! AppDelegate).subject)
@State private var typedString = ""
但我想它也应该与
@绑定一起工作
;只是还没有尝试过。

使用而不是
onAppear()
来查看
文本字符串的绑定

struct TypewriterTextView: View {
    @Binding var textString:String
    @State private var typingInterval = 0.3
    @State private var typedString = ""

    var body: some View {
        Text(typedString).onChange(of: textString) {
            typedString = ""
            Timer.scheduledTimer(withTimeInterval: self.typingInterval, repeats: true, block: { timer in

                if self.typedString.length < self.textString.length {
                    self.typedString = self.typedString + self.textString[self.typedString.length]
                }
                else { timer.invalidate() }
            })
        }
    }
}

根据@Damiaan Dufaux的答案复制并粘贴解决方案

  • 使用它就像system
    onChange
    api一样。它更喜欢在iOS 14上使用系统提供的
    onChange
    api,在较低版本上使用备份计划
  • 当更改为相同的值时,不会调用操作。(如果使用@Damiaan Dufaux的答案,您可能会发现,如果数据更改为相同的值,则会调用操作,因为每次都会重新创建
    model
  • struct ChangeObserver:视图{
    让内容:内容
    让价值:价值
    让动作:(值)->Void
    init(值:值,操作:@escaping(值)->Void,内容:@escaping()->content){
    自我价值=价值
    行动
    self.content=content()
    _oldValue=状态(初始值:值)
    }
    @国有私有var oldValue:值
    var body:一些观点{
    如果oldValue!=值{
    DispatchQueue.main.async{
    oldValue=值
    自我行动(自我价值)
    }
    }
    返回内容
    }
    }
    扩展视图{
    func onDataChange(值为:value的,执行操作:@escaping(u-newValue:value)->Void)->一些视图{
    团体{
    如果可用(iOS 14.0、macOS 11.0、tvOS 14.0、watchOS 7.0、*){
    onChange(of:value,perform:action)
    }否则{
    ChangeObserver(值:值,操作:操作){
    自己
    }
    }
    }
    }
    }
    
    @Binding
    只应在子视图需要写入值时使用。在您的情况下,您只需阅读它,就可以将其更改为
    let textString:String
    body
    将在每次更改时运行。这是在父视图中用新值重新创建此视图时。这就是SwiftUI的工作原理,它只在自上次创建
    视图以来
    结构变量(或let)发生更改的情况下运行body。

    我尝试了它,它直接工作。然后我读了代码,意识到这个答案太聪明了!只需使用一个视图,因为它知道何时需要更新并执行分配的操作。这是一个非常好的介绍。我只是将它与ScrollViewReader结合使用,以便在“选定”项发生更改时滚动到该项,它就像一个符咒一样工作。@在这里绑定是一个错误,使用onChange修复错误会使它变得更糟
    textString
    应该是let,因为此视图不向其写入。
    struct TypewriterTextView: View {
        @Binding var textString:String
        @State private var typingInterval = 0.3
        @State private var typedString = ""
    
        var body: some View {
            Text(typedString).onChange(of: textString) {
                typedString = ""
                Timer.scheduledTimer(withTimeInterval: self.typingInterval, repeats: true, block: { timer in
    
                    if self.typedString.length < self.textString.length {
                        self.typedString = self.typedString + self.textString[self.typedString.length]
                    }
                    else { timer.invalidate() }
                })
            }
        }
    }
    
    import Combine
    import SwiftUI
    
    /// See `View.onChange(of: value, perform: action)` for more information
    struct ChangeObserver<Base: View, Value: Equatable>: View {
        let base: Base
        let value: Value
        let action: (Value)->Void
    
        let model = Model()
    
        var body: some View {
            if model.update(value: value) {
                DispatchQueue.main.async { self.action(self.value) }
            }
            return base
        }
    
        class Model {
            private var savedValue: Value?
            func update(value: Value) -> Bool {
                guard value != savedValue else { return false }
                savedValue = value
                return true
            }
        }
    }
    
    extension View {
        /// Adds a modifier for this view that fires an action when a specific value changes.
        ///
        /// You can use `onChange` to trigger a side effect as the result of a value changing, such as an Environment key or a Binding.
        ///
        /// `onChange` is called on the main thread. Avoid performing long-running tasks on the main thread. If you need to perform a long-running task in response to value changing, you should dispatch to a background queue.
        ///
        /// The new value is passed into the closure. The previous value may be captured by the closure to compare it to the new value. For example, in the following code example, PlayerView passes both the old and new values to the model.
        ///
        /// ```
        /// struct PlayerView : View {
        ///   var episode: Episode
        ///   @State private var playState: PlayState
        ///
        ///   var body: some View {
        ///     VStack {
        ///       Text(episode.title)
        ///       Text(episode.showTitle)
        ///       PlayButton(playState: $playState)
        ///     }
        ///   }
        ///   .onChange(of: playState) { [playState] newState in
        ///     model.playStateDidChange(from: playState, to: newState)
        ///   }
        /// }
        /// ```
        ///
        /// - Parameters:
        ///   - value: The value to check against when determining whether to run the closure.
        ///   - action: A closure to run when the value changes.
        ///   - newValue: The new value that failed the comparison check.
        /// - Returns: A modified version of this view
        func onChange<Value: Equatable>(of value: Value, perform action: @escaping (_ newValue: Value)->Void) -> ChangeObserver<Self, Value> {
            ChangeObserver(base: self, value: value, action: action)
        }
    }
    
    struct ChangeObserver<Content: View, Value: Equatable>: View {
        let content: Content
        let value: Value
        let action: (Value) -> Void
    
        init(value: Value, action: @escaping (Value) -> Void, content: @escaping () -> Content) {
            self.value = value
            self.action = action
            self.content = content()
            _oldValue = State(initialValue: value)
        }
    
        @State private var oldValue: Value
    
        var body: some View {
            if oldValue != value {
                DispatchQueue.main.async {
                    oldValue = value
                    self.action(self.value)
                }
            }
            return content
        }
    }
    
    extension View {
        func onDataChange<Value: Equatable>(of value: Value, perform action: @escaping (_ newValue: Value) -> Void) -> some View {
            Group {
                if #available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) {
                    self.onChange(of: value, perform: action)
                } else {
                    ChangeObserver(value: value, action: action) {
                        self
                    }
                }
            }
        }
    }