Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

iOS 개발일지

LiveActivity로 실시간 현황을 보여주자! (feat.DynamicIsland) 본문

iOS

LiveActivity로 실시간 현황을 보여주자! (feat.DynamicIsland)

Lia's iOS 2024. 1. 9. 08:45

 

LiveActivity란 앱 내의 정보를 잠금화면에서 보여줄 수 있는 기능입니다!

ActivityKit을 사용하며, iOS16.1 이상 버전에서 사용 가능합니다.

배달의 민족에서 많이 보셨을 것 같은데요~

 

뭔가 신기술같고... 멋있고... 이쁘지 않나요?

저는 개인적으로 좋아하던 기능이라, 직접 구현하려니 조금 신났네요ㅋㅋㅋㅋㅎ

아무튼, 단계별로 따라오면 전혀 어렵지 않습니다!

 

 


 

 

widget 자 우선 File -> New -> Target을 선택하고~

 

 

Widget을 검색해 추가해줍니다. Include Live Activity를 꼭 체크해주세요!

 

 

그리고 plist에 가서 Supports Live Activities를 추가하고, Value를 YES로 바꾸면 기본 셋팅은 끝!

 

각 파일 설명

AppIntent : 사용자가 위젯을 길게 눌러 [위젯 편집] 선택 시 보이는 정보를 구성하는 파일
Widget : Widget을 구성하는 파일
WidgetLiveActivity : LiveActivity와 DynamicIsland를 구성하는 파일
WidgetBundle : Widget 번들 파일

 

참고로, Widget Extension을 추가하면 Widget이 자동으로 추가됩니당

Live Activity만 구현하고 싶다면 WidgetBundle 파일에서 Widget을 주석 처리!

import WidgetKit
import SwiftUI

@main
struct TestWidgetBundle: WidgetBundle {
    var body: some Widget {
//        TestWidget()
        TestWidgetLiveActivity()
    }
}

 

 


 

 

import ActivityKit
import WidgetKit
import SwiftUI

struct TestWidgetAttributes: ActivityAttributes {
    public struct ContentState: Codable, Hashable {
        // 실시간 변동 값
        var emoji: Int
    }

    // 고정 값
    let name: String
}

@available(iOS 16.1, *)
struct TestWidgetLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: TestWidgetAttributes.self) { context in
            // Lock screen/banner UI goes here
            VStack {
                Text("Hello \(context.attributes.name) \(context.state.emoji)")
            }
            .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 \(context.state.emoji)")
                    // more content
                }
            } compactLeading: {
                Text("L")
            } compactTrailing: {
                Text("T \(context.state.emoji)")
            } minimal: {
                Text(context.state.emoji)
            }
            .widgetURL(URL(string: "http://www.apple.com"))
            .keylineTint(Color.red)
        }
    }
}

 

Attributes의 ContentState 내부에는 실시간으로 변동시킬 동적인 프로퍼티를 정의하고

그 아래에는 객체 생성 시 값을 한번만 초기화 할 정적인 프로퍼티를 정의하면 됩니다.

 

그리고 dynamicIsland를 볼 수 있는 기기라면, Live Activity가 활성화된 동안 dynamicIsland도 같이 보여주기 때문에 dynamicIsland도 구현해주어야 합니다.

이건 LiveActivity를 전부 끝낸 후 맨 아래에서 다시 보겠습니다.

 

방금 만든 이 LiveActivity 파일의 Target Membership에 Project를 추가해야 앱에서 접근이 가능하겠죠?

체크해줍시다.

 

 


 

Live Activity 시작하기

 

자 이제 실제로 Live Activity를 실행시켜보겠습니다.

저는 Live Activity를 시작할 ViewController가 여러개이기 때문에, 재사용이 가능하도록 Manager 파일을 따로 만들어줬어요.

 

import Foundation
import ActivityKit

@available(iOS 16.1, *)
@objc class TestLiveActivityManager: NSObject {
    private var activity: Activity<TestWidgetAttributes>?
    
    @objc static let shared = TestLiveActivityManager()
    private init(activity: Activity<TestWidgetAttributes>? = nil) {
        self.activity = activity
    }
    
    @objc func onLiveActivity() {
        guard ActivityAuthorizationInfo().areActivitiesEnabled else { return }
        
        // 정적 프로퍼티 초기화
        let attribute = TestWidgetAttributes(name: "name")
        
        // 동적 프로퍼티 기본값 초기화
        let state = TestWidgetAttributes.ContentState(emoji: "emoji")
        
        do {
            // live activity 시작
            self.activity = try Activity.request(attributes: attribute, contentState: state)
        } catch {
            print(error)
        }
    }
    
    @objc func offLiveActivity() {
        Task {
            await activity?.end(using: nil, dismissalPolicy: .immediate)
        }
    }
    
    @objc func updateLiveActivity(emoji: String) {
        Task {
            let newState = TestWidgetAttributes.ContentState(emoji: emoji)
            await self.activity?.update(using: newState)
        }
    }

 

 

특정 시점에 LiveActivity가 바로 닫히길 원한다면, end에서 dismissalPolicy를 .immediate로 설정해주세요!

여기까지 왔다면 이제 사용하고자 하는 시점에서 이렇게 호출해주기만 하면 됩니다. 너무 간단하죠?

TestLiveActivityManager.shared.onLiveActivity()
TestLiveActivityManager.shared.offLiveActivity()
TestLiveActivityManager.shared.updateLiveActivity(emoji: "smail")

 

 


 

Dynamic Island 구성하기

 

 

다이나믹 아일랜드는 크게 expanded, compact, minimal 세 가지 형태가 있습니다.

 

expanded 상태에서는 leading, trailing, center, bottom 으로 영역이 나뉘어져 있고

compact 상태에서는 사이드에 각각 leading side, trailing side가 있습니다.

마지막으로 다이나믹 아일랜드를 사용하는 또 다른 앱이 있을 경우, 한쪽 사이드를 내어주어야 하기 때문에 한 공간만 차지할 수 있습니다.

 

 

        } 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 \(context.state.emoji)")
                    // more content
                }
            } compactLeading: {
                Text("L")
            } compactTrailing: {
                Text("T \(context.state.emoji)")
            } minimal: {
                Text(context.state.emoji)
            }
            .widgetURL(URL(string: "http://www.apple.com"))
            .keylineTint(Color.red)
        }

 

처음 Live Activity 파일이 생성되면 기본적으로 작성되어있는 코드입니다.

깔끔하게 정리되어 있어서 그림과 함께 보면 한눈에 봐도 알기 쉽죠?

 

 

 

틀린 부분이나 궁금하신 점 있으면 댓글로 알려주세요 :)