Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[TIL] WidgetKit #29

Open
samsung-ga opened this issue Jul 25, 2022 · 0 comments
Open

[TIL] WidgetKit #29

samsung-ga opened this issue Jul 25, 2022 · 0 comments
Labels

Comments

@samsung-ga
Copy link
Owner

samsung-ga commented Jul 25, 2022

Widgetkit

시작하기

  • 타겟에서 Widget Extension 검색 후 생성

스크린샷 2022-07-25 오후 2 40 15

- 해당 `Include 뭐시기` 체크박스는 아래에서 설명해줌.


Configuration Deitals 추가

  • 위젯 익스텐션 템플릿은 기본적인 위젯을 실행하는 코드를 제공한다.
  • 이 때 Widget 프로토콜을 채택한 구조체의 body 프로퍼티는 위젯의 콘텐츠가 사용자가 정의할 수 있냐 없냐를 결정한다.
    StaticConfiguration: User-configurable property가 없는 위젯을 위한 configuration
    IntentConfiguration: User-configurable property가 있는 위젯을 위한 configuration
  • 이것은 Widget Extension을 생성할 때, 체크박스를 통해 처음에 정해줄 수 있다.

properties

kind

  • widget의 identifier

provider

  • TimelineProvider 프로토콜을 채택하는 객체
  • WidgetKit에게 위젯을 언제 업데이트할 지 timeline을 제공
  • timeline은 TimelineEntry 타입을 가지고 있고 이것이 위젯을 업데이트할 시간, Date타입을 가지고 있다.
  • 뿐만 아니라, 업데이트 될 위젯의 뷰가 필요로 할 프로퍼티들도 가지고 있다.
  • 정리
    • TimelineEntry 타입이 제공하는 것
      • 위젯 업데이트할 시간
      • 업데이트 될 위젯뷰가 필요로 할 프로퍼티(데이터)

intent

  • 사용자가 직접 정의하는 커스텀 의도... 자세한 내용은 여기에서...

content

  • SwiftUI 뷰를 가진 클로저
  • TimelineEntry 파라미터를 넘기면서 이 뷰를 렌더링함

modifiers

추가적인 configuration 정보를 modifier을 통해 제공 가능하다.

@main
struct GameStatusWidget: Widget {
    var body: some WidgetConfiguration {
        StaticConfiguration(
            kind: "com.mygame.game-status",
            provider: GameStatusProvider(),
        ) { entry in
            GameStatusView(entry.gameStatus)
        }
        .configurationDisplayName("Game Status") // ✅
        .description("Shows an overview of your game status") // ✅
        .supportedFamilies([.systemSmall, .systemMedium, .systemLarge, .systemExtraLarge]) // ✅
    }
}

앱의 위젯이 위젯갤러리에서 보이기 위해서는, 유저가 반드시 앱이 설치된 이후, 위젯을 포함한 앱을 한번 실행해야한다. ✨ 당연한 소리!!

  • @main 어노테이션로 마크한 구조체는 한 개의 위젯을 제공할 때, 위젯 익스텐션의 엔트리 포인트이다.
  • 여러개의 위젯을 제공하고 싶다면, Declare Multiple Widgets in Your App Extension을 보시오!



Provide Timeline Entries

  • 위에서 이야기 했듯이 위젯 콘텐츠를 업데이트하기 위한 date and time을 제공
  • 작동 방법에 대해 설명
      1. 위젯킷은 provider에게 스냅샷을 요청한다.
      1. getSnapshot(in:completion:)메소드에 전달된 context 매개변수의 isPreview 프로퍼티를 확인해 미리보기 요청을 식별한다.
      1. isPreview가 true라면 위젯을 표시한다. 이 때, 서버에서 생성하거나 가져오는데 시간이 걸리는 정보가 필요한 경우 샘플 데이터를 대신 사용한다.
    • 아래와 같은 경우, 서버에서 시간이 걸리는 경우 empty status를 보여준다.
struct GameStatusProvider: TimelineProvider {
    var hasFetchedGameStatus: Bool
    var gameStatusFromServer: String

    func getSnapshot(in context: Context, completion: @escaping (Entry) -> Void) {
        let date = Date()
        let entry: GameStatusEntry

        if context.isPreview && !hasFetchedGameStatus {
            entry = GameStatusEntry(date: date, gameStatus: "")
        } else {
            entry = GameStatusEntry(date: date, gameStatus: gameStatusFromServer)
        }
        completion(entry)
    }
  • 초기 스냅샷을 요청한 후, 위젯킷은 getTimeline(in:completion:)을 불러와 timeline을 요청한다.
  • timeline은 하나 이상의 timelineEntry로 이루어져있고 후속 timeline을 요청할 정책으로 이루어져있다.
// 1개 entry와 15분 후, 새로운 요청을 주는 리로드 정책과 함께 Timeline을 생성하는 예제 
struct GameStatusProvider: TimelineProvider {
    func getTimeline(in context: Context, completion: @escaping (Timeline<GameStatusEntry>) -> Void) {
        // Create a timeline entry for "now."
        let date = Date()
        let entry = GameStatusEntry(
            date: date,
            gameStatus: gameStatusFromServer
        )

        // Create a date that's 15 minutes in the future.
        let nextUpdateDate = Calendar.current.date(byAdding: .minute, value: 15, to: date)!

        // Create the timeline with the entry and a reload policy with the date
        // for the next update.
        let timeline = Timeline(
            entries:[entry],
            policy: .after(nextUpdateDate)
        )

        // Call the completion to pass the timeline to WidgetKit.
        completion(timeline)
    }
}



Placeholder 표시 + 민감한 데이터 숨기기

  • placeholder view는 콘텐츠가 없을 때, 보여주는 일반적인 뷰

  • 예를 들어, 백그라운드에서 데이터를 로드하는 동안, redacted(reason:) modifier를 이용해서 placeholder를 생성한다.

  • 이 modifier는 자동으로 위젯의 뷰를 자동으로 렌더링한다.

  • 데이터가 로드가 완료되고, 뷰에 띄어주기 전, 민감한 정보를 숨기기 위하여 redacted(reason:) 콜백의 unredacted() modifier를 사용해라.

  • 처음 보여지는 경우 외에도, Data Protection을 위하여 WidgetKit은 placeholder를 사용한다.

  • 아래와 같은 경우

콘텐츠 보여주기

  • SwiftUI View를 사용해야 한다.
  • widgetFamily를 이용하여 위젯의 사이즈와, 잠긴 화면에 어떤 위젯을 보여줄 지 나눠 줄 수 있다.
struct GameStatusView : View {
    @Environment(\.widgetFamily) var family: WidgetFamily
    var gameStatus: GameStatus
    var selectedCharacter: CharacterDetail

    @ViewBuilder
    var body: some View {
        switch family {
        case .systemSmall: GameTurnSummary(gameStatus)
        case .systemMedium: GameStatusWithLastTurnResult(gameStatus)
        case .systemLarge: GameStatusWithStatistics(gameStatus)
        case .systemExtraLarge: GameStatusWithStatisticsExtraLarge(gameStatus)
        case .accessoryCircular: HealthLevelCircular(selectedCharacter)
        case .accessoryRectangular: HealthLevelRectangular(selectedCharacter)
        case .accessoryInline: HealthLevelInline(selectedCharacter)
        default: GameDetailsNotAvailable()
        }
    }
}

위젯들은 읽기 전용이고, 상호작용적인 요소들은 제공하지 않는다.





Dynamic 콘텐츠??

  • 이것은 나중에 공부해보도록 하자. 다이나믹한 콘텐츠들을 보여줄 수 있다는 내용같다.


User Interaction에 대한 respond

  • 위젯을 누르면 앱이 실행된다.
  • 이 때, URL을 이용하면 해당 콘텐츠로 네비게이트할 수 있다.
  • URL을 configure하는 방법
@ViewBuilder
var body: some View {
    ZStack {
        AvatarView(entry.character)
            .widgetURL(entry.character.url)
            .foregroundColor(.white)
    }
    .background(Color.gameBackground)
}




여러개의 위젯을 사용할 경우

@main
struct GameWidgets: WidgetBundle {
    @WidgetBundleBuilder
    var body: some Widget {
        GameStatusWidget()
        CharacterDetailWidget()
        LeaderboardWidget()
    }
}




Preview Widget

@Environment(\.widgetFamily) var family
        
Group {
    EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .panda))
        .previewContext(WidgetPreviewContext(family: .accessoryCircular))
        .previewDisplayName("\(family)")
  EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .panda))
        .previewContext(WidgetPreviewContext(family: .accessoryRectangular))
        .previewDisplayName("\(family)")
    EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .panda))
        .previewContext(WidgetPreviewContext(family: .accessoryInline))
        .previewDisplayName("\(family)")
    EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .panda))
        .previewContext(WidgetPreviewContext(family: .systemSmall))
	      .previewDisplayName("\(family)")
    EmojiRangerWidgetEntryView(entry: SimpleEntry(date: Date(), relevance: nil, character: .panda))
        .previewContext(WidgetPreviewContext(family: .systemMedium))
  	      .previewDisplayName("\(family)")
}
@samsung-ga samsung-ga added in progress issue which is in progress SwiftUI and removed in progress issue which is in progress labels Jul 25, 2022
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant