궁금한 내용을 검색해보세요!
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
서근 개발노트
티스토리에 팔로잉
SWIFTUI/Others

SwiftUI : Mask

서근
QUOTE THE DAY

-
Written by SeogunSEOGUN

반응형

Mask

SwiftUI에서는 mask()수정자를 사용하여 이미지 또는 텍스트를 마스크 할 수 있습니다.

 

간단한 예제를 보면서 어떻게 사용할 수 있는지 알아보도록 할게요.

        VStack {
            Image("sample2")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .mask(
                    ZStack {
                        Circle()
                            .frame(width: 208, height: 208)
                        Circle()
                            .frame(width: 90, height: 90)
                            .offset(x: 80, y: 55)
                    }
                )
                .frame(maxHeight: .infinity)
        }

이제 mask를 활용해서 평점을 줄 수 있는 버튼을 하나 만들어 줄까 해요.

 

우선 별이 5개인 뷰를 만들어줍니다.

import SwiftUI

struct maskView: View {
    
    var body: some View {
        ZStack {
            starsView
        }
    }
    private var starsView: some View {
        HStack {
            ForEach(1..<6) { star in
                Image(systemName: "star.fill")
                    .foregroundColor(.gray)
            }
        }
    }
}

그리고 starsViewoverlaymask를 사용하여 회색인 별을 노란색으로 채워주겠습니다.

        ZStack {
            starsView
                .overlay(
                    GeometryReader { geo in
                        Rectangle()
                            .fill(Color.yellow)
                            .mask(starsView)
                    }
                )
        }

이제 frame을 적용해서 숫자가 올라갈수록 rectangleframe넓이도 바뀔 수 있도록 구현해줘야 합니다.

 

@State 속성을 사용하여 rating0으로 설정해줍니다.

struct maskView: View {
    @State var rating: Int = 1
    
    var body: some View {
        ZStack {
            starsView
                .overlay(
                    GeometryReader { geo in
                        ZStack(alignment: .leading) {
                            Rectangle()
                                .foregroundColor(.yellow)
                                .frame(width: CGFloat(rating) / 5 * geo.size.width)
                        }
                    }
                )
        }
    }

@Staterating값을 하나씩 올려보면 별의 색도 채워지는 것을 확인할 수 있습니다. 그리고 overlay뷰를 생성하고 위에 있는 코드를 옮겨줍니다.

    var body: some View {
        ZStack {
            starsView
                .overlay(
                    overlayView
                )
        }
    }
    
    private var overlayView: some View {
        GeometryReader { geo in
            ZStack(alignment: .leading) {
                Rectangle()
                    .foregroundColor(.yellow)
                    .frame(width: CGFloat(rating) / 5 * geo.size.width)
            }
        }
    }

overlayView아래 mask를 적용해볼게요.

                .overlay(
                    overlayView
                        .mask(starsView)
                )

자, 그럼 starsView로 돌아가서 버튼을 눌렀을 때마다 별이 채워질 수 있도록해줘야겠죠? foregroundColor아래 onTapGesture을 추가해줍니다.

    private var starsView: some View {
        HStack {
            ForEach(1..<6) { star in
                Image(systemName: "star.fill")
                    .foregroundColor(.gray)
                    .onTapGesture {
                        rating = star
                    }
            }
        }
    }

withAnimation을 사용하면 더 보기 좋아집니다.

                    .onTapGesture {
                        withAnimation(.easeInOut(duration: 0.3)) {
                        rating = star
                        }
                    }

그런데 한 가지 문제가 있습니다.

 

별 한 개 에서 -> 다섯 개로 높아질 때는 정상적으로 클릭이 가능한데, 5에서 4로 내리면 클릭이 되지를 않습니다.

 

이유는 우리는 지금 overlayView를 클릭하고 있기 때문입니다.

 

onTapGesturestarsView에 줬는데 말이죠. 이럴 때는 overlayView.allowsHitTesting(false)를 할당해주면 완벽히 해결됩니다.

    private var overlayView: some View {
        GeometryReader { geo in
            ZStack(alignment: .leading) {
                Rectangle()
                    .foregroundColor(.yellow)
                    .frame(width: CGFloat(rating) / 5 * geo.size.width)
            }
        }
        .allowsHitTesting(false)
    }

이제 앱을 닫았을 때도 평점이 저장될 수 있도록 UserDefaults를 한번 사용해볼게요.

 

starsViewonTapGestire아래에 다음과 같이 userDefaults를 추가합니다.

    private var starsView: some View {
        HStack {
            ForEach(1..<6) { star in
                Image(systemName: "star.fill")
                    .foregroundColor(.gray)
                    .onTapGesture {
                        withAnimation(.easeInOut(duration: 0.3)) {
                        rating = star
                            UserDefaults.standard.setValue(rating, forKey: "rating_key")
                        }
                    }
            }
        }
    }

그리고 @State도 아래와 같이 수정합니다.

  @State var rating: Int = UserDefaults.standard.integer(forKey: "rating_key")

자 이렇게 앱을 종료 후 재 시작을 해도 부여해줬던 평점이 그대로 저장된 것을 확인할 수 있습니다.

import SwiftUI

struct maskView: View {
    @State var rating: Int = UserDefaults.standard.integer(forKey: "rating_key")
    
    var body: some View {
        ZStack {
            starsView.overlay(overlayView.mask(starsView))
        }
    }
    
    private var overlayView: some View {
        GeometryReader { geo in
            ZStack(alignment: .leading) {
                Rectangle()
                    .foregroundColor(.yellow)
                    .frame(width: CGFloat(rating) / 5 * geo.size.width)
            }
        }
        .allowsHitTesting(false)
    }

    private var starsView: some View {
        HStack {
            ForEach(1..<6) { star in
                Image(systemName: "star.fill")
                    .foregroundColor(.gray)
                    .onTapGesture {
                        withAnimation(.easeInOut(duration: 0.3)) {
                        rating = star
                            UserDefaults.standard.setValue(rating, forKey: "rating_key")
                        }
                    }
            }
        }
    }
}

이미지도 추가해볼게요.

전체 코드

<hide/>

import SwiftUI

struct maskView: View {
    @State var rating: Int = UserDefaults.standard.integer(forKey: "rating_key")
    var body: some View {
        VStack(spacing: 20) {
            starsView.overlay(overlayView.mask(starsView))
            
            Image("sample1")
                .resizable()
                .scaledToFit()
                .mask(
                    ZStack {
                        Circle()
                            .frame(width: 300, height: 300)
                        Circle()
                            .frame(width: 150, height: 150)
                            .offset(x: 100, y: 70)
                        
                    }
                )
        }
    }
    
    private var overlayView: some View {
        GeometryReader { geo in
            ZStack(alignment: .leading) {
                Rectangle()
                    .foregroundColor(.yellow)
                    .frame(width: CGFloat(rating) / 5 * geo.size.width)
            }
        }
        .allowsHitTesting(false)
    }
    
    private var starsView: some View {
        HStack {
            ForEach(1..<6) { star in
                Image(systemName: "star.fill")
                    .font(.title)
                    .foregroundColor(.gray)
                    .onTapGesture {
                        withAnimation(.easeInOut(duration: 0.3)) {
                            rating = star
                            UserDefaults.standard.setValue(rating, forKey: "rating_key")
                        }
                    }
            }
        }
    }
}

 

읽어주셔서 감사합니다🤟

 

본 게시글의 전체 코드 GitHub 👇🏻

 

Seogun95/SwiftUI_ContinuedLearning

SwiftUI 고급 기능 튜토리얼. Contribute to Seogun95/SwiftUI_ContinuedLearning development by creating an account on GitHub.

github.com

 

 


잘못된 내용이 있으면 언제든 피드백 부탁드립니다.


서근


위처럼 이미지 와 함께 댓글을 작성할 수 있습니다.