0

In my data object, I have a dictionary which specifies how many buttons I need to display on the view. I am looping over the dictionary to display the buttons with custom style and when a button is clicked, I intend to display a sheet with content as custom message alert view.

What I am noticing is when I have more than 1 buttons, sheet opens up with custom message alert view only for first button. For other buttons, I do see sheet opening up, but the code in custom view never gets executed.

Code below:

struct CustomActionsView: View {

    let dataObject: CustomModel

    @State var showingModal: Bool = false

    var body: some View {
        VStack {
            var actionName = ""
            // actions is dictionary where key is name that will be displayed on the button and value is name of the image I will be displaying on the button.
            ForEach(dataObject.actions.sorted(by: >), id: \.key) { key, value in
                let imageName = (key == "Cancel" ? "xmark.circle" : value)

                // Button with custom styling.
                CapsuleActionButtonWithImage(actionNameText: key, imageName: imageName, action: { self.showingModal.toggle(); actionName = key; NSLog("Clicked action is - \(actionName)") })
                .sheet (
                    isPresented: $showingModal,
                    content: {
                        // Only execute the logic for clicked button. 
                        if key == actionName {
                            // This function always gets called and returns correct data for the button clicked.
                            let messageViewDetails = Helper.getMessageDetails(dataObject: dataObject, clickedActionName: actionName)

                            // This gets called only for one button.
                            GeneralAlertMessageView(imageName: messageViewDetails.messageViewimageName, mainMessageText: messageViewDetails.messageText, messageViewButtonStyle: .okButton, showingModal: $showingModal)
                        }
                    }
                )

                Spacer()
            }
        }
    }
}

Code for GeneralAlertMessageView which should show up whenever any buttons are clicked:

struct GeneralAlertMessageView: View {
    var imageName: String?
    var mainMessageText: String?
    var messageViewButtonStyle: MessageViewButtonStyle = .noButton
    @Binding var showingModal:Bool
    
    var body: some View {
        VStack {

            let _ = NSLog("Within GeneralAlertMessageView")
            // Some UI code
        }
    }
}

Let's say I have 3 key-value pairs within "dataObject.actions" dictionary and hence I will see 3 buttons on the view. When I click on first button, actionName gets set to the name of the button I clicked(within action closure for CapsuleActionButtonWithImage) and getMessageDetails gets executed and "Within GeneralAlertMessageView" gets printed on the console since GeneralAlertMessageView gets displayed on the sheet.

Whenever I click on 2nd and 3rd buttons, actionName gets set to the name of the button I clicked and getMessageDetails does get executed for the button I clicked, but I never see "Within GeneralAlertMessageView" getting printed and all I see is blank sheet. Any breakpoints within GeneralAlertMessageView doesn't hit either.

Am I doing anything wrong? Is there a better way to open sheet for every button click within for loop?

Thanks!

tech_human
  • 6,592
  • 16
  • 65
  • 107
  • Put the `.sheet` *outside* the `ForEach` and use the `sheet(item:)` form instead of `sheet(isPresented:)` – jnpdx Jul 14 '22 at 23:47
  • Should value of item be my dataObject? – tech_human Jul 14 '22 at 23:48
  • It looks like it should probably be `key`. I'm a little confused by your code, though. You're comparing `key` to `actionName`, but `actionName` is set *inside* the render as `""`? – jnpdx Jul 14 '22 at 23:51
  • The `item` should be whatever is unique for that iteration of the `ForEach` -- the thing that you want to *change* in the `sheet`. – jnpdx Jul 14 '22 at 23:51
  • Got it. Thanks! It will most probably be the actionName since I change the value of it at every button click. I moved actionName outside on the top along with data object and made it as a state variable. I am trying it now. Using actionName because I won't be able to use 'key' directly outside for loop. I set actionName to key when button is clicked. – tech_human Jul 14 '22 at 23:55
  • That won't work -- you can't set a local variable like that inside the render and then use it. That's exactly why you want to use `item:` -- you'd set that to your key *inside* your loop where you're toggling `showModal` right now. – jnpdx Jul 15 '22 at 00:04
  • Even though the question is now closed, I thought of posting what worked for me. Issue was resolved by moving .sheet outside of forEach. I tried sheet(item:), but that was causing other issues while dismissing the sheet, so I tried sheet(isPresented) i.e. same code that I had and it worked. Thanks @jnpdx! – tech_human Jul 15 '22 at 20:44

1 Answers1

1

I don't have your full code, but I have a similar circumstance for your problem.

This will allow you to have a different sheet and a different sheet data for each Button. You can try the code below the image: enter image description here

import SwiftUI

struct ConView: View {

@State var dictionaries : [String:String] = [
    "dog" : "circle.fill",
    "cat" : "square.fill",
    "tree" : "rectangle.fill"
]
@State var temp = ""
@State var isOpened = false

var body: some View {
    VStack {
        ForEach(dictionaries.sorted(by: >), id: \.key) { key, value in
            Button {
                temp = key
                isOpened.toggle()
            } label: {
                HStack {
                    Text(key)
                    Image(systemName: value)
                }
            }
            .padding()
            .background(.black)
            .cornerRadius(10)
        }
        if isOpened {
            ZStack {
            }
            .sheet(isPresented: $isOpened) {
                Text("This is the sheet inside Button: \(temp)")
                let _ = NSLog("Within SheetView")
            }
        }
    }
  }
}
Steven-Carrot
  • 2,368
  • 2
  • 12
  • 37
  • Is isOpened check and ZStack required around .sheet? I was able to make it work without it i.e. ForEach { ... }.sheet(isPresented: $isOpened, content: {...}) – tech_human Jul 15 '22 at 20:47