2016年04月26日

[iOS] リモコン付きイヤフォン(ヘッドフォン)のイベントを監視する

iOS担当のエモトです.先日,大洗に行ってきました.この場所はあのシーンで見た!と街を見て回ってきました.その旅行のあと,映画館にいったのですが,このシーンはあの場所で見た!と,不思議な円環の理に陥ってしまいました.

音声や音楽系アプリを取り扱う場合,イヤフォン制御が大切になります.

(a) イヤフォンを確認する
端末にイヤフォンがつながっているかは以下のようにすれば調べることができます.
#import < AVFoundation/AVFoundation.h >

- (BOOL)hasHeadphone{
    BOOL hasHeadphones = false;
    AVAudioSessionRouteDescription* route = [[AVAudioSession sharedInstance] currentRoute];
    for (AVAudioSessionPortDescription* desc in [route outputs]){
        NSString *portType = desc.portType;
        if ([portType isEqualToString:AVAudioSessionPortHeadphones]){
            || [portType isEqualToString:AVAudioSessionPortBluetoothA2DP]){
            hasHeadphones = true;
        }
    }
    return hasHeadphones;
}
しかしながら,その時だけの状態が取得できても機能不足です.イヤフォンが端末から差し抜きされたときのイベントは以下のようにすれば監視することができます.
// 監視を始める
- (void)startObservation{
    [[AVAudioSession sharedInstance] setActive:true error:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(audioSessionRouteChanged:)
                                                 name:AVAudioSessionRouteChangeNotification
                                               object:nil];
}

// 監視を終える
- (void)removeObservation{
    [[AVAudioSession sharedInstance] setActive:false error:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self
                                                    name:AVAudioSessionRouteChangeNotification
                                                  object:nil];
}

// ヘッドフォンが差し向きされた
- (void)audioSessionRouteChanged:(NSNotification*)notification
{
}

(b) リモコンを確認する
リモコン付きイヤフォンの再生や停止などのイベントは MPRemoteCommandCenter を使えば監視することができます.
#import < MediaPlayer/MediaPlayer.h >

// 監視を始める
-(void)addRemoteCommandCenter{
    MPRemoteCommandCenter *rcc = [MPRemoteCommandCenter sharedCommandCenter];
    [rcc.togglePlayPauseCommand addTarget:self action:@selector(toggle:)];
    [rcc.playCommand addTarget:self action:@selector(play:)];
    [rcc.pauseCommand addTarget:self action:@selector(pause:)];
    [rcc.nextTrackCommand addTarget:self action:@selector(nextTrack:)];
    [rcc.previousTrackCommand addTarget:self action:@selector(prevTrack:)];	
}

// 監視を終える
-(void)removeRemoteCommandCenter{
    MPRemoteCommandCenter *rcc = [MPRemoteCommandCenter sharedCommandCenter];
    [rcc.togglePlayPauseCommand removeTarget:self];
    [rcc.playCommand removeTarget:self];
    [rcc.pauseCommand removeTarget:self];
    [rcc.nextTrackCommand removeTarget:self];
    [rcc.previousTrackCommand removeTarget:self];
}

// 再生・停止のトグルボタンが押された(他のメソッドは省略)
- (void)toggle:(MPRemoteCommandEvent*)event{
    // 何かしらの処理
}
ただし,MPRemoteCommandCenterはiOS7.1から実装されました.iOS7.0も対象する場合はremoteControlReceivedWithEventを使います(ドキュメントではMPRemoteCommandCenterの使用を推奨しています).個人的に,後者はcanBecomeFirstResponderを上書きしないとならないので,実装箇所を限定してしまい,ちょっと使いにくいなと思ってます.
// 監視を始める
- (void)hogehoge{
    id obj = NSClassFromString(@"MPRemoteCommandCenter");
    if (obj) {
        // MPRemoteCommandCenterが使える
        [self addRemoteCommandCenter];
    }else{
        [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
        [self becomeFirstResponder];

        // 監視を終える時は
        // [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
        // [self resignFirstResponder];
    }
}

// trueに上書きする
- (BOOL)canBecomeFirstResponder{
    return true;
}

// イベントが起こった
- (void)remoteControlReceivedWithEvent:(UIEvent*)receivedEvent{
    if (receivedEvent.type == UIEventTypeRemoteControl) {
        UIEventSubtype subtype = receivedEvent.subtype;
        if (subtype == UIEventSubtypeRemoteControlPlay){
        }else if (subtype == UIEventSubtypeRemoteControlPause){
        }else if (subtype == UIEventSubtypeRemoteControlTogglePlayPause) {
        }else if (subtype == UIEventSubtypeRemoteControlPreviousTrack){
        }else if (subtype == UIEventSubtypeRemoteControlNextTrack){
        }
    }
}
posted by Seesaa京都スタッフ at 12:52| Comment(0) | iOS | このブログの読者になる | 更新情報をチェックする

2016年04月11日

ArrayAdapterのコンストラクタのResourceIDの役割

こんにちは、Androidエンジニアの外山です。

今回はAndroidのListViewなどにセットするArrayAdapterのコンストラクタで指定するResourceIDの役割について調べてみました。

ArrayAdapterのコンストラクタ

ArrayAdapterのコンストラクタは以下のようになっています。
public ArrayAdapter(Context context, @LayoutRes int resource, @IdRes int textViewResourceId,
            @NonNull List<T> objects) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        mResource = mDropDownResource = resource;
        mObjects = objects;
        mFieldId = textViewResourceId;
}

ResourceIDの使用箇所

mResourceとmFieldIdはgetViewでViewインスタンスを作成する時に使用されています。

mResourceはViewのレイアウトファイルを参照する際に、mFieldIdはレイアウトファイル内のTextViewを指定する際に使用されているようです(指定しなかった場合は生成したView自身をTextViewとして扱う)。

public View getView(int position, View convertView, ViewGroup parent) {
    return createViewFromResource(mInflater, position, convertView, parent, mResource);
}

private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
        ViewGroup parent, int resource) {
    View view;
    TextView text;
    if (convertView == null) {
        view = inflater.inflate(resource, parent, false);
    } else {
        view = convertView;
    }
    try {
        if (mFieldId == 0) {
            //  If no custom field is assigned, assume the whole resource is a TextView
            text = (TextView) view;
        } else {
            //  Otherwise, find the TextView field within the layout
            text = (TextView) view.findViewById(mFieldId);
        }
    } catch (ClassCastException e) {
        Log.e("ArrayAdapter", "You must supply a resource ID for a TextView");
        throw new IllegalStateException(
                "ArrayAdapter requires the resource ID to be a TextView", e);
    }
    T item = getItem(position);
    if (item instanceof CharSequence) {
        text.setText((CharSequence)item);
    } else {
        text.setText(item.toString());
    }
    return view;
}

mDropDownResourceも同じような感じになっています。



まとめ

getViewをオーバーライドしてsuperメソッドを呼ばない場合は、引数で渡したResourceIDが使われることはないので`-1`など適当な値を渡しておけば大丈夫である。

リスト内のアイテムで一つのTextViewにのみ値をセットする場合はResourceIdを指定することでサブクラスを作成せずにAdapterを作成できるようだ。


RecyclerViewの登場によって使う機会が減ってきたListViewやGridViewですが簡単なリストを作成する際には手軽に使えていいですね。

posted by Seesaa京都スタッフ at 18:53| Comment(0) | Android | このブログの読者になる | 更新情報をチェックする

2015年10月30日

[iOS] FastlaneでゴニョゴニョしてからEMLauncherにアップする

iOSアプリ開発担当の加島です。最近のリズムゲームアプリはすごいですね!(3Dアニメとか)ただ、私は現実のコインを使わないことにしています。今のところ。
さて、今回は巷で話題(となってから少し時間が経ちましたがここ最近僕の中で話題)のFastlaneを使ってみました。基本的な使い方は公式ガイドである以下をご参照ください。

iOSアプリの継続的デリバリーに便利なfastlaneのご紹介 - Qiita
http://qiita.com/gin0606/items/162d756dfda7b84e97d4

iOSアプリのリリースフロー自動化ツールfastlaneのmeetupに通訳で参加しました - Mercari Engineering
http://tech.mercari.com/entry/2015/07/13/143000

このFastlaneのビルドツールであるgymですが、直接ビルド設定を指定することができるようになっています。例えばlane内で

gym(
scheme: 'Dev',
configuration: 'Debug',
)

と指定すれば、DevSchemeのDebugConfigurationでビルドしてくれます!

以下、奮闘の記録です。

続きを読む
posted by Seesaa京都スタッフ at 00:00| Comment(0) | iOS | このブログの読者になる | 更新情報をチェックする