如何整合 Sign in with Apple 到自己的 iOS App 上 (iOS & Backend)

根據蘋果的規定,明年四月之後只要你的 App 中有包含任一第三方登入就必須支援 Sign in with Apple,要不然就無法通過審核上架。 因此稍微花了點時間去研究該如何整合 Sign in with Apple 到 iOS App 裡面,同時為了跟 Backend 說明這個流程也就一併理解 Backend 這邊該怎麼處理,在這邊就分成 App 端與 Server 端來分別說明。 這邊講到的只有要讓 iOS App 支援 Sign in with Apple 部分,如果你的服務是跨平臺,希望讓 Web、Android 也能支援的話,需要在 Apple Developer 上再新建一個 Services ID,然後利用官方提供的 JS Library 或自己串接官方的 API Endpoints iOS App 端 App 端串接 Sign in with Apple 相當簡單,大致分成五個步驟 Apple Developer 後臺啓用 Sign in with Apple 請先前往 https://developer.apple.com/account/resources/identifiers/list 然後點開自己的 App ID 找到 Sign in with Apple 並啓用它 ...

December 10, 2019 · 兔子

利用 Swift Playground 撰寫精美又可互動的技術文件

今天利用時間把一直沒時間只好放在待學期清單裡的 Playground 好好的摸了一遍,剛好可以致敬明天要舉辦的 iPlayground (硬要扯) 當 Swift 公開時,同時也開放了一個很強大的工具,那就是 Swift Playground 。利用 Playground 可以即時的看到自己寫的一些程式執行的結果是否正確,而不用不斷的重新 Build & Run 。而隨著 Swift 版本已經來到了 4.2 的今日,Playground 的功能也越來越強大,不但可以即時顯示結果,甚至可以直接在上面做 UI 、寫文件。 主要目標 今天一開始先設定好我想要理解的幾個目標: 建立 Playground 在 Playground 中設計 UI 整合第三方 Framework 在實際維護的專案中加入 Playground 利用 Playground 撰寫文件 建立 Playground 首先是建立 Playground ,跟一般的檔案一樣,從 Xcode 裡面選擇 File -> New -> Playground 就可以建立一個基本的 Playground 此時可以選擇樣板,基本上就是看你要拿來試做什麼,預先幫你準備好基本需要用到的原始碼。 選擇了 Blank 就會建立一個最基本的 Playground 此時按左邊行數列上面的執行按鈕就可以讓 Playground 執行到那一行,也可以選擇 Editor -> Run Playground 或按 ⌘⇧↩︎ 執行全部,運算出的值會顯示在右邊側邊列。 在 Playground 中設計 UI 在建立 Playground 選擇樣板時選擇 View 的話就會建立一個有個基本 UIView 的 Playground ,也可以在既有的 Playground 中先 import PlaygroundSupport import UIKit ...

October 19, 2018 · 兔子

利用Generic Type跟Extension來優化Storyboard取得UIViewController的動作

每次在處理從Storyboard中取出ViewController的動作時,大多都會用到類似下面這樣的程式碼: let viewController = storyboard.instantiateViewController(withIdentifier: &#34;SomeViewController&#34;) as! SomeViewController 而今天我在做第N次這樣的動作時心想,不知道有沒有辦法讓這一段變得更加的直覺。 簡單地分析了一下,主要我想解決的問題是: 使用字串作爲Identifier容易因爲錯字而導致錯誤 必須要強制轉型成正確的Type 在確定了想改善的方向之後我就開始規劃。首先我先建立一個Struct去存放原本需要的Identifier字串,然後利用Generic Type來將我們想要轉換的Type給存放起來,所以就產生像下面這樣的Struct: `struct StoryboardIdentifier<T: UIViewController> { var identifier: String var type: T.Type init(identifier: String, type: T.Type) { self.identifier = identifier self.type = type } }` 建立好架構後,就用Extension將前面舉例的SomeViewController的Type跟Identifier定義爲一個static variable: extension StoryboardIdentifier { static var some: StoryboardIdentifier&lt;SomeViewController&gt; { return StoryboardIdentifier&lt;SomeViewController&gt;(identifier: &#34;SomeViewController&#34;, type: SomeViewController.self) } } 這樣,就可以直接用 StorybosardIdentifier.some 這樣的方式去取得那個我們定義好的Identifier。 但因爲原本的UIStoryboard並不認識StoryboardIdentifier這個Struct,所以我利用Extension去給它增加一個Method,將帶入的StoryboardIdentifier給轉換成對應的UIViewController: extension UIStoryboard { func instantiateViewController&lt;T&gt;(withIdentifier identifier: StoryboardIdentifier&lt;T&gt;) -&gt; T { return self.instantiateViewController(withIdentifier: identifier.identifier) as! T } } ...

January 22, 2018 · 兔子

使用類似Notification.Name的方法處理UserDefaults與其他當作Key的字串

在Swift 3之前,我們需要使用字串去作為發送或監聽NotificationCenter的事件名稱,而從Swift 3之後,我們可以使用Notification.Name來作為NotificationCenter的名稱。 使用這方法我們就可以事先定義好,不用擔心在不同的地方打錯字串而造成事件無法收到的問題,只需要先像這樣宣告好Name: extension NSNotification.Name { static var userLogin = NSNotification.Name(rawValue: &#34;UserLogin&#34;) } 之後就可以用這個Name去處理NotificationCenter的事件 // 發送 NotificationCenter.default.post(name: .userLogin, object: user)``// 監聽 NotificationCenter.default.addObserver(forName: .userLogin, object: nil, queue: nil) {(notification) in // Handle } 這方法的好處是,在name的位置我們只需要打 . Xcode就會跳出所有定義好的Name,從而避免字串打錯字而發生行為錯誤的問題。 那這引起我的另一個想法,我們是否可以用類似的方法去處理UserDefaults或Keychain等等的Keys呢? 嘗試之後,發現可以用一個extension就做到類似的行為,以下以UserDefaults為例 首先貼上以下這段 extension UserDefaults { enum Key { case default(key: String) init(key: String) { self = .default(key: key) } } } 這裡我們使用extension去建立一個enum在UserDefaults下面,並放一個default的case去儲存字串 然後用extension給UserDefaults新增一些使用UserDefaults.Key而非String的方法: `extension UserDefaults { func set(_ value: Any?, forKey key: UserDefaults.Key) { if case UserDefaults.Key.default(let k) = key { self.set(value, forKey: k) } } ...

September 20, 2017 · 兔子

用Alamofire做HTTP Basic Authentication

這週末有個機會要做個小Sample給別人,這個Sample Project需要用到HTTP API Request且這個Request需要做HTTP Basic Authentication。 想說有這個機會,也不是正式產品,因此就試著完全用Swift來寫,那HTTP Request當然就交給AFNetworking的作者 Mattt Thompson 所製作的另一個套件 Alamofire Alamofire是完全基於Swift所撰寫的HTTP Request套件,整個語法都有著濃濃的Functional Programming特性在其中。 以下範例是直接將Alamofire.swift Copy到Bundle中的作法,如果是用CocoaPods或其他方法安裝的話,請先 import Alamofire ,然後在每個Method call前加上 Alamofire。如 request() 要改成 Alamofire.request() 要發送一個GET方法的要求出去只要這樣: request(.GET, “http://this.is.url/path”) 而要處理回傳的內容則只要接著: request(.GET, “http://this.is.url/path”).response {(req, res, data, error) in // Handle in here``} 相當簡單易懂,但對寫Objective-C久了的人來說一定很不習慣。 不過這次的要求不單只是要送一個Request出去而已,還需要做HTTP Basic Authentication,查了一下官方Repo上的說明,要做Authentication只需要: request(.GET, “http://this.is.url/path”).authenticate(user: &#34;username&#34;, password: &#34;password&#34;).response {(req, res, data, error) in // Handle in here``} 即可,我很興奮地照著實作,然後就一直接到401 Unauthorized的錯誤… 把發送出去的Request攔下來看,結果發現雖然有帶Authentication,但是Header中卻沒有Authentication的欄位。HTTP Basic Authentication除了要求User跟Password之外,還要求要帶一個值 “Authorization” = “Basic MTIzNDo1Njc4" 後面那串亂碼是由字串 “Username:Password” 經由Base64轉換出來的字串,但是Alamofire並沒有實作這個,因此要正常運作,我們必須自行給它加上這個Header。 ...

March 8, 2015 · 兔子

Swift 的 String 操作

在Swift中有自己獨立的字串格式 String,操作的方法與NSString類似,將各種的操作方式整理在這邊方便大家查找。 大概是為了跟Objective-C能夠相容,部分String methods依舊是跟NSString相同,但又多了些新的用法,所以整個用法很不統一,因此在寫這篇文章的同時,順手寫了一個Extension將這些方法整理在一起,可以用比較統一的方法去做字串操作。 如果有人有興趣,可以參考: https://github.com/tuzaiz/RacoonString 取得字串長度 countElements(string) // Swift 1.0``count(string) // Swift 1.2 RacoonString string.length() // RacoonString 尋找字串 var string = “Swift is awesome”``let range = string.rangeOfString(“awesome”)``if let range = range {`` // 尋找字串開始位置`` println(“Start with: \(range.startIndex)”)`` println(“End with: \(range.endIndex)”)`` // 取出要查詢的字串`` println(string[range])``} RacoonString string.match(&#34;awesome&#34;) if let regex = &#34;awe(some)&#34; { string.match(regex)``} 抓出特定範圍的字串 let r = 3..&lt;5``let startIndex = advance(string.startIndex, r.startIndex)``let endIndex = advance(startIndex, r.endIndex — r.startIndex)``string[Range(start: startIndex, end: endIndex)] ...

February 12, 2015 · 兔子

Swift下將NSData轉換成String

Swift中跟Foundation之間的恩怨情仇實在剪不斷理還亂。 在Foundation中大部份I/O時都會取得NSData格式的資料,而以前在Objective-C時取得NSData要轉成NSString需要經過 NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8Encoding]; 將NSData轉換成NSString,但是在Swift下對於字串有自己專用的Type String 我原本以為應該會有 String.stringWithData(data, encoding=.UTF8) 或是 String(data, encoding=.UTF8) 之類的方法可以用,結果找了半天都找不到。 查了半天,結果終於找到解法: var string : String = NSString(data: data, encoding: NSUTF8StringEncoding) 原來因為在Swift下的String基本上背後還是NSString,所以可以直接轉換來使用。 Swift是個好語言,不過為了與既有的Foundation架構還有Objective-C相容實在要花太多的力氣,這是目前學習Swift的一個很大的障礙,接下來就看蘋果會怎麼去發展這個潛力無窮的新語言囉。

October 19, 2014 · 兔子

用Swift寫Core Data會遇到的Type錯誤

這幾天用Swift撰寫Core Data的應用時總是會出現一些詭異的錯誤,像是下面這段Code: var fetchRequest = NSFetchRequest(entityName: “Person”) var people = appDelegate.managedObjectContext?.executeFetchRequest(fetchRequest, error: nil) var person = people?.first as Person println(people) println(person) 同樣類似的用法,我曾遇過下面兩種奇特的狀況 一種是第一個println有輸出Array與值,但person卻是nil。 而另一種則是直接就顯示下面的Warning接著Crash CoreData: warning: Unable to load class named ‘Person’ for entity ‘Person’. Class not found, using default NSManagedObject instead. 查了許多地方後終於找出了問題的所在,原來在Swift中關於Type的形式應該是 ProjectName.TypeName 但Xcode在 .xcdatamodeld 新增Entity時,並不會將Entity指向正確的Type,格式只有 **TypeName,**導致Runtime時找不到該Type,所以不然是因為 as 的轉型態不正確而變成 nil,要不然就是直接Crash。 我想這是諸多Swift為了相容於舊專案而衍生出來的問題之一,不過解法也相當簡單,只要到 .xcdatamodeld 檔案中的Configurations將每一個Entity的Class指向正確的地方就可以了。 現在總算是可以正確的抓出我要的資料了。

October 18, 2014 · 兔子