ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 날씨앱만들기 (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으로 선언한 클로저는

    메서드가 끝나서 탈출하더라도, 언제든지 실행이 가능하다는 것을 의미한다.