ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SwiftUI로 테두리 오버레이 효과 만들기 (feat. ZStack)
    SwiftUI 2024. 11. 14. 15:00

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

    이번 포스팅에서는 SwiftUI로 테두리 오버레이 효과를 만드는 방법에 대해 구현해보려고 합니다 🙋🏻

     


    SwiftUI로 테두리 오버레이 효과 만들기

    사실 테두리 오버레이 효과라고 하면 어떤건지 감이 안올 수 있죠!

    테두리에 쉐도우를 입히거나 어떤 뷰를 오버레이로 까는것을 말할수도 있고, 너무 사용법이 다양하기에 구현되는 결과물을 상상하자니 너무 무궁 무진하죠!

     

    그래서, 먼저 예시를 보이고 이런걸 만들어보려고 한다~ 소개해볼까 합니다 😃

     

     

    요런 뷰를 만들어보려고 해요.

    약간 공책 디자인 같기도하죠?

    여기서 오늘의 챌린지는 저 초록색 뷰를 테두리 위에 겹쳐 올리는겁니다.

    즉, 공책에서도 스프링 부분이 있는데, 어떻게 보면 저 초록색 뷰가 스프링을 표현하는거라고 보면 될것 같아요.

     

    가장 먼저 저 노트의 아이템 컨텐츠를 간단히 만들어 볼께요.

     

    struct Note: Identifiable {
      var id: UUID
      var content: String
      
      init(content: String) {
        self.id = UUID()
        self.content = content
      }
    }

     

    이제 요걸 사용해서 뷰를 그려볼거에요.

     

    먼저 저 초록색 스프링과 노트 타이틀로 표현된 아이템 뷰를 만들어 봅시다.

     

    struct NoteItem: View {
      let content: String
      
      var body: some View {
        HStack(spacing: 30) {
          Circle()
            .fill(Color.green)
            .frame(width: 20, height: 20)
            .overlay(
              Circle()
                .stroke(
                  Color.green.opacity(0.3),
                  lineWidth: 2
                )
            )
          
          Text(content)
            .font(.title)
            .foregroundColor(.black)
          
          Spacer()
        }
      }
    }

     

    간단히 HStack으로 표현할 수 있겠죠?

     

    이제 상위뷰에서 이걸 적절히 잘 사용만 해주면 됩니다.

     

    사실 가장 처음 생각했던 아이디어는, ForEach로 돌린 VStack뷰에 오버레이로 테두리를 적용하고 반복을 돌때 leading값을 -로 주어서 해보면 어떨까란 생각이였어요.

     

    VStack(
      alignment: .leading,
      spacing: 10
    ) {
      Spacer()
        .frame(height: 10)
      
      ForEach(notes, id: \.id) { note in
        NoteItem(content: note.content)
          .padding(.leading, -10)
        
        Rectangle()
          .fill(.gray)
          .frame(height: 1)
          .padding(.horizontal, 20)
      }
    }
    .overlay(
      RoundedRectangle(cornerRadius: 12)
        .stroke(
          Color.black,
          lineWidth: 2
        )
    )

     

    이렇게 말이에요.

     

    NoteItem에 leading 패딩을 음수로 주면 그만큼 왼쪽으로 움직일거고 테두리는 그대로 일테니 겹쳐 표현되지 않을까? 라는 생각이였어요.

     

     

    그러나 결과는 이렇게 클립되서 표현되었습니다 🥹

     

    당연한거긴 하더라구요.

    저 VStack의 해당하는 뷰에 오버레이로 테두리를 넣은것이기에 그게 위에 쌓이니 당연히 아래 쌓이는 초록색과 타이틀은 범위에 들지 않으면 짤릴 수 밖에 없었죠.

     

    이를 해결하기 위해, clipped가 되지 않도록 해보고 여러 방면을 시도했지만, 다 오버레이와 상관없는 적절하지 않은 방법이였습니다.

     

    그래서 이걸 바로잡기 위한 두가지 방법을 소개해볼까해요!

     

    모든건 사실 ZStack을 활용하는건데 약간의 차이는 있습니다.

     

    첫번째 방법부터 볼까요?

     

    struct ContentView: View {
      @State var notes: [Note] = [
        .init(content: "밀린 블로그 포스팅 하기"),
        .init(content: "데드라인 임박한 프로젝트 끝내기"),
        .init(content: "잠 8시간 이상 자기"),
        .init(content: "맥북 수리하기"),
        .init(content: "운동하기"),
        .init(content: "여행 짐 꾸리기"),
        .init(content: "인테리어 시공하기")
      ]
      
      var body: some View {
        ScrollView {
          ZStack {
            RoundedRectangle(cornerRadius: 12)
              .stroke(
                Color.black,
                lineWidth: 2
              )
              .padding(.horizontal, 10)
            
            VStack(
              alignment: .leading,
              spacing: 10
            ) {
              Spacer()
                .frame(height: 10)
              
              ForEach(notes, id: \.id) { note in
                NoteItem(content: note.content)
                
                Rectangle()
                  .fill(.gray)
                  .frame(height: 1)
                  .padding(.horizontal, 20)
              }
            }
          }
          .padding(.horizontal, 20)
        }
      }
    }

     

    이렇게 ZStack으로 먼저 아래에 테두리를 깔아주는것입니다.

    그 다음 위에 앞서 구현한 VStack을 통해 컨텐트들을 표현하죠.

    그럼 먼저 아래 깔린 RoundedRectangle의 사이즈는 정해지지 않았기에, 상황에 맞춰 최대로 표현할 수 있게 제어권을 넘기죠.

    그럼 VStack을 통해 사이즈가 결정되니 부모뷰의 사이즈로 동일하게 될것이고 이에 맞춰 RoundedRectangle도 동일하게 가져가게 되기에, 별도 사이즈를 측정하거나 하지 않아도 충분히 표현 가능한것이죠!

     

    아주 쉽지 않나요?

     

    ZStack의 특성만 이용하면 사실 정말 단순한 뷰였어요.

     

    그럼 두번째 방법을 볼까요?

     

    struct ContentView: View {
      @State var notes: [Note] = [
        .init(content: "밀린 블로그 포스팅 하기"),
        .init(content: "데드라인 임박한 프로젝트 끝내기"),
        .init(content: "잠 8시간 이상 자기"),
        .init(content: "맥북 수리하기"),
        .init(content: "운동하기"),
        .init(content: "여행 짐 꾸리기"),
        .init(content: "인테리어 시공하기")
      ]
      
      var body: some View {
        ScrollView {
          ZStack {
            // 🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻🙋🏻
            GeometryReader { geometry in
              RoundedRectangle(cornerRadius: 12)
                .stroke(
                  Color.black,
                  lineWidth: 2
                )
                .padding(.horizontal, 10)
                .frame(height: geometry.size.height)
            }
            
            VStack(
              alignment: .leading,
              spacing: 10
            ) {
              Spacer()
                .frame(height: 10)
              
              ForEach(notes, id: \.id) { note in
                NoteItem(content: note.content)
                
                Rectangle()
                  .fill(.gray)
                  .frame(height: 1)
                  .padding(.horizontal, 20)
              }
            }
          }
          .padding(.horizontal, 20)
        }
      }
    }

     

    동일한데, 크기에 대해서 추후 더 유지보수성을 띌 수 있도록 GeometryReader를 사용합니다.

    그럼 현재 뷰가 나타나는 크기에 대해 판단하고 그의 수치를 이용할 수 있죠.

    사실상, 기존 코드와 동작도 동일하기에 현재 표현하고자하는 결과물에선 의미없을 수 있지만 이럴 경우 유용해요!

     

    .frame(height: geometry.size.height + 100)

     

    이렇게 수치를 100만큼 더 줘서 뭔가 여백을 표현하거나 테두리의 사이즈를 직접 조정하고 싶다!

    근데, 그 조정하는게 상수로 딱 넣어주는게 아니라, 위에 깔린 뷰의 크기에서 추가로 얼만큼의 수치를 더해주고 싶다 이럴때죠ㅎㅎ

     

     

    그럼 당연하게도 이렇게 테두리의 사이즈만 늘어나게 되는거죠.

     

    이것도 너무 쉽죠!?

     

    이것 또한 당연히 ZStack을 이용하기에 가능하죠.

     

    결국, 이런 뷰를 구현하기 위해서는 ZStack 사용이 필수라고 봅니다.

    SwiftUI에서 ZStack을 사용할 일이 정말 많으니 이렇게도 쓸 수 있구나 익혀보는게 좋을것 같아요 😃

    'SwiftUI' 카테고리의 다른 글

    SwiftUI - refreshable ScrollView  (26) 2024.11.22
    Pagination (feat. SwiftUI & MVVM)  (18) 2024.11.15
    SwiftUI의 Text에 stroke 적용하기  (2) 2024.11.07
    SwiftUI를 이용한 인터랙티브 애니메이션  (0) 2024.11.01
    SwiftUI - Color 혼합하기  (0) 2024.10.28
Designed by Tistory.