Search
Duplicate

iOS Widget

์ƒ์„ฑ์ผ
2024/08/01 07:34
ํƒœ๊ทธ
์ง€ํ˜œ๋กœ์šด ํšŒ์‚ฌ์ƒํ™œ

Swift Widget

๊ฐ„๋‹จ ์‚ฌ์šฉ๋ฒ•

File โ†’ New โ†’ Target โ†’ Widget Extension ์ƒ์„ฑ
โ€ข
Live Activity(Dynamic island) ์—ฌ๋ถ€์™€, Intent ๊ตฌ์„ฑ(๊ธฐ์ดˆ ์„ค์ •)์„ ํฌํ•จํ•  ๊ฒƒ์ธ์ง€๋„ ์„ค์ • ๊ฐ€๋Šฅ
โ€ข
scheme ๋˜ํ•œ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ

ย ์œ„์ ฏ ํด๋” ํŒŒํ—ค์น˜๊ธฐ

1. Bundle (MyWidgetBundle.swift)

// MyWidgetBundle.swift import WidgetKit import SwiftUI @main struct MyWidgetBundle: WidgetBundle { var body: some Widget { MyWidget() } }
Swift
๋ณต์‚ฌ
โ€ข
Widget ํ”„๋กœํ† ์ฝœ์„ ์ฑ„ํƒํ•œ ๊ตฌ์กฐ์ฒด๋ฅผ ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
โ€ข
์—ฌ๋Ÿฌ ์œ ํ˜•์˜ ์œ„์ ฏ์„ ์ง€์›ํ•˜๋ ค๋ฉด ์—ฌ๊ธฐ์— ์†์„ฑ์„ ์ถ”๊ฐ€ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
๊ณต์‹๋ฌธ์„œ ์˜ˆ)
๊ฒŒ์ž„์—๋Š” ๊ฒŒ์ž„์— ๋Œ€ํ•œ ์š”์•ฝ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์œ„์ ฏ ํ•˜๋‚˜, ๊ฐœ๋ณ„ ์บ๋ฆญํ„ฐ์— ๋Œ€ํ•œ ๋””ํ…Œ์ผํ•œ ์ •๋ณด๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์œ„์ ฏ ํ•˜๋‚˜, ์ด๋ ‡๊ฒŒ WidgetBundle์— ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค.
@main struct GameWidgets: WidgetBundle { var body: some Widget { GameStatusWidget() CharacterDetailWidget() } }
Swift
๋ณต์‚ฌ

2. MyWidget

ํ•ด๋‹น ํŒŒ์ผ๋กœ ๋“ค์–ด๊ฐ€๋ณด๋ฉด ๋‹ค์–‘ํ•œ ๊ตฌ์กฐ์ฒด๋“ค์ด ์ถ”๊ฐ€๊ฐ€ ๋˜์–ด์žˆ๋‹ค.
struct Provider: IntentTimelineProvider {} struct SimpleEntry: TimelineEntry {} struct MyWidgetEntryView: View {} struct MyWidget: Widget {}
Swift
๋ณต์‚ฌ

Provider

โ€ข
์œ„์ ฏ์˜ ์ปจํ…์ธ ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๋‚ ์งœ์™€ ์‹œ๊ฐ„์„ ์ง€์ •ํ•˜๊ณ , ์œ„์ ฏ์„ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•œ๋‹ค.
์„ธ ๊ฐ€์ง€ ํ•จ์ˆ˜๊ฐ€ ์กด์žฌ
โ€ข
placeholder
func placeholder(in context: Context) -> SimpleEntry { SimpleEntry(date: Date(), configuration: ConfigurationIntent()) }
Swift
๋ณต์‚ฌ
โ—ฆ
์œ„์ ฏ์ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ธฐ ์ „ ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™”๋ฉด์— ๋ณด์—ฌ์ฃผ์—ฌ์•ผ ํ•  ๊ฐ’๋“ค์„ ์„ค์ •ํ•˜๋Š” ํ•จ์ˆ˜
โ—ฆ
ํ˜„์žฌ ์‹œ๊ฐ„์— ๋Œ€ํ•œ date์™€ configuration์„ ์ƒ์„ฑํ•ด ๋ณด์—ฌ์ฃผ๊ณ  ์žˆ๋Š” ๋ชจ์Šต
โ€ข
getShapshot
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { let entry = SimpleEntry(date: Date(), configuration: configuration) completion(entry) }
Swift
๋ณต์‚ฌ
โ—ฆ
์œ„์ ฏ์„ ๊ณ ๋ฅผ ๋•Œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์—์„œ ๋ณด์—ฌ์ง€๋Š” ๋ฐ์ดํ„ฐ๋“ค์„ ์„ค์ •ํ•˜๋Š” ๋‹จ๊ณ„
โ—ฆ
์ด๋ฏธ ๋‚˜ํƒ€๋‚œ ๊ฒฝ์šฐ completion์„ ํ˜ธ์ถœํ•˜๊ณ , ์œ„์ ฏ์˜ ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜ ์˜ค๋ž˜ ๊ฑธ๋ฆด ๊ฒฝ์šฐ, ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•ด ๋ณด์—ฌ์ค„ ์ˆ˜๋„ ์žˆ๋‹ค.
โ€ข
getTimeline
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { var entries: [SimpleEntry] = [] let currentDate = Date() for hourOffset in 0..<4 { let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! let entry = SimpleEntry(date: entryDate, configuration: configuration) entries.append(entry) } let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) }
Swift
๋ณต์‚ฌ
โ—ฆ
ํ˜„์žฌ ์‹œ๊ฐ„๊ณผ ์„ ํƒ์ ์œผ๋กœ ์œ„์ ฏ์„ ์—…๋ฐ์ดํŠธ ํ•  ๋•Œ ๋ฏธ๋ž˜์˜ ์‹œ๊ฐ„์— ๋Œ€ํ•œ ํƒ€์ž„๋ผ์ธ ํ•ญ๋ชฉ ๋ฐฐ์—ด์„ ์ œ๊ณตํ•œ๋‹ค.
โ—ฆ
์—…๋ฐ์ดํŠธ๋ฅผ ์–ธ์ œ ์ง„ํ–‰ํ•  ๊ฒƒ์ธ์ง€์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ๋‹ด๊ณ  ์žˆ๋Š” ๋ฉ”์„œ๋“œ
ํ˜„์žฌ ์ฝ”๋“œ๋Š” 0~3์„ ํฌํ•จํ•œ ๋ฐ˜๋ณต ํšŸ์ˆ˜ ๋งŒํผ์˜ ์‹œ๊ฐ„์„ entry์— ๋‹ด๊ณ  ์ด๋ฅผ Timeline์œผ๋กœ ๋งŒ๋“ค์–ด ์—…๋ฐ์ดํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค. ์ฆ‰๊ฐ์ ์œผ๋กœ ๋ฐ˜์‘ํ•˜๋Š” ์œ„์ ฏ์„ ๋งŒ๋“ค๊ณ  ์‹ถ์ง€๋งŒ ๊ทธ๋Ÿด ์ˆ˜ ์—†๋‹ค,, ์™œ? ์œ„์ ฏ์ด ํ™”๋ฉด์— ํ‘œ์‹œ๋˜์–ด ์žˆ์–ด๋„ ์œ„์ ฏ์€ ๊ณ„์† ํ™œ์„ฑํ™” ๋˜์ง€ ์•Š๋Š”๋‹ค. ์ •ํ•ด์ง„ ์˜ˆ์‚ฐ ๋‚ด์—์„œ์˜ ๋ฆฌ๋กœ๋”ฉ ๊ณ„ํš์ด ํ•„์ˆ˜์ ์œผ๋กœ ๋ณด์ž„. ์œ„์ ฏ์„ ๋‹ค์‹œ ๋กœ๋“œํ•˜๋ฉด ์‹œ์Šคํ…œ ๋ฆฌ์†Œ์Šค๊ฐ€ ์†Œ๋ชจ๋˜๊ณ , ๋„คํŠธ์›Œํฌ ์ฒ˜๋ฆฌ๊ฐ€ ์ถ”๊ฐ€์ ์œผ๋กœ ๋“ค์–ด๊ฐ€ ์žˆ๋‹ค๋ฉด ๋ฐฐํ„ฐ๋ฆฌ ์†Œ๋ชจ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋Ÿฌํ•œ ์„ฑ๋Šฅ ์˜ํ–ฅ์„ ์ค„์ด๊ณ , ๋ฐฐํ„ฐ๋ฆฌ ์ˆ˜๋ช…์„ ํ•˜๋ฃจ์ข…์ผ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์š”์ฒญํ•˜๋Š” ์—…๋ฐ์ดํŠธ์˜ ๋นˆ๋„์™€ ํšŸ์ˆ˜๋ฅผ ์ œํ•œํ•ด์•ผ ํ•œ๋‹ค. WidgetKit์€ ์‹œ์Šคํ…œ ๋กœ๋“œ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์ •ํ•ด์ง„ ์˜ˆ์‚ฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•˜๋ฃจ ๋™์•ˆ ์œ„์ ฏ์„ ๋‹ค์‹œ ๋กœ๋”ฉํ•˜๊ณ  ๋ฐฐํฌํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์นœ๋‹ค. ์ด ์˜ˆ์‚ฐ์ด ์–ผ๋งˆ๋‚˜ ์ฃผ์–ด์งˆ์ง€๋Š” ๋™์ ์œผ๋กœ ๊ณ„์‚ฐ๋˜๋ฉฐ, ๋‹ค์Œ์„ ํฌํ•จํ•˜๋Š” ๋‹ค์–‘ํ•œ ์š”์†Œ๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ• ๋‹น์ด ๋œ๋‹ค. - ์œ„์ ฏ์ด ์‚ฌ์šฉ์ž์—๊ฒŒ ํ‘œ์‹œ๋˜๋Š” ๋นˆ๋„์™€ ์‹œ๊ฐ„ - ์œ„์ ฏ์„ ๋งˆ์ง€๋ง‰ ์ƒˆ๋กœ๊ณ ์นจํ•œ ์‹œ๊ฐ„ - ์œ„์ ฏ์ด ํฌํ•จ๋œ ์•ฑ์ด ํ™œ์„ฑ ์ƒํƒœ์ธ์ง€ ํ• ๋‹น๋œ ์œ„์ ฏ์˜ ์˜ˆ์‚ฐ์€ 24์‹œ๊ฐ„ ๋™์•ˆ๋งŒ ์ ์šฉ๋œ๋‹ค. ๋˜ํ•œ ์‚ฌ์šฉ์ž์˜ ์ผ์ผ ์‚ฌ์šฉ ํŒจํ„ด์— ๋งž๊ฒŒ ์กฐ์ ˆํ•˜๊ฒŒ ๋œ๋‹ค. ๊ทธ๋ ‡๋‹ค๊ณ  ํ•ด์„œ ์˜ˆ์‚ฐ์ด ์ •ํ™•ํžˆ ์ž์ •์— ์ดˆ๊ธฐํ™” ๋˜๋Š” ๊ฒƒ์€ ์•„๋‹Œ๋ฐ, ์• ํ”Œ์— ๋”ฐ๋ฅด๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์ž์ฃผ ๋ณด๋Š” ์œ„์ ฏ์˜ ๊ฒฝ์šฐ, ์ผ์ผ ์˜ˆ์‚ฐ์—๋Š” ๋ณดํ†ต 40~70ํšŒ์˜ ์ƒˆ๋กœ๊ณ ์นจ์ด ํฌํ•จ๋œ๋‹ค๊ณ  ํ•œ๋‹ค. ๋Œ€๋žต์ ์œผ๋กœ 15๋ถ„~60๋ถ„ ๋งˆ๋‹ค ์œ„์ ฏ์„ ๋‹ค์‹œ ์ƒˆ๋กœ๊ณ ์นจ ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ํ•ด์„์ด ๋˜์ง€๋งŒ, ์—ฌ๋Ÿฌ ์š”์ธ์œผ๋กœ ์ธํ•ด ๊ฐ„๊ฒฉ์ด ๋‹ฌ๋ผ์ง„๋‹ค.
โ†’ ์‹œ์Šคํ…œ์ด ์‚ฌ์šฉ์ž์˜ ํ–‰๋™์„ ํ•™์Šตํ•˜๋Š” ๊ฒƒ์—๋Š” ์‹œ๊ฐ„์ด ๊ฑธ๋ฆฐ๋‹ค. โ†’ ์ด ํ•™์Šต ๊ธฐ๊ฐ„ ๋™์•ˆ ์œ„์ ฏ์€ ํ‰์†Œ๋ณด๋‹ค ๋” ๋งŽ์€ ์ƒˆ๋กœ๊ณ ์นจ์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { var entries: [SimpleEntry] = [] let currentDate = Date() for hourOffset in 0..<4 { let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! // ํ˜„์žฌ ์‹œ๊ฐ„์— value ๋งŒํผ์˜ ์‹œ๊ฐ„์„ ๋”ํ•œ ๊ฐ’์„(0~3) let entry = SimpleEntry(date: entryDate, configuration: configuration) entries.append(entry) // ์ด entry ๊ฐ์ฒด์— ๋„ฃ์–ด์ค€ ํ›„ ๋ฐฐ์—ด์— append ์‹œ์ผœ์ค€๋‹ค. } let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) // ์ดํ›„ entries ๋ฐฐ์—ด์„ ํ†ตํ•ด Timeline์„ ๋งŒ๋“ค๊ณ  completion์„ ํ†ตํ•ด ํ˜ธ์ถœํ•œ๋‹ค. // .atEnd์„ ํ†ตํ•ด ๋งˆ์ง€๋ง‰ date๊ฐ€ ๋๋‚œ ํ›„, ํƒ€์ž„๋ผ์ธ์„ reload ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค. }
Swift
๋ณต์‚ฌ
โ—ฆ
๊ทธ๋ฆผ์„ ๋ณด๋ฉด ์ดˆ๊ธฐ ์œ„์ ฏ์ด ํ™”๋ฉด์— ๋ณด์—ฌ์ง€๊ณ , ์ด๋ฅผ reloadํ•ด์„œ Provide timeline์œผ๋กœ ํ˜๋Ÿฌ๋“ค์–ด์˜จ๋‹ค.
โ—ฆ
์šฐ๋ฆฌ์˜ ๋ฐฐ์—ด์— ์ดˆ์ฐฝ๊ธฐ now ๊ฐ’์€ 0์„ ๋”ํ•œ ๊ฐ’์ธ ํ˜„์žฌ์‹œ๊ฐ„์ด ๋œ๋‹ค.
โ—ฆ
๋ฐฐ์—ด์˜ ๋งˆ์ง€๋ง‰๋ฒˆ ์งธ์— ๋„๋‹ฌํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— Provider๋Š” ํ˜„์žฌ ์‹œ๊ฐ„์— ๋Œ€ํ•œ ๋‹จ์ผ ํ•ญ๋ชฉ์œผ๋กœ ์„ค์ •ํ˜ธ๋”˜ ์ƒˆ๋กœ๊ณ ์นจ ์ •์ฑ…์œผ๋กœ ์‘๋‹ตํ•˜๊ฒŒ ๋œ๋‹ค.
โ—ฆ
๋งˆ๋ฌด๋ฆฌ ๋˜๋ฉด ์ƒˆ ํƒ€์ž„๋ผ์ธ์„ ์š”์ฒญํ•˜๋„๋ก ์ง€์‹œํ•  ๋•Œ๊นŒ์ง€ ๋‹ค๋ฅธ ํƒ€์ž„๋ผ์ธ์„ ์š”์ฒญํ•˜์ง€ ์•Š๋Š”๋‹ค.
โ€ข
์ƒˆ๋กœ์šด ํƒ€์ž„๋ผ์ธ๋„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
โ—ฆ
์˜ˆ) ์ฃผ์‹
โ—ฆ
์ฃผ์‹ ๊ฐœ์žฅ์€ ์ฃผ๋ง์—๋Š” ์ง„ํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ ๊ธˆ์š”์ผ์˜ ์žฅ ์‹œ๊ฐ„ ๋งˆ์ง€๋ง‰๋‚ ์— ๋‚ ์งœ๋ฅผ ์ง€์ •ํ•ด์„œ ์ฃผ๋ง์„ ์Šคํ‚ตํ•  ์ˆ˜ ์žˆ๋‹ค.
โ—ฆ
์ด๋Ÿฌํ•œ ๊ฒฐ๊ณผ๋กœ ์ฃผ์‹์‹œ์žฅ์ด ์—ด๋ฆด ๋•Œ๊นŒ์ง€ ์œ„์ ฏ์„ ์—…๋ฐ์ดํŠธํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค.
let timeline = Timeline(entries: entries, policy: .after(date: Date))
Swift
๋ณต์‚ฌ
โ—ฆ
๊ธฐ์กด .atEnd ์—์„œ .after๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค๋ฉด ํ•ด๋‹น Date ๋‚ ์งœ๊ฐ€ ์ง€๋‚œ ํ›„ ํƒ€์ž„๋ผ์ธ์„ ๋ฆฌ๋กœ๋”ฉํ•˜๊ฒŒ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

SimpleEntry

struct SimpleEntry: TimelineEntry { let date: Date let configuration: ConfigurationIntent }
Swift
๋ณต์‚ฌ
โ€ข
๊ธฐ๋ณธ์ ์œผ๋กœ TimelineEntry ํ”„๋กœํ† ์ฝœ์„ ์ฑ„ํƒํ•ด์•ผ ํ•œ๋‹ค.
โ€ข
ํ•„์ˆ˜ ๊ตฌํ˜„ ํ”„๋กœํผํ‹ฐ๋กœ๋Š” date๊ฐ€ ์žˆ๋‹ค.
โ€ข
์œ„์ ฏ์— ํ‘œ์‹œํ•  ๋‚ ์งœ๋ฅผ ์ง€์ •ํ•˜๊ณ  ํƒ€์ž„๋ผ์ธ ํ•ญ๋ชฉ์„ ์ƒ์„ฑํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•˜๋‹ค. configuration์€ ํ•„์ˆ˜๋กœ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•„๋„ ๋œ๋‹ค.
โ€ข
์œ„์ ฏ extension์„ ํ•  ๋•Œ, Configuration Intent๋ฅผ ์ฒดํฌ ํ–ˆ๋‹ค๋ฉด ์ž๋™์ ์œผ๋กœ ๋”ฐ๋ผ ๋ถ™๊ฒŒ ๋œ๋‹ค. ํ•ด๋‹น ํ”„๋กœํผํ‹ฐ๋Š” ๋™์ ์œผ๋กœ ๋ทฐ๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ ํ•„์š”ํ•˜๋‹ค.

WidgetEntryView

struct MyWidgetEntryView: View { @Environment(\.wigetFamily) var family: WidgetFamily var entry: Provider.Entry var body: some View { switch family { case .systemSmall: SmallView() case .systemMedium: MediumView() case .systemLarge: LargeView() default: Text("None") } } }
Swift
๋ณต์‚ฌ
โ€ข
๋ Œ๋”๋ง ๋˜์–ด์„œ ์‹ค์ œ๋กœ ๋ณด์—ฌ์ค„ ๋ทฐ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๊ตฌ์กฐ์ฒด
โ€ข
WidgetFamily ๋ผ๋Š” ๊ฒƒ์„ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ์‚ฌ์ด์ฆˆ์˜ ๋ทฐ๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
โ€ข
์—ฌ๊ธฐ์„œ ์—”ํŠธ๋ฆฌ๋ฅผ ๋ฐ›์•„ ํ•ด๋‹น ๋ทฐ์— ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋‹ค.

Widget

struct MyWidget: Widget { let kind: String = "MyWidget" var body: some WidgetConfiguration { IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in MyWidgetEntryView(entry: entry) } .configurationDisplayName("My Widget") .description("This is an example Widget.") } }
Swift
๋ณต์‚ฌ
โ€ข
kind์—๋Š” ์œ„์ ฏ์˜ id๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.
โ€ข
intent์—๋Š” ์–ด๋–ค ๊ฒƒ์„ ๋™์ ์œผ๋กœ ์ž‘๋™ํ•˜๋Š”์ง€์— ๋Œ€ํ•ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
โ€ข
provider์—๋Š” ์œ„์—์„œ ๋งํ•œ Provider๋ฅผ ์ฃผ์ž…์‹œํ‚ค๊ณ , entry ๋˜ํ•œ ์ฃผ์ž…์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.
โ€ข
configurationDisplayName์„ ํ†ตํ•ด ์œ„์ ฏ ๊ฐค๋Ÿฌ๋ฆฌ๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” ์œ„์ ฏ ์ถ”๊ฐ€ํ•˜๋Š” ํ™”๋ฉด์—์„œ์˜ ์ƒ๋‹จ์— ์œ„์น˜ํ•œ ํƒ€์ดํ‹€ ์ด๋ฆ„์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.
โ€ข
description ๋˜ํ•œ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ํ•ด๋‹น ํฌ๊ธฐ์˜ ์œ„์ ฏ์— ๋Œ€ํ•œ ์„ค๋ช… ๋˜๋Š” ์ „์—ญ์ ์ธ ์„ค๋ช…์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.
entry๋ฅผ ์ž‘์„ฑํ•  ๋•Œ, configuration์„ ๋ดค์„ ํ…๋ฐ, ํ•ด๋‹น ๋„ค์ด๋ฐ์€ ์œ„ ํŒŒ์ผ์—์„œ ํŒŒ์ƒ๋˜์–ด ๋‚˜์˜ค๊ฒŒ ๋œ๋‹ค.