使用SwiftUI,如何使一个Toggle改变另一个Toggle的状态?

8

我想要三个SwiftUI Toggles,如果其中一个打开,其他两个都应该关闭。

使用UIKit的旧方法是didSet{},但在SwiftUI中不是正确的方式。我不知道是否需要深入了解Combine,才能解决这个问题,表面上看起来应该很简单。

import SwiftUI

// Mutually exclusive toggle switches: when one toggle is on, the other two should be off. This is a start, where to go from here?

struct Junk: View {

    @State private var isOn1:Bool = true
    @State private var isOn2:Bool = false
    @State private var isOn3:Bool = false

    var body: some View
    {
        VStack
        {
            Toggle("T1", isOn: $isOn1)
            Toggle("T2", isOn: $isOn2)
            Toggle("T3", isOn: $isOn3)
        }
    }
}

struct Junk_Previews: PreviewProvider
{
    static var previews: some View
    {
        Junk()
    }
}

首先,为什么“UIKit”方式不是正确的方式?也许它是 - 使用ObservableObject代替三个@State变量。(请注意,我不是说这是正确的,只是说如果你有三个相互依赖的变量 - 就像你发布的代码一样 - 并且它们不是局部的View,那么为什么使用ObservableObject呢?老实说,我认为那才是“正确的SwiftUI方式” - 它将UI与模型分离,将逻辑正确地移动到应该在的位置,除非你发布更多的代码?我需要听听为什么它正确。 - user7014451
我说UIKit的方式是使用didSet,这不是你建议的方式。你建议使用Combine框架中的ObservableObject。谢谢。 - e987
除非使用willSet,否则不会发生这种情况。再次强调,我认为您的问题在于您的示例——它可能是简化的(这是一个好问题),但却掩盖了您的意图——您真的需要三个本地Bools吗?它们在使用ObservableObject的模型中是否更有效?一旦这样做,使用Swift代码如willSetdidSet有什么问题呢? - user7014451
我对SwiftUI和Combine都很陌生。当我最初提出这个问题时,我认为willSet/didSet将被类似于Toggle的尾随闭包所取代,这将允许我们在状态更改时执行操作。 - e987
我们都对SwiftUI&Combine很新奇。(就我个人而言,即使我在IT领域有35年经验,其中5年使用过UIKit,这仍然是一个业余爱好,感觉有点生疏!) 我的批评? WWDC四周后的Beta 3。PresentationButton现在变成了PresentationLink。 很棒。2周后(beta4)?所有内容都已过时。 对于您来说?BindingObject在beta 4中变成了ObservableObject - 并且随着此更改,协议要求didSet更改为willSet。 但是didSet仍然有效!底层结构 - Combine - 仍然存在(并幸运的是是开源的)。 - user7014451
那么,也许正确的方法(对我来说)是问这个问题 - 如果您的 UIKit 应用程序需要三个 Bool 值并且它们需要一起工作,您会如何设计它 - 在 UIKit 中?一个单例?还是某些仅限于一个 UIViewController 的本地东西?也许是框架目标?这真的很重要,因为在大多数情况下 - 除了一个本地情况之外 - SwiftUI 的答案实际上是使用 Combine。(两个月前,我在这里评论了很多次“范式转变”。这是几周以来我第一次这样做。) - user7014451
2个回答

23

由于我不知道你的设计面临的挑战,所以我不会讨论它是否是正确的方法(除非这是你的问题)。然而,为了实现你所要求的,你可以使用一个中间绑定:

struct Junk: View {

    @State private var isOn1:Bool = true
    @State private var isOn2:Bool = false
    @State private var isOn3:Bool = false

    var body: some View
    {
        let on1 = Binding<Bool>(get: { self.isOn1 }, set: { self.isOn1 = $0; self.isOn2 = false; self.isOn3 = false })
        let on2 = Binding<Bool>(get: { self.isOn2 }, set: { self.isOn1 = false; self.isOn2 = $0; self.isOn3 = false })
        let on3 = Binding<Bool>(get: { self.isOn3 }, set: { self.isOn1 = false; self.isOn2 = false; self.isOn3 = $0 })        

        return VStack
            {
                Toggle("T1", isOn: on1)
                Toggle("T2", isOn: on2)
                Toggle("T3", isOn: on3)
        }
    }
}

谢谢kontiki。看起来使用中间绑定是解决这个问题的最佳方法,虽然也可以通过ObservableObject来解决。但是,使用ObservableObject需要更多的代码,并且与此解决方案相比没有任何好处。 - e987
@kontiki 这真的解决了我在使用SwiftUI时遇到的很多问题。非常感谢! - Kirsteins
@kontiki 的解决方案非常棒,值得更多的赞。在很多情况下,ObservableObject 和 Published 的设置过于臃肿。对我很有用。 - FIGBERT
当我将绑定添加到body时,我会得到以下构建错误:“函数声明了一个不透明的返回类型,但在其主体中没有返回语句可以推断出底层类型”。 - biomiker
一个令人愉悦的黑客。 - d5automations

1

这个帖子很旧了,但我在尝试解决同样的问题时遇到了它。我使用.onChange修饰符自己成功解决了它。虽然我是开发新手,但我认为这相当优雅。我的情况只涉及两个切换,但对于三个也应该适用。

struct SettingsView: View {

    @State var showFullArray = true
    @State var showHalfArray = false

    var body: some View {

        VStack {
            Toggle("Show Full Array", isOn: $showFullArray)
                .onChange(of: showFullArray) { newValue in
                    showHalfArray = !newValue
                }
            Toggle("Show Half Array", isOn: $showHalfArray)
                .onChange(of: showHalfArray) { newValue in
                    showFullArray = !newValue
                }
        }
    }
}

这对我有用,但是我必须在showFullArray和showHalfArray的声明中添加“@State”才能使其工作。 - Phydeau
没错。感谢你发现了这个问题。 - atoss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接