切换swiftUI toggle()时,如何触发操作?

切换swiftUI toggle()时,如何触发操作?,swift,toggle,swiftui,Swift,Toggle,Swiftui,在我的SwiftUI视图中,当Toggle()更改其状态时,我必须触发一个操作。切换本身只接受一个绑定。 因此,我尝试在@State变量的didSet中触发该操作。但是迪塞特从来没有被叫来过 是否有任何(其他)方式触发动作?或者用什么方法来观察@State变量的值变化 我的代码如下所示: func toggleAction(state: String, index: Int) -> String { print("The switch no. \(index) is \(state

在我的SwiftUI视图中,当Toggle()更改其状态时,我必须触发一个操作。切换本身只接受一个绑定。 因此,我尝试在@State变量的didSet中触发该操作。但是迪塞特从来没有被叫来过

是否有任何(其他)方式触发动作?或者用什么方法来观察@State变量的值变化

我的代码如下所示:

func toggleAction(state: String, index: Int) -> String {
    print("The switch no. \(index) is \(state)")
    return ""
}
struct PWSDetailView : View {
    @State var isDisplayed: Bool = false
    @ObservedObject var station: PWS
    ...

    var body: some View {
        ...

        UIToggle(isOn: $isDisplayed) { isOn in
            //Do something here with the bool if you want
            //or use "_ in" instead, e.g.
            if isOn != station.isDisplayed {
                PWSStore.shared.toggleIsDisplayed(station)
            }
        }
        ...
    }   
}
struct PWSDetailView:View{
@ObjectBinding var站:PWS
@显示状态变量:Bool=false{
迪塞特{
如果显示!=station.isDisplayed{
PWSStore.shared.toggleIsDisplayed(站点)
}
}
}
var body:一些观点{
VStack{
ZStack(对齐:。引导){
矩形()
.框架(宽度:UIScreen.main.bounds.width,高度:50)
.foregroundColor(颜色为lokalZeroBlue)
文本(station.displayName)
.font(.title)
.foregroundColor(颜色.白色)
.padding(.leading)
}
地图视图(纬度:station.latitude,经度:station.latitude,跨度:0.05)
.框架(高度:UIScreen.main.bounds.height/3)
.padding(.top,-8)
形式{
切换(显示:$isOn)
{Text(“Wetterstation”)}
}
垫片()
}.colorScheme(.dark)
}
}

所需的行为是,当Toggle()更改其状态时,会触发操作“PWSStore.shared.toggleIsDisplayed(station)”。

首先,您是否知道station.isDisplayed的额外KVO通知是一个问题?您是否遇到性能问题?如果没有,那就别担心了

如果您遇到性能问题,并且已经确定这些问题是由于过度显示的
station.isDisplayed
KVO通知造成的,那么下一步要尝试的是消除不必要的KVO通知。您可以通过切换到手动KVO通知来实现这一点

将此方法添加到
站点的类定义中:

@objc class var automaticallyNotifiesObserversOfIsDisplayed: Bool { return false }
并使用Swift的
willSet
didSet
观察员手动通知KVO观察员,但仅当值发生变化时:

@objc dynamic var isDisplayed = false {
    willSet {
        if isDisplayed != newValue { willChangeValue(for: \.isDisplayed) }
    }
    didSet {
        if isDisplayed != oldValue { didChangeValue(for: \.isDisplayed) }
    }
}
您可以尝试以下方法(这是一种解决方法):

在它下面,创建如下函数:

func toggleAction(state: String, index: Int) -> String {
    print("The switch no. \(index) is \(state)")
    return ""
}
struct PWSDetailView : View {
    @State var isDisplayed: Bool = false
    @ObservedObject var station: PWS
    ...

    var body: some View {
        ...

        UIToggle(isOn: $isDisplayed) { isOn in
            //Do something here with the bool if you want
            //or use "_ in" instead, e.g.
            if isOn != station.isDisplayed {
                PWSStore.shared.toggleIsDisplayed(station)
            }
        }
        ...
    }   
}
我想没关系

struct ToggleModel {
    var isWifiOpen: Bool = true {
        willSet {
            print("wifi status will change")
        }
    }
}

struct ToggleDemo: View {
    @State var model = ToggleModel()

    var body: some View {
        Toggle(isOn: $model.isWifiOpen) {
            HStack {
                Image(systemName: "wifi")
                Text("wifi")
            }
       }.accentColor(.pink)
       .padding()
   }
}
类PWSStore:observeObject{ ... var站:PWS @已发布变量isDisplayed=true{ 意志{ PWSStore.shared.toggleIsDisplayed(self.station) } } } 结构PWSDetailView:视图{ @ObservedObject var station=PWSStore.shared ... var body:一些观点{ ... 切换(isOn:$isDisplayed){Text(“Wetterstation Anzegin”)} ... } }

在这里演示

我找到了一个更简单的解决方案,只需使用ontapsignature:D

Toggle(isOn: $stateChange) {
  Text("...")
}
.onTapGesture {
  // Any actions here.
}

这是我的方法。我也面临同样的问题,但我决定将UIKit的UISwitch封装到一个符合UIViewRepresentable的新类中

导入快捷界面
最终类UIToggle:UIViewRepresentable{
@绑定变量:Bool
var changedAction:(Bool)->Void
init(isOn:Binding,changedAction:@escaping(Bool)->Void){
自已
self.changedAction=changedAction
}
func makeUIView(上下文:context)->UISwitch{
设uiSwitch=uiSwitch()
返回开关
}
func updateUIView(uiView:UISwitch,context:context){
uiView.isOn=isOn
uiView.addTarget(self,action:#选择器(switchHasChanged(:)),for:.valueChanged)
}
@objc func开关已更改(\发送方:UISwitch){
self.isOn=sender.isOn
changedAction(sender.isOn)
}
}
然后它的用法是这样的:

func toggleAction(state: String, index: Int) -> String {
    print("The switch no. \(index) is \(state)")
    return ""
}
struct PWSDetailView : View {
    @State var isDisplayed: Bool = false
    @ObservedObject var station: PWS
    ...

    var body: some View {
        ...

        UIToggle(isOn: $isDisplayed) { isOn in
            //Do something here with the bool if you want
            //or use "_ in" instead, e.g.
            if isOn != station.isDisplayed {
                PWSStore.shared.toggleIsDisplayed(station)
            }
        }
        ...
    }   
}

基于@Legolas Wang的回答

当您从切换隐藏原始标签时,您只能将Tap手势附加到切换本身

HStack {
    Text("...")
    Spacer()
    Toggle("", isOn: $stateChange)
        .labelsHidden()
        .onTapGesture {
            // Any actions here.
        }
     }

在我看来,最干净的方法是使用自定义绑定。 这样,您就可以完全控制切换开关何时实际切换

导入快捷界面
结构切换演示:视图{
@国家私有变量isToggled=false
var body:一些观点{
让绑定=绑定(
获取:{self.isToggled},
设置:{
潜在异步函数($0)
}
)
func potentialAsyncFunction(\uNewState:Bool){
//异步的东西
self.isToggled=newState
}
返回切换(“我的状态”,isOn:绑定)
}
}

这是一个不使用Tappostate的版本

@State private var isDisplayed = false
Toggle("", isOn: $isDisplayed)
   .onReceive([self.isDisplayed].publisher.first()) { (value) in
        print("New value is: \(value)")           
   }

以防你不想使用额外的函数,把结构搞得一团糟——使用状态,并在你想要的任何地方使用它。我知道这不是事件触发器的100%答案,但是,状态将以最简单的方式保存和使用

struct PWSDetailView : View {


@State private var isToggle1  = false
@State private var isToggle2  = false

var body: some View {

    ZStack{

        List {
            Button(action: {
                print("\(self.isToggle1)")
                print("\(self.isToggle2)")

            }){
                Text("Settings")
                    .padding(10)
            }

                HStack {

                   Toggle(isOn: $isToggle1){
                      Text("Music")
                   }
                 }

                HStack {

                   Toggle(isOn: $isToggle1){
                      Text("Music")
                   }
                 }
        }
    }
}
}

可用于XCode 12

import SwiftUI

struct ToggleView: View {
    
    @State var isActive: Bool = false
    
    var body: some View {
        Toggle(isOn: $isActive) { Text(isActive ? "Active" : "InActive") }
            .padding()
            .toggleStyle(SwitchToggleStyle(tint: .accentColor))
    }
}
我是这样编码的:

Toggle("Title", isOn: $isDisplayed)
.onReceive([self.isDisplayed].publisher.first()) { (value) in
    //Action code here
}
更新的代码(Xcode 12,iOS14):

iOS13+ 这里有一种更通用的方法,您可以应用于几乎所有内置的
视图
的任何
绑定
,如选择器、文本字段、切换

扩展绑定{
func didSet(执行:@escaping(Value)->Void)->绑定{
返回绑定(
获取:{self.wrappedValue},
设置:{
self.wrappedValue=$0
执行($0)
}
)
}
}
用法简单

@State-var-isOn:Bool=false
切换(“标题”,isOn:$isOn.didSet{(状态)在
打印(状态)
})
损失14+
@State private var isOn=false
var body:一些观点{
切换(“标题”,isOn:$isOn)
.onChange(of:isOn){u isOn in
///在这里使用。。
}
}
SwiftUI 2 如果您使用的是SwiftUI 2/iOS 14,则可以使用extension Binding { func didSet(execute: @escaping (Value) -> Void) -> Binding { Binding( get: { self.wrappedValue }, set: { self.wrappedValue = $0 execute($0) } ) } }
extension Toggle where Label == Text {

    /// Creates a toggle that generates its label from a localized string key.
    ///
    /// This initializer creates a ``Text`` view on your behalf, and treats the
    /// localized key similar to ``Text/init(_:tableName:bundle:comment:)``. See
    /// `Text` for more information about localizing strings.
    ///
    /// To initialize a toggle with a string variable, use
    /// ``Toggle/init(_:isOn:)-2qurm`` instead.
    ///
    /// - Parameters:
    ///   - titleKey: The key for the toggle's localized title, that describes
    ///     the purpose of the toggle.
    ///   - isOn: A binding to a property that indicates whether the toggle is
    ///    on or off.
    ///   - onToggled: A closure that is called whenver the toggle is switched.
    ///    Will not be called on init.
    public init(_ titleKey: LocalizedStringKey, isOn: Binding<Bool>, onToggled: @escaping (Bool) -> Void) {
        self.init(titleKey, isOn: isOn.didSet(execute: { value in onToggled(value) }))
    }

    /// Creates a toggle that generates its label from a string.
    ///
    /// This initializer creates a ``Text`` view on your behalf, and treats the
    /// title similar to ``Text/init(_:)-9d1g4``. See `Text` for more
    /// information about localizing strings.
    ///
    /// To initialize a toggle with a localized string key, use
    /// ``Toggle/init(_:isOn:)-8qx3l`` instead.
    ///
    /// - Parameters:
    ///   - title: A string that describes the purpose of the toggle.
    ///   - isOn: A binding to a property that indicates whether the toggle is
    ///    on or off.
    ///   - onToggled: A closure that is called whenver the toggle is switched.
    ///    Will not be called on init.
    public init<S>(_ title: S, isOn: Binding<Bool>, onToggled: @escaping (Bool) -> Void) where S: StringProtocol {
        self.init(title, isOn: isOn.didSet(execute: { value in onToggled(value) }))
    }
}
@State var isDisplayed: Bool

Toggle("some text", isOn: .init(
    get: { isDisplayed },
    set: {
        isDisplayed = $0
        print("changed")
    }
))