VStack ์์์ ์ฌ๋ฌ ๊ฐ์ View๋ฅผ ์ผํ๋ก ๊ตฌ๋ถํ์ง ์๋ ์ด์
๊ถ๊ธ์ฆ์ด ์๊ฒผ๋ค
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let stackView = UIStackView()
stackView.axis = .vertical
stackView.spacing = 10
let imageView = UIImageView(image: UIImage(systemName: "globe"))
imageView.contentMode = .scaleAspectFit
stackView.addArrangedSubview(imageView)
let label = UILabel()
label.text = "Hello, world!"
stackView.addArrangedSubview(label)
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
}
UIKit์ ์์ฃผ๋ก ๊ฐ๋ฐ์ ํ๋ค๊ฐ, ์ฒ์ SwiftUI๋ฅผ ์ ํ์ ๋ ๊ต์ฅํ ์ ๊ธฐํ ์ ๋ ๊ฐ์ง๊ฐ ์์๋ค.
์ฒซ ๋ฒ์งธ๋ VStack ์์ ์ฌ๋ฌ ๊ฐ์ View๋ฅผ ๋ฃ๋๋ฐ, ๊ทธ View๋ค์ ์ผํ๋ก ๊ตฌ๋ถํ์ง ์๋๋ค๋ ์ ์ด์๋ค. ๊ทธ๋ฆฌ๊ณ ๋๋ฒ์งธ๋ VStack์ UIKit ์ฒ๋ผ ParentView์ ChildView๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ์๋ ์๋๊ณ , ๊ทธ๋ฅ Vstack ํด๋ก์ ์์ View๋ฅผ ๋ฃ๊ธฐ๋ง ํ๋ฉด View๊ฐ ๋๋ฑํ๊ณ ๋ง๋ค์ด์ง๋ค๋ ์ ์ด์๋ค.
VStack ์์์ ์ด๋ค ์ผ์ด ์ผ์ด๋๊ณ ์๊ธธ๋, ์ดํ ๋ก ์ฝ๊ฒ View๊ฐ ๋ง๋ค์ด์ง๋ ๊ฒ์ผ๊น?
VStack
@frozen public struct VStack<Content> : View where Content : View {
/// ์ฃผ์ด์ง ๊ฐ๊ฒฉ๊ณผ ์ํ ์ ๋ ฌ์ ์ฌ์ฉํ์ฌ ์ธ์คํด์ค๋ฅผ ์์ฑํฉ๋๋ค.
///
/// - ๋งค๊ฐ๋ณ์:
/// - alignment: ์ด ์คํ์ ํ์ ๋ทฐ๋ฅผ ์ ๋ ฌํ๊ธฐ ์ํ ๊ฐ์ด๋์
๋๋ค.
/// ์ด ๊ฐ์ด๋๋ ๋ชจ๋ ํ์ ๋ทฐ์ ๋ํด ๋์ผํ ์์ง ํ๋ฉด ์ขํ๋ฅผ ๊ฐ์ง๋๋ค.
/// - spacing: ์ธ์ ํ ํ์ ๋ทฐ ๊ฐ์ ๊ฑฐ๋ฆฌ์
๋๋ค. ๊ฐ ์์ ํ์ ๋ทฐ์ ๋ํด
/// ์คํ์ด ๊ธฐ๋ณธ ๊ฑฐ๋ฆฌ๋ฅผ ์ ํํ๋๋ก ํ๋ ค๋ฉด `nil`์ ์ง์ ํฉ๋๋ค.
/// - content: ์ด ์คํ์ ๋ด์ฉ์ ์์ฑํ๋ ๋ทฐ ๋น๋์
๋๋ค.
@inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
/// ์ด ๋ทฐ์ ๋ณธ๋ฌธ์ ๋ํ๋ด๋ ๋ทฐ์ ํ์
์
๋๋ค.
///
/// ์ฌ์ฉ์ ์ ์ ๋ทฐ๋ฅผ ์์ฑํ ๋, Swift๋ ํ์ ์์ฑ์ธ ``View/body-swift.property``์
/// ๊ตฌํ์์ ์ด ํ์
์ ์ถ๋ก ํฉ๋๋ค.
public typealias Body = Never
}
๊ถ๊ธ์ฆ์ ํด๊ฒฐํ๊ธฐ ์ํด์, VStack ์ด ์ด๋ป๊ฒ ์ด๋ค์ ธ ์๋ ๊ฑด์ง ์์๋ณด๊ธฐ๋ก ํ๋ค.
VStack์ ์ ๋ค๋ฆญ ๊ตฌ์กฐ์ฒด๋ก, Content ๋ผ๋ ์ ๋ค๋ฆญ ํ์ ์ ์ฌ์ฉํ๋ฉฐ, ์ด ํ์ ์ View Protocol๋ฅผ ์ค์ํด์ผ ํ๋ค. ๊ทธ๋ฆฌ๊ณ VStack ์์ฒด๋ View Protocol ์ ์ค์ํ๋ค.
VStack์ ์ด๊ธฐํ ๋ฉ์๋๋ ์ธ ๊ฐ์ง ๋งค๊ฐ๋ณ์๋ฅผ ๊ฐ์ง๋ค. ์ฒซ ๋ฒ์งธ, alignment๋ ์คํ ๋ด ํ์ ๋ทฐ๋ฅผ ์ํ์ผ๋ก ์ ๋ ฌํ๋ ๋ฐฉ์์ผ๋ก, ๊ธฐ๋ณธ๊ฐ์ .center ์ด๋ค. ๋ ๋ฒ์งธ, spacing ์ ์ธ์ ํ ํ์ ๋ทฐ ๊ฐ์ ๊ฑฐ๋ฆฌ๋ก, ๊ธฐ๋ณธ๊ฐ์ nil์ด๋ฉฐ, ์ด๋ ์คํ์ด ๊ธฐ๋ณธ ๊ฑฐ๋ฆฌ๋ฅผ ์ ํํ๋๋ก ํ๋ค. ์ธ ๋ฒ์งธ๋ content ๋ก @ViewBuilder ์์ฑ์ ์ฌ์ฉํ์ฌ ์ด ์คํ์ ๋ด์ฉ์ ์์ฑํ๋ ํด๋ก์ ์ด๋ค.
์ฌ๊ธฐ์ ๋ด๊ฐ ๊ฐ์ง ๊ถ๊ธ์ฆ์ ๋ํ ํด๋ต์ด ๋ ๋ถ๋ถ์ @ViewBuilder ์์ฑ์ด๋ผ๋ ํ๋จ์ด ๋ด๋ ค์ก๋ค. @ViewBuilder ์์ฑ์ ์ํด์, VStack ๋ด์ View๋ค์ด ์กฐํฉ์ด ๋๋ ๊ฒ ๊ฐ์๋ฐ, ๊ทธ๋ฌ๋ฉด @ViewBuilder ์์ฑ์ ๋ฌด์์ผ๊น?
@ViewBuilder
@ViewBuilder ์์ฑ์ ์ด๋ป๊ฒ ์ด๋ฃจ์ด์ ธ ์๋ ๊ฒ์ผ๊น?
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@resultBuilder public struct ViewBuilder {
public static func buildExpression<Content>(_ content: Content) -> Content where Content : View
public static func buildBlock() -> EmptyView
public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
public static func buildBlock<each Content>(_ content: repeat each Content) -> TupleView<(repeat each Content)> where repeat each Content : View
}
- iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0 ์ด์์์ ์ฌ์ฉํ ์ ์์
- @resultBuilder ์ดํธ๋ฆฌ๋ทฐํธ๋ฅผ ์ฌ์ฉํ๊ณ ์์
@resultBuilder
Swift 5.4์ ์๋ก ์๊ธด ๊ธฐ๋ฅ์ผ๋ก ๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด๋ฅผ ๋จ๊ณ๋ณ๋ก ๋น๋ํ๋ ํ์
. ์์ฐ์ค๋ฝ๊ณ ์ ์ธ์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก ์ค์ฒฉ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด๋ฅผ ์์ฑํ๊ธฐ ์ํด ๋๋ฉ์ธ - ํน์ ์ธ์ด(DSL)๋ฅผ ๊ตฌํํ๊ธฐ ์ํด Result Builder๋ฅผ ์ฌ์ฉํจ.
๐ก DSL์ด๋?
“๋๋ฉ์ธ” ์ด๋ผ๋ ํน์ ์์ญ์์ ์๋ํ๋ ํ๋ก๊ทธ๋จ์ ์ํด ์ค๊ณ๋ ์ผ์ข ์ ์ํ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด. DSL์ ํน์ ์ข ๋ฅ์ ์์ ์ ์ผ๋ํด ๋๊ณ ์ค๊ณ๋์๊ธฐ ๋๋ฌธ์ ๊ทธ๋ฌํ ์ข ๋ฅ์ ์์ ์ ๋ ์ฝ๊ฒ ์ํํ ์ ์๋ ํน๋ณํ ๊ธฐ๋ฅ์ ๊ฐ์ง ์ ์์.
resultBuilder์ static method
static func buildBlock(_ components: Component...) -> Component
๋ถ๋ถ ๊ฒฐ๊ณผ์ ๋ฐฐ์ด์ ๋จ์ผ ๋ถ๋ถ ๊ฒฐ๊ณผ๋ก ๊ฒฐํฉํจ
@resultBuilder
struct ArrayBuilder {
static func buildBlock(_ components: [String]...) -> [String] {
return Array(components.joined())
}
}
@ArrayBuilder var array: [String] {
["Apple", "Developer", "Academy"]
["TeaPot"]
}
print(array) // ["Apple", "Developer", "Academy", "TeaPot"]
static func buildExpression(_ expression: Expression) -> Component
ํํ์์ ๋ด๋ถ ํ์ ์ผ๋ก ๋ณํ์ ์ํํ๊ฑฐ๋ ์ฌ์ฉํ๋ ๊ณณ์์ ํ์ ์ถ๋ก ์ ์ํ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์ ๊ณตํ๊ธฐ ์ํด ๊ตฌํํ ์ ์์ต๋๋ค.
@resultBuilder
struct ArrayBuilder {
static func buildBlock(_ components: [String]...) -> [String] {
return Array(components.joined())
}
static func buildExpression(_ expression: String) -> [String] {
return [expression]
}
static func buildExpression(_ expression: [String]) -> [String] {
return expression
}
}
@ArrayBuilder var array: [String] {
["Apple", "Developer", "Academy"]
["TeaPot"]
"Tea"
}
print(array) // ["Apple", "Developer", "Academy", "TeaPot", "Tea"]
static func buildPartialBlock(first: Component) -> Component
buildPartialBlock(accumulated:next:)
Result Builder์์ ์ฌ์ฉ๋๋ ๋ฉ์๋ ์ค ํ๋๋ก, ๋ณต์กํ ๋น๋ ๋ธ๋ก์ ๋จ๊ณ์ ์ผ๋ก ์ฒ๋ฆฌํ๊ธฐ ์ํด ๋์ ๋ ๊ฒ
Swift ์ปดํ์ผ๋ฌ๋ ์ด ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋ ๋ธ๋ก์ ๋ถ๋ถ์ ์ผ๋ก ํ๊ฐํ๊ณ ๊ฒฐํฉํ ์ ์์
@resultBuilder
struct SimpleBuilder {
// ๋น๋ ๋ธ๋ก์ ์ฒซ ๋ฒ์งธ ํํ์์ ์ฒ๋ฆฌํจ
static func buildPartialBlock(first: String) -> String {
return first
}
// ์ด์ ๋จ๊ณ์์ ๋์ ๋ ๊ฒฐ๊ณผ์ ํ์ฌ ํํ์์ ๊ฒฐํฉํจ
static func buildPartialBlock(accumulated: String, next: String) -> String {
return accumulated + " " + next
}
}
@SimpleBuilder var example: String {
"Hello"
"World"
}
๊ทธ๋ฌ๋ฉด ๋ค์ ViewBuilder
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@resultBuilder public struct ViewBuilder {
/// ๋น๋ ๋ด์์ ํํ์์ ์์ฑํฉ๋๋ค.
public static func buildExpression<Content>(_ content: Content) -> Content where Content : View
/// ์๋ฌด๋ฐ ๋ทฐ๊ฐ ์๋ ๋ธ๋ก์์ ๋น ๋ทฐ๋ฅผ ์์ฑํฉ๋๋ค.
public static func buildBlock() -> EmptyView
/// ๋จ์ผ ์์ ๋ทฐ๋ฅผ ์์ ํ์ง ์๊ณ ๊ทธ๋๋ก ์ ๋ฌํฉ๋๋ค.
/// ์์ ๋ทฐ๋ก ์์ฑ๋ ๋จ์ผ ๋ทฐ์ ์๋ `{ Text("Hello") }`์
๋๋ค.
public static func buildBlock<Content>(_ content: Content) -> Content where Content : View
/// ์ฌ๋ฌ ๊ฐ์ ๋ทฐ๋ฅผ ํํ ๋ทฐ๋ก ๊ฒฐํฉํ์ฌ ์์ฑํฉ๋๋ค.
public static func buildBlock<each Content>(_ content: repeat each Content) -> TupleView<(repeat each Content)> where repeat each Content : View
}
VStack ์์์ ๋์ ์๋ฆฌ
// ์ฐธ๊ณ
@inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
// VStack ์ด ์ด๋ป๊ฒ ์ด๋ฃจ์ด์ ธ ์๋์ง
VStack {
Text("Title").font(.title)
Text("Contents")
}
// #1
Vstack.init(content: {
Text("Title").font(.title)
Text("Contents")
return // TODO: build results using 'ViewBuilder'
})
// #2
Vstack.init(content: {
// 1. ๊ฒฐ๊ณผ๋ฅผ ์์ฑํ๋ ๋ชจ๋ ๋ช
๋ น๋ฌธ์ ๋ํ ๋ณ์ ์์ฑ
let v0 = Text("Title").font(.title)
// result builder๊ฐ ๋ณด๊ธฐ ์ ์ ๊ฐ์ด ๋ณ๊ฒฝ๋จ
// ์์ ์๋ฅผ ๊ตฌ์ฑํ๋ ๋ฅ๋ ฅ + result builder ๊ฐ ๋ณด๊ธฐ ์ ์ ๊ฐ์ ์์ ํ๋ค = Swift DSL ์ด ์์ ์๋ฅผ ์์ฃผ ์ฌ์ฉํ๋ ์ด์
let v1 = Text("Contents")
// 2. buildBlock์ ์์
์ ๋ชจ๋ ๋งค๊ฐ๋ณ์๋ฅผ ๋จ์ผ๊ฐ์ผ๋ก ์ฒ๋ฆฌํ๊ฑฐ๋ ๊ฒฐํฉํ์ฌ ๋ฐํํ๋ ๊ฒ
return ViewBuilder.buildBlock(v0, v1)
// 3. Vstack ์ด ์ฝํ
์ธ ๋ก ์ฌ์ฉํ ๋จ์ผ ๊ฐ์ผ๋ก ์ด์
๋ธ ํ ์ ์๋๋ก ํจ
})
// #2
Vstack.init(content: {
let v0 = Text("Title").font(.title)
let v1 = Text("Contents")
return ViewBuilder.buildBlock(v0, v1)
})
๊ฒฐ๋ก
resultBuilder๋ก ๊ตฌ์ฑ๋ ViewBuilder ์์ฑ ๋๋ถ๋ฐ, VStack ์์์ ์ผํ ์์ด ๋ทฐ๋ค์ ๊ตฌ๋ถํ ์ ์์๋ค.