샘성의 iOS 개발 일지

날씨API 데이터를 토대로 View의 Constraints 변경하기 본문

iOS/UiKit

날씨API 데이터를 토대로 View의 Constraints 변경하기

SamusesApple 2023. 3. 25. 16:34
728x90

*목표 : 막대모양 View의 높이를 날씨 api로 받은 데이터에 따라 바뀌도록 만들기 (feat. MVC 패턴)

 

 

1. 원하는 뷰의 오토레이아웃 잡기

 

변경하고 싶은 view가 디폴트 상태일때의 오토레이아웃을 잡으면 된다.

(안그러면 데이터 받기 전에 view가 납작해서 안보이더라..)

 

높이만 바꿔줄 것이기 때문에,

leading, trailing, bottom anchor는 각각 잡고 height anchor를 변경하는 방식으로 할 것이다.

 

이렇게 기본 높이를 각각 60으로 잡았다.

 

 

 

2. 날씨 데이터를 받는 변수 세팅하기

 

UIView 파일에 날씨데이터 결과를 전달 받을 변수를 각각 만들었다

 

그리고 네트워킹을 통해 받은 '기온, 미세먼지'에 대한 정보가 바뀔 때마다 막대가 길어지도록 할 것이기 때문에,

변수의 값이 바뀔때 마다 막대 높이가 바뀌도록 didSet 함수를 세팅해놨다.

API로부터 온도, 미세먼지 데이터를 각각 Int, Int로 전달 받을 것이다.

 

 

 

 

 

3. 날씨 데이터 받기 + 변수에 받은 값 전달하기

 

(따로 네트워킹을 담당하는 WeatherDataManager을 만들어놨다)

 

WeatherDataManager의 온도 데이터를 불러오는 함수를 호출한다.

그리고 데이터 작업이 끝나면, WeatherDataManager의 tempResult라는 변수에 온도 데이터가 들어가게 된다.

 

 

@escaping 클로저를 통해 데이터가 담긴 tempResult를

(2번 과정에서) MainView위에 생성해놓은 tempResult 변수에 담을 것이다.

 

 

[weak self]는 클로저 안에서 외부 변수를 사용하기 때문에, 메모리누수를 방지하기 위해!

 

 

데이터가 소숫점 밑 3자리까지 주기에.. 

소수점 밑으로 한자리까지 반올림되어 표시하고 싶어서 round(tempResult * 10) / 10을 했다.

 

만약 두자리까지 표시되길 원한다면, round(tempResult*100)/100을 하면 된다.

 

        weatherDataManager.getTodayTemp {
            DispatchQueue.main.async { [weak self] in
            // 메인 쓰레드에서 비동기로 처리하도록 (view를 그리는 일을 시킬 것이기에 메인쓰레드에서 작업처리)
                guard let tempResult = self?.weatherDataManager.tempResult else { return }
                self?.mainView.tempResult = round(tempResult * 10) / 10
                //소숫점 밑 한자리까지로 반올림하기 
            }
        }

 

 

 

 

4. DidSet에 들어갈 함수 만들기 ( + UIView 확장)

 

이미 기본으로 잡힌 view의 오토레이아웃이 있기 때문에,

다시 view의 높이 오토레이아웃을 수정할수 있게 하는 메서드를 추가하기 위해 UIView를 확장한다.

extension UIView {
    //높이
    var heightConstraint: NSLayoutConstraint? {
        get {
            return constraints.first(where: {
                $0.firstAttribute == .height && $0.relation == .equal
            })
        }
        set { setNeedsLayout() }
    }
    //넓이
    var widthConstraint: NSLayoutConstraint? {
        get {
            return constraints.first(where: {
                $0.firstAttribute == .width && $0.relation == .equal
            })
        }
        set { setNeedsLayout() }
    }
}

(코드 출처 : https://stackoverflow.com/questions/42669554/how-to-update-the-constant-height-constraint-of-a-uiview-programmatically)

 

넓이는 필요 없기 때문에, 높이만 사용했다.

 

 

 

 이제 값이 변할 때마다 실행되도록, didSet 안에 들어올 함수를 만들어야 한다.

 

 1. 우선, tempResult가 nil일 경우를 대비해서 if let문으로 옵셔널 바인딩을 했다.

 

 2.  if let문 안에, 값에 따라 바뀔 크기의 CGFloat값을 받을 변수, tempStatus를 만들었다.

그리고 한정된 케이스를 다룰 것이기 때문에 switch문으로 5개의 조건을 만들었다.

 

     switch문을 이용해서,

     - 기온 0도 미만일 경우: 막대 높이 - 기본으로

     - 기온 0도 이상 10도 미만: 막대 높이 - 기본 + 현재온도 + 30 

      - 기온 10도 이상 18도 미만: 막대 높이 - 기본 + 현재온도 + 60

            ...(이하생략...)

 

  이런식으로 만들었다.

   (현재 온도를 더하지 않으면, 너무 정해진 높이만큼 높아지는게 딱딱?하게 느껴져서 더했다.)

 

 

 3. if let문 안에, 아까 확장한 함수를 사용하여 Main큐에 비동기로 높이 변경하도록 시켰다. (+ 애니메이션 효과를 곁들여서..

 

   화면을 그리는 일은 무조건! main큐에게 시켜버려야 한다.

 

   그리고 높이 제약이 변경되면, 막대(view)를 바로! 다시 그리도록 layoutIfNeeded() 호출했다. 

 

 

 

    private func setTempUIwithAPIData() {
        if let tempResult = tempResult {
            tempResultLabel.text = "\(String(tempResult))°C"
            var tempStatus = 0
            switch tempResult {
            case _ where tempResult < 0: tempStatus = 0
            case _ where  0 <= tempResult && tempResult < 11: tempStatus = Int(tempResult) + 30
            case _ where  11 <= tempResult && tempResult < 18 : tempStatus = Int(tempResult) + 60
            case _ where 18 <= tempResult && tempResult < 27 : tempStatus = Int(tempResult) + 90
            case _ where 27 <= tempResult : tempStatus = Int(tempResult) + 120
            default: dustResultLabel.text = "Loading"
                tempStatus = 0
            }
            // view 그리는 일, 메인큐-비동기로 처리
            DispatchQueue.main.async { [weak self] in
                MainView.animate(withDuration: 0.5) {
                    self?.tempView.heightConstraint?.constant = 60 + CGFloat(tempStatus)
                    // tempView의 layout을 바로 다시 그리도록 시키기
                    self?.tempView.layoutIfNeeded()
                }
            }
        } else {
            tempResultLabel.text = "Loading"
        }
    }

 

 

끝!

 

 

 

 

 

TIL : 네트워킹을 통해, 데이터 받아와서 ui 변경하기. (didSet 속성감시자 활용)

         오토레이아웃 제약 수정하기 + 애니메이션 효과 주기

 

 

 

 

 

 

지적은 환영 입니다!

728x90