카테고리 없음

Open API를 사용해 날씨 앱 만들기2

ghnn 2025. 4. 18. 19:02

이전 글과 이어지는 내용입니다.

https://ghnn.tistory.com/65

 

Open API를 사용해 날씨 앱 만들기

완성된 앱https://openweathermap.org/ Current weather and forecast - OpenWeatherMapOpenWeather Weather forecasts, nowcasts and history in a fast and elegant wayopenweathermap.org위 사이트에서 제공하는 open API를 이용해 앱을 만들어보

ghnn.tistory.com


API 주소 알아두기

parameters가 늘긴 했지만 내가 쓸 건 똑같다.

그리고 주소도 마지막의 2.5/ 뒤에 weather가 forecast로 바뀐 거 말곤 없나보다.


구조체 생성

Forecast Weather Data의 구조는 이렇게 생겼다.

 

여기서 사용할 것은

list - main - temp

list - dt_txt

두가지이다.

 

아래와 같이 생성해줬다.

struct ForecastWeatherResult: Codable {
    let list: [ForecastWeather]
}

struct ForecastWeather: Codable {
    let main: Main
    let dtTxt: String
    
    enum CodingKeys: String, CodingKey {
        case main
        case dtTxt = "dt_txt"
    }
}

main은 이전에 Current Weather Data를 불러올 때

생성해뒀던 친구와 똑같이 생겼기에 그대로 사용해줬다.

 

 

안에 든 건 세갠데 그 중 하나만 사용할 거긴 하지만

forecast의 main에도 temp_min, temp_max가 있기에

불러오는데 오류는 없을 것 같다.

struct Main: Codable {
    let temp: Double
    let tempMin: Double
    let tempMax: Double
    
    enum CodingKeys: String, CodingKey {
        case temp
        case tempMin = "temp_min"
        case tempMax = "temp_max"
    }
}

API로 데이터를 받아올 메서드 생성

우선 이번에 받아올 데이터는 ViewController에서 바로 사용하는 것이 아니라

TableView로 가져가서 사용해야되기 때문에 정보를 담아둘 변수를 하나 생성해준다.

var dataSource: [ForecastWeather] = []

ForecastWeatherResult가 제일 큰 카테고리이지만

그 안에는 list 하나밖에 없기 때문에

list를 할당해준 ForecastWeather로 생성했다.

 

 

그리고 메서드를 생성해준다.

dataSource에는 result.list를 넣어주면 된다.

private func fetchForecastData() {
    
    let urlComponents = URLComponents(string: "https://api.openweathermap.org/data/2.5/forecast")
    urlComponents?.queryItems = self.query
    
    guard let url = urlComponents?.url else { return }
    
    fetchData(url: url) { [weak self] (result: ForecastWeatherResult?) in
        
        guard let self, let result else {
            print("Invalid forecast URL")
            return
        }
        
        DispatchQueue.main.async {
            self.dataSource = result.list
            tableView.reloadData()
        }
    }
}

tableView는 아직 안 만들었지만 데이터를 할당하고

테이블 뷰를 리로드해야 해서 일단 적어만 둔다.


TableViewCell 생성

Cell을 먼저 만들 거다.

UI를 생성하고 세팅해둔다.

import UIKit
import SnapKit

class TableViewCell: UITableViewCell {
    
    // Cell을 생성할 때 identifier에 넣을 id
    static let id = "TableViewCell"
    
    let dtLabel = UILabel()
    let tempLabel = UILabel()
    
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
        setUI()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func setUI() {
        [dtLabel, tempLabel].forEach {
            $0.backgroundColor = .white
            $0.textColor = .black
            contentView.addSubview($0)
        }
        
        dtLabel.snp.makeConstraints {
            $0.centerY.equalToSuperview()
            $0.leading.equalToSuperview().inset(10)
        }
        
        tempLabel.snp.makeConstraints {
            $0.centerY.equalToSuperview()
            $0.trailing.equalToSuperview().inset(10)
        }
    }
    
    public func configureCell(item: ForecastWeather) {
        
    }
}

public으로 선언한 configureCell은 나중에

ViewController의 UITableViewDataSource에서 사용할 메서드이다.

우선 생성만 해둔다.


TableView 생성

private let tableView = UITableView()

테이블 뷰를 하나 생성해준다.

 

그리고 configureUI() 메서드에 tableView의 세팅을 해준다.

delegate, dataSource는 아직 작성하지 않아서 오류가 나지만 일단 적어둔다.

private func configureUI() {
...
    tableView.dataSource = self
    tableView.delegate = self
    tableView.register(TableViewCell.self, forCellReuseIdentifier: TableViewCell.id)
    tableView.backgroundColor = .white
}

 

 

setUI() 메서드에 오토레이아웃도 잡아준다.

private func setUI() {

...

    [titleLabel, tempLabel, image, stackView, tableView]
        .forEach { view.addSubview($0) }

...

    tableView.snp.makeConstraints {
        $0.top.equalTo(image.snp.bottom).offset(30)
        $0.leading.trailing.equalToSuperview().inset(20)
        $0.bottom.equalToSuperview().inset(50)
    }
}

UITableView - Delegate, DataSource

extension ViewController: UITableViewDelegate {
    
}

extension ViewController: UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        dataSource.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
        guard let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.id) as? TableViewCell else { return UITableViewCell() }
        
        cell.configureCell(item: dataSource[indexPath.row])
        
        return cell
    }
}

TableViewCell에서 생성해뒀던 configureCell 메서드에 위와 같이 정보를 넘겨주면 된다.

 

 

이제 TableViewCell로 돌아가 configureCell 메서드를 작성해주자.

class TableViewCell: UITableViewCell {

...

    public func configureCell(item: ForecastWeather) {
        dtLabel.text = "\(item.dtTxt)"
        tempLabel.text = "\(Int(item.main.temp))\(ViewController.doC)"
    }
}



완성!