SwiftUI Picker onChange或等效版本?

tra*_*per 3 swiftui

我想@State在a Picker被更改但没有更改onChanged并且不能将其didSet置于选择器状态时更改其他一些不相关的变量,那么我还能怎么做呢?

ccw*_*den 77

这是我刚刚决定使用的... (部署目标 iOS 13)

struct MyPicker: View {
    @State private var favoriteColor = 0

    var body: some View {
        Picker(selection: $favoriteColor.onChange(colorChange), label: Text("Color")) {
            Text("Red").tag(0)
            Text("Green").tag(1)
            Text("Blue").tag(2)
        }
    }

    func colorChange(_ tag: Int) {
        print("Color tag: \(tag)")
    }
}
Run Code Online (Sandbox Code Playgroud)

使用这个助手

extension Binding {
    func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value> {
        return Binding(
            get: { self.wrappedValue },
            set: { selection in
                self.wrappedValue = selection
                handler(selection)
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:

如果您的部署目标设置为 iOS 14 或更高版本——Apple已经为 提供了一个内置onChange扩展View,可以这样使用(感谢@Jeremy):

extension Binding {
    func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value> {
        return Binding(
            get: { self.wrappedValue },
            set: { selection in
                self.wrappedValue = selection
                handler(selection)
        })
    }
}
Run Code Online (Sandbox Code Playgroud)


sab*_*and 15

我认为这是更简单的解决方案:

@State private var pickerIndex = 0
var yourData = ["Item 1", "Item 2", "Item 3"]

// USE this if needed to notify parent
@Binding var notifyParentOnChangeIndex: Int    

var body: some View {

   let pi = Binding<Int>(get: {

            return self.pickerIndex

        }, set: {

            self.pickerIndex = $0

            // TODO: DO YOUR STUFF HERE
            // TODO: DO YOUR STUFF HERE
            // TODO: DO YOUR STUFF HERE

            // USE this if needed to notify parent
            self.notifyParentOnChangeIndex = $0

        })

   return VStack{

            Picker(selection: pi, label: Text("Yolo")) {
                ForEach(self.yourData.indices) {
                    Text(self.yourData[$0])
                }
            }
            .pickerStyle(WheelPickerStyle())
            .padding()

   }

}
Run Code Online (Sandbox Code Playgroud)


Tim*_*Tim 8

我知道这是一年前的帖子,但我认为这个解决方案可能会帮助那些需要解决方案的人停下来参观。希望它可以帮助别人。

import Foundation
import SwiftUI

struct MeasurementUnitView: View {
    
    @State var selectedIndex = unitTypes.firstIndex(of: UserDefaults.standard.string(forKey: "Unit")!)!
    var userSettings: UserSettings
    
    var body: some View {
        
        VStack {
            Spacer(minLength: 15)
            Form {
                Section {
                    Picker(selection: self.$selectedIndex, label: Text("Current UnitType")) {
                        
                        ForEach(0..<unitTypes.count, id: \.self) {
                            Text(unitTypes[$0])
                        }
                    }.onReceive([self.selectedIndex].publisher.first()) { (value) in
                        self.savePick()
                    }
                    .navigationBarTitle("Change Unit Type", displayMode: .inline)
                }
            }
        }
    }
    
    func savePick() {
        
        if (userSettings.unit != unitTypes[selectedIndex]) {
            userSettings.unit = unitTypes[selectedIndex]
        }
    }
}


Run Code Online (Sandbox Code Playgroud)


sno*_*ton 7

首先,完全归功于ccwasden 的最佳答案。我不得不稍微修改它以使其对我有用,所以我回答这个问题希望其他人也会发现它也有用。

这是我最终得到的结果(在 iOS 14 GM 和 Xcode 12 GM 上测试)

struct SwiftUIView: View {
    @State private var selection = 0

    var body: some View {
        Picker(selection: $selection, label: Text("Some Label")) {
            ForEach(0 ..< 5) {
                Text("Number \($0)") }
        }.onChange(of: selection) { _ in
            print(selection)
        }
        
    }
}
Run Code Online (Sandbox Code Playgroud)

包含“_ in”正是我所需要的。没有它,我收到错误“无法将‘Int’类型的值转换为预期的参数类型‘()’”

  • 这应该是公认的解决方案,简单且开门见山。 (7认同)

Mic*_*mon 5

我使用分段选择器,并且有类似的要求。尝试了几件事之后,我只使用了同时具有ObservableObjectPublisherPassthroughSubject发布者作为选择的对象。这让我很满意SwiftUI,并且onReceive()我也可以做其他事情。

// Selector for the base and radix
Picker("Radix", selection: $base.value) {
    Text("Dec").tag(10)
    Text("Hex").tag(16)
    Text("Oct").tag(8)
}
.pickerStyle(SegmentedPickerStyle())
// receiver for changes in base
.onReceive(base.publisher, perform: { self.setRadices(base: $0) })
Run Code Online (Sandbox Code Playgroud)

base同时拥有一个objectWillChange和一个PassthroughSubject<Int, Never>想象中称为发布者的发布者。

class Observable<T>: ObservableObject, Identifiable {
    let id = UUID()
    let objectWillChange = ObservableObjectPublisher()
    let publisher = PassthroughSubject<T, Never>()
    var value: T {
        willSet { objectWillChange.send() }
        didSet { publisher.send(value) }
    }

    init(_ initValue: T) { self.value = initValue }
}

typealias ObservableInt = Observable<Int>
Run Code Online (Sandbox Code Playgroud)

定义objectWillChange并不是绝对必要的,但是当我写信时,我想提醒自己它在那里。


paw*_*222 5

SwiftUI 1 和 2

使用onReceiveJust

import Combine
import SwiftUI

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

    var body: some View {
        Picker("Some Label", selection: $selection) {
            ForEach(0 ..< 5, id: \.self) {
                Text("Number \($0)")
            }
        }
        .onReceive(Just(selection)) {
            print("Selected: \($0)")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Geo*_*Geo 5

对于必须同时支持 iOS 13 和 14 的人,我添加了一个适用于两者的扩展。不要忘记导入Combine。

Extension View {
    @ViewBuilder func onChangeBackwardsCompatible<T: Equatable>(of value: T, perform completion: @escaping (T) -> Void) -> some View {
        if #available(iOS 14.0, *) {
            self.onChange(of: value, perform: completion)
        } else {
            self.onReceive([value].publisher.first()) { (value) in
                completion(value)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

Picker(selection: $selectedIndex, label: Text("Color")) {
    Text("Red").tag(0)
    Text("Blue").tag(1)
}.onChangeBackwardsCompatible(of: selectedIndex) { (newIndex) in
    print("Do something with \(newIndex)")
}
Run Code Online (Sandbox Code Playgroud)

重要说明:如果您在完成块内更改观察对象内的已发布属性,则此解决方案将导致 iOS 13 中的无限循环。但是,通过添加检查可以轻松修复它,如下所示:

.onChangeBackwardsCompatible(of: showSheet, perform: { (shouldShowSheet) in
   if shouldShowSheet {
      self.router.currentSheet = .chosenSheet
      showSheet = false
   }
})

Run Code Online (Sandbox Code Playgroud)