Search code examples
iosswiftswiftuigesturezstack

How do I keep clipped content of one subview from blocking gestures in underlying subviews with SwiftUI?


I have a main view consisting of a zStack with a background image at the bottom, a user-loaded image above that, and two toolbars at the top. The toolbars are at the vertical top and bottom of the view. I want those toolbars to appear semi-transparent with background images matching the main view's background image in size and position. If the user drags or scales their image to overlap with the toolbars, it should be obscured by them. To accomplish that, I've built those toolbar views as zStacks with the same background image aligned to the top or bottom, respectively, and clipped the content to match the height of the toolbars.

My problem is that the clipped content of those toolbar views blocks gestures on the underlying, user-loaded image. To help visualize the problem, I've linked a screenshot of the debug view hierarchy for the main view (MockScoringView).

Here's the code for the main view:

GeometryReader { geometry in
            ZStack {

                Rectangle()
                    .foregroundColor(.blue)
                    .frame(width: geometry.size.width, height: geometry.size.height)
                    .offset(x: self.baseOffset.width + self.newOffset.width, y: self.baseOffset.height + self.newOffset.height)
                    .scaleEffect(self.scale)
                    .gesture(DragGesture()
                        .onChanged { value in
                                self.newOffset.width = value.translation.width / self.scale
                                self.newOffset.height = value.translation.height / self.scale
                        }
                        .onEnded { value in
                                self.baseOffset.width += self.newOffset.width
                                self.baseOffset.height += self.newOffset.height
                                self.newOffset = CGSize.zero
                        }
                    )

                VStack(spacing: 0) {
                    MockTopToolbarView()

                    Spacer()

                    MockBottomToolbarView()
                }
            }
        }

And the code for the top toolbar view, which is very similar to the one on bottom:

ZStack {
            Image("Background")
                .resizable()
                .scaledToFill()
                .frame(height: 56, alignment: .top)
                .clipped()

            Rectangle()
                .foregroundColor(.toolbarGray)
                .frame(height: 56)
        }

I'd like to modify the content of the toolbar views' backgrounds so they do not block gestures from reaching the user-loaded image.

Debug View Hierarchy


Solution

  • You could set allowsHitTesting(false) on the background images.

    Alternatively, since you do not want the user image to be visible behind the toolbars you could also just add the Rectangle and toolbars to a single VStack.

    ZStack {
        Image("Background")
                .resizable()
                .scaledToFill()
        VStack {
            TopToolBar()
            Rectangle()
                .foregroundColor(.blue)
                ...
            BottomToolBar()
        }
    }