[SeSAC] November 14, 2023

✔︎ 오늘의 정리

  • SwiftUI
    • body는 연산 프로퍼티다! body: some View
    • ViewModifier
    • 상태 프로퍼티: @State, @binding

 


SwiftUI

body는 연산 프로퍼티이다! body: some View

 

태초에 뷰가 있었다······.

 

 

 

초반 수업 때 자주 들어서 우앗 ㅋㅋ 멋진말.. 하구 생각했었는데 오늘 또 들어서 적어본다.

 

UIKit도 그랬듯이 SwiftUI에도, 태초에는 뷰가 있었다.

 

SwiftUI 파일을 처음 만들게 되면... ...

 

 

import SwiftUI

struct SampleView: View {
    var body: some View {
        Text("Hello, World!")
    }
}

 

이런 코드가 반겨준다.

View protocol을 채택하고 있는 SampleView 구조체가 있고, 그 안에는 body라는 변수가 익숙한 형태로 있다.

어라, 저 형태 ... ... 연산 프로퍼티같지 않은가?

 

맞다!

 

근데 body의 타입인 View는 무엇일까?

 

 

etc-image-0

 

definition을 따라가 보면 body는 View라는 protocol의 associatedType으로 정의되어 있다.

 

어라, associatedType이 뭔데?

간단하게 설명해 보자면, protocol의 generic이라고 보면 된다. 

이 프로토콜에서 이 변수가 특정한 타입으로 고정된 게 아니라 String도 들어갈 수 있고 Int도 들어갈 수 있다면 associatedType으로 정의하면 된다.

 

제네릭을 사용할 때처럼 protocol을 associatedType 뒤에 붙여 제약 조건을 걸어 사용할 수도 있다!

Swift 2.2 이전에는 typealias라고 했는데 지금은 associatedType이라고 한다.

 

암튼~! 

 

 

 

UI를 보여주는 body는 연산 프로퍼티라고 했다. 무엇이든 될 수 있는 associatedType인!

 

직접 확인해 보자.

struct ContentView: View {
    var body: some View {
        
        Button("클릭하깅") {
            let value = type(of: self.body)
            print(value) // Button<Text>
        }
    }
}
struct ContentView: View {
    var body: Button<Text> {
        
        Button("클릭하깅") {
            let value = type(of: self.body)
            print(value) // Button<Text>
        }
    }
}

 

위 두 코드는 같은 코드이다.

왜? body는 associatedType인 연산 프로퍼티니까... ... :3

컴파일 타임에는 anyView라는 타입으로 보이지만, 내부에 들어가는 객체들이 확정되는 런타임 시점에는 자기의 타입을 찾게 되는 것이다.

 

어라, 근데 굳이 타입을 알 수 있으면 `some View` 말고 그냥 타입 쓰면 안대나요?

=> Noooooo... 실질적으로 불가능하다.

 

왜냠 각 개체에 UIKit에서 method 역할을 하는 viewModifier가 붙는다면 body의 타입이 엄청나게 길어지고 복잡해지기 때문이다.

해당 객체의 타입을 명확하게 쓰기 위해 노력하는 것보다 간단하게 some View로 쓰고 다른 걸 맹그는 게,,, 훨씬 좋지 않을까나? :3

 

 

 

 

ViewModifier

잠깐 얘기가 나온 김에 얘도 설명하고 가자면! 공식 문서의 정의는 아래와 같다.

etc-image-1
https://developer.apple.com/documentation/swiftui/view/modifier(_:)

 

기존의 뷰에 modifier method를 사용하면 해당 특성이 적용된 새로운 뷰를 반환한다.

만약 뷰에 여러 modifier method가 붙는다면 일일이 하나하나 반환하고 다시 보낼까!? => Ues. .. ......

여러 modifier가 추가되어 있다면 한 번에 새로운 뷰가 반환되지 않고, 각 modifier가 적용된 뷰가 매번 새롭게 반환된다.

또한, 순서대로 modifier method가 적용되기 때문에 순서가 중요하당.

 

 

만약 자주 사용할 것 같은 modifier method들이 있다면... ..

struct boldCapsuleText: ViewModifier {
    
    func body(content: Content) -> some View {
        content
            .font(.title)
            .bold()
            .padding()
            .foregroundStyle(.white)
            .background(.black)
            .clipShape(.capsule)
    }
    
}
struct SampleView: View {
        var body: some View {
            Text("hello?")
                .modifier(boldCapsuleText())
        }
}

 

위와 같이 ViewModifier를 채택하는 구조체를 생성하여 적용할 수 있다.

 

 

원한다면 한 번 더 extension으로 추상화하여 더욱 간단하게 선언하는 것도 가능하다.

extension View {
    func asBoldCapsuleText() -> some View {
        modifier(boldCapsuleText())
    }
}
struct CustomViewModifier: View {
    var body: some View {
        Text("행복한해파리")
            .asBoldCapsuleText()
    }
}

 

요런 식으루!

 

 

 

 

 

 

 

 

 

 

상태 프로퍼티: @State, @Binding

 

blob

 

//
//  LoginView.swift
//  PracticeSwiftUI
//
//  Created by yeoni on 2023/11/14.
//

import SwiftUI

struct LoginView: View {
    
    @State var email: String = ""
    
    var body: some View {
        ZStack {
            Color.black
                .ignoresSafeArea()
            
            VStack {
                TextField("", text: $email, prompt: Text("이메일 또는 전화번호").foregroundColor(.white))
                    .multilineTextAlignment(.center)
                    .background(
                        RoundedRectangle(cornerRadius: 4)
                            .fill(.gray)
                            .frame(height: 50)
                    )
                    .foregroundStyle(.black)
                    .padding()
            }
        }
    }
}

#Preview {
    LoginView()
}

 

넷플릭스 로그인 화면을 만들기 위해서 먼저 textField를 만들고 있었다.

아 ㅎ_ㅎ 하나 다 맹글었으니 이제 subView로 맹글어서 올려야지~~ 하는 순간!

 

 

etc-image-3

 

엥,,, 오류가 나는 것이다.

이거 어케 해결하냐?!

 

 

왠지 두루뭉실한 기억으로 @State 아니면 @binding을 쓰면 해결될 것 같았다. :3... ...

 

//
//  LoginView.swift
//  PracticeSwiftUI
//
//  Created by yeoni on 2023/11/14.
//

import SwiftUI

struct LoginView: View {
    
    @State var email: String = ""
    
    var body: some View {
        ZStack {
            Color.black
                .ignoresSafeArea()
            
            VStack {
                NetflixTextField(email: $email)
            }
        }
    }
}

#Preview {
    LoginView()
}

struct NetflixTextField: View {
    
    @Binding var email: String
    
    var body: some View {
        TextField("", text: $email, prompt: Text("이메일 또는 전화번호").foregroundColor(.white))
            .multilineTextAlignment(.center)
            .background(
                RoundedRectangle(cornerRadius: 4)
                    .fill(.gray)
                    .frame(height: 50)
            )
            .foregroundStyle(.black)
            .padding()
    }
}

 

일케!

 

@Binding을 쓰면 subView의 매개변수로 @State 값을 받을 수 있다.

 

근데 일단 오류만 해결했다고 넘어갈,, 수는,, 없지 않은가?!

 

고래서 상태 프로퍼티에 대해서 찾아보았다. 

 

 

etc-image-4

https://developer.apple.com/documentation/swiftui/state

 

State | Apple Developer Documentation

A property wrapper type that can read and write a value managed by SwiftUI.

developer.apple.com

 

상태 프로퍼티는 UIKit에서는 없는 친구들이다. 

 

흠. 근데 그건 다른 것도 다 그렇지 않나? 아니~ 골뱅이 붙여서 뭔가를 쓰는 건 전부터 있었던 것 같기도 하다.

 

@available이나 @discardableResult 같은 거 말이다.

 

근데 이 친구들을 property라고 부르지는 않았다. (명확히는 Attribute라고 불렀다!)

 

결론부터 말하자면, State property는 SwiftUI의 작동 원리와 맞닿아 있다.

 

먼저, SwiftUI는 왜 나왔을까? UIKit과의 차이는 무엇이며, 그 차이를 만든 이유는 무엇일까?

 

SwiftUI와 UIKit의 차이를 알면 나머지 질문의 답변이 쉽게 나올 것이다.

 

 

앱에서 UI는 Data에 따라서 변화해야 한다.

 

 

왜, 데이터가 변하면 뷰를 새로 그려줘야 한다든가 tableView / collectionView를 reload해 주어야 한다. 이것 또한 뷰를 새로 그리는 것은 마찬가지이다! 아무튼 기존의 UIKit에서는 데이터가 바뀐다고 자동으로 UI가 바뀌지 않았다. 따라서, 데이터가 변경되는 시점마다 뷰를 새로 그리는 코드를 추가해 주어야 했기 때문에 개발자가 신경써야 할 부분이 많았다. (버그도 많이 발생했고 말이다.)

 

 

etc-image-5
tableViewReload를 까묵어서 디버깅에 날린 시간을 아까워하는 개발자의 초상

 

 

이 부분을 보완하여 나온 것이 SwiftUI이다! SwiftUI는 Source Of Truth를 통하여 이는 데이터와 UI 간의 의존관계를 만들어 관리가 쉽도록 한다. Source Of Truth는 데이터의 일관성과 정확성을 유지하기 위한 개념으로 데이터는 오직 한 군데에만 저장된다는 뜻이다. 만약 데이터가 여러 군데에 존재할 경우 sync를 맞추기 힘들며 일관성을 유지하기가 어렵다. 따라서, SwiftUI에서는 @State property가 SSOT(Single Source Of Truth)로 존재한다.

 

etc-image-6

 

즉, 여러 뷰에서 데이터를 사용해도 그 데이터를 가지고 있는 것은 하나의 뷰이고, 특정 뷰가 가지고 있는 데이터를 참조하는 형식으로 이루어져 있다는 말이다. SwiftUI는 이런 식으로 개발자가 데이터 정합성과 일관성을 지킬 수 있는 구조를 만들 수 있도록 도와준다.

 

 

etc-image-7

 

SwiftUI의 Data Flow는 위와 같이 흘러간다.

 

오직 단방향으로, User -> (User Interactions) -> Action -> (mutating) -> Data -> (Update) -> UI(View) -> (rendering) -> User ... ... 이렇게 빙글빙글 돌아간다.

 

이 단방향으로 흐르는 데이터의 기준은 State이다. @State로 변수를 하나 생성할 때마다 View와 엮인 SOT를 하나씩 추가하게 되는 것이다. 뷰는 이 @State로 선언된 변수의 값이 바뀔까? 하고 관찰하고 있다가 바뀌면 즉시 뷰를 다시 그리게 된다. 

 

그럼 바인딩은 어디에 쓰이는데?

 

기존에...... 문제로 돌아가보자. 아방하게 이,, 이게 왜 안 되지?! 했던 이유는 상태 프로퍼티를 해당 struct가 아닌 하위 뷰에서 바로 접근하려고 해서 그랬다. 그 값을 다른 뷰에서 고대로 가져갈 수는 없다. 왜?! 그럼 데이터의 정합성과 일관성을 지킬 수 없으니까....

 

그럼 우리는 고 데이터를 그대로 가져오지 않고 슬쩍 참조만 하여 사용할 수도 있을 것이다.

 

etc-image-8
진짜참조임

 

그걸 바로! @Binding이라고 한다.

 

Binding된 value는 Derived Value라고도 부르는데, 다른 뷰에서 선언된 @State 변수를 해당하는 뷰 내에서 값을 바꾸고 싶을 때 사용한다. @Binding된 변수에 값을 보내려면 State 변수 앞에 `$`를 붙여서 사용해야 한다. 저 `$`를 붙여 사용하는 것은 PropertyWrapper ProjectedValue라는 프로퍼티를 이용하게 된다는 것을 의미하며, 같은 뷰에서는 일반 변수처럼 사용 가능하다.

 

 

:3

 

재밋다.......

상태프로퍼티 ..... @State와 @Binding을 설명하기 위해 UIKit와 SwiftUI의 다른 점, SwiftUI의 DataFlow까지 공부햇다... 

 

 

나중에 공식문서 글 좀 읽고 DataFlow WWDC영상도 봐야지.. 스유공부를제대로진짜하게댄다면......

 

 

 

 

 

 

https://developer.apple.com/documentation/swiftui/state

 

State | Apple Developer Documentation

A property wrapper type that can read and write a value managed by SwiftUI.

developer.apple.com

https://medium.com/@kyuchul2/swift-attributes-8447793333e5

 

[Swift] Attributes

Attributes

medium.com

https://velog.io/@qwer15417/SwiftUI-뷰-사이의-데이터-흐름-관리

 

[SwiftUI] 뷰 사이의 데이터 흐름 관리

ios app dev tutorials를 보고 번역한 글입니다.정보를 여러 개 복사해 놓는 것은 당신의 앱에 버그를 유발할 수 있습니다. 데이터 중구난방 버그를 피하려면 SSOT(Single Source of Truth)를 당신의 앱에 사용

velog.io

https://wlaxhrl.tistory.com/91

 

Data Flow Through SwiftUI

WWDC 2019 세션 중 하나인 Data Flow Through SwiftUI의 흐름을 따라가며 요약/메모. SwiftUI와 Combine 영상을 다시 보던 중 정리할 겸 해서 포스팅합니다. 발표 시점으로부터 시간이 흐르며 리네이밍 된 API 등

wlaxhrl.tistory.com

https://velog.io/@annapo/iOS-SwiftUI-State와-Data-Flow

 

iOS ) SwiftUI - State와 Data Flow

공식문서Control and respond to the flow of data and changes within your app’s models.번역 ) 앱 모델 내에서 데이터 흐름 및 변경 사항을 제어하고 응답합니다.SwiftUI는 UI 설계의 선언적 접근 방식을 제

velog.io

https://ios-development.tistory.com/1159

 

[iOS - SwiftUI] State, Binding 개념, 사용 방법

목차) SwiftUI의 기본 - 목차 링크 State 란? SwiftUI에 의해 관리되는 property wrapper 타입 struct ContentView: View { @State var age = 20 // Value, set: @escaping (Value, Transaction) -> Void) public static func constant(_ value: Value) ->

ios-development.tistory.com

 

책: 핵심만 골라 배우는 SwiftUI 기반의 iOS 프로그래밍

'TIL' 카테고리의 다른 글

[SeSAC] November 20, 2023  (0) 2023.11.21
[SeSAC] November 18, 2023  (0) 2023.11.19
[SeSAC] November 13 , 2023  (0) 2023.11.14
[SeSAC] November 10, 2023  (0) 2023.11.10
[SeSAC] November 6, 2023  (0) 2023.11.06