본문 바로가기
Project/뿌대식: 부산대학교 학식 알리미

뿌대식: 개발 - 통신

by songmoro 2024. 1. 9.
728x90

화면에 데이터를 보여주기 위해서 HTTP 요청을 위한 코드를 작성합니다.

 

 

<통신 코드>

import Moya
import SwiftUI

enum API {
    case queryDatabase(_ campus: Campus)
}

extension API: TargetType {
    var baseURL: URL {
        let url = "<https://api.notion.com/v1>"
        guard let baseURL = URL(string: url) else { fatalError() }
        return baseURL
    }
    
    var path: String {
        switch self {
        case .queryDatabase(let campus):
            var databaseID: String {
                switch campus {
                case .부산:
                    "///"
                case .밀양:
                    "///"
                case .양산:
                    "///"
                }
            }
            return "/databases/\\(databaseID)/query"
        }
    }
    
    var method: Moya.Method {
        switch self {
        case .queryDatabase:
            return .post
        }
    }
    
    var task: Moya.Task {
        switch self {
        case .queryDatabase:
            return .requestPlain
        }
    }
    
    var headers: [String: String]? {
        switch self {
        default:
            return ["Content-Type": "application/json", "Notion-Version": "2022-02-22", "Authorization": "Bearer ///"]
        }
    }
}

Moya를 사용했고, 캠퍼스 별 데이터베이스를 다르게 해서 데이터를 요청합니다.

데이터베이스와 토큰 값은 임시 주석처리 했습니다.

 

 

<Response Model>

struct QueryDatabase: Codable {
    var results: [QueryProperties]
    
    struct QueryProperties: Codable {
        var properties: [String: QueryProperty]

        struct QueryProperty: Codable {
            var type: String
            var rich_text: [RichText]?
            var select: [String: String]?
            
            struct RichText: Codable {
                var plain_text: String
            }
        }
    }
}

노션 API 데이터는 JSON 형식이고, 데이터베이스는 선택, 리치텍스트 속성만 사용할 것이기 때문에 그에 맞게 구조체를 선언해줍니다.

속성 별 설명은 노션 공식 문서의 API Reference를 참조해주세요.

 

 

<enum>

class MainViewModel: ObservableObject {
    @Published var menu: [Week: [Restaurant: Meal]] = [:]
// ...
}

enum Category: String, CaseIterable, Hashable {
    case 조식, 중식, 석식
}

enum Restaurant: String, CaseIterable, Hashable {
    case 금정회관학생 = "금정회관 학생"
    case 금정회관교직원 = "금정회관 교직원"
    case 샛벌회관
    case 학생회관학생 = "학생회관 학생"
    case 진리관
    case 웅비관
    case 자유관
    // 학생회관 학생
    case 학생회관교직원 = "학생회관 교직원"
    case 비마관
    case 편의동
    case 행림관
}

struct Meal: Hashable {
    var foodByCategory: [Category: String] = [:]
}

식단은 요일, 식당, 식사 분류를 통해 구분할 것이므로 그에 관한 열거형을 선언하고, 데이터에 관한 작업을 위해 뷰 모델을 작성합니다.

CaseIterable을 사용해서 순회를 가능하게 했으며, 순서는 부산대학교 홈페이지에 나온 순서대로 매핑했습니다.

 

 

<데이터 연결>

struct MainView: View {

// ...

private var menu: some View {
        ScrollView {
            if let selectedWeekday = Week(rawValue: selectedDay) {
                ForEach(Campus(rawValue: selectedCampus)!.restaurant, id: \\.rawValue) {
                    if let restaurant = vm.menu[selectedWeekday]![$0] {
                        MenuView(restaurant: $0.rawValue, meal: restaurant)
                    }
                }
            }
        }
    }

private struct MenuView: View {
        @State private var isFavorite = false
        let restaurant: String
        let meal: Meal
        
        var body: some View {
            VStack {
                title
                card
            }
            .padding(.horizontal)
            .padding(.bottom)
        }
        
        private var title: some View {
            HStack {
                Text(restaurant)
                    .font(.headline())
                    .foregroundColor(.black100)
                
                Spacer()
                
                Button {
                    isFavorite = true
                } label: {
                    Image(systemName: "star.fill")
                        .font(.headline())
                        .foregroundColor(isFavorite ? .yellow100 : .black20)
                }
            }
        }
        
        private var card: some View {
            VStack(alignment: .leading) {
                ForEach(Category.allCases, id: \\.self) {
                    if let food = meal.foodByCategory[$0] {
                        Text("\\($0.rawValue)")
                            .font(.subhead())
                            .foregroundColor(.black100)
                            .padding(.bottom, UIScreen.getHeight(2))
                        
                        Text("\\(food)")
                            .font(.body())
                            .foregroundColor(.black100)
                            .padding(.bottom, UIScreen.getHeight(18))
                    }
                }
            }
            .padding()
            .frame(width: UIScreen.getWidth(350), alignment: .leading)
            .background {
                RoundedRectangle(cornerRadius: 12)
                    .foregroundColor(.white100)
                    .shadow(radius: 2)
            }
        }
    }
}

뷰 모델의 데이터를 뷰에 연동해줍니다.

 

 

<화면 스크린샷>

시뮬레이터에 임시 식단들이 정상적으로 나타나는 걸 확인할 수 있습니다.

 

728x90