Published on

πŸ“± iOS - WidgetKit

Authors
  • avatar
    Name
    이창쀀
    Twitter

WidgetKit

WWDC의 iOS νŒŒνŠΈμ—μ„œ κ½€ 크게 μ†Œκ°œλœ WidgetKit이 μžˆμ—ˆμ£ ..

Widget이 이제 μ• λ‹ˆλ©”μ΄μ…˜μ„ κ°–κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

λ‹€λ₯Έ ν”Œλž«νΌμœΌλ‘œμ˜ ν™•μž₯은 λ€μ΄κ΅¬μš”.

그런데 μ „ κ·Έ μ „μ˜ λ‚΄μš©λ„ 아직 λͺ»λ΄€κ±°λ“ μš”..

κ·Έλž˜μ„œ μ§€κΈˆ μ‹œμž‘ν•©λ‹ˆλ‹€.

WidgetKit!

ν•˜μ§€λ§Œ μ‹œμž‘ν•˜μžλ§ˆμž 큰 μž₯벽을 λ§Œλ‚¬μŠ΅λ‹ˆλ‹€.

Widget은 였직 SwiftUI 둜만 λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€β€¦

κ·Έλž˜λ„ μž‘μ€ ν™”λ©΄μ΄λ‹ˆκΉŒβ€¦ ν•œ 번 ν•΄λ΄μ•Όκ² μ–΄μš”..

How WidgetKit Works

μ• ν”Œμ€ μœ„μ ―μ— λŒ€ν•΄μ„œ ꡉμž₯히 μ œν•œμ μΈ μš”μ†ŒλΌλŠ” 생각을 가지고 μžˆλŠ” 것 κ°™μŠ΅λ‹ˆλ‹€.

μ‚¬μš©μžλŠ” ν™ˆ 화면을 ν•˜λ£¨μ— 90회 정도 λ°©λ¬Έν•˜μ§€λ§Œ, κ·Έ μ‹œκ°„μ€ ꡉμž₯히 μ λ‹€λŠ” 것을 κ°•μ‘°ν•˜κ³  μžˆλ„€μš”.

κ·Έλž˜μ„œ Widgetμ—λŠ” λ‘œλ”© 인디케이터가 돌고 μžˆλŠ” 상황이 μ—†μ–΄μ•Ό ν•œλ‹€ κ³  ν•©λ‹ˆλ‹€.

WidgetKit은 λ°±κ·ΈλΌμš΄λ“œ μ΅μŠ€ν…μ…˜ μž…λ‹ˆλ‹€.

μ‹œκ°„μ— 따라 화면듀을 νŒ¨ν‚€μ§•ν•˜μ—¬ ν™ˆμœΌλ‘œ 전달해쀀닀고 ν•˜λŠ”λ°μš”.. μ΄λ ‡κ²Œ ν•¨μœΌλ‘œμ¨ 앱을 μ‹€ν–‰ν•˜κ³ , 데이터λ₯Ό 뢈러였고, 화면에 λ„μš°λŠ” 일련의 과정듀을 μŠ€ν‚΅ν•©λ‹ˆλ‹€.

정말 μœ„μ ―μœΌλ‘œ ν‘œμ‹œλ  화면을 λ­‰ν……μ΄λ‘œ κ°€μ Έμ˜€κΈ°λ§Œ ν•˜λŠ” 것이죠..

κ·Έλž˜μ„œ μœ„μ ―μ„ κ°œλ°œν•˜λŠ”λ° μžˆμ–΄ 핡심적인 ν‚€μ›Œλ“œλŠ” β€œνƒ€μž„λΌμΈβ€ μž…λ‹ˆλ‹€.

μœ„μ ―μ€ νƒ€μž„λΌμΈμ— 따라 λ™μž‘ν•©λ‹ˆλ‹€.

μœ„μ ―μ€ νƒ€μž„λΌμΈμ— 미리 μŠ€μΌ€μ€„λ§λœ λŒ€λ‘œ μ—…λ°μ΄νŠΈ 될 μˆ˜λ„ 있고, μ•±μ—μ„œ μš”μ²­ν•˜μ—¬ μ—…λ°μ΄νŠΈ 될 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

μ—¬κΈ° 달λ ₯ μ•±μ˜ μœ„μ ―μ΄ μžˆλŠ”λ°μš”, 이 κ²½μš°μ—λŠ” 달λ ₯에 λ“±λ‘λœ 일정듀에 따라 μŠ€μΌ€μ€„λ§μ„ ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

각 일정이 λ‹€κ°€μ˜€κ±°λ‚˜ λλ‚œ μ‹œμ μ— μœ„μ ―μ„ μ—…λ°μ΄νŠΈν•  수 μžˆλŠ” 것이죠.

그런데 μ‚¬μš©μžκ°€ 앱을 μ—΄κ³ , 일정을 μˆ˜μ •ν–ˆλ‹€λ©΄μš”?

이럴 λ•ŒλŠ” μ•±μ—μ„œ 직접 μš”μ²­ν•˜μ—¬ μœ„μ ―μ„ μ—…λ°μ΄νŠΈ μ‹œν‚¬ 수 μžˆλŠ” 것이죠. (μ •ν™•νžˆλŠ” νƒ€μž„λΌμΈμ„ μ—…λ°μ΄νŠΈν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.)

Widget Definition

μ‹€μ œλ‘œ μœ„μ ―μ„ λ§Œλ“€κΈ° 전에, μœ„μ ―μ„ μ •μ˜ν•΄μ•Όν•©λ‹ˆλ‹€.

μœ„μ ―μ€ λ„€ κ°€μ§€λ‘œ μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€: kind , configuration , supportedFamilies , placeholder λ‘œμš”!

Kind

μš°μ„  kind μž…λ‹ˆλ‹€.

μœ„μ ―μ€ ν•˜λ‚˜μ˜ μ’…λ₯˜λ§Œ μžˆμ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

같은 μ‚¬μ΄μ¦ˆλΌλ„ μ—¬λŸ¬κ°œμ˜ 정보λ₯Ό μš”μ•½ν•˜μ—¬ λ³΄μ—¬μ£Όκ±°λ‚˜, ν•˜λ‚˜μ˜ 정보λ₯Ό 쑰금 더 μžμ„Έν•˜κ²Œ 보여쀄 μˆ˜λ„ 있죠.

Configuration

μ΄λ ‡κ²Œ λΆ„λ₯˜λœ 각 μ’…λ₯˜(kind)의 μœ„μ ―λ“€μ€ μ–΄λ–€ Configuration 을 μ§€μ›ν•˜λŠ” 지에 λŒ€ν•œ 정보도 λ‹΄κ³  μžˆμŠ΅λ‹ˆλ‹€.

StaticConfiguration κ³Ό IntentConfiguration , μ΄λ ‡κ²Œ 두 가지 Configuration이 μžˆλŠ”λ°μš”, μ•„μ£Ό κ°„λ‹¨ν•œ κ°œλ…μž…λ‹ˆλ‹€!

StaticConfiguration 은 사싀 Configuration이 μ—†λŠ” κ²λ‹ˆλ‹€.

ν•˜λ‚˜μ˜ Configurationλ§Œμ„ μ‚¬μš©ν•˜κΈ° μœ„ν•΄ μ΄λ ‡κ²Œ 이름이 뢙은 것 κ°™μ•„μš”.

λ³„λ„μ˜ 섀정이 ν•„μš” μ—†λŠ”, 였직 ν•˜λ‚˜μ˜ κ³ μ •λœ μ’…λ₯˜μ˜ λ°μ΄ν„°λ§Œμ„ 보여쀄 λ•Œ μ‚¬μš©λ©λ‹ˆλ‹€.

λ°˜λŒ€λ‘œ IntentConfiguration 은 섀정을 톡해 μœ„μ ―μ΄ λ³΄μ—¬μ£ΌλŠ” 화면이 λ‹¬λΌμ§ˆ λ•Œ μ‚¬μš©λ©λ‹ˆλ‹€.

μ˜ˆμ‹œλ‘œλŠ” β€œμ—¬λŸ¬ μ’…λ₯˜μ˜ 리슀트 μ€‘μ—μ„œ μ–΄λ–€ μ’…λ₯˜λ₯Ό μœ„μ ―μœΌλ‘œ 보여쀄 지 선택이 ν•„μš”ν•  λ•Œβ€ κ°€ 있겠죠?

Supported Families

SupportedFamilies λŠ” μ§€μ›ν•˜λŠ” μœ„μ ―μ˜ 크기 μž…λ‹ˆλ‹€.

κΈ°λ³ΈμœΌλ‘œλŠ” μ„Έ μ’…λ₯˜μ˜ 크기λ₯Ό λͺ¨λ‘ μ§€μ›ν•˜λŠ” κ²ƒμœΌλ‘œ λ˜μ–΄μžˆμ§€λ§Œ, κ°œλ°œμžκ°€ 선택할 수 μžˆμŠ΅λ‹ˆλ‹€.

Placeholder

Placeholder 도 κ°„λ‹¨ν•©λ‹ˆλ‹€.

μ–΄λ–€ 데이터도 듀어가지 μ•Šμ€, μœ„μ ―μ΄ μ–΄λ–€ ν˜•νƒœμΈμ§€λ§Œ ν‘œν˜„ν•˜λŠ” λΉ„μ–΄μžˆλŠ” Viewλ₯Ό μ œκ³΅ν•΄μ£Όλ©΄ λ©λ‹ˆλ‹€.

StatelessUI

μœ„μ ―μ€ λ‹€μŒμ˜ λ„€ 가지 원칙을 μ§€μΌœμ•Όν•©λ‹ˆλ‹€.

사싀 μ• ν”Œμ΄ μ œν•œν•΄λ‘κ³  있기 λ•Œλ¬Έμ— μ–΄κΈΈ μˆ˜λ„ μ—†μ§€λ§Œμš”?

  1. μœ„μ ―μ€ μž‘μ€ λ³„λ„μ˜ 앱이 μ•„λ‹ˆλ‹€. 앱에 μ§„μž…μ‹œν‚€κΈ° μœ„ν•œ λ‹¨νŽΈμ μΈ 정보λ₯Ό μ œκ³΅ν•  뿐이닀.
  2. μœ„μ ―μ€ μŠ€ν¬λ‘€μ„ μ§€μ›ν•˜μ§€ μ•ŠλŠ”λ‹€.
  3. μœ„μ ―μ—λŠ” μ›€μ§μ΄λŠ” μ‚¬μ§„μ΄λ‚˜ λΉ„λ””μ˜€κ°€ λ“€μ–΄κ°ˆ 수 μ—†λ‹€.
  4. μœ„μ ―μ—λŠ” 였직 νƒ­ 제슀처만이 μœ νš¨ν•˜λ‹€.

λ„€ 근데 WWDC2023μ—μ„œ μœ„ μ œν•œλ“€ 쀑 "μ›€μ§μ΄λŠ” μ‚¬μ§„μ΄λ‚˜ λΉ„λ””μ˜€κ°€ λ“€μ–΄κ°ˆ 수 μ—†λ‹€"λŠ” μ–΄λŠμ •λ„ ν—ˆμš©μ΄ λμŠ΅λ‹ˆλ‹€.

UI에 μ• λ‹ˆλ©”μ΄μ…˜μ„ μΆ”κ°€ν•  수 있게 μ—…λ°μ΄νŠΈ λ˜μ—ˆκ±°λ“ μš”

μœ„μ ―μ€ 였직 μ•±μ˜ νŠΉμ • 지점에 λ„λ‹¬ν•˜κΈ° μœ„ν•œ μˆμ»·μ„ μ œκ³΅ν•  뿐이고, 이것은 URL API λ₯Ό ν™œμš©ν•΄ κ΅¬ν˜„λœλ‹€κ³  ν•˜λ„€μš”!

Views

μœ„μ ―μ—λŠ” μ„Έ 가지 Viewκ°€ ν•„μš”ν•©λ‹ˆλ‹€.

첫 λ²ˆμ§ΈλŠ” placeholder 둜 이미 닀룬 λ‚΄μš©μ΄κ΅¬μš”..

두 번째둜 snapshot μ΄λΌλŠ” 화면이 ν•„μš”ν•©λ‹ˆλ‹€.

Widget Gallery와 같은 κ³³μ—μ„œ λΉ λ₯΄κ²Œ 데이터λ₯Ό λΆˆλŸ¬μ™€ μ‚¬μš©ν•  수 μžˆλŠ” λ‹¨μΌμ„±μ˜ ν™”λ©΄μž…λ‹ˆλ‹€.

λ§ˆμ§€λ§‰μœΌλ‘œλŠ” timeline μž…λ‹ˆλ‹€.

μ‹€μ œλ‘œ μœ„μ ―μ„ ν™ˆ 화면에 λ„μšΈ λ•Œ μ‚¬μš©λ˜λŠ” ν™”λ©΄μž…λ‹ˆλ‹€.

보톡 snapshotκ³Ό timeline의 첫 화면은 λ™μΌν•œ ν™”λ©΄μœΌλ‘œ κ΅¬μ„±λ˜λŠ”κ²Œ μ’‹λ‹€κ³  ν•˜λ„€μš”.

νƒ€μž„λΌμΈμ€ Light Mode와 Dark Mode일 λ•Œμ˜ 화면을 λ™μ‹œμ— μ œκ³΅ν•˜μ—¬ μ‹œμŠ€ν…œμ΄ μ•Œμ•„μ„œ μ‘°μ •ν•  수 μžˆλ„λ‘ λ˜μ–΄ 있고, ν•œ λ²ˆμ— λ©°μΉ λ™μ•ˆμ˜ Viewλ₯Ό μ œκ³΅ν•œλŠ” 것이 μ’‹λ‹€κ³  ν•©λ‹ˆλ‹€.

Reload

ν•˜μ§€λ§Œ μœ„μ ―λ„ μ’€ 더 자주 μ—…λ°μ΄νŠΈ λ˜μ–΄μ•Ό ν•  λ•Œκ°€ 있겠죠?

이럴 λ•ŒλŠ” reload λΌλŠ” κ°œλ…μ„ μ‚¬μš©ν•©λ‹ˆλ‹€.

Reloadκ°€ μ‹€ν–‰λ˜λ©΄ 화면에 배치된 λͺ¨λ“  μœ„μ ― 에 λŒ€ν•΄μ„œ μ‹œμŠ€ν…œμ΄ μƒˆλ‘œμš΄ timeline을 제곡 ν•˜λ„λ‘ μš”μ²­ν•©λ‹ˆλ‹€.

자 μ½”λ“œλ‘œ ν•œ 번 μ‚΄νŽ΄λ΄…μ‹œλ‹€.

public protocol TimelineProvider {
  associatedType Entry: TimelineEntry

  typealias Context = TimelineProviderContext

  func snapshot(with context: Self.Context,
                completion: @escaping (Self.Entry) -> ())
  func timeline(with context: Self.Context,
                completion: @escaping (Timeline<Self.Entry>) -> ())
}    

μ½”λ“œλ‘œ 봐도 잘 λͺ¨λ₯΄κ² κΈ΄ ν•œλ°μš”.. πŸ€”

EntryλŠ” 보톡 Date 데이터가 λ“€μ–΄κ°„λ‹€κ³  ν•˜κ΅¬μš”..

Contextμ—λŠ” environment 정보와 Entryλ₯Ό μš”μ²­ν•œ μ‹œμŠ€ν…œμ— λŒ€ν•œ 정보가 λ“€μ–΄μ˜¨λ‹€κ³  ν•©λ‹ˆλ‹€.

snapshotκ³Ό timeline은 말 κ·ΈλŒ€λ‘œ 이전에 μ‚΄νŽ΄λ³΄μ•˜λ˜ snapshotκ³Ό timeline 데이터λ₯Ό μ œκ³΅λ°›κΈ° μœ„ν•œ ν•¨μˆ˜μž…λ‹ˆλ‹€.

snapshotμ—λŠ” 단일 데이터가, timelineμ—λŠ” μ—¬λŸ¬ 개의 데이터가 λ“€μ–΄κ°€κ² μ£ ?

public struct Provider: TimelineProvider {
  public func snapshot(with context: Context, 
                       completion: @escaping (SimpleEntry) -> ()) {
    let entry = SimpleEntry(date: Date())
    completion(entry)
  }
  public func timeline(with context: Context, 
                       completion: @escaping (Timeline<Entry>) -> ()) {
    let entry = SimpleEntry(date: Date())
    let timeline = Timeline(entries: [entry, entry], policy: .atEnd)
    completion(timeline)
  }
}

TimelineProviderλ₯Ό μ±„νƒν•˜λ©΄ 이런 ν˜•νƒœμ˜ λͺ¨μŠ΅μΌκ²λ‹ˆλ‹€.

μ—¬κΈ°μ„œ μ£Όλͺ©ν•  점은 policy: .atEnd λΆ€λΆ„μž…λ‹ˆλ‹€.

μ—¬κΈ°μ„œ policyλž€ reloadκ°€ λ˜λŠ” μ‹œμ μ„ μ •ν•˜λŠ” policy라고 λ³Ό 수 μžˆλŠ”λ°μš”,

atEnd 둜 μ œκ³΅ν•œ timeline이 끝날 λ•Œ reloadλ₯Ό ν•  수 있고, after(date: Date)둜 일정 μ‹œκ°„μ΄ 됐을 λ•Œ reloadν•˜κ±°λ‚˜ never둜 μš”μ²­ν•˜μ§€ μ•Šμ„ 수 μžˆμŠ΅λ‹ˆλ‹€.

κ·Έλž˜μ„œ reloadλŠ” λŒ€μ²΄ μ–Έμ œ λ˜λŠλƒ?

μžμ—°μ μœΌλ‘œ λ°œμƒν•˜λŠ” reloadλŠ” μ•„λž˜μ˜ κ·œμΉ™μ— 따라 μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€.

  1. ReloadPolicyλ₯Ό 따라 λ˜κ±°λ‚˜
  2. 더 자주 화면에 λ“±μž₯ν•˜λŠ” μœ„μ ―μ— 더 λ§Žμ€ reloadλ₯Ό μ œκ³΅ν•˜κ±°λ‚˜
  3. κΈ°κΈ° ν™˜κ²½μ΄ λ°”λ€” λ•Œ μ‹œμŠ€ν…œμ΄ κ°•μ œλ‘œ reloadν•©λ‹ˆλ‹€.

앱이 μš”μ²­ν•˜λŠ” reloadλŠ” Background Notification 이 μžˆκ±°λ‚˜, μ‚¬μš©μžκ°€ 데이터λ₯Ό λ³€κ²½ ν–ˆμ„ λ•Œ μ΄λ£¨μ–΄μ§‘λ‹ˆλ‹€.

두 경우 λͺ¨λ‘ WidgetCenter의 reloadTimelines(ofKind:)λ₯Ό μ‚¬μš©ν•˜λ©΄ λœλ‹€κ³  ν•˜λ„€μš”.

  • reloadTimelines(ofKind:)
  • reloadAllTimelines
  • getCurrentConfigurations(completion:)

WidgetKit은 μ΄λ ‡κ²Œ 세가지 APIλ₯Ό μ œκ³΅ν•˜κ³  μžˆλŠ” 것을 μ•Œμ•„λ‘κ³  λ„˜μ–΄κ°‘μ‹œλ‹€!

그런데 μœ„μ ―μ„ μ‚¬μš©ν•  λ•Œ, λ„€νŠΈμ›Œν¬ 톡신을 톡해 μƒˆλ‘œμš΄ 데이터λ₯Ό 받아와야할 λ•Œλ„ 있겠죠?

그럴 λ•ŒλŠ” background URLSessionsλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

ν•˜μ§€λ§Œ! μœ„μ ―μ€ μ ˆλŒ€μ ˆλŒ€ μ‹€μ‹œκ°„ 데이터λ₯Ό λ°˜μ˜ν•˜κΈ° μœ„ν•œ κΈ°λŠ₯이 μ•„λ‹ˆκΈ° λ•Œλ¬Έμ— κ·Έ λΉˆλ„λ₯Ό 잘 섀정해달라고 ν•©λ‹ˆλ‹€.

κ·Έλž˜μ„œ 정리해보면.. μœ„μ ―μ˜ ReloadλŠ” Background Networking 을 톡해 μ΄λ£¨μ–΄μ§€κ±°λ‚˜, Timeline 에 μ˜ν•΄ μ΄λ£¨μ–΄μ§€κ±°λ‚˜ μ•±μ˜ μš”μ²­ 에 따라 μ΄λ£¨μ–΄μ§ˆ 수 μžˆκ² μŠ΅λ‹ˆλ‹€.

Personalization & Intelligence

μ•žμ—μ„œ μœ„μ ―μ€ μ—¬λŸ¬ 섀정에 따라 데이터λ₯Ό λ‹€λ₯΄κ²Œ 보여쀄 수 μžˆλ‹€κ³  ν–ˆμ—ˆμŠ΅λ‹ˆλ‹€.

Personalization

이 κΈ°λŠ₯을 μ‚¬μš©ν•˜λ €λ©΄ App Intent λΌλŠ” 것을 μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

Intent FrameworkλŠ” μ—¬λŸ¬κ°œμ˜ νŒŒλΌλ―Έν„°λ‘œ 이루어져 μžˆμŠ΅λ‹ˆλ‹€.

각 νŒŒλΌλ―Έν„°λŠ” μœ μ €μ—κ²Œ λ¬»λŠ” μΌμ’…μ˜ 질문이라고 μƒκ°ν•˜λ©΄ λ©λ‹ˆλ‹€.

예λ₯Ό λ“€μ–΄ 주식 μ•±μ˜ 경우 μœ μ €κ°€ μœ„μ ―μ„ ν„°μΉ˜ν•˜μ—¬ μ–΄λ–€ 주식을 ν‘œμ‹œν•  것인지λ₯Ό 선택할 수 있겠죠.

이 λ•Œ, App Intentλ₯Ό μ‚¬μš©ν•΄ μœ μ €κ°€ μ•±μ—μ„œ 지정해둔 μ£Όμ‹μ˜ 리슀트λ₯Ό μœ„μ ―μœΌλ‘œ λ°›μ•„μ˜¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

λ§Œμ•½ μœ μ €κ°€ μ›ν•˜λŠ” μ˜΅μ…˜μ΄ λ¦¬μŠ€νŠΈμ— μ—†λ‹€λ©΄?

IntentsλŠ” dynamic options capability κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€.

μœ μ €κ°€ μ’…λͺ©μ„ κ²€μƒ‰ν•˜λ©΄, μ‹œμŠ€ν…œμ΄ 주식 Intents Extension을 μ‹€ν–‰ν•˜μ—¬ 리슀트λ₯Ό μƒˆλ‘­κ²Œ λ°›μ•„μ˜¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

이 뢀뢄은 λ”°λ‘œ 더 곡뢀가 ν•„μš”ν•΄λ³΄μ΄λ„€μš”β€¦

SiriKitκ³Ό κ΄€λ ¨λœ λ‚΄μš©μ΄λΌκ³  ν•©λ‹ˆλ‹€..

Intentλ₯Ό μ‚¬μš©ν•˜λ©΄ μ΄μ „μ˜ μ½”λ“œλ„ λ‹€μŒκ³Ό 같이 λ°”κΏ”μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€.

@main
public struct SampleWidget: Widget {
  private let kind: String = "SampleWidget"

  public var body: some WidgetConfiguration {
    IntentConfiguration(kind: kind,
                        intent: ConfigurationIntent.self
                        provider: Provider(),
                        placeholder: PlaceholderView()) { entry in
                          SampleWidgetEntryView(entry: entry)
                        }
    .configurationDisplayName("My Widget")
    .description("This is an example widget.")
  }
}

StaticConfiguration λŒ€μ‹  IntentConfiguration 을 μ‚¬μš©ν•΄μ£Όκ³  (μ•„.. μ΄λž˜μ„œ 이름이 IntentCongiruationμ΄μ˜€κ΅¬λ‚˜β€¦)

public struct Provider: IntentTimelineProvider {
  public func timeline(for configuration: ConfigurationIntent, with context: Context, 
                       completion: @escaping (Timeline<Entry>) -> ()) {
    let entry = SimpleEntry(date: Date(), configuration: configuration)

    // generate a timeline based on the values of the Intent

    completion(timeline)
  }
}

TimelineProvider도 IntentTimelineProvider둜 μ—…λ°μ΄νŠΈν•΄μ„œ μ œκ³΅ν•΄μ£Όμ–΄μ•Ό ν•©λ‹ˆλ‹€.

IntentHandlerλ₯Ό μ‚¬μš©ν•  λ•Œμ˜ 주의점!

DynamicIntentλ₯Ό κ΅¬μ„±ν•˜κ³ , IntentHandlerλ₯Ό κ΅¬ν˜„ν•  λ•Œ 헀맨 곳이 μžˆμ—ˆλŠ”λ°μš”..

DynamicSummonerSelectionIntentHandler ν”„λ‘œν† μ½œμ„ μ€€μˆ˜ν•˜λ € ν–ˆμ„ λ•Œμž…λ‹ˆλ‹€.

ν•΄λ‹Ή ν”„λ‘œν† μ½œμ„ μ±„νƒν•˜κ³ , stubλ₯Ό μΆ”κ°€ν•˜λ©΄ XCodeκ°€ 두 가지 λ©”μ„œλ“œλ₯Ό μΆ”κ°€ν•΄μ€λ‹ˆλ‹€.

func provideSummonerOptionsCollection(for intent: DynamicSummonerSelectionIntent, with completion: @escaping (INObjectCollection<Summoner>?, Error?) -> Void) {
		//
}

func provideSummonerOptionsCollection(for intent: DynamicSummonerSelectionIntent) async throws -> INObjectCollection<Summoner> {
		//
}

μ €λŠ” μ²˜μŒμ— β€œ@escaping ν΄λ‘œμ € 방식과 async/await의 두 가지 방식을 λͺ¨λ‘ μ œκ³΅ν•΄μ€˜μ•Όν•˜λŠ”κ΅¬λ‚˜!” 라고 μƒκ°ν•˜κ³  μž‘μ—…μ„ ν–ˆμŠ΅λ‹ˆλ‹€.

그러면 이런 μ—λŸ¬κ°€ λœ°κ²λ‹ˆλ‹€..

Method with Objective-C selector conflicts with method with the same Objective-C selector.

λŒ€μ²΄ μ–΄λ””μ„œ Conflictκ°€ λ‚˜λŠ”κ±°μ§€? λΌλŠ” 의문이 λ“€μ—ˆμ§€λ§Œ.. 원인은 κ°„λ‹¨ν–ˆμŠ΅λ‹ˆλ‹€.

Handler에 λ“€μ–΄κ°€μ„œ λ‚΄μš©μ„ 잘 μ‚΄νŽ΄λ³΄λ©΄.. 두 ν”„λ‘œν† μ½œμ΄ λŒ€μ‘λ˜λŠ” Obj-C ν•¨μˆ˜κ°€ κ°™μŠ΅λ‹ˆλ‹€..

κ·Έλž˜μ„œ 같은 ν•¨μˆ˜λΌλŠ” μ—λŸ¬λ₯Ό λ‚΄κ³  μžˆλŠ” 것이죠..

κ·Έλ ‡λ‹€λ©΄ 해결법은…?

두 ν•¨μˆ˜ 쀑 ν•˜λ‚˜λ§Œ κ³¨λΌμ„œ μ‚¬μš©ν•΄μ£Όμ‹œλ©΄ λ©λ‹ˆλ‹€β€¦ πŸ₯²

Intelligence

자 그런데 μ• ν”Œμ΄ μ œκ³΅ν•˜λŠ” κΈ°λ³Έ μœ„μ ― 쀑 β€œμŠ€λ§ˆνŠΈ μŠ€νƒβ€ μ΄λΌλŠ” λ†ˆμ΄ 있죠?

iOSκ°€ μœ μ €μ˜ νŒ¨ν„΄κ³Ό ν™˜κ²½μ„ λΆ„μ„ν•˜μ—¬ 상황에 κ°€μž₯ λ§žλŠ” μœ„μ ―μ„ μ„ νƒν•˜μ—¬ λ³΄μ—¬μ£ΌλŠ” κΈ°λŠ₯μž…λ‹ˆλ‹€.

μ–΄λ–»κ²Œ λ‚΄ μœ„μ ―μ΄ 슀마트 μŠ€νƒμ— 뜰 수 있게 ν•˜λŠλƒ? ν•˜λ©΄ 두가지 방법이 μžˆμŠ΅λ‹ˆλ‹€.

1. Shortcuts Donate

μœ μ €κ°€ μ•± λ‚΄μ—μ„œ ν™œλ™μ„ ν•΄μ„œ Shortcuts둜 donateλ₯Ό ν•˜κ²Œ 되면, 같은 INIntentλ₯Ό μ‚¬μš©ν•˜λŠ” μœ„μ ―μ΄ 슀마트 μŠ€νƒμ˜ μœ„λ‘œ 올 수 있게 λ©λ‹ˆλ‹€.

2. TimelineEntryRelevance API

μ–΄λ–€ μ‹œκ°„μ΄ λ‹€κ°€μ˜€κ³ , λ‚΄ μ•±μ˜ entryκ°€ κ°€μž₯ μ€‘μš”ν•œ μ‹œμ μ΄λΌκ³  μƒκ°λ˜λ©΄, μ‹œμŠ€ν…œμ—κ²Œ score와 duration을 μ œκ³΅ν•˜μ—¬ 슀마트 μŠ€νƒμ˜ μœ„λ‘œ μœ„μΉ˜ν•˜κ²Œ μœ λ„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

RelevanceλŠ” TimelineEntryRelevance(score:)둜 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ—¬κΈ°μ„œ scoreλΌλŠ” νŒŒλΌλ―Έν„°κ°€ μ€‘μš”ν•œλ°, 저희가 μ›ν•˜λŠ” μ–΄λ–€ μƒμˆ˜λ₯Ό λ„£μ–΄μ£Όλ©΄ λ©λ‹ˆλ‹€.

이 μƒμˆ˜μ˜ λ²”μœ„λŠ” μ •ν•΄μ Έμžˆμ§€ μ•ŠμœΌλ©°, 저희가 μ „λ‹¬ν•œ 각 μƒμˆ˜λ“€κ³Όμ˜ μƒλŒ€μ μΈ 차이에 λ”°λΌμ„œ μ•Œμ•„μ„œ μš°μ„ μˆœμœ„κ°€ μ •ν•΄μ§‘λ‹ˆλ‹€.