-
날씨앱만들기 (2) UI 구성 1카테고리 없음 2024. 7. 11. 18:50
일단은 UI 구성이 잘 되었는지 확인이 필요하니까 임의의 컬러, 텍스트값을 넣어서 UI 구성을 해준다.
더보기import UIKit import SnapKit class ViewController: UIViewController { //현재 위치한 도시를 알려주는 라벨 private let titleLabel: UILabel = { let label = UILabel() label.text = "서울특별시" label.textColor = .white label.font = .boldSystemFont(ofSize: 30) return label }() //기온을 나타내는 라벨 private let tempLabel: UILabel = { let label = UILabel() label.text = "20도" label.textColor = .white label.font = .boldSystemFont(ofSize: 50) return label }() //최저기온을 나타내는 라벨 private let tempMinLabel: UILabel = { let label = UILabel() label.text = "20도" label.textColor = .white label.font = .boldSystemFont(ofSize: 20) return label }() //최고기온 나타내는 라벨 private let tempMaxLabel: UILabel = { let label = UILabel() label.text = "20도" label.textColor = .white label.font = .boldSystemFont(ofSize: 20) return label }() //최저기온과 최고기온을 담는 Horizontal Stack View private let tempStackView: UIStackView = { let stackView = UIStackView() stackView.axis = .horizontal stackView.spacing = 20 stackView.distribution = .fillEqually return stackView }() //날씨이미지를 담은 이미지뷰 private let imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit imageView.backgroundColor = .gray return imageView }() override func viewDidLoad() { super.viewDidLoad() configureUI() } private func configureUI() { view.backgroundColor = .black //각 프로퍼티 뷰 생성 [ titleLabel, tempLabel, tempStackView, imageView ].forEach { view.addSubview($0) } //tempStackView에 들어갈 최저기온과 최고기온 뷰 추가 [ tempMinLabel, tempMaxLabel ].forEach { tempStackView.addArrangedSubview($0) } //각 프로퍼티 제약 조건 titleLabel.snp.makeConstraints { $0.centerX.equalToSuperview() $0.top.equalToSuperview().offset(120) } tempLabel.snp.makeConstraints { $0.centerX.equalToSuperview() $0.top.equalTo(titleLabel.snp.bottom).offset(10) } tempStackView.snp.makeConstraints { $0.centerX.equalToSuperview() $0.top.equalTo(tempLabel.snp.bottom).offset(10) } imageView.snp.makeConstraints { $0.centerX.equalToSuperview() $0.width.height.equalTo(160) $0.top.equalTo(tempStackView.snp.bottom).offset(20) } } }
여기까지 구성한 뒤에 서버에서 불러온 데이터로 현재 위치, 현재 기온, 최저 및 최고 기온에 대해 적용해줄 수 있는 로직을 짜야 한다.
아래가 제네릭을 사용해서 url을 가져오는 가장 일반적인 메서드라고 한다.
우리가 하나의 url만 쓰는게 아니니까 하나의 타입을 지정하는 게 아니고, 재사용을 위해 일반적인 메서드를 쓰는 것 같다.
더보기// 서버 데이터를 불러오는 메서드 private func fetchData<T: Decodable>(url: URL, completion: @escaping (T?) -> Void) { let session = URLSession(configuration: .default) session.dataTask(with: URLRequest(url: url)) { data, response, error in guard let data, error == nil else { print("데이터 로드 실패") completion(nil) return } // http status code 성공 범위는 200번대 let successRange = 200..<300 //HTTPURLResponse 안에 http status code를 깔기 위해 타입 캐스팅을 해준다. if let response = response as? HTTPURLResponse, successRange.contains(response.statusCode) { // 받은 데이터가 JSON 형태일테니 Swift 형태로 디코딩해준다. guard let decodedData = try? JSONDecoder().decode(T.self, from: data) else { print("JSON 디코딩 실패") completion(nil) return } // guard let decodedData가 성공했을 경우 completion(decodedData) //성공 범위 안에 들지 못한다면(200번대) } else { print("응답 오류") completion(nil) } }.resume() // resume해야 작동한다. }
제네릭과 이스케이핑..들어본 것 같은데 ㅠㅠ
private func fetchData<T: Decodable>(url: URL, completion: @escaping (T?) -> Void) { let session = URLSession(configuration: .default) session.dataTask(with: URLRequest(url: url)) { data, response, error in guard let data, error == nil else { print("데이터 로드 실패") completion(nil) return
<T> 제네릭이라 부른다.
< T: decodable> ~ (T?) 이 줄에서 (T?)에는 어떤 타입도 들어올 수 있긴하나, 앞쪽에 어떤 T 타입이냐를 물었을 때 Decodable이라고 적어줬기 때문에 Decodable 타입만 준수한다면 어떤 타입이든 들어올 수 있다. 예를 들어 Int, Double, Codable을 채택하고 있는 구조체 등도 가능하다.
@escaping 클로저?
@escaping (T?) -> Void
보통의 코드는 함수 안에서 작성 됐을 때 함수가 끝이나면 함께 책임을 다하고 소멸한다. 하지만 escaping으로 선언한 클로저는
메서드가 끝나서 탈출하더라도, 언제든지 실행이 가능하다는 것을 의미한다.