ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SwiftUI - 조건에 따라 overlay 해주기 (feat. overlayIf)
    SwiftUI 2022. 11. 10. 14:13

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

    이번 포스팅은 SwiftUI에서 조건 분기에 따라 컨텐츠 즉 뷰를 overlay On/Off를 해줄 수 있는 ViewModifier를 만들어 보겠습니다.

     

    SwiftUI에서 overlay와 ZStack이라는 기능들을 활용해 중첩된 뷰를 나타내줄 수 있습니다.

    혹시 해당 개념이 궁금하신분들은 아래 포스팅을 먼저 참고하여 overlay 부분이라도 어떻게 사용되는지 보고 오면 도움이 됩니다🙌

    https://green1229.tistory.com/173

     

    Overlay & ZStack

    안녕하세요. 그린입니다🟢 이번 포스팅에서는 Overlay와 ZStack에 대해 학습해보겠습니다🧑🏻‍💻 왜 알아야하죠? SwiftUI로 뷰를 구성하다보면 Overlay와 ZStack이 비슷한 기능을 해준다는 인식을 받

    green1229.tistory.com

     

    물론 지금부터 만들 ViewModifier라는것이 없을때 기본 overlay로는 처리하는데 한계가 있습니다.
    조건에 따라 다르다는 소리죠.
    그럼 먼저 기본 기능만으로 처리할 수 있는 수준을 보시죠!

     

    기본 overlay ViewModifier로만 조건에 따라 오버레이 컨텐츠 보여주기

    import SwiftUI
    
    struct ContentView: View {
      @State var displayOverlayContent: Bool = false
      
      var body: some View {
        VStack {
          Button(
            action: { displayOverlayContent.toggle() },
            label: { Text("Show and Hide Overlay Content") }
          )
          Spacer()
        }
        .padding()
        .overlay(
          displayOverlayContent ?
          Color.green.frame(width: 300, height: 300)
          : Color.red.frame(width: 300, height: 300)
        )
      }
    }

    위와 같이 간단한 뷰가 있습니다.

    버튼이 클릭되면 overlay되는 컨텐츠는 네모난 색상을 가진 단순한 뷰가 됩니다.

    이 뷰는 클릭 여부에 따라 어떤 색상을 보여줄지 변경되죠!

    그런데 여기서 중요한건 overlay 내부에서 삼항 연산자로 분기를 줘 각 해당하는 뷰를 보여줄 순 있습니다.

    아래처럼 말이죠.

    이거까지는 기본적인 overlay로 가능하며 쉽습니다.

    그런데 만약 클릭 되지 않았을때 즉 displayOverlayContent 값이 false일때 아무 뷰도 보여주고 싶지 않다면 어떻게 해야 할까요?

    EmptyView 혹은 Spacer를 두어도 해결되지 않습니다.

    .overlay(
      displayOverlayContent ?
      Color.green.frame(width: 300, height: 300) as! EmptyView
      : EmptyView()
    )

    타입을 맞춰주고자 이렇게 형변환을 해버린다 해도 런타임 에러를 발생시키죠.

    그렇기에 적절한 방향이 아니라 봅니다.

     

    그렇기에 오늘 만들어볼 overlayIf라는 ViewModifier를 바로 이제 보시겠습니다!

     

    overlayIf ViewModifier 만들기

    위 문제를 해결하는 아주 간단한 구현이 있습니다.

    코드부터 보시죠.

    extension View {
      @ViewBuilder public func overlayIf<T: View>(
        _ condition: Bool,
        _ content: T, 
        alignment: Alignment = .center
      ) -> some View {
        if condition {
          self.overlay(content, alignment: alignment)
        } else {
          self
        }
      }
    }

    View를 extension합니다.

    그리고 overlayIf라는 메서드에 제네릭하게 뷰를 받습니다.

    나타날 조건을 나타내는 condition 파라미터와 받아온 뷰를 content로 삼습니다.

    또 overlay 시 정렬 값을 받아줍니다.

    그 다음 구현부는 보시는것처럼 조건에 따라 오버레이해서 그 뷰를 넘겨줄지 아니면 받아온 뷰 자체를 그대로 넘겨줄지 판단합니다.

    이 모든것은 클로저를 통해 뷰를 구성하고 반환시켜주니 ViewBuilder를 이용해줍니다.

    아주 일방적인 방식이죠😄

     

    이렇게 코드를 다시 만져볼께요!
    import SwiftUI
    
    struct ContentView: View {
      @State var displayOverlayContent: Bool = false
      
      var body: some View {
        VStack {
          Button(
            action: { displayOverlayContent.toggle() },
            label: { Text("Show and Hide Overlay Content") }
          )
          Spacer()
        }
        .padding()
        .overlayIf(
          displayOverlayContent,
          Color.green.frame(width: 300, height: 300)
        )
      }
    }

    자 그럼 아래와 같이 클릭이 되면 나오고 안나오는 원하던 동작을 해줍니다.

    결국 만들어진 overlayIf는 기존 overlay 내부에서 조건을 태워 각 다른 뷰를 보여줄 수 있지 않고 넘어온 오버레이 컨텐츠를 보여줄지 말지를 결정해주고 사용해줄때 적합합니다.

     

    만약 값에 따라 다른 뷰를 보여줘야한다면 overlayIf보다는 기존 overlay를 분기 태워 사용하거나 해당하는 ViewModifier를 만들 수도 있겠습니다.

     

    음 그럼 한번 말나온김에 기존 overlay를 분기 태우지 않고 ViewModifier로 만들어 overlayIf말고 다른걸 만들어 볼께요!

    @ViewBuilder public func overlayIf<T: View, U: View>(
      _ condition: Bool,
      _ firstContent: T,
      _ secondContent: U,
      alignment: Alignment = .center
    ) -> some View {
      if condition {
        self.overlay(firstContent, alignment: alignment)
      } else {
        self.overlay(secondContent, alignment: alignment)
      }
    }

    overlayIf 그대로 메서드명은 사용하고 들어오는 파라미터에 따라 구분 지어줄 수 있어요.

    위와 같이 두개의 오버레이 컨텐츠를 넘겨주게 되면 값에 따라 보여줄 컨텐츠를 달리 해줄 수 있습니다.

    즉, 위와 같이 원하는 동작을 취할 수 있습니다.

     

    또 여기서 중요한건 secondContent에 Spacer, Empty를 넣어주면 첫번째 overlayIf 동작과 동일하게 구현 해줄 수 있어요.즉 저는 최종적으로 두개를 합쳐 아래와 같이 사용하면 좋지 않을까 생각합니다.

    extension View {
      @ViewBuilder public func overlayIf<T: View, U: View>(
        _ condition: Bool,
        _ firstContent: T,
        _ secondContent: U = Spacer(),
        alignment: Alignment = .center
      ) -> some View {
        if condition {
          self.overlay(firstContent, alignment: alignment)
        } else {
          self.overlay(secondContent, alignment: alignment)
        }
      }
    }

    기본값을 주어 해결해주면 깔끔합니다.

     

    마무리

    이렇게 간단히 알아봤어요!

    ViewModifier를 통해 적절히 커스텀하게 만들어준다면 현재 부족한 SwiftUI의 기능들을 메꿀 수 있습니다.

    구현하다보니 어느정도 이런 느낌의 필요성이 있는 기능들을 확장해 만든 pure-swift-ui라는 라이브러리가 있더라구요.

    https://github.com/CodeSlicing/pure-swift-ui

     

    GitHub - CodeSlicing/pure-swift-ui: Bringing Views into Focus

    Bringing Views into Focus. Contribute to CodeSlicing/pure-swift-ui development by creating an account on GitHub.

    github.com

    직접 만들어도 좋고 라이브러리를 채택해도 좋지만 차라리 라이브러리를 보고 필요한것만 확장시켜 만드는게 더 나아보입니다!

    크게 부담되거나 어렵지 않은 기능들이라고 보여서요🚀

Designed by Tistory.