標籤彙整: objective-c

註冊推播在 iOS 8 上失效

在 iOS 8 上編譯會出現以下 log :

registerForRemoteNotificationTypes: is not supported in iOS 8.0 and later.

 

在 iOS 8 必須用 registerForRemoteNotifications 和 registerUserNotificationSettings 取代
以下程式碼同時支援 iOS 8 與 iOS 7

if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
        
    [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings
                                                                             settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge)
                                                                             categories:nil]];
    [[UIApplication sharedApplication] registerForRemoteNotifications];
        
}else {
        
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
}

 

如果要檢查推播有沒有打開,這部份也要修改。

UIRemoteNotificationType types;
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {

    types = [[UIApplication sharedApplication] currentUserNotificationSettings].types;

}else {

    types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
}

if (types == UIRemoteNotificationTypeNone) {
    // 推播未打開
}

shows user location 在 iOS 8 上失效

在 iOS 7 以及更早之前的版本,MapView 顯示使用者位置不需實作到 CLLocationManager ,現在都要了。

在 iOS 8 上編譯會出現以下 log :

Trying to start MapKit location updates without prompting for location authorization. Must call -[CLLocationManager requestWhenInUseAuthorization] or -[CLLocationManager requestAlwaysAuthorization] first.

 

首先必須要修改 info.plist
新增 key值為 NSLocationWhenInUseUsageDescription 或 NSLocationAlwaysUsageDescription
Value值為要出現在螢幕上的字,終於可以客製化訊息了!但也可以留白。
兩者差異僅在前者只有使用中才會定位,後者是在背景也會持續定位。

 

接著修改程式碼,第5行是重點

locationManager = [[CLLocationManager alloc] init];
        locationManager.delegate = self;
        locationManager.desiredAccuracy=kCLLocationAccuracyBest;
        if([locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
            [locationManager requestWhenInUseAuthorization];
        }
        [locationManager startUpdatingLocation];

 

另外授權檢查部分也要修改,請注意4,5行的差異

- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {

    if (
        ([locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)] && status != kCLAuthorizationStatusNotDetermined && status != kCLAuthorizationStatusAuthorizedWhenInUse) ||
        (![locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)] && status != kCLAuthorizationStatusNotDetermined && status != kCLAuthorizationStatusAuthorized)
        ) {

        NSString *message = @"您的手機目前並未開啟定位服務,如欲開啟定位服務,請至設定->隱私->定位服務,開啟本程式的定位服務功能";
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"無法定位" message:message delegate:nil cancelButtonTitle:@"確定" otherButtonTitles: nil];
        [alertView show];

    }else {

        [locationManager startUpdatingLocation];
    }
}

上面那行是 iOS 8 以上,第二行是 iOS 7 以下,因為 kCLAuthorizationStatusAuthorized 在 iOS 8 完全不能使用。

 

別的地方要這樣寫

CLAuthorizationStatus status = [CLLocationManager authorizationStatus];
    
    if (status == kCLAuthorizationStatusAuthorizedWhenInUse || status == kCLAuthorizationStatusAuthorized) {

    // 開始定位

}else {

    // 顯示警告
}

多個 URL 跳轉判斷

使用社群 SDK 時會跳到另一個 App 要求授權再回來,這時需要判斷回來後要做什麼事。

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url
  sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    
    if ([url.scheme isEqualToString:@"fbxxxxxxxxxx"]) {
        return [FBAppCall handleOpenURL:url sourceApplication:sourceApplication];
        
    }else if ([url.scheme isEqualToString:@"wbxxxxxxxxxx"]) {
        return [WeiboSDK handleOpenURL:url delegate:self];
        
    }else if ([url.scheme isEqualToString:@"wxxxxxxxxxxx"]) {
        return [WXApi handleOpenURL:url delegate:self];
        
    }else if ([url.host isEqualToString:@"home"]) {
        // do something
        return YES;
    }

    return NO;
}

以上同時有 Facebook 微博 微信 以及回到此 App 的第一個畫面。

The app references non-public selectors in Payload ... id

此文章已經過期 SDK v4.0.1 之後變不再有此問題

這是一個在編譯時都沒甚麼徵兆,在上傳到 iTunes Connect 檢查才會冒出的警告。
雖然只是警告而非錯誤,怕審核不通過,所以還是處理一下。(好像也有人選擇註記在審核的欄位裡)

The app references non-public selectors in Payload/{Appname}.app/{App name]}: id

花了許多時間才發現是 Facebook SDK 惹的禍,原因是裡面的 property 名稱使用了保留字 id 造成的。
這問題好像存在很久了,只是 Facebook 一直沒去修正。
例如 FBGraphUser.h 這檔案打開來看...


@protocol FBGraphUser<FBGraphObject>
...
@property (retain, nonatomic) NSString *id;
...
@end

 

這樣的檔案不只一個,最簡單的修改方法就是用 [user objectForKey:@"id"] 或 user[@"id"] 來取代原來的 user.id。


/*
 * Callback for session changes.
 */
- (void)sessionStateChanged:(FBSession *)session
                      state:(FBSessionState) state
                      error:(NSError *)error
{
    switch (state) {
        case FBSessionStateOpen:
            if (!error) {
                // We have a valid session
                //NSLog(@"User session found");
                [FBRequestConnection
                 startForMeWithCompletionHandler:^(FBRequestConnection *connection,
                                                   NSDictionary<FBGraphUser> *user,
                                                   NSError *error) {
                     if (!error) {
                         self.loggedInUserID = [user objectForKey:@"id"];
                         self.loggedInSession = FBSession.activeSession;
                     }
                 }];
            }
            break;
        case FBSessionStateClosed:
        case FBSessionStateClosedLoginFailed:
            [FBSession.activeSession closeAndClearTokenInformation];
            break;
        default:
            break;
    }

    [[NSNotificationCenter defaultCenter]
     postNotificationName:FBSessionStateChangedNotification
     object:session];

    if (error) {
        UIAlertView *alertView = [[UIAlertView alloc]
                                  initWithTitle:@"Error"
                                  message:error.localizedDescription
                                  delegate:nil
                                  cancelButtonTitle:@"OK"
                                  otherButtonTitles:nil];
        [alertView show];
    }
}



這類錯誤警告只要用最後的關鍵字搜尋整個專案大概可以找到要改的地方,只不過以 id 這種前後都可以組很多單字就只能用猜的...

nsurl 網址拆解

    NSURL *url = [NSURL URLWithString:@"http://www.test.com/member/index.php?name=cat&age=6"];
    NSLog(@"%@", url.scheme);  // http
    NSLog(@"%@", url.host);  // www.test.com
    NSLog(@"%@", url.path);  // /member/index.php
    if ([url.pathComponents count] >= 2) {
        NSLog(@"%@", [url.pathComponents objectAtIndex:1]);  // member
    }
    NSLog(@"%@", url.lastPathComponent);  // index.php

子網域以及傳入的參數要自行處理。

Line API 傳送文字及圖片 for iOS

Line API 我不想看韓文及日文,終於讓我找到英文版的。
我測試之後發現它無法同時傳送文字及圖片,只能一次傳一種格式。

 

傳送文字時,若是網址會變成超連結。

    NSString *plainString = @"Hello, World! 中文測試~";

    NSString *contentKey = (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                                        (CFStringRef)plainString,
                                                                                        NULL,
                                                                                        (CFStringRef)@"!*'();:@&=+$,/?%#[]",
                                                                                        kCFStringEncodingUTF8);
    NSString *contentType = @"text";

    NSString *urlString = [NSString stringWithFormat:@"line://msg/%@/%@",
         contentType, contentKey];

    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]];

 

傳送圖片的圖片來源可以是網路、專案或是手機內的,只要是 UIImage 都行。

    UIPasteboard *pasteboard = [UIPasteboard pasteboardWithUniqueName];
    NSString *pasteboardName = pasteboard.name;
    NSURL *imageURL = [NSURL URLWithString:@"https://www.google.com.tw/images/srpr/logo4w.png"];
    [pasteboard setData:UIImagePNGRepresentation([UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]]) forPasteboardType:@"public.png"];

    NSString *contentType = @"image";
    NSString *contentKey = (__bridge_transfer NSString*)CFURLCreateStringByAddingPercentEscapes(NULL,
                                                                                             (CFStringRef)pasteboardName,
                                                                                             NULL,
                                                                                             CFSTR(":/?=,!$&'()*+;[]@#"),
                                                                                             CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding));

    NSString *urlString = [NSString stringWithFormat:@"line://msg/%@/%@",
                           contentType, contentKey];
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlString]];

 

加上檢查有沒有安裝 Line 再執行以上動作會更好。

    if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"line://"]]) {
        // do something
    }

nsarray 轉 nsdictionary

將包有 dictionary 的 nsarray 轉成 dictionary , key 為高亮的那一行。

- (NSMutableDictionary *)indexKeyedDictionaryFromArray:(NSArray *)array
{
    id objectInstance;
    
    NSMutableDictionary *mutableDictionary = [[NSMutableDictionary alloc] init];
    for (objectInstance in array) {
        
        [mutableDictionary setObject:objectInstance forKey:[NSString stringWithFormat:@"%@", [objectInstance objectForKey:@"name"]]];

    }
    return (NSMutableDictionary *)mutableDictionary;
}

使用範例

    NSArray *array = [NSArray arrayWithObjects:
                      [NSDictionary dictionaryWithObjectsAndKeys:@"mark", @"name", @"100", @"score", nil],
                      [NSDictionary dictionaryWithObjectsAndKeys:@"tom", @"name", @"78", @"score", nil],
                      [NSDictionary dictionaryWithObjectsAndKeys:@"mary", @"name", @"81", @"score", nil],
                      [NSDictionary dictionaryWithObjectsAndKeys:@"sandy", @"name", @"65", @"score", nil],
                      [NSDictionary dictionaryWithObjectsAndKeys:@"sam", @"name", @"98", @"score", nil],
                      nil];
    
    NSDictionary *dictionary = [self indexKeyedDictionaryFromArray:array];

依照 nsdictionary 中的值來排序

這也是新手滿常見的問題, nsdictionary 裡有 name 跟 score 的值,產生一個由低分到高分的 keys 陣列。
這個方法可以自訂一些條件,nsmutabledictionary也同樣適用喔。

NSMutableDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
                          
                          [NSDictionary dictionaryWithObjectsAndKeys:@"mark", @"name", @"100", @"score", nil], @"mark",
                          [NSDictionary dictionaryWithObjectsAndKeys:@"tom", @"name", @"78", @"score", nil], @"tom",
                          [NSDictionary dictionaryWithObjectsAndKeys:@"mary", @"name", @"81", @"score", nil], @"mary",
                          [NSDictionary dictionaryWithObjectsAndKeys:@"sandy", @"name", @"65", @"score", nil], @"sandy",
                          [NSDictionary dictionaryWithObjectsAndKeys:@"sam", @"name", @"98", @"score", nil], @"sam",
                          
                          nil];

NSArray *blockSortedKeys = [dict keysSortedByValueUsingComparator: ^(id obj1, id obj2) {
        
        if ([[obj1 objectForKey:@"score"] intValue] > [[obj2 objectForKey:@"score"] intValue]) {
            
            return (NSComparisonResult)NSOrderedDescending;
        }
        
        if ([[obj1 objectForKey:@"score"] intValue] < [[obj2 objectForKey:@"score"] intValue]) {
            
            return (NSComparisonResult)NSOrderedAscending;
        }
        
        return (NSComparisonResult)NSOrderedSame;

    }];