iOS) KakaoQRcode 클론코딩 - Widget(1/2)
내용
- 카카오톡에서 qrcode 를 위젯으로 제공하고 있다. 물론 홈으로 가져올 수도 있다. 귀엽다… 구현해보자
😇Widget?!
Widget 은 프로토콜이다.
(히익!)
Home 화면(iOS)이나 Notification Center(macOS) 에 표시할 위젯의 구성 및 내용이다.
Overview
위젯은 바로 앱의 관련 콘텐츠를 한눈에 볼 수 있도록 표시합니다. 사용자는 개별 요구 사항에 맞게 추가, 구성 및 정렬할 수 있다. 여러 유형의 위젯을 제공할 수 있다.
위젯에는 세가지 주요 구성요소가 있다.
- configuration : 위젯이 구성할 수 있는지 여부를 결정하고, 위젯을 식별하고, SwiftUI 뷰를 정의한다.
- timeline provider : 시간이 지남에 따라 위젯의 보기를 업데이트하는 프로세스를 주도한다.
- SwiftUI View : 위젯을 보여주기 위해서 WidgetKit 을 사용한다.
앱에 widget extension 을 추가하고 최신상태로 유지하는 방법에 대해서 Creating a Widget Extension 과 Keeping a Widget Up To Date 를 각각 참조해라.
커스텀 SiriKit 의 intent definition 을 추가하여 사용자가 가장 관련성이 높은 정보를 표시하도록 커스텀하게 할 수 있다. Siri 또는 Shortcuts에 대한 지원을 이미 추가했다면 사용자 지정 가능한 위젯을 지원하는 데 순조롭게 진행되고 있는 것입니다. 자세한 내용은 Making a Configurable Widget 를 참조하십시오.
(무슨 말…? user-configurable 옵션을 제공해서 위젯을 커스텀할 수 있는 IntentConfiguration 위젯을 만드는 내용이다.)
😇 프로젝트 설정
1️⃣ Wdget Extension 추가
- 프로젝트 내에서 [File] → [New] → [Target]
- Widget Extenstion 추가
- target 의 이름을 설정해주면된다.
체크박스 체크 시 Intent Configuration 위젯을 생성한다. 내용에 대해서는 아래에서 더 알아보자
- Activate 해준다.
- 다음과 같은 폴더가 생성된다. 스위프트 파일에서 위젯에 대한 코드를 짜면 된다.
(Include Configuration Intent 체크박스 체크 시)
(Include Configuration Intent 체크박스 체크 해제 시)
.intentdefinition 파일이 없다.
2️⃣ target 를 추가했으니 build 해서 확인해보자
- 사용가능한 Widget 으로 설정되었다.
- size 별로 확인해보자. 지금은 아무런 추가 설정하지 않았기 때문에 시간이 등장한다.
- 홈에도 추가해보겠다.
- Widget 은 SwiftUI 로 코드가 짜져있다. 그래서 canvas 를 통해서 프리뷰를 볼 수 있다.
😇 시작해보자!
앞서 Widget 은 프로토콜이라고 했다. 그렇다면 분명 Widget 을 채택하는 것이 있을 것이고 Required 프로퍼티가 있을 것이다. 바로 body 가 Required 프로퍼티에 해당한다.
📌 body
Widget 의 content 와 behavior 를 나타낸다.
그렇다면 기본적으로 구현된 body 를 뜯어보자
@main
struct KakakoWidget_iOS_CloneCoding: Widget {
let kind: String = "KakakoWidget_iOS_CloneCoding"
// ✅ Required 프로퍼티인 body
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
KakakoWidget_iOS_CloneCodingEntryView(entry: entry)
}
// ✅ 타이틀
.configurationDisplayName("My Widget")
// ✅ 서브타이틀
.description("This is an example widget.")
// ✅ 위젯 크기(기본값은 세개 모두를 가진다.)
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}
아래의 사진과 비교해보면 어떤 항목에 해당하는지 알 수 있다.
- 앱 이름 : 1번 결정.
- kind : widget 의 indentity.
- configurationDisplayName : 2번 결정. 위젯을 추가하거나 편집할 때 표시되는 이름.
- description : 3번 결정. 위젯을 추가하거나 편집할 때 표시되는 설명.
- supportedFamilies : 좌측부터 .systemSmall / .systemMedium / .systemLarge
❓ some
var body: some WidgetConfiguration
아직 명확하지 않은 타입(opaque type)이라서 컴파일 과정에서 오류가 발생하게 되는데 그 오류를 없애기 위해서는 타입을 명확하게 만들어 줘야 하는데 그 역할을 해주는 것이 some 키워드이다. 라고 한다. 즉, 명확하지 않은 타입을 리턴해야하는 경우 사용한다.
참고 :
그래서 body 가 가지는 클로저의 리턴값은 IntentConfiguration 과 StaticConfiguration 모두 사용할 수 있다.
❗️StaticConfiguration & IntentConfiguration
- StaticConfiguration
: 사용자가 구성할 수 있는 옵션이 없는(no user-configurable) 위젯의 콘텐츠를 설명하는 개체.
예를 들어 아래처럼 위젯을 편집할 수 없는 경우.
- IntentConfiguration
: 사용자 지정 의도 정의를 사용하여 사용자 구성 가능한 옵션을(user-configurable) 제공하는 위젯의 콘텐츠를 설명하는 개체.
예를 들어 아래처럼 위젯 편집 할 수 있는 경우.
다음과 같이 위젯 편집해서 사용자가 구성 가능한 옵션을 제공할 수 있는 경우이다.
처음 여기에 체크박스를 체크한 경우 자동으로 IntentConfiguration 으로 설정된다.
자 이제 나머지 코드도 살펴보자
📌 IntentTimelineProvider & TimelineProvider
위의 체크박스 설정 시 user-configurable 위젯을 위한 코드로 구성된다.
- IntentTimelineProvider : user-configurable 위젯의 표시를 업데이트할 때 WidgetKit 에 조언하는 유형.
- TimelineProvider : 위젯의 표시를 업데이트할 때 WidgetKit 에 조언하는 유형.
위의 프로토콜을 채택하는 Provider 로 선언된 구조체에 대해서 알아보자.
(Include Configuration Intent 체크박스 체크해서 IntetnTimelineProvider 로 구조체가 정의되었다.)
import WidgetKit
import SwiftUI
import Intents
// ✅ Widget 의 업데이트할 시기를 WidgetKit 에게 알려주는 프로토콜.
struct Provider: IntentTimelineProvider {
// ✅ (Required)
func placeholder(in context: Context) -> SimpleEntry {
SimpleEntry(date: Date(), configuration: ConfigurationIntent())
}
// ✅ (Required)위젯의 현재시간과 상태를 보여주는 timeline entry 를 제공한다.
// ✅ 위젯을 추가할때와 같이 일시적인 상황에서 나타낼 때 호출.
// ✅ WidgetKit 이 timeline 을 요청하는 방법 첫번째(아래 확인)
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
let entry = SimpleEntry(date: Date(), configuration: configuration)
completion(entry)
}
// ✅ (Required)현재 시간 및 선택적으로 위젯을 업데이트할 미래 시간에 대한 timeline entry 배열을 제공합니다.
// ✅ 처음에 위젯을 랜더링하기 위해서 Provider 에게 timeline 을 요청할 때 호출.
// ✅ WidgetKit 이 timeline 을 요청하는 방법 두번째(아래 확인)
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()
// ✅ 1시간 간격으로 업데이트. 시스템 시간 기준이 아닌 currentDate 를 만든 시간 기준이다. (ex.현재시간 1시. entry 는 [1시,2시,3시,4시,5시])
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)
}
// ✅ policy 가 .atEnd 이기 때문에 마지막 날짜 즉, 5시가 지나면 WidgetKit 이 새 타임라인을 요청한다.
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
// timeline entry 구조체
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationIntent
}
📌 WidgetKit 이 timeline 을 요청하는 방법 2가지
1️⃣ 현재 시간과 상태를 나타내는 스냅샷 줘
위처럼 위젯을 추가할 때 이미 위젯이 그려진다. 위젯을 추가할 때와 같이 일시적인 상황에서 표시하기 위해서 snapshot 요청을 하고 그 메서드가 바로 getSnapshot(for:in:completion:)
이다.
2️⃣ 현재 시간 및 미래 시간을 포함한 timeline entry 배열 줘
다음은 WidgetKit 이 timeline provider 에게 timline 을 요청하는 과정이다.
Widget Extension 이 항상 실행되는 것은 아니기 때문에 WidgetKit 은 언제 실행해야할지 timeline 을 알아야 하기 때문에 getTimeline(for:in:completion:)
메서드로 요청한다.
getTimeline(for:in:completion:)
메서드는 다음과 같이 탈출 클로저를 가진다.
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
policy 파라미터를 살펴보자. timeline 의 TimelineReloadPolicy 를 정하는 역할이다.
- .atEnd : timeline 의 마지막 날짜가 지난 후 WidgetKit 이 새 timeline 을 요청하도록 지정.
- .after(Date) : WidgetKit이 새 timeline 요청할 미래 날짜를 지정.
- .never : 새 timeline 을 사용할 수 있을 때 앱이 WidgetKit에 알림
다음과 같이 사용될 수 있다.(다음은 애플 예제)
참고 :
WidgetKit (2) - TimelineEntry / TimelineProvider / TimelineReloadPolicy
깃허브
GitHub - 28th-SOPT-iOS-CloneCoding/MiraClone-KimHyunGyu: 🧚 아요 미라클론코딩 김현규