Search

[정리] SwiftUI - 애플 도큐먼트 튜토리얼 정리(1편)

contents

Entrypoint

: 응용프로그램이 시작될 때 Program Counter는 Entry Point의 주소로 Branch 한다.
Swift의 Entry Point는 main함수
Entry Point를 표기하는 방식
1.
Attribute @main
2.
Attribute @NSApplicationMain
3.
Attribute @UIApplicationMain
4.
a main.swift file
5.
a file that contains top-level executable code.
  모두 Entry Point(main 함수)를 생성하며 OS는 이를 호출하여 프로그램을 실행한다.
main은 Entry Point의 Symbol로 사용이 된다.

@main

스위프트 5.3버전에서 구현된 범용적인 새로운 엔트리 포인트 제공 기능
이전에는 @UIApplicationMain Attribute로 엔트리포인트를 지정
@main 이 범용적인가?
단일 파일 코드, 프레임워크, 프로젝트, 커스텀 라이브러리 상관없이 동일하게 엔트리포인트를 제공해주기 때문에!
'<TYPE>' is annotated with @main and must provide a main static function of type () -> Void or () throws -> Void.
import SwiftUI @main struct SwiftUI_TutorialTest { static func main() { print("hi") } }
Swift
복사

AppDelegate

우리가 만든 프로젝트에서는 main() 를 볼 수 없습니다. UIKit 프레임워크 내부에 숨어있기 때문입니다.
AppDelegate는 프로토콜 확장을 통한 기본 구현으로 static func main() 을 제공합니다.
AppDelegate 클래스는 UIResponder와 UIApplicationDelegate 프로토콜을 채택하고 있습니다.
UIApplicationDelegate가 main()의 구현을 제공합니다. 즉, UIApplicationDelegate를 채택한 AppDelegate에 컴파일러에 추가된 unique main entry code인 AppDelegate.main()이 실행됩니다.

App

: 앱의 구조와 동작을 나타내는 타입 or 앱의 content를 나타내는 scene representing 타입.(프로토콜)
@main struct SwiftUI_TutorialsApp: App { }
Swift
복사
SwiftUI에서는 App 프로토콜이 main()의 구현을 제공해줍니다.

Scene

var body: some Scene { WindowGroup { ContentView() .environmentObject(modelData) } }
Swift
복사
시스템에서 관리하고 life cycle이 있는 앱 UI 일부
시스템은 실행중인 플랫폼에 따라 사용자에게 표시하는 방법을 결정
WindowGroup : SwiftUI가 기본적으로 제공하는 primitive scene type 중 하나
동일하게 구조화된 window들의 그룹을 표현하는 Scene
각 Window의 템플릿 역할을 합니다.
WindowGroup은 iPadOS/macOS 와 같이 여러 Window를 지원하는 플랫폼에서 여러 자식을 인스턴스화 할 수 있다.
각 Scene은 UI를 공유하지만, 모두 자신만의 독립적인 State를 가집니다.
여러 Window를 띄운다는게 뭘까!
한 Window에서 상태를 변경해도 다른 Window에 영향을 주지 않는다.
Scene의 수명주기는 실행중인 플랫폼에서 담당합니다.

View

Scene의 content를 형성. 플랫폼에서 독립적으로 표시가 가능합니다.
body라는 프로퍼티가 요구됩니다. 그리고 body는 View 프로토콜이 준수하는 인스턴스를 리턴해야합니다.
  View가 Scene를 구성(Scene의 content)하고 Scene이 모여서 App이 된다.

Property Wrappers

: Swift언어로 양방향 Model 혹은 View Model를 구현할 수 있도록 지원해준다.

State property

@State

특정 프로퍼티를 뷰의 상태(State)로 만들어준다. 즉 이 프로퍼티가 변경되면 자동으로 뷰의 데이터도 변경되고, 뷰의 데이터를 바꿔도 이 프로퍼티의 데이터도 자동으로 변경된다.
@stateprivate 프로퍼티에만 사용 가능하다.(권장)
뷰 내부에서 이외에는 사용이 불가능하기 때문에 private 으로 선언하는 것
하위 뷰나 다른 뷰에서 참조하기 위해선 @Binding 를 해야한다.
state property에 해당되는 변수 값이 변경되면 view를 다시 랜더링한다. → 항상 최신의 값을 가짐
상태 프로퍼티에 값을 저장하는 건 ‘단방향 프로세스’를 따른다.
상태가 변경되면 레이아웃에 있는 다른 뷰들도 변경된다.
@State 변수는 Heap에 할당된다.
뷰에 포인터가 있고 새로운 View가 만들어지면 포인터를 새로운 뷰로 옮겨서 힙의 같은 메모리를 가르키게 한다. → View의 상태를 저장 변경
일반적으로 struct 내의 값은 변경할 수 없다.(immutable)
State Property Wrapper를 사용해서 변경할 수 있게 해준다.
지속적으로 변형 가능한 변수를 만들 수 있다.
String, Int, Bool과 같은 간단한 타입에 사용하기 좋다.
@State private var selection: Tab = .featured var body: some View { TabView(selection: $selection) { // 자동으로 변경되는 값을 넣는 부분 CategoryHome() .tabItem { Label("Featured", systemImage: "star") } .tag(Tab.featured) LandmarkList() .tabItem { Label("List", systemImage: "list.bullet") } .tag(Tab.list) } }
Swift
복사
@State var dataToShow = \Hike.Observation.elevation
Swift
복사
얘는 뭘까?

@Binding

다른 인스턴스 소유의 @State 프로퍼티를 빌려올 때 사용한다. 상태 프로퍼티가 선언된 뷰와 그 하위 뷰에 대한 현재 값이다.
@State 되어 있는 값과 @Binding 값은 연결되기 때문에 어느 한 쪽의 값이 바뀌면 다른 한 쪽도 동일하게 바뀐다.
뷰도 이 데이터의 변경을 알아채고 역시 알아서 업데이트된다.
import SwiftUI struct FavoriteButton: View { // changes made inside this view propagate back to the data source @Binding var isSet: Bool var body: some View { Button { isSet.toggle() } label: { Label("Toggle Favorite", systemImage: isSet ? "star.fill" : "star") .labelStyle(.iconOnly) .foregroundColor(isSet ? .yellow : .gray) } } } struct FavoriteButton_Previews: PreviewProvider { static var previews: some View { FavoriteButton(isSet: .constant(true)) } }
Swift
복사
↓ 상위 뷰
HStack { Text(landmark.name) .font(.title) FavoriteButton(isSet: $modelData.landmarks[landmarkIndex].isFavorite) }
Swift
복사

Observable Property

@ObservedObject

ViewModel를 선언할 때 사용하는 프로퍼티 래퍼로 ObservableObject 프로토콜을 준수하는 타입에 사용 가능하다.
@State 의 대표적인 단점은 Value 타입에서만 사용이 가능하다는 점이다.
클래스에서는 사용이 불가능하다.
이 경우에는 observableObject 를 상속받은 클래스의 프로퍼티에 @ObservedObject 를 적용해서 비슷하게 뷰와 프로퍼티를 연결할 수 있게 해준다.
다만 클래스의 모든 프로퍼티의 변화를 추적하지 않는다.
추적을 원하는 프로퍼티는 @Published Property Wrapper를 사용해야 한다.
@Published 로 선언된 변수는 다른 뷰에서 사용하게 되면 해당 변수를 사용하는 모든 뷰가 최신 변경된 값을 반영하기 위해 다시 랜더링 된다.
final class ModelData: ObservableObject { @Published var landmarks: [Landmark] = load("landmarkData.json") @Published var profile = Profile.default var hikes: [Hike] = load("hikeData.json") var features: [Landmark] { landmarks.filter { $0.isFeatured } } var categories: [String : [Landmark]] { Dictionary( grouping: landmarks, by: { $0.category.rawValue } ) } }
Swift
복사
struct ContentView: View { @ObservedObject var data = ModelData() var body: some View { VStack { Text("Hello, \(data.features)!") .padding() } } }
Swift
복사

@StateObject

기능은 ObservedObject와 동일하지만 단점을 보완해서 iOS14에 추가된 기능 State + ObservedObject
ObservedObject와의 차이점
ObservedObject는 View가 새로 그려질 때 새로 생성될 수 있다.(View의 life cycle에 의존)
StateObject는 View가 새로 그려질 때 State처럼 새로 그려지지 않고 참조를 가지고 있어서 새로 생성되지 않는다.(View의 life cycle에 의존하지 않음)
새로 생성된다
문제점 해결

Environment Property

@EnviromentObject

클래스 오브젝트를 추적하기 위한 용도의 Property Wrapper 공유 인스턴스 형태에 적합하게 사용할 수 있다는 점
ObservableObject를 상속받은 클래스를 여러 뷰에서 @EnvironmentObject 형식으로 참조 가능
이름처럼 환경설정 등 여러 곳에서 공유될 만한 데이터를 관리하는 모델로 사용하기 좋다.
이 객체는 SwiftUI 환경에 저장되며, 뷰에서 뷰로 전달할 필요없이 모든 뷰가 접근할 수 있다.
최초 생성 참조가 시작되기 전에 오브젝트를 생성하고 environmentObject() 로 알려주어야 한다.
@main struct SwiftUI_TutorialsApp: App { @StateObject private var modelData = ModelData() var body: some Scene { WindowGroup { ContentView() .environmentObject(modelData) } } }
Swift
복사

@Environment(\.)

시스템에서 제공하는 Environment를 사용하기 위해서 사용하는 방식. 시스템에서 제공하는 Environment만 사용하는 것이 아니고 직접 앱에 필요한 변수를 추가해서 사용할 수 있다.
struct ProfileHost: View { // read or write the edit scope @Environment(\.editMode) var editMode }
Swift
복사
* 상태 프로퍼티 : 사용자 인터페이스 레이아웃 내의 뷰 상태를 저장하는 데 사용 → 현재 콘텐트 뷰에 관한 것   값이 임시적이기 때문에 해당 뷰가 사라지면 값도 없어진다. * Observable 객체 프로퍼티 : 사용자 인터페이스 밖에 있으며 앱 내의 SwiftUI뷰 구조체의 하위 뷰에만 필요한 데이터를 사용하기 위해서   ObservableObject 프로토콜을 따라야 하며, 뷰와 바인딩 될 프로퍼티는 @Published 프로퍼티 래퍼를 사용해서 선언되어야 한다. Observable 객체 프로퍼티와 바인딩 하려면 프로퍼티는 @ObservedObject 프로퍼티 래퍼를 사용해야 한다.
* Environment 객체
: 사용자 인터페이스 밖에 있으며 여러 뷰가 접근해야 하는 데이터일 경우
  @EnvironmentObject 프로퍼티 래퍼를 사용하여 SwiftUI 파일 내에 선언된다. 뷰 화면이 앱에 추가될 때 Environment 객체 또한 초기화되어야 한다.