관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - SwiftUI] 2. 위젯 Widget 사용 방법 - API 데이터 로드와 위젯UI 업데이트 본문

iOS 응용 (SwiftUI)

[iOS - SwiftUI] 2. 위젯 Widget 사용 방법 - API 데이터 로드와 위젯UI 업데이트

jake-kim 2022. 10. 2. 22:49

1. 위젯 Widget 사용 방법 - WidgetKit, WidgetFamily

2. 위젯 Widget 사용 방법 - API 데이터 로드와 위젯UI 업데이트

3. 위젯 Widget 사용 방법 - 위젯 딥링크 구현 방법 (widgetURL)

4. 위젯 Widget 사용 방법 - 위젯 이미지 로드 방법

5. 위젯 Widget 사용 방법 - Provisioning Profile 등록 (WidgetExtension)

 

 

Widget

Widget에 표출할 API 준비

https://meowfacts.herokuapp.com/?count=1
  • response
{
   "data":[
      "The way you treat kittens in the early stages of it's life will render it's personality traits later in life."
   ]
}
  • URLSession을 통한 호출
private func getTexts(completion: @escaping ([String]) -> ()) {
  // https://github.com/wh-iterabb-it/meowfacts
  guard
    let url = URL(string: "https://meowfacts.herokuapp.com/?count=1")
  else { return }
  URLSession.shared.dataTask(with: url) { data, response, error in
    guard
      let data = data,
      let textModel = try? JSONDecoder().decode(TextModel.self, from: data)
    else { return }
    completion(textModel.datas)
  }.resume()
}

struct TextModel: Codable {
  enum CodingKeys : String, CodingKey {
    case datas = "data"
  }
  let datas: [String]
}

API 로드하여 Widget에 표출하는 방법

  • API 관련 모델 추가
    • SimpleEntry는 기존에 있는 모델이므로 여기에 texts 프로퍼티 추가
//  MyWidget.swift

struct TextModel: Codable {
  enum CodingKeys : String, CodingKey {
    case datas = "data"
  }
  let datas: [String]
}

struct SimpleEntry: TimelineEntry {
  let date: Date
  let texts: [String]
}
  • API 조회 메소드 추가
    • getTexts(completion:)
struct Provider: IntentTimelineProvider {  

  ...
  
  private func getTexts(completion: @escaping ([String]) -> ()) {
    guard
      let url = URL(string: "https://meowfacts.herokuapp.com/?count=1")
    else { return }
    URLSession.shared.dataTask(with: url) { data, response, error in
      guard
        let data = data,
        let textModel = try? JSONDecoder().decode(TextModel.self, from: data)
      else { return }
      completion(textModel.datas)
    }.resume()
  }
}
  • @main에서 title, description 수정
    • configurationDisplayName()
    • description()
@main
struct MyWidget: Widget {
  let kind: String = "MyWidget"
  
  var body: some WidgetConfiguration {
    IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
      MyWidgetEntryView(entry: entry)
    }
    .configurationDisplayName("위젯 예제")
    .description("랜덤 텍스트를 불러오는 위젯 예제입니다")
  }
}
  • UI가 보여질 MyWidgetEntryView 구현
struct MyWidgetEntryView : View {
  var entry: Provider.Entry
  
  private var randomColor: Color {
    Color(
      red: .random(in: 0...1),
      green: .random(in: 0...1),
      blue: .random(in: 0...1)
    )
  }
  
  var body: some View {
    ZStack {
      randomColor.opacity(0.7)
      ForEach(entry.texts, id: \.hashValue) { text in
        LazyVStack { // Widget은 스크롤이 안되므로, List지원 x (대신 VStack 사용)
          Text(text)
            .foregroundColor(Color.white)
            .lineLimit(1)
          Divider()
        }
      }
    }
  }
}

Provider 구현

  • 위젯의 데이터를 언제 불러오고 언제 refresh 결정하는 Widget의 핵심
struct Provider: IntentTimelineProvider {
  func placeholder(in context: Context) -> SimpleEntry {
    // TODO
  }
  
  func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
    // TODO
  }
  
  func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    // TODO
  }
  
  // 예제 텍스트를 불러올 API
  private func getTexts(completion: @escaping ([String]) -> ()) {
    // https://github.com/wh-iterabb-it/meowfacts
    guard
      let url = URL(string: "https://meowfacts.herokuapp.com/?count=1")
    else { return }
    URLSession.shared.dataTask(with: url) { data, response, error in
      guard
        let data = data,
        let textModel = try? JSONDecoder().decode(TextModel.self, from: data)
      else { return }
      completion(textModel.datas)
    }.resume()
  }
}
  • Provider에서는 SimpleEntry 모델이 자주 등장
    • 언제(date), 무엇(texts)를 표출할것인지 명시
struct SimpleEntry: TimelineEntry {
  let date: Date
  let texts: [String]
}
  • placeholder(in:)
    • 데이터를 불러오기 전에 표출될 placeholder
func placeholder(in context: Context) -> SimpleEntry {
  SimpleEntry(date: Date(), texts: ["Empty"])
}
  • getSnapshot(for:in:completion:)
    • 데이터 로드 담당 메소드
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
  getTexts { texts in
    let entry = SimpleEntry(date: Date(), texts: texts)
    completion(entry)
  }
}
  • getTimeline(for:in:completion:)
    • 데이터를 언제 리프레시 할지 결정
      • 지금으로부터 3분후마다 리프레시
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
  getTexts { texts in
    let currentDate = Date()
    let entry = SimpleEntry(date: currentDate, texts: texts)
    let nextRefresh = Calendar.current.date(byAdding: .minute, value: 3, to: currentDate)!
    let timeline = Timeline(entries: [entry], policy: .after(nextRefresh))
    completion(timeline)
  }
}

구현 완료)

* 전체 코드: https://github.com/JK0369/ExRandomText

Comments