[iOS 앱개발 초격차 패키지] Part 2. 코로나 현황판 앱 만들기

코로나 현황판 앱

Intro

[기능 설명]
→ 시도별 신규 확진자 수가 파이 차트로 표시됨
→ 도시 항목을 선택하면 상세 현황을 볼 수 있는 화면으로 이동됨

[활용할 기술들]
→ 굿바이 코로나 19 API
→ Alamofire
→ Cocoapods

Alamofire

  • Swift 기반의 HTTP 네트워킹 라이브러리
  • Alamofire의 주요 특징
    → 연결 가능한 request, response 메소드를 제공
    → URL, JSON 형태의 파라미터 인코딩을 지원
    → 파일 데이터 스트링, 멀티 파트 폼 데이터 등의 업로드 기능을 제공
    → HTTP response의 검증과 광범위한 단위 테스트 및 통합 테스트를 제공
  • URLSession 대신 Alamofire를 사용하는 이유
    → 코드의 간소화, 가독성 측면에서 도움을 주고 여러 기능을 직접 구축하지 않아도 쉽게 사용할 수 있음

굿바이 코로나19 API

https://api.corona-19.kr

CocoaPods

https://cocoapods.org

  • 애플 플랫폼에서 개발할 때 외부 라이브러리를 관리하기 쉽도록 도와주는 의존성 관리 도구
  • 설치 방법(터미널)
    → $ sudo gem install cocoapods
    → 프로젝트 경로로 이동하여 pod init을 입력
    → podfile이 생성되는데, 이 파일을 수정하여 외부 라이브러리를 사용할 수 있음
    → 아래와 같이 외부 라이브러리를 입력해준 뒤 터미널에서 pod install 명령어를 사용하여 라이브러리 설치

기능 구현(코드)

//
//  ViewController.swift
//  COVID19
//
//  Created by TAEJANIM on 2021/11/24.
//

import UIKit

import Alamofire
import Charts

class ViewController: UIViewController {
    
    @IBOutlet weak var totalCaseLabel: UILabel!
    @IBOutlet weak var newCaseLabel: UILabel!
    @IBOutlet weak var pieChartView: PieChartView!
    @IBOutlet weak var labelStackView: UIStackView!
    @IBOutlet weak var indicatorView: UIActivityIndicatorView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.indicatorView.startAnimating()
        self.fetchCovidOverview(completionHandler: { [weak self] result in
            guard let self = self else { return }
            self.indicatorView.stopAnimating()
            self.indicatorView.isHidden = true
            self.labelStackView.isHidden = false
            self.pieChartView.isHidden = false
            switch result {
            case let .success(result):
                self.configureStackView(koreaCovidOverview: result.korea)
                let covidOverviewList = self.makeCovidOverviewList(cityCovidOVerview: result)
                self.configureChartView(covidOverviewList: covidOverviewList)
            case let .failure(error):
                debugPrint("error \(error)")
            }
        })
    }
    
    func makeCovidOverviewList(
        cityCovidOVerview: CityCovidOverview
    ) -> [CovidOverview] {
        return [
            cityCovidOVerview.seoul,
            cityCovidOVerview.busan,
            cityCovidOVerview.daegu,
            cityCovidOVerview.incheon,
            cityCovidOVerview.gwangju,
            cityCovidOVerview.daejeon,
            cityCovidOVerview.ulsan,
            cityCovidOVerview.sejong,
            cityCovidOVerview.gyeonggi,
            cityCovidOVerview.chungbuk,
            cityCovidOVerview.chungnam,
            cityCovidOVerview.gyeongbuk,
            cityCovidOVerview.gyeongnam,
            cityCovidOVerview.jeju
        ]
    }
    
    func configureChartView(covidOverviewList: [CovidOverview]) {
        self.pieChartView.delegate = self
        let entries = covidOverviewList.compactMap { [weak self] overview -> PieChartDataEntry? in
            guard let self = self else { return nil }
            return PieChartDataEntry(
                value: self.removeFormatString(string: overview.newCase),
                label: overview.countryName,
                data: overview
            )
        }
        let dataSet = PieChartDataSet(entries: entries, label: "코로나 발생 현황")
        dataSet.sliceSpace = 1
        dataSet.entryLabelColor = .black
        dataSet.valueTextColor = .black
        dataSet.xValuePosition = .outsideSlice
        dataSet.valueLinePart1OffsetPercentage = 0.8
        dataSet.valueLinePart1Length = 0.2
        dataSet.valueLinePart2Length = 0.3
        
        dataSet.colors = ChartColorTemplates.vordiplom() +
        ChartColorTemplates.joyful() +
        ChartColorTemplates.liberty() +
        ChartColorTemplates.pastel() +
        ChartColorTemplates.material()
        
        self.pieChartView.data = PieChartData(dataSet: dataSet)
        self.pieChartView.spin(duration: 0.3, fromAngle: self.pieChartView.rotationAngle, toAngle: self.pieChartView.rotationAngle + 80)
    }
    
    func removeFormatString(string: String) -> Double {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return formatter.number(from: string)?.doubleValue ?? 0
    }
    
    func configureStackView(koreaCovidOverview: CovidOverview) {
        self.totalCaseLabel.text = "\(koreaCovidOverview.totalCase)명"
        self.newCaseLabel.text = "\(koreaCovidOverview.newCase)명"
    }
    
    func fetchCovidOverview(
        completionHandler: @escaping (Result<CityCovidOverview, Error>) -> Void
    ) {
        let url = "https://api.corona-19.kr/korea/country/new/"
        let param = [
            "serviceKey": "s9ntxCvVBUq83ZlJaNdpPzo4Gr2bjTh5E"
        ]
        
        AF.request(url, method: .get, parameters: param)
            .responseData(completionHandler: { response in
                switch response.result {
                case let .success(data):
                    do {
                        let decoder = JSONDecoder()
                        let result = try decoder.decode(CityCovidOverview.self, from: data)
                        completionHandler(.success(result))
                    } catch {
                        completionHandler(.failure(error))
                    }
                case let .failure(error):
                    completionHandler(.failure(error))
                }
            })
    }
    
}

extension ViewController: ChartViewDelegate {
    func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
        guard let covidDetailViewController = self.storyboard?.instantiateViewController(withIdentifier: "CovidDetailViewController") as? CovidDetailViewController else { return }
        guard let covidOverview = entry.data as? CovidOverview else { return }
        covidDetailViewController.covidOverview = covidOverview
        self.navigationController?.pushViewController(covidDetailViewController, animated: true)
    }
}
//
//  CityCovidOverview.swift
//  COVID19
//
//  Created by TAEJANIM on 2021/11/30.
//

import Foundation

struct CityCovidOverview: Codable {
    let korea: CovidOverview
    let seoul: CovidOverview
    let busan: CovidOverview
    let daegu: CovidOverview
    let incheon: CovidOverview
    let gwangju: CovidOverview
    let daejeon: CovidOverview
    let ulsan: CovidOverview
    let sejong: CovidOverview
    let gyeonggi: CovidOverview
    let gangwon: CovidOverview
    let chungbuk: CovidOverview
    let chungnam: CovidOverview
    let jeonbuk: CovidOverview
    let jeonnam: CovidOverview
    let gyeongbuk: CovidOverview
    let gyeongnam: CovidOverview
    let jeju: CovidOverview
}

struct CovidOverview: Codable {
    let countryName: String
    let newCase: String
    let totalCase: String
    let recovered: String
    let death: String
    let percentage: String
    let newCcase: String
    let newFcase: String
}
//
//  CovidDetailViewController.swift
//  COVID19
//
//  Created by TAEJANIM on 2021/11/30.
//

import UIKit

class CovidDetailViewController: UITableViewController {

    @IBOutlet weak var newCaseCell: UITableViewCell!
    @IBOutlet weak var totalCaseCell: UITableViewCell!
    @IBOutlet weak var recoveredCell: UITableViewCell!
    @IBOutlet weak var deathCell: UITableViewCell!
    @IBOutlet weak var percentageCell: UITableViewCell!
    @IBOutlet weak var overseasInflowCell: UITableViewCell!
    @IBOutlet weak var regionalOutbreakCell: UITableViewCell!
    
    var covidOverview: CovidOverview?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.configureView()
    }
    
    func configureView() {
        guard let covidOverview = self.covidOverview else { return }
        self.title = covidOverview.countryName
        self.newCaseCell.detailTextLabel?.text = "\(covidOverview.newCase)명"
        self.totalCaseCell.detailTextLabel?.text = "\(covidOverview.totalCase)명"
        self.recoveredCell.detailTextLabel?.text = "\(covidOverview.recovered)명"
        self.deathCell.detailTextLabel?.text = "\(covidOverview.death)명"
        self.percentageCell.detailTextLabel?.text = "\(covidOverview.percentage)%"
        self.overseasInflowCell.detailTextLabel?.text = "\(covidOverview.newFcase)명"
        self.regionalOutbreakCell.detailTextLabel?.text = "\(covidOverview.newCcase)명"
    }
}

Outro

  • Alamofire
    → Swift 기반의 HTTP 네트워킹 라이브러리
    → URL session에 기반한 라이브러리로, 네트워킹 작업을 단순화하고 네트워킹을 위한 다양한 메소드와 JSON 파싱 등을 제공함
    → URL session과 비교했을 때 코드의 간소함이 가독성 측면에서 도움을 주고, 여러 기능을 직접 구축하지 않고 쉽게 사용할 수 있음
    → 이번 프로젝트에서는 Alamofire의 Request 메소드를 이용하여 굿바이 코로나 API를 호출했고, response data 메소드를 체이닝하여 서버에서 응답받은 데이터를 받아올 수 있도록 구현하였음
  • Cocoapods
    → 애플 플랫폼에서 개발할 때 외부 라이브러리를 관리하는 것이 쉽도록 도와주는 의존성 관리 도구
    → Alamofire와 Charts 라이브러리를 사용하기 위해 cocoapods을 사용했음

결과물


좋은 웹페이지 즐겨찾기