分類彙整: iOS

好書推薦:《人工智慧開發實務 – 使用 Swift》

Practical Artificial Intelligence with Swift 繁體中文版

最近看了《人工智慧開發實務 – 使用 Swift》這本書,覺得這本書寫的真不錯。

不像其他有著艱澀數學與演算法的 Machine Learning 書籍,這本書是屬於實戰類型的書,只用了第十章稍微說明了那些可怕的 ML 演算法,其他章節則教你如何使用 Apple 系統內建的 framework 來做電腦視覺 (Vision)、自然語言處理 (Natural Language)、聲音分析 (Sound Analysis),語音識別 (Speech) 等功能,還有如何使用 CreateML 等工具來訓練自己的 ML 模型。

而且這本書提供了大量的範例程式碼,所以可以很容易的把那些功能運用在現有的 App 上。

這本書真的大推呀!👍 👍 👍

電子專案》使用 iPhone 的 Touch ID 搭配 Arduino Nano 33 解鎖自己的電腦

最近把以前一個用 B4i 寫的 iOS app 用 Swift 重寫,功能是:用 iPhone 的 Touch ID 解鎖自己的電腦。這個程式的原理很簡單,是用一塊 arduino 模擬成 HID Keyboard,然後 iOS app 透過 BLE 把密碼傳送給 arduino 裝置,接著 arduino 裝置再把密碼輸入到電腦,利用這種方式解鎖電腦。

HID Keyboard 的部分,以前是用 Blend Micro 來做,這次則改用 Arduino Nano 33 IoT 來做。

左邊紅色的電路板是 Blend Micro,中間深藍色電路板是 Arduino Nano 33 IoT。

以下是 Demo 影片與 Source code 連結:

詳細情況請看:https://github.com/kyumdbot/AutoPass

Kotlin’s Nullable vs. Swift’s Optional

最近在學 Kotlin 與重學那個學過但沒有學會過的 Swift,發現 Kotlin 的 nullable 與 Swift 的 Optional 非常像,大概有 87 分像吧 (不能再低了)? 所以我做了個對照表:

補充說明:

  1. 使用 ?.as? 運算子時,傳回值的型態一樣會是 nullable (Kotlin) / Optional (Swift),所以對傳回值的後續處理一樣要用 nullable (Kotlin) / Optional (Swift) 的方式對待之。 (心法:只要傳回值有可能出現空值 ( null / nil ),那個傳回值就會是一個 nullable / Optional 。)
  2. Swift Optional 的驚嘆號 ( ! ) 表示直接把 Optional 變數解包並當成有值去對待,所以當變數是空值 (nil) 的時候,對空值調用方法 (或者說:發送訊息) 時當然會當掉,因為空值無法響應那個方法。例如這行程式:str!.uppercased(),當 str 裡是一個 String 的時候,對它調用 .uppercased() 方法是沒問題的,可是當 str 裡是 nil 的時候,這時就會出錯了,因為 nil 沒有 .uppercased() 這個方法,所以會當掉。Kotlin 的 !! 符號是非空斷言 (Not-Null Assertion),告訴編譯器這個地方出現的值不會是 null,所以編譯器會把它當成非空型態去處理,但是,如果這個值不幸是 null 的話,就會拋出一個例外。
  3. Kotlin nullable 與 Swift Optional 不一樣的地方在於:Kotlin nullable 不是一個實際的 Type,例如這行 Kotlin 程式:var str : String? 表示 str 實際上的 Type 會有兩種情況:String 或 null,所以 str 變數的值有可能是一個字串,也有可能是一個空值。但是在 Swift 裡,這行程式:var str : String? 表示 str 的 Type 是一個 Optional,str 變數的值就是一個 Optional,只是這個 Optional 裡的內容可能是一個字串,也可能是一個 nil。也就是説:當一個變數的值可能出現空值時,Swift 用 Optional 去封裝那個變數;但在 Kotlin 裡,把變數宣告成 nullable 的型態時,只是在告訴編譯器,這個變數有可能出現空值 (所以這個變數的 Type 實際上可能會出現兩種情況),然後編譯器就會對這個變數做可空型態的檢查,確保程式碼裡有把出現空值的情況考慮進去。

iOS 筆記》用座標查詢地址

在寫地圖應用的 App 時,有時候需要用到利用座標(經緯度)去查詢那個點的地址,這時可以這麼寫:

#pragma mark - MKMapViewDelegate

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
    // 取得 MapView 中心點座標:
    CLLocationCoordinate2D center = [self.mapView centerCoordinate];
    NSLog(@"緯度:%f, 經度:%f", center.latitude, center.longitude);
    
    CLGeocoder *ceo = [[CLGeocoder alloc] init];
    CLLocation *loc = [[CLLocation alloc] initWithLatitude:center.latitude longitude:center.longitude];
    
    [ceo reverseGeocodeLocation:loc completionHandler:^(NSArray *placemarks, NSError *error) {
        CLPlacemark *placemark = [placemarks objectAtIndex:0];
        if (placemark) {
            // 這個 Dictionary 有地址的相關資訊:
            NSLog(@"addressDictionary %@", placemark.addressDictionary);
                      
            NSLog(@"region: %@",placemark.region);
            NSLog(@"country: %@",placemark.country); 
            NSLog(@"locality: %@",placemark.locality);
            NSLog(@"name: %@", placemark.name);
            NSLog(@"ocean: %@",placemark.ocean);
            NSLog(@"postalCode: %@",placemark.postalCode);
            NSLog(@"subLocality: %@",placemark.subLocality);                 
                      
            NSLog(@"完整地址: %@", placemark.addressDictionary[@"FormattedAddressLines"][0]);
            NSLog(@"City: %@", placemark.addressDictionary[@"City"]);
            NSLog(@"Name: %@", placemark.addressDictionary[@"Name"]);
        } else {
            NSLog(@"Could not locate");
        }
    }];
}

如果是想輸入地址,然後用地址查出座標呢?可以這麼寫:

CLGeocoder *geocoder = [[CLGeocoder alloc] init];
[geocoder geocodeAddressString:@"台灣臺北市信義區市府路1號" completionHandler:^(NSArray *placemarks, NSError *error) {
    if (error) {
        NSLog(@"%@", error);
    } else {
        CLPlacemark *placemark = [placemarks lastObject];
        NSLog(@"緯度:%f, 經度:%f", placemark.location.coordinate.latitude, placemark.location.coordinate.longitude);
    }
}];

參考來源:

  1. http://stackoverflow.com/questions/12599316/i-want-to-get-the-location-name-from-the-coordinate-value-in-mapkit-for-iphone
  2. http://stackoverflow.com/questions/18767351/find-latitude-and-longitude-using-clgeocoder

iOS 筆記》UIWebView 調用 JavaScript 函數時,呼叫 Objective-C 方法

有時候開發 iOS App 時,因為某些考量,會讓整個頁面使用 HTML 頁面,而不是使用原生的 iOS 畫面,但是此 HTML 頁面又必須與原生的 code 互動。例如:我們現在有一個會員註冊頁面使用 HTML 頁面,當註冊成功時,必須通知 App 跳轉頁面,這時我們可以讓 HTML 頁面在註冊成功時,呼叫一個叫做 RegisterOK() 的 JavaScript 函數,當這個函數被調用時,我們呼叫一個 Objective-C 函數或是方法,然後開啟其他的 iOS 頁面,這時可以這麼做:

先包含 JavaScriptCore.h 標頭檔:

#import <JavaScriptCore/JavaScriptCore.h>

使用 JavaScriptCore 跟 UIWebView 互動:

#pragma mark - UIWebView Delegate

//...

- (void) webViewDidFinishLoad:(UIWebView *)webView {
    // 透過 KVC 取得 JavaScriptContext
    JSContext *context =  [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    context[@"RegisterOK"]= ^() {
        NSLog(@"Call RegisterOK()");
        // 執行一些動作,例如跳轉頁面:
        //...
    };
}

參考來源:http://stackoverflow.com/questions/28827317/how-to-listen-to-in-objective-c-uiwebview-when-the-javascript-function-is-trigge

Objective-C 筆記》貓王運算符

有一種常見的三元運算符用法如下:

NSString *aString = nil;
NSString *outString;

outString = aString ? aString : @"";   //=> outString: ""

aString = @"Hello";
outString = aString ? aString : @"";  //=> outString: "Hello"

對於這種三元運算符用法,GCC C Extension 增加了可省略中間運算式的 ?: 運算符來縮短這種寫法,用法如下:

NSString *aString = nil;
NSString *outString;

outString = aString ?: @"";  //=> outString: ""

aString = @"Hello";
outString = aString ?: @"";  //=> outString: "Hello"

我在《 Java 程序員修煉之道》這本書裡看到這種運算符稱作貓王運算符(Elvis operator)

因為這個符號看起來明顯很像貓王鼎盛時期梳的大背頭

重點來了,我寫這篇筆記只是想說:貓王運算符這個名字真是太可愛了! >////<

Objective-C 筆記》GCC 的 C 擴充功能: Code Block Evaluation

在 NSHipster 看到 Clang 有支援 GCC 的擴充功能:Code Block Evaluation,看到後覺得真是驚為天人(又在亂用成語了XD),這個功能真是太實用了,立馬就在正在寫的專案裡用了這種寫法 😀

這個功能是用圓括弧()包起一個代碼區塊{},然後這個代碼區塊最後一個表達式的值就是返回的值,寫法就像這樣:

self.nameLabel = ({
    UILabel *label = [UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 44)];
    label.text = @"Riddle";
    label.textColor = [UIColor greenColor];
    label;
});

self.blogLabel = ({
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 50, 300, 40)];
    label.text = @"http://riddleapple.logdown.com";
    label.textColor = [UIColor redColor];
    label;
});

因為變數只能存活在宣告那個變數的大括號內,因此可以利用這個方式,重複使用一些通用的變數名稱而不會產生衝突!