11

I am trying to insert a button inside of another button using SwiftUI. However, if I press that button, it also animates the outer button being pressed, even though it doesn't run the action closure. Is there a way to prevent this, perhaps using a custom ButtonStyle?

This is what it looks like:

Button

This is what it looks like when the inner button is pressed:

Pressed Button

And here is my code:

var body: some View {
    Button(action: {
        print("outer button pressed")
    }) {
        HStack {
            Text("Button")
            Spacer()
            Button(action: {
                print("inner button pressed")
            }) {
                Circle()
                    .foregroundColor(Color.accentColor.opacity(0.2))
                    .frame(width: 28.0, height: 28.0)
                    .overlay(Image(systemName: "ellipsis"))
            }
        }
        .padding()
        .accentColor(.white)
        .background(Color.accentColor)
        .clipShape(RoundedRectangle(cornerRadius: 14.0, style: .continuous))
    }
    .frame(maxWidth: 200.0)
    .padding()
}
  • 1
    Rethink using tap gesture for circle instead of inner button – Asperi Oct 11 '20 at 06:42
  • 1
    @Asperi Thnks for the suggestion. I considered using onTapGesture, but it would feel weird to use a button that doesn't respond to being tapped. I also tried to use onLongPressGesture, but it didn't work very well either. – 1-877-547-7272 Oct 11 '20 at 07:08

2 Answers2

5

How about using 2 different buttons, within a ZStack?

var body: some View {
    
    ZStack(alignment: .trailing) {
        
        Button(action: {
            print("outer button pressed")
        }) {
            HStack {
                Text("Button")
                Spacer()
            }
            .padding()
            .accentColor(.white)
            .background(Color.accentColor)
            .clipShape(RoundedRectangle(cornerRadius: 14.0, style: .continuous))
        }
        .frame(maxWidth: 200.0)
        .padding()

        Button(action: {
            print("inner button pressed")
        }) {
            Circle()
                .foregroundColor(Color.accentColor.opacity(0.2))
                .frame(width: 28.0, height: 28.0)
                .overlay(Image(systemName: "ellipsis"))
            .padding()
            .accentColor(.white)
            .clipShape(RoundedRectangle(cornerRadius: 14.0, style: .continuous))
        }
        .padding()

    }
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
nicksarno
  • 3,850
  • 1
  • 13
  • 33
  • 1
    In case somebody needed to repeat this on apple watch, you should add `.buttonStyle(.plain)` to each button in ZStack. – Suprafen Mar 29 '23 at 14:55
0

There might be more elegant solutions but the following code should give you the desired behaviour:

struct ContentView: View {
    @State private var innerButtonDidTap = false
    @State private var outerButtonDidTap = false
   
    var body: some View {
        HStack {
            Text("Button")

            Spacer()

            Circle()
                .onTapGesture {
                    print("inner button tapped")
                    innerButtonDidTap.toggle()
                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                        innerButtonDidTap.toggle()
                    }
                }
                .foregroundColor(Color.accentColor.opacity(innerButtonDidTap ? 1.0 : 0.2))
                .frame(width: 28.0, height: 28.0)
                .overlay(Image(systemName: "ellipsis"))
            
        }
        .contentShape(Rectangle())
        .onTapGesture {
            print("outer button tapped")
            outerButtonDidTap.toggle()
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                outerButtonDidTap.toggle()
            }
        }
        .frame(maxWidth: 200)
        .padding()
        .accentColor(.white)
        .foregroundColor(.white)
        .background(Color.accentColor.opacity(outerButtonDidTap ? 0.2 : 1))
        .animation(Animation.linear(duration: 0.1))
        .clipShape(RoundedRectangle(cornerRadius: 14.0, style: .continuous))
    }
}

So instead of a two buttons I'm using a HStack and a Circle in combination with two onTapGesture modifiers.

finebel
  • 2,227
  • 1
  • 9
  • 20