Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - Button with Image is clickable outside

Tags:

ios

swift

swiftui

I have a ScrollView with multiple Buttons. A Button contains a Image and a Text underneath.

As the images are pretty large I am using .scaledToFill and .clipped. And it seems that the 'clipped' part of the image is still clickable even if it's not shown.

In the video you see I am clicking on button 1 but button 2 is triggered.

Gif showing the error

This is my Coding. The Image is inside the View Card.

struct ContentView: View {

    @State var useWebImage = false
    @State var isSheetShowing = false
    @State var selectedIndex = 0

    private let images = [
        "https://images.unsplash.com/photo-1478368499690-1316c519df07?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2706&q=80",
        "https://images.unsplash.com/photo-1507154258-c81e5cca5931?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2600&q=80",
        "https://images.unsplash.com/photo-1513310719763-d43889d6fc95?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2734&q=80",
        "https://images.unsplash.com/photo-1585766765962-28aa4c7d719c?ixlib=rb-1.2.1&auto=format&fit=crop&w=2734&q=80",
        "https://images.unsplash.com/photo-1485970671356-ff9156bd4a98?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2734&q=80",
        "https://images.unsplash.com/photo-1585607666104-4d5b201d6d8c?ixlib=rb-1.2.1&auto=format&fit=crop&w=2700&q=80",
        "https://images.unsplash.com/photo-1577702066866-6c8897d06443?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2177&q=80",
        "https://images.unsplash.com/photo-1513809491260-0e192158ae44?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2736&q=80",
        "https://images.unsplash.com/photo-1582092723055-ad941d1db0d4?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2700&q=80",
        "https://images.unsplash.com/photo-1478264635837-66efba4b74ba?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjF9&auto=format&fit=crop&w=2682&q=80"
    ]

    var body: some View {
        NavigationView {
            ScrollView {
                VStack(spacing: 40) {

                    Text(useWebImage ? "WebImage is used." : "SwiftUI Image is used")
                        .font(.system(size: 18))
                        .bold()
                        .kerning(0.5)
                        .padding(.top, 20)

                    Toggle(isOn: $useWebImage) {
                        Text("Use WebImage")
                            .font(.system(size: 18))
                            .bold()
                            .kerning(0.5)
                            .padding(.top, 20)
                    }

                    ForEach(0..<images.count) { index in
                        Button(action: {
                            self.selectedIndex = index
                            self.isSheetShowing.toggle()
                        }) {
                            Card(imageUrl: self.images[index], index: index, useWebImage: self.$useWebImage)
                        }
                        .buttonStyle(PlainButtonStyle())
                    }
                }
                .padding(.horizontal, 20)
                .sheet(isPresented: self.$isSheetShowing) {
                    DestinationView(imageUrl: self.images[self.selectedIndex], index: self.selectedIndex, useWebImage: self.$useWebImage)
                }
            }
            .navigationBarTitle("Images")
        }
    }
}
struct Card: View {

    let imageUrl: String
    let index: Int
    @Binding var useWebImage: Bool

    var body: some View {
        VStack {
            if useWebImage {
                WebImage(url: URL(string: imageUrl))
                    .resizable()
                    .indicator(.activity)
                    .animation(.easeInOut(duration: 0.5))
                    .transition(.fade)
                    .scaledToFill()
                    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 250, maxHeight: 250, alignment: .center)
                    .cornerRadius(12)
                    .clipped()
            } else {
                Image("image\(index)")
                    .resizable()
                    .aspectRatio(contentMode: .fill)
                    .frame(minWidth: 0, maxWidth: .infinity, minHeight: 250, maxHeight: 250, alignment: .center)
                    .cornerRadius(12)
                    .clipped()
            }

            HStack {
                Text("Image #\(index + 1) (\(useWebImage ? "WebImage" : "SwiftUI Image"))")
                    .font(.system(size: 18))
                    .bold()
                    .kerning(0.5)

                Spacer()
            }
        }
        .padding(2)
        .border(Color(.systemRed), width: 2)
    }
}

Do you have an idea how to fix this issue? I already tried to use .resizable(resizingMode: .tile) but I need to shrink the image before I could use just a tile.

For detailed information you can also find the project on GitHub GitHub Project

I would appreciate your help a lot.

like image 689
stefOCDP Avatar asked Apr 04 '20 00:04

stefOCDP


1 Answers

The .clipped affects only drawing, and by-default Button has all content clickable not depending what it is.

So if you want make your button clickable only in image area, you have to limit hit testing only to its rect explicitly and disable everything else.

Here is a demo of possible approach. Tested with Xcode 11.4 / iOS 13.4.

enter image description here

Demo code (simplified variant of your snapshot):

struct ButtonCard: View {

    var body: some View {
        VStack {
            Image("sea")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .frame(minWidth: 0, maxWidth: .infinity, minHeight: 250, maxHeight: 250, alignment: .center)
                .cornerRadius(12)
                .contentShape(Rectangle())    // << define clickable rect !!
                .clipped()

            HStack {
                Text("Image #1")
                    .font(.system(size: 18))
                    .bold()
                    .kerning(0.5)

                Spacer()
            }.allowsHitTesting(false)         // << disable label area !!
        }
        .padding(2)
        .border(Color(.systemRed), width: 2)
    }
}

struct TestClippedButton: View {
    var body: some View {
        Button(action: { print(">> tapped") }) {
            ButtonCard()
        }.buttonStyle(PlainButtonStyle())
    }
}
like image 188
Asperi Avatar answered Nov 18 '22 10:11

Asperi