BAD_ACCESS

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

同期用のクラス"SyncEngine"とCoreData周りの処理を追加する ーParseでローカルキャッシュ(3)

※tech.kayac.com Advent Calendar 2013に投稿したぼっちiOS開発者がParse(Parse.com)使った結果wwwwwwwww の補足記事になります。 量が多かったので全部で4つの記事に分けています。

Parse.com上に自分のプロジェクトを作成して、いくつかのデータを追加する。ーParseでローカルキャッシュ(1)

Xcode上でプロジェクトを作成し、"ParseSDK"と"AFNetworking"を導入する。ーParseでローカルキャッシュ(2)

同期用のクラス"SyncEngine"とCoreData周りの処理を追加する ーParseでローカルキャッシュ(3)

アプリ起動時に同期の処理を行う。ーParseでローカルキャッシュ(4)

※基本的には以下の記事の内容通りに進めています。自分のアプリに応じて適宜、書き換えていただければと思います。 How To Synchronize Core Data with a Web Service – Part 1 How To Synchronize Core Data with a Web Service – Part 2

"SyncEngine"と"CoreDataController"の追加

チュートリアル記事にて用意されている同期用のクラスSyncEngineをプロジェクトに追加します。

SyncEngine.h

SyncEngine.m

CoreDataController(CoreData周りの処理)を追加します。

CoreDataController.h

CoreDataController.m

あと忘れずに"CoreData.framework"もプロジェクトに追加してください。

"SyncEngine"の役割

SyncEngineの役割は文字通り同期をすることなのですが、SyncEngine内で行われている動きを大まかに把握しておこうと思います。

1.Register

- (void)registerNSManagedObjectClassToSync:(Class)aClass;

同期の対象となるクラスを登録します。

2.Start Sync

- (void)startSync;

同期を開始します。

3.Download Data (from Parse)

- (void)downloadDataForRegisteredObjects:(BOOL)useUpdatedAtDate;

Parseからデータをダウンロードします。また、CoreDataに保存されたデータから最新の更新日付を取り出して、

- (NSDate *)mostRecentUpdatedAtDateForEntityWithName:(NSString *)entityName;

最新の更新日付以降のものをリクエストするようになっています。

4.Application Cache (JSON to Disk)

- (void)writeJSONResponse:(id)response toDiskForClassWithName:(NSString *)className;

JSONで受けとったレスポンスをいったんファイルにキャッシュしています。このキャッシュをもとにCoreDataに書き込みにいきます。

5. Set Record into CoreData

- (void)processJSONDataRecordsIntoCoreData;

CoreDataへの書き込み処理が完了すると、キャッシュ情報を削除します。

6. Delete Cache

- (void)deleteJSONDataRecordsForClassWithName:(NSString *)className;

以上が"SyncEngine"の一連の流れになります。

CoreDataのモデル作成

Xcodeの新規ファイル追加からCore DataのData Model作成を行います。

新しいEnitiyを追加して(ここではOden)下図のようにAtributeも追加していきます。

 2013-12-07 23.04.53.png

これにて、同期処理までの準備は完了です。

次の記事:アプリ起動時に同期の処理を行う。ーParseでローカルキャッシュ(4)

アプリ起動時に同期の処理を行う。ーParseでローカルキャッシュ(4)

※tech.kayac.com Advent Calendar 2013に投稿したぼっちiOS開発者がParse(Parse.com)使った結果wwwwwwwww の補足記事になります。 量が多かったので全部で4つの記事に分けています。

Parse.com上に自分のプロジェクトを作成して、いくつかのデータを追加する。ーParseでローカルキャッシュ(1)

Xcode上でプロジェクトを作成し、"ParseSDK"と"AFNetworking"を導入する。ーParseでローカルキャッシュ(2)

同期用のクラス"SyncEngine"とCoreData周りの処理を追加する ーParseでローカルキャッシュ(3)

アプリ起動時に同期の処理を行う。ーParseでローカルキャッシュ(4)

※基本的には以下の記事の内容通りに進めています。自分のアプリに応じて適宜、書き換えていただければと思います。 How To Synchronize Core Data with a Web Service – Part 1 How To Synchronize Core Data with a Web Service – Part 2

AppDelegateで同期の処理を開始する

SyncEngineCoreDataControllerを組み込み終え、Odenデータモデルの作成を終えればようやく、同期の処理をテストすることができます。

AppDelegateに以下のように同期の準備を行います。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //Parse
    [Parse setApplicationId:YOUR_APP_ID
                  clientKey:YOUR_CLIENT_KEY];
    [PFAnalytics trackAppOpenedWithLaunchOptions:launchOptions];
    
    //Register Sync Class
    [[SyncEngine sharedEngine] registerNSManagedObjectClassToSync:[Oden class]];
    return YES;
}

アプリが起動した時点で同期処理を実行します。

- (void)applicationDidBecomeActive:(UIApplication *)application
{
     [[SyncEngine sharedEngine] startSync];
}

同期したデータをCoreDataから取得して表示させる。

アプリの起動時に同期したデータはCoreDataから取得することができるので、 呼び出すViewController上で、下記のようにデータを取得することで、毎回ParseのAPIを叩く必要がなくなります。

- (void)loadOdenFromCoreData {
    [self.managedObjectContext performBlockAndWait:^{
        [self.managedObjectContext reset];
        NSError *error = nil;
        NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"Oden"];
        [request setSortDescriptors:[NSArray arrayWithObject:
                                     [NSSortDescriptor sortDescriptorWithKey:@"createdAt" ascending:NO]]];
        self.odens = [self.managedObjectContext executeFetchRequest:request error:&error];
        [self.odensView reloadData];
    }];
}

無事におでんデータが表示できたようです。

 2013-12-07 20.57.28.png

id:marigomochaさんおでんの具ありがとう!

Sampleプロジェクトの公開

今回、チュートリアルに従い作成したサンプルプロジェクトは、ParseのフレームワークとApplication ID、Secretを除いたものをGitHubにアップしています。

GitHub:somtd / LocalCacheSample

Xcode上でプロジェクトを作成し、"ParseSDK"と"AFNetworking"を導入する。ーParseでローカルキャッシュ(2)

※tech.kayac.com Advent Calendar 2013に投稿したぼっちiOS開発者がParse(Parse.com)使った結果wwwwwwwww の補足記事になります。 量が多かったので全部で4つの記事に分けています。

Parse.com上に自分のプロジェクトを作成して、いくつかのデータを追加する。ーParseでローカルキャッシュ(1)

Xcode上でプロジェクトを作成し、"ParseSDK"と"AFNetworking"を導入する。ーParseでローカルキャッシュ(2)

同期用のクラス"SyncEngine"とCoreData周りの処理を追加する ーParseでローカルキャッシュ(3)

アプリ起動時に同期の処理を行う。ーParseでローカルキャッシュ(4)

※基本的には以下の記事の内容通りに進めています。自分のアプリに応じて適宜、書き換えていただければと思います。 How To Synchronize Core Data with a Web Service – Part 1 How To Synchronize Core Data with a Web Service – Part 2

ParseSDKのダウンロードと導入

公式のQuick Start Guideに画面のスクリーンショットと詳細な手順が示されていますので、そちらを参照するほうが確実でしょう。

AFNetworkingの導入

今回、AFNetworkingが必要となるのはParseのAPIをRESTで利用するためです。

AFNetworking / AFNetworking

ダウンロードまたはクローンが完了したら、作成したプロジェクトに組み込みましょう。

CocoaPodsを使っている方はPodfileに下記を書き足してinstallして下さい。

platform :ios, '7.0'
pod "AFNetworking", "~> 2.0"

AFNetworkingを使ったParseのAPIクライアントはこんな感じになります。

ここで注意が必要な点は、REST APIを利用する場合のKeyはClient KeyではなくREST API Keyのほうを使うということです。

プロジェクトの構成

ここまで正しく進んでいればプロジェクトは下記のようになっているはずです。

REST APIを使ってParseを利用する準備はできました。 次はデータの取得と同期部分を見ていきます。

Parse.com上に自分のプロジェクトを作成して、いくつかのデータを追加する。ーParseでローカルキャッシュ(1)

※tech.kayac.com Advent Calendar 2013に投稿したぼっちiOS開発者がParse(Parse.com)使った結果wwwwwwwww の補足記事になります。 量が多かったので全部で4つの記事に分けています。

Parse.com上に自分のプロジェクトを作成して、いくつかのデータを追加する。ーParseでローカルキャッシュ(1)

Xcode上でプロジェクトを作成し、"ParseSDK"と"AFNetworking"を導入する。ーParseでローカルキャッシュ(2)

同期用のクラス"SyncEngine"とCoreData周りの処理を追加する ーParseでローカルキャッシュ(3)

アプリ起動時に同期の処理を行う。ーParseでローカルキャッシュ(4)

※基本的には以下の記事の内容通りに進めています。自分のアプリに応じて適宜、書き換えていただければと思います。 How To Synchronize Core Data with a Web Service – Part 1 How To Synchronize Core Data with a Web Service – Part 2

Parse上に新しいアプリを作成

まず、Parse.comにアクセスしてアプリを新規作成します。

DataBrowserを選択して早速クラスを作ります。

Add New Classを選択して、クラスを作成します。

Columnの追加

さきほど作成したクラスにColumnを追加していきます。 ここではname image detailの3つのColumnを追加します。

データの入力

Columnの作成が完了したら、データをいくつか入れてみます。 データの入力が完了すると下記のようになっているはずです。

つづいてXcodeでプロジェクトを作成します。

#6 複数の音源から再生したい音源を選ぶーC4でAudio Playerをつくる(4)

この記事は「一人アドベントカレンダー2013〜Lonely Advent Calender 2013〜」の6日目の記事です。

ようやく「一人アドベントカレンダーやってるんですねw」と話しかけるようになりました。 前回はC4Sampleの音源の再生・停止をコントロールする機能を実装しました。かなりAudio Playerらしくなってきたのではないでしょうか。 今回はあらかじめプロジェクトに組み込んだ複数の音源を選んで再生する機能を実装したいと思います。

音源を選択するためのビューを表示する

これまで単一のビューでAudio Playerを実装してきましたが、音源の選択はさすがに別のビューを使って行いたいと思います。 C4ではいったいどのように新しい画面を追加するのかを調べてつつ、実装していきます。

音源選択画面を表示するためのボタン配置

まずはC4WorkSpace側に音源選択画面を表示するためのボタンを配置します。 例のごとく、前回のサンプルに追記していきます。

-(void)setupUI
{
    //再生・停止ボタン
    playbackButton = [C4Button buttonWithType:CUSTOM];
    playbackButton.frame = CGRectMake(0, 0, 28, 28);
    [playbackButton setBackgroundImage:[C4Image imageNamed:@"icon_play.png"] forState:NORMAL];
    [playbackButton setBackgroundImage:[C4Image imageNamed:@"icon_stop.png"] forState:SELECTED];
    
    playbackButton.center = self.canvas.center;
    [playbackButton runMethod:@"onPlaybackButton" target:self forEvent:TOUCHUPINSIDE];
    [self.canvas addSubview:playbackButton];
    
    //楽曲リスト表示ボタン <---追加
    playlistButton = [C4Button buttonWithType:CUSTOM];
    [playlistButton setBackgroundImage:[C4Image imageNamed:@"icon_list"] forState:NORMAL];
    playlistButton.frame = CGRectMake(self.canvas.width - 40, 30, 20, 25);
    [playlistButton runMethod:@"onPlaylistButton" target:self forEvent:TOUCHUPINSIDE];
    [self.canvas addSubview:playlistButton];

}

ボタンをタップした後の処理も用意しておきます。

-(void) onPlaylistButton
{
    
}

音源選択画面を作成する

ではC4において新たに画面を追加する場合はどうすればいいでしょうか。 画面の切り替えに関してはこの公式ページが詳しいです。 MultiCanvas : C4

こちらの内容を見てみると、遷移先のビュー(Canvas)の初期化の仕方に特徴があるようですが、それはまた別の機会に。

新たに以下の3つのファイルを追加します。 追加するときにsubクラス名をC4CanvasControllerを選択してください。

#5 C4で音楽の再生・停止をコントロールするーC4でAudio Playerをつくる(3)

この記事は「一人アドベントカレンダー2013〜Lonely Advent Calender 2013〜」の5日目の記事です。

前回はC4Sampleの音源の再生位置をシークする機能、計測した音源のレベルにあわせて円の大きさを変化させる機能を実装しました。 非常に簡単な実装で音楽にあわせたオブジェクトの操作ができることがお分かりいただけたと思います。

今回は音源の状態を取得して再生・停止をコントロールする機能をを実装したいと思います。

再生・停止ボタンの追加

前回のコードに手を加えていきます。

@implementation C4WorkSpace {
    C4Sample *sample;
    C4Slider *slider;
    
    //Level Circle
    C4Shape *levelCircle;
    
    //Masked Image
    C4Image *maskedImage;
    
    //Timer
    C4Timer *meterUpdateTimer;

    C4Button *playbackButton; <---追加
}

playbackButtonを追加します。

-(void)setup {
    sample = [C4Sample sampleNamed:@"C4Loop.aif"];
    sample.loops = YES;
    
    //metering enable
    sample.meteringEnabled = YES;
    
//    [sample play]; <---消す
    
    [self setupCircle];
    
    [self setupSlider];
    
    [self setupUI]; <---追加
    
    meterUpdateTimer = [C4Timer timerWithInterval:1/30.0f target:self method:@"updateMeters" repeats:YES];
    [meterUpdateTimer start];
}

setupメソッドでは[sample play]を消し、新たに[self setupUI]を追加します.

setupUIはこのような感じになります。前回作ったUIに再生・停止用のボタンを被せます。

-(void)setupUI
{
    //再生・停止ボタン
    playbackButton = [C4Button buttonWithType:CUSTOM];
    playbackButton.frame = CGRectMake(0, 0, 28, 28);
    [playbackButton setBackgroundImage:[C4Image imageNamed:@"icon_play.png"] forState:NORMAL];
    [playbackButton setBackgroundImage:[C4Image imageNamed:@"icon_stop.png"] forState:SELECTED];
    
    playbackButton.center = self.canvas.center;
    [playbackButton runMethod:@"onPlaybackButton" target:self forEvent:TOUCHUPINSIDE];
    [self.canvas addSubview:playbackButton];
}

これで再生・停止ボタンが追加されました。

音源の再生状態の取得playing

ボタンが押されることで再生・停止が正しく切り替わるように、onPlaybackButtonを実装します。これは先ほど追加したボタンのタッチイベントとして定義しています。

-(void) onPlaybackButton
{
    if (sample.isPlaying) {
        [sample pause];
        playbackButton.selected = FALSE;
    } else {
        [sample play];
        playbackButton.selected = TRUE;
    }
}

このようにC4Sampleのplayingというプロパティを使うことで音源の再生状態を取得して再生・停止を正しく切り替えることができるようになりました!

次回は複数の音源を切り替えて再生する方法を実装する予定です!

#4 C4で音楽にあわせてオブジェクトを変化させるーC4でAudio Playerをつくる(2)

この記事は「一人アドベントカレンダー2013〜Lonely Advent Calender 2013〜」の4日目の記事です。

前回はC4Sampleを使って音源のrate(再生速度) pan(左右に振る) volume(音量) で遊んでみました。 非常に簡単に音源をいじることができることがお分かりいただけたのではないでしょうか。

今回は、音楽にあわせてビジュアル面を変化させる方法を実装していこうと思います。 実装する機能は以下の2つ、

1.音楽の再生位置をSeekするスライドバーの実装

2.音楽のレベルにあわせて大きさの変わる円の実装

今回もSampleプロジェクトを用意しています。

1.音楽の再生位置をSeekするスライドバーの実装

参考:C4Sample: Current Time (Both)

@implementation C4WorkSpace {
    C4Sample *sample;
    C4Slider *slider;
...

音源とスライダーのメンバ変数を定義します。

-(void)setup {
    sample = [C4Sample sampleNamed:@"C4Loop.aif"];
    sample.loops = YES;
    [sample play];
    
    [self setupSlider];
}

setupメソッドで音源の初期化、スライダーのセットアップを行います。

-(void)setupSlider {
    slider = [C4Slider slider:CGRectMake(0, 0, 368, 44)];
    slider.center = CGPointMake(self.canvas.center.x, self.canvas.height - 100);
    
    [slider runMethod:@"changeCurrentTime:" target:self forEvent:VALUECHANGED];
    
    [self.canvas addSubview:slider];
    [self updateSlider];
}

スライダーを初期化して、最後にupdateSliderメソッドを呼んでいます。

-(void)updateSlider {
    slider.value = sample.currentTime / sample.duration;
    [self runMethod:@"updateSlider" afterDelay:0.01f];
}

updateSliderは0.01秒ごとに呼ばれることになり、その時点での再生位置をスライダーの位置にあらわしています。

-(void)changeCurrentTime:(C4Slider *)aSlider {
    sample.currentTime = aSlider.value * sample.duration;
  
}

スライダーの位置を変化させると、再生位置をスライダーの位置に合わせて変えるメソッドが呼ばれます。

ビルドしてみると正しく再生位置をスライダーでコントロールできるかと思います。

2.音楽のレベルにあわせて大きさの変わる円の実装

@implementation C4WorkSpace {
    C4Sample *sample;
    C4Slider *slider;
    
    //Level Circle
    C4Shape *levelCircle;
    
    //Masked Image
    C4Image *maskedImage;
    
    //Timer
    C4Timer *meterUpdateTimer;
}

音源のレベルに応じて大きくなったり小さくなったりする円、その円でマスクされる画像、音源のレベルを測るためのタイマーをメンバ変数に定義します。

-(void)setup {
    sample = [C4Sample sampleNamed:@"C4Loop.aif"];
    sample.loops = YES;
    
    //metering enable
    sample.meteringEnabled = YES;
    
    [sample play];
    
    [self setupCircle];
    
    [self setupSlider];
    
    meterUpdateTimer = [C4Timer timerWithInterval:1/30.0f target:self method:@"updateMeters" repeats:YES];
    [meterUpdateTimer start];
}

音源はレベルを測定することを有効化するためにmeteringEnabled=YESとしておきます。 あとは円のセットアップと測定タイマーを初期化してスタートします。

-(void)setupCircle
{
    maskedImage = [C4Image imageNamed:@"C4Sky.png"];
    maskedImage.width = self.canvas.width;
    maskedImage.center = self.canvas.center;
    
    levelCircle = [C4Shape ellipse:CGRectMake(0, 0, 240, 240)];
    levelCircle.center = CGPointMake(maskedImage.center.x, maskedImage.height/2) ;
    maskedImage.mask = levelCircle;
    [self.canvas addImage:maskedImage];
}

円のセットアップを行う際に、画像のマスクとして円を設定しておきます。

-(void)updateMeters {
    [sample updateMeters];
    
    CGFloat avg = [C4Math pow:10 raisedTo:0.05 * [sample averagePowerForChannel:0]];
    float scale;
    scale = 0.8 + (avg * 0.3);
    
    [UIView animateWithDuration:1/30.0f animations:^{
        CGAffineTransform myTransform = CGAffineTransformMakeScale(scale, scale);
        levelCircle.transform = myTransform;
        maskedImage.center = self.canvas.center;
        maskedImage.mask = levelCircle;
    }];

}

最後にタイマーの更新によって音源のレベルを測定し、円のスケールを変化させるようにします。

これで、音源の再生位置を自由にSeekして、音源のレベルにあわせて円が大きくなったり小さくなったりするAudio Playerができました!

ソースコードこちら!