
SwiftUI는 Android의 Jetpack Compose와 비슷함.
UI 방식은 선언형임 데이터 상태에 따라 바뀜
| 항목 | SwiftUI | Jetpack Compose |
|---|---|---|
| 프로그래밍 언어 | Swift | Kotlin |
| UI 방식 | 선언형 | 선언형 |
| 상태 기반 UI | @State, @Binding, @ObservedObject 등 | remember, mutableStateOf, StateFlow 등 |
| Live Preview | Xcode에서 지원 | Android Studio에서 지원 |
| 코드 구성 | View 구조를 중첩함 (VStack, HStack, ZStack 등) | Composable 함수로 중첩 구조 사용 |
| 재사용성 | Custom View (e.g. MyCardView) | Composable 함수 (e.g. @Composable fun MyCard()) |
| 기존 UI와 호환 | UIKit과 함께 사용 가능 | XML/Android View와 함께 사용 가능 |

사용자가 버튼을 누르거나 텍스트를 입력하는 등의 상호작용을 하면, -> Action으로 변환
이 Action은 Mutation을 통해 State를 변경.
변경된 State는 자동으로 View를 업데이트하고, 이것이 Render 과정을 거쳐 사용자에게 다시 보여짐
외부 이벤트도 Publisher를 통해 같은 방식으로 State를 변경할 수 있음
EX : 네트워크 응답이나 타이머 이벤트 같은 것들이 여기에 해당합니다.
struct ContentView: View {
@State private var count = 0 // 상태 변수 선언
var body: some View {
VStack {
Text("Count: \(count)") // 상태를 표시
Button("Increment") {
count += 1 // 상태 변경
}
}
}
}
@State private var count = 0
= 다이어그램의 State 부분
일반적인 Swift 구조체는 불변이어서 내부 속성을 변경할 수 없지만, @State 속성 래퍼가 이를 가능하게 함.
@State로 감싸진 count 변수의 변경을 감지합니다.
Text("Count: \(count)")
= View 부분
현재 상태를 읽어서 화면에 표시, 중요한 점은 이 텍스트가 count 값에 의존.
이 변수에 의존하는 모든 View를 찾습니다. 여기서는 Text("Count: (count)")가 해당됩니다.
클로저 내부의
count += 1
= Action과 Mutation에 해당.
사용자가 버튼을 누르면 이 액션이 실행되어 상태를 변경
해당 View들을 자동으로 다시 렌더링합니다
이 모든 과정이 자동으로 일어나므로, 개발자는 "어떻게 UI를 업데이트할 것인가"를 신경 쓸 필요가 없습니다.
대신 "어떤 상태일 때 어떤 UI를 보여줄 것인가"만 선언
프로젝트 이름에 따라 생기는 파일
import SwiftUI
@main // 프로그램 시작
struct IosSwiftUIApp: App {
// App은 프로토콜
var body: some Scene {
WindowGroup {
ContentView() // 이 뷰가 화면에 나타남
}
}
}
import SwiftUI
// View 프로토콜을 상속받은 하나의 뷰
struct ContentView: View {
var body: some View {
// UIStackView에서 axis의 vertical 연상
VStack {
// 두개의 자식임 ㅇㅇ
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}

struct TestSomeView: View {
var body: some View {
// 정확한 타입은 말 안 해도 되니까, 어떤 View 하나만 반환.
VStack {
Text("Hello")
Text("World")
}
}
}
// swiftUICore
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
@MainActor @preconcurrency public protocol View {
/// 이 뷰의 본문을 나타내는 뷰의 타입입니다.
///
/// 사용자 정의 뷰를 생성할 때, Swift는 필수 속성인 ``View/body-swift.property``
/// 구현을 통해 이 타입을 추론합니다.
associatedtype Body : View
/// 뷰의 콘텐츠와 동작입니다.
///
/// 사용자 정의 뷰를 구현할 때는 계산된(computed) `body` 속성을 구현하여
/// 뷰의 콘텐츠를 제공해야 합니다.
///
/// SwiftUI가 제공하는 기본 뷰들과, 이미 정의한 다른 복합 뷰들을 조합하여
/// 반환하는 뷰를 작성하십시오:
///
/// struct MyView: View {
/// var body: some View {
/// Text("Hello, World!")
/// }
/// }
///
/// 뷰를 구성하고 뷰 계층 구조를 만드는 방법에 대한 자세한 내용은
/// <doc:Declaring-a-Custom-View>를 참조하십시오.
@ViewBuilder @MainActor @preconcurrency var body: Self.Body { get }
중요한 부분만 보도록 하자.
associatedtype Body : View
associated type은 정의하는 프로토콜이 채택되기 전까지 실제 타입이 명시되지 않음 , Generic과 유사
즉,이 프로토콜 안에서 어떤 타입을 쓸지 지금은 몰라도 됨
나중에 누가 이걸 사용할 때 정하면 된다는 소리
MyView.Body == Text 근데, Text는 View니까 진행struct MyView: View {
var body: some View {
Text("Hello")
}
}
protocol View {
associatedtype Body: View
var body: Self.Body { get }
}
여기서 Body 타입도 다시 View를 따른다는 조건이 있기 때문에,
결국 View 안에서 View를 반환하는 구조가 반복,
Self.Body는 View 프로토콜을 지키는 View라는 것
즉, body는 연산 프로퍼티임을 표시
var body: some View {
Text("Hello, World!")
}
body는 화면에 그릴 내용을 정의한 것.
SwiftUI는 화면을 다시 그릴 필요가 있을 때 body를 다시 호출
그러면 그때마다 새로운 화면 구성이 계산되어 나감.
import Foundation // sqrt 사용을 위해 필요
struct Rect {
var width: Int // 저장 프로퍼티: 너비
var height: Int // 저장 프로퍼티: 높이
var area: Int { // 연산 프로퍼티: 면적 (width * height로 계산됨)
get { // getter: 읽을 때 실행됨
return width * height
}
set(area) { // setter: 값을 설정할 때 실행됨
// 면적 값을 받아서 정사각형 비슷하게 width와 height를 다시 계산
width = Int(sqrt(Double(area))) // 면적의 제곱근을 너비로 사용
height = area / width // 높이는 면적 ÷ 너비
}
}
}
// 예제 사용
var r = Rect(width: 10, height: 7)
print(r.area) // 70 (10 * 7)
r.area = 123 // setter 실행 → 면적을 123으로 변경 → width, height 재계산
print(r.area) // 121 (11 * 11) — 계산 결과에 따라 가장 가까운 정사각형 형태가 됨
print(r.width) // 11
print(r.height) // 11
area가 연산 프로퍼티임
• get 만 존재할 수도 있음(그러나 set만 존재할 수는 없음)
• area는 반드시 var이어야 함(비록 get만 존재할지라도 let이 아니라 var)
• set에서 area를 생략할 수 있고 이때 newValue로 대체할 수 있음