|
| 1 | +<!DOCTYPE html> |
| 2 | +<html lang="ko"> |
| 3 | +<head> |
| 4 | +<meta charset="UTF-8"> |
| 5 | +<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 6 | +<title>π λ°°λ¬ μΆμ Live Activity λ§λ€κΈ° β HIG Lab</title> |
| 7 | +<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;600;700;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet"> |
| 8 | +<style> |
| 9 | +:root{--bg:#fafafa;--surface:#fff;--text:#1d1d1f;--text-2:#6e6e73;--text-3:#86868b;--accent:#0071e3;--green:#34c759;--red:#ff3b30;--border:#d2d2d7;--code-bg:#1e1e2e;--tag-bg:#f5f5f7;--radius:16px} |
| 10 | +*{margin:0;padding:0;box-sizing:border-box} |
| 11 | +body{font-family:'Noto Sans KR',-apple-system,sans-serif;background:var(--bg);color:var(--text);line-height:1.8;-webkit-font-smoothing:antialiased} |
| 12 | +.top-bar{position:sticky;top:0;z-index:100;background:rgba(250,250,250,0.88);backdrop-filter:saturate(180%) blur(20px);border-bottom:.5px solid var(--border)} |
| 13 | +.top-bar-inner{max-width:800px;margin:0 auto;padding:12px 24px;display:flex;align-items:center;justify-content:space-between} |
| 14 | +.top-logo{font-size:16px;font-weight:700;text-decoration:none;color:var(--text)} |
| 15 | +.top-logo span{color:var(--accent)} |
| 16 | +.top-nav{display:flex;gap:20px} |
| 17 | +.top-nav a{font-size:13px;color:var(--text-2);text-decoration:none;font-weight:500} |
| 18 | +article{max-width:800px;margin:0 auto;padding:48px 24px 120px} |
| 19 | +.hero-section{margin-bottom:56px} |
| 20 | +.breadcrumb{font-size:13px;color:var(--text-3);margin-bottom:20px} |
| 21 | +.breadcrumb a{color:var(--accent);text-decoration:none} |
| 22 | +.article-title{font-size:clamp(32px,5vw,44px);font-weight:900;letter-spacing:-1.5px;line-height:1.2;margin-bottom:16px} |
| 23 | +.article-subtitle{font-size:18px;color:var(--text-2);line-height:1.7;margin-bottom:28px} |
| 24 | +.meta-row{display:flex;flex-wrap:wrap;gap:16px;padding-bottom:28px;border-bottom:1px solid var(--border)} |
| 25 | +.meta-item{font-size:13px;color:var(--text-2)} |
| 26 | +h2{font-size:26px;font-weight:800;margin:56px 0 16px;padding-top:24px} |
| 27 | +h3{font-size:20px;font-weight:700;margin:36px 0 12px} |
| 28 | +p{margin-bottom:16px;font-size:16px} |
| 29 | +.hig-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;margin:24px 0} |
| 30 | +.hig-box-header{padding:16px 20px;background:var(--tag-bg);border-bottom:1px solid var(--border);font-size:14px;font-weight:700} |
| 31 | +.hig-box-body{padding:20px} |
| 32 | +.code-block{background:var(--code-bg);border-radius:12px;margin:20px 0;overflow:hidden} |
| 33 | +.code-header{padding:10px 16px;display:flex;justify-content:space-between;align-items:center;background:rgba(255,255,255,0.05);border-bottom:1px solid rgba(255,255,255,0.08)} |
| 34 | +.code-filename{font-size:13px;color:#cdd6f4;font-family:'JetBrains Mono',monospace;font-weight:500} |
| 35 | +.code-lang{font-size:11px;color:#6c7086;background:rgba(255,255,255,0.06);padding:2px 8px;border-radius:4px} |
| 36 | +pre.code-body{margin:0;padding:16px;overflow-x:auto;font-family:'JetBrains Mono',monospace;font-size:13px;line-height:1.6;color:#cdd6f4;white-space:pre;tab-size:4} |
| 37 | +.code-body .kw{color:#cba6f7} |
| 38 | +.code-body .type{color:#89b4fa} |
| 39 | +.code-body .str{color:#a6e3a1} |
| 40 | +.code-body .num{color:#fab387} |
| 41 | +.code-body .comment{color:#6c7086;font-style:italic} |
| 42 | +.code-body .func{color:#89dceb} |
| 43 | +.code-body .prop{color:#f5c2e7} |
| 44 | +.checkpoint{background:linear-gradient(135deg,#0071e308,#5856d608);border:1px solid #0071e320;border-radius:12px;padding:20px 24px;margin:32px 0;display:flex;gap:12px;align-items:flex-start} |
| 45 | +.checkpoint-icon{font-size:24px;flex-shrink:0} |
| 46 | +.checkpoint h4{font-size:15px;font-weight:700;margin-bottom:4px} |
| 47 | +.checkpoint p{font-size:14px;color:var(--text-2);margin:0} |
| 48 | +.resource-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin:24px 0} |
| 49 | +.resource-card{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:20px;text-decoration:none;color:inherit;transition:all .2s;text-align:center} |
| 50 | +.resource-card:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(0,0,0,0.08)} |
| 51 | +.resource-icon{font-size:28px;margin-bottom:8px} |
| 52 | +.resource-label{font-size:13px;font-weight:600;color:var(--accent)} |
| 53 | +.resource-desc{font-size:12px;color:var(--text-3);margin-top:2px} |
| 54 | +@media(max-width:640px){.resource-grid{grid-template-columns:1fr}} |
| 55 | +</style> |
| 56 | +</head> |
| 57 | +<body> |
| 58 | + |
| 59 | +<div class="top-bar"> |
| 60 | + <div class="top-bar-inner"> |
| 61 | + <a href="../index.html" class="top-logo">HIG <span>Lab</span></a> |
| 62 | + <div class="top-nav"> |
| 63 | + <a href="https://github.com/M1zz/HIGLab">GitHub</a> |
| 64 | + <a href="https://youtube.com/@devjari">κ°λ°μ리</a> |
| 65 | + </div> |
| 66 | + </div> |
| 67 | +</div> |
| 68 | + |
| 69 | +<article> |
| 70 | + <div class="hero-section"> |
| 71 | + <div class="breadcrumb"> |
| 72 | + <a href="../index.html">HIG Lab</a> β <a href="../index.html#phase1">Phase 1</a> β ActivityKit |
| 73 | + </div> |
| 74 | + <h1 class="article-title">π λ°°λ¬ μΆμ Live Activity<br>λ§λ€κΈ°</h1> |
| 75 | + <p class="article-subtitle"> |
| 76 | + ActivityKitμ νμ©ν΄ λ°°λ¬ νν©μ μ κΈ νλ©΄κ³Ό Dynamic Islandμμ |
| 77 | + μ€μκ°μΌλ‘ νμΈν μ μλ Live Activityλ₯Ό ꡬνν©λλ€. |
| 78 | + </p> |
| 79 | + <div class="meta-row"> |
| 80 | + <span class="meta-item">π
2025.02</span> |
| 81 | + <span class="meta-item">β± μ΄ μ€μ΅ 2μκ°</span> |
| 82 | + <span class="meta-item">π± iOS 16.1+ / Xcode 15+</span> |
| 83 | + <span class="meta-item">π― μ€κΈ</span> |
| 84 | + </div> |
| 85 | + </div> |
| 86 | + |
| 87 | + <h2>π Live Activityλ?</h2> |
| 88 | + |
| 89 | + <p> |
| 90 | + Live Activityλ <strong>μ§ν μ€μΈ μμ
μ μ€μκ° μν</strong>λ₯Ό μ κΈ νλ©΄κ³Ό Dynamic Islandμ νμν©λλ€. |
| 91 | + μμ ―κ³Ό λ¬λ¦¬ μκ° μ ν(μ΅λ 8μκ°)μ΄ μκ³ , μ€μκ° μ
λ°μ΄νΈκ° κ°λ₯ν©λλ€. |
| 92 | + </p> |
| 93 | + |
| 94 | + <div class="hig-box"> |
| 95 | + <div class="hig-box-header">π Live Activity μ ν© μ¬λ‘</div> |
| 96 | + <div class="hig-box-body"> |
| 97 | + <p><strong>β
μ’μ μ¬λ‘</strong><br>λ°°λ¬/νμ μΆμ , μ€ν¬μΈ κ²½κΈ°, νμ΄λ¨Έ, μμ
μ¬μ, νκ³΅νΈ μ 보</p> |
| 98 | + <p><strong>β λμ μ¬λ‘</strong><br>λ μ¨(β Widget), μΊλ¦°λ μλ¦Ό(β Notification), μ£Όκ°(β Widget)</p> |
| 99 | + </div> |
| 100 | + </div> |
| 101 | + |
| 102 | + <h2>ποΈ ActivityAttributes μ μ</h2> |
| 103 | + |
| 104 | + <div class="code-block"> |
| 105 | + <div class="code-header"> |
| 106 | + <span class="code-filename">DeliveryAttributes.swift</span> |
| 107 | + <span class="code-lang">Swift</span> |
| 108 | + </div> |
| 109 | +<pre class="code-body"><span class="kw">import</span> <span class="type">ActivityKit</span> |
| 110 | + |
| 111 | +<span class="kw">struct</span> <span class="type">DeliveryAttributes</span>: <span class="type">ActivityAttributes</span> { |
| 112 | + <span class="comment">// Static: λ³κ²½ λΆκ°</span> |
| 113 | + <span class="kw">let</span> <span class="prop">orderNumber</span>: <span class="type">String</span> |
| 114 | + <span class="kw">let</span> <span class="prop">storeName</span>: <span class="type">String</span> |
| 115 | + |
| 116 | + <span class="comment">// ContentState: μ€μκ° μ
λ°μ΄νΈ</span> |
| 117 | + <span class="kw">struct</span> <span class="type">ContentState</span>: <span class="type">Codable</span>, <span class="type">Hashable</span> { |
| 118 | + <span class="kw">let</span> <span class="prop">status</span>: <span class="type">DeliveryStatus</span> |
| 119 | + <span class="kw">let</span> <span class="prop">estimatedArrival</span>: <span class="type">Date</span> |
| 120 | + <span class="kw">let</span> <span class="prop">driverName</span>: <span class="type">String</span>? |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +<span class="kw">enum</span> <span class="type">DeliveryStatus</span>: <span class="type">String</span>, <span class="type">Codable</span> { |
| 125 | + <span class="kw">case</span> <span class="prop">preparing</span> <span class="comment">// μ€λΉ μ€</span> |
| 126 | + <span class="kw">case</span> <span class="prop">pickedUp</span> <span class="comment">// ν½μ
μλ£</span> |
| 127 | + <span class="kw">case</span> <span class="prop">nearby</span> <span class="comment">// κ·Όμ² λμ°©</span> |
| 128 | + <span class="kw">case</span> <span class="prop">delivered</span> <span class="comment">// λ°°λ¬ μλ£</span> |
| 129 | +}</pre> |
| 130 | + </div> |
| 131 | + |
| 132 | + <h2>ποΈ Dynamic Island λ μ΄μμ</h2> |
| 133 | + |
| 134 | + <p>Dynamic Islandλ 3κ°μ§ μνκ° μμ΅λλ€: Compact, Minimal, Expanded</p> |
| 135 | + |
| 136 | + <div class="code-block"> |
| 137 | + <div class="code-header"> |
| 138 | + <span class="code-filename">DeliveryLiveActivity.swift</span> |
| 139 | + <span class="code-lang">Swift</span> |
| 140 | + </div> |
| 141 | +<pre class="code-body"><span class="kw">struct</span> <span class="type">DeliveryLiveActivity</span>: <span class="type">Widget</span> { |
| 142 | + <span class="kw">var</span> <span class="prop">body</span>: <span class="kw">some</span> <span class="type">WidgetConfiguration</span> { |
| 143 | + <span class="type">ActivityConfiguration</span>(<span class="kw">for</span>: <span class="type">DeliveryAttributes</span>.<span class="kw">self</span>) { context <span class="kw">in</span> |
| 144 | + <span class="comment">// μ κΈ νλ©΄</span> |
| 145 | + <span class="type">LockScreenView</span>(context: context) |
| 146 | + } dynamicIsland: { context <span class="kw">in</span> |
| 147 | + <span class="type">DynamicIsland</span> { |
| 148 | + <span class="comment">// Expanded</span> |
| 149 | + <span class="type">DynamicIslandExpandedRegion</span>(.leading) { |
| 150 | + <span class="type">Image</span>(systemName: <span class="str">"person.circle"</span>) |
| 151 | + } |
| 152 | + <span class="type">DynamicIslandExpandedRegion</span>(.trailing) { |
| 153 | + <span class="type">Image</span>(systemName: context.state.status.<span class="prop">symbol</span>) |
| 154 | + } |
| 155 | + <span class="type">DynamicIslandExpandedRegion</span>(.center) { |
| 156 | + <span class="type">Text</span>(context.attributes.<span class="prop">storeName</span>) |
| 157 | + } |
| 158 | + <span class="type">DynamicIslandExpandedRegion</span>(.bottom) { |
| 159 | + <span class="type">ProgressView</span>(value: context.state.<span class="prop">progress</span>) |
| 160 | + } |
| 161 | + } compactLeading: { |
| 162 | + <span class="type">Image</span>(systemName: <span class="str">"bicycle"</span>) |
| 163 | + } compactTrailing: { |
| 164 | + <span class="type">Text</span>(context.state.<span class="prop">estimatedArrival</span>, style: .timer) |
| 165 | + } minimal: { |
| 166 | + <span class="type">Image</span>(systemName: <span class="str">"bicycle"</span>) |
| 167 | + } |
| 168 | + } |
| 169 | + } |
| 170 | +}</pre> |
| 171 | + </div> |
| 172 | + |
| 173 | + <h2>β‘ Activity μλͺ
μ£ΌκΈ°</h2> |
| 174 | + |
| 175 | + <div class="code-block"> |
| 176 | + <div class="code-header"> |
| 177 | + <span class="code-filename">ActivityManager.swift</span> |
| 178 | + <span class="code-lang">Swift</span> |
| 179 | + </div> |
| 180 | +<pre class="code-body"><span class="comment">// μμ</span> |
| 181 | +<span class="kw">let</span> activity = <span class="kw">try</span> <span class="type">Activity</span>.request( |
| 182 | + attributes: attributes, |
| 183 | + content: <span class="type">ActivityContent</span>(state: initialState, staleDate: <span class="kw">nil</span>), |
| 184 | + pushType: .token |
| 185 | +) |
| 186 | + |
| 187 | +<span class="comment">// μ
λ°μ΄νΈ</span> |
| 188 | +<span class="kw">await</span> activity.update(<span class="type">ActivityContent</span>(state: newState, staleDate: <span class="kw">nil</span>)) |
| 189 | + |
| 190 | +<span class="comment">// μ’
λ£</span> |
| 191 | +<span class="kw">await</span> activity.end(finalContent, dismissalPolicy: .after(<span class="type">Date</span>().addingTimeInterval(<span class="num">3600</span>)))</pre> |
| 192 | + </div> |
| 193 | + |
| 194 | + <div class="checkpoint"> |
| 195 | + <div class="checkpoint-icon">π</div> |
| 196 | + <div class="checkpoint-content"> |
| 197 | + <h4>μ±λ¦°μ§ μλ£!</h4> |
| 198 | + <p>Dynamic Islandμ μ κΈ νλ©΄μμ λ°°λ¬ νν©μ μ€μκ°μΌλ‘ νμΈν μ μλ Live Activityλ₯Ό μμ±νμ΅λλ€!</p> |
| 199 | + </div> |
| 200 | + </div> |
| 201 | + |
| 202 | + <h2>π¦ νμ΅ μλ£</h2> |
| 203 | + |
| 204 | + <div class="resource-grid"> |
| 205 | + <a href="../activitykit/documentation/higactivitykit/tutorials/table-of-contents" class="resource-card"> |
| 206 | + <div class="resource-icon">π</div> |
| 207 | + <div class="resource-label">DocC νν 리μΌ</div> |
| 208 | + <div class="resource-desc">Xcodeμμ λ°λ‘ μ€μ΅</div> |
| 209 | + </a> |
| 210 | + <a href="https://github.com/M1zz/HIGLab" class="resource-card"> |
| 211 | + <div class="resource-icon">π»</div> |
| 212 | + <div class="resource-label">GitHub νλ‘μ νΈ</div> |
| 213 | + <div class="resource-desc">μ 체 μμ€μ½λ</div> |
| 214 | + </a> |
| 215 | + <a href="https://developer.apple.com/design/human-interface-guidelines/live-activities" class="resource-card"> |
| 216 | + <div class="resource-icon">π</div> |
| 217 | + <div class="resource-label">Apple HIG μλ¬Έ</div> |
| 218 | + <div class="resource-desc">Live Activities κ°μ΄λ</div> |
| 219 | + </a> |
| 220 | + </div> |
| 221 | + |
| 222 | +</article> |
| 223 | +</body> |
| 224 | +</html> |
0 commit comments