본문 바로가기

iOS 앱 개발자 프로젝트

[iOS] Widget - IntentConfiguration (2)

  • Widgets are rendered in a separated process.
  • Changes are driven by timeline entries.
  • Reloads from interactions are guaranteed.

 

 

위젯은 [iOS] Widget - IntentConfiguration (1)에서 말한대로,

TimelineProvider를 정의하는데 이 녀석이 Entries를 return하고, 각각의 Entry가 위젯의 모델이 된다.

위젯이 보여질 때 시스템이 Widget Extension Process를 실행(launch) 시킨 후

위젯의 TimelineProvider에게 Entries를 요청한다. 

 

Provider 부분의 코드를 먼저 살펴보면,

Provider는 IntentTimelineProvider 프로토콜을 구현한 구조체로,

위젯에 필요한 데이터를 제공하고 타임라인을 관리하는 역할을 하고 있다. 

 


 

Provider part

 

1. 타임라인 항목 (Entry)

  • SimpleEntry 구조체 : 위젯에서 표시할 데이터 항목을 정의
  • TimelineEntry 프로토콜을 준수하며, date, title, isCompleted라는 세 개의 속성 추가
struct SimpleEntry: TimelineEntry {
    let date: Date
    let title: String
    let isCompleted: Bool
}

 

 

 

2. Provider 구조체

  • Provider 구조체는 IntentTimelineProvider 프로토콜을 구현
  • Entry와 Intent 타입을 정의
struct Provider: IntentTimelineProvider {
    typealias Entry = SimpleEntry
    typealias Intent = TodoListIntent

 

 

 

3. placeholder(in:)

  • 위젯이 처음 로드될 때, 또는 데이터가 아직 로드되지 않았을 때 표시할 플레이스홀더 항목을 반환
  • 샘플 데이터를 사용하여 빠르게 로드할 수 있도록 했다.
func placeholder(in context: Context) -> SimpleEntry {
    SimpleEntry(date: Date(), title: "Sample Task", isCompleted: false)
}

 

 

 

4. getSnapshot(for:in:completion:)

  • 위젯의 스냅샷을 반환하는 함수
  • 사용자가 위젯을 미리보기 할 때 표시할 데이터를 제공
  • 이 함수도 샘플 데이터를 사용하여 빠르게 반환할 수 있다.
func getSnapshot(for configuration: TodoListIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
    let entry = SimpleEntry(date: Date(), title: "Sample Task", isCompleted: false)
    completion(entry)
}

 

 

 

5. getTimeline(for:in:completion:) 

  • 타임라인 항목을 반환하는 함수
  • 위젯에 표시할 데이터의 배열을 생성하고 반환
  • Core Data에서 할 일 목록을 가져와 랜덤으로 하나를 선택하여 SimpleEntry를 만든다. (할 일 목록 없으면 플레이스홀더 항목 추가)
  • Timeline 객체를 생성하여 완료 핸들러로 반환
func getTimeline(for configuration: TodoListIntent, in context: Context, completion: @escaping (Timeline<SimpleEntry>) -> ()) {
    var entries: [SimpleEntry] = []

    // Core Data에서 할 일 목록을 가져옴
    let todos = CoreDataManager.shared.fetchTodos()

    if let randomTodo = todos.randomElement() {
        let entry = SimpleEntry(date: Date(), title: randomTodo.title, isCompleted: randomTodo.iscompleted)
        entries.append(entry)
    } else {
        // 할 일 목록이 없으면 플레이스홀더 항목 추가
        let entry = SimpleEntry(date: Date(), title: "No Tasks", isCompleted: false)
        entries.append(entry)
    }

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

 

 

요청된 Entries가 View Builder에게 공급되면, 

View Builder는 공급된 Entries를 이용해 View를 생성한다.

그리고 생성된 Views의 Representation은 디스크에 아카이빙되는데,

특정 Entry를 표시할 시간에 시스템은 디스크에 아카이브 해둔 위젯의 representation을 

프로세스에서 디코딩하고 렌더링한다.

 

즉, View Code가 아카이빙을 하는 동안에만 실행이 되기 때문에 View의 representation은

시스템 프로세스에 의해서만 렌더링이 된다.

 

 

출처 : https://inblog.ai/lyoodong/widget

 

 

 

정적인(static) 위젯이라면 문제되지 않겠지만,

우리 앱은 위젯 화면에서 버튼을 탭 해서 메인 앱의 정보를 업데이트 시키는 '동적' 위젯이 되어야 하기에,

reloadTimelines(ofKind:) 메소드를 호출해야 한다.

이 메소드를 호출하면 Entry를 다시 생성하고, 새로운 Views를 복사해서 디스크에 아카이빙한다. 

 


 

코드에서 View 부분을 살펴보며 이해해보자.

위젯의 뷰는 TodoWidgetEntryView 구조체에서 정의되며,

이 구조체는 위젯이 화면에 표시될 때 어떻게 보일지를 결정한다.

 

 

View part

 

 

1. TodoWidgetEntryView 구조체

  • entry 속성은 Provider.Entry 타입의 데이터 항목을 받아온다.
  • body는 SwiftUI의 뷰를 구성한다. (Swift UI 부분에 대한 자세한 내용은 일단 생략) 
struct TodoWidgetEntryView: View {
    var entry: Provider.Entry
    
    var body: some View {
        VStack {
            Text(entry.title) // entry 는 코어데이터에서 가져오는 데이터
                .font(.headline)
                .background(.red)
            Text(entry.isCompleted ? "Completed" : "Not Completed")
                .font(.subheadline)
                .foregroundColor(entry.isCompleted ? .green : .red)
                .background(.red)
            HStack {
                Text("안녕하세요")
                Spacer()
                Text("반갑습니다")
            }
            .background(.red)
        }
        .padding()
        .background(.cyan)
    }
}

 

 

 

Widget 정의 

 

TodoWidget 구조체

  • TodoWidget 구조체는 Widget 프로토콜을 구현
  • kind는 위젯의 고유 식별자를 나타내는 문자열
  • body 속성은 위젯의 구성 방법을 정의
struct TodoWidget: Widget {
    let kind: String = "TodoWidget"
    
    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: TodoListIntent.self, provider: Provider()) { entry in
            TodoWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("Todo Widget")
        .description("Displays a random todo.")
    }
}

 

 

+  IntentConfiguration

  • IntentConfiguration은 위젯의 구성과 관련된 인텐트를 정의
  • kind, intent, provider를 설정하여 위젯의 데이터 제공자와 인텐트를 연결
  • TodoWidgetEntryView를 사용하여 위젯의 뷰를 정의
  • .configurationDisplayName과 .description으로 위젯의 이름과 설명을 설정