앱 화면은 생각보다 크지 않고, 모든 내용을 한 화면에 담을 수 없기에 우리는 '스크롤' 이라는 것을 사용한다. 우리에게 친숙한 것은 위아래, 즉 수직 스크롤일 것이다. 거의 대부분의 앱이나 웹 페이지가 채택하고 있는 방식이기도 하다.
그렇다면, 수직과 수평 스크롤을 사용해서 앱 내에 공간을 만들 수는 없을까?
사실 이게 정말 실용적일지는 모르겠지만, ScrollView와 GeometryReader에 대해 알아보고, 이를 적절히 적용하여 앱 내에서 한 번 구현해보자.
ScrollView 란, 스마트폰을 써본 사람이라면 모두가 아는 스크롤 화면을 구현하는 친구이다.
사용법은 아주아주 간단하다.
스크롤이 필요할 것 같은 View를, ScrollView로 감싸주면 끝이다.
예를 들어, 다음과 같은 코드와 화면을 보자.
struct ContentView: View {
var body: some View {
VStack {
Rectangle().frame(height: 500).foregroundColor(.red)
Rectangle().frame(height: 500).foregroundColor(.blue)
Rectangle().frame(height: 500).foregroundColor(.green)
} // VStack
}
}
코드에서 정의된 3가지 사각형이 화면에 모두 표시되지 않는 것을 볼 수 있다.
그럼, ScrollView로 묶은 뒤에 결과를 보자.
struct ContentView: View {
var body: some View {
// ScrollView 사용
ScrollView {
VStack {
Rectangle().frame(height: 500).foregroundColor(.red)
Rectangle().frame(height: 500).foregroundColor(.blue)
Rectangle().frame(height: 500).foregroundColor(.green)
} // VStack
} // ScrollView
}
}
만약 수평 스크롤을 사용하고 싶다면, 아래와 같이 기본값이 .vertical로 설정되어 있는 방향값을 .horizontal로 주면 된다.
(살짝 예습을 해보면, 방향값을 [.vertical, .horizontal] 로 준다면 수직과 수평 스크롤을 모두 사용할 수 있을 것이다.)
struct ContentView: View {
var body: some View {
// 수평 스크롤
ScrollView(.horizontal) {
HStack {
Rectangle().frame(width: 500).foregroundColor(.red)
Rectangle().frame(width: 500).foregroundColor(.blue)
Rectangle().frame(width: 500).foregroundColor(.green)
} // VStack
} // ScrollView
}
}
이번에는 GeometryReader 에 대해 간단하게 알아보자.
SwiftUI에서는, HStack/VStack/ZStack을 적절히 사용하여 특정 View를 원하는 위치에 놓을 수 있다.
하지만, 어떠한 상황에서는 직접 놓을 자리를 설정해야 하는 경우도 있는데, 이때 GeometryReader 가 사용된다.
GeometryReader는 상위 View에서 자신에게 넘어온 크기의 View 안에서 GeometryReader안의 View를 특정한 위치에 놓을 수 있다.
아래의 예시를 보자.
struct ContentView: View {
var body: some View {
VStack {
Spacer().frame(height: 300)
Divider()
GeometryReader { geo in
Rectangle().frame(width: geo.size.width / 4, height: geo.size.height / 4)
.foregroundColor(.blue)
.position(x: geo.size.width / 2, y: geo.size.height / 2)
}
} // VStack
}
}
코드를 보면, 화면 위쪽에는 높이가 300인 빈 공간이 있고 Divider 선 밑의 공간을 GeometryReader로 전달하고 있다.
GeometryReader 안에서는, 전달받은 View에서 View의 크기의 4분의 1만한 파란 정사각형을 만들고, 이를 View의 정중앙에 위치시키고 있다.
결과는 아래의 화면과 같다.
이렇게, GeometryReader는 그 자체로 View이며 Parent View에게 할당받은 크기 내에서 자신의 Child View들을 원하는 위치에 놓을 수 있다.
그럼, 이제 ScrollView와 GeometryReader를 응용하여 화면 안에 커다란 공간을 만들어보자.
ScrollView 에는 위에서 잠깐 언급한 것처럼 방향값을 [.vertical, .horizontal] 로 설정하면 될 것이고, GeometryReader 에서는 View의 크기를 화면보다 큰 (1000, 1000) 으로 설정할 것이다.
우선, 크기가 (100, 100)인 카드를 하나 정의해준다.
str은 카드 위에 쓰일 문구이고, x와 y는 각각 GeometryReader 안에서 위치를 나타낼 x좌표와 y좌표이다.
struct Card: View {
var str: String
var x: Int
var y: Int
var body: some View {
ZStack {
Rectangle().frame(width: 100, height: 100).foregroundColor(.orange).position(CGPoint(x: x, y: y))
Text(str)
.font(.system(size: 40, weight: .bold))
.position(CGPoint(x: x, y: y))
} // ZStack
}
}
이제, 배경색을 설정한 화면에서 방향값을 [.vertical, .horizontal] 로 설정한 ScrollView 안에 프레임 크기가 (1000, 1000)인 GeometryReader를 만들어준다.
추가로, ScrollView에서 "showsIndicators" 값을 false로 설정하면 스크롤 바를 없앨 수도 있다.
카드는 숫자에 따라,
에 위치시켜주겠다.
코드와 결과를 보자.
struct ContentView: View {
var body: some View {
ZStack {
Color(.lightGray)
.edgesIgnoringSafeArea(.all)
VStack {
ScrollView([.vertical, .horizontal], showsIndicators: false) {
GeometryReader { geo in
Card(str: "1", x: 100, y: 100)
Card(str: "2", x: 900, y: 100)
Card(str: "3", x: Int(geo.size.width) / 2, y: Int(geo.size.height) / 2)
Card(str: "4", x: 100, y: 900)
Card(str: "5", x: 900, y: 900)
} // GeometryReader
.frame(width: 1000, height: 1000)
} // ScrollView
} // VStack
} // ZStack
}
}
전체 코드는 다음과 같다.
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Color(.lightGray)
.edgesIgnoringSafeArea(.all)
VStack {
ScrollView([.vertical, .horizontal], showsIndicators: false) {
GeometryReader { Geo in
Card(str: "1", x: 100, y: 100)
Card(str: "2", x: 900, y: 100)
Card(str: "3", x: Int(Geo.size.width) / 2, y: Int(Geo.size.height) / 2)
Card(str: "4", x: 100, y: 900)
Card(str: "5", x: 900, y: 900)
} // GeometryReader
.frame(width: 1000, height: 1000)
} // ScrollView
} // VStack
} // ZStack
}
}
struct Card: View {
var str: String
var x: Int
var y: Int
var body: some View {
ZStack {
Rectangle().frame(width: 100, height: 100).foregroundColor(.orange).position(CGPoint(x: x, y: y))
Text(str)
.font(.system(size: 40, weight: .bold))
.position(CGPoint(x: x, y: y))
} // ZStack
}
}
GeometryReader는 이렇게만 보면 매우 강력한 도구 같지만, 사실은 탈이 많은 친구이다. 그렇기 때문에 상위 View에서 꼭 필요할 때만 사용하도록 하자.
ScrollView와 Geometryreader를 알아보고 응용해보았다.
어떻게 보면 화면을 구성하는데 있어서 중요한 View들이니 잘 알아두면 좋을 것 같다.
printf("Thank You!\n");
printf("Posted by Thirsty Developer\n");