iOS) KakaoQRcode ํด๋ก ์ฝ๋ฉ - Widget(2/2)
๐ ๋ณธ๊ฒฉ์ ์ผ๋ก ํด๋ก ์ฝ๋ฉ์ ํด๋ณด์
1๏ธโฃ ์ฑ ์ด๋ฆ ๋ณ๊ฒฝ
- ์ฑ ์ด๋ฆ : 1๋ฒ ๊ฒฐ์ . [General] โ [Identity] โ [Display Name] ์์ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํด์ค๋ค.
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
}
}
๐ ๊ฒฐ๊ณผ
์ฐธ๊ณ :
[SwiftUI] Widget ์์ ฏ๋ง๋ค๊ธฐ
๊นํ๋ธ
GitHub - 28th-SOPT-iOS-CloneCoding/MiraClone-KimHyunGyu: ๐ง ์์ ๋ฏธ๋ผํด๋ก ์ฝ๋ฉ ๊นํ๊ท