SwiftUI 리스트와 내비게이션 튜토리얼

3 분 소요

Car 구조체 추가하기

자동차 모델을 나타내는 구조체 선언

import SwiftUI

struct Car: Codable, Identifiable {
    var id: String
    var name: String
    
    var description: String
    var isHybrid: Bool
    
    var imageName: String
}

JSON 파일에 있는 각 필드에 대한 프로퍼티를 포함
Identifiable 프로토콜 선언 -> List 뷰에서 식별
Codable 프로토콜 선언 -> JSON 파일 읽어오기


JSON 데이터 로딩하기

JSON 파일을 읽어와서 Car 객체로 변환한 다음 배열에 넣는 작업

import UIKit
import SwiftUI

var carData: [Car] = loadJson("carData.json")

func loadJson<T: Decodable>(_ filename: String) -> T {
    let data: Data
    
    guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
    // JSON 파일의 url 주소 입력
    else {
        fatalError("\(filename) not found.")
    }
    
    do {
        data = try Data(contentsOf: file)
        // 짧은 파일을 동기적으로 읽는데 사용
    } catch {
        fatalError("Could not load \(filename): \(error)")
    }
    
    do {
        return try JSONDecoder().decode(T.self, from: data)
        // JSON 파일을 Car 객체로 디코딩
    } catch {
        fatalError("Unable to parse \(filename): \(error)")
    }
}

loadJson() 함수의 코드는 JSON 파일을 로드하는 표준 방식


데이터 저장소 추가하기

List 뷰를 최신 데이터로 유지하기 위해 사용자 인터페이스는 게시된 프로퍼티(published property) 를 포함
Observable 객체에 의존

import SwiftUI
import Combine

class CarStore : ObservableObject {
    
    @Published var cars: [Car]
    
    init (cars: [Car] = []) {
        self.cars = cars
    }
} 

초기화로 전달된 배열을 게시자로 선언


콘텐트 뷰 설계하기

import SwiftUI

struct ContentView: View {
 
    @ObservedObject var carStore : CarStore = CarStore(cars: carData)
.
.

CarStore의 인스턴스에 구독 객체(observed object) 바인딩 추가
carData 배열을 초기화 작업에 전달

    var body: some View {
        List {
            ForEach (carStore.cars) { car in
                    
                ListCell(car: car) // 반복문의 car 객체가 전달
            }
        }
    }
}
struct ListCell: View {

    var car: Car

    var body: some View {
        HStack {
            Image(car.imageName)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 100, height: 60)
            Text(car.name)
        }
    }
}

carStore 프로퍼티를 통해 배열에 접근
스크린샷 2022-06-06 오전 3 29 04


상세 뷰 설계하기

상세 뷰: 리스트의 행을 터치하면 선택한 자동차의 추가 정보가 표시

import SwiftUI

struct CarDetail: View {
    
    let selectedCar: Car
    
    var body: some View {
        Text("Hello, World")
        
    }
}

struct CarDetail_Previews: PreviewProvider {
    static var previews: some View {
        CarDetail(selectedCar: carData[0])
    }
}

Car 구조체에 대한 프로퍼티 추가
carData 배열에 첫 번째 자동차의 세부 내용이 표시

var body: some View {
    Form {
        Section(header: Text("Car Details")) {
            Image(selectedCar.imageName)
                .resizable() // 크기 조절
                .cornerRadius(12.0) // 이미지에 라운드 코너 적용
                .aspectRatio(contentMode: .fit) // 정비율 조절
                .padding()
                
            Text(selectedCar.name)
                .font(.headline)
                
            Text(selectedCar.description)
                .font(.body)
                
            HStack {
                Text("Hybrid")
                    .font(.headline)
                Spacer()
                Image(systemName: selectedCar.isHybrid ? "checkmark.circle" : "xmark.circle")
            }
        }
    }
}

Form 컨테이너: 뷰들을 그루핑하고 서로 다른 섹션으로 나눌 수 있는 컨테이너 뷰
스크린샷 2022-06-06 오전 4 04 28


리스트에 내비게이션 추가하기

사용자가 선택한 자동차의 상세 정보가 담긴 상세 화면을 표시

struct ContentView: View {
    
    @ObservedObject var carStore : CarStore = CarStore(cars: carData)
    
    var body: some View {
        NavigationView {
            List {
                ForEach (carStore.cars) { car in
                    ListCell(car: car)
                }
            }
        }
    }
}

List 뷰를 NavigationView 로 감쌈

struct ListCell: View {
    
    var car: Car
    
    var body: some View {
    
        NavigationLink(destination: CarDetail(selectedCar: car)) {
            HStack {
                Image(car.imageName)
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .frame(width: 100, height: 60)
                Text(car.name)
            }
        }
    }
}

HStack을 CarDetail 뷰를 목적지로 하는 NavigationLink 로 감쌈
선택된 Car 객체를 전달


자동차 추가하는 뷰 설계하기

리스트에 새로운 자동차 정보를 추가하는 뷰

import SwiftUI

struct AddNewCar: View {
    
    @ObservedObject var carStore : CarStore
    
    @State var isHybrid = false
    @State var name: String = ""
    @State var description: String = ""
.
.
}
    
struct AddNewCar_Previews: PreviewProvider {
    static var previews: some View {
        AddNewCar(carStore: CarStore(cars: carData))
    }
}

carStore 바인딩에 대한 참조체를 저장하는 선언부 추가
Add 버튼을 터치하면 ContentView에서 뷰로 전달

상세 정보를 사용자가 입력하게 될 TextField 뷰와 Text 뷰 추가

struct DataInput: View {
    
    var title: String
    @Binding var userInput: String
    
    var body: some View {
        VStack(alignment: HorizontalAlignment.leading) {
            Text(title)
                .font(.headline)
            TextField("Enter \(title)", text: $userInput)
                .textFieldStyle(RoundedBorderTextFieldStyle())
        }
        .padding()
    }
}

String 값과 사용자가 입력한 내용이 전달될 상태 프로퍼티 바인딩 전달
상태 프로퍼티가 전달될 경우 @Binding 프로퍼티 래퍼 사용

func addNewCar() {
    let newCar = Car(id: UUID().uuidString,
                     name: name,
                     description: description,
                     isHybrid: isHybrid,
                     imageName: "tesla_model_3" )
                     
    carStore.cars.append(newCar) // carStore 배열에 추가
}

식별자를 생성하는 UUID() 메서드를 사용하여 새로운 Car 객체 생성
이미지는 재사용
스크린샷 2022-06-06 오전 4 50 57


Add 버튼과 Edit 버튼 구현하기

리스트 컴포넌트 수정자, navigationBarTitle() navigationBarItems() 수정자 사용

var body: some View {
        
    NavigationView {
        List {
            ForEach (carStore.cars) { car in
                ListCell(car: car)
            }
        }
        .navigationBarTitle(Text("EV cars"))
        .navigationBarItems(leading: NavigationLink(destination: 
                            AddNewCar(carStore: self.carStore)) {
            Text("Add")
                .foregroundColor(.blue)
        }, trailing: EditButton())
    }
}

스크린샷 2022-06-06 오전 4 55 41

extra space 발생
스크린샷 2022-06-06 오전 4 56 19
Large Title 때문? -> Large Title 이면서 공간을 없애는 방법…


Edit 버튼 메서드 추가하기

EditButton 뷰에 의해 사용될 메서드 추가

List {
    ForEach (carStore.cars) { car in
                
        ListCell(car: car)
    }
    .onDelete(perform: deleteItems)
    .onMove(perform: moveItems)
}

리스트의 모든 열에 사용될 것이므로 리스트 셀에 적용

<deleteItems 함수>

func deleteItems(at offsets: IndexSet) {
    carStore.cars.remove(atOffsets: offsets)
}

선택된 행의 오프셋이 전달, 배열에서 해당 항목을 삭제하는 데 사용

<moveItems 함수>

func moveItems(from source: IndexSet, to destination: Int) {
    carStore.cars.move(fromOffsets: source, toOffset: destination)
}

사용자가 리스트의 행 위치를 이동하면 호출
오프셋과 함께 이동된 위치가 전달

카테고리:

업데이트: