[SwiftUI] Tutorial 1. SwiftUI Essentials

DongHeon·2023년 10월 16일
0

SwiftUI

목록 보기
4/6
post-thumbnail

SwiftUI Tutorials Chapter 별로 정리하는 시간을 가져보겠습니다.

오늘은 Chapter 1. Swift Essential에 대해 간단하게 정리해 보겠습니다.

자세한 소개보다는 제가 생각했을 때 중요한 Point와 동작하지 않는 코드에 대해서만 따로 설명하겠습니다. 그 외에는 자료를 따라 하면 정상적으로 동작합니다.
<iOS 17버전 부터 동작하는 코드가 존재하기 때문에 사용이 불가능한 코드가 존재합니다.>

튜토리얼을 따라 하게 되면 위 사진처럼 랜드마크를 소개하는 앱이 완성됩니다.

Creating and combining views

중요 Point

SwiftUI는 UIKit과 다르게 Controller라는 개념이 존재하지 않습니다. 그렇기 때문에 View의 역할이 좀 더 명확하다는 느낌을 받았습니다.

SwifuUI의 Life Cycle을 따르는 앱은 App프로토콜을 준수합니다. 또한 @main을 표시해 진입점을 식별하게 됩니다.

SwiftUI View 파일을 처음 생성하게 되면 아래 코드와 같습니다.

struct ContentView: View {
    var body: some View {
     	Text("Hello, world!")
    }
}

여기서 보면 body의 타입 View 앞에 some 키워드가 있는 것을 확인할 수 있습니다.

여기서 View는 struct, class로 구현된 객체가 아닌 프로토콜입니다. some View가 의미하는 것은 View 프로토콜을 채택하는 타입이라는 뜻입니다. 왜냐면 body는 하나 이상의 화면을 반환해야 하기 때문입니다.

struct ContentView: View {
    var body: some View {
    	Text("Turtle Rock")
    		.font(.title)
            .foregroundStyle(.yellow)
            
        HStack {
            Text("안녕하세요")
            Spacer()
            Text("Hello")
       }
       .font(.subheadline)
       .foregroundStyle(.secondary)
    }
}

SwiftUI의 View는 Builder 패턴을 통해 구성됩니다. 또한 아래 HStack처럼 Stack 내부에 있는 View에 같은 속성을 부여할 수 있습니다.

동작하지 않는 코드

// Tutorial
struct MapView: View {
    var body: some View {
        Map(initialPosition: .region(region)) // iOS 17버전 부터 사용가능
    }

    private var region: MKCoordinateRegion {
        MKCoordinateRegion(
            center: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868),
            span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
        )
    }
}

// 수정
struct MapView: View {
    @State private var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 34.011_286, longitude: -116.166_868),
        span: MKCoordinateSpan(latitudeDelta: 0.2, longitudeDelta: 0.2)
    )

    var body: some View {
        Map(coordinateRegion: $region)
    }
}

Property Wrapper에 대한 설명은 나중에 따로 글을 작성하겠습니다.

Building lists and navigation

중요 Point

List초기화하는 방법은 여러 가지가 존재합니다. Tutorial에서는 2가지 방법을 사용해 List를 초기화하고 있습니다.

case 1.

struct LandmarkList: View {
    var body: some View {
        List(landmarks, id: \.id) { landmark in
            LandmarkRow(landmark: landmark)
        }
    }
}

첫 번째 방법은 id(식별자)를 이용해 key path 기반으로 행을 식별해 초기화 시키는 방법입니다.

case 2.

struct LandmarkList: View {
    var body: some View {
        List(landmarks) { landmark in
            LandmarkRow(landmark: landmark)
        }
    }
}

Identifiable 프로토콜을 채택한 타입을 통해 초기화할 수 있습니다. Identifiable 프로토콜은 안정적인 ID개념을 제공할 수 있습니다.

Handling user input

중요 Point

제가 생각하는 중요한 점이 대부분 Property Wrapper에 관한 내용이기 때문에 Property Wrapper 글을 쓰고 난 뒤 링크 남기겠습니다.

동작하지 않는 코드

// Tutorial
@Observable // iOS 17부터 사용 가능합니다.
class ModelData {
    var landmarks: [Landmark] = load("landmarkData.json")
}

// 수정
class ModelData: ObservableObject {
    @Published var landmarks: [Landmark] = load("landmarkData.json")
}

@Observable 키워드를 사용하지 않기 때문에 해당 데이터를 사용하는 코드를 수정해야 합니다.

// Tutorial
struct LandmarkList: View {
    @Environment(ModelData.self) var modelData
    @State private var showFavoritesOnly = false


    var filteredLandmarks: [Landmark] {
        landmarks.filter { landmark in
            (!showFavoritesOnly || landmark.isFavorite)
        }
    }


    var body: some View {
        NavigationSplitView {
            List {
                Toggle(isOn: $showFavoritesOnly) {
                    Text("Favorites only")
                }


                ForEach(filteredLandmarks) { landmark in
                    NavigationLink {
                        LandmarkDetail(landmark: landmark)
                    } label: {
                        LandmarkRow(landmark: landmark)
                    }
                }
            }
            .navigationTitle("Landmarks")
        } detail: {
            Text("Select a Landmark")
        }
    }
}

// 수정
struct LandmarkList: View {
    @ObservedObject var modelData: ModelData
    @State private var showFavoritesOnly = true
    
    var filteredLandmarks: [Landmark] {
        modelData.landmarks.filter { landmark in
                (!showFavoritesOnly || landmark.isFavorite)
        }
    }

    
    var body: some View {
        NavigationSplitView {
            List {
                Toggle(isOn: $showFavoritesOnly) {
                    Text("Favorites only")
                }
                
                ForEach(filteredLandmarks) { landmark in
                    NavigationLink {
                        LandmarkDetail(modelData: modelData, landmark: landmark)
                    } label: {
                        LandmarkRow(landmark: landmark)
                    }
                }
            }
            .navigationTitle("Landmarks")
        } detail: {
            Text("Select a Landmark")
        }
    }
}

@Environment 키워드는 13버전부터 사용 가능하지만 Observable 키워드와 함께 사용이 불가능하기 때문에 코드를 수정했습니다. Environment 키워드를 공식문서를 통해 확인하면 View 환경에서 값을 읽는 Property Wrapper 입니다.

오늘은 SwiftUI Tutorial Chapter 1을 정리해 봤습니다. Property Wrapper와 Chapter 1에서 사용된 Wrapper에 대해 정리해 보겠습니다.

0개의 댓글