안녕하세요. 개발하는 정주입니다.
오늘은 Weather Today의 기능 구현에 대해 포스팅하려고 합니다.
이번 기능 구현에서도 Clean Code에 유념하여 코드를 작성하였습니다.
목차
JSON 파싱
이번 프로젝트에서는 JSON 파싱이 기본적으로 들어갑니다.
따라서 Assets의 JSON 파일을 읽어와 파싱 해야 합니다.
이번 포스팅에서는 화면 1의 JSON 파싱만 작성해보겠습니다.
[
{"korean_name":"한국","asset_name":"kr"},
{"korean_name":"독일","asset_name":"de"},
{"korean_name":"이탈리아","asset_name":"it"},
{"korean_name":"미국","asset_name":"us"},
{"korean_name":"프랑스","asset_name":"fr"},
{"korean_name":"일본","asset_name":"jp"}
]
화면 1에서 사용되는 JSON 형식입니다. korean_name과 asset_name 두 개로 이루어졌습니다.
Struct 정의
struct Country: Codable {
let name: String
let assetName: String
var flagName: String {
return "flag_\(assetName)"
}
enum CodingKeys: String, CodingKey {
case name = "korean_name"
case assetName = "asset_name"
}
}
위 JSON Object를 받을 구조체를 정의해주었습니다.
Swift에서는 변수나 상수명에 언더바(_)를 사용하지 않기 때문에 CodingKeys를 이용해줍니다.
korean_name은 name으로 asset_name은 assetName으로 받아줍니다.
flagName은 깃발 이미지 파일 이름입니다.
리뷰어의 코멘트 덕분에 computed 프로퍼티를 이용해 Swift 스러운 코드를 짤 수 있었습니다.
JSON 파싱
이제 Country Struct를 이용해서 JSON 파싱을 해보도록 합시다.
Assets에서 JSON 파일을 읽어오는 자세한 방법은 이전 포스팅을 참고해 주세요!
2021.12.10 - [iOS/iOS 개발] - [iOS/Swift] Assets에서 JSON 파일 읽어오기
[iOS/Swift] Assets에서 JSON 파일 읽어오기
[iOS/Swift] Assets에서 JSON 파일 읽어오기 안녕하세요. 개발하는 정주입니다. 오늘은 Assets에서 JSON 파일을 읽어오는 방법에 대해 포스팅하려고 합니다. JSON을 파일로 지정해서 Asset에 넣고 사용할 때
jeong9216.tistory.com
guard let asset = NSDataAsset.init(name: fileName) else {
return
}
let jsonDecoder = JSONDecoder()
do {
self.countries = try jsonDecoder.decode([Country].self, from: asset.data)
} catch {
print(error.localizedDescription)
}
Swift의 Codable 덕분에 정말 편하게 Decoding이 가능합니다.
여기까지 Country List 설정이 완료 되었습니다.
TableView 사용하기
화면 1, 2 에서는 TableView를 사용하고 있습니다.
TableView의 공식 문서부터 보도록 하겠습니다.
https://developer.apple.com/documentation/uikit/views_and_controls/table_views
Apple Developer Documentation
developer.apple.com
TableView를 사용한 이유
"Display data in a single column of customizable rows."이라고 정의되어 있습니다.
각각 하나의 column으로 이루어진 모습이기 때문에 테이블 뷰를 이용하기 적절합니다.
프로토콜 채택
테이블뷰를 사용하기 위해서는 UITableViewDelegate, UITableViewDataSource를 채택해야 합니다.
이는 UIViewController 옆에 프로토콜 채택 코드를 작성하는 것보다
View Controller를 extension 하여 따로 구성하면 가독성을 높일 수 있습니다.
extension CountryViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.countries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: self.countryCellIdentifier, for: indexPath)
return cell
}
}
기본적인 형태는 이렇게 작성할 수 있습니다.
row의 개수는 JSON 파일에서 불러와 세팅한 country 개수가 될 것입니다.
cell은 재사용을 위해 dequeueReusableCell을 사용하였습니다.
Cell 구성하기
cell의 모습을 보도록 하겠습니다.
왼쪽에 이미지, 가운데 Label, 오른쪽에 액세서리 뷰가 있습니다.
뷰 세팅은 2편 화면 구성(2021.12.21 - [iOS/iOS 프로젝트] - [iOS/부스트 코스] WeatherToday(2) - 화면 구성)에서 다루었습니다.
이번 편에서는 왼쪽 이미지와 가운데 Label을 파싱 한 데이터로 설정해주는 법을 다루겠습니다.
let country = self.countries[indexPath.row]
cell.textLabel?.text = country.name
cell.imageView?.image = UIImage(named: country.flagName)
Label에는 나라 이름을, Image에는 깃발 이미지를 가져와서 설정해주었습니다.
이렇게 cell의 기본 View에 접근하여 데이터를 넣을 수 있습니다.
deselect 구현
iOS에서는 cell을 select 하면 선택된 상태가 유지가 됩니다. 즉, 직접 deselect를 구현해줘야 합니다.
구현 로직을 생각해보면 선택이 되면 -> 선택을 해제한다. 이므로 선택이 된다는 것을 캐치해야 합니다.
관련 메서드는 UITableViewDelegate에서 찾을 수 있었습니다.
https://developer.apple.com/documentation/uikit/uitableviewdelegate
Apple Developer Documentation
developer.apple.com
바로 didSelectRowAt입니다. 설명에 나와있듯 row가 선택되면 호출되는 메서드입니다.
해당 메서드를 정의해보겠습니다.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
row가 select가 되면 deslectRow를 호출하여 deselect를 해줍니다.
Swift는 이처럼 메서드, 인자가 문장처럼 읽혀 구글링 하기도, 코드를 작성하기도 좋습니다.
이렇게 프로토콜 채택까지 끝났습니다.
Segue 화면 이동
이번 프로젝트에서는 내비게이션 컨트롤러와 스토리보드를 이용해서 화면 이동을 했습니다.
스토리보드를 이용한다면 Segue를 이용하는 것이 좋다고 합니다.
화면이 이동하는 경로를 한눈에 볼 수 있기 때문에 해당 프로젝트처럼 화면이 적은 경우엔 굉장히 유용합니다.
스토리보드를 이용한 화면 이동 자체는 굉장히 간단합니다.
뷰를 클릭하고 control을 누른 뒤 이동할 View Controller로 드래그 & 드롭하면 됩니다.
Segue 데이터 전달
UIViewController 간의 데이터 전달은 prepare()을 이용하면 됩니다.
UIViewController 공식 문서(https://developer.apple.com/documentation/uikit/uiviewcontroller/)의 prepare()입니다.
Segue가 수행될 때 호출되는 메서드입니다.
따라서 화면이 이동할 때 동작되는 로직을 prepare 메서드에 구현하면 되는 것입니다.
저는 연습의 목적으로 두 가지 스타일로 구현을 해보았습니다.
Segue의 목적지 체크
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let nextViewController: CityViewController = segue.destination as? CityViewController else {
return
}
if let selectedIndexPath = countryTableView.indexPathForSelectedRow {
nextViewController.country = countries[selectedIndexPath.row]
}
}
segue의 destination이 CityViewController일 때만 아래 로직을 실행하도록 하였습니다.
해당 방법은 특정 View Controller로 넘어갈 때 항상 동일한 로직이라면 유용해 보입니다.
Segue identifier 체크
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "goToCityDetailsViewController", sender: indexPath.row)
tableView.deselectRow(at: indexPath, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToCityDetailsViewController" {
if let vc = segue.destination as? CityDetailsViewController,
let index = sender as? Int {
vc.city = cities[index]
}
}
}
segue의 identifier에 따라 분기 처리할 수 있도록 구현하였습니다.
identifier는 스토리보드에서 설정해주었습니다.
해당 segue가 발동할 때 prepare가 호출되고 segue의 identifier를 체크해서 로직을 구현할 수 있었습니다.
따라서 동일한 View Controller를 가더라도 버튼에 따라 동작이 다른 경우가 있는데 그런 경우에 사용하기 좋을 것 같다고 생각합니다.
마무리 잡담
오늘은 Weather Today의 주요 기능 구현에 대해 포스팅해보았습니다.
JSON, TableView, Segue를 다루는 방법에 대해 배우는 게 이번 프로젝트의 의도 같습니다.
다음 편에서는 리뷰어의 코드 리뷰에 대해 포스팅하겠습니다.
감사합니다!
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.