Programming/iOS

[WidgetKit] 변경가능한 위젯 만들기 (Making a Configurable Widget)

devssun 2021. 10. 30. 00:44
728x90
반응형

Making a Configurable Widget

링크
https://developer.apple.com/documentation/widgetkit/making-a-configurable-widget

편집가능한 위젯 만들기 (가능한,,)
김종민 가능한
https://www.youtube.com/watch?v=Ek_gBKHDdPA

위젯은 Custom 속성을 제공할 수 있다. 아래와같이 바로가기할 수 있는 메뉴를 설정하거나 배송 조회 할 때 송장번호 입력 등을 할 수 있다. Siri 제안Siri 바로 가기 와 동일한 메커니즘을 갖는다.



위젯에 configurable property 추가하는 법

  1. Xcode project에 custom intent definition을 추가한다.
  2. IntentTimelineProvider를 사용해서 사용자의 선택을 timeline entry에 통합한다.
  3. 속성이 동적 데이터에 의존하는 경우 intents extension을 구현한다.

앱이 이미 Siri 제안 또는 Siri 단축어를 지원하고 custom intent가 있는 경우 대부분의 작업이 이미 수행되어 있을거고 그렇지 않으면 지원을 추가하는 것이 좋다. intent를 최대한 활용하려면 SiriKit을 참고한다.

  • 참고로 Widget Extension을 추가할 때 Include Configuration Intent 를 활성화하면 Configurable Widget으로 생성된다.


Add a Custom Intent Definition to Your Project

  • Xcode 프로젝트에서 New File > SiriKit Intent Definition File 을 선택한다. 이름을 입력하면 해당 파일이 추가된다.
  • Intent Definition File에서 왼쪽 하단의 + 버튼을 눌러 Intent, Customize System Intent, Type, Enum을 생성할 수 있다.


Dynamic Option

사진의 현대카드 앱 위젯처럼 바로가기 메뉴를 사용자가 선택할 수 있게 하려면 Custom IntentType이 필요하다.

아래 사진과 같이 설정해준다.

  1. Custom Intent
    • Category - View
    • 체크박스는 Widgets만 체크한다. SiriKit도 연동하고 싶다면 다른 것도 체크한다.
  2. Parameters
    • 사용자가 선택할 메뉴를 Type으로 생성한다.
    • 다중 선택을 위해 Array - Supports multiple values, Dynamic Options - Options are provided dynamically 를 체크한다.
    • Configurable를 체크한다.

이제 코드로 할 것은 1. 위젯에게 사용자가 선택할 옵션을 넘겨주어야하고 2. 선택한 메뉴가 위젯에 나오도록 구성해야한다.


Xcode는 Custom Intent에 대한 Custom Class를 자동으로 생성해주는데 Xcode의 오른쪽 inspector의 네번째 메뉴에 Custom Class가 있다.
화살표를 누르면 해당 클래스로 이동하는데 IntentHandling protocol에 provide<Type>OptionsCollection 과 같은 protocol이 생성된 것을 확인할 수 있다. (이때 에는 위에서 Type에 설정해준 타입 이름이 들어간다.)

  • 이 protocol이 보이지않는다면 빌드하거나 Xcode를 재부팅한다.


Intent Handler

위에서 말한 1. 위젯에게 사용자가 선택할 옵션을 넘겨주기 작업을 하려면 Intent Handler가 필요하다. Widget extension을 생성할 때와 같이 Intent Extension을 생성한다.

생성된 IntentHandler class에서 위에서 만들어준 Intent의 IntentHandling을 상속받고 provide<Type>OptionsCollection protocol 구현을 작성한다.

해당 함수 안에서 사용자가 선택할 값을 넘겨주면된다. 이 작업을 하지않으면 사용자가 옵션을 클릭했을 때 옵션이 없다는 에러가 뜨게 된다.

// Example : IntentHandler

import Intents

class IntentHandler: INExtension {

    override func handler(for intent: INIntent) -> Any {
        // This is the default implementation.  If you want different objects to handle different intents,
        // you can override this and return the handler you want for that particular intent.

        return self
    }

}

extension IntentHandler: QuickLinkMenuSelectionIntentHandling {

    func provideMenuOptionsCollection(for intent: QuickLinkMenuSelectionIntent,
                                      with completion: @escaping (INObjectCollection<Menu>?, Error?) -> Void) {
        let menus: [Menu] = QuickLink.availableMenu.map { menu in
            return Menu(identifier: menu.identifier, display: menu.title)
        }

        completion(INObjectCollection(items: menus), nil)
    }
}

이제 2. 선택한 메뉴가 위젯에 나오도록 구성하는 작업을 한다. 이전에 StaticConfiguration으로 생성한 위젯은 IntentConfiguration, IntentTimelineProvider로 수정하면된다.

func getTimeline(for configuration: Self.Intent, in context: Self.Context, completion: @escaping (Timeline<Self.Entry>) -> Void) 이 함수에서 configuration 파라미터를 가지고 사용자가 선택한 메뉴를 Entry에 전달한다.

// Example : IntentTimelineProvider - getTimeline

func getTimeline(for configuration: QuickLinkMenuSelectionIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> Void) {
        var entries: [QuickLinkEntry] = []
        let selectedMenus = configuration.menu == nil ? QuickLink.defaultMenu : makeMenu(configuration.menu!)
        // 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 = QuickLinkEntry(date: entryDate, menus: selectedMenus)
            entries.append(entry)
        }

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

EntryView에서 전달받은 값으로 메뉴를 구성하면 완성!!

// Example : QuickLinkWidgetEntryView

struct QuickLinkWidgetEntryView : View {
    var entry: QuickLinkProvider.Entry

    @Environment(\.widgetFamily) var family

    @ViewBuilder
    var body: some View {
        switch family {
        case .systemMedium:
            VStack(alignment: .center) {
                HStack(alignment: .center, spacing: 40.0) {
                                        // 4개 이상 선택했을 때 UI가 깨지는 것을 방지하기 위해 최대 4개만 표시하도록 함
                    ForEach(0..<min(4, entry.menus.count), id: \.self) { index in
                        let menu = entry.menus[index]
                        LinkView(link: URL(string: menu.link)!,
                                 imageName: menu.icon,
                                 title: menu.title)
                    }
                }
                Divider()
                SearchView()
            }
            .padding(.all)
        default:
            Text("")
        }
    }
}
  • default menu을 만들어 preview, placeholder 등에 넘겨주면 빈 위젯이 뜨는 것을 방지할 수 있고 사용자가 어떤 위젯인지 확인하기 쉬워진다.
반응형