[Xamarin][iOS] TabBarのようにUIButtonを等間隔で並べたフッターを作る

今回はUIButtonを等間隔で並べてフッターを作る方法です。
ぶっちゃけそれTabBarでいいんじゃないの?と思いますが
デザイン面でTabBarではクリアにできない問題もあったりします。
このテクニックはUIButtonだけじゃなく、View要素であれば何にでも使えます。

そもそも、iOSの場合View要素を等間隔で並べるってすごい面倒くさいです。
ボタンが偶数個の場合はUIViewを段組みにして比較的簡単に実装できますが、
奇数個になった瞬間大抵のプログラマは発狂するんじゃないでしょうか。

そんな面倒くさいこととはおさらばだ!
というわけで、Interface Builderを使うのをやめてコードでやりましょう。

// Viewの初期化のあたりでリストを初期化しておく
private List buttons;

nfloat _buttonWidth = this.Bounds.Size.Width / this.buttons.Count;
nfloat _buttonHeight = this.Bounds.Size.Height;

this.buttons.ForEach(button =>
{
	button.Frame = new CGRect(_buttonWidth * this.buttons.IndexOf(button), 0, _buttonWidth, _buttonHeight);
});

親Viewの横幅をボタンの数で割って、
そのViewの中に配置する各ボタンの横幅に設定してあげるだけですね。
このやり方だと、ボタンが偶数でも奇数でも対応出来るのでだいぶ楽だと思います。

 

[Xamarin][iOS] ステータスバーを非表示にしてフルスクリーンにする

エントリー追加時の環境
Xamarin Studio 6.1.2

今回はアプリをフルスクリーンにする設定です。
Xamarin.iOSでアプリを作る際に、
ステータスバー(時間とかバッテリー残量の表示領域)を消したい場合があります。

早速設定方法です。

Info.plistを開き、
アプリの設定から「配置情報」の「ステータスバーを非表示にする」にチェックを入れます。

そのままInfo.plistのソースに行を追加します。
追加する行は「View controller-based status bar appearance」です。

追加した行の型はブール値にし、値はNo(いいえ)にします。

設定は以上です。

[Xamarin][iOS] Xamarin.iOSアプリにAdMobを組み込む

エントリー追加時の環境
Xamarin Studio 6.1.2
AdMob 7.11.0

Xamarinだいぶ流行ってきていますね。
かくいう自分もXcode/Android Studioから乗り換えつつあります。
開発環境が変わるメリット・デメリットは多々ありますが、、
Xamarinはいいぞ!C#はいいぞ!

さてさて、個人のデベロッパとしてはどうしても
見過ごすわけにはいかないものがあります。
それはアプリ内広告です。

Xamarinってそもそも広告組み込めるの?というわけですね。
大丈夫です。大丈夫なんです。ちゃんと出来ます。Xamarinは出来る子です。
というわけで今回はXamarin.iOSで広告を表示させてみましょう。

※ 事前準備として、AdMob+Firebaseのアカウントが必須です。

まずAdMobの管理画面でアプリと広告ユニットを登録しIDなどを取得します。
取得しなければいけないのは下記のリストです。
・アプリID(ca-app-pub-XXXXXXXXXXXXXXXX~NNNNNNNNNN)
・広告ユニットID(ca-app-pub-NNNNNNNNNNNNNNNN/NNNNNNNNNN)
・GoogleService-Info.plist(Firebaseから取得する)

アプリID・広告IDはAdMobの管理画面から取得してきてください。
GoogleService-Info.plistはこんな手順で取得します。(画像見づらくてすみません)

1. アプリの管理画面からFirebaseの結合手順表示ボタンをクリックします。
2. ダイアログが表示されるので、「こちら」をクリックします。
3.Firebaseのページが開くので、GoogleService-Info.plistをダウンロードします。

先程挙げたリストをもって、ここからXamarin Studioでの作業です。

ソリューションエクスプローラの「コンポーネント」を右クリックし、
「コンポーネントのを更に取得する…」を選択します。
AdMobで検索し、「Firebase AdMob for iOS」を追加します。
先程Firebaseからダウンロードした「GoogleService-Info.plist」をソリューションに追加します。
「GoogleService-Info.plist」を右クリックし、
ビルドアクションの設定をBundleResourceに変更します。(やらないとエラーになる)

AppDelegate.csにコードを追加します。
追加するのは、SDKのインポートとFinishedLaunchingでの初期化処理です。

using Google.MobileAds;
using Firebase.Analytics;

public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
	App.Configure();
	// AdMobから取得したアプリIDを設定
	MobileAds.Configure("ca-app-pub-XXXXXXXXXXXXXXXX~NNNNNNNNNN");

	return true;
}

最後に、広告を表示するViewの設定をします。

上記のStoryBoardに紐づくViewControllerのクラスファイルのViewDidLoadにコードを追加します。

using Google.MobileAds;

public override void ViewDidLoad()
{
	base.ViewDidLoad();
	// AdMobで作成した広告のユニットIDを設定
	this.bannerView.AdUnitID = "ca-app-pub-NNNNNNNNNNNNNNNN/NNNNNNNNNN";
	this.bannerView.RootViewController = this;

	Request request = Request.GetDefaultRequest();
	// テスト用の端末IDをリストで設定
	request.TestDevices = new String[]{ Request.SimulatorId };

	this.bannerView.LoadRequest(request);
}

ではシミュレータを起動して実行してみましょう。

Error MT3001: Could not AOT the assembly '/Users/~/iOS/obj/iPhone/Debug/build-iphone8.1-10.1.1/mtouch-cache/Build/Firebase.Analytics.dll' (MT3001) (hoge.iOS)
Error MT3001: Could not AOT the assembly '/Users/~/iOS/obj/iPhone/Debug/build-iphone8.1-10.1.1/mtouch-cache/Build/Google.MobileAds.dll' (MT3001) (hoge.iOS)

んービルドエラーですね。なんででしょうね。
ドキュメントを良く読むと・・・?

> • App doesn’t compile when Incremental builds is enabled. (Bug [#43689][5])
なるほどインクリメンタルビルドほにゃららと書いてあります。

これを回避するにはプロジェクトのオプションから
iOS Buildの「インクリメンタルビルドを有効にする」のチェックを外してあげます。

なお開発中は本番の広告を表示しているとペナルティを受ける可能性があります。
そのため開発中は実機のデバイスをテストデバイスに登録してあげないといけません。
必ずアプリケーション出力ログを見てみましょう。下記のようなログはないでしょうか。

2016-11-24 02:58:33.538 AdMobTest.iOS[1733:1075253] <Google> To get test ads on this device, call: request.testDevices = @[ @"NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN" ];

もしログがあった場合、コードを少し修正します。
テストデバイス登録しておけば、テスト用の広告しか表示されなくなります。

request.TestDevices = new String[] { Request.SimulatorId, "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN" };

AdMob広告組み込みの作業は以上です。

Ruby on Rails5の新機能ActionCableを試す!

※1 執筆時のRailsはバージョン5.0.0.1。
※2 Webサーバにはnginxを使用。
※3 リバースプロキシにnginxを使った構成も対応する。

Rails5.0.0.1はすでにインストールされているものとする。

定番のコマンドでプロジェクトを作る。

$ rails new sample-project

次にチャンネルを作る。
これにより作成されるファイルは下記の通り。
<hoge_channel.rb>はサーバサイドが行う処理が記述される。
<hoge.coffee>はクライアントサイドが行う処理が記述される。

$ rails g channel hoge piyo
(hogeがチャンネル名、piyoはアクション名)
      create  app/channels/hoge_channel.rb
   identical  app/assets/javascripts/cable.js
      create  app/assets/javascripts/channels/hoge.coffee

※ もしエラーが出たらGemfileのtherubyracerがコメントアウトされていると思うので有効にしてbundle installする。

<development.rb, production.rb>に「ソケットを許可するアクセス元」を追記。
つまり自分のドメインを書けば良い。

config.action_cable.allowed_request_origins = ['http://hogepiyo.com']

Railsの準備は以上。
次はnginxサーバの設定に移る。

!注意点!

rc版のRailsでは<route.rb>に

mount ActionCable.server => '/cable'

を書いたり、

<cable.coffee>の

@App ||= {}
App.cable = ActionCable.createConsumer()

をコメント外したりという作業が必要だった。
Ruby on Rails 5.0.0.1ではこれら作業は必要ない。

ここからNginxの設定。
RailsではWebSocketを使うのにPuma等が推奨されている。
Unicornはあいにく非推奨、これはシングルスレッドノンブロッキングI/Oという仕様のため。
そのためPumaに対してPIDないしSocketでアクセスを流してあげるのが好ましい(たぶん)
設定内容は単純に/cable(RailsのWebSocketの標準設定)へのアクセスをupgradeしてPumaに渡すだけである。

Nginxのconfファイルの参考

upstream puma {
  server unix:/var/run/puma.sock fail_timeout=0;
}

server {
中略
    location /cable {
        proxy_pass http://puma/cable;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
中略
}

これで晴れてActionCable(WebSocket)機能が動くようになる。

また、nginxのリバースプロキシがある場合は、リバースプロキシにも設定が必要になる。
アプリケーションサーバと同様に/cableに対して
upgradeしてアプリケーションサーバへアクセスを流すだけである。

server {
中略
    location /cable {
           proxy_pass http://backend;
           proxy_http_version 1.1;
           proxy_set_header Upgrade $http_upgrade;
           proxy_set_header Connection "upgrade";
           proxy_set_header Host $host;
    }
中略
}

もしこの記述をしなかった場合サーバは404を返すうえに、
アプリケーションサーバではこのようなエラーログが残る。

ブラウザのコンソールに表示されるログ
WebSocket connection to 'ws://hogepiyo.com/cable' failed: Error during WebSocket handshake: Unexpected response code: 404

Railsのログにはこのように出力される
Started GET "/cable" for *.*.*.* at YYYY-MM-DD 00:00:00 +0900
Started GET "/cable/"[non-WebSocket] for *.*.*.* at YYYY-MM-DD 00:00:00 +0900
Failed to upgrade to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: close, HTTP_UPGRADE: )
Finished "/cable/"[non-WebSocket] for *.*.*.* at YYYY-MM-DD 00:00:00  +0900
※ IP等は伏せてる

ここで注意しないといけないのがこの一行を忘れないこと。
> proxy_set_header Host $host;

[Raspberry Pi] MacでMicroUSBカードにRaspbianをインストールする

公式からOSのイメージをダウンロードしておく。
Macに書き込み先となるMicroUSBカードを接続する(USBカードのR/W等)。

接続したら書き込み先のUSBカードのマウント先を調べため、ここからターミナルで作業する。

$ diskutil list
中略
/dev/disk3
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *15.6 GB    disk3
   1:             Windows_FAT_32 NO NAME                 15.6 GB    disk3s1
/dev/disk4
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *4.1 GB     disk4
   1:             Windows_FAT_32 boot                    62.9 MB    disk4s1
   2:                      Linux                         4.1 GB     disk4s2

今回の場合、/dev/disk3がUSBカードだ。/dev/disk4はRaspbianのイメージをマウントしてある。
マウント先がわかったので、disk3をアンマウントする。アンマウントしないと書き込みに時にエラーメッセージが出る。

$ sudo diskutil unmountDisk /dev/disk3
Unmount of all volumes on disk3 was successful

次にUSBカードに書き込みを行う。
disk3以外に書き込んだ場合はデータが消えるかもしれないので間違えないように。

書き込みコマンドはdd。
ifには読み込み元→イメージファイル。
ofには書き込み先→USBカード。
bsはブロックサイズ。1mでよい。

$ sudo dd bs=1m if=./Downloads/2016-02-09-raspbian-jessie.img of=/dev/disk3

書き込みには結構な時間がかかるので、画面が止まっていてもctrl+cなどしないように。
完了すると下記のようなメッセージが表示される。

$ sudo dd bs=1m if=./Downloads/2016-02-09-raspbian-jessie.img of=/dev/disk3
3936+0 records in
3936+0 records out
4127195136 bytes transferred in 5794.746202 secs (712231 bytes/sec)

もしアンマウントをしなかった場合は下記のようになる。

$ sudo dd bs=1m if=./Downloads/2016-02-09-raspbian-jessie.img of=/dev/disk3
dd: /dev/disk3: Resource busy

書き込みが終わったらUSBカードを抜いてラズパイにセット。