SwiftUI Essential (WWDC)

미니·2023년 3월 15일
1
post-thumbnail

오늘은 SwiftUI에 대해서 WWDC를 본 내용을 정리하고, 이에 대한 견해를 살짝 적어보려고 합니다.
직접적으로 내용을 정리한 내용이기 때문에 틀린 부분이 있을 수 있습니다.

SwiftUI는 앱을 빌드하는 가장 빠른 방법을 제공하도록 설계된 프레임워크라고 애플이 말하십니다. ㅎㅎ
그렇지만, 많은 것들이 친숙하게 다가올 것입니다. 저희 애플 형님께서 다시 구축한것이 아니라 방법을 바꾸셨기 때문이래요.

저희는 앱을 구현하기 위해서 기본적으로 구현해야 하는 사항들이 있습니다. 가령 예를 들어서 다양한 장치에 대해서 앱이 작동하기 위해서 오토 레이아웃을 조정해왔습니다. 또한, 이런 기능 뿐만 아니라 저희 앱에서 사용자에게 제공할 기능을 구현도 해야 합니다. 그렇다면, 기본적으로 해야 하는 일들을 해준다면, 저희는 사용자에게 제공해야 하는 기능만 구현하면 되지 않을까요?
그래서 SwiftUI는 기본적으로 구현해야 하는 사항들에 대해서 제공할 수 있도록 합니다.

View

저희가 앱을 구현하면서 가장 기본이 되는 것은 어떤 것이 있었나요? UIView가 가장 기본이 되는 타입이 되었습니다. SwiftUI도 이와 동일한 기능을 하는 타입이 존재합니다. View라는 타입입니다.

Code & View Hierarchy


다음 그림을 보시면 왼쪽은 SwiftUI로 구현한 코드이고, 왼쪽은 저희가 구상한 뷰의 계층 구조입니다. 이를 유심히 보시면 계층 구조가 코드에 동일한 형태로 나타나는 것을 볼 수 있습니다.
또한, addSubview와 같은 메서드를 활용하지 않게 됩니다. 이는 SwiftUI의 요소들에서 가장 큰 특징인 뷰 계층 구조를 하나씩 구축하기 보다는 이미 구성된 구조로 초기화가 된다는 것입니다.
이는 다른 이야기로 말하게 되면, 선언적으로 구현된다라고 표현할 수 있습니다.
이와 반대되는 개념인 명령형 코드가 있습니다. 이를 쉽게 이해하기 위해서 예시를 하나 들어보겠습니다.

Declare vs Imperative

아보카도 토스트 만드는 방법을 친구에게 전화로 알려주기 위해서는 순차적으로 모든 절차를 설명해야 할 것입니다. 하지만, 아보카도 토스트를 전문으로 만드는 음식점에 주문을 하는 것은 어떻게 할까요? 단순히 어떤 토핑을 제거해주세요와 같은 요구사항만 전달하면 될 것입니다. 즉, SwiftUI는 아보카도 토스트 가게와 같은 전문가이고, 개발자들의 주문에 따라서 뷰를 구성하게 되는 것입니다.


이를 다시 정리하면, 명령형은 명시적으로 명령을 전달하여서 결과를 구성한다. 와 같은 의미로 이해할 수 있습니다. 선언형은 묘사를 통해서 결과를 구성하고, 이를 어떻게 생성할지 다른 주체에 의해서 결정되게 됩니다.

View Container

Stack


Container View는 기본적으로 HStack VStack 이 존재하게 됩니다. 이들의 구성을 보게 되면, ViewBuilder 속성이 붙어있는 값을 가지게 됩니다. 이는 클로저의 형태를 가지고 호출하게 됩니다. 이는 중괄호와 들여쓰기를 사용하여서 뷰와 그 내부 콘텐츠를 구별할 수 있는 자연스러운 구문을 만들게 됩니다. Swift 컴파일러는 클로저 내부에 있는 값들을 한번에 VStack 내부의 뷰로 구성하는 방법을 알고 있으며, 우리는 이를 통해서 뷰를 구성해달라고 요청하는 것 뿐이다.

View Modifier

Text("Avocado Toast").font(.title)

다음 코드를 보게 되면, Text요소에 대해서 폰트를 설정하기 위해서 점문법을 통해서 스타일을 정의하고 있다. 이러한 종류의 메서를 Modifier라고 부른다. 이는 기존의 뷰에 새로운 뷰를 구성하는 방법이다. 이는 기존 뷰를 감싸는 뷰를 구성하도록 한다. 이를 통해서 뷰 계층 구조가 빠르게 복잡해질 수 있다. 우리는 이를 선언형 프로그래밍을 통해서 해결할 수 있다. ViewModifier를 활용하는 것은 결정론적인 방법을 활용하게 되는 것이다. 우리는 이미 SwiftUI에게 얼만큼의 패딩을 줘야 하는지, 어느정도의 크기의 폰트를 주어야 하는 것인지 이미 줬기 때문에 SwiftUI는 이를 연산하여서 뷰로 반환을 해줄 뿐이다.

위와 같은 이유들로 뷰 계층 구조에 대한 성능에 대한 이슈를 고민하지 않아도 된다. 우리는 컴파일 시간에 어떤 값으로 뷰를 변화시켜야 하는지 이미 알려 주었기 때문에 SwiftUI는 이에 대해서 최적화를 수행하게 된다. 더 작고 단일 목적의 뷰를 선호하고, 이를 통해서 이해하기 쉽고 유지 관리를 쉽게 만듭니다. 작은 뷰들을 통해서 더 복잡한 뷰를 구성할 수 있게 되며, 재사용성이 증가하게 됩니다.

Custom View


이전 주문 내열을 볼 수 있는 뷰를 구성하려고 합니다. 저희는 List라는 요소를 통해서 컬렉션 뷰로 매핑하여서 콘텐츠를 생성하고 뷰를 반환하게 됩니다. UIKit을 통했다면, 프로토콜을 준수하는 구조체 대신 공통 뷰를 가지는 슈퍼 클래스를 상속 받게 구성하였을 겁니다. 이는 alpha, BackgroundColor와 같은 속성을 내부에 저장하게 되며, UIView의 저장된 속성을 상속하고 고유한 기능도 추가해야 합니다.


하지만, SwiftUI는 alpha, BackgroundColor에 대해서 동일한 종류의 공통 뷰 속성을 별도의 수정자로 표시하게 됩니다. 즉, 내부적으로 값을 저장하는 것이 아니라 외부에서 값을 지정하고 이에 대해서 뷰를 변화시켜준다는 얘기입니다. 이러한 속성에 대해서 저장소가 모든 개별적인 뷰에서 상속되는 대신 Modifier를 활용하여서 계층 구조 전체에게 이런 역할이 분산되게 됩니다. 이는 뷰를 가볍게 만들어 줍니다.


class를 사용하지 않기 때문에 상속을 통해서 공통적으로 저장해야 하는 값이 없기 때문에 프로토콜로 구현할 수 있게 됩니다. 뷰 프로토콜은 UI의 일부를 정의하고 작은 뷰를 함께 구성하여서 큰 뷰로 구성하게 됩니다. 뷰 프로토콜은 단일 요소인 body의 값으로 뷰를 반환하게 된다. 즉, 자기 자신과 같은 타입을 반환하게 된다. 이는 재귀적으로 보일 것이다. 하지만, SwiftUI는 결국 끝에 원자적인 뷰를 반환하게 된다.

우리는 Custom View를 구성하는 방법으로 구조체를 구현할 수 있다는 것을 확인했다. 이는 SwiftUI에서 뷰가 선언적으로 정의되는 방식으로 동작하게 한다. 즉, 시간에 따라서 뷰를 업데이트 해야 하는 영구적인 개체가 아니게 된다. 따라서 입력중 하나가 변경되면, body의 속성을 다시 파악하여서 다시 그리게 된다. 우리는 선언적으로 값의 변경에 의해서 뷰를 다시 랜더링 해주는 커스텀한 뷰를 구성하게 된 것이다. 이는 우리가 UIKit에서 View의 상태를 관리해 오던 것에서 자유롭게 해준다. (reload와 같은 메서드)

Control

우리가 UIKit에서 가장 신경을 많이 쓰게 되는 것은 사용자의 입력이였을 것이다. SwiftUI에서의 버튼을 한번 살펴 보자.

func printNumber() {
	print(123)
}

Button(action: printNumber) {
	Text("Button")
}

우리가 보통적으로 활용하던 방법과 다르다. 그이유는 SwiftUI의 컨트롤은 적응형 으로 구성하였기 때문이다. 이는 다음과 같은 특성을 가지게 된다.

  • 모양이 아닌 목적을 묘사한다.
  • 기본 행동이 똑똑하다.
  • 재사용성이 높다.
  • 더 개인화하기에 편하다.


즉, 사용자가 버튼을 눌렸을 경우에 어떤 것을 수행 해야 하는지에 초점을 맞추게 된 것이다. 이런 특성들을 통해서 다양한 플랫폼에서 모양은 다르지만 동일한 목적을 가지는 컨트롤을 구성할 수 있게 된다.

0개의 댓글