[SwiftUI] URLSession 통신 await async JSON 데이터 로딩 처리하기

반응형

뷰 모델 생성

뷰 모델을 생성합니다. 이름은 UsersViewModel 이고 ObservableObject를 상속합니다. 다음 두 개의 속성을 @Publised 래퍼로 가지고 있습니다.

  • users : 통신의 결과 값인 User 객체의 배열
  • isLoading : 통신 중을 확인하는 Boolean 값

fetchUsers()라는 함수를 만들어서 통신 결과 값 데이터를 반환합니다.


final class UsersViewModel: ObservableObject {

    @Published var users: [User] = []
    @Published var isLoading = false

    func fethUsers() async throws {
        isLoading = true

        defer { isLoading = false }

        let (data, _) = try await URLSession.shared.data(from: URL(string: "https://jsonplaceholder.typicode.com/users")!)
        self.users = try JSONDecoder().decode([User].self, from: data)
    }
}

 

User 객체 생성

https://jsonplaceholder.typicode.com/users 에서 가져온 JSON 데이터를 구조화해서 Codable 을 상속 받는 모델 객체를 만듭니다.


struct User: Codable {
    let id: Int
    let name: String
    let username: String
    let email: String
    let address: Address
    let phone: String
    let website: String
    let company: Company
}

struct Address: Codable {
    let street: String
    let suite: String
    let city: String
    // let zipCode: Int
    let geo: Geo
}

struct Company: Codable {
    let name: String
    let catchPhrase: String
    let bs: String
}

struct Geo: Codable {
    let lat: String
    let lng: String
}


extension User {
    static var dummy: User{
        .init(
            id: 1,
            name: "John",
            username: "MR John",
            email: "john@gmail.com",
            address: .init(street: "seoul", 
                           suite: "sss",
                           city: "korea",
                           geo: .init(lat: "1.00", lng: "20.0")),
            phone: "010-2222-3334",
            website: "https://www.naver.co.kr",
            company: .init(name: "Naver",
                           catchPhrase: "LOVE",
                           bs: "AAAA"))
    }
}

 

 

사용자 정보 객체 생성

사용자 정보를 보여줄 뷰를 생성합니다. 단순히 name 이라는 문자열만 받아서 List에 들어갈 필드를 만들어줍니다. VStack 안에 Text를 추가해서 Cell 을 디자인 해줍니다.


struct UserInfoView: View {
    let name: String

    var body: some View {
        VStack {
            Text(name)
                .bold()
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding()
        .background(.gray.opacity(0.1), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
        // .listRowSeparator(.hidden)
    }
}

 

 

상세화면 생성

List의 각 셀을 눌렀을 때 상세뷰를 표현해 줍니다. 데이터를 받아오기 위해서 User 변수를 하나 선언합니다. 이 데이터는 네비게이션 링크를 눌렀을 때 UserDetailView를 생성하면서 할당 됩니다.



struct UserDetailView: View {

    let user: User

    var body: some View {
        ScrollView {
            VStack(alignment: .leading) {
                info

                Divider()

                contact

                Divider()

                company

                Divider()

                address

            }
            .frame(maxWidth: .infinity, alignment: .leading)
            .padding()
            .background(.gray.opacity(0.1), in: RoundedRectangle(cornerRadius: 10, style: .continuous))
            .padding()
            .navigationTitle("User Detail")
        }
    }

}


private extension UserDetailView {
    var info: some View {
        VStack(alignment: .leading) {
            Text("Info")
                .bold()
            Text(user.name)
            Text(user.username)
        }
    }

    var contact: some View {
        VStack(alignment: .leading) {
            Text("Centent")
                .bold()
            Text(user.website)
            Text(user.phone)
            Text(user.email)
        }
    }

    var company: some View {
        VStack(alignment: .leading) {
            Text("Centent")
                .bold()
            Text(user.company.name)
            Text(user.company.catchPhrase)
            Text(user.company.bs)
        }
    }

    var address: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Address")
                .bold()
            Text(user.address.city)
            Text(user.address.street)
            Text(user.address.suite)
        }
    }
}


 

 

컨텐츠 뷰

이제 각 뷰와 데이터를 연동해줍니다. 우선 vm 이라는 UserViewModel을 생성하고 vm 의 isLoading 변수를 통해 화면을 2개로 분리합니다. isLoading이 true 이면 로딩화면을 만들어주고 (여기서는 그냥 Color.red 로만 선언) isLoading이 false라면 NavigationView에 List를 포함하는 뷰를 그려줍니다. vm.users를 데이터로 하고 마찬가지로 UserInfoView, UserDetailView에 해당 데이터를 전달합니다.



struct ContentView: View {

    @StateObject var vm = UsersViewModel()

    var body: some View {
        ZStack {
            if vm.isLoading {
                Color.red
            } else {
                NavigationView {
                    List {
                        ForEach(vm.users, id: \.id) { user in

                            UserInfoView(name: user.name)
                                .background(
                                    NavigationLink("", destination: UserDetailView(user: user))
                                        .opacity(0)
                                )

                        }
                        .listRowSeparator(.hidden)
                    }
                    .listStyle(.plain)
                    .navigationTitle("Users")
                }
            }
        }
        .task {
            do {
                try await vm.fethUsers()
            } catch {
                print(error)
            }
        }
    }
}

 

 

 

 

반응형

댓글

Designed by JB FACTORY