SwiftUI:如何使用动态生成的按钮将参数从一个视图传递到下一个视图?

SwiftUI: How to pass an argument from one view to the next with dynamically generated buttons?

问题:

  1. 当从 Landing.swift 传递输入参数时,我无法强制打开我的 alpha、beta 或 gamma 按钮。
  2. 我不明白为什么当 onAppear 在堆栈中触发时,输出变为:
gamma is the title
beta is the title
alpha is the title
gamma is the title
beta is the title
alpha is the title

困惑 -> 当 ForEach 循环内部只有 3 个元素时,为什么输出 2x?

背景: 我正在尝试将参数从一个视图 (Landing.swift) 传递到另一个视图 (ContentView.swift),然后根据该参数强制正确的按钮(在 ContentView 中)触发 ON 状态,以便它被选中。我在 ButtonOnOff.swift 中显示了下面的逻辑,可以跟踪选择的内容和未选择的内容。

例如,ContentView 中有 3 个按钮(alpha、beta 和 gamma),并且根据从 Landing 中选择的输入按钮选择,相应的 alpha、beta 或 gamma 按钮(在 ContentView 中)应该打开。

我正在 ContentView 中动态生成这 3 个按钮,并希望将来可以灵活地扩展到 10 个或更多。因此,为什么我在 ContentView 中使用 ForEach。我需要一些帮助,请理解我是否错误地使用了 EnvironmentObject/ObservedObject 或其他东西。

维护 ON/OFF 逻辑与代码一起正常工作。也就是说,如果您手动按下 alpha,它会打开,但其他两个会关闭,依此类推。

提前感谢您的帮助! :)

Testing.swift

import SwiftUI

@main

struct Testing: App {

    @StateObject var buttonsEnvironmentObject = ButtonOnOff()

    var body: some Scene {
        WindowGroup {
            Landing().environmentObject(buttonsEnvironmentObject)
        }
    }
}

Landing.swift

import SwiftUI

struct Landing: View {
    
    @State private var tag:String? = nil
    
    var body: some View {
        NavigationView {
            ZStack{
                HStack{
                    NavigationLink(destination: ContentView(landingChoice:tag ?? ""), tag: tag ?? "", selection: $tag) {
                        EmptyView()
                    }
                    Button(action: {
                        self.tag = "alpha"
                    }) {
                        HStack {
                            Text("alpha")
                        }
                    }
                    Button(action: {
                        self.tag = "beta"
                    }) {
                        HStack {
                            Text("beta")
                        }
                    }
                    Button(action: {
                        self.tag = "gamma"
                    }) {
                        HStack {
                            Text("gamma")
                        }
                    }
                }
                .navigationBarHidden(true)
            }
            .navigationViewStyle(StackNavigationViewStyle())
        }
    }
}

ContentView.swift

import SwiftUI

struct ContentView: View {

    var btnName:String
    @EnvironmentObject var buttonEnvObj:ButtonOnOff
    
    init(landingChoice:String){
        self.btnName = landingChoice
        print("\(self.btnName) is the input string")
    }

    var body: some View {
        VStack{
            Form{
                Section{
                    ScrollView(.horizontal, showsIndicators: false) {
                        HStack(spacing:10) {
                            ForEach(0..<buttonEnvObj.buttonNames.count) { index in
                                BubbleButton(label: "\(buttonEnvObj.buttonNames[index])")
                                .padding(EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 0))
                                 .onAppear {
                                     print("\(buttonEnvObj.buttonNames[index]) is the title")
                                }
                            }
                        }
                    }.frame(height: 50)
                }
            }
        }
    }
}

struct BubbleButton: View{

    @EnvironmentObject var buttonBrandButtons:ButtonOnOff
    var label: String
    
    var body: some View{
        HStack{
            Button(action: {
                print("Button action")
                buttonBrandButtons.changeState(buttonName: self.label)
                
            }) {
                ZStack {
                    VStack{
                        HStack {
                            Spacer()
                            Text(label)
                                .font(.system(size: 12,weight:.regular, design: .default))
                                .foregroundColor(buttonBrandButtons.buttonBrand[self.label]! ? Color.white : Color.gray)
                            Spacer()
                        }
                    }
                    .frame(height:30)
                    .fixedSize()
                }
            }
            .background(buttonBrandButtons.buttonBrand[self.label]! ? Color.blue : .clear)
            .cornerRadius(15)
            .overlay(buttonBrandButtons.buttonBrand[self.label]! ?
                     RoundedRectangle(cornerRadius: 15).stroke(Color.blue,lineWidth:1) : RoundedRectangle(cornerRadius: 15).stroke(Color.gray,lineWidth:1))
            .animation(.linear, value: 0.15)
        }
    }
}

ButtonOnOff.swift

import Foundation

class ButtonOnOff:ObservableObject{
    
    var buttonNames = ["alpha","beta","gamma"]
    
    @Published var buttonBrand:[String:Bool] = [
        "alpha":false,
        "beta":false,
        "gamma":false
    ]
    
    func changeState(buttonName:String) -> Void {
        for (key,_) in buttonBrand{
            if key == buttonName && buttonBrand[buttonName] == true{
                buttonBrand[buttonName] = false
            } else{
                buttonBrand[key] = (key == buttonName) ? true : false
            }
        }
        print(buttonBrand)
    }
}

如需简短回答,只需添加

    .onAppear(){
        buttonEnvObj.changeState(buttonName: self.btnName)
    }

ContentView 将突出显示所选的按钮。

至于一个可以随意扩展的方案。我会建议一切都采用单一事实来源并进行一些简化。

struct Landing: View {
    @EnvironmentObject var buttonEnvObj:ButtonOnOff
    
    @State private var tag:String? = nil
    
    var body: some View {
        NavigationView {
            ZStack{
                HStack{
                    NavigationLink(destination: ContentView(), tag: tag ?? "", selection: $tag) {
                        EmptyView()
                    }
                    //Put your buttons here
                    HStack{
                        //Use the keys of the dictionary to create the buttons
                        ForEach(buttonEnvObj.buttonBrand.keys.sorted(by: <),  id: \.self){ key in
                            //Have the button set the value when pressed
                            Button(action: {
                                self.tag = key
                                buttonEnvObj.changeState(buttonName: key)
                            }) {
                                Text(key)
                            }
                        }
                    }
                }
                .navigationBarHidden(true)
            }
            .navigationViewStyle(StackNavigationViewStyle())
        }
    }
}

struct ContentView: View {
    
    @EnvironmentObject var buttonEnvObj:ButtonOnOff
    
    var body: some View {
        VStack{
            Form{
                Section{
                    ScrollView(.horizontal, showsIndicators: false) {
                        HStack(spacing:10) {
                            //Change this to use the dictionary
                            ForEach(buttonEnvObj.buttonBrand.sorted(by: {[=11=].key < .key }), id:\.key) { key, value in
                                BubbleButton(key: key, value: value)
                                    .padding(EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 0))
                                    .onAppear {
                                        print("\(value) is the title")
                                    }
                            }
                        }
                    }.frame(height: 50)
                }
            }
        }
    }
}

struct BubbleButton: View{
    
    @EnvironmentObject var buttonBrandButtons:ButtonOnOff
    var key: String
    var value: Bool
    
    var body: some View{
        HStack{
            Button(action: {
                print("Button action")
                buttonBrandButtons.changeState(buttonName: key)
                
            }) {
                ZStack {
                    VStack{
                        HStack {
                            Spacer()
                            Text(key)
                                .font(.system(size: 12,weight:.regular, design: .default))
                                .foregroundColor(value ? Color.white : Color.gray)
                            Spacer()
                        }
                    }
                    .frame(height:30)
                    .fixedSize()
                }
            }
            .background(value ? Color.blue : .clear)
            .cornerRadius(15)
            .overlay(value ?
                     RoundedRectangle(cornerRadius: 15).stroke(Color.blue,lineWidth:1) : RoundedRectangle(cornerRadius: 15).stroke(Color.gray,lineWidth:1))
            .animation(.linear, value: 0.15)
        }
    }
}

class ButtonOnOff:ObservableObject{
    //Get rid of this so you can keep the single source
    //var buttonNames = ["alpha","beta","gamma"]
    
    //When you want to add buttons just add them here it will all adjust
    @Published var buttonBrand:[String:Bool] = [
        "alpha":false,
        "beta":false,
        "gamma":false
    ]
    
    func changeState(buttonName:String) -> Void {
        for (key,_) in buttonBrand{
            if key == buttonName && buttonBrand[buttonName] == true{
                buttonBrand[buttonName] = false
            } else{
                buttonBrand[key] = (key == buttonName) ? true : false
            }
        }
        print(buttonBrand)
    }
}