Starbucks Caramel Frappuccino
본문 바로가기
  • 그래 그렇게 조금씩
SwiftUI/SwiftUI(Basic)

27. [SwiftUI] Popover Views (sheet, transition, offset) .zIndex(), px/pt

by Toughie 2023. 4. 27.

SwiftUI에서 새로운 뷰를 팝오버 시키는 방법은 다양하다.

이번 시간에는 sheet, transition, offset을 통해 새로운 뷰를 팝오버 하는 방법에 대해 알아보자.

 

sheet

 

//  Created by Toughie on 2023/04/26.
//
import SwiftUI

struct Popover: View {
    //이 변수의 변화에 따라 새로운 뷰를 팝오버 시킴
    @State var showSecondScreen: Bool = false
    
    var body: some View {
        ZStack {
            Color.green.opacity(0.5)
                .ignoresSafeArea()

            VStack {
                Spacer()
                Button("Button") {
                        showSecondScreen.toggle()
                }
                .padding()
                .font(.largeTitle)
                .foregroundColor(.gray)
            }
//             SHEET
            .sheet(isPresented: $showSecondScreen) {
                SecondScreen()
            }
        }
    }
}

struct SecondScreen: View {
    //sheet
    @Environment(\.presentationMode) var presentationMode
    
    var body: some View {
        ZStack(alignment: .topLeading) {
            Color.purple
                .ignoresSafeArea()
            
            Button {
                presentationMode.wrappedValue.dismiss()
            } label: {
                Image(systemName: "xmark")
                    .foregroundColor(.white)
                    .font(.largeTitle)
                    .padding()
                    .padding(.vertical)
            }
        }
    }
}

시트가 사용도 간편하고 흔히 생각하는 모달의 애니메이션도 간편하게 구현할 수 있어서 좋다고 생각한다.

dismiss를 위해서 @Environment 부분을 작성해야 한다는 부분을 다시 한번 기억하자.

 

transition

뷰 계층에서 없던 뷰가 추가될 때 사용하는 transition으로도 비슷한 효과를 낼 수 있다.

가장 다이나믹하게 화면전환 효과를 구현할 수 있다.(sheet, offset에 비해)

상태 변수가 true인 경우에 새로운 뷰를 생성하는 것이다.(이 때 트랜지션 효과를 주는 것)

다만 위의 예시와 달리 secondView의 X버튼이 동작할 수 있도록 Binding변수를 따로 추가해야한다.

//  Created by Toughie on 2023/04/26.
//

import SwiftUI

struct Popover: View {
    
    @State var showSecondScreen: Bool = false
    
    var body: some View {
        ZStack {
            Color.green.opacity(0.5)
                .ignoresSafeArea()

            VStack {
                Spacer()
                Button("Button") {
                        showSecondScreen.toggle()
                }
                .padding()
                .font(.largeTitle)
                .foregroundColor(.gray)
            }
            // TRANSITION
            ZStack {
                if showSecondScreen {
                        SecondScreen(showSecondScreen: $showSecondScreen)
                            .transition(.move(edge: .bottom))
                            //애니메이션이 deprecated 돼서 이 부분을 따로 아래에 다룰 것이다.
                            //일단 동작은 한다 이렇게 써도..
                            .animation(.spring())
                        //                        .animation(.spring(),value: showSecondScreen)
                }
            }
            //쌓는 순서
            .zIndex(2.0)

        }
    }
}

struct SecondScreen: View {
//    transition
    @Binding var showSecondScreen: Bool
    
    var body: some View {
        ZStack(alignment: .topLeading) {
            Color.purple
                .ignoresSafeArea()
            
            Button {
                    showSecondScreen.toggle()
            } label: {
                Image(systemName: "xmark")
                    .foregroundColor(.white)
                    .font(.largeTitle)
                    .padding()
                    .padding(.vertical)
            }
        }
    }
}

이와 같은 방식으로 transition을 활용할 수 있는데.. 이전부터 계속 거슬렸던 아래 경고가 생긴다.

'animation' was deprecated in iOS 15.0: Use withAnimation or animation(_:value:) instead.

 

지금은  showSecondScreen 상태변수에 따라 애니메이션이 걸려야 하기 때문에

.animation(.spring()) -> .animation(.spring(), value: showSecondScreen)으로 변경했더니

경고는 사라졌지만 애니메이션이 동작을 하지 않는 것이다..

 

먼저 지선생님을 통해 .animation과 withAnimation의 차이에 대해 간단하게 알아봤다.

 

.animation modifier와 withAnimation 함수의 가장 큰 차이점은 적용 대상입니다.

.animation modifier는 특정 뷰에 애니메이션 효과를 직접 적용하고,

withAnimation 함수는 애니메이션 효과가 적용될 코드에 적용됩니다.

이로 인해, .animation modifier는 애니메이션 효과가 적용되는 뷰와 함께 사용할 때 유용하고,

withAnimation 함수는 여러 개의 뷰나 상태를 변경할 때 유용합니다.

또한, withAnimation 함수는 애니메이션 효과가 적용될 코드 블록 내부에서만 적용되는 반면,

.animation modifier는 해당 뷰에 계속해서 적용됩니다.

 

추론인데.. .animation은 특정 뷰에 애니메이션 효과를 적용한다고 했다.

근데 지금은 transition을 활용하기 때문에 showSecondScreen이 true값으로 바뀌면 새로운 뷰가 생성된다.

그리고 나서 이 생성된 뷰에 애니메이션을 직접 적용하려는 것이다. 그러니까 안되는 것이다..

 

다시 말해보면

기존에 존재하는 뷰가 있고, 이 뷰에서 상태변수의 변화에 따라 애니메이션을 적용한다? .animation(value:)를 써도 잘 동작 할 것이다.

하지만 지금은 뷰가 없다가 새로 생기기 때문에 .animation(value:)이 상태변수의 변화를 감지할 수가 없는 것이다.

약간 기존에 존재하는 뷰에 .animation을 거는 것이 인수인계를 받는 느낌이라면

기존에 존재하지 않다가 방금 새로 생긴 뷰에 직접 .animation을 걸면 방금 들어온 신입이라 어리둥절한 느낌이랄까..?

 

이 부분은 좀 더 정확하게 알아볼 필요가 있을것 같다.

그래서 우선은 withAnimation을 통해서 showSecondScreen을 변화시키는 함수에 애니메이션을 걸어줬다.

버튼에서 showSecondScreen 변수를 토글하는 클로저에 다 걸어줬다는 의미.

//  Created by Toughie on 2023/04/26.
//

import SwiftUI

struct Popover: View {
    
    @State var showSecondScreen: Bool = false
    
    var body: some View {
        ZStack {
            Color.green.opacity(0.5)
                .ignoresSafeArea()

            VStack {
                Spacer()
                Button("Button") {
                //새로운 뷰가 팝업될 때
                    withAnimation(.spring()) {
                        showSecondScreen.toggle()
                    }
                }
                .padding()
                .font(.largeTitle)
                .foregroundColor(.gray)
            }
   
            // TRANSITION
            ZStack {
                if showSecondScreen {
                        SecondScreen(showSecondScreen: $showSecondScreen)
                        //모달뷰처럼 보이게 수직패딩을 준 것
                            .padding(.top,50)
                            .transition(.move(edge: .bottom))
                }
            }
            //쌓는 순서
            .zIndex(2.0)
        }
    }
}

struct SecondScreen: View {
//    transition
    @Binding var showSecondScreen: Bool
    
    var body: some View {
        ZStack(alignment: .topLeading) {
            Color.purple
                .ignoresSafeArea()
            
            //dismiss 애니메이션을 위한 코드
            Button {
                withAnimation(.spring()) {
                    showSecondScreen.toggle()
                }
            } label: {
                Image(systemName: "xmark")
                    .foregroundColor(.white)
                    .font(.largeTitle)
                    .padding()
                    .padding(.vertical)
            }
        }
    }
}

+ 새로운 modifier를 발견했으니 알아보자

.zIndex() 

ZStack은 마지막 뷰가 맨 위로 차곡차곡 쌓이는 개념이었다.

(기본적으로 뷰가 쌓이는 순서는 뷰가 작성된 순서에 따름)

여기서 뷰가 쌓이는 순서를 변경하고 싶을 때 .ZIndex() modifier를 사용할 수 있다.

값이 높을수록 상위에 쌓인다. (ex. .zIndex(1.0), zIndex(2.0)_상위)

음수도 가능한데, 음수로 갈수록(갚이 작아질 수록) 하위에 쌓이는 개념이다.

 

offset

SwiftUI에서 뷰의 위치를 이동시키는 것은 크게 offsetr과 position이 있다.

둘의 차이점을 먼저 알아보자.

 

offset

뷰를 현재 위치에서 상대적으로 이동시킨다.

이동할 방향과 거리를 지정할 수 있다.(x, y 값을 CGFloat을 활용해서)

.offset(x: 100, y:100) -> 뷰를 '현재 위치에서' 오른쪽으로 100pt, 아래쪽으로 100pt 이동시킨다.

 

position

뷰를 절대적인 위치로 이동시킨다.

마찬가지로 CGFloat 값을 활용하며 .position(x: 50, y: 50)은 

뷰를 x축과 y축에서 각각 50pt씩의 위치로 이동시킨다.

 

+포인트(pt)와 픽셀(px)의 차이점은?🤔

픽셀은 모니터와 같은 디스플레이에서 가장 작은 단위다.

이 픽셀의 빛 세기를 조절해서 색을 나타내고 하는 것이다. 고해상도 모니터는 픽셀이 많다!

 

포인트는 실제 크기와 상관없이 디스플레이에서 뷰를 측정하는 단위다.

예전 아이폰에서는 1포인트 == 1픽셀 이었지만, 지금은 아이폰의 화면에 굉장히 고해상도가 되어서

1포인트 안에 여러픽셀이 들어있는 경우가 많다.

 

즉 픽셀은 디스플레이의 물리적 단위이며 그림을 그릴 때 자주 사용되는 단위이다.

포인트는 뷰의 크기를 지정할 때 사용하는 추상적인 단위이다.

 

그래서 위의 코드를 offset을 통해서 구현한다면!

메인뷰의 ZStack 안에서 뷰 생성.

버튼 토글에 따라 뷰가 새로 생겼다가 없어졌다가 하는 것이 아니라

이미 뷰 계층에 존재하는데, 삼항연산자를 통해 위치를 변경하는 로직이다.

즉 showSecondScreen = false인 경우에는 기기 스크린의 높이만큼 아래로 슉 내려가 있어서 안보이다가

showSecondScreen = true가 되면 원래 위치대로 보이는 것이다. (위치 이동 과정에 애니메이션도 걸어줌)

SecondScreen(showSecondScreen: $showSecondScreen)
                .padding(.top,50)
                .offset(y: showSecondScreen ? 0 : UIScreen.main.bounds.height)
                .animation(.spring(), value: showSecondScreen)

 

근데 개인적으로 offset은 별로 안좋은것 같다..

위치를 잡아줘야 하고 뷰가 여러개 쌓이면 뷰 계층에 이미 여러 뷰들이 존재하기 때문에 비효율적이랄까?

그래서 transition을 최대한 써보도록 하면서 sheet도 사용할 것 같다. 물론 offset이 필요한 경우에는 쓰겠지만!

 

 

오늘은 새로운 뷰를 팝오버 시키는 방법들에 대해 알아봤다 :)