カテゴリー別アーカイブ: Xcode

【Xcode】iOS10で各種ユーザーデータへのアクセス許可を参照

【OSX 10.11.6+Xcode 8.0 (8A218a)+iPhoneSE(iOS 10.0.2)】

前回の記事でアプリの初回起動時以外に、各種ユーザーデータへのアクセス許可をどのように参照すればいいのかを前回対象とした「写真」「マイク」「音声認識」「メディアライブラリ」についてまとめました。

※以下、コードは Objective-C です。

写真


従来の写真アクセスである「ALAssetsLibrary」でコードを書いていると・・・

'ALAssetsLibrary' is deprecated: first deprecated in iOS 9.0 - Use PHPhotoLibrary from the Photos framework instead

というワーニングが出ます。

従来の「ALAssetsLibrary」はiOS9以降非推奨になり、Photos frameworkの「PHPhotoLibrary」を使いなさいということなので、「PHPhotoLibrary」ベースでのチェック方法です(もちろん写真のアクセス自体も「PHPhotoLibrary」ベースである前提です)。

//写真設定:参照(アプリの設定で許可されているかどうか)
-(BOOL)isPermitPhotoLibrary
{
    PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
    switch (status) {
        case PHAuthorizationStatusNotDetermined:
            // このアプリに与える権限が未選択(アプリ初回起動時)
            return NO;
            break;
            
        case PHAuthorizationStatusRestricted:
            // 設定による使用制限で、アプリのアクセスの許可を変更できない
            return NO;
            break;
            
        case PHAuthorizationStatusDenied:
            // このアプリに与える権限を拒否(アプリ初回起動時「許可しない」を選択)
            return NO;
            break;
            
        default:
        case PHAuthorizationStatusAuthorized:
            // このアプリに与える権限を許可(アプリ初回起動時「OK」を選択)
            return YES;
            break;
    }
}

マイク


いくつか方法があるようですが、ここでは「AVAudioSessionRecordPermission」を使ってます。

//---マイク使用許可チェック
-(BOOL)isPermitMic{
    AVAudioSessionRecordPermission status = [AVAudioSession sharedInstance].recordPermission;
    
    switch (status) {
        case AVAudioSessionRecordPermissionUndetermined:
            // このアプリに与える権限が未選択(アプリ初回起動時)
            NSLog(@"---AVAudioSession:未選択");
            return NO;
            break;
            
        case AVAudioSessionRecordPermissionDenied:
            // このアプリに与える権限を拒否(アプリ初回起動時「許可しない」を選択)
            NSLog(@"---AVAudioSession:拒否");
            return NO;
            break;
            
        default:
        case AVAudioSessionRecordPermissionGranted:
            // このアプリに与える権限を許可(アプリ初回起動時「OK」を選択)
            NSLog(@"---AVAudioSession:許可");
            return YES;
            break;
    }
}

【参考】
マイクのアクセス許可 – iOS9とXcode7
authorizationStatusForMediaType: – AVCaptureDevice | Apple Developer Documentation
AVAudioSessionRecordPermission – AVFoundation | Apple Developer Documentation
Swift: アルバム・カメラ・マイク・プッシュ通知のアクセス許可判定一覧 | siro:chro

メディアライブラリ


MPMediaLibraryの「authorizationStatus」を使います。Appleのリファレンスにもあんまり詳しいことは書いてないです。何でだ?

//メディアライブラリ設定:参照(アプリの設定で許可されているかどうか)
-(BOOL)isPermitMediaLibrary
{
    MPMediaLibraryAuthorizationStatus status = [MPMediaLibrary authorizationStatus];
    switch (status) {
        case MPMediaLibraryAuthorizationStatusNotDetermined:
            // このアプリに与える権限が未選択(アプリ初回起動時)
            NSLog(@"---MPMediaLibrary:未選択");
            return NO;
            break;
            
        case MPMediaLibraryAuthorizationStatusRestricted:
            // 設定による使用制限で、アプリのアクセスの許可を変更できない
            NSLog(@"---MPMediaLibrary:許可を変更できない");
            return NO;
            break;
            
        case MPMediaLibraryAuthorizationStatusDenied:
            // このアプリに与える権限を拒否(アプリ初回起動時「許可しない」を選択)
             NSLog(@"---MPMediaLibrary:拒否");
            return NO;
            break;
            
        default:
        case MPMediaLibraryAuthorizationStatusAuthorized:
            // このアプリに与える権限を許可(アプリ初回起動時「OK」を選択)
            NSLog(@"---MPMediaLibrary:許可");
            return YES;
            break;
    }
}

【参考】
authorizationStatus – MPMediaLibrary | Apple Developer Documentation
MPMediaLibraryAuthorizationStatus – MediaPlayer | Apple Developer Documentation

音声認識(Speech.Framework)


SFSpeechRecognizerの「authorizationStatus」を使います。

    [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
        dispatch_async(dispatch_get_main_queue(), ^{
            switch (status) {
                case SFSpeechRecognizerAuthorizationStatusAuthorized: {
                    NSLog(@"--許可あり");
                    break;
                }
                case SFSpeechRecognizerAuthorizationStatusDenied: {
                    NSLog(@"--拒否");
                    break;
                }
                case SFSpeechRecognizerAuthorizationStatusRestricted: {
                    NSLog(@"--設定により使えない");
                    break;
                }
                case SFSpeechRecognizerAuthorizationStatusNotDetermined: {
                    NSLog(@"--未認証");
                    break;
                }
            }
        });
        
    }];

【参考】
SFSpeechRecognizer – Speech | Apple Developer Documentation
authorizationStatus – SFSpeechRecognizer | Apple Developer Documentation
SFSpeechRecognizerAuthorizationStatus – Speech | Apple Developer Documentation

おまけ:自分のアプリの設定を呼び出す


各種ユーザーデータへのアクセス許可を参照して、許可がないものは改めて許可してもらいましょう。ということで、設定アプリの中にある自分の設定を呼び出す方法です。

いわゆるURLスキームというやつらしいですが、iOS10で使い方(openURLのメソッド)が変わっているようです。また下記の参考リンクに「URLスキームの内容をinfo.plistに記載する」とありますが、単純に自分のアプリ設定を呼び出すだけなら不要なようです(実機で動作確認済)。

//このアプリの設定を呼び出し
- (void)prefsButtonPushed {
    NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
    if ([[UIApplication sharedApplication] canOpenURL:url]) {
        [[UIApplication sharedApplication] openURL:url
                                           options:@{}
                                 completionHandler:nil];
        //以下は、iOS9までの書き方
        //[[UIApplication sharedApplication] openURL:url];
    }
}

これを実行すると、自分のアプリの設定が表示されます。

自分のアプリの設定が表示されます。
自分のアプリの設定が表示されます。

【参考】
application:openURL:options: – UIApplicationDelegate | Apple Developer Documentation
UIApplicationOpenSettingsURLString – UIKit | Apple Developer Documentation
[iOS 10] UIApplication の openURL: が Deprecated になりました | Developers.IO
iOS9でカスタムURLスキームの遷移に失敗するときの注意点 – Qiita
【iOS9】特定のURLスキームのみを呼び出し可能にする→.canOpenURL(url)について | MUSHIKAGO APPS MEMO
投稿の編集 ‹ AS blind side — WordPress
標準アプリのURLスキームについて – もう一人のY君

最後に


今回出てきた「Photo.Framework」「Speech.Framwork」については、別途まとめたいですが、とりあえず気になる人は下記を参考に。

・Photo.Framework
Photos vs Assets Library – いまさら始めるPhotos.framework
[iOS 8] PhotoKit 1 – Photos frameworkの概要 | Developers.IO
Photos Framework を使って写真.appと画像をやりとりする – Qiita
Photos frameworkを使ってiPhoneアルバム内の写真を取得・削除する+α – Think Big Act Local
Photos.framework でカメラロールを取得する – ObjecTips
投稿の編集 ‹ AS blind side — WordPress

・Speech.Framwork
[iOS 10] SFSpeechRecognizerで音声認識を試してみた | Developers.IO
[iOS 10] SFSpeechRecognizerの音声認識処理の仕組みを見てみる | Developers.IO
【iOS 10】Speechフレームワークで音声認識 – 対応言語リスト付き – Over&Out その後
新規投稿を追加 ‹ AS blind side — WordPress
【iOS】Speech Frameworkの実装 – Qiita
【iOS 10】Speech Frameworkで音声認識 – あたも開発ブログ
【iOS10】Speech Recognition API(音声認識API)の制約まとめ – Qiita

【Xcode】iOS10以降は各種ユーザーデータへアクセスに許可が必要

【OSX 10.11.6+Xcode 8.0 (8A218a)+iPhoneSE(iOS 10.0.2)】

iPhoneの音楽ライブラリからアートワークを使おうと思って、前に書いた古いコードから必要な部分を移植して動かしてみたらエラーが出た。

[access] This app has crashed because it attempted to access privacy-sensitive data without a usage description.  The app's Info.plist must contain an NSAppleMusicUsageDescription key with a string value explaining to the user how the app uses this data.

iOS10以降は、ユーザーデータにアクセするには、Info.plistに該当アクセスに対応したキーと使用目的を示すテキストを記述する必要があるようです。詳しくは下記リンクを。

[iOS 10] 各種ユーザーデータへアクセスする目的を記述することが必須になるようです | Developers.IO

具体的には、TARGETSからinfoを選んで、下記のように追加するだけ。

TARGETS > info で必要なアクセス先に対する許可を追加します。
TARGETS > info で必要なアクセス先に対する許可を追加します。ここではメディアライブラリ、マイク、音声解析、写真ライブラリの4つを指定。

Keyの選択肢は、全て「Privacy – 」で始まっているので、そこから必要なものを追加すると、アプリの初回起動時にそれぞれ許可を求めるダイアログが表示されます(keyに指定したString部分が、ダイアログの説明分として表示されます)。

info.plistで設定したアクセスに対して、許可を求めるダイアログが表示される。
info.plistで設定したアクセスに対して、許可を求めるダイアログが表示される。

ダイアログでの選択(許可するかしないか)は、iPhoneの 設定>アプリ名の中に保存され、ユーザーが切り替え可能です。逆に言うと、初回起動時以降は、ユーザーに設定で許可を変更するよう促す必要がある、ということですね。

アクセス許可は、iPhoneの 設定>アプリ名の中に保存される。
アクセス許可は、iPhoneの 設定>アプリ名の中に保存される。

で、冒頭の音楽ライブラリのアートワークを使うには、メディアライブラリへのアクセス許可を求めればいいようです(今回は音楽データそのものは使っていません)。

【参考】
iOS10ではカメラアクセスなどの目的を明示しないと強制終了する – Qiita
[iOS 10] ユーザーのプライバシー情報にアクセスするときは理由を書こう | Developers.IO
iOS 10 からユーザの許可を求めるアラートへの説明文追加が必須に – 強火で進め

【Xcode】Cardboard for unityのデモをXcode7で書き出す際の注意点

【OSX 10.11.3+Xcode 7.2.1+Unity 5.3.2f1+iPhone5(iOS 9.2.1)】

VRも盛り上がりを見せつつある昨今、iPhoneでVRのテストをしてみようかとググっていて、こんな記事を見つけました。

Cardboard SDK for Unityを使ってVR対応アプリを作る & Live2Dのキャラを目の前に召喚する – Qiita

以前、「ぱのかの」(現「ルクルク」)のイベントでスマホ用ビューアをもらったことを思い出しました。

IMG_1196
ぱのかの(ルクルク)ビューア。 ここ で手に入ります。

とりあえず「Cardboard SDK for Unity」のデモをテストしてみてみました。手順は下記のリンクを参照。

iOS 用 Unity のスタート ガイド  |  Cardboard  |  Google Developers

で、この記事通りに進めてみたもののXcodeからの書き出しでエラーになります。Xcode7リリース前の記事であるため、2点ほどエラーになった箇所があり、その対処のための備忘録です。

Xcode7.2.1でbitcodeエラー

Unityからビルドすると、Xcodeのプロジェクト一式が書き出されるので、プロジェクトファイル(今回は”Unity-iPhone.xcodeproj”)をXcodeで開くことになります(今回初めて知りました)。

で、Xcodeからビルドすると下記のようなエラーになります。

ld: '/Users/c_geru/work/unity/testProject/xcode/Libraries/Plugins/iOS/libvrunity.a(vraudio_unity.o)' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)

これは、プロジェクト設定の Build Settings > Enable BitcodeをNOにすればいいらしい。詳しくは下記参照。

Xcode7でbitcodeのエラーが出た – Qiita

Xcode7.2.1で”_SecTrustEvaluate”エラー

で、もう一回ビルドしてみると、今度は別のエラーが2件。

Undefined symbols for architecture armv7:
  "_SecTrustEvaluate", referenced from:
      ___75-[GTMSessionFetcher URLSession:task:didReceiveChallenge:completionHandler:]_block_invoke651 in libvrunity.a(GTMSessionFetcher.o)
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Undefined symbols for architecture armv7:
  "_SecTrustEvaluate", referenced from:
      ___75-[GTMSessionFetcher URLSession:task:didReceiveChallenge:completionHandler:]_block_invoke651 in libvrunity.a(GTMSessionFetcher.o)
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)

これらは、Security.frameworkが足りないことによるエラーのようです。プロジェクト設定の General > Linked Frameworks and Libraries で Security.framework を追加してやればOK。詳しくは下記参照。

[Unity] Xcodeで”_SecTrustEvaluate”, referenced from: エラーが出る – Qiita

これで無事、実機にビルドできました。

iPhone5にビルドした様子。
iPhone5に「Cardboard SDK for Unity」のデモをビルドした様子。

【Xcode】Apple Watch用アプリのテスト(2)- 親子間(iPhone、Apple Watch)の通信

【Xcode7.2 + MacOX10.11.2 + watchOS 2】
前回に引き続き、Apple Watch用アプリを試してみました。今回は親子間(iPhone、Apple Watch)の通信について。実機がないのでSimulatorで確認。

同じ方の書いたこの記事を参考にしました。

Apple Watch アプリから親アプリの情報を更新する。 – Apple Watch アプリプログラミング

ただこのままだとエラーが出て、コンパイルが通りません。watchOS 2では、ここで通信に使っている WKInterfaceController.openParentApplication() というメソッドが使えないようです。

ios – ‘openParentApplication(_:reply:)’ has been explicitly marked unavailable here – Xcode 7 Beta – Stack Overflow

そこで調べてみたら、この記事で解説されていました。

[Apple Watch] Watch Connectivity: sendMessage 即座にメッセージを送るには

準備は、こんな感じになります。

・WatchConnectivity を import する。
・WCSessionDelegate を設定する。
・WCSession のサポートの有無をチェックする(WCSession.isSupported())。
・WCSession がサポートされているときには WCSession を初期化(WCSession.defaultSession())して、delegateを設定し、activeにする(activateSession())。

後は、送信・受信用のメソッドをWCSession 用に書き換えてやればOKです。

・送信:– sendMessage:replyHandler:errorHandler:
・受信:– session:didReceiveMessage:replyHandler:

ちょっと気になったのは、Simulatorでのテストでは、iPhone >> Watch への送信は即時に受け取れるんですが、Watch >> iPhone への送信は、10秒ほどのタイムラグが出ました。これがSimulator のみの問題なのか、Apple Watch の仕様なのかは、実機がないので確認出来ていません。

【その他参考リンク】
WCSession Class Reference – iOS Developer Library
WCSessionDelegate Protocol Reference – iOS Developer Library
iOS – watchOS 2 の Watch Connectivity を使ってみた – Qiita
[watchOS 2][iOS 9] Watch Connectivity で情報をやりとりする様々な方法 | Developers.IO

【Xcode】Apple Watch用アプリのテスト(1)

【Xcode7.2 + MacOX10.11.2 + watchOS 2】
Apple Watch用アプリを試してみました。実機がないのでSimulatorで確認。

いろいろ探してみたけど、見た中ではこの記事が一番わかりやすかった。

・簡単な Apple Watch アプリを初めて作ってみる – Apple Watch アプリプログラミング –

Apple Watchアプリは、必ず親となるiPhoneアプリがあって、その子としてWatchKit App を作成するということのようです。

このサイトの通りで基本的に問題ないのだけれど、唯一違うのはwatchOSが2にバージョンアップされているので、プロジェクトエディターの追加ボタンから、Apple Watch のWatchKit App を選択するところで、選択肢が2つに増えているところ。ここでは左(watchOS 2)を選択。

WatchKit Appが、watchOS 2とwatchOS1の2種類あるある。
WatchKit Appが、watchOS2とwatchOS 1の2種類ある。無印がwatchOS2用。

もう1点、Simulatorで実行時に「”Install of Apple Watch Application never finished” 」というエラーが出たこと。これは、iPhone側のSimulatorでWatchアプリから、該当アプリの設定を開いて”Show App on Apple Watch”のスイッチを一度切ってから入れ直せばOKでした。詳しくは下記リンクで。

ios – “Install of Apple Watch Application never finished” Error when deploying watch kit app to device – Stack Overflow

これでSimulator上でApple Watchアプリの確認ができました。

【Xode】NavigationController配下にTabBarControllerを組み込む

【Xcode6.2 + iOS 8.4(iPhone5) + MacOX10.9.5】

文字通りのことをやろうと思ったんだけど、なかなかいい例がなかったのでメモ。

TabBarControllerとNavigationContorollerの組み合わせ例はいくつかあったんだけど、大抵がTabBarControllerをベースにして、子にNavigationContorollerを使うというパターン。

今回やりたかったのは、NavigationContorollerをベースにして、その配下でTabBarControllerを使うパターン。アプリの設定画面部分だけをTabBarControllerにするようなイメージです。

それから前提として「Viewは、プログラム生成ではなくStoryboardに配置する」ということ。AutoLayoutを使うことを考えると、レイアウトはStoryboard、処理はコードで分けるのがいいんじゃないかと。

Storyboardの構成はこんな感じです。
storyboard

・ViewController
・SettingViewController
・TimeSettingViewController
・SpeedSettingViewController

この4つは、それぞれ同名のカスタムクラスを作ってStoryboardで割り当てています。ViewControllerがrootControllerになっていて、ボタンタップで2つのタブ(TimeSettingViewController, SpeedSettingViewController)を持つTabBarController(SettingViewController)を呼び出す構成です。

※ちなみにTabBarControllerを配置すると自動的に接続されるSegueは削除してあります。その設定もコード上で試してみたかったので。

で、まずはUITabBarControllerの定義。どこで使うにせよ、これはAppDelegateの中に書くのが定石らしい。

【AppDelegate.h】
・Storyboardの各Viewを参照するので、その定義ファイル(.h)のimportと保存するUITabBarControllerの定義を追加する。

#import <UIKit/UIKit.h>
#import "SettingViewController.h"
#import "TimeSettingViewController.h"
#import "SpeedSettingViewController.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate,UITabBarControllerDelegate>
{
    UITabBarController *myTabBarController;
}

@property (strong, nonatomic) UIWindow *window;
@property (nonatomic,strong) UITabBarController *myTabBarController;

@end

【AppDelegate.m】
・あとで参照する可能性もあるので、各タブのcontrollerも一応定義しておく。
・UITabBarController の生成は、didFinishLaunchingWithOptionsで行う。
・tabControllerの切替イベント(shouldSelectViewController, didSelectViewController)もココに定義。

#import "AppDelegate.h"

@interface AppDelegate ()

@end

@implementation AppDelegate
{
    TimeSettingViewController *TimeSettingVC;
    SpeedSettingViewController *SpeedSettingVC;
}

@synthesize myTabBarController;


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    //UITabBarControllerを生成(storyboard上のUITabBarControllerを指定)
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    myTabBarController = [storyboard instantiateViewControllerWithIdentifier:@"SettingVC"];
    
    //各タブ用UIViewControllerを生成(storyboard上のUIViewControllerを指定)
    TimeSettingVC = [storyboard instantiateViewControllerWithIdentifier:@"TimeSettingVC"];
    SpeedSettingVC = [storyboard instantiateViewControllerWithIdentifier:@"SpeedSettingVC"];
    
    //UITabBarControllerに各タブ用UIViewControllerを設定
    NSArray *controllers = [NSArray arrayWithObjects:TimeSettingVC,SpeedSettingVC, nil];
    [myTabBarController setViewControllers:controllers animated:YES];
    
    //デリゲートの設定
    myTabBarController.delegate = self;
    
    //初期表示のタブを指定
    myTabBarController.selectedViewController = myTabBarController.viewControllers[1];
    
    NSLog(@"--array: %@",myTabBarController.viewControllers);
    NSLog(@"--selectedViewController: %@",myTabBarController.selectedViewController);
    NSLog(@"--index: %d",myTabBarController.selectedIndex);

    return YES;
}

-(BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
     NSLog(@"--tabBarControllerを切り替えます!");
    NSLog(@"--selectedIndex: %d",tabBarController.selectedIndex);
    NSLog(@"-- vc: %@",viewController);
    
    return YES;
}

-(void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
    NSLog(@"--tabBarControllerが切り替わった!");
    NSLog(@"--selectedIndex: %d",tabBarController.selectedIndex);
    NSLog(@"-- vc: %@",viewController);
}

@end

※22行目〜28行目までは、tabBarControllerに各タブを定義する処理なので、Storyboard上でSegueが定義されている場合は不要。

ここでのポイントは、AppDelegateからStoryboardを参照するには、storyboardWithNameを使わないといけないということ。各Viewからなら、self.storyboardでいけるんだけど。

【参考】

次にtabBarContorollerを呼び出す処理を書きます。viewControllerにボタンを配置して、そこからリンクします。

【ViewController.h】
・AppDelegateをimport。
・showSettingViewはボタンタップ時の処理。

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

@interface ViewController : UIViewController

- (IBAction)showSettingView:(id)sender;

@end

【ViewController.m】
・AppDelegateを参照してtabBarControllerを取得して、pushViewControllerで表示する。

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (IBAction)showSettingView:(id)sender {
    //AppDelegateを参照して、myTabBarControllerを呼び出す
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    [self.navigationController pushViewController:appDelegate.myTabBarController animated:YES];
}

@end

iOS View Controller カタログ(PDF)によれば・・・

Tab Barインターフェイスを作成する前に、その使い方を決定する必要があります。Tab Barインターフェイスはデータに対して支配的な構成となるため、用途は以下に限定されます。
・ウィンドウのルートView Controllerとして追加する。
・Split Viewインターフェイスの2つのView Controllerの1つとして追加する。(iPadのみ)
・別のView Controllerからモーダルモードで表示する。
・Popoverから表示する。(iPadのみ)

モーダルモードでのTab Bar Controllerの表示
(一般的ではありませんが)アプリケーションでTab Bar Controllerをモーダルモードで表示すること もできます。Tab Barインターフェイスは、通常はアプリケーションのメインウインドウにインストー ルされ、必要な場合にだけ更新されます。ただし、インターフェイスの設計において必要と認められ る場合には、Tab Bar Controllerをモーダルモードで表示することも可能です。たとえば、アプリケー ションの主たる操作モードを、Tab Barインターフェイスを使用したまったく別のモードに切り替える には、クロスフェードトランジションを使用して、第2のTab Bar Controllerをモーダルモードで表示します。
Tab Bar Controllerをモーダルモードで表示する場合は、必ずpresentModalViewController:animated: メソッドの第1パラメータにTab Bar Contrllerオブジェクトを渡します。このTab Bar Controllerは、表示する前にすでに設定されていなければなりません。したがって、Tab Barインターフェイスをメインウ インドウにインストールする場合とまったく同じ方法で、ルートView Controllerを作成して設定し、それらをTab Bar Controllerに追加する必要があります。

ということらしいので、

・TabBarControllerの定義は、AppDelegateで行う。
・表示は、別のView Controllerからモーダルモードで行う。

ということでいいようです。

【参考】
Tab Bar Controllers
Combined View Controller Interfaces
iOS View Controller カタログ(PDF/上記2つの日本語訳)
iOS View Controller プログラミングガイド(PDF)
StoryboardとSegueの基本 – Kesin’s diary
画面間でのデータの受け渡しに付いて: 永遠ログ
iOS – segue による画面遷移メモ – Qiita
日本語ドキュメント – Apple Developer
プログラム側からUITabBarControllerを切り替えて、さらに画面遷移させたい時 – makoラボ
Programming.log, [Objective-C] applicationDidFinishLaunching とは

【Xcode】UIBezierPathをアニメーションで描画

【Xcode6.2 + iOS 8.4(iPhone5) + MacOX10.9.5】

UIBezierPathはベジェ曲線を描画するクラスですが、この描画そのものをアニメーションさせられないかと調べてみました。

まずはUIBezierPathの使い方、この辺が参考になります。
[iPhone] UIBezierPath 図形の描画 (Objective-C)
[Swift] UIBezierPathを使って、直線、楕円、矩形などで描画する
UIBezierPathで曲線を描画してみた | 丸ノ内テックブログ
UIBezierPathで図形を描画する。: iPhoneソースコードがきたなくて

で、実際に書いてみたのがこちら。

iOS Simulator Screen Shot 2015.10.05 3.03.25
UIBezierPathでローディング的な円を描いてみた。

コードはこんな感じ。

・CircleView.h

//
//  CircleView.h
//  testUIBezierPath
//
//  Created by c-geru on 2015/10/04.
//  Copyright (c) 2015年 c-geru. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface CircleView : UIView

@end

・CircleView.m

//
//  CircleView.m
//  testUIBezierPath
//
//  Created by c-geru on 2015/10/04.
//  Copyright (c) 2015年 c-geru. All rights reserved.
//

#import "CircleView.h"

@implementation CircleView

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // 円弧 -------------------------------------
    UIBezierPath* arc
    = [UIBezierPath bezierPathWithArcCenter:
       CGPointMake(self.frame.size.width/2,self.frame.size.height/2) radius:125.0f startAngle:M_PI/2*-1 endAngle:M_PI*2 clockwise:YES];
    UIColor *aColor = [UIColor lightGrayColor];
    [aColor setStroke];
    arc.lineWidth = 10;
    // 点線のパターンをセット
    CGFloat dashPattern[2] = { 2.0f, 4.0f };
    [arc setLineDash:dashPattern  count:2 phase:0];
    [arc stroke];

    // 円弧 (アニメーション)-------------------------------------
    UIBezierPath* arc2
    = [UIBezierPath bezierPathWithArcCenter:
       CGPointMake(self.frame.size.width/2,self.frame.size.height/2) radius:125.0f startAngle:M_PI/2*-1 endAngle:M_PI*4/4 clockwise:YES];

    UIColor *aColor2 = [UIColor blueColor];
    [aColor2 setStroke];

    arc2.lineWidth = 10;
    // 点線のパターンをセット
    CGFloat dashPattern2[2] = { 2.0f, 4.0f };
    [arc2 setLineDash:dashPattern2  count:2 phase:0];
    [arc2 stroke];
}
@end

で、この青い部分をローディングバー的にアニメーションしたかったわけです。

基本的にアニメーションはCALayerってことはわかったんだけど、アニメーションさせるためのパラメタの渡し方がよくわかりませんでした。

iPhone – パスに沿ってアニメーションさせる – Qiita
CALayerを使ってアニメーションしよう – make.appアプリ開発Tips

更に調べてみると、描画要素(fillColor,strokeColor,lineWidth,lineDashPattern)をUIBezierPathでなく、CALayer側に渡してあげればいいみたい。アニメーションで描画するので、考えてみれば当然か。

iOS Development Tricks: Custom Circular Progress View for iOS
CAShapeLayer example – Round corners view with dashed line border | Luka Gabric

最終的にアニメーションできたコードがコレ。

・CircleView.h

//
//  CircleView.h
//  testUIBezierPath
//
//  Created by c-geru on 2015/10/04.
//  Copyright (c) 2015年 c-geru. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>

@interface CircleView : UIView

@end

・CircleView.m

//
//  CircleView.m
//  testUIBezierPath
//
//  Created by c-geru on 2015/10/04.
//  Copyright (c) 2015年 c-geru. All rights reserved.
//

#import "CircleView.h"

@implementation CircleView

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // 円弧 -------------------------------------
    UIBezierPath* arc
    = [UIBezierPath bezierPathWithArcCenter:
       CGPointMake(self.frame.size.width/2,self.frame.size.height/2) radius:125.0f startAngle:M_PI/2*-1 endAngle:M_PI*2 clockwise:YES];
    UIColor *aColor = [UIColor lightGrayColor];
    [aColor setStroke];
    arc.lineWidth = 10;
    // 点線のパターンをセット
    CGFloat dashPattern[2] = { 2.0f, 4.0f };
    [arc setLineDash:dashPattern  count:2 phase:0];
    [arc stroke];

    // 円弧 (アニメーション)-------------------------------------
    UIBezierPath* arc2
    = [UIBezierPath bezierPathWithArcCenter:
       CGPointMake(self.frame.size.width/2,self.frame.size.height/2) radius:125.0f startAngle:M_PI/2*-1 endAngle:M_PI*4/4 clockwise:YES];

    if (self.pathLayer == nil)
    {
        CAShapeLayer *shapeLayer = [CAShapeLayer layer];

        shapeLayer.path = [arc2 CGPath];

        shapeLayer.fillColor = [UIColor clearColor].CGColor;
        shapeLayer.strokeColor = [UIColor blueColor].CGColor;
        shapeLayer.lineWidth = 10;

        shapeLayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:2], [NSNumber numberWithInt:4], nil];

        [self.layer addSublayer:shapeLayer];
        self.pathLayer = shapeLayer;
    }

    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    pathAnimation.duration = 3.0;
    pathAnimation.fromValue = [NSNumber numberWithFloat:0.0];
    pathAnimation.toValue = [NSNumber numberWithFloat:1.0];
    [self.pathLayer addAnimation:pathAnimation forKey:@"drawCircleAnimation"];
}
@end

【Xcode】Facebook投稿でplugin com.apple.share.Facebook.post invalidated

【Xcode6.2 + iOS 8.4(iPhone5) + MacOX10.9.5】

引き続きFacebook投稿の話です。

前回のポストに書いたようにFacebook投稿自体は出来るようになったのですが、プログラムで設定したメッセージが表示されませんでした。コードはこんな感じ。

//facebook:送信
- (IBAction)sendFB:(UIBarButtonItem *)sender {

    // 組み込みのFacebookが利用可能な端末かを検証する
    if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) {
        
        // Facebook投稿機能のインスタンスを作成する
        SLComposeViewController *slComposeViewController = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeFacebook];
        
        
        // 投稿するコンテンツを設定する
        // 表示する文字列
        [slComposeViewController setInitialText:@"#アイコン顔カメラ"];
        // URL
        [slComposeViewController addURL:[NSURL URLWithString:@"http://www.c-geru.com/"]];
        // 画像
        [slComposeViewController addImage:[UIImage imageNamed:@"icon_10.png"]];
        
        // 処理終了後に呼び出されるコールバックを指定する
        [slComposeViewController setCompletionHandler:^(SLComposeViewControllerResult result) {
            
            switch (result) {
                case SLComposeViewControllerResultDone:
                    NSLog(@"Done!!");
                    break;
                case SLComposeViewControllerResultCancelled:
                    NSLog(@"Cancel!!");
            }
        }];  
        
        // 表示する
        [self presentViewController:slComposeViewController animated:YES completion:nil];    
    }

}

で、表示される画面がコレ。「slComposeViewController setInitialText:」のテキストが表示されていません。
メッセージが表示されない

この画面でテキスト入力すると、そのテキストは投稿されますが、Xcodeに下記のようなメッセージが表示されます。

plugin com.apple.share.Facebook.post invalidated

【追記】2015.8.17 ちなみに「slComposeViewController setInitialText:」をコメントにしても、投稿後に上記メッセージが表示されます。

調べてみると、こんな情報が。

ios – UIActivityViewControllerでFacebookにシェアできない – スタック・オーバーフロー
sharing – ios plugin com.apple.share.Facebook.post do not show provided text – Stack Overflow

これによると、Facebook側のポリシー変更ということのようです。

Platform Policy 2.3 Example and Explanation

メッセージは基本ユーザに入力してもらうと考えれば、ハッシュタグつけるとか以外に困ることはなさそうですが、一応備忘録として。

【Xcode】Facebook投稿で「ログインしていません」と表示される

【Xcode6.2 + iOS 8.4(iPhone5) + MacOX10.9.5】

アプリからFacebook/twitterへの投稿は、投稿するだけなら各サービスへのアプリ登録は必要なく、Social.FrameworkをインポートしてSLComposeViewControllerを使えばカンタンに出来るらしい。

Objective-C – LINE/Facebook/Twitter投稿 – Qiita
[XCODE] iOS組み込みのFacebook投稿機能を使う方法 – YoheiM .NET
【Xcode】Facebook、TwitterにiOS標準フォームで投稿する
serviceType – SLComposeViewController Class Reference

で、試してみたところ、Facebookの方は「ログインしていません」とメッセージが出て投稿できない。

ログインしていません
表示されるアラート

iPhone側では・・・

・Facebookアプリはインストール、ログイン済。
・iPhoneの 設定>Facebook でID、パスワードは登録済み。

になっています。

いろいろテストしてみたところ、下記の手順で投稿できるようになりました。

1.Facebookアプリをログアウト。
2.iPhoneの 設定>Facebook でID、パスワードの登録を確認。
3.Facebookアプリでログイン。

Facebookアプリに最初にログインしたときに、iPhoneの 設定>Facebook でID、パスワードを登録せずに、Facebookアプリ側でログインしていたため、Xcode側からは設定情報を使ってログインされていないと判断されていたみたいです。

またFacebookアプリはログイン状態を保持しているので、iPhone上でFacebookアプリが起動されているかどうかではなく、Facebookアプリログインされた状態になっているかどうかで判断しているようです。

ブラウザ上でのFacebookページへのログイン状態は関係しないようです。まあ設定の情報を使っていないので、当たり前か。

【Objective-C】BSXPCMessage received error for message: Connection interrupted というエラーメッセージ

【Xcode6.2 + iOS 8.4(iPhone5) + MacOX10.9.5】

Xcode5でiOS7をターゲットにして作ったアプリの改修をしていて、Xcode6.2でiPhone5の実機にビルドすると「BSXPCMessage received error for message: Connection interrupted」というメッセージが出てきます。

※同じ環境で、iPhone4S(iOS 7.1.2)では発生せず。

起動時のみログにメッセージが表示されるだけで、動作そのものには影響なさそうだけど、気になるので調べてみました。

【参考】ios – BSXPCMessage received error for message: Connection interrupted – Stack Overflow

このサイトを見てみると、どうやら「iOS 8でCIFilterのバグが原因」ということらしい。

CIContextのcontextWithOptionsで指定するoptionに、iOS8以降で項目(kCIContextPriorityRequestLow, kCIContextWorkingFormat)が追加されているので、明示的にオプションを指定しないとダメということなのかな?

【参考】+ contextWithOptions: – CIContext Class Reference
【参考】Context Options – CIContext Class Reference

とりあえずこのアプリはiOS7以上になっているので、「kCIContextUseSoftwareRenderer」を指定することでエラーは消えました。

【修正前】

_imageContext = [CIContext contextWithOptions:nil];

【修正後】

NSDictionary *contextOptions = [NSDictionary dictionaryWithObjectsAndKeys:
                                    [NSNumber numberWithBool:YES],kCIContextUseSoftwareRenderer,nil];
_imageContext = [CIContext contextWithOptions:contextOptions];

※ imageContextはCIContextとして定義されています。

@property (nonatomic, strong) CIContext *imageContext;

その他、下記のサイトを参考にしました。感謝。

超できるかな(Swiftでカメラアプリ1)
Study Swift: CIFilter – Invert the photo color by built-in filter
CoreImageで画像の加工をする その3「GPUにするべきかCPUにするべきか」 – Teratoma

手持ちの環境が対応してないので確認出来ないんだけど、Xcode6.3では直っているのかしらん?