iOS) KakaoQRcode ํด๋ก ์ฝ”๋”ฉ - Widget(2/2)

4 minute read

๐Ÿ˜‡ ๋ณธ๊ฒฉ์ ์œผ๋กœ ํด๋ก ์ฝ”๋”ฉ์„ ํ•ด๋ณด์ž

1๏ธโƒฃ ์•ฑ ์ด๋ฆ„ ๋ณ€๊ฒฝ

  • ์•ฑ ์ด๋ฆ„ : 1๋ฒˆ ๊ฒฐ์ •. [General] โ†’ [Identity] โ†’ [Display Name] ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•ด์ค€๋‹ค.

19

2๏ธโƒฃ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์œ„์ ฏ ์ƒ์„ฑ

์œ„์˜ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด์„œ ์˜๋ฌธ์„ ๊ฐ€์กŒ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ์ด๋ฆ„๋„ ์„ค๋ช…๋„ ํฌ๊ธฐ๋„ ๋‹ค๋ฅธ ์œ„์ ฏ๋“ค์„ ์–ด๋–ป๊ฒŒ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์„๊นŒ?(์• ๋‹ˆ๋ฉ”์ด์…˜์€ ์ฐธ ์ข‹๋‹ค ํฌ-)

๐Ÿ“Œ WidgetBundle

๋‹จ์ผ widget extension ์—์„œ ์—ฌ๋Ÿฟ ์œ„์ ฏ์„ ๋…ธ์ถœ์‹œํ‚ค๋Š”๋ฐ ์‚ฌ์šฉ๋˜๋Š” container.

์—ฌ๋Ÿฌ ์œ ํ˜•์˜ ์œ„์ ฏ์„ ์ง€์›ํ•˜๋ ค๋ฉด WidgetBundle ์„ ์ฑ„ํƒํ•˜๋Š” ๊ตฌ์กฐ์ฒด์— @main ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜์‹ญ์‹œ์˜ค.

  • apple developerโ€™s example code
@main
struct GameWidgets: WidgetBundle {
   var body: some Widget {
       GameStatusWidget()
       CharacterDetailWidget()
   }
}

๐Ÿ“Œ IntentTimelineProvider & TimelineProvider

์นด์นด์˜คํ†ก ์œ„์ ฏ์„ ์‚ดํŽด๋ณด๋ฉด ์ „๋ถ€ StaticConfiguration ์ฒ˜๋Ÿผ ์œ„์ ฏ์˜ ํŽธ์ง‘์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋ž˜์„œ body ํด๋กœ์ € ๋ฆฌํ„ด๊ฐ’์œผ๋กœ StaticConfiguration ์„ ์„ค์ •ํ–ˆ๋‹ค.

(target ๋ฅผ ๋งŒ๋“ค ๋•Œ ์ฒดํฌ๋ฐ•์Šค์— ๋”ฐ๋ผ์„œ ์ฝ”๋“œ๊ฐ€ ๋‹ฌ๋ผ์ง„๋‹ค.)

  • KakakoWidget_iOS_CloneCoding.swift

์ฃผ์„ ์ฒ˜๋ฆฌ ๋œ ๋ถ€๋ถ„๋“ค์ด IntentConfiguration ์— ํ•ด๋‹นํ•˜๋Š” ๊ฒฝ์šฐ๋“ค์ด๋‹ค.

import WidgetKit
import SwiftUI
//import Intents

//struct Provider: IntentTimelineProvider {
struct Provider: TimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
//        SimpleEntry(date: Date(), configuration: ConfigurationIntent())
        SimpleEntry(date: Date())
    }

//    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
    func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) {
//        let entry = SimpleEntry(date: Date(), configuration: configuration)
        let entry = SimpleEntry(date: Date())
        completion(entry)
    }

//    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
    func getTimeline(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)
            let entry = SimpleEntry(date: entryDate)
            entries.append(entry)
        }

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

struct SimpleEntry: TimelineEntry {
    let date: Date
//    let configuration: ConfigurationIntent
}

// ...

struct KakaoWidget_iOS_CloneCoding_Previews: PreviewProvider {
    static var previews: some View {
//        QRcodeWidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
        QRcodeWidgetEntryView(entry: SimpleEntry(date: Date()))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}

๐Ÿคจ ์—ฌ๋‹ด

  • ์นด์นด์˜คํ†ก์ด ์˜๋„ํ•œ๊ฒƒ์ธ์ง€ ์•„๋‹Œ์ง€ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒ โ€œ์ฆ๊ฒจ์ฐพ๊ธฐโ€ ์œ„์ ฏ์˜ ๊ฒฝ์šฐ๋Š” ๊ฐ™์€ ์œ„์ ฏ์—์„œ ๋‹ค๋ฅธ ํฌ๊ธฐ๋ฅผ ์‚ฌ์šฉํ–ˆ๋Š”๋ฐ(์œ„์ ฏ์„ ๋„˜๊ฒจ๋„ ๊ฐ™์€ ์„ค๋ช…์€ ๊ทธ๋Œ€๋กœ) โ€œํ†ก์บ˜๋ฆฐ๋”โ€ ์œ„์ ฏ์—์„œ๋Š” systemSmall ๊ณผ systemMedium ์„ ๋‹ค๋ฅธ ์œ„์ ฏ ๊ฐ™์€ ์„ค๋ช…์„ ์‚ฌ์šฉํ•œ๋‹ค.(์œ„์ ฏ์„ ๋„˜๊ธฐ๋ฉด ๊ฐ™์€ ์„ค๋ช…์ด ๋˜ ๋“ฑ์žฅ) ๊ฐ™์€ ์„ค๋ช…์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋ผ๋ฉด ๊ตณ์ด ๋‹ค๋ฅธ ์œ„์ ฏ์— ํ•  ์ด์œ ๋Š” ์—†์—ˆ์„ ํ…๋ฐ ์˜์•„ํ–ˆ๋‹ค.

์œ„์˜ ๊ฐœ๋…๋“ค์— ๋Œ€ํ•ด์„œ ์ดํ•ดํ•ด๋ดค์œผ๋‹ˆ ๊ตฌํ˜„ํ•ด๋ณด์ž!

๐Ÿ“Œ ์‚ฌ์ด์ฆˆ ๋ณ„๋กœ ๋ทฐ ๋Œ€์‘ + ์—ฌ๋Ÿฌ ์œ ํ˜•์˜ ์œ„์ ฏ ์ง€์›

์นด์นด์˜คํ†ก ์œ„์ ฏ์„ ์‚ดํŽด๋ณด๋ฉด ๊ฐ™์€ ์œ„์ ฏ์ด์ง€๋งŒ ์‚ฌ์ด์ฆˆ๋ณ„๋กœ ๋ทฐ๊ฐ€ ๋Œ€์‘๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

๋˜ํ•œ, ์—ฌ๋Ÿฌ ์œ ํ˜•์˜ ์œ„์ ฏ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.(์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์ ธ์„œ 2๊ฐ€์ง€ ์œ„์ ฏ๋งŒ ์ถ”๊ฐ€ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋‚จ๊ฒผ๋‹ค. ์ „์ฒด์ฝ”๋“œ๋Š” ๊นƒํ—ˆ๋ธŒ์— ์žˆ๋‹ค.)

  • KakakoWidget_iOS_CloneCoding.swift
// ...(๋‹ค๋ฅธ ์œ„์ ฏ๋“ค)

// โœ… Calender widget. ํ†ก์บ˜๋ฆฐ๋” ์—ญํ• ์„ ํ•˜๋Š” ์œ„์ ฏ ์ƒ์„ฑ.
struct CalenderWidgetEntryView: View {
    // โœ… ์œ„์ ฏ์˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜์— ์ ‘๊ทผํ•ด์„œ ์‚ฌ์ด์ฆˆ๋ณ„๋กœ ๋ทฐ๋ฅผ ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๋‹ค.
    @Environment(\.widgetFamily) var family: WidgetFamily
    var entry: Provider.Entry

    var body: some View {
        sizeBody()
    }
    
    @ViewBuilder
    func sizeBody() -> some View {
        switch family {
        case .systemSmall:
            Text("ํ†ก์บ˜๋ฆฐ๋” - small")
        case .systemMedium:
            Text("ํ†ก์บ˜๋ฆฐ๋” - medium")
        default:
            EmptyView()
        }
    }
}

struct CalenderWidget: Widget {
    let kind: String = "CalenderWidget"

    var body: some WidgetConfiguration {
        // โœ… IntentConfiguration ์˜ ๊ฒฝ์šฐ.
        // IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
        //     CalenderWidgetEntryView(entry: entry)
        // }
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            CalenderWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("ํ†ก์บ˜๋ฆฐ๋”")
        .description("์˜ˆ์ •๋œ ์ผ์ •์„ ์‰ฝ๊ฒŒ ํ™•์ธํ•˜๊ณ  ํ†ก์บ˜๋ฆฐ๋”์—\n๋น ๋ฅด๊ฒŒ ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค.")
        // โœ… ๋ฐฐ์—ด์— ๋‹ด๊ธด ์ˆœ์„œ ์ƒ๊ด€์—†์ด small -> medium -> large ์ˆœ์„œ๋‹ค.
        .supportedFamilies([.systemMedium, .systemSmall])
    }
}

// โœ… QRcode widget. QR์ฝ”๋“œ ์—ญํ• ์„ ํ•˜๋Š” ์œ„์ ฏ ์ƒ์„ฑ.
struct QRcodeWidgetEntryView : View {
    var entry: Provider.Entry

    var body: some View {
        Text("QR์ฝ”๋“œ")
    }
}

//@main
struct QRcodeWidget: Widget {
    let kind: String = "QRcodeWidget"

    var body: some WidgetConfiguration {
        // โœ… IntentConfiguration ์˜ ๊ฒฝ์šฐ.
        // IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
        //     QRcodeWidgetEntryView(entry: entry)
        // }
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            QRcodeWidgetEntryView(entry: entry)
        }
        .configurationDisplayName("QR์ฒดํฌ์ธ")
        .description("ํ™ˆ ํ™”๋ฉด์—์„œ QR์ฒดํฌ์ธ ํŽ˜์ด์ง€๋กœ\n ๋น ๋ฅด๊ฒŒ ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค.")
        .supportedFamilies([.systemSmall])
        
    }
}

// โœ… ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ ์œ„์ ฏ ์ถ”๊ฐ€
// โœ… ์—ฌ๊ธฐ์— @main ์ถ”๊ฐ€
@main
struct KakaoWidget: WidgetBundle {
    var body: some Widget {
        // ...(๋‹ค๋ฅธ ์œ„์ ฏ๋“ค)
        CalenderWidget()
        QRcodeWidget()
    }
}

๐Ÿ“Œ ๊ฒฐ๊ณผ

๊ฒฐ๊ณผ์ ์œผ๋กœ ์ด 4๊ฐœ(๋‚ดํ”„๋กœํ•„, ์ฆ๊ฒจ์ฐพ๊ธฐ(2๊ฐœ), ํ†ก์บ˜๋ฆฐ๋”(2๊ฐœ), QR์ฒดํฌ์ธ) ์˜ ์œ„์ ฏ์„ ๋งŒ๋“ค์–ด๋ณด์•˜๋‹ค.

3๏ธโƒฃ ํ•ด๋‹น ์œ„์ ฏ์œผ๋กœ ์•ฑ ๋‚ด์˜ ํŠน์ • ๋ทฐ ์ ‘๊ทผ

์ด์ œ ์œ„์ ฏ์„ ํ†ตํ•ด์„œ ์•ฑ ๋‚ด์˜ ํŠน์ • ๋ทฐ๋กœ ์ ‘๊ทผํ•˜๋„๋ก ํ•ด๋ณด๊ฒ ๋‹ค.

๐Ÿ“Œ ๊ตฌํ˜„

โ—๏ธ[widgetURL(:)](https://developer.apple.com/documentation/swiftui/view/widgeturl(:))

์‚ฌ์šฉ์ž๊ฐ€ ์œ„์ ฏ์„ ํด๋ฆญํ•  ๋•Œ containing app ์—์„œ ์—ด๋ฆด URL ์„ค์ •.

overview

์œ„์ ฏ์˜ ๋ทฐ๊ณ„์ธต์—์„œ ํ•˜๋‚˜์˜ widgetURL(_:) ๋งŒ ์ง€์›ํ•œ๋‹ค. ์—ฌ๋Ÿฌ ๋ทฐ์— widgetURL(_:) ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ž‘๋™๋˜์ง€ ์•Š๋Š”๋‹ค.

  • KakaoWidget_iOS_CloneCoding.swift
// QRcode widget
struct QRcodeWidgetEntryView : View {
    // โœ… AppDelegate ์—์„œ ํ™•์ธํ•  URL
    let url = URL(string: "qrcode")
    var entry: Provider.Entry

    var body: some View {
        Text("QR์ฝ”๋“œ")
            // โœ… ์ด ์ฒ˜๋Ÿผ ํ•˜๋‚˜์˜ widgetURL(_:) ๋งŒ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.
            .widgetURL(url)
    }
}

โ—๏ธ application(_:open:options:)

url ๋กœ ์ง€์ •๋œ ๋ฆฌ์†Œ์Šค๋ฅผ ์—ด๋ผ๊ณ  ์š”์ฒญํ•˜๊ณ  launch option ์˜ dictionary ์ œ๊ณต.

Return Value

delegate ๊ฐ€ ์š”์ฒญ์„ ์„ฑ๊ณต์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•œ ๊ฒฝ์šฐ true. URL ๋ฆฌ์†Œ์Šค ์—ด๊ธฐ ์‹œ๋„๊ฐ€ ์‹คํŒจํ•œ ๊ฒฝ์šฐ false.

  • AppDelegate.swift
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        let qrcodeLinkPath = "qrcode"
        
        // โœ… URL ๊ตฌ๋ฌธ๋ถ„์„
        guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true), let path = components.path else {
            return false
        }
        // โœ… ์ •์˜๋œ URL ์š”์ฒญ ์™ธ์˜ ์‹œ๋„๋Š” false.
        if path == qrcodeLinkPath {
            // โœ… qr์ฝ”๋“œ ํ™”๋ฉด์ „ํ™˜
            let nextVC = QRCodeViewController()
            nextVC.modalPresentationStyle = .overFullScreen
            window?.rootViewController?.present(nextVC, animated: true, completion: nil)
            
            return true
        } else {
            return false
        }
    }

๐Ÿ“Œ ๊ฒฐ๊ณผ

์ฐธ๊ณ  :

Apple Developer Documentation

iOS 14+ ) Widget

[SwiftUI] Widget ์œ„์ ฏ๋งŒ๋“ค๊ธฐ

๊นƒํ—ˆ๋ธŒ

GitHub - 28th-SOPT-iOS-CloneCoding/MiraClone-KimHyunGyu: ๐Ÿงš ์•„์š” ๋ฏธ๋ผํด๋ก ์ฝ”๋”ฉ ๊น€ํ˜„๊ทœ

Categories:

Updated: