2013年10月16日水曜日

iosでのappstoreの国設定切り替え

ios7対応、2013.10.15現在

・設定アプリ>一般>言語環境から、言語と書式(国)を切り替える
・設定アプリ>iTunes Store/App Storeからその国に設定しているAppleIDでSign in
※初回はレビューしてクレカ情報とか入力してください言われるけど、alertのレビューをおした後に表示される画面でAppStore国を設定して「次へ」で次画面へ行ったあとに
キャンセルすればOK

2013年9月12日木曜日

cocos2d-xでCocosDenshion::SimpleAudioEngineのplayEffect()がresume時(background to foreground)に鳴らない問題対応

BGMは問題ないのだが、効果音(playEffect)が一度backgroundに移行後、再度foregroundに移行した時に鳴らない場合がある。
target os: iOS6.1
audio format: .caf
cocos2dx ver.: 2.1rc0-x-2.1.4

SimpleAudioEngine::playEffectを追ってみたら[CocosDenshion playSound:sourceGroupId:pitch:pan:gain:loop:]でも特にエラーが起きておらず、正常にOpenALのalSourcePlay()が呼び出されているようだ。loadも問題ないし原因不明。

ググったら公式フォーラムでちらほら報告があるだけ。
Sound FX not playing after a while.
http://www.cocos2d-x.org/boards/6/topics/3153
http://www.cocos2d-x.org/issues/2512

とりあえずの対処としてBackgroundに移行する時にend()して、Foregroundにresumeする時にアプリ起動時にpreloadすることで音を鳴らす事ができる(ただしpause~resumeではないので、途中から鳴らす事はできない。)
bool AppDelegate::applicationDidFinishLaunching()
{
    // some code ...

    CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("press_button.caf");
}
void AppDelegate::applicationDidEnterBackground()
{
    CCDirector::sharedDirector()->pause();
    CCDirector::sharedDirector()->stopAnimation();

    // end
    SimpleAudioEngine::sharedEngine()->end();
}

// this function will be called when the app is active again
void AppDelegate::applicationWillEnterForeground()
{
    CCDirector::sharedDirector()->resume();
    CCDirector::sharedDirector()->startAnimation();

    // preloadEffect
    CocosDenshion::SimpleAudioEngine::sharedEngine()->preloadEffect("press_button.caf");
}

ゲーム系で音が鳴らなくなるって結構致命的な気がするんだけど、v2.1.4stableとか、v3.xでは直ってるのかな。不明。

2013年9月2日月曜日

Google Analytics iOS SDK v2.0beta から v3.0への移行、変更点

Automatic Screen Measurement - 自動的な画面計測

// v2 -------
// SomeViewController.h
#import "GAITrackedViewController.h"
@interface SomeViewController : GAITrackedViewController
@end

// SomeViewController.m
- (void)viewDidAppear:(BOOL)animated {
  [super viewDidAppear:animated];
  self.trackedViewName = @"Some Screen";
}

// v3 -------
// change "self.trackedViewName" to "self.screenName"


Manual Screen Measurement - 手動での画面計測

// v2 -------
[[[GAI sharedInstance] defaultTracker] sendView:view]; // or trackView:view

// v3 -------
#import "GAIFields.h"
#import "GAIDictionaryBuilder.h"
[[[GAI sharedInstance] defaultTracker] set:kGAIScreenName value:view];
[[[GAI sharedInstance] defaultTracker] send:[[GAIDictionaryBuilder createAppView] build]];


Event Tracking - イベントのトラッキング

// v2 -------
[[[GAI sharedInstance] defaultTracker] sendEventWithCategory:category withAction:action withLabel:label withValue:value];

// v3 -------
#import "GAIDictionaryBuilder.h"
[[[GAI sharedInstance] defaultTracker] send:[[GAIDictionaryBuilder createEventWithCategory:category action:action label:label value:value] build]];

初期化時は[GAI sharedInstance].debugがなくなったくらい。

2013年7月19日金曜日

cocos2dxでModalなAlertView(Dialog)

coco2dxでAlertViewのような仕組みが見つからなかった。CCMessageBoxというのがあるがあくまで簡易的なものでボタンを2つは置けないようで。
[Cocos2d-x] モーダルレイヤを作成するを参考にして作ってみた。細かいところは未検証。とりあえず動くかなという状態。


FSAlertLayer.cpp

#include "FSAlertLayer.h"

USING_NS_CC;

// ceate with 2 buttons
FSAlertLayer* FSAlertLayer::create(const char* message, CCObject *target, SEL_CallFuncN okSelector, SEL_CallFuncN ngSelector)
{
    FSAlertLayer* layer = FSAlertLayer::create();
    layer->setStyle(message, target, okSelector, ngSelector);
    return layer;
}


// create with only 1 button
FSAlertLayer* FSAlertLayer::create(const char *message, cocos2d::CCObject *target, cocos2d::SEL_CallFuncN okSelector)
{
    return FSAlertLayer::create(message, target, okSelector, NULL);
}


bool FSAlertLayer::init()
{
    // half clear black bgcolor
    if (! CCLayerColor::initWithColor(ccc4(0, 0, 0, 100))) {
        return false;
    }
    
    CCDirector* pDirector = CCDirector::sharedDirector();
    pDirector->getTouchDispatcher()->addTargetedDelegate(this, kCCMenuHandlerPriority, true);
    
    CCSize vsize = pDirector->getVisibleSize();
    
    // bg
    CCSprite* frame = CCSprite::create("bg_alert.png");
    frame->setTag(FSAlertLayerTagFrame);
    frame->setPosition(ccp(vsize.width / 2, vsize.height / 2));
    this->addChild(frame);
    
    return true;
}


void FSAlertLayer::setStyle(const char* message, cocos2d::CCObject *target, cocos2d::SEL_CallFuncN okSelector, cocos2d::SEL_CallFuncN ngSelector)
{
    this->target        = target;
    this->okSelector    = okSelector;
    this->ngSelector    = ngSelector;
    
    CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
    
    // message
    CCLabelTTF *pMsg = CCLabelTTF::create(message, "", 16);
    pMsg->setPosition(ccp(visibleSize.width / 2, visibleSize.height / 2));
    this->addChild(pMsg);
    
    // OK Button
    CCMenuItemImage *pOKItem = CCMenuItemImage::create("btn_ok.png", "btn_ok.png", this, menu_selector(FSAlertLayer::menuAlertOK));
    
    int btnHeight = visibleSize.height / 2 - this->getChildByTag(FSAlertLayerTagFrame)->getContentSize().height / 2 + pOKItem->getContentSize().height / 2 + 10;
    
    pOKItem->setTag(FSAlertLayerTagOK);
    pOKItem->setPosition(ccp(visibleSize.width / 2, btnHeight));
    
    CCMenu* pMenu = CCMenu::create(pOKItem, NULL);
    pMenu->setPosition(CCPointZero);
    pMenu->setTag(FSAlertLayerTagMenu);
    this->addChild(pMenu);
    
    // set as 2 buttons
    if (ngSelector) {
        pOKItem->setPosition(ccp(visibleSize.width / 2 - pOKItem->getContentSize().width / 2, btnHeight));
        
        // NG btn
        CCMenuItemImage* pNGItem = CCMenuItemImage::create("btn_back.png", "btn_back.png", this, menu_selector(FSAlertLayer::menuAlertClose));
        pNGItem->setTag(FSAlertLayerTagNG);
        pNGItem->setPosition(ccp(visibleSize.width / 2 + pOKItem->getContentSize().width / 2, btnHeight));
        pMenu->addChild(pNGItem);
    }
}


// touch event disable other layers
bool FSAlertLayer::ccTouchBegan(CCTouch* touch, CCEvent* event)
{
    return true;
}


void FSAlertLayer::menuAlertOK(CCObject* pSender)
{
    CCLOG("OK");
    
    this->menuAlertClose(pSender);
    
    if (this->target && this->okSelector) {
        (this->target->*this->okSelector)(this);
    }
}


void FSAlertLayer::menuAlertClose(cocos2d::CCObject *pSender)
{
    this->removeFromParentAndCleanup(true);
    
    CCDirector* pDirector = CCDirector::sharedDirector();
    pDirector->getTouchDispatcher()->removeDelegate(this);
    
    if (((CCNode*)pSender)->getTag() == FSAlertLayerTagNG && this->target && this->ngSelector) {
        (this->target->*this->ngSelector)(this);
    }
}

FSAlertLayer.h

#ifndef __xxx__FSAlertLayer__
#define __xxx__FSAlertLayer__

#include "cocos2d.h"

#define FSAlertLayerTagMenu     101
#define FSAlertLayerTagOK       102
#define FSAlertLayerTagNG       103
#define FSAlertLayerTagFrame    110


class FSAlertLayer : public cocos2d::CCLayerColor
{
public:
    static FSAlertLayer* create(const char* message, CCObject* target, cocos2d::SEL_CallFuncN okSelector, cocos2d::SEL_CallFuncN ngSelector);
    static FSAlertLayer* create(const char* message, CCObject* target, cocos2d::SEL_CallFuncN okSelector);
    
    virtual bool init();
    void setStyle(const char* message, CCObject* target, cocos2d::SEL_CallFuncN okSelector, cocos2d::SEL_CallFuncN ngSelector);
    virtual bool ccTouchBegan(cocos2d::CCTouch *pTouch, cocos2d::CCEvent *pEvent);
    
    void menuAlertOK(CCObject* pSender);
    void menuAlertClose(CCObject* pSender);
    
    CREATE_FUNC(FSAlertLayer);
    
private:
    cocos2d::CCObject* target;
    cocos2d::SEL_CallFuncN okSelector;
    cocos2d::SEL_CallFuncN ngSelector;
};

#endif /* defined(__xxx__FSAlertLayer__) */


利用側

// call from Scene, Layer, etc
FSAlertLayer *alertLayer = FSAlertLayer::create("XXXを使用します。よろしいですか", this, callfuncN_selector(ThisScene::menuOKCallback), callfuncN_selector(ThisScene::menuNGCallback));
// 適当に高い数値を入れて最上部に表示させる
this->addChild(alertLayer, 100001);

2013年6月21日金曜日

xcode更新後で新規に作成したファイルでauto completeやsyntax colorが働かない

Xcodeを更新したらsyntax coloring(code highlight)やauto-complete(code hint)が動かない、作業用インデックスに追加されていないぽい。

まず以下を試す。organizerでderiveddataを削除、他ユーザのworkspace情報なども削除。
http://stackoverflow.com/a/8165886

それでもダメで、しょうがなくProject, Targetの情報をひとつずつチェックしてったら、どうやらProject->Targets->Build Phases->Compile Sourcesに新しいファイルが含まれていないのが原因だったようだ。新規ファイル作成時にこれまではチェックされていたはずのTargetsがはずれていた。

これをチェックしてファイルを新規作成ことで問題は解消された(既存ファイルの場合はCompile Sourcesに直接mファイルを追加)




とても無駄な時間だった…。

2013年6月10日月曜日

勉強会ログ: 6月7日(金) (Twitter) API 勉強会/API1.0告別式 @恵比寿 @engineyard_jp #apihack

2013.6.7
第9回

次回以降の予定
・PaaS API
・ヘルスケアデバイス系API Nicke+とか
・地理系API GEO系
・決済系API


■twitter API1.1
・Twitterエコシステムの定義
 ・いわゆるtwitterクライアントは自社で出すので作って欲しくない
  ・”クライアント”の定量的な判断基準はない
  ・今後新規のいわゆるクライアントはもう無理(ユーザ10万に限るなど…)
  ・ポイントはtwitterにとって有益になるかどうか
・ディベロッパ利用規約変更
 ・表示形式の指定がrequiredに
 ・タイムラインのレンダリングは、twitterのタイムラインであることがわかるように
  ・twitterの鳥を配置
・API1.0 -> 1.1
 ・endpointの変更
  ・単純なURLプリフィクスの変更
  ・xml廃止、jsonのみに
  ・廃止されるエンドポイント(public_timeline, retweet関連など)
 ・レートリミット方式変更
  ・1.0:1アカウント350回/1時間、1.1:1エンドポイント15回/15分・多く使われるものは180回/15分 (いずれもOAuth必須)
 ・OAuth必須に
  ・1.0:一部は150回/1時間でOAuthなしで呼び出し可能、1.1:全て必須

期限
・1.0は、3/5>6/11に廃止(at SF time)

移行
・twitter4j(Java):バージョン3.0.xに切り替え>コンパイルエラーをつぶす
・他言語:エンドポイントのバージョン部分を1から1.1にかえてエラーをつぶす


■Twitter Cards
・tweetにメタ情報、付加情報を埋め込む為の仕様
・140文字以外の文章として出してくれる
・例:webページURLをはるとそのページのアイコンが自動で表示
・例:SlideShare、tweet内にスライド

方法
・webページにmetaタグで埋め込む
・twitter:card, summary photo, player

Twitter CardsとOpen Graph
・twitter cardsはopen graphがベースになっており、だいたい一緒
・基本のメタ情報(url, title, description, image...)はopen graphを設定しておけばtwitter cardsとして表示される(twitter:cardは必要)

事前申請が必要
・open graphは普通は不要(Facebookなど)
・twitterの申請ページからフォーム入力して申し込み
・期間は数日から2週間くらい(申請確認メールに返信すればチケット作られるのでやってくれるはず)
・何故必要なのか
 ・tweetが多いので全URLに反応してクロールするのは高コストなため事前申請(ほぼ登録みたいな感じ?)にしている

>webを展開するなら、Twitter Cardsは対応しておいた方が良さそう。tweetへの追加情報の表示は魅力的


■なんとなくわかってきたtwitter APIとのつきあい方(小宮さん)
・twitter4jを使ってツイートをDBに蓄積するにあたって、実際のなま情報
・仕様と違うとか
・注意するポイント6点

1)アプリケーションの登録に注意する
・WebSite:twitter.comドメインとかいれといてAPIをはげしくつかうと、アプリケーションのstatusがsuspendedになって、アプリケーションも削除できずどうにもできなくなる

2)絵文字に負けない
・絵文字(UTF-8外字)を含むテキストをVARCHAR(140)に保存すると文字数オーバ
・絵文字を保存する際は、MySQLならcharsetをutfmb4にする必要があり、絵文字1文字につき4文字分消費するので、560にする必要がある

3)NULL文字に負けない
・時々tweetにNULL文字が紛れ込むことがある
・NULLは除外する方がよい

4)NFD/NFKDに負けない
・UNICODE正規化:発音記号等を1バイト文字2文字で表現するような分解

5)ユーザが常に存在するとは限らない
・search/tweetsのstatus["user"]が存在しないことがある

6)screen_nameが15文字であるはずがない
・サイトには15文字と書いてあるが、過去に20文字許されてた時期があった。


■家族フォト
MVP(mininum valuable product)
・最小構成でサービスをリリースする
・その後仮説と検証を経てよりよいものにつくってく

ベネッセとデジタルガレージの協業
・2ヶ月でweb, iOS, Android向けアプリを出さなきゃなかった
・APIは自社json(HTTPS)、bgはappEngine
・appEngineではSSLを使う際、金払った実績がないと利用できない制約がある
・appEngine:dbモジュールからndbモジュールに変更:modelで定義してるものは自動でmemcacheでキャッシュを利用してくれる
・スマホアプリは基本APIをたたく。
・PhoneGapを使った(iphone4あたり少しもっさりだが、一般のユーザはそんな気にしない)
・sharp端末で不具合頻発、あとandroidでは画像の保存ディレクトリが端末によって違ったりした
・phonegapはhtml+css+jsで開発しやすかった

仮説と検証
・DL当日しか使ってないユーザは30〜40%
・週1以上起動ユーザは約半数


■Acitivity body (fitbit API)(朝倉さん)
・アクティビティトラッカー

fitbit feature
・歩数
・移動距離
・消費カロリー
・上位機種は、階段上り下りや睡眠時間なども記録可能

Fitbit API(json or xml)
・userinfo
・body measurements(体組織:体重、慎重)
・Activities(歩数とか)
・利用
 ・要ディベロッパー登録

Third party libs
・ruby, php, .net, java


■飯テロのススメ(美馬さん)
・twitter日本語で、一番つぶやかれる時間は0時
・おやすみが一番いわれるのは23:50分くらい


■Application-only authentication
・ユーザの認可を必要とせずに、twitter APIにアクセスできるようになる認可方式
・tweet投稿や、フォロー等の操作に依存するAPIは使用できない
・一部APIのレートリミットは大きく緩和されている。search/tweetsは+270回、friends/list・folloowers/listは+15回

twitter4jでの実験的な利用方法
・OAuthコンシューマーキーを作る
・twitter4j.propertiesを編集する:consumerkey, consumersecretの設定、useSSL=true(3.3.4以前)、enableApplicationOnlyAuthentication=trueにする
・twitter.getOAuth2Token();を呼び出すとA-oAが適用になる


■だれとくアプリからSNS活用への道(佐藤さん)
・秋葉の雁川という中華料理屋のアプリ
・そのおすすめアプリサービス(androidアプリ)
・twitter, Facebook, mixi, forsquareへ同時投稿や行ったときのチェックインを一括で行うアプリ
・twitter ->twitter4j, forsquare->forsuqare-api-java
・mixi, facebook ->SDKが多機能だったので独自に利用部分だけ実装
・公開後1年たったら公式アカウントからキャンペーンに使いたいという連絡が来た>杏仁豆腐おごってもらった。



その他メモ
・twitterのAPIが1.1になって、tweetを集めるのはやりやすくなったが、フォロワー関係を取得しづらくなっている
・3時間で128ツイート、1日に1024ツイートで規制。メンションしまくりでsuspended


2013年2月22日金曜日

修正BSD, MIT, Apacheライセンスのライブラリを使ってスマホアプリを作る場合の対応

以下のライセンスのライブラリ等を利用しスマホアプリを作成、配布(公開)する場合。正しいかどうかはわかりません。。。

エンドユーザ向け表示


修正BSDライセンス、MITライセンス

・ライブラリのソースファイル内ヘッダコメントやREADME等にあるライセンス表記を、ライブラリ名とともにそのまま表示する。
※無い場合はライブラリ名、著作権表記(無ければライブラリ所在URLとか?)、免責事項を含む一般的なライセンス表記を表示
※アプリ上の設定等にライセンスについてや著作権表示みたいな専用の画面を作ってそこに記述するのがオーソドックス

Apacheライセンス

・ライブラリ内にNOTICEファイル等帰属告知やライセンス表記があるものがあればそれをそのまま表示する
※無ければApacheライセンスのテキストとライブラリ名を表示
※上記同様専用の画面を作る


開発ソース

修正BSDライセンス、MITライセンス

・ライブラリ上に記載される著作権表記等はそのままにする

Apacheライセンス

・ライブラリ上に記載される著作権表記等はそのままにする
・ライブラリを改変する場合はその旨を上記著作権表記あたりに追加記述する


参考

Wikipedia - MITライセンス
Wikipedia - BSDライセンス
Wikipedia - Apacheライセンス
BSDライセンスの特徴:BSDライセンスが適用されたソフトウェアを商用利用する場合の注意点を教えてください。またMITライセンスとの違いも教えてください

2013年1月11日金曜日

webView:shouldStartLoadWithRequest:navigationType:でloadRequestするとPOSTがGETになる

UIWebViewで全リクエストのリクエストヘッダを追加変更したい場合は以下の方法が利用できる。
Custom HTTP headers for every request made in UIWebViews - Nomad Planet

ヘッダを追加変更して独自にloadRequestをし、NOを返す(呼び出し元のloadRequestキャンセル)ことでヘッダの追加を実現している。

ただし独自loadRequest時も再度webView:shouldStartLoadWithRequest:navigationType:が呼ばれる為、無限ループを避けるような処理が加えられている(ヘッダあるなしでの分岐)

問題は上記方法ではPOSTリクエストにならない


おそらくUIWebView内部の問題だが、webView:shouldStartLoadWithRequest:navigationTypeでリクエストオブジェクトを変更して再度loadRequestすると何故かPOSTがGETになってしまう。(再loadRequest直前はPOSTのままなのに。。)
元のPOSTリクエストオブジェクトを設定してloadRequestを呼び出しても同様だった。


解決策

以下の方法を使う。
UIWebViewでUser-Agentを変更する - 武之新

隠しDelegateであるUIWebViewWebViewDelegateのメソッドuiWebView:resource:willSendRequest:redirectResponse:fromDataSource:を実装することでリクエストヘッダというかリクエストオブジェクト自体の変更が可能。
実装対象のクラスがUIWebViewDelegateプロトコルを継承していればあとは上記uiWebView~メソッドを実装するだけのお手軽さ。


というか、リクエスト前にその内容をいじれないなんてどうなってんの?甚だ疑問
そして上述のブログでも述べられているがこれでappleの審査通るかは不明。ググってみたけどそれらしき記述は発見できず。

追記)審査通った2013前半頃