[SwiftUI] Mask

Junyoung Park·2022년 8월 18일
0

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가 있기 때문에 overlayViewstarsView 크기만큼 커지게 되면 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개의 댓글