BAD_ACCESS

おもにiOS、ときどき変な電子工作、ガジェット話。

ローカルP2Pアプリをつくるためにググりまくった結果

GKSession、GKVoiceChatServiceがiOS7からdeprecated orz...

こんな感じで紹介されていたGKSessionとGKVoiceChatServiceの組み合わせによる構成も今は推奨されていない。

iOS7からはMultipeer Connectivity Framework

今まで、GKSession等を使って構成していたP2Pを専用のフレームワークを使ってつくることができる。

まずは、WWDC 2013 Session VideosNearby Networking with Multipeer Connectivityで概要を確認。 そのあとAppleの公式サンプルアプリMultipeerGroupChatをビルドして実機確認。

【簡単】MCAdvertizerAssistantとMCBrowserViewController

クラスメソッドさんのこの記事「[iOS 7] P2P 通信を手軽に実現する Multipeer Connectivity Framework を使ってみる」に実装例を含めて詳しく書いてある。 MCAdvertizerAssistantとMCBrowserViewControllerが端末を探して、相手と接続するまでの煩わしい部分をよしなにしてくれるので、非常に簡単!

MCBrowserViewControllerはフレームワークでUIまで用意してくれちゃっているので、公式のサンプルアプリも併せるとすごく簡単にわりとリッチなP2Pのチャットアプリまで作れてしまう。

「デフォルトのピア選択画面はねーよ」という意見もあり、

Screenshot 2013.11.26 00.25.24.png

iOS7のフラットなデザインと相まって、MCBrowserViewControllerで提供してくれるUIは非常にシンプルで洗練されているのですが、いかんせん味気がないため、用意しているデザインなどとフィットしないということが十分にありえます。

※今回はその目的のために色々と調べてきたわけです。

【カスタマイズ】MCNearbyServiceAdvertiserとMCNearbyServiceBrowserを使ってP2P接続

こんな感じで実際にも需要はありそうなところだと思います。

一番、欲しい形でまとまっていたのがこの方の記事です。 [iOS 7 SDK] Multipeer Connectivity framework で Peer-to-Peer Connectivity

ここで紹介されているテスト接続用のアプリを同じように書いてみて、接続できたので、一応は調べたいところまでは行きました。

以下、気になる点

  • Wi-fiではうまく接続できたが、Bluetoothでは??になるときがあった。

このあたりドキュメント上、Wi-fiもBluetoothも問題なくいけるはずだけどちょっと心配。

  • 接続を許可するところなどもこっちで書かないといけないので、汎用的に使えるように書いておきたい。

  • VoiceChat部分の実装。

いいのなんかないかな。

ただ「いいね!」を押してもらうだけのシンプルなサンプルアプリ"Go Like On"を書いた

ずいぶん前に書いたサンプルアプリ。アプリ側からワンタップで「いいね!」までいけるかと聞かれて試しに実装してみた。 クライアント側の実装はすごくシンプルだけど、facebook developer側の設定がちょっとややこしかったのでメモとして残しとく。

GitHub : somtd / GoLikeOn

 2013-11-23 1.19.00.png

Check Account

まず、端末側でfacebookにサインアップしているかをチェックする。このときアプリのアクセス許可を求めるダイアログが表示される。

- (void)checkAccounts {
    __block __weak ViewController *weakSelf = self;
    ACAccountType *accountType;
    ACAccountStore *accountStore = [[ACAccountStore alloc] init];
    
    if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) {
        accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
        
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 @"YOUR_FACABOOK_APP_ID", ACFacebookAppIdKey,
                                 [NSArray arrayWithObjects:@"public_actions", @"publish_stream", nil], ACFacebookPermissionsKey,
                                 ACFacebookAudienceFriends, ACFacebookAudienceKey,
                                 nil];
        [accountStore
         requestAccessToAccountsWithType:accountType
         options:options
         completion:^(BOOL granted, NSError *error) {
             NSLog(@"error:%@",[error description]);
             NSArray *accountArray = [accountStore accountsWithAccountType:accountType];
             NSLog(@"accounts:%@",accountArray);
             for (ACAccount *account in accountArray) {
                 NSString *text = [NSString stringWithFormat:@"%@",[account username]];
                 [weakSelf performSelectorOnMainThread:@selector(showFacebookAccount:)
                                            withObject:text
                                         waitUntilDone:YES];
             }
         }];
    }
}

アカウントの取得に成功するとアカウント(メールアドレス)が表示され、デカイいいねボタンがアクティブになる。

Facebook developer設定

基本設定

基本設定の中でbundle IDを指定する。

setting_1

詳細設定

App TypeNative/Desktopに、 App Secret in ClientNo

setting_2

ここでちゃんとアクション型として"Like"を指定しておく。 setting_3

いいねする

Facebookアプリ側の設定が正しく行われていれば、下記のメソッドでちゃんと「いいね」できるようになっているはず!

- (IBAction)onLikeButton:(id)sender {
    NSDictionary *information = @{@"url":@"http://somtd.hatenablog.com"};
    [self postStreakWithInfomation:information];
}

- (void)postStreakWithInfomation:(NSDictionary *)information {
    
    if ([SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]) {
        ACAccountStore *accountStore = [[ACAccountStore alloc] init];
        ACAccountType *accountType = [accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierFacebook];
        
        NSDictionary *optionsToRead  = @{ ACFacebookAppIdKey: @"YOUR_FACABOOK_APP_ID",
                                          ACFacebookPermissionsKey: @[@"basic_info"],
                                          };
        NSDictionary *optionsToWrite = @{ ACFacebookAppIdKey: @"YOUR_FACABOOK_APP_ID",
                                          ACFacebookPermissionsKey: @[@"publish_stream"],
                                          ACFacebookAudienceKey : ACFacebookAudienceFriends,
                                          };
        
        [accountStore requestAccessToAccountsWithType:accountType options:optionsToRead completion:^(BOOL granted, NSError *error) {
            [accountStore requestAccessToAccountsWithType:accountType options:optionsToWrite completion:^(BOOL granted, NSError *error) {
                
                NSArray *accountArray = [accountStore accountsWithAccountType:accountType];
                for (ACAccount *account in accountArray) {
                    
                    NSString *urlString = [NSString stringWithFormat:@"https://graph.facebook.com/%@/og.likes", [[account valueForKey:@"properties"] valueForKey:@"uid"]] ;
                    NSURL *url = [NSURL URLWithString:urlString];
                    NSDictionary *params = [NSDictionary dictionaryWithObject:information[@"url"] forKey:@"object"];
                    SLRequest *request = [SLRequest requestForServiceType:SLServiceTypeFacebook
                                                            requestMethod:SLRequestMethodPOST
                                                                      URL:url
                                                               parameters:params];
                    [request setAccount:account];
                    [request performRequestWithHandler:^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error) {
                        NSLog(@"responseData=%@", [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding]);
                    }];
                }
            }];
        }];
    }
}

リポジトリこちら(GitHub : somtd / GoLikeOn)から

iTunes Storeの情報を簡単に取ってきて表示させるだけのサンプル"AppRankingParser"を書いた

Appleが太っ腹にも提供している"RSS Feed Generator" 目当てのランキングを取り出すためのインターフェイスをつくりました。 (RSS側の値が変更されると身も蓋もないことになるので今後なんとかしたい)

GitHub somtd/AppRankingParser

 2013-11-21 18.11.53.png 2013-11-21 18.11.41.png

使ってもらうのはclass以下の4つのファイルです。

  • RankingApi.h

  • RankingApi.m

  • RankingApiClient.h

  • RankingApiClient.m

RankingApiClientAFHTTPClientのサブクラスとなっているので、こちらをそのまま利用する場合はAFNetworkingが必要になります。

RankingApiの概要

ARAudiobooks : iOSアプリのランキングを取得

+ (void)getiOSAppsRankingWithCountry:(ARCountry)country
                            feedType:(ARiOSApps)feedType
                               genre:(ARiOSAppsGenre)genre
                               limit:(int)limit
                             handler:(void (^)(id response, NSError *error))handler;

ARCountry : RSS取得対象の国

現在は、UnitedStates,Japanのみ利用可能。

ARiOSApps : フィードタイプ

  • ARiOSAppsTypeTopFree // iPhoneトップ無料

  • ARiOSAppsTypeTopPaid // iPhoneトップ有料

  • ARiOSAppsTypeTopGross // iPhoneトップセールス

  • ARiOSAppsTypeTopFreeiPad // iPadトップ無料

  • ARiOSAppsTypeTopPaidiPad // iPadトップ有料

  • ARiOSAppsTypeTopGrossiPad // iPadトップセールス

  • ARiOSAppsTypeNew //新着アプリ

  • ARiOSAppsTypeNewFree //新着無料アプリ

  • ARiOSAppsTypeNewPaid //新着有料アプリ

ARiOSAppsGenre : アプリのジャンル

  • iOSAppsBooks //ブック

  • iOSAppsBusiness //ビジネス

  • iOSAppsCatalogs //カタログ

  • iOSAppsEducation //教育

  • iOSAppsEntertaiment //エンターテインメント

  • iOSAppsFinance //ファイナンス ・・・

使い方(Demo)

% git clone git@github.com:somtd/AppRankingParser.git

% cd AppRankingParser/AppRankingParserDemo

% pod install

AppRankingParser/AppRankingParserDemoディレクトリの AppRankingParserDemo.xcworkspaceを開く。

Demoでは適当なUITableViewControllerのサブクラスで上記のクラスメソッドを呼んでやってます。

[RankingApi getiOSAppsRankingWithCountry:Japan
                                        feedType:_iOSAppsType
                                           genre:0
                                           limit:20
                                         handler:^(id response, NSError *error) {
                                             _array = [NSMutableArray arrayWithCapacity:0];
                                             NSLog(@"response:%@",response);
                                             for (NSDictionary *appDictionary in (NSArray *)response) {
                                                 App *app = [[App alloc] initWithDictionary:appDictionary];
                                                 [_array addObject:app];
                                             }
                                             [self.tableView reloadData];
                                         }];

※Appというクラス名は適切ではありませんでした。ランキングを取得した結果をオブジェクトとして取り出す用に使っています。

はじめてのC4!C4のデモまとめ

前回、C4について本当に簡単にまとめて概要を説明してしまったため、結局何ができるのかさっぱりわからない記事になってしまった\(^o^)/

今回はあまり深く掘り下げずに、公式ページのTutorialsをもとにして、どういうことができるか動画を中心にまとめる。

C4Shape関連

Line End Points

Animated Dash Pattern 1

Animated Dash Pattern 2

Animated Dash Pattern 3

Shape Morphing

Interactive Curves

Image関連

Image Property

Movie関連

Volueme

Height/Width

Rate

OpenGL関連

Animation

C4Control関連

Perspective Distance

Subview Shapes

Animated Images Masking

User Interface関連

Interaction関連

LongpressAdvanced

Advanced Panning

Advanced

phasing

probability

シンプルなだけにできることの範囲は広そう。

はじめてのC4!

最近気になっているiOS向けのframeworkについて手を出しついでに簡単にまとめてみます。

公式ページはこちらC4:http://www.c4ios.com/

あんまり日本語の記事を見ないので手探りではありますが、今回は概要の拙訳とHello Worldから。

なんて読むの?

おそらく"CocoaFor"と読むのではないでしょうか。

C4とは

Code, Creatively.

C4 is a brand new creative-coding framework lets you build expressive user experiences and create works of art. C4 gives you the power of the native iOS programming environment with a simplified API that lets you get down to working with media right away. Build artworks, design interfaces, explore new possibilities of working with media and interaction.

要はメディアの扱いに特化したiOS向けのフレームワークです。 openFrameworksのiOS版?といったところなのでしょうか。

To The New Ones

Creating an expressive, intuitive mobile application is hard work. C4 makes this simple by letting you build projects that already deal with a lot of boring stuff. You get to start creating gorgeous elements faster and with more ease than tackling app development on your own.

C4 will help you learn how to make things move and interact in a very short amount of time. It is designed to look and feel like a simpler version of Objective-C, so that when you're ready to venture out on your own, the step from build C4 apps to native mobile applications for iOS is tiny.

C4はObjective-C自体の複雑さを感じさせることなく、小さなステップでネイティブのアプリをつくることができます。 とにかく、つくりたいものをより早く、簡単に、ゴージャスにつくることができます。

To The Crazy Ones

If you're already a developer and you know the ins and outs of developing for mobile platforms C4 still has a lot to offer you. First, C4 approaches animation and interaction in a synthetic way so that all its media objects are consistent and similar. If you want to learn from the way we build things the API is open (you can grab it HERE). If you don't want to learn how we do things, but just get down to using the core API you can include a compiled C4 library in your own projects and take advantage of all the work we've been doing (you can pull the headers and the libC4.a file from a project built off of an Xcode template).

すでにモバイルでのアプリ開発に親しんでいる人であっても、C4から学べることは多いはず。 中身をもっと詳しく知りたい人はC4はすべてオープンになっているのでそこから色々調べていってもいいし、 とにかく手っ取り早く試したい人はライブラリごとプロジェクトにつっこんでビルドしてもいい。

Open Source

C4 is an open source project, hosted on GitHub. It has an MIT license, so it’s as open as possible.

C4のプロジェクトはGitHubにホストされていて、ライセンスはMITとなっているので、公開できるもの全てがOpen Sourceになっている。

ではさっそくHello World

C4をはじめるために以下の3つのものが必要になる。

  • Mac (OSX 10.9で試してます。)

  • Xcode (Xcode5.02で試してます。)

  • C4 (最新バージョンXcode5対応はこちらから)

今回はXcodeでの開発経験がある前提ではじめてのC4をすすめていく。

まずはC4のインストーラーを起動する。

 2013-11-20 1.04.03.png

インストール完了後、Xcodeを立ち上げる。

Xcodeの[File] > [New] > [Project...]を選択。

 2013-11-20 1.07.44.png

プロジェクトのテンプレートでC4が選択できるようになっている。

[C4 Single View Application]を選択し、適当なプロジェクト名をつけてプロジェクトを作成。

 2013-11-20 1.10.29.png

プロジェクトをビルドしてエラーが出ずに無事にシミュレーター上に以下のスプラッシュが表示されればひとまずは成功です。

iOS_2013.11.20 1.14.10.png

次回以降で、C4のサンプルを試しながら何か作っていこうと思います。

C4をはじめるにあたってfollowすべきアカウント一覧

GitHub

C4Framework

Travis Kirton

Twitter

@CocoaFor

@postfl

Bluetoothサイコロ「DICE+」を試す

DICE+ DICE+が届いたので早速"Hello world"してみる。

DICE+をHello Worldする。

iOS版、android版、Unity版が用意されていてサンプルコードはGithub上にアップされている。

DICE+ repositories

すぐcloneして試すことができるが、DicePlus.framework自体はデベロッパ登録した後に手に入れることができるので、まずdeveloperサイトでサインアップしておく。

このリポジトリ"GameTechnologies / Hello-DICEPlus-for-iOS"からcloneしてきたプロジェクトに、developerサイトからダウンロードしたDicePlus.frameworkをドラッグして追加する。

実機でビルドが完了したら、DICE+本体を180度ひっくり返すことで電源が入る。

Bluetoothが接続しにいっている様子は伺えるが、iPhoneとDICE+の接続が確立しない。

色々と調べてみたが、ソフトウェア側には問題がないようなので、ハード側(DICE+側)を確認する。

Firmware uploaderで最新のDICE+にアップデート

結論からいうと出荷時のDICE+のファームウェアのバグ?で接続が確立していないようだった。 (デモとして配布されているDICE+対応ゲームはちゃんとうごいていたので、その時点でのファームウェアに対応して開発されたのだろうと思う)

ファームウェアのアップデートに際して、まずはuploaderをダウンロードしてくる。

 2013-10-22 19.45.15.png

ダウンロードしたアプリケーションを開くと下記のような画面が出る。 どうやらファームウェアをアップするためにまずはUSBケーブルの 抜き差しを2回 する必要がある。 抜き差しを2回 というのが重要。

 2013-10-22 19.32.18.png

普通にUSBケーブルを差しているだけだと薄いオレンジ色で点滅しているが、アップロードモードになっているときは白色で点滅。

 2013-10-23 23.06.29.png

認識されると下記のような画面に変わるので、uploaderをダウンロードしたところから最新のFirmwareをダウンロードしてきて、アップロードする。

リベンジ Hello DICE+のプロジェクトを再びビルド

今度は接続までの時間がスゴく短くなった!ちゃんと接続を確立したことを確認して(サイコロの字が緑色に光る)サイコロを振る!

ログの結果はこちら。完全にハロワー用のコードなのでログはシンプル。 もっと色々な情報が取れるのかも。

2013-10-23 23:11:13.550 Hello DICE+[18850:907] Good and bad rolls: 3
2013-10-23 23:11:13.552 Hello DICE+[18850:907] Good rolls: 3
2013-10-23 23:11:18.260 Hello DICE+[18850:907] Good and bad rolls: 4
2013-10-23 23:11:18.262 Hello DICE+[18850:907] Good rolls: 4

とにかくちゃんとサイコロの出目までとれた!

リアクティブプログラミングをかじる ReactiveCocoaについて

遅ればせながら、リアクティブプログラミングをかじる。

なぜリアクティブプログラミングは重要か。

すごくまとまっている記事ですが、まだイメージできない...

なのでObjective-Cで書かれたコードを読んで理解してみようと試みている。

ReactiveCocoaはCocoa/Cocoa touch向けのリアクティブプログラミングframework。 OctoKitの中で採用されたりもしているので読み始めてみた。

参考:ReactiveCocoa

Github:ReactiveCocoa/ReactiveCocoa

どういうものかをざっと把握するために、GithubのREADMEの一部を訳してみた。 ちょっとまだどこまで理解できているのかわからないので、次はサンプルアプリものぞいてみる。

Introduction

ReactiveCocoaはリアクティブ・プログラミングの機能が実装されています。

RAC(ReactiveCocoa)は現在と未来の値をキャプチャしたRACSignalを提供します。

RACはソフトウェア側が継続的に値の監視を行う必要はありません。

RACRACSignalに反応したり、連鎖させたり、結合させたりすることによって、宣言的に記述することができます。

RACSignalはFuture,Promiseデザインパターンのように、非同期処理を記述することができます。 これによってネットワークのコードを含むソフトウェアを大幅に簡素化することができます。

またRACを使う主な利点の一つとしては、callbackやBlocks、notification、KVO、さらにdelegateメソッドといったあらゆる非同期処理を単一のインターフェイスで扱うことができる点といえるでしょう。

Signals

次の例では、self.usernameに変更が合った場合、コンソールに新しい名前を表示させます。

RACObserve(self, username)によって現在のself.usernameの値を送信するRACSignalを新たに作成します。

-subscribeNext::新しい値が送信されるたびにBlockが実行されます。

[RACObserve(self, username) subscribeNext:^(NSString *newName) {
    NSLog(@"%@", newName);
}];

Chaining

RACSignalははKVOのnotificationとは異なり連鎖することができます。

次の例では、コンソールに表示させるのは"j"から始まる名前の場合とします。

-filter::そのBlock内の実行結果がYESを返したときにのみ値を送信する新しいRACSignalを返します。

[[RACObserve(self, username)
   filter:^(NSString *newName) {
       return [newName hasPrefix:@"j"];
   }]
   subscribeNext:^(NSString *newName) {
       NSLog(@"%@", newName);
   }];

State

Signalは状態の導出にも用いることができます。

これまでのようにプロパティを監視し、その変化に応じて状態を示すための新たなプロパティを設定しなくとも、RACによってSignalや操作そのものが状態の特性を示すことができるのです。

次の例では、パスワード入力欄とパスワード確認用の入力欄の値が同じであればcreateEnabledTrueとする一方向のバインディングを作成します。

RAC()バインディングをいい感じに見せるためのマクロです。

+combineLatest: reduce:はSignalの配列を受け取り、各Signalの最新の値をもとにBlockを実行し、その実行結果に応じて新たに戻り値を送信するRACSignalを作成します。

RAC(self, createEnabled) = 
[RACSignal combineLatest:@[ RACObserve(self, password),RACObserve(self, passwordConfirmation)] 
                   reduce:^(NSString *password, NSString *passwordConfirm) {
                            return @([passwordConfirm isEqualToString:password]);
    }];

Not just KVO

Signalはただ単にKVOするだけではなく、どんなストリーム上にでもつくりだすことができます。 次の例では、ボタンを押したかどうかについても表すことができます。

RACCommandはUIのアクションを示すRACSignalのサブクラスになります。

-rac_commandがNSButtonに追加されています。 ボタンが押されることで自分自身にコマンドを通知します。

self.button.rac_command = [RACCommand command];
[self.button.rac_command subscribeNext:^(id _) {
    NSLog(@"button was pressed!");
}];

Asynchronous network operations

次の例では、ネットワーク経由でログインするためのボタンをフックします。 ボタンが押される度にloginCommandが通知されます。

self.loginCommand = [RACCommand command];

loginCommandが値を送信するたびに、このBlockは、ログインプロセスを開始します。

-addActionBlock:コマンドが実行されるたびに、このブロック内で実行した結果を返します。

self.loginSignals = [self.loginCommand addActionBlock:^(id sender) {
 //この`-logIn`メソッドはrequestが完了したときにsignalを返します。
    return [client logIn];
}];

ログインが成功したときのみ、ログメッセージを表示します。

[self.loginSignals subscribeNext:^(RACSignal *loginSignal) {
    [loginSignal subscribeCompleted:^(id _) {
        NSLog(@"Logged in successfully!");
    }];
}];

ボタンをタップしたときにログインを実行するようにします。

self.loginButton.rac_command = self.loginCommand;

またSignalsはタイマーや他のUIイベント、時間を通じて変化するものについてであれば何でも表現することができます。

非同期の処理にSignalを使う場合、Signalを変換したり連鎖したりすることで、さらに複雑な処理を構築することができます。

次の例では2つのネットワーク処理の両方が完了した時点にコンソールログを表示する処理になります。

+marge::Signalの配列を受け取り、全てのSignalが完了した時点で新しいRACSignalを返します。

-subscribeCompleted::Signalが完了した時点でBlockを実行します。

[[RACSignal 
    merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]] 
    subscribeCompleted:^{
        NSLog(@"They're both done!");
    }];

SignalはBlockのコールバックをネスティングする代わりに非同期処理を順次実行するために連鎖させることができます。 これはFuture,Promiseデザインパターンでよく用いられる方法に似ています。

次の例では、まずユーザーログインを行い、キャッシュされたメッセージをロードします。次にサーバーから残りのメッセージを受信し、全てが終わった時点でコンソールにログを表示します。

-flattenMap::新しいSignalを受ける度にBlockを実行し、受け取ったすべてのSignalを単一のSignalにマージして新しいRACSignalを返します。

[[[[client logInUser] //`-loginUser`は完了後にSignalを送る。 
    flattenMap:^(User *user) {
        // Return a signal that loads cached messages for the user.
        return [client loadCachedMessagesForUser:user];
    }]
    flattenMap:^(NSArray *messages) {
        // Return a signal that fetches any remaining messages.
        return [client fetchMessagesAfterMessage:messages.lastObject];
    }]
    subscribeNext:(NSArray *newMessages) {
        NSLog(@"New messages: %@", newMessages);
    } completed:^{
        NSLog(@"Fetched all messages.");
    }];

RACは非同期処理の結果をバインドすることも容易になります。

次の例では、ユーザーの画像がダウンロードされたらすぐにself.imageViewのimageにセットするための一方向のバインディングをつくります。

-deliverOn::他のキューに自分の仕事をさせる新しいSignalを作成します。この例ではメインスレッドに戻って、バックグラウンドキューに作業を移すために用いられています。

-map::この例ではフェッチされたユーザーのBlockを呼び出し、Blockから返された値を送信する新しいRACSignalを返します。

RAC(self.imageView, image) = [[[[client 
    fetchUserWithUsername:@"joshaber"]
    deliverOn:[RACScheduler scheduler]]
    map:^(User *user) {
        // Download the avatar (this is done on a background queue).
        return [[NSImage alloc] initWithContentsOfURL:user.avatarURL];
    }]
    // Now the assignment will be done on the main thread.
    deliverOn:RACScheduler.mainThreadScheduler];