@blog.justoneplanet.info

日々勉強

iPhoneアプリでステータスバーを隠す

■コード

以下の記述でステータスバーを消すことができる。

[UIApplication sharedApplication].statusBarHidden = YES;

■Info.plist

もしくはInfo.plistにUIStatusBarHiddenというキーを追加し、値にYESをセットする。

androidで設定画面を作る

■設定画面

SettingActivity.java

以下のようにしてActivityを定義する。

package info.justoneplanet.android.sample.preference;

import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.PreferenceActivity;
import android.util.Log;

public class SettingActivity extends PreferenceActivity implements OnPreferenceChangeListener {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.setting);
    }
    
    /**
     * 設定が変更された時
     */
    @Override
    public boolean onPreferenceChange(Preference preference, Object newValue) {
        Log.e("preference changed - " + preference.toString(), newValue.toString());
        return false;
    }
}

驚くことに保存するロジックは書かなくて良い。以下のXMLを定義すればkeyを元にして自動的に保存される。

res/xml/setting.xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
    <PreferenceCategory android:title="@string/group1">
        <CheckBoxPreference
            android:key="@string/item1_key" 
            android:title="@string/item1" 
            android:summary="@string/item1_summary" 
            android:summaryOn="@string/item1_summary_on" 
            android:summaryOff="@string/item1_summary_off" 
            android:defaultValue="true" />
    </PreferenceCategory>
    <PreferenceCategory android:title="@string/group1">
        <ListPreference 
            android:key="@string/item2_key" 
            android:title="@string/item2"
            android:summary="@string/item2_summary" 
            android:dialogTitle="@string/item2" 
            android:entries="@array/list_entries" 
            android:entryValues="@array/list_entryvalues" 
            android:selectable="true"
            android:enabled="true" 
            android:positiveButtonText="OK" 
            android:negativeButtonText="Cancel" 
            android:defaultValue="2nd">
            </ListPreference>
        <RingtonePreference
            android:key="@string/melody_key"
            android:title="@string/melody"
            android:summary="@string/melody_summary"
            android:showDefault="true" />
    </PreferenceCategory>
</PreferenceScreen>

■設定呼び出し画面

以下のようにして通常のSharedPreferenceから読み出しできる。PreferenceManager.getDefaultSharedPreferences(this);だけが設定画面用の呼び出しである。

package info.justoneplanet.android.sample.preference;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;

public class MainActivity extends Activity {
    
    private static final int REQUEST_CODE = 200;
    private SharedPreferences sharedPreferences;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // sharedpreferenceの呼び出し
        sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
    }
    
    @Override
    public void onResume() {
        super.onResume();
        
        // 設定から戻ってきたときに反映されているように
        // 設定1
        TextView textView1 = (TextView) findViewById(R.id.item1);
        textView1.setText(String.valueOf(sharedPreferences.getBoolean(getString(R.string.item1_key), true)));
        
        // 設定2
        TextView textView2 = (TextView) findViewById(R.id.item2);
        textView2.setText(String.valueOf(sharedPreferences.getString(getString(R.string.item2_key), getString(R.string.not_set))));
        
        // 設定3
        TextView textView3 = (TextView) findViewById(R.id.item3);
        textView3.setText(String.valueOf(sharedPreferences.getString(getString(R.string.melody_key), getString(R.string.not_set))));
    }
    
    /**
     * メニューボタンが押された時
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        boolean result = super.onCreateOptionsMenu(menu);
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.main, menu);
        return result;
    }
    
    /**
     * メニューが選択された時
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        switch(item.getItemId()){
        case R.id.menu_setting:
            Intent intent = new Intent(this, SettingActivity.class);
            startActivityForResult(intent, REQUEST_CODE);
            return true;
        }
        return false;
    }
}

ロジックには関係しないが後でコピーする為にリソースも残しておく。

res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Preference</string>
    <string name="menu_setting">Setting</string>
    <string name="not_set">Not set, yet.</string>
    <string name="group1">group 1</string>
    <string name="item1">item 1</string>
    <string name="item1_key">item1</string>
    <string name="item1_summary_on">it is on</string>
    <string name="item1_summary_off">it is off</string>
    <string name="item1_summary">summary1</string>
        
    <string name="group2">group 2</string>
    <string name="item2">item 2</string>
    <string name="item2_key">item2</string>
    <string name="item2_summary">summary1</string>
    
    <string name="melody">melody</string>
    <string name="melody_key">melody</string>
    <string name="melody_summary">melody</string>
</resources>

res/values/array.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <string-array name="list_entries">
    <item>Item 1</item>
    <item>Item 2</item>
    <item>Item 3</item>
  </string-array>
  <string-array name="list_entryvalues">
    <item>1</item>
    <item>2</item>
    <item>3</item>
  </string-array>
</resources>

ちなみに属性値を誤って入力しなかったりするとR.javaが崩れる。

res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/app_name" />
    <TextView
        android:id="@+id/item1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/app_name" />
    <TextView
        android:id="@+id/item2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/app_name" />
    <TextView
        android:id="@+id/item3"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/app_name" />
</LinearLayout>

androidのView

めも。サンプルコードなのでアレ。

TextView

テキストを表示する場合は以下のようにする。

<TextView
    android:id="@+id/list_face"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:textSize="17sp"
    android:textColor="#333333" />

EditText

以下のようにして編集可能なテキスト領域を設置する。

<EditText
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="top"
    android:id="@+id/edit_text"/>

デフォルトでは水平方向でセンタリングされるのでgravityを指定し変更する。

AutoCompleteTextView

以下のように入力補助付きのテキストエリアを生成できる。

<AutoCompleteTextView
    android:id="@+id/autocomp"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:completionThreshold="2" />

但し、これだけでは完結しない。

候補表示用レイアウトファイル

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >
</TextView>

プログラム

以下のようにして入力候補をセットする。

ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.text, new String[]{"hoge", "fuga", "piyo"});
((AutoCompleteTextView) findViewById(R.id.autocomp)).setAdapter(adapter);

ImageView

画像表示用View。

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_launcher"
    android:id="@+id/icon" />

VideoView

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:id="@+id/movie" />

以下のようにして動画ソースをセットする。

((VideoView) findViewById(R.id.movie)).setVideoPath("/data/adult.mp4");

Button

以下のようにしてボタンを設置することができる。

<Button
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="@string/app_name" />

以下のようにしてクリックした時のイベントを定義する。

((Button) findViewById(R.id.button)).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    }
});

androidでintentを使って他のアプリケーションと連携する

メモ。

■ソース

デフォルトのブラウザで共有を押した時の挙動と同じものが以下のコードで実現できる。

Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, "text");
startActivity(Intent.createChooser(intent, "title"));

ちなみに画像などを送ることもできる。

MySQLでテーブルをコピーする

忘れてしまうのでメモしておく。

CREATE TABLE new_table LIKE old_table;
INSERT INTO new_table SELECT * FROM old_table;

これは以下のように異なったDB間でも行うことができる。

CREATE TABLE new_table LIKE `old_database`.`old_table`;
INSERT INTO new_table SELECT * FROM `old_database`.`old_table`;

In App Purchaseをつかってみる

■フレームワーク

StoreKitを追加する。

#import <StoreKit/StoreKit.h>

■実装

以下のようにしてSKProductsRequestDelegateとSKPaymentTransactionObserverを実装する。/p>

HogeViewController.h

@interface HogeViewController : UIViewController<SKProductsRequestDelegate, SKPaymentTransactionObserver> {
    bool isLoading;
    SKProductsRequest       *skProductsRequest;
    UIActivityIndicatorView *spinner;
    UIView                  *loaderBg;
    UILabel                 *loaderTitle;
}
@property bool isLoading;
- (IBAction)purchaseButtonPushed:(id)sender;
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response;
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransaction:(NSArray *)transactions;
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0);

HogeViewController.m

購入処理の開始
- (IBAction)purchaseButtonPushed:(id)sender {
    if([[UIDevice currentDevice] networkAvailable] == NO){
        return;
    }
    if(self.isLoading){
        return;
    }
    if([SKPaymentQueue canMakePayments]){
        self.isLoading = true;
        
        // loader
        loaderBg = [[UIView alloc] init];
        [loaderBg setFrame:CGRectMake(100, 150, 120, 85)];
        [loaderBg setBackgroundColor:[UIColor blackColor]];
        [loaderBg.layer setCornerRadius:13.0f];
        [loaderBg setAlpha:0.7];
        [self.view addSubview:loaderBg];
        
        loaderTitle = [[UILabel alloc] init];
        [loaderTitle setTextColor:[UIColor whiteColor]];
        [loaderTitle setFrame:CGRectMake(0, 57, 120, 18)];
        [loaderTitle setText:@"Loading..."];
        [loaderTitle setTextAlignment:UITextAlignmentCenter];
        [loaderTitle setBackgroundColor:[UIColor clearColor]];
        [loaderTitle setAlpha:1.0];
        [loaderBg addSubview:loaderTitle];
        
        // spinner
        spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
        [spinner setCenter:CGPointMake(self.view.frame.size.width/2.0, self.view.frame.size.height/2.0)];
        [self.view addSubview:spinner];
        [spinner startAnimating];
        [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
        
        // 課金部分
        // identiferを元にappleサーバに問い合わせます
        [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
        skProductsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:@"identifer"]];
        [skProductsRequest setDelegate:self];
        [skProductsRequest start];
    }
    else{// 本体の設定でアプリ内課金をOFFにしている人向けの表示
        [self showAlert:@"cannot purchase" text:@"設定 > 一般 > 機能制限で[App内での購入]をONにしてください"];
    }
}

上述のようにアプリ内課金は本体の設定で無効にできる事を考慮する。

appleのサーバにidentiferをお問い合わせした結果の処理

identiferをセットしてリクエストしたレスポンスに商品データがあれば、その商品の購入手続きに入る。

#pragma mark -
#pragma mark SKProductsRequestDelegate
- (void) productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
    if (response == nil) {
        return;
    }
    for(SKProduct *product in response.products){// productを元にした購入オブジェクトをキューに入れて購入手続きに
        SKPayment *payment = [SKPayment paymentWithProduct:product];
        [[SKPaymentQueue defaultQueue] addPayment:payment];
    }
}

後述のオブザーバーの管理を忘れないようにすること。

トランザクションの状態に変更があった時の処理

以下のように状態はswitch文で分岐する。

#pragma mark -
#pragma mark SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransaction:(NSArray *)transactions {
    BOOL isFinished = YES;
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchasing:// 何らかのOKを押す前の処理
                break;
            case SKPaymentTransactionStatePurchased:// success : 決済手続き完了処理
                [queue finishTransaction:transaction];
                
                // もし自社サーバでユーザが購入を完了したかどうかappleサーバに確認する場合は、
                // transaction.transactionReceiptの値をbase64に変換して自社サーバに送信
                // [transaction.transactionReceipt base64EncodedString];
                
                isFinished = NO;
                break;
            case SKPaymentTransactionStateFailed://  途中でキャンセルした時
                isFinished = NO;
                [queue finishTransaction:transaction];
                break;
            case SKPaymentTransactionStateRestored:// 通常はコールされない
                isFinished = NO;
                [queue finishTransaction:transaction];
                break;
            default:
                break;
        }
    }
    if (isFinished == NO) {// トランザクションが何らかの完了をした時=>ローディングを消す
        self.isLoading = false;
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        [loaderTitle removeFromSuperview];
        [loaderBg removeFromSuperview];
        [spinner stopAnimating];
    }
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_0){
    [self paymentQueue:queue updatedTransaction:transactions];
}
オブザーバー

dealloc部分で課金処理キューからオブザーバーをしっかり削除するようにする。

- (void)dealloc{
    [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];// これ!!!
    [loaderBg release];
    [loaderTitle release];
    [skProductsRequest release];
    [spinner release];
    [super dealloc];
}

サーバ側

以下のようにしてユーザから送信されてきたレシート情報を自社サーバ側でappleに確認する。

$ch  = curl_init();
$url = "https://sandbox.itunes.apple.com/verifyReceipt";// サンドボックス(テスト用)
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, '{"receipt-data" : "' . $receipt . '"}');
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_HEADER, FALSE);
$result = curl_exec($ch);

■テスト

xcodeからインストールしたアプリではサンドボックス経由という事が購入ダイアログで表示されます。storeからインストールしたアプリでは本番と同じダイアログになります。特にコードを変更する必要はなく自動で判定してくれるようです。

Cの配列を使ってみる

基本は以下のように型つきの入れ物を用意する。

int main(int argc, char *argv[])
{
    int ary[5];
}

以下のように初期化も同時に行える。

int main(int argc, char *argv[])
{
    int ary[5] = {1, 2, 3, 4, 5};
}

上述の場合、要素数は以下のように省略して良い。

int main(int argc, char *argv[])
{
    int ary[] = {1, 2, 3, 4, 5};
}

配列の要素数は以下のようにして求めることができる。

int main(int argc, char *argv[])
{
    int ary[5] = {1, 2, 3, 4, 5};
    printf("%lu", sizeof(ary) / sizeof(ary[0]));// 5 = 全体のサイズ / 1要素のサイズ
}

煩雑ですな。ループは普通にfor文とか使う。

さくらのVPSを10分でWebSocketサーバにする

たぶん10分くらいで可能。

yum install openssl-devel
wget http://nodejs.org/dist/node-v0.4.3.tar.gz
tar xvzf node-v0.4.3.tar.gz
cd node-v0.4.3
./configure
make
make install
curl http://npmjs.org/install.sh | sh
npm install websocket-server
npm install base64

make中は一休みできる(●´ω`●)

最新版

websocket-serverは新しいWebSocketの仕様に対応してないのでwebsocketを使用する。

yum install openssl-devel gcc-c++ make
wget http://nodejs.org/dist/v0.6.8/node-v0.6.8.tar.gz
tar xvzf node-v0.6.8.tar.gz
cd node-v0.6.8
./configure
make
make install
curl http://npmjs.org/install.sh | sh
npm install websocket

■CentOS6.2

以下のコマンドで軽く設定してiptablesの設定ファイルを生成しておく。

system-config-firewall-tui

実際の設定は以下のコマンドを実行して行う。

vim /etc/sysconfig/iptables
/etc/init.d/iptables restart
vim /etc/sysconfig/ip6tables
/etc/init.d/ip6tables restart

naveを使ってnodeをインストールする。

sudo yum -y install openssl-devel gcc-c++ git make
git clone https://github.com/isaacs/nave.git ~/.nave
~/.nave/nave.sh install stable
~/.nave/nave.sh use stable
echo "~/.nave/nave.sh use stable" >> ~/.bash_profile
sudo curl https://npmjs.org/install.sh --insecure | sh
npm -g install forever

node-base64をインストールする

どうやら環境によっては以下のエラーが出る。

../base64.cc:138: error: 'malloc' was not declared in this scope
../base64.cc:141: error: 'free' was not declared in this scope

■解決策

ソースをダウンロードしbase64.ccに以下のラインを追加する。

#import <stdlib.h>

その後、以下のコマンドでインストールする。

npm install dir/

最後のスラッシュは必要なかったかもしれない。