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
?我查看了文档,但找不到任何内容。
现在有两个新的
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.")
}
}
}
此示例不需要使用单独的函数。你只需要一个关闭。
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)
// }
}
}
}
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)
}
}
}
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)")
}
@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
何时更改值的优势。
let textBinding = Binding<String>(
get: { /* get */ },
set: { /* set $0 */ }
)
'onChange(of:perform:)' 在 iOS 17.0 中已弃用:使用所以,现在你可以用两种方式之一来写:
onChange
用两个或零参数的动作闭包代替。
.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
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”错误。
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()
}
}