본문 바로가기
SwiftUI Project/SwiftUI Troubleshooting

[날씨의 i] WidgetKit 4탄! - 위젯 TimelineProvider 이해하기, 위젯 새로고침, 날씨 서버와 통신하기 (2.0 업데이트 도전~!)

by thekoon 2023. 9. 17.

WidgetKit 시리즈의 마지막입니다!

위젯... 정말.. 쉽지 않았습니다...

 

그래도 어느정도 생각한대로 앱을 구성하고, 현재 2.0 업데이트 심사도 요청한 상태라

어느정도 후련한 마음입니다!

부디 한번만에 앱이 통과되길!

 

우선 이전에 Combine으로 코드를 구성했었는데요!

앱이 데이터를 가져오고, 오류없이 동작하기는 하나

일부 화면이 새로 안 그려지는 치명적인 문제가 있었습니다..!

 

아직 왜 그런지 원인을 찾지 못했는데

아직 Combine에 대한 이해가 부족한 것 같아서 더 공부한 뒤에 다시 도전해보려 합니다.

일단 앱 업데이트를 해야하니

제게 익숙한 방식인 CompletionHandler로 리팩토링하니 다행히 잘 동작했습니다!

 

우선, 위젯에서 위치정보를 수집하고 동작하는것은 아이폰에 부담을 줄 수 있다고 생각해

최소한의 필요한 통신만 하기 위해

쿼리의 x,y 값은 원래 앱에서 위치정보를 수집할때 UserDefaults로 저장해 값을 가지고 있다가

위젯에 전달해주는 방식을 택했습니다.

 

그리고 서버에서 받아오는 데이터를 변경하고, 그리는데 필요한 데이터들을 뷰모델로 구성했습니다.

 

서버에서 요청한 데이터를 필요한 데이터만 골라 WidgetData타입으로 가공하고,

 

가공한 데이터를 뷰모델로 만들어주었습니다!

확실히 컴바인보다 코드가 길어지긴 해서

욕심나네요 컴바인!

공부를 더 해보겠습니다ㅎㅎ

 

그리고 위젯에서 가장 중요한 메서드는 

func getTimeline(in context: Context, completion: @escaping (Timeline<WeatherEntry>) -> ()) 라고 생각하는데요!

 

WidgetCenter.shared.reloadTimelines(ofKind: "com.thekoon.NotiWeather.WidgetExtension") 함수를 실행하거나,

타임라인에서 설정한 시간이 되서 위젯이 새로고침될때 이 함수가 실행됩니다!

 

저는 WidgetCenter.shared.reloadTimelines(ofKind: "com.thekoon.NotiWeather.WidgetExtension") 함수를

앱이 백그라운드로 갈때, 앱이 종료될때 실행되도록 SceneDelegate에 설정했는데요,

 

앱이 시작할때 저 함수가 실행되고, 앱이 종료되거나 백그라운드로 가면 위젯이 이미 새로고침이 되어있어 더 깔끔해보이는데

저는 앱에서 위치좌표가 업데이트되고, 그 위치좌표를 위젯에서 활용하다 보니앱을 종료하거나 백그라운드로 갈때 새로고침을 하도록 설정했습니다.앱의 업데이트되는 데이터가 필요없는 위젯을 만드신다면 앱이 실행할때 새로고침하는것이 더 좋을 것 같네요.

 

그리고 Timeline을 만들면서 고생을 많이 했는데요..!

이건 애플탓도 조금 있습니다?!

처음 WidgetKit을 만들면 자동으로 초기 코드를 생성해주는데요, 이것 때문에 오히려 조금 헷갈렸습니다.

let timeline = Timeline(entries: entries, policy: .atEnd) / 여기서 .atEnd로 표시되면 앞에 0,1,2,3의 엔트리가 각각

let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! / 1시간 간격으로 저장됩니다.

 

이렇게 저장한 entry를 completion(timeline)으로 보내서 설정한 시간에 entry가 동작하게 됩니다.

 

앞서 컴바인과 함께 이 함수를 구성했었는데 업데이트가 잘 안되었던 원인을 곰곰히 생각해보니

제 앱은 서버와 통신한 뒤에 가져온 최신 데이터를 그려줘야 하는데

이 방식으로 하면 entry를 만드는 시점에 있는 데이터가 4시간동안 유지되어서

앱이 새로 그려지지 않지 않았나..라고 생각해봅니다.

 

https://developer.apple.com/documentation/widgetkit/timelinereloadpolicy

 

TimelineReloadPolicy | Apple Developer Documentation

A type that indicates the earliest date WidgetKit requests a new timeline from the widget’s provider.

developer.apple.com

그래서 공식문서에 있는 .after() 방식으로  변경하고

 

테스트를 위해 

let nextRefresh = Calendar.current.date(byAdding: .second, value: 10, to: currentDate)!

let timeline = Timeline(entries: [entry], policy: .after(nextRefresh)) 로 설정해

10초 뒤마다 위젯을 자동으로 그려주는지 확인해보았는데

왜..동작을 안하는 거에요ㅠㅠ

 

참고로 위의 let timeline = Timeline(entries: entries, policy: .atEnd) .atEnd는

1초 주기의 업데이트도 즉각즉각 잘 됩니다!

 

그래서 .after(nextRefresh)가 동작 안하는 줄 알고

시뮬레이터에 아이폰을 꽂은채로

.atEnd 방식으로 업데이트를 어떻게 해주지..고민하고 있는데

갑자기 5분 뒤에 뜬금없이 업데이트가 되는 거에요?

그래서 찾아보니 iOS에서 너무 과도한 위젯 업데이트는 리소스 낭비를 하게되니

자기가 알아서 적절하게 새로고침을 한다고 합니다.

 

대신 데이터통신을 하지 않으시는 분들은

.atEnd 방식으로 하시면 원하시는 주기마다 정확히 위젯이 동작하기때문에 더 좋은 방식같아요!

 

날씨앱 특성상 API가 업데이트되는 주기도 있는 편이고,

위젯도 즉각즉각 테스트하고 확인하기가 어려워서 이번 업데이트 테스트를 하는데 평소보다 시간이 더 많이 걸린 것 같아요.

 

그리고 마지막으로 서버와 통신을 하니

통신을 실패했을때를 대비해 재요청 함수도 만들었습니다.

전에 만든 함수는 retryCount가 따로 없이 일정 시간이 지나면 Alert이 뜨면서

앱을 종료하거나 재시도할  수 있게 만들었었는데요

 

위젯은 홈화면에 있다보니 따로 Alert을 만들기 이상하기도 하고,

기상청 서버가 은근 응답 안해주는 경우도 있어서

응답이 안온다고 응답이 올 때까지 계속 요청하면 아이폰에 무리가 갈 것 같았습니다.

그래서 기존의 재요청 코드에서 자체적으로 10번 재요청하고, 그래도 안되면 앱을 종료하는 방향으로 수정했습니다.

테스트해보니 잘 작동하네요!

 

이렇게 위젯을 마무리하고, 앱스토어에 심사 제출했습니다!

이 정도 고생이면 판 올림이 맞는 것 같아서

처음으로 2.0 메이저 업데이트를 하게 되었네요!

 

새로운 기술을 시도해서 만들땐 어려웠지만,

고민을 하고, 이것저것 배우면서 만들다 보니

새로운 기술에 대해 더 깊게 이해하게되고

뿌듯함도 얻는 것 같아서 좋습니다!!

 

부디 한번만에 통과해서 다음 글은 2.0 업데이트 후기로 찾아오고 싶네요!

화이팅 화이팅!!