mica
mica

Reputation: 4308

swiftUI Button with width:0 nonetheless active

I set the width of a SwiftUI Button to 0 to "deactivate" it.
If the with of the button is set to 0, the button disappears as expected, but clicking in the left edge of the yellow Stack activates the Button.
Why does this happen?
How can I avoid it?

struct ContentView: View {
  @State var zeroWidth = false

  var body: some View {
    VStack{
      ButtonLine( leftButtons: [ButtonAttr( label: "LB1",
                                            action: {print("LB1")},
                                            iconSystemName : "person"
                                          )],
                                 zeroWidth: zeroWidth
                  )
      Button("Toggle width \(zeroWidth ? "On" : "Off" ) "){ self.zeroWidth.toggle() }
    }
  }
}

struct ButtonLine: View {
  let leftButtons : [ButtonAttr]
  let zeroWidth : Bool

  var body: some View {

    HStack(spacing: 0) {
      ForEach(leftButtons.indices, id: \.self)
      { i in
        HStack(spacing: 0.0)
        {
          Button(action: { self.leftButtons[i].action() }) {
            ButtonLabel( singleline: false,
                         buttonAttr: self.leftButtons[i]
            )
            .padding(0)
            //.background(Color.green)  // not visible
          }
         .buttonStyle(BorderlessButtonStyle())
            .frame( width: self.zeroWidth ? 0 : 100, height: 50) 
         .background(Color.green)
         .clipped()
         .foregroundColor(Color.white)
         .padding(0)
        }
        // .background(Color.blue)   // not visible
      }
      // .background(Color.blue)   // not visible
      Spacer()
      Text("CONTENT")
      .background(Color.green)
      .onTapGesture {
        print("Content tapped")
      }
      Spacer()
    }
    .background(Color.yellow)
      .onTapGesture {
        print("HS tapped")
    }
  }
}

struct ButtonLabel: View {
  var singleline : Bool
  var buttonAttr : ButtonAttr
  var body: some View {
    VStack (spacing: 0.0) {
      Image(systemName: buttonAttr.iconSystemName).frame(height: singleline ? 0 : 20).clipped()
      .padding(0)
      .background(Color.blue)
      Text(buttonAttr.label)
      .padding(0)
      .background(Color.blue)
    }
    .padding(0)
    .background(Color.red)
  }
}

struct ButtonAttr
{   let label : String
    let action: ()-> Void
    let iconSystemName : String
}

Upvotes: 1

Views: 711

Answers (3)

user3441734
user3441734

Reputation: 17582

there is very simple explanation.

try next snippet

struct ContentView: View {
    var body: some View {
        Text("Hello").padding().border(Color.yellow).fixedSize().frame(width: 0)
    }
}

enter image description here

Why?

.frame(..) 

is defined as a function of View, which return another View, as any kind of View modifier. The resulting View has .zero sized frame, as expected.

It is really true? Let's check it!

struct ContentView: View {
    var body: some View {
        HStack(spacing: 0) {
            Rectangle()
                .fill(Color.orange)
                .frame(width: 100, height: 100)
            Text("Hello")
                .padding()
                .border(Color.black)
                .fixedSize()
                .frame(width: 0, height: 0)
            Rectangle()
                .fill(Color.green)
                .frame(width: 100, height: 100)
                .blendMode(.exclusion)
        }
    }
}

enter image description here

Just add .clipped modifier to your Text View

struct ContentView: View {
    var body: some View {
        HStack(spacing: 0) {
            Rectangle()
                .fill(Color.orange)
                .frame(width: 100, height: 100)
            Text("Hello")
                .padding()
                .border(Color.black)
                .fixedSize()
                .frame(width: 0, height: 0)
                .clipped()
            Rectangle()
                .fill(Color.green)
                .frame(width: 100, height: 100)
                .blendMode(.exclusion)
        }
    }
}

and the Text "disappearsenter image description here" ...

It disappears from the screen, but not from View hierarchy!. Change the code again

struct ContentView: View {
    var body: some View {
        HStack(spacing: 0) {
            Rectangle()
                .fill(Color.orange)
                .frame(width: 100, height: 100)
            Text("Hello")
                .padding()
                .border(Color.black)
                .fixedSize().onTapGesture {
                    print("tap")
                }
                .frame(width: 0, height: 0)
                .clipped()
            Rectangle()
                .fill(Color.green)
                .frame(width: 100, height: 100)
                .blendMode(.exclusion)
        }
    }
}

and you see, that there is still some "invisible" area sensitive on tap gesture

enter image description here

Upvotes: 2

KevinP
KevinP

Reputation: 2738

You can disable you Button by adding a .disabled(self.zeroWidth)

Button(action: { self.leftButtons[i].action() }) {
    ButtonLabel( singleline: false,
                buttonAttr: self.leftButtons[i]
    )
        .padding(0)
       //.background(Color.green)  // not visible
}
.disabled(self.zeroWidth)
.buttonStyle(BorderlessButtonStyle())
.frame( width: self.zeroWidth ? 0 : 100, height: 50)
.background(Color.green)
.clipped()
.foregroundColor(Color.white)
.padding(0)

enter image description here

You can debug the view hierarchy by clicking that icon in xcode: enter image description here

Upvotes: 1

Asperi
Asperi

Reputation: 258541

Instead of tricky "deactivate", just use real remove, like below

    HStack(spacing: 0.0)
    {
        if !self.zeroWidth {
            Button(action: { self.leftButtons[i].action() }) {
                ButtonLabel( singleline: false,
                             buttonAttr: self.leftButtons[i]
                )
                    .padding(0)
                //.background(Color.green)  // not visible
            }
            .buttonStyle(BorderlessButtonStyle())
            .frame(width: 100, height: 50)
            .background(Color.green)
            .clipped()
            .foregroundColor(Color.white)
            .padding(0)
        }
    }.frame(height: 50) // to keep height persistent

Upvotes: 2

Related Questions