c-geru のすべての投稿

フリーランスのための入院心得

訳あって、4月の後半から5月半ばにかけて、入院しました。生命の危険に関わるような大事ではないのですが、外科手術を伴うため一週間程度の見込みで入院しました。が、いろいろと予想外の展開で結局延べ25日も入院することに・・・。

基本的にここは技術ブログなんですが、生まれてこの方初めての入院でいろいろ知らないことも多かったので、誰かのお役に立てばとまとめを書いてみます。私と同じフリーランスの立場の人にとっては、特に金銭的なことは気がかりだと思いますし(有給休暇とかないし)、親やパートナー、子供の入院などで自分が手続きなどしないといけないこともあり得ると思うので。

今回は・・・

・予定(計画)された入院である。
・入院先は、某国立大学の付属病院。
・保険は国民健康保険。

という前提に基づいた体験談です。病院や加入保険によって多少の違いがあるかもです。

1.入院するには保証金が必要。

これは全然知りませんでした(笑)。昔、コドモが緊急入院したことがあるのですが、その時には払った記憶がないんだけど・・・。

※妻から「文京区は中学生までは医療費無料なので、差額ベッド代しか払ってない」との指摘を受けました。そういやそうだった。

私が入院した病院では・・・

a.クレジットカードによる入院費用支払い保証書
b.入院預かり金の支払い

のどちらかを選択するようになっていました。

a.の場合は、入院申込書・保証書と一緒にクレジットカードを提示すると預かり金は要りません。使えるカードを持っているという事実が担保になるわけです。

b.の場合は、預かり金の支払いと入院申込書・保証書に「連帯保証人」の記載が必要になります。ちなみに診療内容によって保証金の額は異なります。私の場合は「保険診療」で、預かり金は100,000円でした。

私はa.を選択しました。お金の用意も不要だし、連帯保証人も必要ないので、手続きもカンタンです。

※ a.の場合、クレジットカードの提示はあくまで支払能力があることの証明という意味合いで、支払方法は現金、クレジットカード(提示カードでも別のカードでも)のどちらでも、支払時に選択できます。

2.限度額適用認定証

健康保険に加入していれば、医療費が高額になった際に自己負担限度額を超えた額が払い戻される「高額療養費制度」があります。ただしこれは「あとから申請して払い戻しを受ける」制度なので、まず入院費用を全額払う必要があります。これは一時的とはいえ、結構な負担になります。

※高額療養費制度の自己負担限度額は、収入によって異なります。また平成27年1月診療分から自己負担限度額が変更されたようです。詳しくはこちら

病院から渡された入院についての書類に書いてあったのですが、「限度額適用認定証」というものがあって、これを保険証と併せて医療機関等の窓口に提示すると、1ヵ月 (1日から月末まで)の窓口でのお支払いが自己負担限度額までになります。但し入院手続き時に提示が必要なので、入院までに市区町村役所に申請してもらっておく必要があります。私が行ったときは、PDFをダウンロードした書類を用意しておいて、その場で発行してくれました。参考までに文京区の情報を貼っておきます。

【参考】文京区 限度額適用認定証について

※ちなみにこの「限度額適用認定証」、有効期間中は常に保険証と一緒に提示する必要があるそうです。また毎年8月更新(つまり有効期限が7月末)なので、7月〜8月を跨ぐ場合には注意が必要です(継続の場合は必ず7月中に適用証の交付申請が必要です)。

3.入院費は月単位で支払う

これも全然気づいてなかったのですが、入院中に自分のベッドまで事務の方が来て請求書渡されました(笑)。入院費は月単位での精算になります。つまり毎月支払うということです。また上記2.「限度額適用認定証」のところにも書いてありますが「1ヵ月 (1日から月末まで)の窓口でのお支払いが自己負担限度額」ということなので、自己負担限度額は1ヶ月分の費用に対する限度であって、かかった費用全体に対する限度額ではないという点に注意が必要です。よって入院は二ヶ月に跨がるよりも一ヶ月で済む方が自己負担額は少なくて済むということになります。

これは意外と盲点でした。なので予め入院期間が決まっている(目安がある)場合には、1ヶ月(1日から月末まで)で収まるように計画するのが吉です。私のように長引く可能性もありますが・・・。

4.寝具とタオルなど

私の入院した病院は貸しだす業者が入っていて、寝間着+フェイスタオル+バスタオルで一日350円でした。必要に応じて一日何回取り替えても金額は同じであることと、手術前後は前開き(浴衣のような)である必要があった、また毎日の洗濯の手間を考えて、業者を利用しました。これも月毎に請求書が送られてきて振込しました。5月分は今日現在未着です。おそらく5末締めで送られてくると思われます。

5.差額ベッド代

どこのセレブだよって感じですが(笑)、入院予定を立ててもベッドが開かないと入院延期になる可能性もあるとのことで、申込時に二人部屋でも可として申し込みました。仕事も調整もした上での入院だったので、予定がズレても困るしこの程度の差額(二人部屋の差額は2.780円/日)ならまあいいかと思って。予定通りに入院する必要がある場合には考える必要があるかもしれません。

※実際は四人部屋に入れたので、差額分はかかりませんでした。ちなみにこの病院の一番高い特別室の差額ベッド代は、194,400円だそうです。広さは155平方メートル。まさにセレブ(笑)。

6.その他にかかる費用

ベッドの脇にTVと冷蔵庫。TVは有料なのは知ってたし見る気もなかったのでいいんですが、冷蔵庫も有料(笑)。デポジット付きのチャージ可能なカード式で、200円/24h。術後安静でベッドから動けなかったので、水やら飲み物などを保管するのに使いました。入院が長引くと、入れておく飲み物代も含めて意外と費用がかさみます。

7.証明書の申請

生命保険等に加入していれば、入院一日当たりいくらか保険が下りると思いますが、病院で証明書等の書類を記入してもらい、それを使って申請する必要が出てきます。よって予め保険会社から必要書類を入手して、退院前に病院に記入をお願いしておきます。費用に関する書類なので、退院が確定した時点で作成可能になるとのこと。大きな病院だと申請自体の窓口が混み合っていますが、入院中なら病棟で申請が可能なので、その方が楽です。また私の入院先では作成までに最大三週間ほどかかるとのことで、封筒、切手を用意して郵送を依頼するか、後で取りに行くことになります。

保険によって変わると思いますが、ウチは都民共済なので、参考までにリンクを貼っておきます。自分の加入している保険会社に問い合わせるといいと思います。

【参考】共済金のご請求 手続きの流れ:生命共済|東京都民共済

※今回の場合、約一週間で証明書が発行されました。費用は2,160円。

ざっとこんなところでしょうか。

入院費の支払はクレジットカードで行いました。多額の現金を持ち歩く必要がないのと、万が一の場合にはリボ等に切り替えることもできるので、いろいろ便利です。先の入院預かり金の話も含めて、使うかどうかは別にしてやはりクレジットカードは持っていた方がいいと思います。これからフリーになるつもりの人は、会社員のうちに1枚作っておきましょう(笑)。

健康に気をつけるに越したことはありませんが、いつどんな形で入院する事態になるかわからないので、知識としてお役に立てば。

【Google Apps Script】Youtube動画の公開/非公開を切り替える

最近知ったのですが、Google Apps Scriptというのがあって、Googleドライブ上に作成したドキュメントやGoogleの各サービスをjavaScriptペースでハンドリングできるらしい。

【参考】
ASCII.jp:Web制作をちょっと便利にするGoogle Apps Script入門

※わかりやすいですが、ちょっと古い記事なのでメニューの場所などが現在のものと違っていたりします。

ちょうど必要があって、Youtube動画の公開/非公開を切り替えるスクリプトを考えてみました。

まず近そうなもので参考にしたのはこの辺。

【参考】
GoogleAppsScript – 公開されたGASのYouTubeAPIを少し使ってみた。 – Qiita
GoogleAppsScript – GASでYouTubeの自分の再生リストを全て非公開にする。 – Qiita

※参考ページにもありますが、YoutubeのAPIは拡張サービスという位置付けで、YouTube Data APIの有効化と承認が必要になります。

基本的にはYoutube Data APIをそのままJavaScriptの書式で書けばいいということなんですが、リストの取り方が・・・

Channels(チャンネル)
Playlists(再生リスト)
PlaylistItems(再生リストの項目)
Videos(動画)

といくつかあって、公開/非公開の切替は動画IDが必要になるのだけれど、確認した限りでは動画IDを含んだ結果を返すものは、

・PlaylistItems(再生リストの項目)
・Videos(動画)

の2つみたいです。

ただPlaylistItems(再生リストの項目)でのリスト取得も、Videos(動画)でのリスト取得もidまたはplaylistidが必要になります。Playlistは必ず作成するとは限らないし、Channels(チャンネル)から辿っていくのも大変そうだしと思ってAPIを見ていたら、Search(検索)のリスト取得に「forMine」という自分のIDに紐付いたものだけを検索するフィルタがあったので、これを使うことにしました。

とりあえず自分のIDに紐付いた動画idを取得して、スプレッドシートに書き出すのはこんな感じ。

//---自分のvideoを検索する
function searchMyVideos() {
  var nextToken = "";
  var arrayId = [];
  while(nextToken != null) {
    var res = YouTube.Search.list('id,snippet', 
                                     {maxResults: 50,
                                      forMine: true,
                                      order: "date",
                                      type: "video",
                                      pageToken: nextToken});
    
    for(var i = 0; i < res.items.length; i++) {
      var item = res.items[i];
       arrayId.push(item.id.videoId);
    }

    nextToken = (res.nextPageToken == "") ? null :res.nextPageToken;
  }
  
  if (arrayId.length > 0){
    getMyVideoList(arrayId);
  }
 
  Browser.msgBox("動画一覧を取得しました。");
}

//---videoの詳細情報を取得
function getMyVideoList(arrayId) {
  var nextToken = "";
  var id = String(arrayId);
  var arrayStatus = [];
  
  var sheetId = '**********************************';
  var spreadsheet = SpreadsheetApp.openById(sheetId);
  var newsheet = SpreadsheetApp.openById(sheetId).getSheetByName('動画リスト');
  newsheet.getRange(1, 1).setValue('id');
  newsheet.getRange(1, 2).setValue('タイトル');
  newsheet.getRange(1, 3).setValue('作成日');
  newsheet.getRange(1, 4).setValue('公開/非公開/限定公開');
  
  var count = 2;
  
  while(nextToken != null) {
    var playlistResponse = YouTube.Videos.list('snippet, status',{id:id});

    for(var i = 0; i < playlistResponse.items.length; i++) {
      var item = playlistResponse.items[i];
);
      
      arrayStatus.push(item.snippet.title + ";" + item.status.privacyStatus);
      
      newsheet.getRange(count, 1).setValue(item.id);
      newsheet.getRange(count, 2).setValue(item.snippet.title);
      newsheet.getRange(count, 3).setValue(item.snippet.publishedAt);
      newsheet.getRange(count, 4).setValue(item.status.privacyStatus);
      count++;
    }

    nextToken = playlistResponse.nextPageToken;

  }
}

検索で取得できるリストの項目数は最大50件で、続きがある場合は「nextPageToken」に何かしらの値が入ってくるようなので、値がある場合は処理を繰り返しています。で、リストを取得するごとに配列(arrayId)に動画id(item.id.videoId)を保存しておいて、検索が終わったらまとめてスプレッドシートに書き出しています。

また「var sheetId = ‘**********************************’;」にはスプレッドシートのidを指定するのですが、これは・・・

function checkId(){
  Logger.log(SpreadsheetApp.getActiveSpreadsheet().getId());
}

を実行してログの出力結果から取得するか、スプレッドシートのURLの・・・

https://docs.google.com/spreadsheets/d/**********/edit#gid=xxxxxxxx

「**********」の部分から取得できます。この例だと「**********」というidのスプレッドシートの「動画リスト」という名前のタブのシートを指定していることになります。

続いて、動画を全部非公開にするのはこんな感じ。

//---自分のvideoを非公開にする
function setPrivateMyVideos() {
  var nextToken = "";
  var arrayId = [];
  while(nextToken != null) {
    var res = YouTube.Search.list('id,snippet', 
                                     {maxResults: 50,
                                      forMine: true,
                                      order: "date",
                                      type: "video",
                                      pageToken: nextToken});
    
    for(var i = 0; i < res.items.length; i++) {
      var item = res.items[i];
       arrayId.push(item.id.videoId);
    }

    nextToken = (res.nextPageToken == "") ? null :res.nextPageToken;
  }
  
  if (arrayId.length > 0){
    updatePrivacyStatus_private(arrayId);
  }
  Browser.msgBox("全ての動画を非公開にしました。");
}

//---VideoのprivacyStatusをprivateに変更する
function updatePrivacyStatus_private(arrayId) {
  var nextToken = "";
  var id = String(arrayId);
  while(nextToken != null) {
    var playlistResponse = YouTube.Videos.list('snippet, status',{id:id});

    for(var i = 0; i < playlistResponse.items.length; i++) {
      var item = playlistResponse.items[i];
      if(item.status.privacyStatus != "private") {
        item.status.privacyStatus = "private";
        var updateRes = YouTube.Videos.update(item, "status,snippet");
      }

    }

    nextToken = playlistResponse.nextPageToken;

  }
}

スプレッドシートに書き出す代わりに、ステータス(item.status.privacyStatus)が非公開(private)でないものを非公開に更新しています。

最後に全て公開するにはこうします。

//---自分のvideoを公開する
function setPublicMyVideos() {
  var nextToken = "";
  var arrayId = [];
  while(nextToken != null) {
    var res = YouTube.Search.list('id,snippet', 
                                     {maxResults: 50,
                                      forMine: true,
                                      order: "date",
                                      type: "video",
                                      pageToken: nextToken});
    
    for(var i = 0; i < res.items.length; i++) {
      var item = res.items[i];
       arrayId.push(item.id.videoId);
    }

    nextToken = (res.nextPageToken == "") ? null :res.nextPageToken;
  }
  
  if (arrayId.length > 0){
    updatePrivacyStatus_public(arrayId);
  }
  Browser.msgBox("全ての動画を公開しました。");
}

//---VideoのprivacyStatusをpublicに変更する
function updatePrivacyStatus_public(arrayId) {
  var nextToken = "";
  var id = String(arrayId);
  while(nextToken != null) {
    var playlistResponse = YouTube.Videos.list('snippet, status',{id:id});

    for(var i = 0; i < playlistResponse.items.length; i++) {
      var item = playlistResponse.items[i];

      if(item.status.privacyStatus != "public") {
        item.status.privacyStatus = "public";
        var updateRes = YouTube.Videos.update(item, "status,snippet");
      }

    }

    nextToken = playlistResponse.nextPageToken;

  }
}

こちらではステータス(item.status.privacyStatus)が公開(public)でないものを公開に更新しています。

ちなみに処理が終わったのを確認するために「Browser.msgBox();」でポップアップを表示しているのですが、これを処理の途中で挟むと、処理が途中までしか実行されないということがありました。原因はよくわからないですが、あくまで確認用ということで余り使わない方がいいかも。

それからログ書きだし(Logger.log())はAPIの呼び出しごとにリセットされるようで、スクリプトエディタの表示>ログで確認しても最後のAPIの分しか書き出されていません。ASのtrace()やJSのconsole.log()のイメージで使うには、一回変数に入れてから最後に書き出すしかなさそうです。

【CreateJS】AS3からHTML5 Canvasに書き出す場合のJavaScript(その2)

swfで作ったコンテンツをHTML5 Canvasに書き出すお勉強のその2です。

今回は座標制御について。

青いボタン(btn_start)を押すとスロット(slot_mc0)が回転して、ランダムな位置で停止するというサンプルです。スロットが回転中はボタンを押せないようにしています。これをCanvasで動くように書き換えます(赤字はインスタンス名)。swfサンプルはこちら

screen_test02_02

スロット(slot_mc0)のMovieClipの構造はこんな感じです。4つの絵の入ったMovieClipが縦に2つ(img_mc0,img_mc1)並んでいて、全体をマスクしています。マスクの範囲から出てしまったら、もう一方のMovieClipの上に来るような仕組みです。(当然ながら、MovieClipはx=0で揃えてあります)。

で、ASはこんな感じ。まずはルートのタイムラインに書いてあるスクリプト。JSに変換する前提なので、型指定はしていません。
【AS:ルートのタイムライン】

var timer_msg;

//startボタン:使用可
useStartBtn();

function onOver_start(event){
	var mc = event.currentTarget;
	mc.alpha = 0.7;
}

function onOut_start(event){
	var mc = event.currentTarget;
	mc.alpha = 1;
}

function onClick_start(event){
	//startボタン:使用不可
	nouseStartBtn();

	slot_mc0.setRandom();
	slot_mc0.startTimer_spin(1000*(0+1),3);
	slot_mc0.startSlot();
}

function stopSlot(mc){
	//startボタン:使用不可
	useStartBtn();
}

function useStartBtn(){
	btn_start.addEventListener(MouseEvent.ROLL_OVER,onOver_start);
	btn_start.addEventListener(MouseEvent.ROLL_OUT,onOut_start);
	btn_start.addEventListener(MouseEvent.CLICK,onClick_start);
	btn_start.buttonMode = true;

	btn_start.alpha = 1;
}

function nouseStartBtn(){
	btn_start.removeEventListener(MouseEvent.ROLL_OVER,onOver_start);
	btn_start.removeEventListener(MouseEvent.ROLL_OUT,onOut_start);
	btn_start.removeEventListener(MouseEvent.CLICK,onClick_start);
	btn_start.buttonMode = false;	

	btn_start.alpha = 0.3;
}

スロット自体の処理は、AS版ではスロット(slot_mc0)MovieClipのタイムラインに書いています。
【AS:slot_mc0のタイムライン】

var array_pos = [0,110,220,330,440];
var speed_max = 30;
var flg_end = false;
var flg_stop = false;
var flg_remove = false;
var posNo = 0;
var imgNo = 0;
var speed;
var timer_spin;
var pos_stop;

//開始位置をランダムにする
function setRandom():void{
	var pos_y = xRandomInt(0,img_mc0.height);

	img_mc0.y = pos_y;
	img_mc1.y = img_mc0.y-img_mc1.height;

	//はみ出しチェック
	ckeckFrameOut();

	//スピード:初期化
	speed = speed_max;

	//停止フラグ:初期化
	flg_stop = false;
	flg_end = false;
	flg_remove = false;
	posNo = 0;
}

//回転
function spin(event){
	img_mc0.y += speed;
	img_mc1.y += speed;

	if (flg_end){
		checkEndPoint();
	}
	if (flg_stop){
		checkStopPoint();
	}

	ckeckFrameOut();

	if (flg_remove){
		flg_remove = false;
		//停止時の位置ずれ補正
		img_mc0.y = pos_stop;
		if (img_mc0.y < 0){
			img_mc1.y = img_mc0.y + img_mc0.height;
		} else {
			img_mc1.y = img_mc0.y - img_mc0.height;
		}

		if (img_mc1.y == 0){
			img_mc0.y = img_mc1.y - img_mc0.height;
		}

		this.removeEventListener(Event.ENTER_FRAME,spin);
		var parentObj = this.parent;
		parentObj.stopSlot(this);
	}
}

//停止地点チェック
function checkEndPoint(){
	var i;
	var dis_mim = img_mc0.height;
	var flg_check = false;

	//---スロットをランダムにする
	for (i=0;i<array_pos.length;i++){
		if (img_mc0.y >= 0 && img_mc0.y <= array_pos[i]){
			flg_check = true;
			break;
		} else if (img_mc0.y < 0 && img_mc0.y <= array_pos[i]*-1){
			flg_check = true;
			break;
		} else {
			flg_check = false;
		}
	}

	if (flg_check){
		var dis = Math.abs(array_pos[i] - Math.abs(img_mc0.y));
		if (dis < dis_mim){
			dis_mim = dis;
			posNo = i;
		}
	}

	var fugo = (img_mc0.y < 0) ? -1 :1;
	pos_stop = array_pos[posNo] * fugo;

	//絵柄番号:判定(img_mc0.yが+値の時は、img_mc1の位置が当たりになる)
	imgNo = (pos_stop < 0) ? posNo :(array_pos.length-1) - posNo;
	imgNo = (imgNo == 4) ? 0:imgNo;

	flg_end = false;

	var amari = dis_mim % speed;

	if (amari > 0){
		img_mc0.y += amari;
	}

	flg_stop = true;
}

//停止監視
function checkStopPoint(){
	if (pos_stop == img_mc0.y){
		flg_stop = false;
		flg_remove = true;
	} else {
		var margin = Math.abs(Math.abs(pos_stop) - Math.abs(img_mc0.y));
		if (margin < speed){
			speed = margin;
		}
	}
}

//はみ出しチェック
function ckeckFrameOut(){
	if (img_mc0.y >= img_mc0.height){
		img_mc0.y = img_mc1.y - img_mc0.height;
	} else if (img_mc1.y >= img_mc1.height){
		img_mc1.y = img_mc0.y - img_mc1.height;
	}
}

//ランダムな整数値を取得
function xRandomInt (nMin,nMax) {
	// nMinからnMaxまでのランダムな整数を返す
	var nRandomInt = Math.floor(Math.random()*(nMax-nMin+1))+nMin;
	return nRandomInt;
} 

//Timer開始
function startTimer_spin(interval,count){
	timer_spin = new Timer(interval,count);
	timer_spin.addEventListener(TimerEvent.TIMER,onCheckTimer_spin);
	timer_spin.addEventListener(TimerEvent.TIMER_COMPLETE,onEndTimer_spin);
	timer_spin.start();
}

function startSlot(){
	this.addEventListener(Event.ENTER_FRAME,spin);
}

function onCheckTimer_spin(event){
	var timerObj = event.currentTarget;
	var remain_count = timerObj.repeatCount - timerObj.currentCount;
	if (remain_count <= 3){
		remain_count = (remain_count == 0) ? 1 :remain_count;
		speed = Math.round(speed / remain_count);
	}
}

//Timer停止
function onEndTimer_spin(event){
	//終了フラグ:on
	flg_end = true;
}

そして、これをCanvasで書き出すために、JavaScriptで書き直したものが下記です。こちらも同じくFlash CCのルートのタイムラインに書いています。Canvasでパブリッシュしたサンプルはこちら
【Canvas版JavaScript】

//インスタンスへのMouseOver関連イベントはコレが必須。
stage.enableMouseOver();

var h_img_mc = this.slot_mc0.img_mc1.y - this.slot_mc0.img_mc0.y;

var speed_max = 30;
var array_pos = [0,110,220,330,440];

//startボタン:使用可
useStartBtn(this);

function onClick_start(event){
	//startボタン:使用不可
	nouseStartBtn(event.currentTarget.parent);

	var mc = event.currentTarget;
	var slot = mc.parent.slot_mc0;

	slot.img_mc0.height = h_img_mc;
	slot.img_mc1.height = h_img_mc;

	setRandom(slot);
	slot.startTime = createjs.Ticker.getTime(true);
	startSlot(slot);
	startTimer_spin(slot,1000*(0+1),3);

}

function onOver_start(event){
	var mc = event.currentTarget;
	mc.alpha = 0.7;
}

function onOut_start(event){
	var mc = event.currentTarget;
	mc.alpha = 1;
}

//開始位置をランダムにする
function setRandom(mc){
	var pos_y = xRandomInt(0,mc.img_mc0.height);

	mc.img_mc0.y = pos_y;
	mc.img_mc1.y = mc.img_mc0.y-mc.img_mc1.height;

	//はみ出しチェック
	ckeckFrameOut(mc);

	//スピード:初期化
	mc.speed = speed_max;

	//停止フラグ:初期化
	mc.flg_stop = false;
	mc.flg_end = false;
	mc.flg_remove = false;
	mc.posNo = 0;
	mc.remain_count = 0;

}

//はみ出しチェック
function ckeckFrameOut(mc){
	if (mc.img_mc0.y >= mc.img_mc0.height){
		mc.img_mc0.y = mc.img_mc1.y - mc.img_mc0.height;
	} else if (mc.img_mc1.y >= mc.img_mc1.height){
		mc.img_mc1.y = mc.img_mc0.y - mc.img_mc1.height;
	}
}

//ランダムな整数値を取得
function xRandomInt (nMin,nMax) {
	// nMinからnMaxまでのランダムな整数を返す
	var nRandomInt = Math.floor(Math.random()*(nMax-nMin+1))+nMin;
	return nRandomInt;
} 

//Timer開始
function startTimer_spin(mc,interval,count){
	mc.remain_count = count;
	mc.addEventListener("tick",onCheckTimer_spin);
}

function startSlot(mc){
	mc.addEventListener("tick",spin);
}

function onCheckTimer_spin(event){
	var mc = event.currentTarget;
	var wCount = Math.floor((createjs.Ticker.getTime(true) - mc.startTime)/1000);

	var count = 3 -  wCount;

	if (mc.remain_count == count){
		return;
	} else {
		mc.remain_count = count;

		if (mc.remain_count <= 3){
			if (mc.remain_count == 0){
				mc.removeEventListener("tick",onCheckTimer_spin);
				onEndTimer_spin(mc);
			} else {
				mc.speed = Math.round(mc.speed / mc.remain_count);
			}
		}
	}
}

//回転
function spin(event){
	var mc = event.currentTarget;

	mc.img_mc0.y += mc.speed;
	mc.img_mc1.y += mc.speed;

	if (mc.flg_end){
		checkEndPoint(mc);
	}
	if (mc.flg_stop){
		checkStopPoint(mc);
	}

	ckeckFrameOut(mc);

	if (mc.flg_remove){
		mc.lg_remove = false;

		//停止時の位置ずれ補正
		mc.img_mc0.y = mc.pos_stop;
		if (mc.img_mc0.y < 0){
			mc.img_mc1.y = mc.img_mc0.y + mc.img_mc0.height;
		} else {
			mc.img_mc1.y = mc.img_mc0.y - mc.img_mc0.height;
		}

		if (mc.img_mc1.y == 0){
			mc.img_mc0.y = mc.img_mc1.y - mc.img_mc0.height;
		}

		mc.removeEventListener("tick",spin);
		stopSlot(mc);
	}
}

//停止監視
function checkStopPoint(mc){
	if (mc.pos_stop == mc.img_mc0.y){
		mc.flg_stop = false;
		mc.flg_remove = true;
	} else {

		var margin = Math.abs(Math.abs(mc.pos_stop) - Math.abs(mc.img_mc0.y));
		if (margin < mc.speed){
			mc.speed = margin;
		}
	}
}

//
function onEndTimer_spin(mc){
	//終了フラグ:on
	mc.flg_end = true;

}

//停止地点チェック
function checkEndPoint(mc){
	var i;
	var dis_mim = mc.img_mc0.height;
	//var posNo = 0;
	var flg_check = false;

	//---スロットをランダムにする
	for (i=0;i<array_pos.length;i++){
		if (mc.img_mc0.y >= 0 && mc.img_mc0.y <= array_pos[i]){
			flg_check = true;
			break;
		} else if (mc.img_mc0.y < 0 && mc.img_mc0.y <= array_pos[i]*-1){
			flg_check = true;
			break;
		} else {
			flg_check = false;
		}
	}

	if (flg_check){

		var dis = Math.abs(array_pos[i] - Math.abs(mc.img_mc0.y));
		if (dis < dis_mim){
			dis_mim = dis;
			posNo = i;
		}
	}

	var fugo = (mc.img_mc0.y < 0) ? -1 :1;
	mc.pos_stop = array_pos[posNo] * fugo;

	//絵柄番号:判定(img_mc0.yが+値の時は、img_mc1の位置が当たりになる)
	imgNo = (mc.pos_stop < 0) ? posNo :(array_pos.length-1) - posNo;
	imgNo = (imgNo == 4) ? 0:imgNo;

	mc.flg_end = false;

	var amari = dis_mim % mc.speed;

	if (amari > 0){
		mc.img_mc0.y += amari;
	}

	mc.flg_stop = true;
}

function stopSlot(mc){
	//startボタン:使用可
	useStartBtn(mc.parent);
}

function useStartBtn(target){
	target.btn_start.addEventListener("mouseover", onOver_start);
	target.btn_start.addEventListener("mouseout", onOut_start);
	target.btn_start.addEventListener("click",onClick_start);
	target.btn_start.cursor = "pointer";

	target.btn_start.alpha = 1;
}

function nouseStartBtn(target){
	target.btn_start.removeEventListener("mouseover", onOver_start);
	target.btn_start.removeEventListener("mouseout", onOut_start);
	target.btn_start.removeEventListener("click",onClick_start);
	target.btn_start.cursor = "normal";

	target.btn_start.alpha = 0.3;
}

AS版ではスロット(slot_mc0)MovieClipの中に書かれていたスクリプトも、Canvas版ではルートのタイムラインにまとめてあります。MovieClipの中にスクリプトを書かない方がいいと聞いたのでと、JSだとやはり1ファイルにまとまっていたほうがわかりやすいので。

※その為Canvas版では、【slot_mc0のタイムライン】の冒頭(1〜10行目)で書かれている変数も、固定値のもの(1〜2行目)はルートに書き、その他(3〜10行目)はスロット(slot_mc0)のプロパティにしてあります。また関数には引数でMovieClipを渡すようにしてあります。

で、ここからが本題のCanvas版用にJavaScriptで書き換える時の注意点。

1.オブジェクトの基準点が違う

まず気をつけないといけないのは、Cavnasに描き出されたにMovieClipの座標は、Flash CC上での座標(+)ではなくて中心点(○)であるということ。これを知らないで座標を取得すると訳のわからない位置が表示されてハマります。なので必ずオブジェクトの座標と中心点が重なるようにしておくこと。

【参考】
・Chitose Web: Toolkit for CreateJS 座標がずれる時

2.オブジェクトのwidth,heightが取れない。

そしてもう1つ。DisplayObject.getBounds()という一見オブジェクトのサイズを返してくれそうなメソッドがありますが、これが役に立たないということ。そもそも作られた目的が違うようです。詳しくは下記を参照。

【参考】
・FN1312005 | EaselJS 0.7.1: ColorFilterクラスでインスタンスの塗りのグラデーションを反転させる | HTML5 : テクニカルノート(FumioNonaka.com)
※「03 Shapeオブジェクトに矩形領域を定める ー DisplayObject.getBounds()とDisplayObject.setBound」を参照

またDisplayObjectにwidth,heigitのプロパティがありません。スロット(slot_mc0)をループさせるために中のMovieClipの高さが必要なので、ここではimg_mc0とimg_mc1のy座標の差を高さとして算出し(【Canvas版JavaScript】19〜20行目)、img_mc0とimg_mc1に「height」というプロパティ名で渡しています(【Canvas版JavaScript】4行目)。

※ img_mc0の高さはわかっているので固定値で変数にすることも可能ですが、絵柄を増やして高さが変更になることも考慮して計算で出しています。

3.Timerの使い方

AS版ではスロット(slot_mc0)の回転をEvent.ENTER_FRAMEでやって、Timerを一定間隔で動かして回転速度と停止のコントロールをしていたのですが、EaselJSで該当するTickerは静的クラスでインスタンス化できないので複数作れません。

・EaselJS v0.8.0 API Documentation : Ticker

なので、回転開始時間をcreatejs.Ticker.getTime()で取得してスロットのプロパティに設定し(【Canvas版JavaScript】23行目)、経過時間をチェックして擬似的にインターバル処理を行っています(onCheckTimer_spin/【Canvas版JavaScript】87〜107行目)。

【参考】
・JavaScript – CreateJS(EaselJS)でタイマを管理するTickerクラスの使い方 – Qiita

※JSのTimerを使えばいいのでは?という指摘を受けましたが、書いてる時点ではCreateJSの中のメソッドしか頭になく気がつきませんでした(笑)。そちらの方が、TimerとENTER_FRAMEにそれぞれ対応していていいかもしれません。

4.thisの参照

スタートボタンの制御を関数化していますが(useStartBtn/nouseStartBtn)、ここでも問題が。AS版のようにボタンを「this.btn_start」で参照すると、オブジェクトがないよと怒られます。

[Error] TypeError: undefined is not an object (evaluating 'this.btn_start.addEventListener')

console.log(“this >> “+this)で調べてみると、ルートで参照した場合は・・・

this >> [MovieClip (name=null)]

となり、functionの中で参照した場合は・・・

this >> [object Window]

と返ってきます。

FlashCCが書き出すHTMLを見てみてると・・・

<script>
var canvas, stage, exportRoot;

function init() {
	canvas = document.getElementById("canvas");
	images = images||{};

	var loader = new createjs.LoadQueue(false);
	loader.addEventListener("fileload", handleFileLoad);
	loader.addEventListener("complete", handleComplete);
	loader.loadManifest(lib.properties.manifest);
}

function handleFileLoad(evt) {
	if (evt.item.type == "image") { images[evt.item.id] = evt.result; }
}

function handleComplete() {
	exportRoot = new lib.test02_html5();

	stage = new createjs.Stage(canvas);
	stage.addChild(exportRoot);
	stage.update();

	createjs.Ticker.setFPS(lib.properties.fps);
	createjs.Ticker.addEventListener("tick", stage);
}
</script>

となっていて、Flashからの要素をexportRootにラップしてstageにaddChildしているので、ルートで参照したときのthisの値([MovieClip (name=null)])は、exportRootを指しています。

一方、functionの中で呼び出したものはページのルートであるWindowを指すようです。

ここではカンタンにルートから呼び出す関数にはthisを渡して参照するようにしました(【Canvas版JavaScript】10行目、218〜225行目)。

※他の方法としては、直接exportRootを参照に使うか、stageの中から参照することも可能です。

exportRoot.btn_start.addEventListener("mouseover", onOver_start);
stage.getChildAt(0).btn_start.addEventListener("mouseover", onOver_start);

またはJSのFunction.bind()を使って、thisの参照を書き換えた変数を生成して呼び出す方法もあります。

var func = useStartBtn.bind(this.btn_start);
func(this);

【参考】
・FN1303001 | CreateJSで関数にスコープを定める ー proxy()関数 | HTML5 : テクニカルノート
・FN1303002 – 関数に任意のthis参照を定める – HTML5 : テクニカルノート
・JavaScript の超便利なメソッド bind で this を制御する | Foreignkey, Inc.

5.まとめ

処理 AS JavaScript
オブジェクトの座標(x,y) 座標(+) 中心点(○)
オブジェクトのサイズ(width,height) width,height プロパティなし
ルートのthis参照 root exportRoot
functionのthis参照 root Window
繰り返し処理 Event.ENTER_FRAME Ticker
タイマー Timer Timer(setTimeout/setInterval)

上記の説明は、私の環境のFlash CCで書き出される下記のバージョンのCreateJSを前提としています。

<script src="http://code.createjs.com/easeljs-0.7.1.min.js"></script>
<script src="http://code.createjs.com/tweenjs-0.5.1.min.js"></script>
<script src="http://code.createjs.com/movieclip-0.7.1.min.js"></script>
<script src="test01_html5.js"></script>

【CreateJS】AS3からHTML5 Canvasに書き出す場合のJavaScript(その1)

今更ですが文字通り、swfで作ったコンテンツをHTML5 Canvasに書き出すお勉強です。

Flash Professional CCでは「コマンド」>「AS3 ドキュメント形式から HTML5 Canvas に変換」でCanvas用のファイルに変換できます。基本的なことはこちらから。

・Flash Professional CC:今すぐ試してほしい! 新しいHTML5書き出しADC Plus

これは前から知ってるわけですが、「じゃあJS部分はどう書くか?」ってざっとググっても変換用サンプルに書かれているJSはたいていルートでの「gotoAndPlay」レベル。で、もうちょっと実際に使う形でASに書いたものをどう書き換えればいいのか?というのを少しずつまとめていきます。

screen_test01

 

とりあえず簡単なサンプルで、win_mcというMovieClip(グレーの部分)にbtn(白の部分)というMovieClipが入っている。win_mcは10フレームで、btnをクリックすると1コマずつ切り替わるというもの。swfサンプルはこちら

で、ASはこんな感じ。ルートのタイムラインに書いています。JSに変換する前提なので、型指定はしていません。

win_mc.gotoAndStop(1);

win_mc.btn.buttonMode = true;
win_mc.btn.addEventListener(MouseEvent.CLICK,onClickBtn);

function onClickBtn(event){
	var target = event.currentTarget;
	var num_max = target.parent.totalFrames;
	var num_current = target.parent.currentFrame;
	var num_next = (num_current + 1) > num_max ? 1 : (num_current + 1);
	target.parent.gotoAndStop(num_next);
}

で、これをCanvasで書き出すために、JavaScriptで書き直したものが下記です。こちらも同じくFlash CCのルートのタイムラインに書いています。Canvasでパブリッシュしたサンプルはこちら

//インスタンスへのMouseOver関連イベントはコレが必須。
stage.enableMouseOver();

this.win_mc.gotoAndStop(0);

this.win_mc.btn.cursor = "pointer";
//win_mc.btn.buttonMode = true;
this.win_mc.btn.addEventListener("click",onClickBtn);

function onClickBtn(event){
	var target = event.currentTarget;
	//var num_max = target.parent.totalFrames;
	var num_max = target.parent.timeline.duration;
	var num_current = target.parent.currentFrame;
	//var num_next = (num_current + 1) > num_max ? 1 : (num_current + 1);
	var num_next = (num_current + 1) > (num_max - 1) ? 0 : (num_current + 1);
	console.log("num_current:"+num_current+"/num_next:"+num_next+"/num_max:"+num_max);
	target.parent.gotoAndStop(num_next);
}

JavaScriptの方は書き換えた箇所に、元のASをコメントにして残してあるのでわかると思いますが、まとめると違いは下記の通り。

処理 AS JavaScript
カーソル変更 buttonMode = true cursor = “pointer”
総フレーム数取得 target.parent.totalFrames target.parent.timeline.duration
フレームの数え方 フレームカウントが1から フレームカウントが0から

で、ここが落とし穴だったんですけど、予めStage.enableMouseOver()メソッドを呼出しておかないと、マウスポインタとインスタンスの重なりについてのイベントが起こらないということ。これがないとcursor = “pointer”を設定してもカーソルは変わらない。また・・・

this.win_mc.btn.addEventListener("mouseover", mouseOverHandler);
this.win_mc.btn.addEventListener("mouseout", mouseOutHandler);

のようなマウスイベントも取れません。

こちらの記事が参考になりました。やはり困ったときは野中さん(笑)。

F-site | EaselJSでインスタンスにロールオーバーしたとき指差しカーソルにする

ちなみに私の環境のFlash CCでは、下記のバージョンのCreateJSが使われています。

<script src="http://code.createjs.com/easeljs-0.7.1.min.js"></script>
<script src="http://code.createjs.com/tweenjs-0.5.1.min.js"></script>
<script src="http://code.createjs.com/movieclip-0.7.1.min.js"></script>
<script src="test01_html5.js"></script>

パブリッシュしてもローカルにライブラリがないと思ったら、ネット上のライブラリを参照してるんですね。これも今回初めて気づきました。

【追記その1】2015.3.30
野中さんから指摘を受けましたが、総フレーム数を取得する「target.parent.timeline.duration」は、MovieClipが持っている値ではなくて、「target.parent.timeline」でTweenJSのTimelineクラスを参照して、そのプロパティであるdurationを参照している、ということのようです。

・TweenJS v0.6.0 API Documentation : Timeline > Properties > duration
・EaselJS v0.8.0 API Documentation : MovieClip > Properties > timeline

【追記その2】2015.3.30
パブリッシュするときにCreateJSのライブラリをホスト(ネット上のライブラリ)にするか、ローカル書きだしにするかは「パブリッシュ設定」で変更できます。また書き出しに使用されるバージョンは、Flash CC側で固定になっているそうです。こちらも野中さんからご教授いただきました。

・FN1312001 | Flash Professional CC 13.1のHTML5 CanvasドキュメントとCreateJS | HTML5 : テクニカルノート(FumioNonaka.com)

【Objective-C】調査中のリンクまとめ(主にカメラ周り)

いろいろ調べ物が重なって、ブラウザのタブ開きすぎなのでメモ的にまとめ。ブックマークだと埋もれてしまうので。

【UIImagePickerController. cameraOverlayView関連】
・UIImagePickerController Class Reference
・UIImagePickerControllerの使い方など | ビズリーチラボ
・[Objective-C] UIImagePickerViewController のカメラ上に自作ボタンを置く | 極上の人生

【AVCaptureSession関連】
・5分で作るiOS7風カメラアプリ | Coma’s Tech Blog
・iOS Still Image Capture Using AVCaptureSession | musical geometry
・AVCaptureDevice Class Reference
・UIImagePickerControllerを使わないカメラ機能のサンプルView(iOS4以上) – 西海岸より
・iOSのカメラ機能を使う方法まとめ【13日目】 | Developers.IO

【その他】
・UIActivityIndicatorView を使って画面中央にインジケーターを表示してみた – present

アプリの宣伝についてのリンクなど

アプリの宣伝などについてのリンクあれこれ。とりあえずメモ。

AppBank Network – iOS.Androidアプリの収益化を実現する広告ネットワーク
【保存版】無料でできるアプリプロモーション一覧 | albatrus.com
Programmer’s Report: iPhone アプリ宣伝方法を考える
アプリリリースを無料で告知できちゃう!iPhonePLUSさんの新サービス使ったらランキングがワッショイ!ワッショイ! – あぷまがどっとねっと
ASCII.jp:今すぐできるASOの基本と7つの改善事例 (1/4)|失敗しないスマホアプリ企画&マーケティング

【HTML/CSS】諸々リンクなど

たまにやろうとすると色々引っかかるのでメモ。順次追加予定。

【HTML】
HTML:HTML5でIE8以下のバージョンに対応させる | raining
HTML5で追加されたinput要素のタイプはiPhone、Androidでどのくらい使えるのか | Developers.IO
ほんっとにはじめてのHTML5:[51] セレクトメニューを作ろう <select><option><optgroup>
videoタグでサイト内に動画を埋め込む(IE8対応)
【Web制作】HTML5のvideoタグにはMP4形式動画だけ指定すればよい(IE8対応)@2014年末
IE8でHTML5のvideoタグを使う方法

【CSS】
IE8とIE9できれいに透過を適応させるCSS
【シンプルなソース】CSSだけでアニメーション・ドロップダウンメニュー
横並びリストを中央寄せにする – CSSテクニック – acky info

【JS/JQuery】
jQueryでスマートフォンとタブレットでviewportを切り替える実験(iPhone6 Plus対応) | BlackFlag
実は簡単にできる!PCサイトとスマホサイトを選んで振り分ける方法 | smart4meブログ
[JS][jQuery] 要素の存在を確認する6通りのコードと実行速度 | きほんのき
jQueryでセレクトボックスのoption要素を追加/削除する方法
jQueryプラグイン「ColorBox」でオリジナルの閉じるボタン(Closeボタン)を設置する | blog|blow→in
JavaScript – リンクタグを使用した閉じるボタン記述方法「colorbox.js」 – Qiita

【スマホ関連】
jQuery×HTML5×CSS3を真面目に勉強(4):WebページをRetina対応させるテクニック~基礎知識編 (2/2) – @IT
いまさら聞けないRetina対応のための「ピクセル」の話 – Rriver

【Swift】大重さんのSwiftセミナー私的まとめ(67WS GAT銀座/2014.11.12)

今日はGAT銀座で「【無料】Xcode 6のSwiftがすごい! みんなそろってiOSアプリ開発再スタート!」というセミナーに行ってきました。講師は大重美幸さん。

話の中で一番大事だと思った言葉は「ネットにあるサンプルの多くは、iOS8やSwift1.0以前が多くて最新版だと動かないものが多い」とのこと。

以下、ざっくりまとめです。ちなみに絶賛Objective-C修行中なので、Swiftは未体験。

・Swiftは.h(ヘッダファイル),.m(実行ファイル)の二つではでなく、.swiftファイル1つで書き出される

・StoryboardはiPad画面が基本で、端末ごとのレイアウト画面はない。Autolayoutで配置し、全ての画面に対応するのが基本。
※Storyboardの画面下部、wAny hAnyと書かれた部分をクリックしてレイアウト切り替え可能。このレイアウトが新設されたSizeクラスと関係するらしい。各端末サイズをconpactWidth,conpactHeight、RegularWidth,RegularHieghtで分類するらしい。

・アシスタントエディタ>Previewで、各機種ごとのレイアウトが確認可能。

・Playground(Swift専用)
左側のように書くと、右側に変数や定数の値が表示されるインタプリタなウィンドウなんだけど、何のためにあるのかは出なかったような?(聞き逃した?)

スクリーンショット 2014-11-13 2.19.27

・変数はvarで定義できる。変数は値を設定するだけの型推論で書けるけれど、値を指定しないで定義するときには型指定が必要。
(例)var price:int

・定数はletで定義できる。定義時に値の代入が必須。
※Swiftは定数letを多用することで処理速度を上げている可能性もあるかも、とのこと。

・switch構文が大幅に変わっているらしい(breakが不要。逆に連続させる場合は、fallthroughを書くなど)
【参考】
Swiftの列挙型、switch文、網羅性チェックが素晴らしい!(リンクはセミナーで照会されたものではありません)

・タプル(Tuple)について。
【参考】構造体、列挙型、タプル(3/3):初心者のためのSwiftプログラミング入門
(リンクはセミナーで照会されたものではありません)

・レンジ演算子について。
【参考】Swift言語ガイド第2章基本演算子 – 前を向くために Part3
(リンクはセミナーで照会されたものではありません)

・Optional Valueについて。
【参考】Swift の Optional Value を便利だと思った話 – もくもくろぐ
(リンクはセミナーで照会されたものではありません)

だいたいこんな感じでした。資料は山のように用意されたようですが、15分延長も時間足らず…。是非、レジュメアップ希望します。

大重さんのSwift本(iOS8.1+Swift1.1対応)は、12月には出せるんじゃないかとのこと。

以上ざっとですが、覚えている限りのまとめです。参加された方で間違いなどあれば、ご指摘下さい。

————————————————————

大重さんの本も参考に、先日無事アプリがリリースされました。パチパチ。「アイコン顔カメラ/icon face camera」というiPhoneアプリです。無料なので是非試してみて下さい。

【Objective-C】iOSで音声認識サンプル(UIDictationController)

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.5】

音声認識機能を調べていて、OpenEarsは日本語には今ひとつなので、非公式クラスらしけど、UIDictationControllerを使ってテストしてみました。

【参考】
iOS 5.1 の音声入力を使ってアプリケーションを操作してみる – 24/7 twenty-four seven
【iOS】【JPlayer】再生/停止の切り替えで音声認識をスタートして自動でストップする – The jonki

何故か1つ目のリンクのサンプルはうまく動かなくて、2つ目のリンクの方は音楽プレーヤーに組み込まれているので、音声認識だけを切り出してサンプルを作ってみました。

参考にしたリンクによれば、音声認識はキーボード経由でのみ行われ・・・

1.キーボードを表示。
2.音声入力ボタンを押す。
3.話す。
4.完了ボタンを押す。

という手順を踏んだ後、録音された音声がサーバ経由で文字列変換されて戻ってくるらしいです。

で、上記2.と4.を自動化するメソッドが、UIDictationControllerという非公開クラスに含まれています。

※キーボードは、UITextInputを紐付けた不可視のUIViewを用意して、becomeFirstResponderで表示する。
※UITextInputを使うので、必要なメソッドが定義されていないと、かなりの数のエラーがでます。警告だけど。

このサンプルでは、開始ボタンを押すと音声認識が開始(startDictation)され、timer(4.75秒)もしくは停止ボタンで音声認識が終了(stopDictation)するようになっています。画面はこんな感じ。

IMG_7121

認識率はさすがにいいです。コレ、早く公開APIにしてくれたらいろいろ使えそうなのになあ。iOS5.1からずっと非公開ってことは何かあるんですかね、問題が。

音声認識を使ったアプリのアイデアはあるんですが、OpenEarsで試してみるか、この方式で作っておいて公開APIになるまで寝かしておくか、悩みどころです。

一応、ソースをGitHubに上げてみました。初めてのGitHub(笑)。ご参考までに。

【Objective-C】ランダムな数値を取得する方法

【Xcode5.1.1 + iOS 7.1 + MacOX10.9.5】

配列からランダムな数を取得するというのは、どんなプログラムでもよくやります。

for (int i=[iconArray count]-1; i>=0; i--) {
	int j = rand() % (i+1);
	[iconArray exchangeObjectAtIndex:i withObjectAtIndex:j];
}

一見よさそうだけど、なんとコレ、毎回同じ数値しか返ってきません。このrand()という関数は、毎回初期化しないと同じ値を返すらしい。たとえばこの記事なんかは、初期化をすっ飛ばして書いてあるので、実際に使ってみるとハマるわけです(笑)。

※上の処理だと、初期化がまったく行われていないので、ビルドしたアプリを起動し直しても、ビルドし直しても毎回必ず同じ値になります。

で、初期化を加えたのがコレ。

srand(time(NULL));
for (int i=[iconArray count]-1; i>=0; i--) {
	int j = rand() % (i+1);
	[iconArray exchangeObjectAtIndex:i withObjectAtIndex:j];
}

srand()はrand()の初期化用関数で、毎回違う値を返すことで、返す値が変わることになるらしい。

【参考】
指定した範囲内で乱数を発生させる | Objective-Cでのアプリ開発記

で、こっちのarc4random()は初期化込みの関数で、初期化を意識せずに使える。

for (int i=[iconArray count]-1; i>=0; i--) {
	int j = arc4random() % (i+1);
	[iconArray exchangeObjectAtIndex:i withObjectAtIndex:j];
}

この方法が、初期化ミスもなく一番いいのではないかという結論になるわけですが。

【参考】
[Xcode]ランダム数値の生成 | C++ | alperithm
iPhone-Labo: ランダムな数値を取得する – rand() arc4random()

ググってみるとだいたい「arc4random()使えば問題ないよね」って書いてあるんだけど、そもそもsrand()とrand()が別々に実装されている理由って何なんですかね?そっちの方がすごく気になる。知ってる人がいたら教えてください。

ちなみにココで書かれている、配列を後ろから参照して入れ替える方法をFisher–Yatesアルゴリズムというらしい。初めて聞きました。

【参考】
Fisher–Yatesアルゴリズムがすごかったです。: PandaNoir
Fisher-Yates Shuffle – Faith and Brave – C++で遊ぼう