[iOS] Static Widget 만들기
iOS

[iOS] Static Widget 만들기

728x90

Widget 이란?

홈 화면이나 알림 센터에 표시할 위젯의 구성 및 콘텐츠

* ios 14부터 가능

 

Widget 구성 요소

  1. Configuration:
    • 구성 가능 여부 결정: 위젯이 구성 가능한지 여부를 결정합니다.
    • 위젯 식별: 각 위젯은 고유한 문자열을 통해 식별됩니다. 일반적으로 Bundle Identifier를 사용합니다.
    • 콘텐츠 정의: SwiftUI를 사용하여 위젯의 콘텐츠를 표시하는 보기를 정의합니다.
  2. Timeline Provider:
    • 업데이트 프로세스 구동: 시간이 지남에 따라 위젯 보기를 업데이트하는 프로세스를 구동합니다.
    • 타임라인 제공: WidgetKit에게 위젯을 언제 업데이트할지를 나타내는 타임라인을 제공합니다.
  3. 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이 동일한 그룹에서 데이터를 공유할 수 있게 됩니다. 

  1. App Groups 설정:
    • 프로젝트 설정으로 이동하여 App Groups를 추가합니다.
    • 앱과 Extension의 TARGETS에서 동일한 그룹을 설정합니다.
  2. UserDefaults 활용:
    • 데이터를 공유할 때 UserDefaults를 활용합니다.
    • App Groups를 통해 동일한 UserDefaults 인스턴스를 생성하여 데이터를 저장하고 읽습니다.
let sharedUserDefaults = UserDefaults(suiteName: "group.com.example.myapp")
sharedUserDefaults?.setValue("Shared Data", forKey: "sharedKey")

위젯 딥링크

위젯 딥링크는 사용자를 앱의 특정 화면으로 이동시키는 기능을 제공합니다. 

  1. 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앱에서 마진 생기는 이슈 발생

위젯&nbsp;xcode 15, ios 17에서 margin 생김

 

아래 코드로 해결 

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
    }
}

[참고]

https://forums.developer.apple.com/forums/thread/731423

728x90

'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