우선 위젯킷을 사용하기 위해서는 [ File → New → Target ] 에서 Widget Extension 을 검색해서 추가해준다.
여기서 Product Name 을 입력하고 Finish 를 누르고 Activate 시키면 기본적인 위젯이 앱에 추가된다. ( 별도의 빈 프로젝트에서는 별탈 없이 위젯이 추가되지만 기존 앱에는 제대로 추가되지 않을 수 있음… )
여기서 Include Configuration Intent
체크 박스는 이후에 설명할 인텐트에 관한 것인데 체크 박스를 선택하면 커스텀을 할 수 있는 Intent 가 생성된다고 보면 된다.
💡 WidgetKit 은 기본적으로 SwiftUI 로 만들어진다.
이제 WidgetKit 을 구성하고 있는 요소들을 살펴보자
위젯생성 시 미리보기 스냅샷, 스냅샷에 필요한 정보, 위젯을 리프레시할 때 필요한 정보 ( 리프레시 주기 ) 를 위젯으로 전달하기 위한 객체 ? 라고 볼 수 있다.
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
를 이용해서 커스텀하는 부분은 다음 글을 참고하길 바란다.
위젯이 들고 있는 정보들을 가지고 있는 객체
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationIntent
}
TimelineEntry 프로토콜을 채택하고 있기 때문에 Date 는 반드시 가지고 있어야 한다.
하지만 relavance 는 옵셔널( ? ) 이기 때문에 반드시 가질 필요는 없다.
이외에도 위젯이 들고 있어야 하는 정보들을 이곳에서 선언해두고 사용하면 된다.
위젯에 대한 뷰를 그리는 객체
struct widgetEntryView : View {
var entry: Provider.Entry
var body: some View {
Text(entry.date, style: .time)
}
}
실제 위젯이 어떻게 그려지느냐는 이곳에서 구성한다.
위젯이 실질적으로 돌아가는 부분이다.
또한, 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
위의 configurationDisplayName
과 description
은 위젯 갤러리의 다음 부분이다.
supportedFamilies
를 통해 지원하는 크기를 지정할 수 있으며 그 옵션들은 다음과 같다.
세 가지 사이즈 이외에 .systemExtraLarge
, .accessoryCircular
등 몇 가지가 더 있지만 아이패드, 애플워치 등에서 사용되는 위젯에 한하여 작동하는 듯하다.
또한 세 가지 사이즈는 위의 표와 같이 기기의 사이즈에 따라 유동적으로 리사이즈되며 안드로이드의 위젯과 같이 생성된 위젯의 사이즈를 리사이즈하는 것은 불가능한 것으로 보인다.
body 는 WidgetConfiguration 타입이며
두 가지를 선택하여 구성한다.
StaticConfiguration 은 말 그대로 정적인 사용자가 설정을 변경할 수 없다는 의미이며 주로 정보를 보여주기만 하는 지도, 활동, 캘린더 위젯이 여기에 해당한다.
IntentConfiguration 은 사용자가 의도대로 위젯의 설정을 바꿀 수 있는 설정이다.
앞서 언급한 위젯을 생성할 때 Include Configuration Intent
체크 박스가 이것에 해당하는 옵션을 묻는 것이다.
또한, 위젯을 여러 개 사용하고 싶은 경우는 다음과 같이 작성하면 됩니다.
import WidgetKit
import SwiftUI
@main
struct CustomWidgetBundle: WidgetBundle {
@WidgetBundleBuilder
var body: some Widget {
FirstWidget()
SecondtWidget()
}
}
위젯을 추가할 때 미리보기를 제공하는 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 (1) - Widget Protocol / WidgetConfiguration / EntryView