 Apple Lover Developer & Artist

영속적인 디자인에 현대의 공감을 채워넣는 공방입니다

 Apple/Stanford iOS Programming (SwiftUI)

Lecture 7 Review Part 1: ViewModifier Animation

singularis7 2021. 9. 4. 00:49
반응형

Animation

SwiftUI에서 애니메이션이 어떻게 동작하는가? 모바일 UI 측면에서 애니메이션은 중요한 요소이기 때문에 SwiftUI에서는 이를 쉽게 구현할 수 있도록 설계해두었다.

애니메이션을 구현할 수 있는 방법으로 Shape를 활용해 보는 방법이 존재한다. 예를들어 Memorize 앱에서 카드의 뒷쪽에 파이 모양의 카운트 다운 타이머를 사용하는 경우가 해당된다. 이 때에는 Angle을 활용하여 애니메이션을 구현하였다.

또다른 방법으로 ViewModifier를 사용하는 방법이 존재한다. 그렇다면 ViewModifier은 무엇인가?

우리는 이미 ViewModifier를 사용해본 경험이 있다. 예를들어 aspectRatio, padding, foregroundColor, font와 같은 것들이 해당된다. 이들은 모두 View에 있는 modifier라는 함수를 호출한다. 

위에서 보이는 것처럼 AspectModifier 부분에는 VIewModifier 프로토콜을 만족하기만 하면 어떤 것이 와도 상관없다. 그렇다면 ViewModifier 프로토콜은 무엇인가? 개념적으로 다음과 같이 정의해볼 수 있다.

protocol ViewModifier {
    typealias Content // the type of the View passed to body(content:)
    func body(content: Content) -> some View {
    	return some View that almost certainly contains the View content
    }
}

이처럼 body라는 하나의 함수를 갖고 있다. 이 함수의 역할은 파라미터로 넘어온 것에 기반하여 새로운 View를 생성하여 리턴해주는 역할을 한다. 다른말로 View를 수정해주는 역할을 수행하며 이 프로토콜을 준수하기만 하면 나만의 수정자도 정의할 수 있게된다! 

예를들어 어느 View.modifier(content) 식으로 수정자를 호출했을 때 파라미터로 넘어오는 View가 바로 .modifier를 불러온 View이다. 

ViewModifier의 코드는 View를 구현한 코드와 매우 유사하며 (위에서 보이둣 var body가 아닌 func body를 사용하고 있다) 이렇게 구성된 이유는 ViewModifiers 가 View이기 때문이다.

Speacial Modifier의 일종인 GeometryEffect가 존재하며 Geometry modifier을 만드는데 사용된다. 당장은 Scale(크기 조정), rotation(회전), translating(변환)등을 수정하는 ViewModifier을 만들때 사용된다는 점만 알아두자!

ViewModifier

what exactly are functions like foregroundColor, font, padding, etc, doing?

해답은 예시를 통해 배우는게 가장 좋은 방법이다. 예를들어 다른 View를 "card-ify" 해주는 ViewModifier를 만들고 싶다고 가정해보자! "card-ify"하다는 의미는 Memorize 게임에서 처럼 다른 View를 가져와서 카드로 넣어주는 기능을 담당한다. cardify를 구현하는 수정자는 어떻게 생겼을까? 

Text("👻").modifier(Cardify(isFaceUp: true)) // eventually .cardify(isFaceUp: true)

struct Cardify: ViewModifier {
    var isFaceUp: Bool
    func body(content: Content) -> some View {
    	ZStack {
        	if isFaceUp {
            	RoundedRectangle(cornerRadius: 10).fill(Color.white)
                RoundedRectangle(cornerRadius: 10).stroke()
                content
            } else {
                RoundedRectangle(cornerRadius: 10)
            }
        }
    }
}

위 코드에서 첫번째로 보이는 것은 modifier를 사용하는 것이다. 유령 이모지 텍스트를 카드 모양으로 바꿔주는 기능을 담당한다. 새로운게 나왔다고 두려워하지 말자 우리는 1분만에 ViewModifier를 정복할 수 있다! 강조사항은 다음과 같다!

  • ViewModifier의 var body 대신 func body가 사용되며 파라미터로 넘어오는 content가 바로 Text("👻")이다!
  • modifier은 argument 를 받아올 수 있다. 예시의 경우 isFaceUp이 해당된다.
  • .modifier를 호출하면 some View를 return 한다.

색으로 구분하면 직관적으로 이해된다.

그런데 생각해보면 위와 같은 modifier 호출 방식은 .padding과 같은 modifier를 호출하는 방법과 차이가 존재한다. 이전에는 modifier 메서드를 통하지 않고 바로 호출할 수 있었는데 지금은 그렇지 않다. 해결방법이 없을까?

해결 방법은 생각보다 간단하다. modifier가 리턴해주는 것 또한 View이고 호출하는 것도 View이니 View 프로토콜에 extension으로 붙여주기만 하면 손쉽게 사용자 정의 modifier를 View에 추가하여 사용할 수 있게된다!

Implement Cardify (using ViewModifier)

이전 데모에서 .opacity, .foregroundColor, .padding과 같은 몇가지 ViewModifier를 사용해 본 경험이 있을 것이다.  이번 데모에서는 어떻게 나만의 ViewModifier를 생성할지 알아보는 것이 목표이다. 

우리가 정의하고자하는 ViewModifier은 Padding과 매우 유사하다. 생각해보면 padding modifier은 내가 원하는 어느 View하나를 가져와서 주위에 일정 간격의 공간으로 둘러싸고 수정한 View를 리턴한다.

새로 정의하고자 하는 ViewModifier는 old view 하나를 가져와서 card에다가 담아주는 기능을 구현하며 기존에 정의해 둔 어느 다른 카드와 마찬가지로 카드의 정면이 보일 때에는 old View 내용이 카드에 담겨져서 보일 것이며 카드의 후면이 보일때에는 foregroundColor에 맞춰서 색이 변경되어 보일 것이다. 

어쨋거나 새로운 ViewModifier를 정의하기위해서 새로운 Swift 파일을 생성해보자! 이때 SwiftUI 템플릿을 사용하여 생성하면 기본적인 View를 생성하는 코드가 담겨진체로 파일이 생성되기 때문에 순수한 Swift 파일을 생성할 것을 권장한다.

다른 프로토콜을 conform 시킬 때에도 비슷한 오류가 발생했듯이 이번에도 ViewModifier이 요구하는 메서드 혹은 변수를 구현해줄 것을 요구하는 Xcode 경고창이 뜨고 있다.

앞에서 배운 것처럼 var body가 아닌 func body를 구현해주면 경고창이 사라진다. body 함수의 입력(content)은 우리가 수정하고자 하는 View가 넘어오게 되며 cardify modifier를 사용했을때 content로 넘어온 view를 수정하게 된다. 

func body의 구현에는 View 프로토콜을 준수하기 위한 var body 구현과 마찬가지로 UI Building을 위한 코드가 포함된다. 우리의 목표는 body 부분에 카드 모양의 UI를 만들어주는 코드가 들어가는 것이기 때문에 이전에 작성한 CardView struct를 참조하여 modifier를 구현해보도록 하자!

개선된 코드를 보면 이전의 코드와 큰 틀에 있어서는 차이가 없다. 하지만 파이차트가 들어가는 일부의 코드가 content로 대체되었다! 이제 고민은 위처럼 새롭게 정의한 modifier를 어떻게 사용할지에 대한 문제이다!

이전 코드와 비교해보면 cardify의 contentfh 어느 코드가 들어가야 할지 한눈에 찾아볼 수 있다! 두줄의 코드를 ZStack Combiner를 통해 결합하여 content로 넣어주면 문제가 손쉽게 해결될 수 있을 것이다.

위에서 보이는 ZStack 외에도 Circle과 같은 View Protocol를 Conform 하는 프로토콜은 전부 카드 안에 집어 넣을 수 있다. 코드가 잘 동작하기는 한데 modifier를 불러오는 방식에 있어서 분명 아쉬움이 존재한다. .oppacity 혹은 .padding을 사용하는 방법과 비슷하게 사용할 수 있도록 코드를 개선시켜보자! 목표는 .cardify와 같은 방식으로 호출할 수 있도록 만드는 것이다. 

Cardify struct가 정의된 Swift 파일에 View extension 하나를 추가해준다. 위와 같이 정의하면 View 프로토콜을 만족하는 모든 인스턴스에서 cardify 메서드를 호출하여 카드 모양을 구현해낼 수 있다. 이제 다음과 같이 호출해도 정상적으로 동작한다!

코드가 좀 단정해진 것 같기는 한데 이거 구현하려고 ViewModifier를 정의한 거야?

이러한 질문을 한번쯤 생각해볼 수 있다. 다시 처음으로 돌아가서 생각해보면 본래 목표는 애니메이션을 구현하는 것이었으며 Shape와 ViewModifier를 통해 애니메이션을 구현할 수 있다는 점을 상기할 수 있다. 위와 같이 ViewModifier로 정의하면 카드를 뒤집거나 회전시키는 등의 사용자 정의 애니메이션을 구현하는데 많은 도움이 될 것이다. 

반응형