[SwiftUI] 튜토리얼 2단계

chosh·2023년 1월 31일
0

JSON 파일 import

xcode 좌측 창을 네비게이션판넬이라고하는데, 거기에 import 할 파일을 원하는 경로에 드래그&드랍 한다.
그럼 창이 팝업 되는데, Copy items if needed 라는 옵션에 체크하고 finish를 눌러 import 한다.

Copy items if needed

이 옵션을 체크하면 복사본을 만들어서 import 하게 된다.(프로젝트 내에 파일이 없다면)
만약 체크하지 않았는다면,
원래 있던 경로를 참조하게 되어 프로젝트 종료시 또는 git을 통해서 올렸을때 다른사람이 pull 을 받으면 파일이 없을 수 있음
그래서 되도록이면 체크하고 사용하는것이 일반적이라고 함

Json 파일의 값을 관리할 구조체 생성(수정 필요)

  1. Swift File 을 하나 만든다 (커맨드+n -> Swift File 클릭 -> 이름 지정 및 생성)

  2. 구조체를 선언한다

    import Foundation
    
    struct Landmark: Hashable, Codable {
    
    }
  3. json에서 읽어올 키를 만들어준다 (Hashable, Codable 타입은 따로 공부해야겠다)

    import Foundation
    
    struct Landmark: Hashable, Codable {
        var id: Int
        var name: String
        var park: String
        var state: String
        var description: String
    }
  4. 외부에서 몰라도 되는 값은 private로 선언

    import Foundation
    import SwiftUI
    
    struct Landmark: Hashable, Codable {
        var id: Int
        var name: String
        var park: String
        var state: String
        var description: String
    
        // 외부에서 이미지 이름을 사용하지 않고, 이미지를 바로 사용할 때,
        private var imageName: String // imageName값은 외부에서 접근할게 아니기 때문에 private
    
        var image: Image { // 이미지 이니셜라이저로 생성한걸 가져다 쓸 예정
            Image(imageName)
        }
    }

JSON load 함수 생성

  1. Swift File 을 만든다

  2. 로드 함수를 추가한다(swift는 json 파일을 읽는 함수를 직접 만들어야 된다고 한다. 그래서 대부분 Alamofire 라는 라이브러리를 사용해서 구현한다고 하니 나중에 사용법을 알아봐야 겠다.

  3. tutorial 에서 제공하는 load 함수

    import Foundation
    
    var landmarks: [Landmark] = load("landmarkData.json") // JSON 파일을 로드 시켜서 번수에 담는다
    
    func load<T: Decodable>(_ filename: String) -> T {
        let data: Data
    
        guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
        else {
            fatalError("Couldn't find \(filename) in main bundle.")
        }
    
        do {
            data = try Data(contentsOf: file)
        } catch {
            fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
        }
    
        do {
            let decoder = JSONDecoder()
            return try decoder.decode(T.self, from: data)
        } catch {
            fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
        }
    }

파일 그룹화

파일들은 Views, Model, Resources 로 그룹화

그룹화 하는 방법

  1. 원하는 파일들을 커맨드를 눌러서 선택한다
  2. 상단 맥 상태바에서 File -> New -> Group from Selection 을 눌러서 그룹 폴더를 생성한다 (상태바 말고, 우클릭으로도 할 수 있음)

전역 변수(수정필요)

swift 파일에 var로 선언하면 다른 파일에서도 사용이 가능하다

// ModelData.swift
import Foundation

var landmarks: [Landmark] = load("landmarkData.json") // 이 값을 다른 파일에서 그냥 사용하니까 사용이 됨

func load<T: Decodable>(_ filename: String) -> T {
    let data: Data
// --생략--
// LandmarkRow.swift
// --생략--
struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow(landmark: landmarks[0]) // 여기서 그냥 이렇게 가져다 쓸수 있음
    }
}

구조체 타입(수정필요)

struct 로 선언한 구조체를 타입처럼 선언해서 사용

// LandmarkRow.swift
import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark // Landmark 구조체를 타입처럼 써줌
    
// --생략--

구조체 매개변수(수정필요)

struct 로 선언한 구조체를 다른곳에서 사용할 때 구조체 안에 선언한 변수를 매개변수로 사용할 수 있음

// LandmarkRow.swift
import SwiftUI

struct LandmarkRow: View {
    var landmark: Landmark // 이 변수를 저기 아래에서 매개변수 지정한거 처럼 사용
    
    var body: some View {
        HStack {
            landmark.image
                .resizable()
                .frame(width: 50, height:50)
            
            Text(landmark.name)
            
            Spacer()
        }
    }
}

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkRow(landmark: landmarks[0]) // 구조체를 불러서 인자에 값을 넣어주는 방식으로 사용
    }
}

preview 크기 지정

struct LandmarkRow_Previews: PreviewProvider {
    static var previews: some View {
        Group{
            LandmarkRow(landmark: landmarks[0])
            LandmarkRow(landmark: landmarks[1])
        }
        .previewLayout(.fixed(width: 300, height: 70)) // 이 메소드를 이용해서 크기를 지정해서 볼 수 있음
        
    }
}

List 생성

  1. body 안에서 List 로 묶어 주면 됨
// LandmarkList.swift
struct LandmarkList: View {
    var body: some View {
        List { // List 로 묶어주면 여러개 띄우기 가능
            LandmarkRow(landmark: landmarks[0]) // import없이 그냥 가져다가 사용 가능
            LandmarkRow(landmark: landmarks[1])
        }
    }
}

동적인 데이터 설정(문법 공부 후 수정)

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

Identifiable 프로토콜을 이용하면 id 속성을 가져다 쓸 수 있음

Landmark.swift
import Foundation
import SwiftUI
import CoreLocation

struct Landmark: Hashable, Codable, Identifiable {
    var id: Int // Identifiable 프로토콜을 지정 하기 위해서는 id속성이 있어야 됨
    var name: String
    var park: String
    var state: String
    var description: String
// --생략--

LandmarkList.swift
struct LandmarkList: View {
    var body: some View {
        List(landmarks) { landmark in // 이제 이렇게 쓸수 있음
            LandmarkRow(landmark: landmark) 
        }
    }
}

Navigation 구현

  1. navigation을 추가할 UI를 NavigationView로 감싼다
struct LandmarkList: View {
    var body: some View {
        NavigationView { // 리스트에 네이게이션 기능을 넣어줄 예정
            List(landmarks) { landmark in
                LandmarkRow(landmark: landmark)
            }
            .navigationTitle("Landmarks") // 이렇게 하면 리스트에 navigation 제목을 달아줄 수 있음(이건 navigation 된 후에 되돌아가는 버튼에 보이는 글씨이기도 함)
        }
    }
}
  1. NavigationView안에 NavigationLink를 넣어서 이동할 View를 정해주고, label을 지정해서 누를 버튼을 랜더링
struct LandmarkList: View {
    var body: some View {
        NavigationView {
            List(landmarks) { landmark in
                NavigationLink { // 여기 안에 있는 페이지(디테일뷰)로 이동
                    LandmarkDetail()
                } label: { // 여기 안에 있는 View를 클릭해서 이동
                    LandmarkRow(landmark: landmark)
                }
            }
            .navigationTitle("Landmarks")
        }
    }
}

여러 디바이스에서 보기

  1. previewDevice 메서드를 이용해서 보고 싶은 디바이스를 설정
struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        LandmarkList()
            .previewDevice(PreviewDevice(rawValue: "iPhone SE (2nd generation)")) // 이 줄을 추가해서 원하는 디바이스 미리보기를 활성화
    }
}
  1. 미리보기에 디바이스 명을 넣고 싶으면
struct LandmarkList_Previews: PreviewProvider {
    static var previews: some View {
        ForEach(["iPhone SE (2nd generation)", "iPhone XS Max"], id: \.self) { deviceName in
              LandmarkList()
                  .previewDevice(PreviewDevice(rawValue: deviceName))
                  .previewDisplayName(deviceName) // 이 줄을 추가해주면 미리보기 상단에 디바이스명 표시
          }
    }
}
profile
제가 참고하기 위해 만든 블로그라 글을 편하게 작성했습니다. 틀린거 있다면 댓글 부탁드립니다.

1개의 댓글

comment-user-thumbnail
2023년 1월 31일

튜토리얼 2단계를 하면서 궁금한 점,

  1. swift 파일 내에서 var로 선언하면 다른 파일에서도 사용이 가능한 전역 변수가 되는지?
    (ModelData 파일에서 변수에 담아준 json로드값을 다른곳에서 그냥 사용함;;)

  2. struct 로 구조체를 생성하면 타입으로 사용할 수 있는건지?
    (Landmark 파일에 선언한 구조체를 다른곳에서 타입으로 지정해서 사용하는 코드가 있음;;)

답글 달기