Search code examples
swiftswiftuinavigationrounded-cornersios17

Setting a Corner Radius on a NavigationStack: Unresponsive Content View Areas in SwiftUI


I attempted to apply a cornerRadius to the NavigationStack, but as a result, the touch area became restricted to the initial view position. In simpler terms, if the slider is closed, you cannot open it by tapping it unless you touch the red area depicted in the image. (ControllerView is responsible for animation)

import SwiftUI

private struct ControllerView<Content: View>: View {
    private var content: Content
    @Binding var isOpen: Bool
    
    init(isOpen: Binding<Bool>, @ViewBuilder content: () -> Content) {
        self._isOpen = isOpen
        self.content = content()
    }
    
    var body: some View {
        GeometryReader { proxy in
            ZStack {
                Color.yellow.ignoresSafeArea()
                content
                    .cornerRadius(30) /// <- Here
                    .offset(x: isOpen ? 300 : 0)
            }
            .ignoresSafeArea()
        }
    }
}

private struct GreenView: View {
    @Binding var isOpen: Bool
    @Binding var navigation : [AnyHashable]
    
    var body: some View {
        NavigationStack(path: $navigation) {
            Color.green.ignoresSafeArea()
            .onTapGesture {
                print("GreenView tapped!")
                withAnimation { isOpen.toggle() }
            }
        }
    }
}


struct SwiftUIView9: View {
    @State private var isOpen = true
    
    var body: some View {
        ControllerView(isOpen: $isOpen) {
            GreenView(isOpen: $isOpen, navigation: .constant([]))
        }
    }
}

#Preview {
    SwiftUIView9()
}




Touchable Area


Solution

  • By trial and error, I found that if isOpen is initially false, i.e. initially offset(0), then the tap area is correct.

    We can exploit this by setting the offset initially to 0, and then immediately to isOpen ? 300 : 0 when the view appears

    @State var appeared = false
    
    var correctOffset: CGFloat {
        if !appeared {
            return 0
        }
        return isOpen ? 300 : 0
    }
    

    Add an onAppear to set appeared to true:

    content
        .cornerRadius(30)
        .offset(x: correctOffset)
        .onAppear {
            DispatchQueue.main.async {
                appeared = true
            }
        }
    

    This incorrect touch area behaviour is happening with some other modifiers too, clipped, colorInvert etc. This might just be another quirk with NavigationStack.