0

I have a set of buttons to show for the user and I used CollectionView to align the buttons. Each button is a Vstack with an Image and Text components. The tap is reactive only on the image but not on Text and the padding space around.

I am looking to solve this to make it reactive all over the button. I found suggestions

  • to set ContentShape to rectangle and it didn't work
  • use Hstack to insert spaces on Left and right of the Text but that didn't work either.

Sample code: ToolBarItem:

var body: some View {
    VStack {
        
        Button(action: {
            
            // Delegate event to caller/parent view
            self.onClickAction(self.toolBarItem)
            
            }) {
            
            VStack {
                HStack {
                    Spacer()
                    Image(self.toolBarItem.selectedBackgroundImage)
                    .renderingMode(.original)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .padding(EdgeInsets(top: 5, leading: 3, bottom: 0, trailing: 3))
                    .frame(width: CGFloat(self.toolBarItem.cellWidth * 0.60),
                           height: CGFloat(self.toolBarItem.cellHeight * 0.60))
                    Spacer()
                }
               .contentShape(Rectangle())
               
          
                HStack {
                  Spacer()
                  Text(self.toolBarMenuInfo.specialSelectedName)
                    .foregroundColor(Color.red)
                    .padding(EdgeInsets(top: 0, leading: 0, bottom: 5, trailing: 0))
                  Spacer()
                }
                 .contentShape(Rectangle())
            }
            .frame(width: CGFloat(self.toolBarItem.cellWidth),
                               height: CGFloat(self.toolBarItem.cellHeight))
            .background(Color.blue.opacity(0.5))
            }
    }
}

The above ToolBarItem is placed inside the Collection view (custom Object created by me) for as many items required. Refer attachment and the tap occurs only on the image surrounded by green marking.

enter image description here

has anyone had similar issue? Any inputs is appreciated.

Dinakar
  • 1,198
  • 15
  • 37
  • Have you tried just using `.onTapGesture` on the `VStack`, instead of putting the `VStack' in a `Button`? – Yrb Jan 06 '22 at 12:34
  • I tried and it didn't work either. – Dinakar Jan 08 '22 at 06:51
  • Without a [Minimal Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) it is impossible to help you troubleshoot. – lorem ipsum Jan 12 '22 at 00:18
  • The green border is not included in your code. There is a huge chunk missing. The disconnect is likely there. – lorem ipsum Jan 12 '22 at 01:29
  • The green border is not part of my code, It is a marking where the tap actually occurs on a button. The Tap responds only in the rectangle and not around and on the text. the behavior is same for all the buttons. Sorry that my question is not clear, I only meant to show the tap area.. – Dinakar Jan 13 '22 at 01:06

2 Answers2

0

I strongly suspect that you issue has to do with the border. but I don't know exactly because you haven't provided that code.

Here is a version of the button view that would give you the effect you see to want.

struct FloatingToolbarButtonView: View {
    @Binding var model: ButtonModel
    let size: CGSize
    var body: some View {
        Button(action: {
            //Set the model's variable to selected
            model.isSelected.toggle()
            //Perform the action
            model.onClick()
        }, label: {
            VStack {
                //REMOVE systemName: in your code
                Image(systemName: model.imageName)
                //.renderingMode(.original)
                    .resizable()
                //Maintains proportions
                    .scaledToFit()
                //Set Image color
                    .foregroundColor(.white)
                //Works with most images to change color
                    .colorMultiply(model.colorSettings.imageNormal)
                    .padding(5)
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
                //Set border color/width
                    .border(Color.green, width: model.isSelected ? 3:0)
                Spacer()
                
                Text(model.label)
                //Set Text color
                    .foregroundColor(model.colorSettings.labelNormal)
            }
            
            .padding(EdgeInsets(top: 5, leading: 3, bottom: 0, trailing: 3))
            
            
        }).frame(width: size.width, height: size.height, alignment: .center)
            .background(model.colorSettings.backgroundNormal)
    }
}

enter image description here

And this is what the model I used looks like

//Holds Button information
struct ButtonModel: Identifiable{
    let id: UUID = UUID()
    var label: String
    var imageName: String
    ///Action to be called when the button is pressed
    var onClick: () -> Void
    ///identify if the user has selected this button
    var isSelected: Bool = false
    var colorSettings: ButtonColorSettings
}

I created the buttons in a view model so I can have an easy to to set the action and access isSelected as needed.

//ViewModel that deals with all the button creation and onClick actions
class FloatingToolbarParentViewModel: ObservableObject{
    //Settings the buttons at this level lets you read `isPressed`
    @Published var horizontalButtons: [ButtonModel] = []
    @Published var moreButton: [ButtonModel] = []
    @Published var verticalButtons: [ButtonModel] = []
    
    init(){
         horizontalButtons = horizontalSamples
         moreButton = [mer]
         verticalButtons = veticalSamples
    }
}
//MARK: Buttons
extension FloatingToolbarParentViewModel{
    //MARK: SAMPLES fill in with your data
    var identify:ButtonModel {ButtonModel(label: "Identify", imageName: "arrow.up.backward", onClick: {print(#function + " Identfy")}, colorSettings: .white)}
    var tiltak:ButtonModel  {ButtonModel(label: "Tiltak", imageName: "scissors", onClick: {print(#function + " Tiltak")}, colorSettings: .white)}
    var tegn:ButtonModel { ButtonModel(label: "Tegn", imageName: "pencil", onClick: {print(#function + " Tegn")}, colorSettings: .white)}
    var bestand:ButtonModel  {ButtonModel(label: "Bestand", imageName: "leaf", onClick: {print(#function + " Identfy")}, colorSettings: .red)}
    var mer:ButtonModel  {ButtonModel(label: "Mer", imageName: "ellipsis.circle", onClick: {print(#function + " Mer")}, colorSettings: .red)}
    var kart:ButtonModel  {ButtonModel(label: "Kart", imageName: "map.fill", onClick: {print(#function + " Kart")}, colorSettings: .white)}
    var posisjon:ButtonModel  {ButtonModel(label: "Posisjon", imageName: "magnifyingglass", onClick: {print(#function + " Posisjon")}, colorSettings: .white)}
    var spor:ButtonModel  {ButtonModel(label: "Spor", imageName: "circle.fill", onClick: {print(#function + " Spor")}, colorSettings: .red)}
    
    var horizontalSamples :[ButtonModel]  {[identify,tiltak,tegn,bestand]}
    var veticalSamples :[ButtonModel]  {[kart,posisjon,spor]}
}

The rest of the code to get the sample going is below. It isn't really needed but it will give you a working product

struct FloatingToolbarParentView: View {
    @State var region: MKCoordinateRegion = .init()
    @StateObject var vm: FloatingToolbarParentViewModel = .init()
    
    var body: some View {
        ZStack{
            Map(coordinateRegion: $region)
            ToolbarOverlayView( horizontalButtons: $vm.horizontalButtons, cornerButton: $vm.moreButton, verticalButtons: $vm.verticalButtons)
        }
    }
}
struct ToolbarOverlayView: View{
    @State var buttonSize: CGSize = .zero
    @Binding var horizontalButtons: [ButtonModel]
    @Binding var cornerButton: [ButtonModel]
    @Binding var verticalButtons: [ButtonModel]
    var body: some View{
        GeometryReader{ geo in
            VStack{
                HStack{
                    Spacer()
                    VStack{
                        Spacer()
                        FloatingToolbarView(buttons: $verticalButtons, buttonSize: buttonSize, direction: .vertical)
                    }
                }
                Spacer()
                HStack{
                    Spacer()
                    FloatingToolbarView(buttons: $horizontalButtons, buttonSize: buttonSize)
                    
                    FloatingToolbarView(buttons: $cornerButton, buttonSize: buttonSize)
                }
                //Adjust the button size on appear and when the orientation changes
                    .onAppear(perform: {
                        setButtonSize(size: geo.size)
                    })
                    .onChange(of: geo.size.width, perform: { new in
                        setButtonSize(size: geo.size)
                    })
            }
        }
    }
    //Sets the button size using and minimum and maximum values accordingly
    //landscape and portrait have oppositive min and max
    func setButtonSize(size: CGSize){
        buttonSize = CGSize(width: min(size.width, size.height) * 0.15, height: max(size.width, size.height)  * 0.1)
    }
}


//Toolbar group for an array of butons
struct FloatingToolbarView: View {
    @Binding var buttons :[ButtonModel]
    let buttonSize: CGSize
    var direction: Direction = .horizontal
    var body: some View {
        Group{
            switch direction {
            case .horizontal:
                HStack(spacing: 0){
                    ForEach($buttons){$button in
                        FloatingToolbarButtonView(model: $button, size: buttonSize)
                    }
                }
            case .vertical:
                VStack(spacing: 0){
                    ForEach($buttons){$button in
                        FloatingToolbarButtonView(model: $button, size: buttonSize)
                    }
                }
            }
        }
    }
    enum Direction{
        case horizontal
        case vertical
    }
}


@available(iOS 15.0, *)
struct FloatingToolbarParentView_Previews: PreviewProvider {
    static var previews: some View {
        FloatingToolbarParentView()
        FloatingToolbarParentView().previewInterfaceOrientation(.landscapeLeft)
    }
}
//Holds Button Color information
//You havent provided much info on this so I assume that you are setting the colors somewhere
struct ButtonColorSettings{
    var labelNormal: Color
    var imageNormal: Color
    var backgroundNormal: Color
    //Sample Color configuration per image
    static var white = ButtonColorSettings(labelNormal: .white, imageNormal: .white, backgroundNormal: .black.opacity(0.5))
    static var red = ButtonColorSettings(labelNormal: .black, imageNormal: .red, backgroundNormal: .white)
}
lorem ipsum
  • 21,175
  • 5
  • 24
  • 48
0

Have you tried putting .contentShape(Rectangle()) on the whole VStack inside the Button or on the button itself? That should probably solve it.

Rudrank Riyam
  • 588
  • 5
  • 13