Hi, I’m Hsien-Te Hsieh 🧑🏻‍💻

  • • Lawyer turned software developer, based in Taipei, Taiwan
  • • Software developer focused on iOS development
  • • Expanding into backend, DevOps, and API integration

[Day 27] 實現 iOS 自動化部署(二)- 設定 Azure Pipelines

前言 在上一篇文章中,我們完成了 iOS 專案的本地端準備工作。現在,我們將實際設定 Azure Pipelines,目標是讓專案能夠自動化建置並部署到 App Store Connect。 步驟一:Apple Connect 新增 API 金鑰 為了讓 Azure Pipelines 這類 CI/CD 工具能以自動化的方式將 App 上傳至 App Store Connect,我們必須先產生一組專用的 API 金鑰。這組金鑰將授權 Azure Pipelines 執行上傳等操作。 首先登入 App Store Connect 帳號。 登入後,點擊進入「使用者與存取權限」區塊。 選取「使用者與存取權限」,並要求存取權限。 接著,切換到「整合」頁籤,點擊「產生 API 金鑰」。 為這組金鑰設定一個好辨識的名稱,並在「存取權限」欄位選擇「App 管理」。這個權限足以讓 Pipeline 執行建置、上傳與發布等任務。 金鑰產生後,請下載 .p8 檔案並妥善保存,因為它只能下載一次。同時,將頁面上的 Key ID 和 Issuer ID 記錄下來,這些資訊在後續設定中都會用到。 步驟二:Azure Pipelines Connection 設定 有了 Apple 提供的憑證後,接下來我們回到 Azure DevOps,設定 Pipeline 與 App Store Connect 之間的橋樑。 ...

October 10, 2025 · 2 分鐘

[Day 26] 實現 iOS 自動化部署(一)- App Store 的前置作業

前言 時間來到了第 26 天,App「台灣公路 K 點通」(對,取名了XD) 核心功能已大致完成,是時候著手建立一套自動化的 CI/CD (持續整合/持續部署) 流程了。預想目標是,當推送一個新的 Git Tag 時,Azure DevOps 能自動幫我完成測試、建置、簽署,並將 App 上傳到 App Store Connect,以供後續提交審查。 但在開始處理 CI/CD 之前,有個前置作業必須先完成,那就是我們必須先在 Apple 的後台註冊我們的 App。 CI/CD 無法無中生有 在我們的情境當中,CI/CD 工具的角色是更新部署一個已經存在的 App 紀錄。它們無法替我們決定 App 的名稱、定價、隱私權政策等需要人工決策的資訊。因此,我們必須先手動完成 App 的註冊與設定,CI/CD 的自動化流程才能接著運作。 整個前置作業分為兩步驟: 在 Apple Developer 網站註冊 App ID 在 App Store Connect 新增 App 步驟一:在 Apple Developer 網站註冊 App ID App ID 是你的 App 在 Apple 生態系中獨一無二的識別碼,也稱之為Bundle Identifier。CI/CD 工具和 Xcode 都會用這個 ID 來識別你的專案。 登入 Apple Developer 網站 前往 developer.apple.com,使用你的開發者帳號登入。 ...

October 9, 2025 · 2 分鐘

[Day 25] 使用 XCTest 來建立單元測試

前言 CSV 資料的讀取與解析,這項功能是 App 的核心之一。然而,隨著專案越來越複雜,我們很可能會在未來的某個時候不小心改動到相關程式碼,或是 CSV 檔案有異動,導致這個功能壞掉。為了確保它能一直穩定運作,我們需要為它建立一道防線——單元測試。 單元測試可以幫助我們驗證一小段程式碼(一個「單元」)的行為是否符合預期。今天,我們將使用 Apple 官方的測試框架 XCTest 來為 RoadDataManager 撰寫測試,確保它在面對正常與異常資料時,都能做出正確的反應。 將 XCTest 加入專案 如果你的專案一開始建立時沒有勾選 Include Tests,我們需要手動將其加入。 首先,在 Xcode 專案導覽器中點擊最上層的專案檔案,進入設定頁面。在「TARGETS」區域點擊左下角的「+」按鈕,選擇新增「Unit Testing Bundle」。你可以為這個測試目標命名,例如 RoadMileLocatorTests,Xcode 便會為你建立好測試所需的環境。 撰寫你的測試案例 建立測試目標後,Xcode 會自動生成一個樣板檔案。雖然可以直接使用,但更好的做法是為特定的類別建立專屬的測試檔案。這樣一來,當 RoadDataManager 的功能需要擴充或修改時,我們就能立刻找到對應的測試案例來進行驗證。 import XCTest final class RoadMileLocatorTests: XCTestCase { override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDownWithError() throws { // Put teardown code here. This method is called after the invocation of each test method in the class. } func testExample() throws { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. // Any test you write for XCTest can be annotated as throws and async. // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. } func testPerformanceExample() throws { // This is an example of a performance test case. measure { // Put the code you want to measure the time of here. } } } 這個是由 Xcode 自動產生的單元測試的 Template: ...

October 8, 2025 · 2 分鐘

[Day 24] 地理圍欄通知(二)

前言 首先簡要回顧前幾天的進度: Day 11:我們實作了地理圍欄的核心功能,讓 App 能夠在用戶進入或離開特定區域時觸發事件 。 Day 21 & 22:我們優化了地圖互動,實現了點擊圖釘後彈出詳細資訊視窗(Sheet),並加入了跳轉至 Apple Maps 或 Google Maps 的功能 。 Day 23:我們導入了 UserDefaults 和 Codable,實現了追蹤狀態的持久化。現在 App 即使關閉重啟,也能記得正在追蹤的地點 。 但程式碼跑起來是一回事,但在真實世界中是否可運作又是另一回事,所以今天的核心任務是驗證。我們將設計一系列的測試情境,徹底檢驗地理圍欄通知的穩定性與準確性,並記錄測試過程與結果。 測試準備 使用 GPX 檔案進行定位模擬 新增 GPX 檔案 為了確保測試的準確性與可重複性,我們需要一個可靠的方法來模擬使用者的移動。在 Day 11 測試地理圍欄功能時,我們是透過直接修改模擬器定位座標的方式來進行,雖然可行,但操作上較為繁瑣且不易模擬連續的路徑。今天我們將採用另一種方式,那就是在 Xcode 專案中匯入 GPX 檔案,用它來模擬「進入區域」與「離開區域」的完整行為。 GPX(GPS Exchange Format)是一種標準的 XML 格式,可以用來記錄 GPS 座標點。你可以在 Xcode 裡面直接新增一個 GPX 檔案,Xcode 會提供一個標出蘋果總部的座標預設的模板。 <?xml version="1.0"?> <gpx version="1.1" creator="Xcode"> <!-- Provide one or more waypoints containing a latitude/longitude pair. If you provide one waypoint, Xcode will simulate that specific location. If you provide multiple waypoints, Xcode will simulate a route visiting each waypoint. --> <wpt lat="37.331705" lon="-122.030237"> <name>Cupertino</name> <!-- Optionally provide a time element for each waypoint. Xcode will interpolate movement at a rate of speed based on the time elapsed between each waypoint. If you do not provide a time element, then Xcode will use a fixed rate of speed. Waypoints must be sorted by time in ascending order. --> <time>2014-09-24T14:55:37Z</time> </wpt> </gpx> 這個模板讓我們可以直接修改 <wpt> 標籤中的 lat 和 lon數值,將其變更為我們需要的任何地點。不過,若要模擬一條完整的路線,手動輸入每個路徑點的座標顯然不切實際。因此,這裡我們不打算自己逐點加入,而是借助第三方工具來更快速地產出包含連續路徑的 GPX 檔案。 ...

October 7, 2025 · 2 分鐘

[Day 23] 地理圍欄通知(一)

前言 今天要來完成的是地理圍欄(Geofencing)功能,讓使用者可以針對選定的里程位置訂閱通知,當進入/離開該區域時,會收到推播通知。 關於在 LoactionManager 裡面實作有關包含定位權限、通知權限以及建立與監控 Geofencing 的邏輯,我們在 Day 11 時已經有相當程度的介紹了,因此今天主要是將 UI 建立好,並串接以上的邏輯,而重點會放在「如何管理追蹤狀態」這件事情上,我們等等進一步談這部分。 新增啟用追蹤按鈕 昨天在 PinDetailSheet 裡面加上了 Apple Maps 與 Google Maps 的按鈕,現在我們要加上讓使用者控制啟用追蹤的按鈕: struct PinDetailSheet: View { // ... let isTracking: Bool // 判斷是否正在追蹤 let onToggleTracking: () -> Void // 點擊追蹤按鈕後執行的邏輯 // ... var body: some View { // ... VStack(spacing: 12) { HStack(spacing: 12) { // Apple Maps Button // Google Maps Button } Button(action: onToggleTracking) { HStack { Image(systemName: isTracking ? "bell.slash.fill" : "bell.fill") Text(isTracking ? "取消追蹤" : "啟用追蹤") } .frame(maxWidth: .infinity) .padding(.vertical, 4) } .buttonStyle(.borderedProminent) .tint(isTracking ? .red : .orange) .controlSize(.large) } } .padding(EdgeInsets(top: 12, leading: 16, bottom: 20, trailing: 16)) } ...

October 6, 2025 · 4 分鐘