ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • allowsHitTesting을 통한 뷰 터치 이벤트 넘기기
    SwiftUI 2023. 10. 13. 10:57

    안녕하세요. 그린입니다 🍏

    이번 포스팅에서는 특정 뷰의 터치 이벤트를 무시하고 다음 뷰로 넘기는 뷰 모디파이어인 allowsHitTesting을 학습해볼께요 🙋🏻

     

    이번 포스팅은 SwiftUI로 뷰를 구성하고 터치 이벤트를 뷰가 쌓여있을때 다루기에 너무 유용한데 아주 쉽기에 포스팅이 되게 간단할 수 있습니다!

     

    너무 짧아서 스크롤을 내리기도 전에 포스팅이 끝날 수 있으니 빠르게 치고 빠져봐요 🧙🏻‍♀️

     


    우선 언제 사용할까요?

    아래와 같은 코드가 있다고 가정해볼께요.

    struct ContentView: View {
      @State var isDisplayText: Bool = false
      
      var body: some View {
        VStack {
          Text("Green")
            .font(.title)
            .bold()
            .foregroundColor(.green)
            .opacity(isDisplayText ? 1 : 0)
          
          ZStack {
            Rectangle()
              .fill(.green)
              .cornerRadius(10)
              .frame(width : 100, height: 100)
              .onTapGesture {
                isDisplayText.toggle()
              }
            
            Rectangle()
              .fill(.red)
              .cornerRadius(10)
              .frame(width : 50, height: 50)
          }
        }
      }
    }

     

    ZStack으로 뒤에는 그린 색상의 크기 100인 사각형이 있고 그 위에 레드 색상의 크기 50인 사각형이 있습니다.

    그린 사각형을 터치하면 isDisplayText의 상태가 변경되고 이에 따라 Green 텍스트가 노출되게 되죠.

     

    그런데 여기서 레드 사각형을 누르면 어떻게 될까요?

     

     

    레드 사각형을 누르면 실제 그린 사각형위에 있기에 어떠한 반응도 없고 초록 영역을 눌러야지만 상태값이 변하게 됩니다.

    왜그러냐면 레드 사각형이 위에 있어 탭 이벤트가 레드 사각형을 우선으로 리스폰더하기 때문입니다.

     

    물론 ZStack에 onTapGesture로 해당 로직을 이전시켜 원하는 바를 이룰 수도 있습니다.

     

    그런데 이럴 경우가 아닌 현재코드처럼 사용해야 할때는 레드 사각형의 탭 이벤트를 무시하고 아래 존재하는 그린 사각형으로 탭 이벤트 리스폰더를 전달해야 할거에요!

     

    이럴때 오늘 다뤄볼 allowsHitTesting이 사용됩니다 ☺️

     

    그럼 바로 알아보고 적용해볼까요?


    allowsHitTesting

    공식문서에서는 아주 간단히 소개되어 있어요.

     

    해당 뷰가 Hit Test 동작에 참여하는지 여부를 받아 적용시켜주는 메서드입니다.

     

    func allowsHitTesting(_ enabled: Bool) -> some View

     

    코드 선언은 이러하며 파라미터로 탭 이벤트를 적용시킬지 무시하고 다음 하위로 이벤트를 이관할지 받아와 해당 뷰를 구성하는 모디파이어죠.

     

    설명 이게 끝입니다ㅎㅎ..

     

     

    그럼 위 코드에서 레드 사각형에 적용시켜볼까요?

     

    struct ContentView: View {
      @State var isDisplayText: Bool = false
      
      var body: some View {
        VStack {
          Text("Green")
            .font(.title)
            .bold()
            .foregroundColor(.green)
            .opacity(isDisplayText ? 1 : 0)
          
          ZStack {
            Rectangle()
              .fill(.green)
              .cornerRadius(10)
              .frame(width : 100, height: 100)
              .onTapGesture {
                isDisplayText.toggle()
              }
            
            Rectangle()
              .fill(.red)
              .cornerRadius(10)
              .frame(width : 50, height: 50)
              .allowsHitTesting(false)
          }
        }
      }
    }

     

    너무 간단해서 할 말이 없네요ㅋㅋ

    단순히 적용할 뷰에 allowsHitTesting을 false로 붙여주면 됩니다.

    만약 true로 붙여주면 탭 이벤트를 적용하겠다는 소리이기에 기존 사용하지 않은 구현과 동일한 동작을 합니다.

     

    false로 미적용 시키면 아래와 같이 동작해요!

     

     

    즉, 레드 사각형을 탭해도 레드 사각형의 탭 이벤트는 적용되지 않음으로 하위에 배치된 그린 사각형으로 탭 이벤트가 전달됩니다.

     

    너무 간단하죠~?

     

    그런데 하나 더 들어가볼까요?

    레드 사각형 대신에 이미지를 한번 올려볼께요.

     

    struct ContentView: View {
      @State var isDisplayText: Bool = false
      
      var body: some View {
        VStack {
          Text("Green")
            .font(.title)
            .bold()
            .foregroundColor(.green)
            .opacity(isDisplayText ? 1 : 0)
          
          ZStack {
            Rectangle()
              .fill(.green)
              .cornerRadius(10)
              .frame(width : 100, height: 100)
              .onTapGesture {
                isDisplayText.toggle()
              }
            
            Image(systemName: "car.2.fill")
              .frame(width: 50, height: 50)
          }
        }
      }
    }

     

    이때 우리가 예상하는 바는 이미지 영역을 탭하면 하위 그린 사각형의 onTapGesture가 작동하지 않아야 합니다.

     

    그런데 결과는 작동합니다!

    그 이유는 이미지는 그린 사각형보다 레이아웃 상 상위에 존재하곤 있지만 직접적인 탭 제스쳐를 가지고 있지 않는 컴포넌트입니다.

    그렇기에 탭 이벤트 리스폰더가 체이닝되어 이미지를 건너뛰고 이 이벤트 리스폰더를 받아줄 하위로 연결되어 그린 사각형의 탭 이벤트가 동작하는것이죠.

     

    그렇기에 이미지에서 어떤 이벤트를 동작시켜주고 하위로 리스폰더를 가져가지 않게 하려면 아래와 같이 붙여줘야 합니다.

     

    struct ContentView: View {
      @State var isDisplayText: Bool = false
      
      var body: some View {
        VStack {
          Text("Green")
            .font(.title)
            .bold()
            .foregroundColor(.green)
            .opacity(isDisplayText ? 1 : 0)
          
          ZStack {
            Rectangle()
              .fill(.green)
              .cornerRadius(10)
              .frame(width : 100, height: 100)
              .onTapGesture {
                isDisplayText.toggle()
              }
            
            Image(systemName: "car.2.fill")
              .frame(width: 50, height: 50)
              .onTapGesture {
                // code
              }
              // 필요에 따라 적용
              .allowsHitTesting(false)
          }
        }
      }
    }

     

    여기서 또 이미지의 onTapGesture를 미적용 시키려면 allowsHitTesting을 적용하면 되는데 사실 이럴 필요없이 onTapGesture 코드도 사용하지 않으면 번거롭게 할 필요는 없죠.

     

    다만 allowsHitTesting에 파라미터 상태값을 컨트롤하여 상황에 따라 이미지가 탭되고 안되고를 컨트롤 할때는 필요하겠죠?

     

    이 이벤트 리스폰더에 대해 조금 더 개념적으로 원리를 자세히 알아보고 싶으면 아래 포스팅을 보셔도 좋을듯 합니다 😃
     

    Responder Chain / Touch Event

    안녕하세요. 그린입니다! 이번 포스팅에서는 Responder Chain / Touch Event에 대해 알아보겠습니다. Responder란? : 이벤트를 핸들링하고 반응할 수 있는 객체 : 모든 Resonder 객체는 UIResponder에서 상속된 클

    green1229.tistory.com

     


    마무리

    아주 간단하고 유용하지 않나요?

    이 기회에 어떤 컴포넌트들이 직접적인 탭 제스쳐를 가지고 있는지 찾아봐도 좋을것 같아요!

     


    레퍼런스

     

    allowsHitTesting(_:) | Apple Developer Documentation

    Configures whether this view participates in hit test operations.

    developer.apple.com

    'SwiftUI' 카테고리의 다른 글

    SwiftUI에서 VoiceOver 사용하기  (40) 2023.11.23
    BorderlessButtonStyle의 활용  (44) 2023.10.19
    SwiftUI에서 View의 Size 측정하기  (56) 2023.09.27
    ScrollTargetBehavior  (49) 2023.09.18
    DatePicker & Picker 사용하기  (43) 2023.09.11
Designed by Tistory.