SwiftUI

Kyu hyunSung·2025년 6월 23일

IOS

목록 보기
8/9
post-thumbnail

Model Data

[swiftUI] State and Data Flow - [1탄]

SwiftUI는 Android의 Jetpack Compose와 비슷함.

UI 방식은 선언형임 데이터 상태에 따라 바뀜

항목SwiftUIJetpack Compose
프로그래밍 언어SwiftKotlin
UI 방식선언형선언형
상태 기반 UI@State, @Binding, @ObservedObjectremember, mutableStateOf, StateFlow
Live PreviewXcode에서 지원Android Studio에서 지원
코드 구성View 구조를 중첩함 (VStack, HStack, ZStack 등)Composable 함수로 중첩 구조 사용
재사용성Custom View (e.g. MyCardView)Composable 함수 (e.g. @Composable fun MyCard())
기존 UI와 호환UIKit과 함께 사용 가능XML/Android View와 함께 사용 가능

0. 상태 관리

사용자가 버튼을 누르거나 텍스트를 입력하는 등의 상호작용을 하면, -> 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를 보여줄 것인가"만 선언

1. (본인의프로젝트명)App

프로젝트 이름에 따라 생기는 파일

  • Storyboard 방식의 AppDelegate, SceneDelegate에 해당
  • @main 키워드가 필요, 전체 프로젝트에서 한번만 사용
import SwiftUI

@main // 프로그램 시작
struct IosSwiftUIApp: App {
    // App은 프로토콜
    var body: some Scene {
        WindowGroup {
            ContentView() // 이 뷰가 화면에 나타남
        }
    }
}

2. Content View

  • 앞의 (본인의프로젝트명)App에서 생성
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()
}

2-1. Some View

  • Swift의 opaque return type (불투명 반환 타입).
  • 모든 복잡한 타입을 대신해서 some View라는 추상 타입을 제공
struct TestSomeView: View {
    var body: some View {
    // 정확한 타입은 말 안 해도 되니까, 어떤 View 하나만 반환.
        VStack {
            Text("Hello")
            Text("World")
        }
    }
}

3. View Protocal

  • SwiftUI의 모든 것은 View 프로토콜을 중심으로 돌아감.
// 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 }

중요한 부분만 보도록 하자.

3-1. associatedtype Body: View

  • associatedtype은 프로토콜 안에서 사용하는 타입의 자리 표시자(placeholder)
associatedtype Body : View

associated type은 정의하는 프로토콜이 채택되기 전까지 실제 타입이 명시되지 않음 , Generic과 유사

즉,이 프로토콜 안에서 어떤 타입을 쓸지 지금은 몰라도 됨
나중에 누가 이걸 사용할 때 정하면 된다는 소리

  • 예시 : MyView.Body == Text 근데, TextView니까 진행
struct MyView: View {
    var body: some View {
        Text("Hello")
    }
}

3-2 Self.Body

  • Self는 현재 View를 구현하는 타입 자신을 의미
  • Self.Body는 해당 타입이 채택한 associatedtype Body를 의미
protocol View {
    associatedtype Body: View
    var body: Self.Body { get }
}

여기서 Body 타입도 다시 View를 따른다는 조건이 있기 때문에,
결국 View 안에서 View를 반환하는 구조가 반복,
Self.Body는 View 프로토콜을 지키는 View라는 것

3-3. { get }

  • Computed property: 저장 프로퍼티와 달리 저장 공간을 갖지 않고, 다른 "저장 프로퍼티" 의 값을 읽어 연산을 실행하거나, 프로퍼티로 전달받은 값을 다른 프로퍼티에 저장

즉, body는 연산 프로퍼티임을 표시

  • EX : 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로 대체할 수 있음

profile
디지털 치매 예방

0개의 댓글