Dynamic Island를 위한 ActivityKit_ 개념 학습

Zeto·2022년 12월 12일
0

Swift_Framework

목록 보기
1/6

iPhone 14pro부터 적용되는 다이내믹 아일랜드를 활용하기 위해 해당 기능을 제공하는 ActivityKit에 대한 학습을 해보고자 한다.
(Xcode ver 14.1부터 가능)

Live Activity 란?

  • iPhone의 잠금 화면과 다이내믹 아일랜드에 해당 앱의 최신 데이터를 표시할 수 있게 해주는 개념.
  • Live Activity에 대한 업데이트는 오직 ActivityKit으로만 가능하며 최대 8시간까지 지속 가능. (8시간을 초과하면 시스템에서 자동으로 종료 시킴)
  • 업데이트 데이터는 4kb 이하로만.

WidgetKit (ActivityKit 활용 위한 필수 개념)

  • Target > Widget Extension을 통해 템플릿 생성이 가능. (Widget 프로토콜을 준수하는 Struct에서 body 타입인 WidgetConfiguration으로 내부를 구현)
  • WidgetConfiguration의 종류는 세 가지.

    1) Static Configuration (고정 UI 위젯)
    2) Intent Configuration (사용자 설정 UI 위젯)
    3) Activity Configuration (Live Activity)

  • IntentTimelineProvider, TimelineEntry를 준수하는 각각의 Struct를 통해 위젯을 업데이트. (데이터와 업데이트 시점을 제공)

위의 요소들을 활용하여 지속적으로 최신 데이터를 표시해주는 위젯을 잠금화면이나 다이내믹 아일랜드에서 출력해줄 수 있도록 만들어줄 수가 있다.

ActivityKit 활성 방법

1) info.plist에서 Live Activity 활성화

info.plist에서 Supports Live Activities 키를 추가하고 Value를 Yes(True)로 설정.

2) Widget Extension을 Target에 추가

  1. File > New > Target에서 Widget Extension을 추가

  2. Live Activity를 사용할 것이므로 Include Live Activity를 선택하여 생성. (Xcode가 14.1일 경우 Widget Extension 추가 시, 자동으로 활성화 체크가 되어 있음)

3) 주요 파일 생성

위의 단계까지 마치면 총 3가지의 파일이 생성되는데 대략적인 개념은 아래와 같다.

1. DynamicIslandWidgetBundle
위젯의 UI를 출력해줄 body가 존재하며 해당 body는 각각 Widget과 LiveActivity를 생성해주고 있다.

@main
struct DynamicIslandWidgetBundle: WidgetBundle {
    var body: some Widget {
        DynamicIslandWidget()
        DynamicIslandWidgetLiveActivity()
    }
}

2. DynamicIslandWidget
기존의 WidgetKit에 대한 개념이 필요하다.

  • TimelineEntry (Protocol)
    위젯의 업데이트와 관련하여 어떤 시간에 어떤 데이터를 통해 진행할 지에 대한 정보를 담고 있으며 해당 Struct를 Array로 보낸다.

    struct SimpleEntry: TimelineEntry {
        let date: Date
        let configuration: ConfigurationIntent
    }
  • IntentTimelineProvider (Protocol)
    위젯을 업데이트 할 시기를 WidgetKit에게 알려주는 역할. 위에서 얘기한 프로토콜을 채택한 Struct를 Entry로 포함하며 위젯에 표시될 placeholder, 데이터를 fetch하여 출력해주는 getSnapshot, 타임라인과 관련된 설정을 다루는 getTimeline 메서드들이 존재한다.

    struct Provider: IntentTimelineProvider {
        func placeholder(in context: Context) -> SimpleEntry {
            SimpleEntry(date: Date(), configuration: ConfigurationIntent())
        }
    
        func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
            let entry = SimpleEntry(date: Date(), configuration: configuration)
            completion(entry)
        }
    
        func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
            var entries: [SimpleEntry] = []
    
            // Generate a timeline consisting of five entries an hour apart, starting from the current date.
            let currentDate = Date()
            for hourOffset in 0 ..< 5 {
                let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
                let entry = SimpleEntry(date: entryDate, configuration: configuration)
                entries.append(entry)
            }
    
            let timeline = Timeline(entries: entries, policy: .atEnd)
            completion(timeline)
        }
    }
  • EntryView
    위젯 뷰를 출력하는 Struct.

    struct DynamicIslandWidgetEntryView : View {
        var entry: Provider.Entry
    
        var body: some View {
            Text(entry.date, style: .time)
        }
    }
  • Widget (Protocol)
    제공된 Provider를 통해 Configuration을 생성하고 이를 통해 EntryView에 데이터를 전달하여 위젯 뷰를 출력시켜주는 작업을 진행.
    configurationDisplayName, description은 각각 위젯 생성 화면에서 앱 이름과 설명 라인에서 입력 값이 출력된다.

    struct DynamicIslandWidget: Widget {
        let kind: String = "DynamicIslandWidget"
    
        var body: some WidgetConfiguration {
            IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
                DynamicIslandWidgetEntryView(entry: entry)
            }
            .configurationDisplayName("My Widget")
            .description("This is an example widget.")
        }
    }

3. DynamicIslandWidget
Dynamic Island와 관련된 내용을 입력하는 파일이며 ActivityKit을 import한 상태이다.

  • ActivityAttributes (Protocol)
    Widget에서 보았던 TimelineEntry와 유사한 기능으로 시간에 따라 변화하는 값, 즉 상태에 대한 정의를 내려줄 수 있다. 다만 TimelineEntry와 달리 시작될 때의 상수 값도 포함하여 상태가 캡슐화한다. 이에 상태와 상수 값에 대한 구분을 위해 내부에서 ContentState를 associatedType으로 지정하도록 구현을 요구한다.

    struct DynamicIslandWidgetAttributes: ActivityAttributes {
        public struct ContentState: Codable, Hashable {
            // Dynamic stateful properties about your activity go here!
            var value: Int
        }
    
        // Fixed non-changing properties about your activity go here!
        var name: String
    }
  • ActivityConfiguration
    ActivityConfiguration이 Live Activity 시작 요청을 받으면 이와 함께 전달받은 ActivityAttributesContentStateActivityViewContext 타입으로 래핑해서 클로저 내부로 전달한다.

    struct DynamicIslandWidgetLiveActivity: Widget {
        var body: some WidgetConfiguration {
            ActivityConfiguration(for: DynamicIslandWidgetAttributes.self) { context in
                // Lock screen/banner UI goes here
                VStack {
                    Text("Hello")
                }
                .activityBackgroundTint(Color.cyan)
                .activitySystemActionForegroundColor(Color.black)
    
            } dynamicIsland: { context in
                DynamicIsland {
                    // Expanded UI goes here.  Compose the expanded UI through
                    // various regions, like leading/trailing/center/bottom
                    DynamicIslandExpandedRegion(.leading) {
                        Text("Leading")
                    }
                    DynamicIslandExpandedRegion(.trailing) {
                        Text("Trailing")
                    }
                    DynamicIslandExpandedRegion(.bottom) {
                        Text("Bottom")
                        // more content
                    }
                } compactLeading: {
                    Text("L")
                } compactTrailing: {
                    Text("T")
                } minimal: {
                    Text("Min")
                }
                .widgetURL(URL(string: "http://www.apple.com"))
                .keylineTint(Color.red)
            }
        }
    }

ActivityViewContext는 클로저 내부에서 context라는 상수명을 지니고 내부에 3가지의 프로퍼티를 지니고 있다.

  • let attributes: ActivityAttributes
  • let state: ContentState
  • let activityID: String (해당 Live Activity의 고유 식별자)

DynamicIslandWidget 파일을 ContentView에서 사용해야하기 때문에 해당 파일의 TargetMembership 체크가 필요

profile
중2병도 iOS가 하고싶어

0개의 댓글