-
[MVVM 연습 프로젝트][Lotto](0) - MVC로 만들어보기iOS 2021. 12. 28. 23:33
Device : 2021 M1 Macbook pro
OS : macOS Monterey 12.0.1
Xcode : Version 13.1 (13A1030d)안녕하세요! miniOS입니다.
이번에는 MVVM을 공부하면서 간단하게 적용해볼 수 있는 연습용 프로젝트를 만들어 보려고 합니다.
회차별로 로또 추첨 결과에 대해서 얻어오는 서비스를 만들어 보겠습니다.
코드는 아래 명령을 통해서 다운받을 수 있습니다.
git clone -b lotto_0 https://github.com/sseungmn/MVVM_Practice.git
준비
로또 API
https://www.dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=<#회차번호#>
Project
Info.plist를 다른 위치로 옮겼을 때에는
위 요소를 현재 Info.plist의 위치로 수정해주어야 합니다.
withoutStoryboard
처음이신 분들은 Storyboard없이 code로 UI 구현하기를 참고해주세요!
API Handing
위에 기입해놓은 로또 API를 호출해보면 결과는 아래와 같습니다.
이 JSON구조를 구조화 하기 위해서 QuickType을 이용합니다.
왼쪽 필드에 JSON결과를 붙여넣어주면 오른쪽에 구조체 코드가 나오는데, 이것이 우리의 Model이 됩니다.
위 코드를 Model파일에 넣어주면 됩니다.
이제, 코드를 통해서 API 통신을 하고, 위와 같은 결과를 받아와봅시다.
// 각각의 error에 대해서 처리하기 위해서, 간단한 세부 오류를 정의했습니다. enum APIError: Error { case invalidResponse, invalidData, noData, failed } class APIService { // 요청에 성공했을 때와 오류가 발생했을 때 각각의 값들을 전달해주기 위해서 // completion closure을 아래와 같이 선언했습니다. static func requestLotto(_ round: Int, completion: @escaping (Lotto?, APIError?) -> Void) { let url = URL(string: "https://www.dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=\(round)")! URLSession.shared.dataTask(with: url) { data, response, error in guard error == nil else { completion(nil, .failed) return } guard let response = response as? HTTPURLResponse else { completion(nil, .invalidResponse) return } // 이곳의 response는 위에서 캐스팅해준 HTTPURLResponse타입입니다. 순서를 주의! guard response.statusCode == 200 else { completion(nil, .failed) return } guard let data = data else { completion(nil, .noData) return } do { // 이곳에서 JSON으로 받은 data를 우리의 Model에 맞춰서 decoding해줍니다. let decodedData = try JSONDecoder().decode(Lotto.self, from: data) completion(decodedData, nil) } catch { completion(nil, .invalidData) } // dataTask는 기본적으로 suspend상태이기 때문에 명시적으로 resume()을 해주어야 합니다. }.resume() } }
요청이 잘 되는지 테스트 해보기 위해서 LottoViewController에 넣고 실행해보겠습니다.
override func viewDidLoad() { super.viewDidLoad() APIService.requestLotto(854) { lotto, error in print(lotto) } }
UI
당첨번호, 당첨금, 날짜. 세 가지 정보를 보여주기 위해서 간단하게 UI를 구성해보겠습니다.
LottoView
import UIKit import SnapKit class LottoView: UIView { let num1Label = UILabel() let num2Label = UILabel() let num3Label = UILabel() let num4Label = UILabel() let num5Label = UILabel() let num6Label = UILabel() let num7Label = UILabel() let dateLabel = UILabel() let moneyLabel = UILabel() let stackView: UIStackView = { let view = UIStackView() view.axis = .horizontal view.spacing = 8 view.backgroundColor = nil view.distribution = .fillEqually return view }() // code로만 View를 구성했을 때 불리는 method이다. override init(frame: CGRect) { super.init(frame: frame) backgroundColor = .white setConfig() setConstraints() } // xib파일로 View를 구성했을 때 호출되는 method이다. // coder를 통해서 xib를 nib으로 바꿔준다. required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setConfig() { [num1Label, num2Label, num3Label, num4Label, num5Label, num6Label, num7Label, dateLabel, moneyLabel].forEach { label in label.backgroundColor = .lightGray label.textAlignment = .center label.textColor = .black } } func setConstraints() { addSubview(dateLabel) dateLabel.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.center.equalToSuperview() make.height.equalTo(50) } // 포함관계를 갖는 View의 Contraints를 구성할 때에는 항상 순서에 주의해야한다. // 가장 큰 View의 Contraints를 먼저 잡아주고 점점 그 하위 View를 적용해주어야 한다. addSubview(stackView) stackView.snp.makeConstraints { make in make.leading.trailing.equalToSuperview() make.bottom.equalTo(dateLabel.snp.top).offset(-20) make.height.equalTo(50) } [num1Label, num2Label, num3Label, num4Label, num5Label, num6Label, num7Label].forEach { label in stackView.addArrangedSubview(label) } addSubview(moneyLabel) moneyLabel.snp.makeConstraints { make in make.top.equalTo(dateLabel.snp.bottom).offset(20) make.leading.trailing.equalToSuperview() make.height.equalTo(50) } } }
LottoViewController
import UIKit class LottoViewController: UIViewController { let mainView = LottoView() // 앱의 생명주기 상 viewDidLoad보다 먼저 불리는 method이다. // 이곳에서 view를 바꿔주면 기본적으로 ViewController에 보여주는 view를 바꿀 수 있다. override func loadView() { super.loadView() view = mainView } override func viewDidLoad() { super.viewDidLoad() } }
UI에 Data 보여주기
LottoViewController
에 아래 코드를 넣게 되면 API를 통해서 가져온 데이터를 View에 보여주게 됩니다.override func viewDidLoad() { super.viewDidLoad() APIService.requestLotto(854) { lotto, error in guard error == nil else { return } guard let lotto = lotto else { return } self.mainView.num1Label.text = "\(lotto.drwtNo1)" self.mainView.num2Label.text = "\(lotto.drwtNo2)" self.mainView.num3Label.text = "\(lotto.drwtNo3)" self.mainView.num4Label.text = "\(lotto.drwtNo4)" self.mainView.num5Label.text = "\(lotto.drwtNo5)" self.mainView.num6Label.text = "\(lotto.drwtNo6)" self.mainView.num7Label.text = "\(lotto.bnusNo)" self.mainView.moneyLabel.text = "\(lotto.firstWinamnt)원" self.mainView.dateLabel.text = lotto.drwNoDate } }
하지만, 예상과는 달리 오류가 발생하게 됩니다.
UI에 대한 코드를 적용할 때, mainThread에서 하지 않았기 때문에 발생한 오류입니다.
이에 대한 자세한 내용은 UIKit과 DispatchQueue.main의 관계에서 보실 수 있습니다.
LottoAPI
override func viewDidLoad() { super.viewDidLoad() APIService.requestLotto(854) { lotto, error in guard error == nil else { return } guard let lotto = lotto else { return } DispatchQueue.main.async { // 기존 코드 } } }
결과는 아래와 같이 나옵니다
'iOS' 카테고리의 다른 글
[MVVM 연습 프로젝트][Lotto](2) - 기능 추가하기 (0) 2021.12.30 [MVVM 연습 프로젝트][Lotto](1) - MVVM으로 리펙토링 (0) 2021.12.30 [Architecture] MVVM에 대해서 알아보기 (0) 2021.12.28 [맥주] 맥주 추천 어플 만들기(0) - 준비 (0) 2021.12.23 [TMDB] CollectionView 레이아웃 만들기 (0) 2021.12.23