fullScreenCover로 Popup을 띄우는 여러가지 방법

마이노·2024년 4월 4일
0

15주 글쓰기 🐣

목록 보기
6/10
post-thumbnail

Popup

여러분들은 팝업을 띄울 때 어떤 modifier를 선호하시나요? 이번에 제가 만나게 된 이슈들은 배경색이 있는 팝업을 띄우는 과정에서 겪은 트러블 슈팅입니다.

fullScreenCover

fullScreenCover는 이름 그대로 화면을 전체사이즈로 덮어버리는 modifier입니다. parameter로 Binding Value를 받아 Toogle형식으로 많이 사용합니다.

기본 애니메이션은 아래에서 위로 올라와 전체화면을 덮는 형식의 modifier인데요, 배경색이 존재하는 팝업을 (ex.화면이 띄워졌을 때 배경을 흐리게 한다던가..하는)
fullScreenCover로 띄우게 되면 배경색도 같이 말려 올라가면서 굉장히 부자연스러운 animation이 연출됩니다.

struct SecondView: View {
    @State private var popupPresented: Bool = false
    var body: some View {
        ZStack {
            Color.green
            VStack {
                Button(action: { popupPresented.toggle() }) {
                    Text("팝업 띄우기")
                        .font(.largeTitle)
                        .foregroundStyle(.white)
                }
            }
            .fullScreenCover(isPresented: $popupPresented) {
                PopupView(popupPresented: $popupPresented)
            }
        }
    }
}

위 코드를 통해 실행되는 팝업은 설명한대로 흰 배경이 같이 말려들어가는 형태를 보여줍니다. 사실 이게 당연한 동작입니다.

저희는 저 배경까지 올라가는 것을 원하지 않을 수 있습니다. 즉 유저가 선택적으로 커스텀하고 싶을 때 적용할 수 있으려면 어느 부분의 수정이 필요할까요?

  1. 배경색 없애기

    .fullScreenCover(isPresented: $popupPresented) {
    		PopupView(popupPresented: $popupPresented)
    			.presentationBackground(.black.opacity(0.7))
    }

    배경색은 매우 간단하게 없앨 수 있습니다. 띄우는 뷰에서 presentationBackground을 통해 팝업의 배경색을 변경할 수 있습니다.

배경색 변경? OK 그러나 여전히 말려들어감

배경색이 잘 변경이 되었습니다. 하지만 아래에서 위로 올라오는 애니메이션은 여전히 해결되지 않았습니다. 그래도 배경색은 변경을 성공했습니다.

  1. 애니메이션 수정
.fullScreenCover(isPresented: $popupPresented) {
		PopupView(popupPresented: $popupPresented)
				.presentationBackground(.black.opacity(0.7))
}
.transaction { $0.disablesAnimations = true }

transaction에 접근해 전환 시 애니메이션 효과를 비활성화 할 수 있습니다.

애니메이션을 disable하니 어떤가요? 당연하게도 애니메이션 효과를 끔으로써 자연스럽게 등장하는 UX를 포기하게 됩니다.

앞서 살펴본 두가지의 해결법은 모두 띄우려고 하는 View(부모 View)에서 제어하고자 하였습니다. 그렇다면 팝업 뷰에서 수정한다면 해결이 될까요?

어떤 방식으로 접근할 수 있을까요? 우선 말려들어가는 애니메이션 효과를 끈 상태로 접근해야할 것 같습니다. 기본 애니메이션이 그렇게 적용되있으니까요!

추가로 저는 팝업뷰의 offset을 통해 초기에는 화면 밖에 위치하도록 offset을 설정해두고 팝업이 onAppear되었을 때, 즉 팝업이 화면에 띄워졌을 때 offset값을 원래대로 돌려놓아 화면에 보이는 트릭을 생각해봤습니다.

// PopupView
// 초기에 설정된 offset 값
@State private var yOffset: CGFloat = 1000

VStack {
	...
}
.offset(y: yOffset)
.onAppear { yOffset = 0 }
.animation(.spring, value: yOffset)

위 말씀드린대로 offset 설정, 화면에 나타났을 때 원상복구, 이를 애니메이션 처리하는 세가지로직을 만들었습니다.

// 1. 배경색 변경
// 띄우는 뷰에서 설정
.presentationBackground(.black.opacity(0.7))

// 2. 배경색 설정
// 팝업 뷰에서 설정
ZStack { Color.black.opacity(0.7).ignoreSafeArea() }

배경 색은 마찬가지로 둘중 한 가지방법을 적용합니다. 당연하게도 transaction의 animation을 disable한 상태로 시작합니다.

애니메이션을 disable했지만 팝업 자체는 offset animation이 적용되어 잘 작동하는 모습을 보였습니다.

그러나 배경에 애니메이션은 여전히 꺼져있습니다.
팝업뷰의 배경색에 애니메이션 처리를 추가하면 되는 것 아닌가? 싶었지만 이또한 적용되지 않았습니다.

또 하나의 해결법

하위 뷰(팝업)에서 애니메이션 등 제어들을 해본결과 모든 제어는 띄우는 뷰에서 작업하는 것이 필요하다는 것을 깨달았습니다. 따라서 ViewModifier를 하나 만들어 다른 프로젝트에서도 재사용하고자 하였습니다.

private struct PopupBackgroundModifier: ViewModifier {
    @Binding var presented: Bool
    let changeColor: Color
    
    func body(content: Content) -> some View {
        ZStack {
            if presented {
                changeColor.ignoresSafeArea()
            }
            content
        }
        .animation(.spring(), value: presented)
    }
}

우선 띄워지고 사라지는 값의 변화를 확인하기 위한 바인딩 값이 필요할 것입니다. 어떠한 배경색으로 변화를 시킬 것인지 해당 컬러를 입력받고자 하였습니다.

내부에서 ZStack으로 binding값에 따라 뷰 전체 크기만큼 적용시킬 수 있습니다. 이를 애니메이션을 통해 더욱 자연스러운 배경색 조절이 가능합니다.

extension View {
    func updatePopupBackground(_ isPresented: Binding<Bool>,
                               _ changeColor: Color) -> some View {
        self.modifier(PopupBackgroundModifier(presented: isPresented,
                                              changeColor: changeColor))
    }
}

실제 사용할 때에는 View extension으로 만들어 주어 편리하게 관리할 수 있도록 합니다.

// 사용부
VStack {
	...
}
.fullScreenCover(isPresented: $popupPresented) {
	PopupView(popupPresented: $popupPresented)
		.presentationBackground(.clear)
}
.updatePopupBackground($popupPresented, .black.opacity(0.7))

우선 팝업의 배경색을 제거해야 했습니다. 어처피 부모 뷰에서 모든 작업이 이루어 질 것이기 때문에 이또한 같이 진행했습니다.

이로써 fullScreenCover의 아래에서 위로 올라오는 애니메이션도 살리면서 배경색까지 자연스럽게 변화하는 팝업 뷰를 만들 수 있었습니다.

번외

func `if`<Content: View>(_ condition: Bool, 
						 apply: (Self) -> Content) -> some View {
	if condition { apply(self) } 
	else { self }
}

// 기존
if presented { changeColor.ignoresSafeArea() }

// 원하는 것
.if(presented) { stack in
		stack.ignoresSafeArea()
}

ViewModifier에서 if (condition)을 통해 분기처리 코드를 작성했었습니다. 이를 해결하기 위해 if View Extension코드를 작성했습니다.

하지만

실제 적용했을 때 뷰가 깨졌었고 애니메이션 또한 적용되지 않았습니다.

이유는 return type이 some View이기 때문입니다. 기존에 가지고 있던 타입을 잃어버려 플래그에 따라 다른 형태의 뷰가 됩니다. 그래서 뷰가 깨지게 됩니다.

마치며

지금까지 fullscreencover를 통해 팝업을 띄우는 여러가지 방법과 시도들을 함께 알아보았습니다. 여러분들은 어떤 방식으로 팝업을 띄우시나요?

다른 modifier를 통해 띄우는 경우, ZStack이나 overlay를 통해 띄우는 방법 등등 스유에는 여러가지 많은 방법으로 팝업을 띄울 수가 있어요. 오늘은! 그중 하나인 풀스크린을 통한 팝업을 알아보았습니다. 읽어주셔서 감사합니다 😆

profile
아요쓰 정벅하기🐥

1개의 댓글

comment-user-thumbnail
2024년 4월 5일

여러 시도 끝에 답을 찾아 내셨군요
이런 재미로 개발을 하는 것 같습니다
이번에 구현하신 팝업을 모듈로 빼서 다른 프로젝트에서도 쓸 수 있게 구성하는 것도 좋을 것 같습니다
깃허브에 오픈소스로 개발해보는 것도 좋을 것 같구요

답글 달기