안녕하세요. 오늘은 SwiftUI의 ViewThatFit에 대해서 알아보겠습니다.
Responsive View (반응형 뷰)는 화면의 크기에 따라서 다른 View를 보여주는 View를 의미합니다. 브라우저 화면의 크기를 마음대로 바꿀 수 있는 앱 개발에 더 쉽게 접할 수 있는 개념인데요. iOS를 비롯한 모바일 개발의 경우 디바이스의 크기가 정해져 있기 때문에 (안드로이드와는 다르게 iOS를 사용하는 기기의 사이즈는 정말로 제한되어 있죠) 웹 개발보다는 상대적으로 덜 신경을 쓰는 개념이기도 합니다.
웹 개발의 경우 브라우저의 크기는 사용자가 마음대로 변경할 수도 있고 모바일 브라우저에도 대응을 해야하기 때문에 반응형 뷰를 신경써서 만들어야 하는데요. 모바일의 경우 상대적으로 덜하지만 반응형 뷰를 신경 써서 개발해야 하는 경우가 발생하기도 합니다. 아래 예시를 한번 볼까요?
예를 들어서 사용자가 어떤 버튼을 클릭을 했을 때 특정 UI가 보여져야 한다고 가정을 해봅시다. 이 예시를 아주 단순화해서 구현한 것이 아래 화면입니다.
import SwiftUI
struct ResponsiveView: View {
@State var showRectangle: Bool = false
var body: some View {
HStack {
Rectangle()
.frame(width: showRectangle ? 320 : 0)
HStack {
Spacer()
HStack {
showButton
hideButton
}
}
.padding(.trailing)
}
}
private var showButton: some View {
Button("show") {
withAnimation {
showRectangle = true
}
}
}
private var hideButton: some View {
Button("hide") {
withAnimation {
showRectangle = false
}
}
}
}
이 때 왼쪽에 UI가 등장하면서 오른쪽에 있는 버튼들의 텍스트가 강제로 줄바꿈이 됩니다. 텍스트가 표시될 공간이 부족해서 인데요. 공간을 고려했을 때 버튼들이 수평으로 배치되지 않고 수직으로 배치되면 텍스트를 그대로 유지할 수 있을 것 같습니다.
물론 이번 예시의 경우 if문을 활용해서 할 수도 있겠습니다만 경우의 수가 많아지면 코드가 복잡해질 수 있습니다. 따라서 View가 표시될 공간에 대해서 GeometryReader 등을 활용해서 계산을 해야하는 번거로움도 있고요.
ViewThatFits는 사이즈에 맞춤형으로 변하기를 원하는 View를 선언해두기만하면 됩니다. 아래 코드 처럼요.
ViewThatFits {
HStack {
showButton
hideButton
}
VStack {
showButton
hideButton
}
}
이렇게 안에 정의하면 알아서 OS에서 뷰가 들어갈 수 있는 공간에 적합한 View를 넣어줍니다.
ViewThatFits 안에 View를 선언하는 순서는 우선순위 순입니다. 즉 더 큰 뷰가 먼저 선언되어야 합니다. 위 버튼들의 경우 HStack에 배치하는 것이 더 많은 공간을 필요로 합니다. 따라서 원하는 대로 동작하기 위해서는 HStack이 먼저 선언되어야 합니다.
참고로 VStack이 먼저 선언되면 공간이 작던 많던 수직으로 버튼을 배치할 수 있으므로 공간이 충분할 때에도 수직으로 버튼이 배치되게 됩니다.
생략이 가능하지만 ViewThatFits는 .horizontal 혹은 .vertical을 인자로 받습니다. 해당 인자는 View의 공간 계산을 어느 축으로 할 것인가 입니다. 예를 들면 위 버튼의 경우는 가로를 기준으로 길이가 부족하면 HStack에서 VStack으로 변하게 됩니다. 반대의 경우도 있을 것입니다.
예시 코드처럼 in에 아무것도 전달하지 않으면 가로, 세로를 모두 고려해서 View를 표시합니다. 하지만 만약 특정 축을 전달하면 그 축 기준의 길이만 고려해서 View를 선택합니다. 예를 들어 위 코드에 vertical를 전달하면 가로의 길이를 계산하지 않고 세로만 기준으로 생각 하므로 버튼의 배치가 변하지 않습니다. 세로 기준으로는 공간이 충분하기 때문입니다.
위 예시는 true 혹은 false의 두 가지 경우에만 대응하면 되므로 사실 그렇게 와닿는 예시는 아닙니다. 하지만 아래 예시는 어떨까요? View의 크기를 슬라이더로 조절하는 View입니다.
struct ResponsiveView2: View {
@State var width: Float = 20
var body: some View {
VStack {
ViewThatFits(in: .horizontal) {
Text("아아아아아아아아")
Text("아아아아아아아")
Text("아아아아아아")
Text("아아아아아")
Text("아아아아")
Text("아아아")
Text("아아")
Text("아")
}
.frame(width: CGFloat(width))
Slider(value: $width, in: 20...150)
}
}
}
위 같은 View를 if문으로 처리하려면 일단 View의 크기를 GeometryReader를 통해서 읽어와야 합니다. 그리고 주어진 크기에 텍스트가 몇개나 들어갈 수 있는지도 알아야 합니다. 엄청나게 복잡한 코드가 예상이 됩니다. 하지만 ViewThatFit은 아주 간단하게 원하는 View를 만들 수 있습니다.
오늘은 ViewThatFit에 대해서 알아봤습니다. 포스팅을 작성하면서 계속 iPhone 8이 생각이 났습니다. 많은 iOS 개발자 분들이 공감할 것 같은데요. 다른 화면에서는 멀쩡하지만 iPhone 8에서만 실행하면 너무 화면이 작아서 깨지는 View가 많습니다. 하지만 ViewThatFits를 사용하면 훨씬 쉽게 작은 화면의 아이폰에 대응할 수 있을 것 같습니다.