<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>iOS &#8211; 隨心所欲</title>
	<atom:link href="https://doeverythingiwant.com/tag/ios-2/feed/" rel="self" type="application/rss+xml" />
	<link>https://doeverythingiwant.com</link>
	<description>iOS Developer 的隨筆記錄</description>
	<lastBuildDate>Sun, 08 Mar 2026 09:39:52 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://doeverythingiwant.com/wp-content/uploads/2025/08/cropped-0802.png</url>
	<title>iOS &#8211; 隨心所欲</title>
	<link>https://doeverythingiwant.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>[AI] Xcode 26 與 AI</title>
		<link>https://doeverythingiwant.com/ai-xcode-26-and-ai/</link>
					<comments>https://doeverythingiwant.com/ai-xcode-26-and-ai/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Sun, 08 Mar 2026 09:39:49 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[AI]]></category>
		<category><![CDATA[Apple intelligence]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Xcode]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=2512</guid>

					<description><![CDATA[Photo by Immo Wegmann on Unspl&#8230;]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center">Photo by <a href="https://unsplash.com/@tinkerman?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener">Immo Wegmann</a> on <a href="https://unsplash.com/photos/a-piece-of-cardboard-with-a-keyboard-appearing-through-it-vi1HXPw6hyw?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener">Unsplash</a></p>



<p>Xcode 26 終於可以使用 AI 啦~~雖然去年的發表了，我因為公司在趕專案累到爆，回家就不想開電腦，前一陣子才開始使用，覺得很好玩(?)</p>



<p>不過，相信很多人應該都有用 Claude code 這位神奇的夥伴，和在 Xcode 裡直接使用體驗不太一樣。 </p>



<p>這篇簡單寫一下要怎麼使用，最後加上我個人的小心得</p>



<span id="more-2512"></span>



<h2 class="wp-block-heading">前置作業</h2>



<ul class="wp-block-list">
<li>需要有 Xcode 26</li>



<li>macOS 需要升到Tahoe</li>



<li>ChatGPT 帳號或 Claude 的付費帳號<br></li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Xcode 26</h2>



<p>設定 AI Tool 的方式，先點開 Xcode 的 Setting </p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img fetchpriority="high" decoding="async" width="470" height="578" src="https://doeverythingiwant.com/wp-content/uploads/2026/03/AI01.png" alt="" class="wp-image-2514" style="width:446px;height:auto" srcset="https://doeverythingiwant.com/wp-content/uploads/2026/03/AI01.png 470w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI01-244x300.png 244w" sizes="(max-width: 470px) 100vw, 470px" /></figure>
</div>


<p>在左側選單上可以看到 Intelligence 的選項，Xcode 內建 ChatGPT 與 Claude ，只要點下去登入就可以了，ChatGPT 免費帳號就可以使用，但 Claude 必須要是付費帳號才可以使用 。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" width="1024" height="822" src="https://doeverythingiwant.com/wp-content/uploads/2026/03/AI02-1024x822.png" alt="" class="wp-image-2515" style="aspect-ratio:1.2457363416976752;width:730px;height:auto" srcset="https://doeverythingiwant.com/wp-content/uploads/2026/03/AI02-1024x822.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI02-300x241.png 300w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI02-768x617.png 768w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI02-1536x1233.png 1536w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI02-1170x939.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI02-585x470.png 585w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI02.png 1724w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>


<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">使用方式</h2>


<div class="wp-block-image is-style-default">
<figure class="alignleft size-full is-resized"><img decoding="async" width="784" height="2140" src="https://doeverythingiwant.com/wp-content/uploads/2026/03/AI03.png" alt="" class="wp-image-2518" style="aspect-ratio:0.3663558394999438;width:240px;height:auto" srcset="https://doeverythingiwant.com/wp-content/uploads/2026/03/AI03.png 784w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI03-110x300.png 110w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI03-375x1024.png 375w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI03-768x2096.png 768w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI03-563x1536.png 563w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI03-750x2048.png 750w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI03-585x1597.png 585w" sizes="(max-width: 784px) 100vw, 784px" /></figure>
</div>


<p>首先，要切到 AI 功能，可以由圖示中的 (1) 的按鈕作切換</p>



<p>(2) 的按鈕可以選擇想要使用的AI 模型是哪一種</p>



<p>(3) 可以選擇對話串</p>



<p>(4) 是歷史記錄，不過，不知道是不是因為我使用的是免費帳號，只要重開Xcode 後，歷史記錄就會消失了</p>



<p>(5) 就是下Prompt 的地方</p>



<p></p>



<p></p>



<p></p>



<p></p>



<p></p>



<p></p>



<p></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Coding</h2>



<p>在範例圖中，我下了「在頂部加上搜尋列」，它就會自動幫我修改程式，會先告訴你會怎麼調整，然後就會直接調整了。</p>



<p>AI 調整的部分會用彩色Highlight 標示，一般有使用 Git 的話，也會用單一顏色來表示調整過的地方，說不定Apple 就是為了特別區別自己調整的與 AI 調整的才用彩色Highlight。</p>



<p>手機使用 AI 的時候也會有彩色Highlight出現，Apple 就是這一點我覺得做得很不錯，統一提示，只要在Apple 的產品看到彩色Highlight就會知道是AI 的東西</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="2334" height="1390" src="https://doeverythingiwant.com/wp-content/uploads/2026/03/AI04.png" alt="" class="wp-image-2519" srcset="https://doeverythingiwant.com/wp-content/uploads/2026/03/AI04.png 2334w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI04-300x179.png 300w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI04-1024x610.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI04-768x457.png 768w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI04-1536x915.png 1536w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI04-2048x1220.png 2048w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI04-1920x1143.png 1920w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI04-1170x697.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2026/03/AI04-585x348.png 585w" sizes="(max-width: 2334px) 100vw, 2334px" /></figure>
</div>


<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">心得</h2>



<p>在 Xcode 使用 AI 就是這麼簡單，我個人覺得比 Claude code 的入門簡單一點，不過，Claude code 能做的事情很多，多學一點完全沒有問題。</p>



<p>在上面的範例中，下完指令之後它就會自動修改程式，實際上，若使用在即有專案它遇到問題的時候也是會先詢問，例如「這會影響到某個檔案是否需要一起調整」、「這需要看某個檔案是否可以檢視它」之類的。</p>



<p>若是請它調整即有的程式，我會先要求提出調整計畫，確認它會修改什麼、怎麼修改，才讓它執行，不會一下子就讓它調整。</p>



<p>當把AI 做為小幫手的時候，比較需要學習的是Prompt 要怎麼下，它寫出來的東西也要Review 。 我個人這陣子的使用心得是覺得很有趣，當然也會有覺得害怕的部分，但有趣佔大多數，它會寫出一些我從來沒有想過的方式，也可以學習，就是和過去一樣「把好的東西學起來」。</p>



<p>最後祝大家 Coding 愉快</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/ai-xcode-26-and-ai/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>[SwiftUI] AttributedString and Hyperlink</title>
		<link>https://doeverythingiwant.com/swiftui-attributedstring-and-hyperlink/</link>
					<comments>https://doeverythingiwant.com/swiftui-attributedstring-and-hyperlink/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Sun, 18 May 2025 09:33:16 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[AttributedString]]></category>
		<category><![CDATA[Hyperlink]]></category>
		<category><![CDATA[iOS]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=2466</guid>

					<description><![CDATA[Photo by Vladislav Klapin on U&#8230;]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center">Photo by <a href="https://unsplash.com/@lemonvlad?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank" rel="noopener">Vladislav Klapin</a> on <a href="https://unsplash.com/photos/a-person-holding-up-a-sign-that-says-hello-PVr9Gsj93Pc?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank" rel="noopener">Unsplash</a></p>



<p>記得還在寫 UIKit 的時候，其實很不喜歡寫 <code>AttributedString</code> 就覺得很麻煩，因為通常會有這樣的需求都是因為想要做超連結(Hyperlink)，記得那時候要做到這樣是很麻煩的，除了要使用 <code>AttributedString</code>外，還要做點擊開網頁…通常&#8230;&#8230;都會想盡辨法打消設計師們的念頭。</p>



<span id="more-2466"></span>



<p>最近因為工作上的需要，所以就來去找找SwiftUI 要怎麼做超連結(Hyperlink)的方式，發現比起過去簡單很多，無論是在設定URL 或是 Handle Action 都很簡單，好用很多。</p>



<h2 class="wp-block-heading">Text</h2>



<p>若是想要在一串文字中間加上不同的字體或顏色，最常想到的就是 <a href="https://developer.apple.com/documentation/foundation/attributedstring" target="_blank" rel="noopener">AttributedString</a> ，不過，若不是想要做超連結的話，SwiftUI 的 <code>Text</code> 就可以做到了，像這樣:</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code> Text(&quot;Hello, world! &quot;)
 +
 Text(&quot;Go google &quot;)
   .foregroundColor(.orange)
 +
 Text(&quot;website&quot;)</code></pre></div>



<p>印出來的結果會是這樣。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="548" height="86" src="https://doeverythingiwant.com/wp-content/uploads/2025/05/截圖-2025-05-04-下午4.35.17.png" alt="" class="wp-image-2467" srcset="https://doeverythingiwant.com/wp-content/uploads/2025/05/截圖-2025-05-04-下午4.35.17.png 548w, https://doeverythingiwant.com/wp-content/uploads/2025/05/截圖-2025-05-04-下午4.35.17-300x47.png 300w" sizes="(max-width: 548px) 100vw, 548px" /></figure>



<p>不過，如果想要加上 <code>.onTapGesture</code> 的話就沒有辨法了，用 <code>+</code> 串連的<code>Text</code>已經不是單純的 <code>Text</code> ，無法使用<code>.onTapGesture</code> 所以，想要做超連結的話，還是用<code>AttributedString</code>比較好。</p>



<h2 class="wp-block-heading"><code>AttributedString</code></h2>



<p><a href="https://developer.apple.com/documentation/foundation/attributedstring" target="_blank" rel="noopener">AttributedString</a> 是可以讓一串文字裡加入不同樣式的一種方式，這次特別寫出超連結的做法，它的做法是計算出特定字串的 Range ，再依據這個 Range 中的字是需要什麼樣式和顏色，只要能讓它知道需要不一樣的字是什麼，基本上就沒什麼問題。</p>



<p><code>AttributedString</code>中的 <code>AttributeScopes.FoundationAttributes</code> 裡有提供 <code>Link</code> 參數，就是用來做為超連結使用的。</p>



<p>以下是實作方式</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>private func getLinkText(_ text: String, linkText: String, linkURL: String) -&gt; AttributedString {
    var attributedString = AttributedString(text)
    attributedString.font = .systemFont(ofSize: 14, weight: .regular)
    attributedString.foregroundColor = Color.gray
        
    if let range = attributedString.range(of: linkText) {
        attributedString[range].font = .systemFont(ofSize: 14, weight: .medium)
        attributedString[range].foregroundColor = Color.orange
        attributedString[range].link = URL(string: linkURL)
    }
        
    return attributedString
}</code></pre></div>



<p>解析一下程式碼</p>



<ul class="wp-block-list">
<li><code>var attributedString = AttributedString(text)</code> ，這裡的 <code>text</code> 需要是全部的句子</li>



<li><code>attributedString.range(of: linkText)</code>，<code>linkText</code> 就是想要做成超連結的文字段</li>



<li><code>attributedString[range].link</code>，這裡寫入想要開啟的網站</li>
</ul>



<p><code>font</code> 與 <code>foregroundColor</code> 可以自行設定，應該有人有發現，像 <code>View</code> 的 <code>foregroundColor</code> 已經<mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">Deprecated</mark> 了，現在都使用 <code>foregroundStyle</code> ，但這裡的 <code>foregroundColor</code> 是 <code>AttributeScopes</code>裡的，不是 <code>View</code> 的喔。</p>



<h2 class="wp-block-heading">Handle link</h2>



<p>已經組好有中間有超連結的字串了，再來就直接用 <code>Text</code> 就可以了，最後的範例是要怎麼Handle link 行為</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>Text(getLinkText(&quot;Hello, world! Go google&quot;, linkText: &quot;google&quot;, linkURL: &quot;https://www.google.com.tw&quot;))
                .environment(\.openURL, OpenURLAction { _ in
                    return .systemAction
                })</code></pre></div>



<p>重要的就是使用 <a href="https://developer.apple.com/documentation/swiftui/openurlaction" target="_blank" rel="noreferrer noopener">OpenURLAction</a> ，與 <code>environment(\.openURL)</code> 一起使用，就可以Handle Link Text 點擊的行為，範例上寫的 <code>.systemAction</code> 的行為，就是指外開至Safari ，以下簡單說明 <code>OpenURLAction</code> 的回傳值。</p>



<ul class="wp-block-list">
<li><a href="https://developer.apple.com/documentation/swiftui/openurlaction/result/handled" target="_blank" rel="noreferrer noopener">handled</a> : 就是自行處理，例如，想要直接用 <code>.sheet</code> 開啟網頁，這時候就需要 <code>return handled。</code></li>



<li><a href="https://developer.apple.com/documentation/swiftui/openurlaction/result/discarded" target="_blank" rel="noopener">discarded</a> : 就是不做任何處理。</li>



<li><a href="https://developer.apple.com/documentation/swiftui/openurlaction/result/systemaction" target="_blank" rel="noopener">systemAction</a> : 就是直接外開至Safari ，由Safari 幫你開網頁</li>



<li><a href="https://developer.apple.com/documentation/swiftui/openurlaction/result/systemaction(_:)" target="_blank" rel="noopener">systemAction(_:)</a> : 是用 Safari 開，但是還可以帶別的 URL 或是需要加工URL (這個URL 是指Swift 裡的 <a href="https://developer.apple.com/documentation/Foundation/URL" target="_blank" rel="noopener">URL</a> 物件喔)</li>
</ul>



<h2 class="wp-block-heading">最後</h2>



<p>只要記得上述這些東西，就可以比較簡單地寫出超連結的文字了，畢竟是手機，建議二組超連結的文字不要太近，不然很容易會點錯喔…</p>



<p>最後，祝大家 Coding 愉快!!</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/swiftui-attributedstring-and-hyperlink/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>[iOS] Codable 有關的那些小事情</title>
		<link>https://doeverythingiwant.com/codable-decoder/</link>
					<comments>https://doeverythingiwant.com/codable-decoder/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Sun, 08 Dec 2024 08:11:47 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[codable]]></category>
		<category><![CDATA[Decoder]]></category>
		<category><![CDATA[iOS]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=2372</guid>

					<description><![CDATA[說到 Codable 已經推出很久了，也用了好幾年了，但是對它還是不算是很了解，該怎麼說呢，因為Codable 最常運用在解析 API Response，基本上寫完之後，除非API 回傳值要改變，你大概到離職的那一刻都不會再去修改它了，所以即使它推出了很多年了，實際上接觸的時間意外地少。
最近正好有機會再次寫不少 Codable 的部分，發現了一些過去不知道的東西，把它們記錄在這裡。]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center">Photo by <a href="https://unsplash.com/@markusspiske?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank" rel="noopener">Markus Spiske</a> on <a href="https://unsplash.com/photos/matrix-movie-still-iar-afB0QQw?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank" rel="noopener">Unsplash</a></p>



<p>說到 Codable 已經推出很久了，也用了好幾年了，但是對它還是不算是很了解，該怎麼說呢，因為Codable 最常運用在解析 API Response，基本上寫完之後，除非API 回傳值要改變，大概到離職的那一刻都不會再去修改它了，所以即使它推出了很多年了，實際上接觸的時間意外地少。</p>



<p>最近正好有機會再次寫不少 Codable 的部分，發現了一些過去不知道的東西，把它們記錄在這裡。</p>



<span id="more-2372"></span>



<h2 class="wp-block-heading">CodingKey</h2>



<p>最常使用在API 回傳值的Key 風格 不是 Swift 習慣的風格，做為對應解析之用</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>enum CodingKeys: String, CodingKey {
    case id = &quot;product_id&quot;
    case name
    case description
    case expiryDate = &quot;expiry_date&quot;
}</code></pre></div>



<p>在API 回傳值裡的 <code>product_id</code> 就會被放到 <code>id</code>裡，這是非常好用的地方。</p>



<p>另一個好用的地方在於也可以讓decoder只解析寫在 CodingKeys 裡的那些參數，舉個例</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>struct SubItem: Decodable,Identifiable {
   var id = UUID().string
   var name: String
   var description: String
   var isActive: Bool
}</code></pre></div>



<p><code>SubItem</code> conform Identifiable protocol ，因此有一個參數是<code>id</code> 且還是直接使用UUID，並不希望它被拿來當解析用的參數時，就可以使用 CodingKey來避開 <code>id</code> ，就不需要再寫 <code>init(from decoder: any Decoder)</code></p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>struct SubItem: Decodable,Identifiable {
   var id = UUID().string
   var name: String
   var description: String
   var isActive: Bool

   enum CodingKeys: String, CodingKey {
    case name
    case description
    case isActive 
  }
}</code></pre></div>



<h2 class="wp-block-heading">Try? &amp; decodeIfPresent()</h2>



<p>基於因為不知道API 會回傳什麼，是否真的會依照文件回傳，為了讓App 不會因為回傳值問題需導至 Crash，一般而言，除非100%保証一定會有值的情況下，大多數參數都會被設定為 Optional，在解析的時候就會用上  <code>decodeIfPresent</code> ，當回傳值完全沒有這個參數，或是參數是nil(null)的時候，它會回傳 nil。</p>



<p>一般情況下，這樣就幾乎不會有什麼問題，但是，就是這個但是，當回傳值是有值的時候，但偏偏回傳的資料型態是錯誤的，這樣的寫法就還是會解析失敗，導致整個物件都會是nil。</p>



<p>如果你是覺得「為什麼會回傳資料型態不同的資料來啊? 這不合理啊」，沒錯，理解上不應該出現回傳值的資料有誤的情況，多數情況下都會要求修正。因為App 端對於資料型態相對來說是嚴格的，解析失敗最後就可能造成 Crash ，所以最好是大家都遵守好訂好的資料型態，不應該回傳不正確的型態。</p>



<p>在有點歷史的產品裡或是一開始就沒有 App 端產品就可能會發生，回傳值會變成不預期的資料型態問題，且可能也不是馬上可以修正的情況，這種時候就需要先確認有問題的資料是不是需要用的，若是需要用的就必須要再另外處理，若是不需要用的資料，就是<code>try?</code> 出馬的時候了!</p>



<p><code>try?</code> 本來就是在解析失敗的時候會回傳nil ，本來比較常會和 <code>decode</code>() 一起使用，如果和 <code>decodeIfPresent</code> 一起使用，所有可以會造成問題的情況都會包含到，這樣就比較不用怕因為資料型態解析失敗問題，當然，前提是有問題的資料並不是會使用到的資料</p>



<p>以下就是合體技的寫法</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>init(from decoder: any Decoder) throws {
   let container = try decoder.container(keyedBy: CodingKeys.self)
   id = try? container.decodeIfPresent(String.self, forKey: .id)
   name = try? container.decodeIfPresent(String.self, forKey: .name)
}</code></pre></div>



<h2 class="wp-block-heading">假如運氣特好遇到…</h2>



<p>萬一真的運氣特好遇到同一支API 回傳值在不同情況下某一個參數會回傳完全不同的型態，偏偏你又必須要使用這些參數，那只好自定義解析方式了，方法上網Google 一下就會找得到了，不然也可以詢問 Claude AI. </p>



<p>其方式是 <code>extenstion KeyedDecodingContainer</code> 這個Struct ，寫一個通用的 <code>decode</code>，之後就可以利用這個 <code>decode</code> 為它加上一些自定義的解析方式</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func decodeAny&lt;T&gt;(_: T.Type, forKey key: K, transform: ((Any) -&gt; T?)? = nil) throws -&gt; T? {
        guard contains(key) else { return nil }
        
        //預設轉換邏輯
        let defaultTransform: (Any) -&gt; T? = { value in
            if let typedValue = value as? T {
                return typedValue
            }
            
            if let stringConvertible = T.self as? LosslessStringConvertible.Type {
                let stringValue = String(describing: value)
                return stringConvertible.init(stringValue) as? T
            }
            
            return nil
        }
        
        let transformer = transform ?? defaultTransform
        
        //不同型別的解法
        let attempts: [(Any.Type, (K) throws -&gt; Any)] = [
            (String.self, {try decode(String.self, forKey: $0) as Any}),
            (Int.self, {try decode(Int.self, forKey: $0) as Any}),
            (Double.self, {try decode(Double.self, forKey: $0) as Any}),
            (Bool.self, {try decode(Bool.self, forKey: $0) as Any})
        ]
        
        // value: 取得API 資料的型態,transformed: 利用transformer 解析成需要的型態
        for(_, decoder) in attempts {
            if let value = try? decoder(key),
               let transformed = transformer(value) {
                return transformed
            }
        }
    
        return nil
    }</code></pre></div>



<p>例如 <code>Bool</code> 是最常遇到擁有各種不同型態的表示法的，就可以為它來寫一個方便的解析方式</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func decodeBool(forKey key: K) throws -&gt; Bool? {
        try decodeAny(Bool.self, forKey: key) { value in
            switch value {
            case let boolValue as Bool:
                return boolValue
            case let intValue as Int:
                return intValue != 0
            case let doubleValue as Double:
                return doubleValue != 0
            case let stringValue as String:
                let lowercased = stringValue.lowercased()
                if [&quot;true&quot;, &quot;t&quot;, &quot;yes&quot;, &quot;1&quot;].contains(lowercased) { return true }
                if [&quot;false&quot;, &quot;f&quot;, &quot;no&quot;, &quot;0&quot;].contains(lowercased) { return false }
                return nil
            default nil
            }
        }
    }</code></pre></div>



<p>說真的，最好是不要遇到需要這樣寫的情況，為什麼呢? 主要的原因是萬一真的是 API 的錯誤，但因為App 端有自定義解析的方式，所以就解析成功了，那麼就可能完全不會有人知道可能真的是資料錯誤的問題，當這樣資料數量多了之後，也許就會造成後續資料分析有誤，或有些神奇的情況。</p>



<p>最後，祝大家 Coding 愉快!!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/codable-decoder/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>[iOS] Network Monitor</title>
		<link>https://doeverythingiwant.com/ios-network-monitor/</link>
					<comments>https://doeverythingiwant.com/ios-network-monitor/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Sun, 20 Oct 2024 14:25:17 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[network monitor]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=2361</guid>

					<description><![CDATA[Photo by Frederik Lipfert on U&#8230;]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center">Photo by <a href="https://unsplash.com/@frederikli?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank" rel="noopener">Frederik Lipfert</a> on <a href="https://unsplash.com/photos/a-person-holding-an-iphone-in-their-hand-y67dwfB2AiM?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank" rel="noopener">Unsplash</a></p>



<p>在iOS12之前想要偵測網路狀態的話，大家都會使用 Reachability 這套官方工具，且有 <a href="https://developer.apple.com/library/archive/samplecode/Reachability/Introduction/Intro.html" data-type="link" data-id="https://developer.apple.com/library/archive/samplecode/Reachability/Introduction/Intro.html" target="_blank" rel="noreferrer noopener">Demo Code</a> 可以直接使用，所以只要說到偵測網路第一時間一定會想到使用 Reachability。</p>



<p>在 iOS 12之後官方推出了 <a href="https://developer.apple.com/documentation/network" target="_blank" rel="noreferrer noopener">Network Kit</a> 其中有很多與網路相關的服務，期中一個就可以用來偵測網路狀態，程式碼比過去少又比較清楚，配合現在官方主推的 SwiftUI 也很好用。</p>



<span id="more-2361"></span>



<h2 class="wp-block-heading">Monitor</h2>



<h3 class="wp-block-heading">Create Publisher</h3>



<p>像這樣的Monitor 是建議獨立建立一個 Class 來處理，不要放在其他的Class 裡面</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>import Network

class NetworkMonitor: ObservableObject {
    
    @Published private(set) var isConnected: Bool = false
    @Published private(set) var networkType: NWInterface.InterfaceType? = .other
}</code></pre></div>



<p>首先，記得要 <code>import Network</code>，為了能讓SwiftUI 使用記得要Conform <code>ObservableObject</code></p>



<p><code>isConnected</code> 就是在確認是否為連線狀態，<code>networkType</code> 就是屬於那一種連線類型，官方有分為 <code>wifi</code>、<code>cellular</code>…等等，詳細分類可以參考 <a href="https://developer.apple.com/documentation/network/nwinterface/interfacetype" target="_blank" rel="noreferrer noopener">NWInterface.InterfaceType</a> 的說明。</p>



<h3 class="wp-block-heading">Create Monitor</h3>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>let networkMonitor = NWPathMonitor()
let workQueue = DispatchQueue(label: &quot;NetworkMonitor&quot;)</code></pre></div>



<p>接著往下建立 Monitor ，在Network Kit 裡有一個Class 是 <a href="https://developer.apple.com/documentation/network/nwpathmonitor" data-type="link" data-id="https://developer.apple.com/documentation/network/nwpathmonitor" target="_blank" rel="noreferrer noopener">NWPathMonitor</a> ，它就是用來偵測網路狀態的，上述這樣的寫法表示要偵測所有型態的網路，有需要的話也可以指定想要偵測的網路型態。</p>



<p>一般來說偵測網路的動作一定不可以影響到主線程的運行，因此我們需要新增一條負責偵測網路用的 <code>DispatchQueue</code>，名稱可以自訂，這裡是稱它為 <code>NetworkMonitor</code>。</p>



<h3 class="wp-block-heading">Create Path update handler</h3>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>networkMonitor.pathUpdateHandler = { [weak self] path in
    self?.isConnected = path.status == .satisfied
    self?.networkType = path.availableInterfaces.first?.type
}</code></pre></div>



<p>當網路狀態改變的時候，可以在 <code>pathUpdateHandler</code> 這個 Closure 取得資訊，會回傳 <code>NWPath</code> 這個物件，它有網路狀態的相關資訊。</p>



<h3 class="wp-block-heading">Start</h3>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>networkMonitor.start(queue: workQueue)</code></pre></div>



<p>最後再開始偵測，記得指定先前設定好的Queue</p>



<p>完整的程式如下</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>class NetworkMonitor: ObservableObject {
    
    @Published private(set) var isConnected: Bool = false
    @Published private(set) var networkType: NWInterface.InterfaceType? = .other
    
    let networkMonitor = NWPathMonitor()
    let workQueue = DispatchQueue(label: &quot;NetworkMonitor&quot;)
    
    init() {
        
        networkMonitor.pathUpdateHandler = { [weak self] path in
            self?.isConnected = path.status == .satisfied
            self?.networkType = path.availableInterfaces.first?.type
        }
        
        networkMonitor.start(queue: workQueue)
    }
}</code></pre></div>



<h3 class="wp-block-heading">View</h3>



<p>接著就可以將這個NetworkMonitor 放到View 上使用，以下是一個簡單的範例</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>struct ContentView: View {
    
    @StateObject var networkMonitor = NetworkMonitor()
    
    var body: some View {
        VStack {
            Label {
                Text(networkMonitor.isConnected ? &quot;Connected&quot; : &quot;Disconnected&quot;)
            } icon: {
                Image(systemName: networkMonitor.isConnected ? &quot;network&quot; : &quot;network.slash&quot;)
                    .foregroundStyle(networkMonitor.isConnected ? .green : .red)
            }

        }
        .padding()
    }
}</code></pre></div>



<p>當然，App 應該只會有一個 Network Monitor ，這個時候可以使用 <code>EnvironmentObject</code> 的方式來傳遞 Network Monitor，寫法可以參考以下方式，當然要從哪裡開始建立 Network Monitor 都可以依照個人需求，理論上應該是App 開啟時就需要做偵測啦，記得使用 <code>.environmentObject()</code>來讓其他畫面可以使用 <code>EnvironmentObject</code> 方式讀取到 Network Monitor。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>@main
struct NetworkDemoApp: App {
    
    @StateObject var networkMonitor = NetworkMonitor()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(networkMonitor)
        }
    }
}</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>struct ContentView: View {
    
    @EnvironmentObject var networkMonitor: NetworkMonitor
    
    var body: some View {
        VStack {
            Label {
                Text(networkMonitor.isConnected ? &quot;Connected&quot; : &quot;Disconnected&quot;)
            } icon: {
                Image(systemName: networkMonitor.isConnected ? &quot;network&quot; : &quot;network.slash&quot;)
                    .foregroundStyle(networkMonitor.isConnected ? .green : .red)
            }

        }
        .padding()
    }
}</code></pre></div>



<p>最後，祝大家 Coding 愉快!!</p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/ios-network-monitor/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SwiftUI &#8211; Drop &#038; Drag List</title>
		<link>https://doeverythingiwant.com/swiftui-drop-drag-list/</link>
					<comments>https://doeverythingiwant.com/swiftui-drop-drag-list/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Sun, 09 Jun 2024 10:41:02 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[Drop and Drag]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[SwiftUI]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=2280</guid>

					<description><![CDATA[忘記是那一年的WWDC公布了一個拖曳相關的功能，主要是讓你可&#8230;]]></description>
										<content:encoded><![CDATA[
<p>忘記是那一年的WWDC公布了一個拖曳相關的功能，主要是讓你可以將iPhone 的資料像是圖片之類的丟到iPad 或Mac 上，因此有了 API 可以用在 Drop &amp; Drag 動作上。</p>



<p>最近因為工作需要花了一些時間在研究Drop &amp; Drag 應該要怎麼寫，需要實作的是一般的拖拉元件改變它的順序，並不需要讓它能拖到另一個裝置上，寫作上就會比較簡單一點。</p>



<span id="more-2280"></span>



<p>參考以下文件與官方網站來練習</p>



<p><a href="https://dogpa0130.medium.com/swift-practice-177-swiftui-%E6%8B%96%E6%9B%B3-drop-drag-%E6%94%B9%E8%AE%8A%E9%99%A3%E5%88%97array%E4%BD%8D%E7%BD%AE-b63ff5edb935" target="_blank" rel="noreferrer noopener">Swift Practice # 177 SwiftUI 拖曳 Drop &amp; Drag 改變陣列Array位置</a></p>



<p><a href="https://mobileappsacademy.medium.com/drag-and-drop-list-in-swiftui-30b53682d447" target="_blank" rel="noreferrer noopener">Drag and Drop List in SwiftUI</a></p>



<p><a href="https://developer.apple.com/documentation/swiftui/drag-and-drop" target="_blank" rel="noreferrer noopener">Apple Documentation &#8211; Drag and Drop</a></p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Start</h2>



<p>一開始當然要先做一個要進行拖曳的List，建立一個顯示各種茶的List</p>



<p>建立List 的 Item View</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>struct TeaItemView: View {
    
    var title: String
    
    var body: some View {
        HStack {
            Text(title)
                .font(.system(size: 26))
        }
        .frame(maxWidth: .infinity)
        .padding(.vertical, 30)
        .background(.green)
        .clipShape(RoundedRectangle(cornerRadius: 20))
        
    }
}</code></pre></div>



<p>再建立一個用 <code>ForEach</code> 做的 List </p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>import SwiftUI

struct ContentView: View {
    
    @State private var draggedItem: String?
    @State private var fruitItems = [&quot;&#x1fad6; Tea&quot;, &quot; &#x2615; Coffee&quot;, &quot; &#x1f9c3; Juice&quot;, &quot;&#x1f9cb;Pearl milk tea&quot;, &quot;&#x1f95b; Milk&quot;, &quot;&#x1f379; Fruit tea&quot;, &quot;&#x1f9c9; Mate tea&quot;, &quot;&#x1f375; Matcha tea&quot;]
    
    var body: some View {
        
        ScrollView(showsIndicators: false) {
            VStack(spacing: 10) {
                ForEach(fruitItems, id: \.self) { fruit in
                    TeaItemView(title: fruit)
                }
            }
            .padding(.horizontal, 20)
        }
        .background(.black)
    }
}</code></pre></div>



<p><code>draggedItem</code> 是負責記錄要被拖曳的元件，再來Run 一下程式，會出現下面畫面</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="295" height="640" src="https://doeverythingiwant.com/wp-content/uploads/2024/06/teaList.gif" alt="" class="wp-image-2285"/></figure>
</div>


<h2 class="wp-block-heading">DrogDelegate</h2>



<p>如果要讓List 的元件可以被拖曳，必須實作 DropDelegate 這個Protocol，新增一個 DropDragDelegate 來實作它</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>import SwiftUI

struct DropDrapDelegate: DropDelegate {
    
    let destinationItem: String
    
    @Binding var fruits: [String]
    @Binding var draggedItem: String?
    
    func dropUpdated(info: DropInfo) -&gt; DropProposal? {
        return DropProposal(operation: .move)
    }
    
    func performDrop(info: DropInfo) -&gt; Bool {
        draggedItem = nil
        return true
    }
    
    func dropEntered(info: DropInfo) {
        if let draggedItem {
            let fromIndex = fruits.firstIndex(of: draggedItem)
            if let fromIndex {
                let toIndex = fruits.firstIndex(of: destinationItem)
                if let toIndex, fromIndex != toIndex {
                    withAnimation {
                        self.fruits.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: (toIndex &gt; fromIndex ? (toIndex + 1) : toIndex))
                    }
                }
            }
        }
    }
}</code></pre></div>



<p>在這個 Delegate 中有幾個需要實作的 function</p>



<p><code>func dropUpdated(info: DropInfo) -> DropProposal?</code>  </p>



<p>宣告這個拖曳是要什麼動作，以範例來說只是要做移動，所以是 <code>.move</code>，可以去官方文件查看 <code>DropProposal</code> 可以知道更多設定值，預設值會是 <code>nil</code>。</p>



<p><code>func performDrop(info: DropInfo) -> Bool</code></p>



<p>拖曳完成的時候會呼叫到它，回傳 <code>true</code> 表示 Drop was successful</p>



<p><code>func dropEntered(info: DropInfo)</code></p>



<p>在這個拖曳要做些什麼? 這是我對這個function 的理解，這個範例主要是要交換順序，所以裡面寫的就是交換的方式</p>



<h2 class="wp-block-heading">View</h2>



<p>再回到 View ，將 <code>OnDrop</code> 與 <code>OnDrag</code> 加到剛剛寫好的 List 中</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>ScrollView(showsIndicators: false) {
    VStack(spacing: 10) {
        ForEach(fruitItems, id: \.self) { fruit in
            TeaItemView(title: fruit)
                .onDrag({
                    draggedItem = fruit
                    return NSItemProvider()
                })
                .onDrop(of: [.text], delegate: DropDrapDelegate(destinationItem: fruit, fruits: $fruitItems, draggedItem: $draggedItem))
            
        }
    }
    .padding(.horizontal, 20)
}
.background(.black)
</code></pre></div>



<p><code>onDrag</code> </p>



<p>這個的重點在於 <code>NSItemProvider</code> ，<code>NSItemProvider</code> 它是為了在進行 <code>Drag-and-drop</code> 或是 <code>Copy-and-paste</code> 時傳送資料或檔案用的，設定方式我自己是不太理解，常見的設定是 <code>URL</code>，這個範例用不到，就直接建立一個空的ItemProvider，若有需要傳送資料到不同裝置上的話應該需要留意一下。</p>



<p><code>onDrop(of:delegate:)</code></p>



<p>設定Type與Delegate ，Delegate 就是先前建立的 DropDrapDelegate，直接代需要的參考就好。</p>



<p>Type的設定有二種方式，第一種是給它 <code>The uniform type identifiers</code> ，這是需要可以辨識要傳送的資料，查了一下，應該是可以自定這個 Identifier。第二種是設定 <code>UTType</code> ，在範例中的 <code>[.text]</code> 就是 <code>UTType</code> 裡的 <code>Text</code>，在官方文件中可以看到很多種類型，那<code>UTType</code>是什麼? 它其實就是指要進行<code>load, send, or receive</code>的資料類型。</p>



<p>加上 <code>onDrag</code> 與 <code>onDrop</code> 之後就可以進行拖曳了</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="295" height="640" src="https://doeverythingiwant.com/wp-content/uploads/2024/06/Finish.gif" alt="" class="wp-image-2288"/></figure>
</div>


<p>可以在 <a href="https://github.com/AprilXMoon/DragDropDemo" target="_blank" rel="noreferrer noopener">Github</a> 上下載這個範例程式</p>



<p>最後祝大家 Coding 愉快!!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/swiftui-drop-drag-list/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>SDK, Framework and Library</title>
		<link>https://doeverythingiwant.com/sdk_framework_library/</link>
					<comments>https://doeverythingiwant.com/sdk_framework_library/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Sun, 17 Dec 2023 09:47:07 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[Framework]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[Librar]]></category>
		<category><![CDATA[SDK]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=2206</guid>

					<description><![CDATA[Photo by Safar Safarov on Unsp&#8230;]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center">Photo by <a href="https://unsplash.com/@safarslife?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank" rel="noopener">Safar Safarov</a> on <a href="https://unsplash.com/photos/turned-on-gray-laptop-computer-MSN8TFhJ0is?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank" rel="noopener">Unsplash</a></p>



<p>前些日子突然和同事聊起了 SDK、Framework 和 Library 差別在哪裡? </p>



<p>就我所知 SDK 包含了一堆東西，UI、function …等等，就範圍來說是最大的，Framework 和 Library 很像，都是有一些funcation 可以讓開發人員呼叫。</p>



<p>嗯…感覺好像不是很清楚，還記得以前有找過資料，但是後來都忘記了，想說正好有個機會重新再把這三個東西了解更多一點</p>



<span id="more-2206"></span>



<h2 class="wp-block-heading">SDK</h2>



<p>SDK(<em>Software Development Kit</em>) 軟體開發套件，感覺只要有帶 Kit 這個字的總是有一群東西…</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>A software development kit (SDK) is a collection of software development tools in one installable package. They facilitate the creation of applications by having a compiler, debugger and sometimes a software framework. They are normally specific to a hardware platform and operating system combination. To create applications with advanced functionalities such as advertisements, push notifications,etc; most application software developers use specific software development kits.</p>
<cite>Wikipedia</cite></blockquote>



<p>SDK 除了包含 Framework 外還有compiler、 debugger。</p>



<p>舉例的話，對開發者來說，我們常常在工作時說的 iOS 就是一個 SDK，iOS SDK 提供 iOS App 的開發環境，當然要透過 Xcode 這個IDE (<em>integrated development environment</em>)工具來開發，同理也有 Android SDK。</p>



<p>這和使用者常常指的iOS 不一樣，iPhone 的iOS 是 Operating Systems 是負責管理App的系統，不要搞錯了喔~</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Framework</h2>



<p>Framework 就如其名，是一個框架，一個package</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Software framework is an abstraction in which software, providing generic functionality, can be selectively changed by additional user-written code, thus providing application-specific software. It provides a standard way to build and deploy applications and is a universal, reusable software environment that provides particular functionality as part of a larger software platform to facilitate the development of software applications, products and solutions.</p>
<cite>Wikipedia</cite></blockquote>



<p>Framework 基本上提供了許多可以幫助你完成應用程式的一些元件。</p>



<p>舉例來說，SwiftUI &amp; UIKit 它們都是Framework ，它們提供了很多 UI 相關框架(元件)讓開發者只需要加上一些程式碼就可以完成畫面，至於像是 Text 是怎麼處理多語系與字型、List 是怎麼讓畫面可以下拉更新，這些都不需要開發者處理。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Library</h2>



<p>Library 就是一堆函式的集合，最早在學習 C++ 的時候就知道的名詞</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Library is a collection of non-volatile resources used by computer programs, often for software development. These may include configuration data, documentation, help data, message templates, pre-written code and subroutines, classes, values or type specifications.</p>
<cite>Wikipedia</cite></blockquote>



<p>Library &#8230; 嗯…看來就是一堆可以提供你呼叫用的function、class、 data ，這個就有點難舉例了…</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Library vs Framework</h2>



<p>說起來其實 Library 和 Framework 很像，上網找了一下多數說二者之間的差別在於<em> Inversion of Control</em> </p>



<p>Library 的話，開發者可以呼叫 Library 裡的 function 做一些處理，最後會得到結果，然後再由開發者決定要做些什麼處理，控制權是在開發者手上的</p>



<pre class="wp-block-code"><code>You own the control</code></pre>



<p>Framework提供了框架並擁有內建行為，開發者可以依照Framework 提供的框架，填入一些程式碼，讓 Framework 來幫你處理後面的事情，你只需要在框架指定的地方寫上程式碼就行了，控制權反而是在 Framework 手上，這種情況就稱為 <em>Inversion of Control</em> </p>



<pre class="wp-block-code"><code>The framework calls you</code></pre>



<h2 class="wp-block-heading">總結</h2>



<p>SDK : 包含 Libraries、Frameworks、Tools、Documentation ，所表示的概念比較大範圍</p>



<p>Framework: 提供框架(包含現成可用的程式)，呼叫時通常只需要再寫上一些程式碼即可，控制權是屬於 Framework的</p>



<p>Library: 提供可讓開發者呼叫的 function、data、class…等，控制權是屬於開發者的</p>



<p>最後照慣例祝大家 Coding 愉快!!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/sdk_framework_library/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>從零開始學SwiftData</title>
		<link>https://doeverythingiwant.com/learningswiftdata/</link>
					<comments>https://doeverythingiwant.com/learningswiftdata/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Tue, 10 Oct 2023 02:31:00 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[SwiftData]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=2112</guid>

					<description><![CDATA[Photo by Nathan da Silva on Un&#8230;]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center">Photo by <a href="https://unsplash.com/@silvawebdesigns?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank" rel="noopener">Nathan da Silva</a> on <a href="https://unsplash.com/photos/k-rKfqSm4L4?utm_content=creditCopyText&amp;utm_medium=referral&amp;utm_source=unsplash" target="_blank" rel="noopener">Unsplash</a></p>



<p>WWDC23 中發表了一個新的Framework &#8211; SwiftData，它是本機端資料儲存方式之一，過去若想用DB方式將資料存在本機端最常用的就二種，一是iOS本家的Core Data，另一個就是第三方工具的SQLite，Core Data 雖然很好用，但是前置作業非常麻煩，設定完之後一定不會記得，所以SQLite看起來比較多人喜歡。</p>



<p>然而SwiftData 簡化了非常多Core Data 需要做的事情，讓資料儲存變得非常簡單，當然也完全支援SwiftUI，這樣的東西怎麼可以不學起來呢?</p>



<p>本篇主要會使用Apple 與 AppCoda 的教學來學習</p>



<span id="more-2112"></span>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>想要使用SwiftData 需要使用 <strong>Xcode15 &amp; iOS 17</strong> 才行</p>



<p>本篇的參考教學文章在此</p>



<p>Apple</p>



<ul class="wp-block-list">
<li><a href="https://developer.apple.com/videos/play/wwdc2023/10154/" target="_blank" rel="noreferrer noopener">WWDC23 Build an app with SwiftData</a></li>



<li>Sample code <a href="https://developer.apple.com/documentation/SwiftUI/Building-a-document-based-app-using-SwiftData" target="_blank" rel="noreferrer noopener">Building a document-based app using SwiftData</a></li>
</ul>



<p>AppCoda</p>



<ul class="wp-block-list">
<li><a href="https://www.appcoda.com/swiftdata/" target="_blank" rel="noreferrer noopener">Getting Started with SwiftData for SwiftUI Development</a></li>
</ul>



<p>本文章的程式碼多數來自於Apple的 Sample code</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>SwiftData 是和Core Data 一樣可以讓你將資料儲存在本機端中，它特別的地方在於可以直接將寫好的DataModel 變成是儲存的物件且寫法非常簡單，首先需要寫好一個DataModel，請注意必需要使用Class 才行</p>



<h2 class="wp-block-heading">建立Model</h2>



<p>若是下載原本的Sample code 的話配合WWDC23的影片一起學習會比較好喔，我是直接抓重點來說，借用在 Building a document-based app using SwiftData 的範例中的 Card DataModel</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift" data-show-lang="1"><code>final class Card {
    var front: String
    var back: String
    var creationDate: Date

    init(front: String, back: String, creationDate: Date = .now) {
        self.front = front
        self.back = back
        self.creationDate = creationDate
    }
}</code></pre></div>



<p>若要把這個Card 變成 SwiftData 的 Model的話，只要做二個步驟</p>



<p>第一是<strong> import SwiftData</strong></p>



<p>第二是在 Class 加上 <strong>@Model</strong></p>



<p>好，完工</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>import SwiftData

@Model
final class Card {
    var front: String
    var back: String
    var creationDate: Date

    init(front: String, back: String, creationDate: Date = .now) {
        self.front = front
        self.back = back
        self.creationDate = creationDate
    }
}</code></pre></div>



<p>沒錯，就是這麼簡單! 如果想要設定 Unique Key 例如像是 ID 之類的，當然可以設定，只要在參數前面加上 <code>@Attribute(.unique)</code> 就行了</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>@Model
final class Card {
    @Attribute(.unique) var cardId: String
    ...
}</code></pre></div>



<p>還有一個比較特別的寫法是，如果哪天想要為這個參數變更名字，又不想要讓這個參數變成是一個新的參數，可以使用 <code>@Attribute(originalName:)</code>，這樣它就會自動對應到過去的名稱</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>@Model
final class Card {
    @Attribute(originalName: &quot;creationDate&quot;) var creation_Date: Date
    ...
}</code></pre></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">在SwiftUI 上使用</h2>



<p>接著說明怎麼串接到 SwiftUI 的畫面裡，先找到有 <code>@main</code> 的檔案，在 <code>ContentView()</code> 下加上</p>



<p><code>.modelContainer(for: Card.self)</code>，modelContainer 記得先前在使用Core Data 的時候也有類似的設定，這裡是把想要做為SwiftData 的model 在 for: 後面，當然可以寫很多個用Array 形式來寫</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>import SwiftUI
import SwiftData

@main
struct SwiftDataFlashCardSample: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .modelContainer(for: Card.self)
        }
    }
}</code></pre></div>



<p>接著在需要使用SwiftData 的View 上宣告 modelContext ，這就是用來做新增與刪除，再來就是要做Query ，只要宣告要使用的參數，這裡是 <code>var cards: [Card]</code>，在最前面的地方寫上 <code>@Query</code> 就完成了，</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>import SwiftData
struct ContentView: View {
    @Environment(\.modelContext) private var modelContext
    @Query private var cards: [Card]
    ...
}</code></pre></div>



<p>也可以在Query 的時候加上 Sort 或 Filter</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>@Query(sort: \.creationData) private var cards: [Card]
@Query(filter: #Predicate&lt;Person&gt; { Person in $0.name == &quot;John&quot; }) private var persons: [Person]</code></pre></div>



<p>加上 <code>@Query</code> 的參數就會自動追蹤變化，在參數有變更的時候自動更新畫面，所以並不需要特別做什麼處理。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">資料操作</h2>



<p>接著來說操作，資料操作主要的就是三個動作，新增(insert)、修改(update)與刪除(Delete)</p>



<h2 class="wp-block-heading">Insert</h2>



<p>Insert 的時候要使用到先前宣告過的 modelContext，呼叫 insert 的 function 即可</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>let newCard = Card(front: &quot;Sample Front&quot;, back: &quot;Sample Back&quot;)
modelContext.insert(newCard)</code></pre></div>



<h2 class="wp-block-heading">Update</h2>



<p>其實寫上 <code>@Model</code> 的 Data Object，SwiftData 就會自動追蹤參數變化，只要是已經有新增過的 Item 不需要特別寫些什麼，SwiftData 就會自動去更新它的參數內容</p>



<h2 class="wp-block-heading">Delete </h2>



<p>Delete 的時候和 Insert 一樣，可以告訴它想刪除的item 是那一個，也可以寫條件刪除</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>let card = Cards[0]
modelContext.delete(card)</code></pre></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>目前感覺SwiftData 比起Core Data 來說簡單很多，不過畢竟 SwiftData 才剛推出，功能就沒有 Core Data強大，就如同SwiftUI 剛推出的時候一樣，有很多UIKit 很容易做到的事情，SwiftUI 還沒有支援，相信之後SwiftData 功能會慢慢變多，就等它未來發展啦</p>



<p>最後祝大家 Coding 愉快!!</p>



<p>可以在<a href="https://github.com/AprilXMoon/Todolist" target="_blank" rel="noreferrer noopener">這裡</a>下載我依照 AppCoda 教學做出的Sample code</p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/learningswiftdata/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>從零開始寫 WatchOS(3) &#8211; Trouble Shootings</title>
		<link>https://doeverythingiwant.com/learning_watchos3_trouble_shootings/</link>
					<comments>https://doeverythingiwant.com/learning_watchos3_trouble_shootings/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Sun, 16 Jul 2023 09:24:32 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[WatchOS]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=2072</guid>

					<description><![CDATA[Photo by Raagesh C on Unsplash&#8230;]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center">Photo by <a href="https://unsplash.com/@raagesh?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener">Raagesh C</a> on <a href="https://unsplash.com/wallpapers/iphone/apple-watch?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener">Unsplash</a></p>



<p>當好不容易把Watch OS App 寫好之後，開開心心按下 Run 然後就發現怎麼跑起來和我想像的不一樣? 就算是用實機也無法順利執行App，如果你也有這個煩惱(?) 這篇把我在開發過程中遇到的問題紀錄下來，也許可以幫助到你</p>



<span id="more-2072"></span>



<h2 class="wp-block-heading">Pair Watch with simulator</h2>



<p>實機的話就比較不會有配對問題，若想要用Simulator 來跑的話，要怎麼樣才能配對Watch ?</p>



<p>首先，請記得下載 Watch Simulator，沒下載就什麼都不用說了</p>



<p>打開Xcode 之後請到 <strong>Window</strong> -> <strong>Devices and Simulators</strong>，選擇想要使用的Simulator在畫面上可以看到 Paired watches 這區，按下 + </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="686" src="https://doeverythingiwant.com/wp-content/uploads/2023/07/w001-1024x686.png" alt="" class="wp-image-2073" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/07/w001-1024x686.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w001-300x201.png 300w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w001-768x514.png 768w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w001-1536x1028.png 1536w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w001-2048x1371.png 2048w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w001-1920x1286.png 1920w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w001-1170x783.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w001-585x392.png 585w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w001-263x175.png 263w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>接著就可以選取想要配對的Watch，要記得自己配對的機種，之後在跑程式的時候記得要跑有配對的Simulator 才行喔</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="532" src="https://doeverythingiwant.com/wp-content/uploads/2023/07/w002-1024x532.png" alt="" class="wp-image-2074" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/07/w002-1024x532.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w002-300x156.png 300w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w002-768x399.png 768w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w002-585x304.png 585w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w002.png 1098w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">明明沒有出現 Error 訊息，資料卻沒有互相傳遞?</h2>



<p>這時候需要<strong>檢查二個地方</strong></p>



<h3 class="wp-block-heading">First</h3>



<p><strong>iOS App Target </strong>中的 <strong>General</strong> -><strong> Frameworks, Libraries and Embedded Content</strong>，是否有把 Watch OS App 給加進去</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="179" src="https://doeverythingiwant.com/wp-content/uploads/2023/07/w003-1024x179.png" alt="" class="wp-image-2077" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/07/w003-1024x179.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w003-300x52.png 300w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w003-768x134.png 768w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w003-1536x268.png 1536w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w003-1920x336.png 1920w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w003-1170x205.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w003-585x102.png 585w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w003.png 1968w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading">Second</h3>



<p>Watch OS Target 中的 Info 裡 <code>WKCompanionAppBundleIdentifier</code> ，這個參數後面要帶上 iOS App 的Bundle id，Watch OS App 是用這個參數在找要連接的 iOS App 所以可不要寫錯了</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="36" src="https://doeverythingiwant.com/wp-content/uploads/2023/07/w004-1024x36.png" alt="" class="wp-image-2078" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/07/w004-1024x36.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w004-300x11.png 300w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w004-768x27.png 768w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w004-1170x41.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w004-585x21.png 585w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w004.png 1410w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">打包失敗</h2>



<p>在好不容易跑起來且測試沒有問題時，要打包卻發現打包失敗!?</p>



<p>這時候請檢查 iOS App 與 Watch OS App 的 <strong>Version &amp; Build number 是否有一致</strong>，若沒有一致的話，會打包失敗的</p>



<p></p>



<p>以上皆是我自己遇到的問題，若有誤歡迎告知</p>



<p>最後祝大家 Coding 愉快!!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/learning_watchos3_trouble_shootings/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>從零開始寫 WatchOS(2) &#8211; Watch Connectivity</title>
		<link>https://doeverythingiwant.com/learing-watchos2-watch-connectivity/</link>
					<comments>https://doeverythingiwant.com/learing-watchos2-watch-connectivity/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Sun, 09 Jul 2023 08:22:30 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[WatchOS]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=2047</guid>

					<description><![CDATA[Photo by Raagesh C on Unsplash&#8230;]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center">Photo by <a href="https://unsplash.com/@raagesh?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener">Raagesh C</a> on <a href="https://unsplash.com/wallpapers/iphone/apple-watch?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener">Unsplash</a></p>



<p><a href="https://doeverythingiwant.com/learing-watchos2-watch-connectivity/" data-type="link" data-id="https://doeverythingiwant.com/learing-watchos2-watch-connectivity/">上篇</a>利用了 Apple 的官方教學簡單了解要怎麼建立一個 Watch App，不過在教學裡的Watch 資料是可以獨立取得的，在多數的情況下，都是要從 iOS App 中取得資料，需要與 iOS App 溝通才行，而 Watch Connectivity 就是用來與WatchOS 溝通的Framework</p>



<span id="more-2047"></span>



<p>本篇使用 Xcode 14.3 &amp; SwiftUI</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Watch Connectivity</h2>



<p>它是負責Watch 與 App 之前的溝通，有提供幾種資料傳遞，也有提供背景傳遞方式，本篇內容是利用 Watch Connectivity 中的SendMessage 來進行簡單的資料傳遞，這種方式是必須在手錶不是在背景的情況下才可以進行資料傳送，算是比較即時的資料傳送方式</p>



<p></p>



<p>WatchConnectivity 就如它的名字是負責用來做為 WatchOS App 與 iOS App 之間的溝通，因此在 WatchOS 端與 iOS 端都需要寫相同的function 用來確認連結與傳遞資料，就如圖片所示</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="566" src="https://doeverythingiwant.com/wp-content/uploads/2023/07/截圖-2023-07-02-下午9.11.56-1024x566.png" alt="" class="wp-image-2064" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/07/截圖-2023-07-02-下午9.11.56-1024x566.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2023/07/截圖-2023-07-02-下午9.11.56-300x166.png 300w, https://doeverythingiwant.com/wp-content/uploads/2023/07/截圖-2023-07-02-下午9.11.56-768x424.png 768w, https://doeverythingiwant.com/wp-content/uploads/2023/07/截圖-2023-07-02-下午9.11.56-1536x849.png 1536w, https://doeverythingiwant.com/wp-content/uploads/2023/07/截圖-2023-07-02-下午9.11.56-2048x1132.png 2048w, https://doeverythingiwant.com/wp-content/uploads/2023/07/截圖-2023-07-02-下午9.11.56-1920x1061.png 1920w, https://doeverythingiwant.com/wp-content/uploads/2023/07/截圖-2023-07-02-下午9.11.56-1170x646.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2023/07/截圖-2023-07-02-下午9.11.56-585x323.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">WCSection</h2>



<p>WCSession 無論那種傳送方式都是使用這個 Class ，在 iOS App 與 WatchOS App 都要寫上 WCSession，它會進行檢查與傳送工作</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>import WatchConnectivity
if WCSession.isSupported() {
    let session = WCSession.defaultSession()
    session.delegate = self
    session.activateSession()
}</code></pre></div>



<ul class="wp-block-list">
<li>需要先使用 <code>WCSession.isSupported()</code>確認是否有支援互相傳送資料</li>



<li>建立一個Session基本上沒有什麼特別需求的話就直接使用Default 的就好</li>



<li>設定好 Delegate </li>



<li>最後就是 Activate session</li>
</ul>



<h2 class="wp-block-heading">WCSessionDelegate</h2>



<p>這個Delegate 中有用來接收雙方資料的function 也有確認是否連線成功的 function，若使用 SwiftUI 來撰寫，接收會影響UI 更新的參數時，記得要使用 <code>@Published var</code> 來接，不然UI 就不會更新了</p>



<p>下面三個function 在 App 端一定要實作的，Watch 端的話只需要實作第一個，簡單說一下這三個的用途</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
    
func sessionDidBecomeInactive(_ session: WCSession) {
}

func sessionDidDeactivate(_ session: WCSession) {
}</code></pre></div>



<ul class="wp-block-list">
<li><code>activationDidCompleteWith</code>，就是當呼叫完 <code>activateSession</code> 之後會觸發</li>



<li><code>sessionDidBecomeInactive</code>，停止連線的時候會觸發</li>



<li><code>sessionDidDeactivate</code>，當所有資料傳送完成且停止連線的時候會觸發</li>
</ul>



<p>本篇中在這三個時機點並沒有要特別處理什麼事情，所以並不會在這三個function 裡寫東西。</p>



<p></p>



<h2 class="wp-block-heading">Send Message</h2>



<p>用 Message 的方式傳送的 Data 是 Dictionary ，Key 可以自定，使用 Dictionary 就可以使用Decodable 的讓它變成 Data Object 這樣之後會比較好處理</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func sendHiToWatch() {
    guard let wcSection = section, wcSection.isReachable else {
         return
    }
    
    let hiDic = [&quot;Say&quot;: &quot;Hi, Watch&quot;]
    wcSection.sendMessage(hiDic) { result in
        print(&quot;Result : \(result)&quot;)
    }
}</code></pre></div>



<p>在 <code>sendHiToWatch</code> 裡簡單建立一了個 Dictionary ，Key是 &#8220;Say&#8221;，Value是 &#8220;Hi, Watch&#8221;，因為 Message 的傳送方式  Watch App 必需要在前景，因此使用 <code>isReachable</code> 來確認是否可以傳送Message。</p>



<p>再來就是呼叫 <code>sendMessage</code> 這個func 將想要的資料傳送出去</p>



<h2 class="wp-block-heading">Receive Message</h2>



<p>在 <code>WCSessionDelegate</code> 中 <code>didReceiveMessage</code> 就是用來接收，可以寫在App 端與 Watch 端，接收完之後也可以依情況回傳 <code>reply</code> ，<code>reply</code> 的資訊會在 <code>sendMessage</code> 的Result中。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-swift" data-lang="Swift"><code>func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -&gt; Void) {
        
     let hiMessage = message[&quot;Say&quot;] as! String
        
     DispatchQueue.main.async {
         self.appMessage = hiMessage
     }
        
     let reply = [&quot;Get&quot;: &quot;Good&quot;]
     replyHandler(reply)
}</code></pre></div>



<p>之所以使用 <code>DispatchQueue.main.async{} </code>是因為 <code>appMessage</code> 這個參數是要用來更新UI 的，更新UI 的參數一定要在main thread 上才行喔。</p>



<h2 class="wp-block-heading">Run</h2>



<p>寫完之後就可以跑跑看啦~ 若是要用Simulator 的話，記得要下載Watch Simulator 並且與 iPhone Simulator 配對，此外還有一些設定要做，不然就會發現App 都可以跑起來但是就是無法傳送資料</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="865" height="1024" src="https://doeverythingiwant.com/wp-content/uploads/2023/07/w005-865x1024.png" alt="" class="wp-image-2075" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/07/w005-865x1024.png 865w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w005-254x300.png 254w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w005-768x909.png 768w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w005-1298x1536.png 1298w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w005-1170x1384.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w005-585x692.png 585w, https://doeverythingiwant.com/wp-content/uploads/2023/07/w005.png 1616w" sizes="(max-width: 865px) 100vw, 865px" /></figure>
</div>


<p><a href="https://doeverythingiwant.com/coding/%e5%be%9e%e9%9b%b6%e9%96%8b%e5%a7%8b%e5%af%ab-watchos3-trouble-shootings/" target="_blank" rel="noreferrer noopener">下一篇會整理一下我在這個中間遇到的一些問題，並說明要 Simulator要如何配對</a></p>



<p>最後祝大家 Coding 愉快!!</p>



<h2 class="wp-block-heading">Reference</h2>



<h5 class="wp-block-heading">Video</h5>



<p><a rel="noreferrer noopener" href="https://developer.apple.com/videos/play/wwdc2021/10003/" target="_blank">There and back again: Data transfer on Apple Watch</a></p>



<p><a href="https://developer.apple.com/videos/play/wwdc2015/713/" target="_blank" rel="noreferrer noopener">Introducing Watch Connectivity</a></p>



<h5 class="wp-block-heading">Document</h5>



<p><a rel="noreferrer noopener" href="https://developer.apple.com/documentation/watchconnectivity" target="_blank">Watch Connectivity</a></p>



<p></p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/learing-watchos2-watch-connectivity/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>從零開始寫 WatchOS</title>
		<link>https://doeverythingiwant.com/learning-apple-watch-app/</link>
					<comments>https://doeverythingiwant.com/learning-apple-watch-app/#respond</comments>
		
		<dc:creator><![CDATA[艾普利]]></dc:creator>
		<pubDate>Tue, 04 Apr 2023 15:04:03 +0000</pubDate>
				<category><![CDATA[寫程式]]></category>
		<category><![CDATA[iOS]]></category>
		<category><![CDATA[WatchOS]]></category>
		<guid isPermaLink="false">https://doeverythingiwant.com/?p=2024</guid>

					<description><![CDATA[Photo by Raagesh C on Unsplash&#8230;]]></description>
										<content:encoded><![CDATA[
<p class="has-text-align-center">Photo by <a href="https://unsplash.com/@raagesh?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener">Raagesh C</a> on <a href="https://unsplash.com/wallpapers/iphone/apple-watch?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener">Unsplash</a></p>



<p>此篇主要是我看  Apple <a rel="noreferrer noopener" href="https://developer.apple.com/tutorials/swiftui" target="_blank">SwiftUI&nbsp;Tutorials</a> 中的 <a rel="noreferrer noopener" href="https://developer.apple.com/tutorials/swiftui/creating-a-watchos-app" data-type="URL" data-id="https://developer.apple.com/tutorials/swiftui/creating-a-watchos-app" target="_blank">Creating a watchOS App </a>學習 watchOS 時，因為使用 Xcode 14撰寫有些不同的地方，就想記錄下來，在 Xcode 14 之後建立 watchOS 已經不會出現Extension Folder 了，主因是現在可以建立獨立的watchOS ，不過目前多數 watchOS App 還是屬於輔助 iOS App 的角色</p>



<span id="more-2024"></span>



<p>本篇使用 Xcode 14.2 &amp; SwiftUI</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>從<a rel="noreferrer noopener" href="https://developer.apple.com/tutorials/swiftui/creating-a-watchos-app" target="_blank">Creating a watchOS App</a> 中下載專用的Project，解壓縮之後可以看到有 &#8220;Complete&#8221; &amp; &#8220;StartingPoint&#8221; Folders，Complete 是已經完成的App，即然是要學習寫 watchOS 當然是開啟 StartingPoint 中的專案，開啟專案之後，會發現這個專案其實是延續先前的教學，若你有<a rel="noreferrer noopener" href="https://developer.apple.com/tutorials/swiftui" target="_blank">SwiftUI&nbsp;Tutorials</a>第一章開始練習的話，應該不陌生。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">建立 Target</h2>



<p>教學的第一步就是建立 Target ，在開啟 add Target 之後就發現畫面不一樣了，若沒有下載任何watchOS Simulator 的話，Xcode 會提配你要下載，下載Simulator 大概要3G記得確認一下容量。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="727" src="https://doeverythingiwant.com/wp-content/uploads/2023/04/010-1024x727.png" alt="" class="wp-image-2025" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/04/010-1024x727.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2023/04/010-300x213.png 300w, https://doeverythingiwant.com/wp-content/uploads/2023/04/010-768x545.png 768w, https://doeverythingiwant.com/wp-content/uploads/2023/04/010-1170x830.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2023/04/010-585x415.png 585w, https://doeverythingiwant.com/wp-content/uploads/2023/04/010.png 1460w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>切到watchOS 的頁籤還是可以看到 App Intents Extension，實際上選它建立出來的就和先前的有87%像，不過少了watchApp這個Target，感覺上怪怪的? 後來看了WWDC的Video 才發現應該要選擇 Application 中的App</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="726" src="https://doeverythingiwant.com/wp-content/uploads/2023/04/011-1024x726.png" alt="" class="wp-image-2027" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/04/011-1024x726.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2023/04/011-300x213.png 300w, https://doeverythingiwant.com/wp-content/uploads/2023/04/011-768x545.png 768w, https://doeverythingiwant.com/wp-content/uploads/2023/04/011-1170x830.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2023/04/011-585x415.png 585w, https://doeverythingiwant.com/wp-content/uploads/2023/04/011.png 1464w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>按下 Next 之後會發現有幾個選項</p>



<ul class="wp-block-list">
<li>Watch-only App，是建立獨立App 用的</li>



<li>Watch App with New Companion iOS App，選這個會再幫你建立一個 iOS App</li>



<li>Watch App for Existing iOS App，在已存在的App 中建立Watch App </li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="748" height="210" src="https://doeverythingiwant.com/wp-content/uploads/2023/04/012.png" alt="" class="wp-image-2028" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/04/012.png 748w, https://doeverythingiwant.com/wp-content/uploads/2023/04/012-300x84.png 300w, https://doeverythingiwant.com/wp-content/uploads/2023/04/012-585x164.png 585w" sizes="(max-width: 748px) 100vw, 748px" /></figure>



<p>前二個選項應該是給建立新專案時選的，當你直接建立 WatchOS project 的時候，前二個選項使用起來比較合理，若在已經存在的專案加入WatchOS target 前二個選項反而比較不太適用，總之以這個教學App來說就是要選用第三個選項來建立Watch App</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="644" height="198" src="https://doeverythingiwant.com/wp-content/uploads/2023/04/013.png" alt="" class="wp-image-2029" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/04/013.png 644w, https://doeverythingiwant.com/wp-content/uploads/2023/04/013-300x92.png 300w, https://doeverythingiwant.com/wp-content/uploads/2023/04/013-585x180.png 585w" sizes="(max-width: 644px) 100vw, 644px" /></figure>



<p>再來就照著教學指示，輸入名字再按下Next 後 Activate Watch scheme 就可以了。</p>



<p>Supports Running Without iOS App Installation 這個選項可以在Watch App 的 Target 裡找到</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="209" src="https://doeverythingiwant.com/wp-content/uploads/2023/04/014-1024x209.png" alt="" class="wp-image-2030" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/04/014-1024x209.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2023/04/014-300x61.png 300w, https://doeverythingiwant.com/wp-content/uploads/2023/04/014-768x157.png 768w, https://doeverythingiwant.com/wp-content/uploads/2023/04/014-1536x313.png 1536w, https://doeverythingiwant.com/wp-content/uploads/2023/04/014-1920x391.png 1920w, https://doeverythingiwant.com/wp-content/uploads/2023/04/014-1170x239.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2023/04/014-585x119.png 585w, https://doeverythingiwant.com/wp-content/uploads/2023/04/014.png 1952w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">共享資料</h2>



<p>教學一開始就要求你把Watch 裡的 Assets 刪除，我試過之後發現刪除之後Build 會告知找不到 Watch App Icon，幾次嘗試之後，我決定不把 Watch 的 Assets，直接把icon 圖放上去，在 Xcode 更新之後 App icon 可以只給 1024*1024 的圖，其他尺寸的icon 全都由Xcode 自動處理，說真的這功能很棒且早就應該要這麼做了啊!! icon尺寸真的很多…遇到設計師不是很用心，想請對方出圖的時候就很痛苦，追圖也很痛苦……</p>



<p>另一個就是 <code>LandmarksApp.swift </code>這個檔案可以不用與Watch App 共享，因為Watch App 有自己的入口點了，若命名是照教學取名的話，就是<code>WatchLandmarksApp.swift</code> 這個檔案。記得將原本寫在 <code>LandmarksApp.swift</code> 裡的內容也Copy 到<code>WatchLandmarksApp.swift</code>，主要是這個Watch App 所用的資料並沒有需要從 iOS App 這裡取得，直接Copy 寫法就可以了。</p>



<p>剩下的就可以依照教學選取需要共享的檔案，並在Target Membership 把 WatchLandmarks Watch App 這個打勾</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="524" height="206" src="https://doeverythingiwant.com/wp-content/uploads/2023/04/015.png" alt="" class="wp-image-2032" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/04/015.png 524w, https://doeverythingiwant.com/wp-content/uploads/2023/04/015-300x118.png 300w" sizes="(max-width: 524px) 100vw, 524px" /></figure>



<p></p>



<h2 class="wp-block-heading">建立Detail View &amp;  加上 List</h2>



<p>這二段照著教學做就可以了，基本上沒有什麼需要特別留意的地方，只要記得是直接新增檔案在 Watch App</p>



<p></p>



<h2 class="wp-block-heading">建立 Custom Notification Interface</h2>



<p>先前建立 Watch Extension 的時候會自動建立 <code>NotificationView</code> &amp; <code>NotificationController </code>，但後來就不會自動建立了，所以若是要建立 Custom Notification Interface 的話，上面二個檔案需要自行建立，所以就先到 New -&gt; File 建立這二個檔案之後再照著教學做吧~</p>



<p>當想要Run 起來試試的時候，發現不能測試，這時候需要新增一個 Scheme，並把Watch Interface 選為 <strong>Dynamic Notification</strong>，需要用這個 Scheme 去Run 才可以試 Notification</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="555" src="https://doeverythingiwant.com/wp-content/uploads/2023/04/016-1024x555.png" alt="" class="wp-image-2033" srcset="https://doeverythingiwant.com/wp-content/uploads/2023/04/016-1024x555.png 1024w, https://doeverythingiwant.com/wp-content/uploads/2023/04/016-300x163.png 300w, https://doeverythingiwant.com/wp-content/uploads/2023/04/016-768x416.png 768w, https://doeverythingiwant.com/wp-content/uploads/2023/04/016-1536x832.png 1536w, https://doeverythingiwant.com/wp-content/uploads/2023/04/016-1170x634.png 1170w, https://doeverythingiwant.com/wp-content/uploads/2023/04/016-585x317.png 585w, https://doeverythingiwant.com/wp-content/uploads/2023/04/016.png 1868w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>另外還要記得 Payload 要加上<code> Simulator Target Bundle</code> 這個 Key，就樣就可以像iPhone 一樣，直接將Payload 文件拖拉到Simulator 上測試啦~</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<p>一開始有說到下載的檔案裡有Complete 的 Folder ，若真的不知道要怎麼寫的話，可以從這裡去對照看，我自己是覺得看教學還是要自己親自去打過一遍會比較有印象。</p>



<p>這個教學並沒有教到要如何和 iOS App 做資料傳遞，只是建立簡單的 Watch App，若想要和iOS App 進行資料傳遞是要使用到 Watch Connectivity 這個 Framework，它提供了雙向溝通的方式，之後有機會再寫一篇來實作這個。</p>



<p>最後祝大家 Coding 愉快!!</p>
]]></content:encoded>
					
					<wfw:commentRss>https://doeverythingiwant.com/learning-apple-watch-app/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
