Aug 16, 2021, TIL (Today I Learned) - App Extension

Inwoo Hwang·2021년 8월 26일
0
post-thumbnail

앱 확장 프로그램

앱 확장 프로그램을 사용하면 맞춤형 기능과 콘텐츠를 앱 외부로 확장할 수 있으며 사용자가 다른 앱 또는 시스템과 상호 작용하는 동안에도 앱 기능을 사용할 수 있습니다.

An app extension is not an app

it implements a specific, well scoped task that adheres to the policies defined by a particular extension point.

Host app

extension

호스트앱에서 다른 앱의 기능을 가져오는 것

Containing App

extension과 연결되어있는 본체 앱: ex: google photo

How an App Extension Communicates

An app extension communicates primarily with its host app, and does so in terms reminiscent of transaction processing

*트랜잭션은 작업의 완전성을 보장해주는 것입니다. 즉, 논리적인 작업 set을 모두 완벽하게 처리하거나 또는 처리하지 못할 경우에는 원 상태로 복구해서 작업의 일부만 적용되는 현상이 발생하지 않게 만들어주는 기능입니다. 사용자의 입장에서는 작업의 논리적 단위로 이해할 수 있고 시스템 입장에서는 데이터를 접근 또는 변경하는 프로그램의 단위가 됩니다.

  1. App extension 과 containing app 사이에는 직접적인 의사소통이 없다.
  2. Widget의 경우 Open URL을 통해 앱을 호출할 수 있다.
  3. Containing app과 app extension은 공유 자원에 접근이 가능하며, 읽기/쓰기 권한을 가진다.
    1. KeyChain 활용
    2. AppGroup생성 → UserDefault에 저장
    3. 모두를 타겟멤버로 삼는 파일에 전역변수 만들기

App Extension's Life Cycle

Because an app extension is not an app, its life cycle and environment are different. An app that a user employs to choose an app extension is called host

Let's Build an App: Landmark

import Foundation

var landmarks: [Landmark] = load("landmarkData.json")

func load<T: Decodable>(_ filename: String) -> T {
    let data: Data
    
    guard let file = Bundle.main.url(forResource: filename, withExtension: nil) else {
        fatalError("Couldn't find \(filename) in main bundle.")
    }
    
    do {
        data = try Data(contentsOf: file)
    } catch {
        fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
    }
    
    do {
        let decoder = JSONDecoder()
        return try decoder.decode(T.self, from: data)
    } catch {
        fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
    }
}
var landmarks: [Landmark] = load("landmarkData.json")

Widget Configuration

  1. Static Configuration
  2. Intent Configuration
    • For a widget with user-configurable properties. 유저가 상호작용할 수 있는 위젯
@main
struct LandmarksWidget: Widget {
    let kind: String = "LandmarksWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            LandmarksWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("LandmarksWidget")
        .description("세계 곳곳의 랜드마크 사진을 확인하세요")
    }
}

Provider: timeline을 제공하는 역할

struct LandmarksEntry: TimelineEntry {
    let date: Date
    let number: Int
}
struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> LandmarksEntry {
        LandmarksEntry(date: Date(), number: 2)
    }

    func getSnapshot(in context: Context, completion: @escaping (LandmarksEntry) -> ()) {
        let entry = LandmarksEntry(date: Date(), number: 2)
        completion(entry)
    }

    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [LandmarksEntry] = []

        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
        let currentDate = Date()
        for minuteOffset in 0 ..< 10 {
            let entryDate = Calendar.current.date(byAdding: .minute, value: minuteOffset, to: currentDate)!
            let entry = LandmarksEntry(date: entryDate, number: minuteOffset)
            entries.append(entry)
        }

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

placeholder: widget이 렌더링 되기 전에 나타날 화면을 전달하는 메서드

getSnapshot: widget gallery에서 어떤 위젯 화면이 보여질지 전달하는 메서드

getTimeLine: timeline 객체 속에 언제 무엇을 업데이트해야하는지 알려주는 entry들을 넣어줌. currentDate에 1분씩 더해진 형태로 시간과 정보를 저장합니다.

policy: .atEnd, .after(_:), never

atEnd: 정해진 timeLine의 모든 시간 정책이 끝난 뒤 reload timeline 해줌

.after(2 hr): 정해진 timeLine의 시간 정책을 다쓰기 전에 2시간이 지난 뒤 timeline을 reload

View를 만드는 단계

struct LandmarkView: View {
    var landmark: Landmark
    
    var body: some View {
        landmark.image
            .resizable()
            .aspectRatio(contentMode: .fill)
    }
}
struct LandmarksWidgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        LandmarkView(landmark: landmarks[entry.number])
    }
}

개발자 계정을 갖게 되면 userdefault와 keychain을 활용하여 앱 정보를 가져와서 위젯에 표현 해 줘보면 좋겠다. intent configuration을 통해서 user default에 정보를 작성하는 작업을 해 보면 좋겠다.

profile
james, the enthusiastic developer

0개의 댓글