[SwiftUI] Mask

Junyoung Park·2022년 8월 18일

SwiftUI

목록 보기
8/136
post-thumbnail

How to use Mask in SwiftUI to create a 5-star rating button | Continued Learning #8

Mask

  • 마스크 모디파이어를 통해 실제 뷰 상단에서 마스킹 기법을 사용 가능

구현 목표

구현 태스크

  1. 별 이미지에 대한 rating 변수 변화 감지
  2. 별 뷰 위에 별 뷰 모양의 마스크 뷰를 올리기
  3. rating 값이 바뀔 때마다 마스크 뷰 너비 변경

핵심 코드

GeometryReader { geometry in
            ZStack(alignment: .leading) {
                Rectangle()
                    .fill(LinearGradient(gradient: Gradient(colors: [.red, .blue]), startPoint: .leading, endPoint: .trailing))
                    .frame(width: CGFloat(rating) / 5 * geometry.size.width)
            }
        }
        .allowsHitTesting(false)
  • GeometryReader를 통해 별 뷰 전체의 너비를 알 수 있다. 별 뷰 전체의 너비를 통해 rating 값이 바뀔 때 현재 직사각형의 너비를 얼마나 줄지 결정할 수 있다.
  • mask 모디파이어를 통해 마스킹하는 뷰 사이즈가 마스킹되는 뷰 사이즈로 바뀐다. 즉 '덮히는' 효과가 이루어진다.
  • fill을 통해 색깔이, frame을 통해 직사각형 크기가 변경되고 있다. 실제로 rating 값에 따라 직사각형 크기가 변경될 뿐이지만, mask를 하고 있는 상태이기 때문에 별에 대한 색깔이 '차오르는' 것처럼 느껴질 것이다.
  • 현재 rating을 감지하고 있는 starsView 위에 overlayView가 있기 때문에 overlayView가 starsView 크기만큼 커지게 되면 rating 감지가 되지 않는다. overlayView 자체가 터치 이벤트를 받지 못하도록 hitTesting을 사용한다.

소스 코드

import SwiftUI

struct MaskBootCamp: View {
    @State var rating: Int = 0
    var body: some View {
        ZStack {
            starsView.overlay(overlayView.mask(starsView))
        }
    }
    
    private var overlayView: some View {
        GeometryReader { geometry in
            ZStack(alignment: .leading) {
                Rectangle()
                    .fill(LinearGradient(gradient: Gradient(colors: [.red, .blue]), startPoint: .leading, endPoint: .trailing))
                    .frame(width: CGFloat(rating) / 5 * geometry.size.width)
            }
        }
        .allowsHitTesting(false)
    }
    
    private var starsView: some View {
        HStack {
            ForEach(1..<6) { index in
                Image(systemName: "star.fill")
                    .font(.largeTitle)
                    .foregroundColor(.gray)
                    .onTapGesture {
                        withAnimation(.easeInOut) {
                            rating = index
                        }
                    }
            }
        }
    }
}

구현 화면

profile
JUST DO IT

0개의 댓글