分類彙整: Objective-C

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;
});

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

Objective-C 筆記》NSSelectorFromString

在 Objective-C 裡,可以使用 NSSelectorFromString() 函式把一個 NSString 轉換成 Selector,這樣就可以在執行期才決定要傳送的訊息:

SEL aSelector = NSSelectorFromString(@"redColor");  // 把一個字串轉換成 Selector

例如,有一個儲存顏色字串的 Array :

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // color string array
        self.items = @[@"Red", @"Green", @"Blue", @"Yellow", @"Orange"];
    }
    return self;
}

然後在一個 TableView 裡顯示這些顏色字串與顏色:

#pragma mark - UITableView Data Source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return _items.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }
    
    // 利用字串產生 Selector
    SEL colorMethod = NSSelectorFromString([NSString stringWithFormat:@"%@Color", [_items[indexPath.row] lowercaseString]]);

    if ([UIColor respondsToSelector:colorMethod]) {
        // 發送訊息
        cell.textLabel.textColor = [UIColor performSelector:colorMethod];
    }
    
    cell.textLabel.text = _items[indexPath.row];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;

    return cell;
}

執行結果會像這樣:

Objective-C 筆記》使用 GCD 的 dispatch_apply 函數實現類似 Ruby 的 map 方法

Ruby 語言的 Array 類別有一個 map 方法,可以把一個 Block 應用到 Array 裡的每個元素上,並將 Block 的傳回值收集成一個新的 Array,例如:

a = ['a', 'b', 'c']
a.map {|e| e.upcase}   # => ['A', 'B', 'C']

在 Objective-C 裡可以利用 GCD 的 dispatch_apply 函數來實現 map 方法:

#import <Foundation/Foundation.h>

@interface NSArray (RAMethods)
- (NSArray *)map:(id (^)(id item))block;
@end
#import "NSArray+RAMethods.h"

@implementation NSArray (RAMethods)

- (NSArray *)map:(id (^)(id item))block {
    NSMutableArray *results = [NSMutableArray array];
    for (int i = 0; i < self.count; ++i) {
        [results addObject:[NSNull null]];
    }
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_apply(self.count, queue, ^(size_t index) {
        [results replaceObjectAtIndex:index withObject:block([self objectAtIndex:index])];
    });
    
    return results;
}
@end

使用方式:

#import "NSArray+RAMethods.h"
...

NSArray *array = @[@"a", @"b", @"c", @"d", @"e"];
NSArray *tmpArray = [array map:^(id item) {
                        NSLog(@"%@", item);
                        return [item uppercaseString];
                    }];
NSLog(@"----------");
NSLog(@"tmpArray: %@", tmpArray);

輸出的結果:

a
c
d
e
b
----------
tmpArray: (
    A,
    B,
    C,
    D,
    E
)

dispatch_apply 函數有三個參數:第一個參數是迭代次數,第二個參數是要使用的 Dispatch Queue ,第三個參數是要執行的 Block。

可以注意到在輸出結果中,虛線上方的字母次序跟 array 裡的次序不同,這是因為 dispatch_apply 函數會把第三個參數的 Block 加到 Dispatch Queue 裡,各個 Block 的執行時間不定,所以虛線上方的字母次序才會跟 array 裡的不同。

iOS 筆記》UIColor

每次使用 UIColor 的 +colorWithRed:green:blue:alpha: 方法都要把 RGB 數值換算成 0 ~ 1.0 間的浮點數,覺得太麻煩了,可以使用下面這個方法,給一組 RGB 十進制數值的陣列:

#import <UIKit/UIKit.h>

@interface UIColor (RAMethods)
+ (UIColor *)colorWithRGB:(NSArray *)array alpha:(CGFloat)alpha;
@end
#import "UIColor+RAMethods.h"

@implementation UIColor (RAMethods)

+ (UIColor *)colorWithRGB:(NSArray *)array alpha:(CGFloat)alpha {
    if (array.count != 3) {
        return nil;
    }
    
    NSMutableArray *results = [NSMutableArray array];
    
    for (NSNumber *number in array) {
        float colorValue = ((float)[number intValue])/255;
        [results addObject:[NSNumber numberWithFloat:colorValue]];
    }

    return [self colorWithRed:[results[0] floatValue]
                        green:[results[1] floatValue]
                         blue:[results[2] floatValue]
                        alpha:alpha];
}
@end

使用方式:

#import "UIColor+RAMethods.h"

...

// R: 127, G: 127, B: 127 (灰色)
UIColor *color = [UIColor colorWithRGB:@[@127, @127, @127] alpha:1.0f];

// R:255, G: 0, B: 0 (紅色)
UIColor *color = [UIColor colorWithRGB:@[@255, @0, @0] alpha:1.0f];