티스토리 뷰

반응형
SMALL

Today Extension 예시

이번 포스트에서는 Today Extension올바르게 사용하는 방법에 대해 알아보도록 하겠습니다.

Today Extension은 지난 포스트에도 한번 설명한 바가 있듯이 알림 센터에 나오는 위젯이라고 생각하시면 됩니다.

Host APP을 실행하지 않고도 간단한 정보를 간편하게 사용자에게 제공해주기 위해 사용하는 요소입니다.

 

Today Extension에서도 Core Data와 API Request를 할 수 있습니다.

이 때 주의해야 할 점이 있습니다.

 

바로 Today Extension의 View Controller의 Life Cycle에 대한 이해가 중요합니다.

왜 그럴까요?

 

Today View Controller는 사용자가 홈 화면에서 가장 왼쪽 화면으로 스와이프하거나 알림센터를 내릴 때 화면에 나타납니다.

이 때 2가지의 경우가 있습니다.

 

1.  알림 센터를 벗어난 후 짧은 시간 안에 다시 알림 센터를 열어 메모리가 해제되지 않은 상태
2. 알림 센터를 벗어난 후 오랜 시간이 지나 메모리가 해제되어 새로 위젯이 생성 및 로드 될 경우

 

알림센터를 벗어난 후 짧은 시간 안에 다시 돌아올 경우

이와 같은 경우에는 다음과 같은 메소드가 순차적으로 실행됩니다.

 

viewWillAppearviewDidAppear

 

메모리가 해제되지 않아 Snapshot이 남아 있는 경우에는 새로운 데이터가 들어오기 전까지는 기존 데이터를 위젯에 표시합니다.

 

알림센터를 벗어난 후 오랜 시간 후에 돌아올 경우

 

viewDidLoadviewWillAppear → viewDidAppear → widgetPerformUpdate

 

메모리에서 해제되었기 때문에 view를 Load하는 과정이 추가됩니다.

이 경우에는 기존 스냅샷이 없기 때문에 새로 데이터를 받은 후에 위젯에 표시합니다.

 

그렇다면 Data Fetch는 언제하면 좋을까요?

Data Fetch라고 한다면, 네트워크를 통한 비동기 Load 혹은 Core Data, User Defaults 등을 통한 데이터 Load를 포함합니다.

 

viewDidAppear 혹은 viewWillAppear에서 데이터 로드를 수행한다면 어떻게 될까요?

이미 화면에 Default 값이 들어간 뷰가 나타나는 상황에서 새로운 데이터의 Fetch가 완료될 때 위젯이 갱신될 것입니다.

 

이렇게 데이터를 Fetch하면 좋지 않습니다.

위젯이 로딩되는 과정과 비동기 데이터 Fetch의 과정

그러면 viewDidLoad()에서 데이터 Fetch를 수행하면 어떨까요?

비동기로 데이터를 불러오는 상황일 경우 Today View Controller는 비동기로 데이터를 가져오는 동안 viewDidLoad를 지나 widgetPerformUpdate를 수행하고 있을지도 모릅니다.

 

그렇다면, 기존에 스냅샷으로 보여주고 있던 정보 혹은 기본 값으로 지정한 뷰가 화면에 나오고 있는 상황에서 새로운 데이터의 Fetch가 완료되게 되며 이 역시 사용자 입장에서는 위젯이 깜빡거리는 듯한 효과가 발생할 수 있습니다.

viewDidLoad에서 데이터를 불러와도 소용 없는데 그럼 어떻게 하나요!

주로 사용하는 방법은 Data를 캐싱하는 방법이 있습니다.

User Defaults 등으로 미리 데이터를 캐싱해두고 viewDidLoad에서 UserDefaults로부터 데이터를 불러오고 그 후에 데이터를 새로 Fetch 하는 것입니다.

 

다음 코드는 데이터를 받아온 후 기존 캐시된 데이터와 비교를 한 후에 변경된 경우에 view에 적용하는 코드입니다.

MVP 패턴을 따르고 있기 때문에 view.applyFavoriteView 메소드를 호출하고 있습니다.

apiRequest.favoriteListRequest(listBody: requestBody, completion: { result in
      switch result {
      case .success(let searchResult):
        let fetchedItem = searchResult.items[0]
        var isDataChanged = false
        if let originItem = self.model.favoriteLinkItem {
          if originItem.id != fetchedItem.id {
            // 값이 변경되었으므로 뷰에 새로 적용해야 함.
            isDataChanged = true
          }
        } else {
          isDataChanged = true
        }
         
        if isDataChanged {
          let item = WidgetDrawerFavoriteItem(type: .link, previewText: fetchedItem.title, thumbnailURL: fetchedItem.imageUrl, id: fetchedItem.id, drawerId: fetchedItem.drawerId, createdAt: fetchedItem.createdAt)
           
          self.view.applyFavoriteToView(item: item)
           
          // Save to UserDefaults
          if let favoriteData = try? JSONEncoder().encode(item) {
            UserDefaults.standard.set(favoriteData, forKey: "linkCache")
          }
        }
      case .failure(_):
        self.view.applyFavoriteToView(item: nil)
      }
    })

값이 변경될 경우 새로운 값을 User Default에 저장하고 새로운 값을 view에 적용하고 있습니다.

 

view controller에서는 다음과 같이 적용해볼 수 있습니다.

override func viewDidLoad() {
    super.viewDidLoad()
    self.extensionContext?.widgetLargestAvailableDisplayMode = .expanded
    self.presenter = TodayPresenter(view: self)
    
    ...
     
    // Load Usage Data
    if let usageCache = UserDefaults.standard.data(forKey: "usageData") {
      if let usageData = try? JSONDecoder().decode(DrawerHomeUsage.self, from: usageCache) {
        self.applyUsageDataToView(usage: usageData)
      }
    }
     
    // Load Favorite Data
    self.presenter.fetchCachedFavoriteData()
    
    ...
  }

viewDidLoad에서 캐싱된 데이터를 불러오는 모습을 볼 수 있습니다.

API Call을 통한 데이터 로드는 viewWillAppear 혹은 viewDidAppear에서 진행합니다.

 

위와 같은 형태로 구현할 경우 위젯은 기존 캐싱된 데이터가 나올 것이고 그러다가 혹시나 데이터가 새로 변경된 값이 Fetch된다면 해당 view만 변경될 것입니다.

추가 : Layout 설정은 어떻게 해야하나요?

Layout 설정 (간격, 길이 등) 은 viewDidLoad에서 반드시 수행해야 합니다.

다양한 크기의 Device를 지원해야 하기 때문에 기존에 Storyboard에 AutoLayout으로 적용할 경우 위젯을 처음 실행하였을 때, Storyboard 상의 사이즈가 일시적으로 나타났다가 새로 계산된 길이로 변경됩니다.

 

이렇게 변경될 경우 View가 순간적으로 이동하거나 Size가 순간적으로 변하는 등의 효과로 보일 수 있기 때문에 이 역시 주의해야 합니다.

 

StackView의 경우에는 코드 레벨에서 길이를 계산할 필요가 없습니다.

StackView의 경우 고정 프레임 속성이 존재하기 때문에 꼭 Code Level에서 간격을 계산할 필요 없이 속성만 지정해주면 자동으로 뷰가 업데이트 됩니다. (Equal Spacing, Fill Equally 등)

 

StackView 고정 프레임 속성에 대해서는 추후 포스트로 정리하겠습니다.

 

Code Level에서 계산을 하려면 정확환 Widget의 View 크기를 가져오기 위해 viewDidAppear에서 가져와야 하는데 이 경우 또 다른 깜빡이는 현상을 경험할 수 있습니다.

 

이번 포스트에서는 Today Extension의 Life Cycle을 잘 사용하여 잘못된 UX를 피할 수 있는 방법에 대해 간단하게 알아보았습니다.

 

반응형
LIST
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함