๐ŸŽ iOS

[Dynamic Islandโ›ฐ๏ธ] Live Activity๋กœ ํ”ผ์ž ๋ฐฐ๋‹ฌ์„ ํ•˜์ž.๐Ÿ• - ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ

gom1n 2025. 5. 29. 22:33

์•ˆ๋…•ํ•˜์„ธ์š”...

์˜ค๋Š˜์€ ๋‹ค์ด๋‚˜๋ฏน ์•„์ผ๋žœ๋“œ์— ๋Œ€ํ•ด ์˜ค๋žœ๋งŒ์— ๊ธ€์„ ์จ๋ณผ๊ฑด๋ฐ,

์‚ฌ์‹ค ๋‹ค์ด๋‚˜๋ฏน ์•„์ผ๋žœ๋“œ๊ฐ€ ๋‚˜์˜จ ์ง€๋Š” ๊ฝค ๋์Šต๋‹ˆ๋‹ค. 2023๋…„์ด์—ˆ๋‚˜?

 

๊ทธ๋Ÿฌ๋‚˜ ๊ทธ๋™์•ˆ ๊ด€์‹ฌ์„ ๊ฐ–์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

์™œ๋ƒ๋ฉด ์ œ ํฐ์€ ๋‹ค์ด๋‚˜๋ฏน ์•„์ผ๋žœ๋“œ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ์•„์ดํฐ12์˜€๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.ใ…‹ใ…‹ใ…‹

 

 

๊ทธ๋Ÿฌ๋‚˜,

์ตœ๊ทผ์— ํฐ์„ ๋ฐ”๊พธ๊ณ  ๋‚œ ํ›„ ๊ฐ‘์ž๊ธฐ ์‹ ๊ฒฝ์“ฐ์ด๋”๋ผ๊ตฐ์š”...

๋‹ค์ด๋‚˜๋ฏน ์•„์ผ๋žœ๋“œ... ๊ท€์—ฝ๋„ค... ๋‚˜๋„ ํ•ด๋ณผ๊นŒ...? ใ…Ž_ใ…Ž

 

 

์ƒ๊ฐ๋ณด๋‹ค ์–ด๋ ต์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

์ผ๋‹จ ์œ„์ ฏ๊ณผ ๊ฐ™์ด SwiftUI ๊ธฐ๋ฐ˜์œผ๋กœ ์ด๋ฃจ์–ด์ง€๋ฉฐ, ์ƒˆ๋กœ์šด ํƒ€๊ฒŸ์œผ๋กœ ์ถ”๊ฐ€ํ•ด์ค˜์•ผํ•˜๊ณ ,

์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๋ณ€ํ™”๋Š” ์•ฑ์—์„œ ์กฐ์ ˆํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

 

 

File > New > Target > 'Widget Extension' ์œผ๋กœ ์œ„์ ฏ ์ต์Šคํ…์…˜์„ ์ถ”๊ฐ€ํ•ด์ฃผ๋Š”๋ฐ,

Include Live Activities๋ฅผ ๊ผญ ์ฒดํฌํ•ด์ค˜์•ผํ•ฉ๋‹ˆ๋‹ค.

 

 

 

๊ทธ๋ฆฌ๊ณ , ์•ฑ ํƒ€๊ฒŸ์—์„œ ๊ฐ„๋‹จํ•œ UI๋ฅผ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.

์ €๋Š” ํ”ผ์ž ๋ฐฐ๋‹ฌ์„ ์ฃผ์ œ๋กœ ์‚ผ์•„, ํƒญ ์‹œ ๋ฐฐ๋‹ฌ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๋ฒ„ํŠผ์„ ๋‚˜์—ดํ–ˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ•

 

 

 

ํ”ผ์ž๋ฅผ ๋จน๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‹ค์ด๋‚˜๋ฏน ์•„์ผ๋žœ๋“œ๋กœ ๋ณด์—ฌ์ค„ Attributes๋“ค์„ ์ •์˜ํ•ด์ค˜์•ผํ•ฉ๋‹ˆ๋‹ค.

struct PizzaDeliveryAttributes: ActivityAttributes {
    public struct ContentState: Codable, Hashable {
        var deliveryStage: DeliveryStage
        
        /// ... 
    }
}

 

 

๋‹ค์ด๋‚˜๋ฏน ์•„์ผ๋žœ๋“œ์˜ UI๋ฅผ ๊พธ๋ฐ‰๋‹ˆ๋‹ค.

์ €๋Š” ์ž„์˜๋กœ ProgressBar๋กœ ์ƒํƒœ๊ฐ’ ๋ณ€ํ™”๋ฅผ ์‰ฝ๊ฒŒ ๋ˆˆ์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ฒŒ๋” ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

struct PizzaDeliveryLiveActivity: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: PizzaDeliveryAttributes.self) { context in
        
            // ์ด ๊ณณ์— UI๋ฅผ ๊พธ๋ฐ‰๋‹ˆ๋‹ค. 
            // ์ €๋Š” ์ž„์˜๋กœ ProgressBar๋ฅผ ์ •์˜ํ•ด ๋ณด์—ฌ์ฃผ์—ˆ๋‹ค์ฃ .ใ…‹
            
            VStack(alignment: .leading, spacing: 16) {
                
                ProgressView(value: progress(for: context.state.deliveryStage))
                            .progressViewStyle(.linear)
                            .tint(.orange)
                            .padding(.horizontal, 16)
                            .padding(.top, 8)
                
                Spacer(minLength: 8)
            }
            .activityBackgroundTint(.cyan)
            .activitySystemActionForegroundColor(.black)

        } dynamicIsland: { context in
            DynamicIsland {
                // Expanded
                DynamicIslandExpandedRegion(.leading) {
                    Text("๐Ÿ•")
                }
                DynamicIslandExpandedRegion(.trailing) {
                    Text(context.state.deliveryStageTitle)
                }
                DynamicIslandExpandedRegion(.bottom) {
                    ProgressView(value: progress(for: context.state.deliveryStage))
                                .progressViewStyle(.linear)
                                .tint(.orange)
                                .padding(8)
                }
            } compactLeading: {
                Text("๐Ÿ•")
            } compactTrailing: {
                Text(context.state.deliveryIcon)
            } minimal: {
                Text("๐Ÿ•")
            }
        }
    }
    
    func progress(for stage: DeliveryStage) -> Double {
        switch stage {
        case .checking: return 0.1
        case .cooking: return 0.4
        case .delivering: return 0.8
        case .delivered: return 1.0
        }
    }
}

 

 

์•ฑ ํƒ€๊ฒŸ์—์„œ Live Activity๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  + ์—…๋ฐ์ดํŠธํ•˜๊ณ  + ์ข…๋ฃŒํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

func startPizzaDeliveryActivity() {
    let attributes = PizzaDeliveryAttributes()
    let initialContentState = PizzaDeliveryAttributes.ContentState(deliveryStage: .checking)

    do {
        let _ = try Activity<PizzaDeliveryAttributes>.request(
            attributes: attributes,
            contentState: initialContentState,
            pushType: nil
        )
    } catch {
        print("Failed to start activity: \(error)")
    }
}
    
func updatePizzaDeliveryActivity(to stage: DeliveryStage) async {
    for activity in Activity<PizzaDeliveryAttributes>.activities {
        await activity.update(using: .init(deliveryStage: stage))
    }
}
    
func endPizzaDeliveryActivity() async {
    for activity in Activity<PizzaDeliveryAttributes>.activities {
        await activity.end(using: .init(deliveryStage: .delivered), dismissalPolicy: .immediate)
    }
}

 

 

 

๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ์‹ฌ๊ฐํ•˜๊ฒŒ ๊ท€์—ฌ์›€

์ฐธ๊ณ ๋กœ ํ”ผ์ž๋Š” ๋ฐ”์งˆ ํ”ผ์ž๋กœ ์ถ”์ •(?) ์ค‘์ž…๋‹ˆ๋‹ค. ์ œ ์ง€ํ”ผํ‹ฐ๊ฐ€ ๋งŒ๋“ค์–ด์ค€ ๊ฒƒ์ด์—ฌ์š”.

 

 

๋‹ค์ด๋‚˜๋ฏน ์•„์ผ๋žœ๋“œ๊ฐ€ ์ž‘์•„์กŒ์„ ๋• ์ด๋ ‡๊ฒŒ ๋ณด์ž…๋‹ˆ๋‹ค. ๊ท€์—ฝ๋‹ค.