[Swift] WidgetKit 정리

kkatal_chae·2022년 9월 19일
0

[Swift] WidgetKit

목록 보기
1/1
post-thumbnail

프로젝트에 위젯킷 추가

우선 위젯킷을 사용하기 위해서는 [ File → New → Target ] 에서 Widget Extension 을 검색해서 추가해준다.

여기서 Product Name 을 입력하고 Finish 를 누르고 Activate 시키면 기본적인 위젯이 앱에 추가된다. ( 별도의 빈 프로젝트에서는 별탈 없이 위젯이 추가되지만 기존 앱에는 제대로 추가되지 않을 수 있음… )

여기서 Include Configuration Intent 체크 박스는 이후에 설명할 인텐트에 관한 것인데 체크 박스를 선택하면 커스텀을 할 수 있는 Intent 가 생성된다고 보면 된다.

💡 WidgetKit 은 기본적으로 SwiftUI 로 만들어진다.

이제 WidgetKit 을 구성하고 있는 요소들을 살펴보자

Provider

위젯생성 시 미리보기 스냅샷, 스냅샷에 필요한 정보, 위젯을 리프레시할 때 필요한 정보 ( 리프레시 주기 ) 를 위젯으로 전달하기 위한 객체 ? 라고 볼 수 있다.

struct Provider: IntentTimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), configuration: ConfigurationIntent())
    }

    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), configuration: configuration)
        completion(entry)
    }

    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []

        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, configuration: configuration)
            entries.append(entry)
        }

        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

하나씩 살펴보면

func placeholder ( in context: Self.Context ) -> Self.Entry

위젯을 추가할 때 보이는 위젯 갤러리 상에서 보여지는 스냅샷을 만들어낼 때 위젯에 대한 정보를 전달해주기 위한 함수인 것 같다.

func getSnapshot( for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Self.Entry) -> Void )

위젯 갤러리에서 보이는 위젯 미리보기를 생성하기 위한 함수

func getTimeline(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Timeline<Self.Entry>) -> Void)

위젯이 리프레시 되는 주기에 대한 정보를 위젯으로 전달하는 함수

이 함수가 Provider 에서 가장 중요한 부분이라고 생각된다. 사람들이 핸드폰을 이용할 때 위젯이 존재하는 홈 화면이나 잠금화면을 오랫동안 보고 있지 않기 때문에 적절한 시기에 최신 정보를 가지고 있는 것이 중요하다.

이러한 이유 때문에 위젯으로 반드시 Timeline 을 넘겨주어야 한다.

위젯을 리프레시하면 시스템 리소스가 소모되고 추가적인 네트워킹 및 처리로 인해 배터리가 소모된다.

애플은 리프레시 과정이 퍼포먼스에 영향을 끼치는 것을 방지하기 위해 업데이트 횟수와 빈도를 제한한다.

위젯은 하루 단위로 쓸 수 있는 메모리가 한정되어 있으며 사용자의 일일 사용 패턴에 맞춰 재설정된다. 대략 하루에 40 - 70회가 적정하다고 보며 이를 시간으로 환산하면 15 - 60 분이다.

또한 리프레시하는 데 소요되는 시간이 있고 시스템과의 충돌과 같은 여러 가지 이유로 인해 최소 5분의 간격을 두고 리프레시하는 것을 권장한다.

getTimeline 에 보면 Date 를 넘겨줄 때 policy 라고 해서 리프레시를 어떤 시기에 진행할 것인지를 정할 수 있다.

이와 관련해서 여러 개의 앱일 때, 특정 시간에 리프레시를 하고 싶을 때 widgetCenter 를 이용해서 커스텀하는 부분은 다음 글을 참고하길 바란다.

WidgetKit (3) - WidgetCenter

[WidgetKit] TimeLineProvider와 WidgetCenter

Entry

위젯이 들고 있는 정보들을 가지고 있는 객체

struct SimpleEntry: TimelineEntry {
    let date: Date
    let configuration: ConfigurationIntent
}

TimelineEntry 프로토콜을 채택하고 있기 때문에 Date 는 반드시 가지고 있어야 한다.

하지만 relavance 는 옵셔널( ? ) 이기 때문에 반드시 가질 필요는 없다.

이외에도 위젯이 들고 있어야 하는 정보들을 이곳에서 선언해두고 사용하면 된다.

WidgetEntryView

위젯에 대한 뷰를 그리는 객체

struct widgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        Text(entry.date, style: .time)
    }
}

실제 위젯이 어떻게 그려지느냐는 이곳에서 구성한다.

Widget

위젯이 실질적으로 돌아가는 부분이다.

또한, Widget 프로토콜을 채택하고 있으며 이에 따라 body 를 반드시 구현해야 한다.

@main
struct widget: Widget {
    let kind: String = "widget"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
            widgetEntryView(entry: entry)
        }
        .configurationDisplayName("My Widget")
        .description("This is an example widget.")
				.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
    }
}

kind

위젯의 identifier 에 해당하는 부분으로 애플의 예시를 보면 com.temp.widget 이런 식으로 bundleIdentifier 처럼 설정한 모습을 볼 수 있다.

WidgetConfiguration

위의 configurationDisplayNamedescription 은 위젯 갤러리의 다음 부분이다.

supportedFamilies 를 통해 지원하는 크기를 지정할 수 있으며 그 옵션들은 다음과 같다.

세 가지 사이즈 이외에 .systemExtraLarge , .accessoryCircular 등 몇 가지가 더 있지만 아이패드, 애플워치 등에서 사용되는 위젯에 한하여 작동하는 듯하다.

또한 세 가지 사이즈는 위의 표와 같이 기기의 사이즈에 따라 유동적으로 리사이즈되며 안드로이드의 위젯과 같이 생성된 위젯의 사이즈를 리사이즈하는 것은 불가능한 것으로 보인다.

body 는 WidgetConfiguration 타입이며

  • StaticConfiguration
  • IntentConfiguration

두 가지를 선택하여 구성한다.

StaticConfiguration 은 말 그대로 정적인 사용자가 설정을 변경할 수 없다는 의미이며 주로 정보를 보여주기만 하는 지도, 활동, 캘린더 위젯이 여기에 해당한다.

IntentConfiguration 은 사용자가 의도대로 위젯의 설정을 바꿀 수 있는 설정이다.

앞서 언급한 위젯을 생성할 때 Include Configuration Intent 체크 박스가 이것에 해당하는 옵션을 묻는 것이다.

또한, 위젯을 여러 개 사용하고 싶은 경우는 다음과 같이 작성하면 됩니다.

import WidgetKit
import SwiftUI

@main
struct CustomWidgetBundle: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        FirstWidget()
        SecondtWidget()
    }
}

PreviewProvider

위젯을 추가할 때 미리보기를 제공하는 Provider, 보통은 Provider 에서 getSnapshot 을 통해서 미리보기를 제공하지만 그러지 않을 경우에 제공하는 부분인 듯하다 ( 확실하지는 않아요… )

Deep Linking

위젯을 활용할 때 가장 필요한 부분이 Deep Linking 부분이다.

기본적으로는 위젯을 누르면 연결되어 있는 앱이 실행된다.

만약 위젯을 눌렀을 때 앱의 특정한 기능이 실행되도록 하고 싶다면 Deep Linking 을 사용하면 된다.

struct widgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        ZStack {
            Text(entry.date, style: .time)
        }.widgetURL(URL(string: "url")!)
        
    }
}

func widgetURL(_ url: URL?) -> some View 을 이용하면 위젯을 눌렀을 때 연결된 앱으로 정보를 전달하여 앱의 특정 기능을 실행하도록 할 수 있다.

만약 위젯에서 여러 가지 클릭 이벤트를 커스텀하고 싶다고 하면 Link API 를 이용하면 된다.

struct widgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        
        Link(destination: URL(string: "url")!) {
            VStack {
                Text("url")
            }
        }
        Divider()
        Link(destination: URL(string: "url2")!) {
            VStack {
                Text("url2")
            }
        }
        
    }
}
💡 이 때, Link API 는 .systemSmall 에서는 사용할 수 없고, .systemMedium 부터만 사용할 수 있다는 사실에 유의하자 ( widgetURL 은 모든 사이즈에서 사용가능하다 )

참고한 블로그

[WidgetKit] 위젯만들기

WidgetKit (1) - Widget Protocol / WidgetConfiguration / EntryView

WidgetKit 정리

[WidgetKit] Deep linking (LinkAPI, widgetURL modifier)

0개의 댓글