https://developer.apple.com/tutorials/swiftui-concepts
공식 문서를 정리한 글입니다.
우선 Swift UI는 사용자 인터페이스를 쉽게 구성할 수 있도록 도와주는 선언형 프레임워크입니다. 앱의 구조는 App, Scene,View 프로토콜로 이루어져있습니다.
우선 App 파일을 살펴보면 다음과 같습니다.
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@main 속성을 적용하며 앱의 진입점을 알립니다. 이 속성은 반드시 하나만 있어야 해요.
앱 프로토콜 내부에는 body가 존재하고, 여기서는 Scene인 앱의 컨텐츠를 리턴합니다. Scene에는 사용자 인터페이스를 정의하는 뷰 계층을 포함하고 있어요. 여기서는 WindowGroup을 사용했지만 이외에도 Window, DocumentGroup,Settings가 존재합니다.
ContentView는 이미지나 텍스트로 구성된 뷰의 계층을 만드는 커스텀 뷰 입니다. 이것에 대해 더 자세히 알아봅시다!
SwiftUI에서, 씬은 앱의 UI인 뷰 계층을 포함하고 있습니다.
뷰 계층은 뷰와 연결된 다른 뷰들의 레이아웃을 정의합니다.
예를 들어 아래 코드를 보면, WindowGroup scene은 ContentView를 포함하고, ContentView는 다른 뷰들을 포함하고 있습니다.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.padding()
}
}
ContentView는 View 프로토콜을 따르는 구조입니다. 그리고 위의 MyApp과 마찬가지로 body속성을 갖게 됩니다.
VStack은 Row와 같은 역할을 입니다. VStack은 내부에 포함된 모든 뷰들을 동시에 렌더링하기 때문에, 서브 뷰가 적을 때 사용하는게 좋습니다.서브 뷰가 많아질 경우, 화면에 표시된 뷰만 렌더링하는 LazyVStack을 사용하는 것을 추천합니다.
VStack의 첫번째 서브 뷰는 이미지입니다. init(systemName:) 메소드를 이용하여 기본 이미지를 불러왔습니다. 기본 아이콘은 SF Symbols 라이브러리에 있습니다. view modifier인 imageScale(:)과 foregroundColor(:)를 이용해 이미지의 크기와 색상을 지정했습니다.
두번째 서브 뷰는 읽기 전용 텍스트입니다. 이미지와 마찬가지로 modifier를 통해 디테일을 수정할 수 있습니다.
이후 패딩을 이용하여 여백을 줍니다. 뷰 내부에 여백을 어디에 얼만큼 줄 지 정할 수 있습니다. 예를 들어 padding([.bottom, .trailing], 20)인 경우, 아래와 오른쪽에 20만큼의 여백이 생기게 됩니다.
SwiftUI는 앱의 ui를 만드는 데 도움이 되는 빌딩 블록을 제공합니다. 이중 하나는 뷰 계층이 포함된 Scene입니다. SwiftUI가 제공하는 씬에서 앱의 뷰 계층을 지정하거나 커스텀 씬을 생성할 수 있습니다. 두 가지 방법 모두 알아봅시다.
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
TabView {
ContentView()
.tabItem {
Label("Journal", systemImage: "book")
}
SettingsView()
.tabItem {
Label("Settings", systemImage: "gear")
}
}
}
}
}
이제 구조는 익숙해졌으니, 구조 설명은 생략합니다. 뷰 계층에서 루트 노드는 TabView입니다. TabView는 여러 탭을 통해 서브 뷰들을 스위칭할 수 있습니다.
루트 노드 안의 서브 뷰는 ContentView와 SettingsView가 됩니다. 둘다 커스텀 뷰! 각각의 뷰에는 tabItem(_:) modifier를 통해 탭의 이미지나 텍스트를 만들어줍니다.
아이폰이랑 맥에서도 앱을 동작하고 싶어요! 하지만 위의 코드를 보면 맥 os에는 맞지 않아보입니다. 맥에 맞게 다른 뷰 계층을 선언하는 코드를 추가해봅시다.
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
#if os(iOS)
WindowGroup {
TabView {
ContentView()
.tabItem {
Label("Journal", systemImage: "book")
}
SettingsView()
.tabItem {
Label("Settings", systemImage: "gear")
}
}
}
#elseif os(macOS)
WindowGroup {
AlternativeContentView()
}
Settings {
SettingsView()
}
#endif
}
}
이전 코드와 달라진 점은 조건문을 통해 iOS와 macOS를 구분한 뒤, macOS의 경우 WindowGroup에 또다른 뷰 계층을 정의해주었습니다.
앞에서 본 뷰 계층과는 다르게, 여기서는 루트 노드가 커스텀 뷰인 AlternativeContentView가 되었습니다.
두번째 씬인 Settings(macOS에만 존재해요!) 에서는 Mac의 앱 메뉴에서 사용할 수 있는 설정 메뉴 항목을 제공합니다.
The Settings 씬은 커스텀 뷰인 SettingView를 포함하고 있어요. 여기서는 아래와 같은 앱 설정창을 보여줍니다.
지금까지는 MyApp 구조에서 다양한 버전의 앱을 정의하는 작업을 해보았습니다. 그렇지만 코드가 너무 길어 유지관리하기 어려워지겠죠?ㅠ
이를 해결하기 위해서는 커스텀 씬을 사용해봅시다. 커스텀 씬은 다른 씬으로부터 구성할 수 있습니다.
import SwiftUI
struct MyScene: Scene {
var body: some Scene {
WindowGroup {
TabView {
ContentView()
.tabItem {
Label("Journal", systemImage: "book")
}
SettingsView()
.tabItem {
Label("Settings", systemImage: "gear")
}
}
}
}
}
iOS 기기에서 보여줄 씬을 분리하여, 커스텀 씬인 MyScene을 만들어 보았습니다. 역시 Scene 프로토콜을 따르는 구조입니다.
App 프로토콜을 따르는 구조와 마찬가지로, body가 필요해요!
내부의 WindowGroup부터는 위에서 먼저 다룬 "앱에 씬 추가하기"에서 했던 코드와 일치합니다.
import SwiftUI
struct MyAlternativeScene: Scene {
var body: some Scene {
WindowGroup {
AlternativeContentView()
}
#if os(macOS)
Settings {
SettingsView()
}
#endif
}
}
macOS를 위한 씬도 같은 방법으로 만들어 주었습니다.
body 속성은 두번째 씬인 Settings를 포함하는데요, 이 씬은 macOS에서만 가능하니 if 조건문을 통해 지정된 플랫폼인지 확인하는 작업을 거쳤습니다.
MyScene과 MyAlternativeScene를 만들어 보았습니다. "다른 뷰 계층 정의하기"에서 배운 MyApp 구조에서 커스텀씬을 이용한 구조로 코드 리팩토링을 해봅시다!
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
#if os(iOS)
MyScene()
#elseif os(macOS)
MyAlternativeScene()
#endif
}
}
MyApp 구조가 훨씬 더 간결하게 바뀌었네요!
짝짝짝~ 유지관리가 조금 더 쉬워졌습니다.