View Protocol
UIKit과 비교하였을 때 SwiftUI의 특징은 struct로 이루어졌다는 것과 View Protocol을 채택하고 있다는 것입니다. SwiftUI에서 우리가 작성하는 대부분의 화면은 View 프로토콜 기반으로 구성되며, 그 중심에는 body라는 프로퍼티가 존재합니다.
@MainActor @preconcurrency public protocol View {
/// The type of view representing the body of this view.
///
/// When you create a custom view, Swift infers this type from your
/// implementation of the required ``View/body-swift.property`` property.
associatedtype Body : View
/// The content and behavior of the view.
///
/// When you implement a custom view, you must implement a computed
/// `body` property to provide the content for your view. Return a view
/// that's composed of built-in views that SwiftUI provides, plus other
/// composite views that you've already defined:
///
/// struct MyView: View {
/// var body: some View {
/// Text("Hello, World!")
/// }
/// }
///
/// For more information about composing views and a view hierarchy,
/// see <doc:Declaring-a-Custom-View>.
@ViewBuilder @MainActor @preconcurrency var body: Self.Body { get }
}
모든 SwiftUI 뷰는 이 프로토콜을 준수해야 하고, body 프로퍼티를 구현해야 합니다. View 프로토콜은 SwiftUI의 렌더링 시스템과의 연결 고리 역할을 하며, body 프로퍼티를 통해 어떤 View를 화면에 보여줄지 선언할 수 있습니다.
body는 왜 꼭 필요한가?
SwiftUI에서 모든 View는 반드시 body 프로퍼티를 통해 자신이 화면에 어떤 내용을 렌더링할지를 정의해야 합니다. 이 body는 연산 프로퍼티(computed property)이며, @ViewBuilder라는 특수 속성 래퍼가 적용되어 있습니다.
즉, 이 body는 단순한 View 하나만 반환하는 것이 아니라, 조건문(if), 반복문(ForEach), 여러 개의 View를 나열한 구문도 지원할 수 있는 DSL(Domain-Specific Language)처럼 동작합니다.
그런데 같은 연산 프로퍼티임에도 body와 결과의 차이가 발생하는 경우가 존재합니다.
var ted: some View {
if numb < 50 {
Text("TED")
} else { Image(systemName: "star") }
}
var body: some View {
if numb < 50 {
Text("TED")
} else { Image(systemName: "star") }
}
func ted() -> some View {
if numb < 50 {
Text("TED")
} else { Image(systemName: "star") }
}
다음과 같이 프로퍼티명만 바꿔줬을 뿐인데, body는 에러가 나지 않는 반면, ted로 정의된 부분은 컴파일 에러가 발생합니다. 메서드 또한 동일하게 에러가 발생합니다.
연산 프로퍼티나 메서드는 some View를 반환해야 하는데, if 구문의 분기에 따라 반환되는 타입(Text vs Image)이 다르기 때문에 컴파일 에러가 발생하는 것입니다.
반면 body는 컴파일 에러가 발생하지 않고 정상적으로 실행됩니다. 이유는 body 프로퍼티에는 ViewBuilder가 내장되어 있지만, 다른 것들은 ViewBuilder가 정의되어 있지 않기 때문입니다.
@ViewBuilder @MainActor **@preconcurrency** **func** body(content: Self.Content) -> Self.Body
View Protocol의 정의를 보았을 때, body는 @ViewBuilder 로 선언되어 있는 것을 알 수 있습니다.
ViewBuilder
ViewBuilder란 클로저에서 뷰를 구성할 수 있도록해주는 커스텀 파라미터 속성으로, 클로저에서 View를 구성할 수 있도록 도와줍니다. @resultBuilder로 구성되어있고, 이는 여러 개의 View를 선언형 스타일로 묶어줍니다.
@resultBuilder
struct ViewBuilder
@ViewBuilder
var body: some View {
Text("Hello")
Text("World")
}
사실 이러한 식은 내부적으로는 다음과 같이 동작하여 하나의 View를 반환해줍니다.
ViewBuilder.buildBlock(Text("Hello"), Text("World"))
즉, body이외의 다른 프로퍼티에서 명시적으로 @ViewBuilder를 선언하지 않았기 때문에, 다른 타입으로 인식하여 컴파일 에러가 발생한 것이었습니다.
resultBuilder
resultBuilder란 Swift 5.4부터 공식 지원된 기능으로, 커스텀 DSL(Domain-Specific Language)을 만들 수 있게 해주는 기능입니다. resultBuilder는 여러 표현식을 받아 하나의 결과로 조합해주는 빌더입니다.
@resultBuilder
struct StringBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined(separator: " ")
}
}
@StringBuilder
func helloWorld() -> String {
"Hello,"
"Swift"
"World!"
}
// 결과: "Hello, Swift World!"
이처럼 여러 표현식을 자동으로 조합해서 하나의 덩어리로 반환해주는 기능입니다.
ViewBuilder는 resultBuilder를 기반으로 SwiftUI에서 만든 뷰 전용 빌더인 것입니다.
따라서 @ViewBuilder로 정의한다면 꼭 body 프로퍼티가 아니더라도 에러가 발생하지 않습니다.
'iOS > SwiftUI' 카테고리의 다른 글
[SwiftUI] SwiftUI란 (0) | 2025.04.17 |
---|---|
[SwiftUI] NavigationStack (0) | 2023.08.11 |
[SwiftUI] ViewModifier (0) | 2023.08.11 |