当状态发生变化时如何运行操作?

问题描述 投票:0回答:9
enum SectionType: String, CaseIterable {
    case top = "Top"
    case best = "Best"
}

struct ContentView : View {
    @State private var selection: Int = 0

    var body: some View {
        SegmentedControl(selection: $selection) {
            ForEach(SectionType.allCases.identified(by: \.self)) { type in
                Text(type.rawValue).tag(type)
            }
        }
    }
}

如何运行代码(例如,当

print("Selection changed to \(selection)")
状态发生变化时,
$selection
?我查看了文档,但找不到任何内容。

ios swift state swiftui
9个回答
84
投票

现在有两个新的

onChange(of:initial:_:)
,一个带有 zero 闭包参数,另一个带有 two

两者现在还提供了选择在视图最初出现时运行代码块的选项,方法是使用

initial: true
(默认为
false
)。当您只想获取新值时,前者的闭包参数为零,现在是新的“默认值”。后者具有两个闭包参数,可让您获取要比较的旧值和新值,类似于我们过去使用捕获组来获取旧值的方式。

iOS 17.0+

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

    var body: some View {
        Toggle("Light", isOn: $isLightOn)
            .onChange(of: isLightOn) {
                if isLightOn {
                    print("Light is now on!")
                } else {
                    print("Light is now off.")
                }
            }
    }
}

iOS 14.0+

您可以使用

onChange(of:perform:)
修饰符,如下所示:

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

    var body: some View {
        Toggle("Light", isOn: $isLightOn)
            .onChange(of: isLightOn) { value in
                if value {
                    print("Light is now on!")
                } else {
                    print("Light is now off.")
                }
            }
    }
}

iOS 13.0+

以下内容作为

Binding
的扩展,因此只要值发生变化就可以执行闭包。

extension Binding {
    
    /// When the `Binding`'s `wrappedValue` changes, the given closure is executed.
    /// - Parameter closure: Chunk of code to execute whenever the value changes.
    /// - Returns: New `Binding`.
    func onUpdate(_ closure: @escaping () -> Void) -> Binding<Value> {
        Binding(get: {
            wrappedValue
        }, set: { newValue in
            wrappedValue = newValue
            closure()
        })
    }
}

例如这样使用:

struct ContentView: View {
    
    @State private var isLightOn = false
    
    var body: some View {
        Toggle("Light", isOn: $isLightOn.onUpdate(printInfo))
    }
    
    private func printInfo() {
        if isLightOn {
            print("Light is now on!")
        } else {
            print("Light is now off.")
        }
    }
}

此示例不需要使用单独的函数。你只需要一个关闭。


52
投票
您不能在

didSet

 上使用 
@State
 观察者,但可以在 
ObservableObject
 属性上使用。 

import SwiftUI import Combine final class SelectionStore: ObservableObject { var selection: SectionType = .top { didSet { print("Selection changed to \(selection)") } } // @Published var items = ["Jane Doe", "John Doe", "Bob"] }

然后像这样使用它:

import SwiftUI enum SectionType: String, CaseIterable { case top = "Top" case best = "Best" } struct ContentView : View { @ObservedObject var store = SelectionStore() var body: some View { List { Picker("Selection", selection: $store.selection) { ForEach(FeedType.allCases, id: \.self) { type in Text(type.rawValue).tag(type) } }.pickerStyle(SegmentedPickerStyle()) // ForEach(store.items) { item in // Text(item) // } } } }
    

26
投票
iOS 13+

您可以使用

onReceive

import Combine import SwiftUI struct ContentView: View { @State private var selection = false var body: some View { Toggle("Selection", isOn: $selection) .onReceive(Just(selection)) { selection in // print(selection) } } }
    

25
投票
在 iOS 14 中,现在有一个

onChange

 修饰符,您可以像这样使用:

SegmentedControl(selection: $selection) { ForEach(SectionType.allCases.identified(by: \.self)) { type in Text(type.rawValue).tag(type) } } .onChange(of: selection) { value in print("Selection changed to \(selection)") }
    

23
投票
如果您有一个更新

@Binding

 的组件,这是另一个选项。而不是这样做:

Component(selectedValue: self.$item, ...)

您可以这样做并拥有更大的控制权:

Component(selectedValue: Binding( get: { self.item }, set: { (newValue) in self.item = newValue // now do whatever you need to do once this has changed }), ... )

通过这种方式,您可以获得绑定的好处以及检测

Component

 何时更改值的优势。


3
投票
您可以使用绑定

let textBinding = Binding<String>( get: { /* get */ }, set: { /* set $0 */ } )
    

3
投票
iOS 17+

又变了!单参数闭包现在会发出此警告:

'onChange(of:perform:)' 在 iOS 17.0 中已弃用:使用

onChange

用两个或零参数的动作闭包代替。

所以,现在你可以用两种方式之一来写:

    零参数方式。
  1. 2参数方式。
零参数

.onChange(of: playState) { model.playStateDidChange(state: playState) }
参见 

https://developer.apple.com/documentation/swiftui/view/onchange(of:initial:_:)-8wgw9?ref=optimistic-closures.com

2参数

.onChange(of: playState) { oldState, newState in model.playStateDidChange(from: oldState, to: newState) }
参见 

https://developer.apple.com/documentation/swiftui/view/onchange(of:initial:_:)-4psgg?ref=optimistic-closures.com


1
投票
并没有真正回答你的问题,但这是设置的正确方法

SegmentedControl

(不想将该代码作为评论发布,因为它看起来很难看)。将您的 
ForEach
 版本替换为以下代码:

ForEach(0..<SectionType.allCases.count) { index in Text(SectionType.allCases[index].rawValue).tag(index) }

用枚举案例甚至字符串标记视图会导致其行为不当——选择不起作用。

您可能还想在

SegmentedControl

 声明后添加以下内容以确保选择有效:

Text("Value: \(SectionType.allCases[self.selection].rawValue)")



完整版

body

:

var body: some View { VStack { SegmentedControl(selection: self.selection) { ForEach(0..<SectionType.allCases.count) { index in Text(SectionType.allCases[index].rawValue).tag(index) } } Text("Value: \(SectionType.allCases[self.selection].rawValue)") } }

关于你的问题 – 我尝试将

didSet

 观察者添加到 
selection
,但它会导致 Xcode 编辑器崩溃,并在尝试构建时生成“分段错误:11”错误。


0
投票
我喜欢通过将数据移动到结构中来解决这个问题:

struct ContentData { var isLightOn = false { didSet { if isLightOn { print("Light is now on!") } else { print("Light is now off.") } // you could update another var in this struct based on this value } } } struct ContentView: View { @State private var data = ContentData() var body: some View { Toggle("Light", isOn: $data.isLightOn) } }
这种方式的优点是,如果您决定根据 

didSet

 中的新值更新结构中的另一个 var,并且如果您使绑定动画化,例如
isOn: $data.isLightOn.animation()
 那么您更新的使用其他变量的任何视图都将在切换期间以动画方式显示它们的更改。如果您使用 
onChange
,则不会发生这种情况。

例如这里列表排序顺序更改动画:

import SwiftUI struct ContentData { var ascending = true { didSet { sort() } } var colourNames = ["Red", "Green", "Blue", "Orange", "Yellow", "Black"] init() { sort() } mutating func sort(){ if ascending { colourNames.sort() }else { colourNames.sort(by:>) } } } struct ContentView: View { @State var data = ContentData() var body: some View { VStack { Toggle("Sort", isOn:$data.ascending.animation()) List(data.colourNames, id: \.self) { name in Text(name) } } .padding() } }
    
© www.soinside.com 2019 - 2024. All rights reserved.