🎸 [Swift] Live Activity μ‚¬μš©ν•΄ 보기

TygerΒ·2024λ…„ 7μ›” 14일
3

Swift with iOS

λͺ©λ‘ 보기
3/3
post-thumbnail

🎸 Live Activity μ‚¬μš©ν•΄ 보기

ActivityConfiguration | Apple Developer
Live Activities | Apple Developer

🍎 WidgetKit μ΄λž€ ?
πŸ”’ μž κΈˆν™”λ©΄ μœ„μ ― λ§Œλ“€κΈ°(WidgetKit)

이번 κΈ€μ—μ„œλŠ” WidgetKit을 μ‚¬μš©ν•˜λŠ” LiveActivity에 λŒ€ν•΄μ„œ μ‚΄νŽ΄λ³΄λ„λ‘ ν•˜κ² λ‹€.

LiveActivity ?

LiveActivityλŠ” iOS 16 λ²„μ „μ—μ„œ μƒˆλ‘œ λ„μž…λœ κΈ°λŠ₯이닀.

μ‚¬μš©μžκ°€ μ‹€μ‹œκ°„μœΌλ‘œ 진행 쀑인 μž‘μ—…μ„ ν™ˆ ν™”λ©΄μ΄λ‚˜ 잠금 ν™”λ©΄μ—μ„œ 직접 좔적할 수 μžˆλ„λ‘ μ§€μ›ν•˜μ—¬ μ‚¬μš©μžμ—κ²Œ μ‹€μ‹œκ°„μœΌλ‘œ μ—…λ°μ΄νŠΈλ˜λŠ” 정보λ₯Ό μ œκ³΅ν•  수 μžˆλ„λ‘ ν•˜λŠ” κΈ°λŠ₯이닀.

μ‹€μ‹œκ°„ 정보λ₯Ό ν‘œμ‹œν•˜κ³  μ—…λ°μ΄νŠΈ ν•  수 있으며, μŒμ‹ 배달, νƒμ‹œμ˜ 도착 μ‹œκ°„, μš΄λ™ κ²½κΈ° 점수 및 μ‹€μ‹œκ°„ 경둜 탐색 λ“±μ˜ μ„œλΉ„μŠ€μ—μ„œ μœ μš©ν•˜κ²Œ 정보λ₯Ό μ œκ³΅ν•΄ 쀄 수 μžˆλ‹€.

여기에 16.2 버전 λΆ€ν„° DynamicIslandλ₯Ό μ§€μ›ν•˜λŠ” λ””λ°”μ΄μŠ€μ— λŒ€ν•΄μ„œλŠ” κΈ°μ‘΄ λ…ΈμΉ˜ μ˜μ—­μ— 좔가적인 μ‹€μ‹œκ°„ 정보 μ œκ³΅λ„ κ°€λŠ₯ν•œ κΈ°λŠ₯이닀.

LiveActivityλŠ” WidgetKitκ³Ό ν•¨κ»˜ ꡬ성이 κ°€λŠ₯ν•˜κ³  ActivityKit을 μ‚¬μš©ν•΄ μ‹€μ‹œκ°„ 데이터λ₯Ό μ—…λ°μ΄νŠΈν•˜κ³  λ³€κ²½ν•  수 μžˆμ–΄ μ‚¬μš©μžκ°€ 앱을 열지 μ•Šλ”λΌλ„ μ›ν•˜λŠ” μ‹€μ‹œκ°„ 데이터λ₯Ό λ°›μ•„λ³Ό 수 μžˆμ–΄ μ‚¬μš©μž κ²½ν—˜μ„ 크게 ν–₯μƒμ‹œν‚¬ 수 μžˆλŠ” κ°•λ ₯ν•œ 도ꡬ이닀.

Add WidgetKit

LiveActivity μ‚¬μš©μ„ μœ„ν•΄μ„œλŠ” μœ„μ ―μ„ λ§Œλ“œλŠ” 방법과 λ™μΌν•˜κ²Œ WidgetKit을 μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.

File > New > Target

Widget Extension을 μ„ νƒν•΄μ£Όμž.

WidgetKit의 이름을 λ„£μ–΄μ£Όκ³  "include Live Activity"λ₯Ό μ„ νƒν•˜μ—¬ LiveActivity도 μΆ”κ°€ν•΄ μ£Όλ©΄ λœλ‹€.

μ—¬κΈ°μ„œ "include Configuration App Intent"λ₯Ό μ²΄ν¬ν•˜κ²Œ 되면 WidgetKit의 κΈ°λŠ₯ 쀑 ν•˜λ‚˜μΈ ν™ˆ μœ„μ ―μ˜ 생성 νƒ€μž…μ„ IntentConfiguration νƒ€μž…μœΌλ‘œ μƒμ„±λ˜κ²Œ 되고, 체크λ₯Ό ν•˜μ§€ μ•Šκ²Œ 되면 StaticConfiguration νƒ€μž…μ΄ μ μš©λœλ‹€.

ν™ˆ μœ„μ ―μ˜ μ’…λ₯˜λ₯Ό 잘 λͺ¨λ₯΄μ‹œλŠ” 뢄듀을 μœ„ν•΄ κ°„λ‹¨ν•˜κ²Œ μ„€λͺ…ν•˜μžλ©΄, ν™ˆ μœ„μ ―μ˜ νŽΈμ§‘μ΄ κ°€λŠ₯ν•œ μœ„μ ―μ„ IntentConfiguration νƒ€μž…μ΄λΌν•˜κ³  μœ„μ ―μ˜ νŽΈμ§‘μ΄ λΆˆκ°€λŠ₯ν•œ μœ„μ ―μ„ StaticConfiguration이라고 ν•œλ‹€.

WidgetKit의 μ „λ°˜μ μΈ λ‚΄μš©λ“€μ„ μžμ„Ένžˆ μ•Œκ³  μ‹Άλ‹€λ©΄ 이전에 μž‘μ„±ν•œ 글을 μ°Έκ³ ν•˜μ‹œκΈΈ λ°”λž€λ‹€.

μ •μƒμ μœΌλ‘œ 생성이 되면 μœ„μ ― 파일과 LiveActivity μœ„μ ― 파일이 각각 μƒμ„±λ˜μ–΄ μžˆμ„ 것이닀.

이 쀑 LiveActivity νŒŒμΌμ„ μ‚¬μš©ν•˜λ©΄ 되고, μœ„μ ― νŒŒμΌμ€ ν™ˆ/잠금 μœ„μ ―μ— μ‚¬μš©λ˜λŠ” νŒŒμΌμ΄λ‹ˆ ν•„μš”μ— 따라 ν•΄λ‹Ή νŒŒμΌμ„ μ‚¬μš©ν•˜μ‹œκ±°λ‚˜ μ§€μ›Œμ£Όμ‹œλ©΄ λœλ‹€.

만일 본인의 μ„œλΉ„μŠ€κ°€ ν™ˆ/잠금 μœ„μ ―μ€ μ‚¬μš©ν•  ν•„μš”κ°€ μ—†κ³ , μ˜€λ‘œμ§€ LiveActivity만 μ‚¬μš©ν•˜κΈΈ μ›ν•œλ‹€λ©΄ μ•„λž˜ 처럼 μ½”λ“œλ₯Ό μ μš©ν•΄ μ£Όλ©΄ λœλ‹€.
WidgetKit을 μΆ”κ°€ν•˜λ©΄ κΈ°λ³Έμ μœΌλ‘œλŠ” ν™ˆ μœ„μ ―μ€ μ§€μ›λ˜κ²Œ λœλ‹€.

WidgetKit에 μƒμ„±λœ WidgetBundle νŒŒμΌμ— μΆ”κ°€λ˜μ–΄ μžˆλŠ” ν™ˆ μœ„μ ― κ΄€λ ¨ 객체λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠμœΌμ‹œλ©΄ λœλ‹€.

@main
struct LiveActivityWidgetBundle: WidgetBundle {
    var body: some Widget {
//        LiveActivityWidget()
        LiveActivityWidgetLiveActivity()
    }
}

LiveActivityλŠ” iOS 16.2버전 λΆ€ν„° μ‚¬μš©μ΄ κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ—, iOS 16.2μ΄ν•˜ 버전을 νƒ€κ²Ÿν•˜λŠ” μ„œλΉ„μŠ€μΈ 경우 μ•„λž˜μ™€ 같이 μ½”λ“œλ₯Ό μΆ”κ°€ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.

@main
struct TimerWIdgetBundle: WidgetBundle {
    var body: some Widget {
        TimerWIdget()
        if #available(iOS 16.2, *) {
            TimerWIdgetLiveActivity()
        }
    }
}

μš°μ„  LiveActivityκ°€ μ •μƒμ μœΌλ‘œ λ…ΈμΆœλ˜λŠ”μ§€ ν…ŒμŠ€νŠΈλ₯Ό ν•΄λ³΄μž.

Widget Extension을 μƒμ„±ν•œ 이름이 저와 λ™μΌν•œ 경우라면 μ•„λž˜ μ½”λ“œλ₯Ό μ‚¬μš©ν•΄ LiveActivityλ₯Ό μ‹€ν–‰μ‹œμΌœ λ³Ό 수 μžˆλ‹€.

import SwiftUI
import ActivityKit

struct ContentView: View {
    var body: some View {
        VStack {
            Button {
                let attributes = TimerWIdgetAttributes(name: "Timer Sample")
                let contentState = TimerWIdgetAttributes.ContentState(emoji: "πŸ˜€")
                let content = ActivityContent(state: contentState, staleDate: nil)
                do {
                    _ = try Activity<TimerWIdgetAttributes>.request(
                        attributes: attributes,
                        content: content,
                        pushType: nil
                    )
                } catch {
                    print("LiveActivityManager: Error in LiveActivityManager: \(error.localizedDescription)")
                }
            }label: {
                Text("Show")
            }
        }
        .padding()
    }
}

만일 attributes 이름이 λ‹€λ₯΄λ‹€λ©΄ Widet ν΄λ”μ•ˆμ— LiveActivity νŒŒμΌμ—μ„œ 찾을 수 μžˆλ‹€.

μ—¬κΈ°μ„œ μ½”λ“œλ₯Ό λ˜‘κ°™μ΄ λ„£μ—ˆλŠ”λ°λ„ attributes 객체λ₯Ό 찾지 λͺ»ν•˜λŠ” μ—λŸ¬κ°€ λ°œμƒν–ˆμ„ 것이닀.

이건 λΉŒλ“œ νƒ€κ²Ÿμ— ν¬ν•¨λ˜μ–΄ μžˆμ§€ μ•ŠκΈ° λ•Œλ¬Έμ— λ°œμƒν•œ 문제둜 Target Membershipμ—μ„œ μΆ”κ°€ν•΄ μ£Όλ©΄ λœλ‹€.

LiveActivity 파일둜 κ°€μ„œ Target Membershipμ•ˆμ— μ•± νƒ€κ²Ÿμ„ μ„ νƒν•΄μ„œ ν¬ν•¨μ‹œμΌœ μ£Όλ©΄ 객체λ₯Ό 찾을 수 μžˆλ‹€.

이제 μ‹€ν–‰μ‹œμΌœ 보도둝 ν•˜μž.

LiveActivityκ°€ 싀행이 λ˜μ§€ μ•ŠλŠ”λ°, 아직 LiveActivity 지원을 ν™œμ„±ν™” μ‹œν‚€μ§€ μ•Šμ•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

Info.plist에 "Supports Live Activities"λ₯Ό μΆ”κ°€ν•˜κ³  Valueλ₯Ό "YES"둜 μ„€μ •ν•΄μ£Όλ©΄ λœλ‹€.

이제 μ •μƒμ μœΌλ‘œ LiveActivityκ°€ μ‹€ν–‰λ˜λŠ” 것을 확인할 수 μžˆλŠ”λ°, μž κΈˆν™”λ©΄μ—μ„œ LiveActivityκ°€ λ…ΈμΆœμ΄ 되고, 앱을 λ°±κ·ΈλΌμš΄λ“œλ‘œ λ³΄λƒˆμ„ λ•Œμ—λ„ DynamicIsland에 등둝이 잘 λœκ²ƒμ„ 확인할 수 μžˆλ‹€.

Supports Live Activities
NO YES

기본적인 섀정은 μ™„λ£Œν–ˆμœΌλ‹ˆ 본격적으둜 LiveActivity에 λŒ€ν•΄μ„œ μ•Œμ•„λ³΄λ„λ‘ ν•˜μž.

LiveActivity

이제 본격적으둜 LiveActivity의 UI ꡬ쑰 λΆ€ν„° μ‚¬μš© λ°©λ²•κΉŒμ§€ μžμ„Ένžˆ 닀뀄보도둝 ν•˜κ³˜λ‹€.

UI ꡬ쑰

LiveActivityλ₯Ό μ‚¬μš©ν•˜κΈ° μ•žμ„œ μ •ν™•νžˆ μ–΄λ–€ λΆ€λΆ„μ—μ„œ μ‚¬μš©ν•˜λŠ”μ§€λ₯Ό μ•Œκ³  μžˆμ–΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— UI μš”μ†Œκ°€ 어디에 μ‚¬μš©λ˜λŠ” 건지λ₯Ό 확인해 보도둝 ν•˜μž.

LiveActivity 파일의 μ½”λ“œλ₯Ό 확인해보면, Widget μ½”λ“œλ₯Ό 확인할 수 μžˆλ‹€.

λ°”λ‘œ μ—¬κΈ° μ½”λ“œμ—μ„œ UIλ₯Ό κΎΈλ©°μ£Όλ©΄ λ˜λŠ”λ° ActivityConfiguration, dynamicIsland μš”μ†Œλ₯Ό ν™•μΈν•˜λ©΄ λœλ‹€.

μœ„μ˜ 이미지λ₯Ό 보면 크게 3κ°€μ§€μ˜ ꡬ쑰λ₯Ό κ°€μ§€κ²Œ λœλ‹€.

ActivityConfigurationμš”μ†Œλ₯Ό μ‚¬μš©ν•΄μ„œ μž κΈˆν™”λ©΄μ—μ„œ 보여쀄 UIλ₯Ό μž‘μ—…ν•  수 있고, DynamicIsland의 κΈ°λ³Έ μš”μ†ŒλŠ” compact, λ‹€μ΄λ‚˜λ―Ήμ•„μΌλžœλ“œμ˜ ν™•λŒ€λœ μƒνƒœμ˜ μš”μ†ŒλŠ” DynamicIslandExpandedRegion을 μ‚¬μš©ν•  수 μžˆλ‹€.

DynamicIslandλ₯Ό 자주 μ‚¬μš©ν•˜μ‹œλŠ” 뢄듀은 ꡬ쑰λ₯Ό 잘 μ•„μ‹œκ² μ§€λ§Œ 크게 3κ°€μ§€μ˜ UI νƒ€μž…μ΄ μ‚¬μš©λœλ‹€.

μ—¬κΈ°μ„œ minimal은 2개 μ΄μƒμ˜ LiveActivity앱이 ꡬ동 μ€‘μΌλ•Œμ—λ§Œ λ…ΈμΆœλ˜λŠ” ꡬ쑰이고, 2개 쀑 ν•˜λ‚˜λŠ” DynamicIsland에 배치되고, λ‚˜λ¨Έμ§€ ν•˜λ‚˜λŠ” 독립적인 μ˜μ—­μ— ν‘œμ‹œλ˜λŠ”λ° 기쀀은 μ•±μ—μ„œ μ •ν•œ μš°μ„ μˆœμœ„λ₯Ό λ”°λ₯΄κ²Œ λœλ‹€.

expand compact minimal

ActivityKit

μœ„μ—μ„œ μƒ˜ν”Œ μ½”λ“œλ₯Ό μ‚¬μš©ν•΄μ„œ LiveActivityλ₯Ό μ‹€ν–‰ν•΄ λ΄€λŠ”λ° μ •ν™•νžˆ LiveActivityλ₯Ό μ–΄λ–»κ²Œ μ œμ–΄ν•΄μ•Ό ν•˜λŠ”μ§€μ— λŒ€ν•΄μ„œ μ•Œμ•„λ³΄μž.

LiveActivityλŠ” ActivityKit을 μ‚¬μš©ν•΄μ„œ μ œμ–΄λ₯Ό ν•  수 μžˆλ‹€.

ActivityKit을 ν†΅ν•œ μ œμ–΄λŠ” μ‹œμž‘, μ—…λ°μ΄νŠΈ, μ’…λ£Œ μ΄λ ‡κ²Œ 3가지 κΈ°λŠ₯을 μ œκ³΅ν•œλ‹€.

start

λ¨Όμ € LiveActivityλ₯Ό ν™œμ„±ν™” 해보도둝 ν•˜μž.

requestλ₯Ό μ‚¬μš©ν•΄ LiveActivityλ₯Ό μ‹€ν–‰ν•  수 있으며, attributes, content, pushType λͺ¨λ‘ ν•„μˆ˜λ‘œ 등둝해야 ν•œλ‹€.

attributes : 정적 μ–΄νŠΈλ¦¬λ·°νŠΈ 객체

let attributes = TimerWIdgetAttributes(name: "Timer Sample")

content : 동적 μ–΄νŠΈλ¦¬λ·°νŠΈ 객체
contentStateλ₯Ό μ‚¬μš©ν•΄ λ™μ μœΌλ‘œ λ³΄μ—¬μ£Όκ³ μž ν•˜λŠ” 데이터λ₯Ό λ„˜κ²¨μ€„ 수 있으며, ActivityContent 객체λ₯Ό 생성해 μ£Όλ©΄ λœλ‹€.

let contentState = TimerWIdgetAttributes.ContentState(emoji: "πŸ˜€")
let content = ActivityContent(state: contentState, staleDate: nil,relevanceScore: 1)

- staleDate: λ§Œλ£Œμ‹œκ°„μœΌλ‘œ, nil μ§€μ •μ‹œ 8μ‹œκ°„ ν›„ μ’…λ£Œ (μž κΈˆν™”λ©΄ 12μ‹œκ°„)
- relevanceScore: minimal μš°μ„ μˆœμœ„λ‘œ 0~1둜 μ„€μ •

pushType : μ—…λ°μ΄νŠΈ ν™œλ™μ— λŒ€ν•œ ν‘Έμ‹œ μ•Œλ¦Ό μ—¬λΆ€
nil μ‚¬μš©μ‹œ μ•±μ˜ μ—…λ°μ΄νŠΈλ‘œλ§Œ ν™œλ™μ„ 변경함

μ•„λž˜μ™€ 같이 LiveActivityλ₯Ό μ‹œμž‘ν•˜λ©°, μ‹œμž‘ν•¨κ³Ό λ™μ‹œμ— μž κΈˆν™”λ©΄μ—λ„ λ…ΈμΆœλ˜κΈ° μ‹œμž‘ν•œλ‹€.

private func start() {
	let attributes = TimerWIdgetAttributes(name: "Timer Sample")
	let contentState = TimerWIdgetAttributes.ContentState(emoji: "πŸ˜€")
	let content = ActivityContent(state: contentState, staleDate: nil,relevanceScore: 1)
	do {
		_ = try Activity<TimerWIdgetAttributes>.request(
                attributes: attributes,
                content: content,
                pushType: nil
            )
	} catch {
		print("LiveActivityManager: Error in LiveActivityManager: \(error.localizedDescription)")
	}
}

LiveActivityλŠ” μ΅œλŒ€ λ…ΈμΆœμ‹œκ°„μ΄ μ •ν•΄μ Έ μžˆλŠ”λ°, μ’…λ£Œ μ‹œκ°„μ„ λ”°λ‘œ μ§€μ •ν•˜κ±°λ‚˜ μ’…λ£Œλ₯Ό μ‹œμΌœμ£Όμ§€ μ•ŠλŠ”λ‹€λ©΄ μ΅œλŒ€ 8μ‹œκ°„ κΉŒμ§€ λ…ΈμΆœμ΄ κ°€λŠ₯ν•˜κ³  μž κΈˆν™”λ©΄μ—μ„œλŠ” μ΅œλŒ€ 12μ‹œκ°„ κΉŒμ§€ λ‚¨μ•„μžˆμ„ 수 μžˆλ‹€.

동적 데이터 μ‚¬μš©κ³Ό κ΄€λ ¨ν•΄μ„œλŠ” LiveActivityκ°€ 자체 μƒŒλ“œλ°•μŠ€μ— μ‹€ν–‰λ˜κΈ° λ•Œλ¬Έμ— λ„€νŠΈμ›Œν¬ μ ‘κ·Όμ΄λ‚˜ μœ„μΉ˜ 데이터 μ—…λ°μ΄νŠΈ λ“±μ˜ 데이터λ₯Ό 받을 수 μ—†κΈ° λ•Œλ¬Έμ— 동적 데이터λ₯Ό μ‚¬μš©ν—ˆκ³ μž ν•œλ‹€λ©΄ μ•±μ˜ ActivityKit을 ν†΅ν•œ 동적 데이터 μ „λ‹¬μ΄λ‚˜ ActivityKit의 ν‘Έμ‹œλ₯Ό 등둝해 ν‘Έμ‹œ μ•Œλ¦Όμ„ 톡해 데이터λ₯Ό 얻을 수 μžˆλ‹€.
ν‘Έμ‹œ μ•Œλ¦Όμ„ ν†΅ν•œ 동적 데이터λ₯Ό κ°€μ Έμ˜¬ λ•Œμ—λ„ 4KB μ΄μƒμ˜ λ°μ΄ν„°λŠ” 전달 받을 수 μ—†μœΌλ‹ˆ 이 점을 κ³ λ €ν•˜μ…”μ•Ό ν•œλ‹€.

update

μ΄λ²ˆμ—λŠ” μ•±μ˜ 이벀트λ₯Ό λ°œμƒμ‹œμΌœ LiveActivity의 데이터λ₯Ό μ—…λ°μ΄νŠΈν•˜λŠ” 방법에 λŒ€ν•΄μ„œ μ•Œμ•„λ³΄μž.

λ¨Όμ € ν˜„μž¬ ν™œμ„±ν™”λœ LiveActivity의 정보λ₯Ό κ°€μ Έμ˜€λ„λ‘ ν•˜μž.
ActivityKit의 activities의 ν˜„μž¬ ν™œλ™μ˜ 정보가 λ‹΄κ²¨μžˆλ‹€.

for activity in Activity<TimerWIdgetAttributes>.activities {
	// current LiveActivity
}

ν™œλ™μ— λŒ€ν•œ 정보λ₯Ό 가져와 ν™œμ„±ν™”λœ μ•‘ν‹°λΉ„ν‹°μ˜ 데이터λ₯Ό μ—…λ°μ΄νŠΈν•  수 μžˆλ‹€.

ContentState에 데이터λ₯Ό λ³€κ²½ν•˜κ³ μž ν•˜λŠ” λ°μ΄ν„°λ‘œ 등둝해주면 λœλ‹€.

update의 contentλŠ” ν•„μˆ˜λ‘œ μ „λ‹¬ν•΄μ•Όν•˜λŠ” 값은 μ•„λ‹ˆλ‹€.

private func update() async{
	let status = TimerWIdgetAttributes.ContentState(emoji: "Update")
	let content = ActivityContent(state: status, staleDate: nil)
	for activity in Activity<TimerWIdgetAttributes>.activities {
            await activity.update(content)
	}
}

end

λ§ˆμ§€λ§‰μœΌλ‘œ LiveActivity의 ν™œλ™μ„ μ’…λ£Œμ‹œν‚€λŠ” 방법이닀.

μ—¬κΈ°μ„œ μ’…λ£Œλ₯Ό ν•˜λŠ” 방법은 2가지 방법이 μžˆλŠ”λ°, λ‹¨μˆœνžˆ μ’…λ£Œλ§Œ ν•  수 있고 μ•„μ˜ˆ μž κΈˆν™”λ©΄μ—μ„œ μ§€μ›Œμ€„ 수 μžˆλ‹€.

μ΄λ²ˆμ—λ„ update 방법과 λ™μΌν•˜κ²Œ ν˜„μž¬ ν™œλ™ 쀑인 μ•‘ν‹°λΉ„ν‹°λ₯Ό κ°€μ Έμ™€μ„œ μ‹€ν–‰ 쀑인 μ•‘ν‹°λΉ„ν‹°λ₯Ό μ’…λ£Œν•΄ 주도둝 ν•˜μž.

private func end() async{
	let finalStatus = TimerWIdgetAttributes.ContentState(emoji: "End")
	let finalContent = ActivityContent(state: finalStatus, staleDate: nil)
	for activity in Activity<TimerWIdgetAttributes>.activities {
		await activity.end(finalContent)
	}
}

dismissalPolicy

ν™œλ™μ„ μ’…λ£Œν•˜λŠ” λ°©λ²•μ—λŠ” 3κ°€μ§€μ˜ νƒ€μž…μ΄ μžˆλ‹€.

.default
LiveActivity κΈ°λŠ₯만 μ’…λ£Œ

await activity.end(finalContent,dismissalPolicy: .default)

.immediate
LiveActivity κΈ°λŠ₯ μ’…λ£Œ 및 제거

await activity.end(finalContent,dismissalPolicy: .immediate)

.after(Date)
LiveActivityλ₯Ό μƒμ„±ν•œ μ‹œκ°„μ— μ’…λ£Œ 및 제거

await activity.end(finalContent,dismissalPolicy: .after(Date()))
default immediate after(10초 ν›„)

Attributes

LiveActivity의 μš”μ†Œ 쀑 μ΄μ–΄μ„œ μ‚΄νŽ΄λ³Ό μš”μ†ŒλŠ” λ°”λ‘œ μœ„μ—μ„œ μ‚¬μš©ν•΄λ³Έ
ActivityAttributes 객체이닀.

ν•΄λ‹Ή 객체의 ContentStateλŠ” ν•„μˆ˜λ‘œ μ‚¬μš©ν•΄μ•Ό ν•˜λ©° ν•΄λ‹Ή ν•„λ“œμ— 동적 객체λ₯Ό 생성할 수 μžˆλ„λ‘ ν•˜λ©΄ λœλ‹€.

동적 객체의 λ³€μˆ˜λŠ” ν•˜λ‚˜λ§Œ μ‚¬μš©ν•΄μ•Ό ν•˜λŠ” 것은 μ•„λ‹ˆκ³ , μ—¬λŸ¬ λ³€μˆ˜λ“€μ„ μ‚¬μš©ν•˜μ—¬λ„ λ˜λ‹ˆ LiveActivity에 λ³΄μ—¬μ£Όκ³ μž ν•˜λŠ” μ μ ˆν•œ ꡬ쑰λ₯Ό λ§Œλ“€μ–΄ ν™œμš©ν•΄ μ£Όλ©΄ λœλ‹€.

참고둜 κΈ°λ³Έ μ½”λ“œμ— μƒμ„±λ˜μ–΄ μžˆλŠ” name λ³€μˆ˜λŠ” ν•„μˆ˜κ°€ μ•„λ‹ˆλ‹€.

struct TimerWIdgetAttributes: ActivityAttributes {
    public struct ContentState: Codable, Hashable {
        // Dynamic stateful properties about your activity go here!
        var emoji: String
    }

    // Fixed non-changing properties about your activity go here!
    var name: String
}

Widget

λ§ˆμ§€λ§‰μœΌλ‘œ UIλ₯Ό κ΅¬μ„±ν•΄μ£ΌλŠ” Widget 객체에 λŒ€ν•΄μ„œ μ•Œμ•„λ³΄μž.

Widget은 WidgetConfiguration ν”„λ‘œν† μ½œμ„ λ°˜ν™˜ν•΄μ•Ό ν•˜λ©° LiveActivityλŠ” ActivityConfiguration을 μ‚¬μš©ν•  수 μžˆλ‹€.

ActivityConfiguration(for: TimerWIdgetAttributes.self) { context in
         // Lock Scrren UI
		} dynamicIsland: { context in
        // DynamicIsland UI
	}
)

DynamicIsland

LiveActivity의 μž κΈˆν™”λ©΄κ³Ό ν•¨κ»˜ ꡬ성이 κ°€λŠ₯ν•œ DynamicIsland에 λŒ€ν•΄μ„œλ„ μ•Œμ•„λ³΄λ„λ‘ ν•˜μž.

μž κΈˆν™”λ©΄μ—μ„œλŠ” μ•±μ˜ ν™œμ„±ν™” μƒνƒœμ—μ„œλ„ μž κΈˆν™”λ©΄μ— λ…ΈμΆœλ˜μ§€λ§Œ, DynamicIslandλŠ” 앱이 λΉ„ν™œμ„± μƒνƒœμΈ κ²½μš°μ—λ§Œ λ…ΈμΆœλ˜κ²Œ λœλ‹€.

DynamicIsland의 UIλŠ” μœ„μ—μ„œλ„ κ°„λ‹¨ν•˜κ²Œ μ‚΄νŽ΄λ΄€μ§€λ§Œ, 크게 3κ°€μ§€λ‘œ κ΅¬μ„±λœλ‹€.

expand compact minimal

λ¨Όμ €, compact λͺ¨λ“œμ— λŒ€ν•΄μ„œ μ‚΄νŽ΄λ³΄λ©΄, ν™œμ„±ν™”λœ λ‹€μ΄λ‚˜λ―Ήμ•„μΌλžœλ“œκ°€ 단 ν•˜λ‚˜μ˜ μ•±λ§Œ μžˆλŠ” κ²½μš°μ— λ…ΈμΆœλ˜λŠ” λͺ¨λ“œμ΄λ©° LeadingSide, TrailingSide의 UIλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

struct TimerWIdgetLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: TimerWIdgetAttributes.self) { context in
        //
        } dynamicIsland: { context in
            DynamicIsland {
                DynamicIslandExpandedRegion(.leading) {
                }
                DynamicIslandExpandedRegion(.trailing) {
                }
                DynamicIslandExpandedRegion(.bottom) {
                }
            } compactLeading: {
            	// Compact λͺ¨λ“œ Leading Side
            } compactTrailing: {
            	// Compact λͺ¨λ“œ Trailing Side
            } minimal: {
            }
        }
    }
}

μ΄μ–΄μ„œ ν™œμ„±ν™” 된 λ‹€μ΄λ‚˜λ―Ήμ•„μΌλžœλ“œκ°€ 2개 μ΄μƒμ˜ 앱이 μžˆλŠ” κ²½μš°μ— μ‚¬μš©λ˜λŠ” λͺ¨λ“œμΈ minimal에 λŒ€ν•΄μ„œ μ‚΄νŽ΄λ³΄μž.

minimal λͺ¨λ“œμ‹œ attached, detached의 μ˜μ—­μ— UIλ₯Ό λ…ΈμΆœν•˜κ²Œ λ˜λŠ”λ°, UI에 λŒ€ν•œ ꡬ성은 compact λͺ¨λ“œμ²˜λŸΌ λ³„λ„λ‘œ ꡬ성해주지 μ•Šμ•„λ„ ν•˜λ‚˜μ˜ UIλ₯Ό μ‚¬μš©ν•˜κ²Œ λœλ‹€.

struct TimerWIdgetLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: TimerWIdgetAttributes.self) { context in
        //
        } dynamicIsland: { context in
            DynamicIsland {
                DynamicIslandExpandedRegion(.leading) {
                }
                DynamicIslandExpandedRegion(.trailing) {
                }
                DynamicIslandExpandedRegion(.bottom) {
                }
            } compactLeading: {
            } compactTrailing: {
            } minimal: {
            	// minimal λͺ¨λ“œ 
            }
        }
    }
}

λ§ˆμ§€λ§‰μœΌλ‘œ expand λͺ¨λ“œμ— λŒ€ν•΄μ„œ μ‚΄νŽ΄λ³΄λ„λ‘ ν•˜κ² λ‹€.

expandλͺ¨λ“œλŠ” ν™œμ„±ν™”λœ λ‹€μ΄λ‚˜λ―Ήμ•„μΌλžœλ“œλ₯Ό 길게 ν„°μΉ˜ ν–ˆμ„ λ•Œμ— 더 λ§Žμ€ 정보λ₯Ό 보여주기 μœ„ν•΄ μ‚¬μš©λ˜λŠ” λͺ¨λ“œμ΄λ‹€.

μ—¬κΈ°μ„œλŠ” leading, trailing, bottom, center μ΄λ ‡κ²Œ 4κ°€μ§€μ˜ μ˜μ—­μ„ ꡬ성할 수 μžˆλŠ”λ° 각 μ˜μ—­λ“€μ€ μ˜΅μ…”λ„λ‘œ ν•„μˆ˜λŠ” μ•„λ‹ˆμ§€λ§Œ λ°˜λ“œμ‹œ ν•˜λ‚˜μ˜ μ˜μ—­μ— λŒ€ν•΄μ„œλŠ” μ½”λ“œλ₯Ό μ‚¬μš©ν•΄ μ£Όμ–΄μ•Ό ν•œλ‹€.

struct TimerWIdgetLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: TimerWIdgetAttributes.self) { context in
        //
        } dynamicIsland: { context in
            DynamicIsland {
                DynamicIslandExpandedRegion(.leading) {
                	// expand λͺ¨λ“œ leading μ˜μ—­
                }
                DynamicIslandExpandedRegion(.trailing) {
                	// expand λͺ¨λ“œ trailing μ˜μ—­
                }
                DynamicIslandExpandedRegion(.bottom) {
                	// expand λͺ¨λ“œ bottom μ˜μ—­
                }
                DynamicIslandExpandedRegion(.center) {
                	// expand λͺ¨λ“œ center μ˜μ—­
                }
            } compactLeading: {
            } compactTrailing: {
            } minimal: {
            }
        }
    }
}

λ‹€μ΄λ‚˜λ―Ήμ•„μΌλžœλ“œ UIλ₯Ό μž‘μ—…ν•  λ•Œμ— λ°˜λ“œμ‹œ κ³ λ €ν•΄μ•Ό ν•˜λŠ” 뢀뢄이 μžˆλŠ”λ°, λ°”λ‘œ μ˜€λ²„ν”Œλ‘œμš°κ°€ λ°œμƒν•˜κ²Œ 되면 ν™œμ„±ν™”κ°€ μ•ˆλ  수 μžˆμœΌλ‹ˆ 이 뢀뢄을 κ³ λ €ν•΄ μ‚¬μ΄μ¦ˆ 기쀀을 λ§žμΆ°μ€˜μ•Ό ν•œλ‹€.

마무리

WidgetKit의 κΈ°λŠ₯ 쀑 LiveActivityλ₯Ό μ‚¬μš©ν•˜μ—¬ μž κΈˆν™”λ©΄κ³Ό λ‹€μ΄λ‚˜λ―Ήμ•„μΌλžœλ“œμ˜ UI μ‚¬μš© 및 μ œμ–΄ 방법에 λŒ€ν•΄μ„œ μ•Œμ•„λ΄€λ‹€.

이번 κΈ€μ—μ„œ 직접 예제λ₯Ό μ‚¬μš©ν•΄ LiveActivityλ₯Ό κ΅¬μ„±ν•˜λ©΄μ„œ 글을 μž‘μ„±ν•˜λ €κ³  ν•˜μ˜€μ§€λ§Œ κΈ€μ˜ λ‚΄μš©μ΄ λ„ˆλ¬΄ κΈΈμ–΄μ Έμ„œ λ³„λ„λ‘œ 글을 μ˜¬λ¦¬λ„λ‘ ν•˜κ² λ‹€.

κ°μ‚¬ν•©λ‹ˆλ‹€.

profile
Flutter Developer

0개의 λŒ“κΈ€

κ΄€λ ¨ μ±„μš© 정보