Widget 이란?
홈 화면이나 알림 센터에 표시할 위젯의 구성 및 콘텐츠
* ios 14부터 가능
Widget 구성 요소
- Configuration:
- 구성 가능 여부 결정: 위젯이 구성 가능한지 여부를 결정합니다.
- 위젯 식별: 각 위젯은 고유한 문자열을 통해 식별됩니다. 일반적으로 Bundle Identifier를 사용합니다.
- 콘텐츠 정의: SwiftUI를 사용하여 위젯의 콘텐츠를 표시하는 보기를 정의합니다.
- Timeline Provider:
- 업데이트 프로세스 구동: 시간이 지남에 따라 위젯 보기를 업데이트하는 프로세스를 구동합니다.
- 타임라인 제공: WidgetKit에게 위젯을 언제 업데이트할지를 나타내는 타임라인을 제공합니다.
- Widget View:
- SwiftUI를 활용한 콘텐츠 표시: 위젯을 표시하기 위해 WidgetKit에서 사용하는 SwiftUI 보기를 정의합니다.
1. Configuration
Widget의 content를 나타내는 타입
- StaticConfiguration
사용자가 구성 할 수 있는 프로퍼티(ser-configurable properties)가 없는 위젯
- IntentConfiguration
사용자가 구성 할 수 있는 프로퍼티(user-configurable properties)가 있는 위젯
ex) 사용자가 위젯 구성 요소를 편집할 수 있음
StaticConfiguration의 생성자
- kind
각 위젯은 고유한 문자열을 통해 식별됩니다. 일반적으로 Bundle Identifier를 사용합니다.
- provider
위젯을 새로고침할 타임라인을 결정하는 객체입니다.
- content
WidgetKit이 Widget을 렌더링하는데 필요한 SwiftUI View가 포함됩니다.
Widget Modifier
- configurationDisplayName
사용자가 위젯을 추가/편집 할 때 위젯에 표시되는 이름을 설정하는 메소드입니다.
- description
사용자가 위젯을 추가/편집할 때 위젯에 표시되는 설명을 설정하는 메소드입니다.
- supportedFamilies
위젯이 지원하는 크기를 설정합니다.
Widget은 4가지의 size를 가집니다.
(systemSmall, systemMedium, systemLarge, systemExtraLarge)
* systemExtraLarge는 ios 15부터 가능
2. TimelineProvider
Widget을 업데이트 할 시기를 WidgetKit에 알려주는 타입
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date())
}
func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date())
completion(entry)
}
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
var entries: [SimpleEntry] = []
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(date: entryDate)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let date: Date
}
placeholder(in:):
- placeholder는 위젯이 로드될 때 보여줄 초기 상태를 정의합니다.
- 이 함수는 시스템이 위젯의 초기 상태를 보여주기 위해 호출됩니다.
- 현재 시간을 가진 SimpleEntry를 반환합니다.
getSnapshot(in:completion:):
- getSnapshot은 현재 위젯의 상태를 정의하고, Widget Gallery에서 위젯을 미리 보여줄 때 호출됩니다.
- 이 함수는 비동기적으로 완료되는 클로저를 통해 SimpleEntry를 반환합니다.
getTimeline(in:completion:):
- getTimeline은 실제 위젯의 동작을 정의하고, 위젯의 타임라인을 구성합니다.
- 현재 시간부터 5시간 동안에 해당하는 엔트리를 1시간 간격으로 생성하여 Timeline을 반환합니다.
- Timeline은 갱신 주기와 관련된 엔트리 배열을 포함하며, .atEnd 정책을 사용하여 위젯이 더 이상 업데이트하지 않도록 설정합니다.
3. Widget View
Widget의 content를 보여주는 SwiftUI View
WidgetFamily에 따라 분기 설정 가능
struct WidgetEntryView : View {
@Environment(\.widgetFamily) private var widgetFamily
var entry: Provider.Entry
var body: some View {
switch widgetFamily {
case .systemSmall:
Text("systemSmall")
case .systemMedium:
Text("systemMedium")
case .systemLarge:
Text("systemLarge")
@unknown default:
Text("unknown")
}
}
}
앱과 Extension 데이터 공유
앱과 위젯 간에 데이터를 공유하기 위해서는 App Groups를 사용합니다. App Groups를 설정하면 앱과 Extension이 동일한 그룹에서 데이터를 공유할 수 있게 됩니다.
- App Groups 설정:
- 프로젝트 설정으로 이동하여 App Groups를 추가합니다.
- 앱과 Extension의 TARGETS에서 동일한 그룹을 설정합니다.
- UserDefaults 활용:
- 데이터를 공유할 때 UserDefaults를 활용합니다.
- App Groups를 통해 동일한 UserDefaults 인스턴스를 생성하여 데이터를 저장하고 읽습니다.
let sharedUserDefaults = UserDefaults(suiteName: "group.com.example.myapp")
sharedUserDefaults?.setValue("Shared Data", forKey: "sharedKey")
위젯 딥링크
위젯 딥링크는 사용자를 앱의 특정 화면으로 이동시키는 기능을 제공합니다.
- URL Scheme 활용:
- 앱에서 URL Scheme을 정의하여 위젯에 링크를 제공합니다.
- 위젯에서 해당 URL을 열면 앱이 실행되고 특정 화면으로 이동합니다.
// AppDelegate.swift
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
// 위젯 URL과 일치할때 로직 구현
return true
}
2. Deep Link URL 전송:
- 위젯에서 앱으로 딥링크 URL을 전송하여 특정 화면으로 이동합니다.
// Widget EntryView
.widgetURL(URL(string: "myapp://specific-screen")!)
앱에서 위젯 리프레시
앱에서 위젯을 리프레시하는 경우, WidgetKit에 새로운 타임라인 업데이트를 요청하여 위젯을 갱신합니다.
- reloadTimelines(ofKind:): 특정 종류의 모든 위젯에 대한 타임라인을 다시 로드합니다.
WidgetCenter.shared.reloadTimelines(ofKind: "myWidgetKind")
- reloadAllTimelines(): 포함하는 앱에 속해 구성된 모든 위젯의 타임라인을 다시 로드합니다.
WidgetCenter.shared.reloadAllTimelines()
Widget Xcode 15, ios 17 대응
xcode 15에서 아카이브한 앱이면 ios 17앱에서 마진 생기는 이슈 발생
아래 코드로 해결
extension WidgetConfiguration {
func disableContentMarginsIfNeeded() -> some WidgetConfiguration {
#if compiler(>=5.9) // Xcode 15
if #available(iOSApplicationExtension 15.0, *) {
return self.contentMarginsDisabled()
} else {
return self
}
#else
return self
#endif
}
}
extension View {
func widgetBackground(_ color: Color) -> some View {
#if compiler(>=5.9) // Xcode 15
if #available(iOSApplicationExtension 17.0, *) {
return containerBackground(color, for: .widget)
} else {
return background(color)
}
#else
return background(color)
#endif
}
}
[참고]
'iOS' 카테고리의 다른 글
[iOS] STT 구현하기(feat. Speech 프레임워크) (2) | 2024.03.03 |
---|---|
[iOS] iOS 캡쳐 방지 기술 (3) | 2022.11.01 |
[iOS] 클로저에서 [weak self] 알아보기 (0) | 2022.07.27 |
[iOS] APN, FCM 정리 (0) | 2022.07.25 |
[iOS] UIStackView 정리 (0) | 2022.07.12 |