[Swift] Widget Kit

잡담

최근들어 컴퓨터 앞에 앉아 있을 시간이 거의 없어서 몇 일간 게시글을 올리지 못했네요..

이 글은 이전에 프로젝트할 때, 제가 wiki에 썼던 글인데 블로그에 남겨놓을려고 가져왔어요!

간략한 설명

자.. 여러분 widget 좀 만들어 볼까하는데, 이거 만들자고 SwiftUI 배우기가 참 그렇죠? 사실 상 UI 쪽 관련해서만 SwiftUI를 알아야하지 다른 부분에선 딱히 몰라도 되는 것 같습니다. 명확한 사실은 아니지만 ㅎㅎ 😁

widget을 만들기 위해서 필수적으로 알아야할 것들을 알려드릴게요

  1. Provider
  2. TimelineEntry
  3. EntryView

진짜 간략하게 이렇게 3가지 구조체가 있는데, 이해하기 쉽게 설명을 드리자면,

Provider는 데이터를 가공하는 구조체, TimelineEntry는 데이터 정의, EntryView는 가공된 데이터를 보여주는 View

이렇게 생각하시면 음.. 이해하기 쉬우실 것 같아요

Provider

첫 번째로 Provider입니다. 위에 설명했듯이 데이터를 가공해주는 구조체라고 말씀을 드렸는데, 처음에 프로젝트를 생성하시면 맨 위에 아래와 같이 3가지의 함수가 생성되어 있을 겁니다.

struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> TimelineEntry {
    
    }
    
    func getSnapshot(in context: Context, completion: @escaping (TimelineEntry) -> ()) {
    
    }
    
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    
    }
}
  • placeholder : 위젯이 처음 나올 때, 위젯이 대충 이렇게 생겼구나~ 라는걸 보여주기 위한 메서드
  • getSnapshot : 위젯을 추가할 때, 미리보기로 보여주기 위한 메서드
  • getTimeline : 설정한 시간 주기마다 가공된 데이터를 뷰에 전달하기 위한 메서드

이게 제가 이해한대로 설명을 적어봤는데 혹시라도 잘못된 부분이 있다면, 조언 부탁드립니다 ㅎㅎ 위에 설명대로, placeholder와 getSnapshot은 default한 값을 간단하게 보여주기 위한 메서드라고 생각하시면 될 것 같습니다. 말 그대로 약간 음.. 미리보기 느낌??

getTimeline은 widget을 몇 분, 또는 몇 시간 단위로 업데이트 할 것인지에 대해 설정할 수 있고, 어떤 데이터로 업데이트할 것인지도 설정해줄 수 있습니다.

저 같은 경우엔, 여기서 URLSession을 사용하여 API를 호출했습니다. 호출해서 얻은 response로 JSON을 파싱해서 데이터를 timelineEntry 라는 구조체에 담아서 completion으로 넘겨줍니다. 대략적인 코드는 아래와 같고, Timeline이라는 구조체를 만들어서 completion에 넘겨주는 모습입니다.

이 부분에서 이해가 잘 안될수도 있다고 생각합니다만.. UserInfoEntry가 위에서 말씀드린 timelineEntry를 채택한 구조체라고 생각하시면 될 것 같습니다.

let info = try? JSONDecoder().decode(Response<UserData>.self, from: data)
                                            
let entry = UserInfoEntry(date: date, data: info?.data)
                                            
completion(Timeline(entries: [entry], policy: .after(nextUpdate)))

TimelineEntry

자! 여러분 이제 TimelineEntry입니다. 사실상 이건 간단한 구조체인데요.. 음.. 모델이라고 생각하시면 될 것 같습니다. 초기에 프로젝트를 생성하시면, 아래와 같이 구조체가 만들어져 있을 겁니다.

struct 여러분의프로젝트이름entry: TimelineEntry {
    let date: Date
}

date는 아마 업데이트 시점을 설정하기 위해서? 필요하다고 생각합니다. 위에 말씀드렸듯이 getTimeline 메서드를 호출할 때, 업데이트 시점을 정해준다고 했죠? 그래서 필요한 정보라고 생각이 듭니다. 이제 이걸 필요한 데이터를 추가해서 커스텀하면 되는데, 생각보다 정말 간단합니다. 그냥 저기다가 원하는 데이터 타입으로 프로퍼티만 추가시켜주면 끝입니다. 👍

struct 여러분의프로젝트이름entry: TimelineEntry {
    let date: Date
    let 여려분이원하는데이터: 여러분이원하는 데이터타입
}

진짜 간단하죠? 이렇게 설정을 해놓고, provider에서 추가된 프로퍼티에 맞게 코드를 조금씩만 바꿔주면 끝입니다.

EntryView

마지막으로 EntryView입니다. 위에서 데이터타입을 정의해주고, 그런다음 가공도 해주고.. 그 다음은 이제 데이터를 뷰에 보여줘야겠죠? 이것도 마찬가지로 처음에 widget 프로젝트를 생성하면, 자동으로 생성되어 있습니다. SwiftUI를 써야한다는 걱정때문에 계속 미뤘는데, 이렇게 친절하게 다 틀을 만들어줄거라곤 생각도 못했어요 😁 아래와 같이 생성되어 있을 겁니다.

struct Project04WidgetEntryView: View {
    var entry: Provider.Entry

    var body: some View {
    }
}

진짜 다왔어요 여러분 아까 getTimeline 메서드에서 completion으로 Timeline 구조체를 만들어서 entry를 넘겨줬죠? 이게 다 저기 선언되어있는 entry라는 변수에 들어가 있습니다. 저 변수 안에 들어가있는 데이터를 뷰에다 뿌려주기만하면?? 끝입니다. 여러분 물론 뷰를 보여주는 부분에서는 SwiftUI에 대한 학습이 필요합니다만.. 여기서는 다루지 않겠습니다.

요약(FLOW)

흐름에 대해 요약을 해드리자면, 이와같은 흐름이라고 보시면 될 것 같습니다.

꿀팁

메인프로젝트에서 정의해놓은 class 사용하기!!

서버에서 받아올 데이터 타입이나 네트워크를 담당하는 객체를 이미 메인프로젝트에 만들어놨는데, widget에서는 다른 프로젝트니까 다시 파일을 생성해야하나.. 라는 생각을 저도 처음에 했습니다. 하지만!! 그냥 간단하게 재사용할 수 있더라구요.. ㅎㅎ 아주 좋습니다.

그냥 이렇게 재사용하길 원하는 파일에 가서 target Membership에 체크만 해주면 됩니다. 간단하죠?

UserDefaults widget에서 공유하는 방법

당연히 같은 앱이니까 UserDefaults가 공유되지 않을까?? 라는 희망찬 생각을 했었는데, 역시 아니였어요.. 하지만, 찾아보니 방법이 있더라구요! 유레카!!

이런 식으로 그룹을 정의해서 추가시켜준다면, 그룹끼리 UserDefault를 공유할 수 있다고 합니다. 그룹이니까 메인프로젝트와 widgetExtension 둘 모두 그룹을 동일한 이름으로 설정해줘야 합니다.

여기서 끝이 아닙니다. 그룹에서 공유하는 UserDefaults를 static 변수로 선언해줘야 합니다.

extension UserDefaults {
    static var shared: UserDefaults {
        let appGroupId = "group.namkibeom.project04"
        return UserDefaults(suiteName: appGroupId)!
    }
}

아까 설정해줬던 그룹의 이름을 그대로 사용해서 UserDefaults를 생성해주고, shared를 standard 대신 사용하면 저장된 정보가 공유됩니다.