銀の弾丸、はじめました

Unityとかガジェットとか

Pebbleアプリ勉強会を開催しました

以前からやりたいと思っていたPebbleアプリ勉強会.

突発で10/26(月)に決めて10/31(土)に決行.

さいあく,もう1人の主催者 @itosue ともくもくと何かやれればいいか程度に考えていましたが,計4人集まりました.

e7d8f6d046c4e47266982cdc44.doorkeeper.jp

初級者向けと言う事もあり,スライドもそれなりに作りこんだつもりです. 内容は CloudPebblePebble.js を使っています.( 公式チュートリアルの一部 をそのまま使っています)

参加者は全員エンジニアだと思っていたのですが,営業の方が1名おりました

(HtmlもJavaScriptも分からない方だったので,どの程度吸収できたのか…自分がいたらないばかりに色々中途半端になってしまったかも…)

このスライドをなぞるだけで,のべ90分は使ったと思います(色々と不具合でたり回線繋がらなかったり…)
ハンズオンは絶対予定より押してしまいますね…

時間は13〜18時まで(スライド,その他で半分づつくらいの割合だったかと思います)

スライドとは別に団欒

  • オススメのWatchFaceやApp
  • 普段どのような使い方をしているか?
  • ストラップ(ベルト),保護フィルムの話.
  • TIME LINEについて
  • 日本語パック(日本語化してない方のPTを日本語化しました!)
  • Voiceについて
  • Pebble社の今後の噂

ベストなWatchFace,ベストなApp,ベストな使い方

そんなモノはなくて,皆色々な自分流のベストな使い方をしている印象でした.
(当然,人気アプリなどは使われてるケースが多いですが)
個人的に驚いた使い方は,iPhoneを持っておらず,普段iPadで通知がないからPebble Timeは使える.って方がおりました.
iPadでもPebble Timeアプリを使える事に驚きですが,凄い使い方だと思いました.
iPadでも日本語化できました!!)

CloudPebble

現在はスライドの通り,バグがあるのであれですが,ボクが伝えたかったのが,CloudPebble凄いよ!って事でした.
気になっていた「同一Wi-Fi環境下のCloudPebbleで実機デバッグできるのか?」を2台で検証したところ,ちゃんと機能しました!
回線が遅かった印象を受けましたので,やはりワークショップ・勉強会では各位Wi-Fi環境は用意する方が良さそうです.

反省点

実は今回初めて勉強会を開いたのですが,本当に反省点ばかりです.
スライドも誤字脱字,冗長なコードだらけですし,営業の参加者の方のフォローが中途半端でした.
参加人数が少ないとは言え,もっと参加者サイドに立った考え方で進行するべきでした.
(アンケート機能とかも使わなかったのでレベル感とか当日になるまで分からなかった)
伝達ミスもあり,当日の集合時間も1時間程ズレてしまった(開催が突発過ぎた)

CloudPebbleで実機実行時にメモリエラー
(コンソール青文字)→ Pebble Timeアプリ再起動,Pebble Time再起動で通りました.
(ハンズオンやる場合,直前に再起動させた方が良さそうです.)

次回

次回は前もって募ってもう少し規模感のある勉強会が開ければと思っております.
(やって欲しいネタとかあればコメントなり Twitter なりで連絡ください.)
LTなどを交えながらBLEコンパニオンアプリ連携やTIME LINE使ったサービス・アプリなど,内容の濃いモノや,
初級者向けのPebble.js,CloudPebbleの良さも引き続き伝えられたらと思っています.

お住まい大阪近辺で興味があればメンバー登録だけでも,ボチっとよろしくお願いします.

関西Pebbleアプリ勉強会 | Doorkeeper

都内近郊でもやってくれとお声がけを頂くのですが,ボクがやらなくても東京近郊でなら直ぐに人が集まりそうですよ!!

It's Time!!

f:id:japanetfutan:20151031184426j:plain

Xcode7.1と6.4を共存させると6.4が起動直後に落ちる問題と回避策

Xcode.appをリネームしてXcode6.4とXcode7.1をいつも通り共存させる.

で,普段通りにXcode6.4でPJを開くといきなりXcodeが落ちる. キャッシュ等を削除しても一向に変わらず…でも他のPJは開ける.

で,思ったのが Storyboard

この落ちてしまうPJはデフォルト(Xcode開いてすぐ表示される部分)で Storyboardが表示されるようになってる事を思い出した.

そこで,1度Xcode7.1でPJを開き,Storyboardがデフォルト開かれるので MainViewController.m などの他のファイルを開きXcode7.1を終了(特に保存などはしない)

その後,Xcode6.4でPJを開くとXcodeが落ちずに MainViewController.m が開く.

ここで,Storyboardを開くと

やっぱXcodeが落ちる.

回避策

Storyboardの編集はXcode7.1で行う. あるいはAppCodeとか…

Storyboardに何かあるんだろうけど特にエラーとか出ずに落ちるしなんだかなぁ…

追記

他の環境Mac OS Xでも確認. storyboardだけでなくxibファイルでも落ちる

環境違っても起きたしあちこちで同じような人多そうなんだけどなぁ

2015/10/28 更に追記

と,思ったら似たような事象があちこちで起こっているみたいです.

dev.classmethod.jp

本当に困ったもんです…

2015/11/11 追記

同様の事象が他の方でも起こっているのを確認.
色々とヤバそう…そして7.1.1を入れてもダメだった.
色々キャッシュ消してもダメだったので以下の方法でもう1度やってみたところ,

dev.classmethod.jp

↑このやり方で取り敢えずXcode6.4のみインストールでStoryboadが機能しました.
試していませんが,Xcode7.1.xと共存される場合は以下の方法しかないかもしれません.
怖くて7.1.xインストールできません…

qiita.com

BLEデバイス(Peripheral)のUUID一覧を取得する

iOS Objective-C のメモ

// セントラル
@property (nonatomic, strong) CBCentralManager *centralManager;
// ペリフェラル
@property (nonatomic, strong) CBPeripheral *peripheral;
@property (nonatomic) NSMutableArray *peripherals;
@end

NSMutableArray *a_peripheral;
NSString * const ServiceUUID = @"XXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX";

@implementation PeripheralTableViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSLog(@"PeripheralTableViewController - viwDidLoad");

    self.peripherals = [NSMutableArray array];  // データ用
    a_peripheral = [NSMutableArray array];      // 文字列用

    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self
                                                               queue:nil];
    self.serviceUUID = [CBUUID UUIDWithString:ServiceUUID];
    self.characteristicUUID = [CBUUID UUIDWithString:CharacteristicUUIDHeartRateMeasurement];
}


// セントラルマネージャ状態が変化
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {

    NSLog(@"Updated state: %ld", (long)central.state);

    switch (central.state) {
        case CBCentralManagerStatePoweredOn:
        {
            // スキャン開始
            NSArray *services = @[self.serviceUUID];
            [self.centralManager scanForPeripheralsWithServices:services
                                                        options:nil];
            break;
        }
        default:
            break;
    }
}

// ペリフェラル発見
- (void)   centralManager:(CBCentralManager *)central
    didDiscoverPeripheral:(CBPeripheral *)peripheral
        advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
    NSLog(@"peripheral:%@, advertisementData:%@, RSSI:%@",
          peripheral, advertisementData, RSSI);

    // 単数の場合は以下で良いが,
    //self.peripheral = peripheral;
    // 複数のPeripheral取得には配列に格納する必要がある
    // strong属性の配列(NSMutableArray)に保持
    if (![self.peripherals containsObject:peripheral]) {
        NSLog(@"peripheral.identifier : %@", peripheral.identifier);

        [self.peripherals addObject:peripheral.identifier];

        // <__NSConcreteUUID 0x15e85410> 1174BF05-3D9A-2AD4-40D8-8A35E4D74A4A
        // <__NSConcreteUUID 0x15e8a1c0> 20B403AA-24E0-C70A-48A6-62BD9F40A1E3
        NSString *source = [NSString stringWithFormat:@"%@", peripheral.identifier];

        NSString *pattern = @"(^<.*> )";
        NSString *replacement = @"";

        NSRegularExpression *regexp = [NSRegularExpression
                                       regularExpressionWithPattern:pattern
                                       options:NSRegularExpressionCaseInsensitive
                                       error:nil
                                       ];

        NSString *str = [regexp
                         stringByReplacingMatchesInString:source
                         options:NSMatchingReportProgress
                         range:NSMakeRange(0, source.length)
                         withTemplate:replacement
                         ];
        NSLog(@"これがPeripheralのUUIDだ!!! : %@",  str);

        [a_peripheral addObject:str];

        NSLog(@"self.peripherals -> %@", self.peripherals);
        NSLog(@"a_peripheral -> %@", a_peripheral);
        NSLog(@"self.peripherals count : %d", [self.peripherals count]);
        NSLog(@"a_peripheral count : %d", [a_peripheral count]);
    }

    // スキャン停止
    //[self.centralManager stopScan];

    // 接続開始
    //[central connectPeripheral:peripheral options:nil];
}

加工した文字列用の配列を用意したのは,生のまま出力すると以下の用になってしまうため

(結果1)

self.peripherals -> (
    "<__NSConcreteUUID 0x15e85410> 1174BF05-3D9A-2AD4-40D8-8A35E4D74A4A",
    "<__NSConcreteUUID 0x15e8a1c0> 20B403AA-24E0-C70A-48A6-62BD9F40A1E3"
)

<__NSConcreteUUID 0x15e8a1c0>  の部分が不要で,気持ち悪いので
別途文字列にして加工してUUIDのみ配列に入れてる.

(結果2)

a_peripheral -> (
    "1174BF05-3D9A-2AD4-40D8-8A35E4D74A4A",
    "20B403AA-24E0-C70A-48A6-62BD9F40A1E3"
)

追記(2015/12/04)

どうやら,文字列操作しなくてもUUIDのNSStringを取得できるらしい…

[peripheral.identifier UUIDString]

stackoverflow.com

参考文献

iOS×BLE Core Bluetoothプログラミング

iOS×BLE Core Bluetoothプログラミング

Pebble.jsによる色の指定(色一覧)

Pebble.js の色一覧

 
色の指定は以下の Color Name を指定する.
大文字(アッパーケース),小文字(ローケース)も区別するので注意が必要です.
現在(2015/10/03)Pebble.jsによる色の指定方法がまとまったドキュメントが公式サイトにすらないので,コードを元にまとめました.
(永久保存版かもw)
 
* white, clearWhiteの違いはよく分かりません.
 
 
clear
* clearはPebble Classicだと白,Pebble Timeだと黒になると思います.
black
 
oxfordBlue
 
dukeBlue
 
blue
 
darkGreen
 
midnightGreen
 
cobaltBlue
 
blueMoon
 
islamicGreen
 
jaegerGreen
 
tiffanyBlue
 
vividCerulean
 
green
 
malachite
 
mediumSpringGreen
 
cyan
 
bulgarianRose
 
imperialPurple
 
indigo
 
electricUltramarine
 
armyGreen
 
darkGray
 
liberty
 
veryLightBlue
 
kellyGreen
 
mayGreen
 
cadetBlue
 
pictonBlue
 
brightGreen
 
screaminGreen
 
mediumAquamarine
 
electricBlue
 
darkCandyAppleRed
 
jazzberryJam
 
purple
 
vividViolet
 
windsorTan
 
roseVale
 
purpureus
 
lavenderIndigo
 
limerick
 
brass
 
lightGray
 
babyBlueEyes
 
springBud
 
inchworm
 
mintGreen
 
celeste
 
red
 
folly
 
fashionMagenta
 
magenta
 
orange
 
sunsetOrange
 
brilliantRose
 
shockingPink
 
chromeYellow
 
rajah
 
melon
 
richBrilliantLavender
 
yellow
 
icterine
 
pastelYellow
 
white
 
clearWhite
 
 

Sample

// sample_code.js
var textField = new UI.Text({
  position: new Vector2(18,55),
  size: new Vector2(144, 168),
  font: 'gothic-28',
  backgroundColor: 'oxfordBlue',
  color: 'lightGray',
  textOverflow:'wrap',
  text: 'Hello world.'
});
 

参考サイト

 
 
 

Pebbleアプリ公開時メモ

Pebbleアプリ(Watchapp,Watchface)をストア公開する際のメモ

Pebbleアプリの登録は Developerサイト から行う.

Watchapp公開時に必要なモノ

  • pbwファイル
  • 144x144のpngファイル(多分,PebbleApp Storeサイト用)
  • 48x48のpngファイル(多分,スマフォのPebbleアプリ内表示用)
  • 144x168のpngかアニメーションgifファイル(画面ショット1〜5枚)
  • ヘッダ用イメージファイル720x320 png
  • バナー用イメージファイル(任意)720x320 png(ヘッダと同じのでもいいと思う)

Watchface公開時に必要なモノ

  • pbwファイル
  • 144x168のpngかアニメーションgifファイル(画面ショット1〜5枚)
  • バナー用イメージファイル(任意)720x320 png

Watchfaceの場合は上記の他に,pbwファイル生成前(ビルド前)に28x28のpngファイルをリソース画像として追加する必要がある.(28x27とかでも大丈夫)

CloudPebbleだと,まず RESOURCES で画像を ADD NEW で追加して, Settings -> MENU IMAGE で追加した画像を選択してからビルドすればpbwに画像(アイコン)が内包される.

この画像はWatchfaceアプリの一覧(リスト)の左側に表示されるアイコンになる.

f:id:japanetfutan:20150923143222p:plain

アプリ公開時注意点

Timeline使わないのにTimelineの項目の enable timeline を押してしまうともうだめですw

削除して1からアップロードし直さないとダメっぽい…

f:id:japanetfutan:20150923143224p:plain

Timeline使う予定ないのに,このボタン押したら最後…って言う仕様…
(それのせいで作り直した…)

Pebble.jsについてメモ

ドキュメントはここ.

developer.getpebble.com

実は結構,やれる事少ない…と言うか単純.
でも,ドキュメントが色々と省略されてて色々試行錯誤の連続だった(今も)
なので,メモを残しておく.

Pebble.js使う上で必ず宣言するやつ.

var UI = require('ui');
var Vector2 = require('vector2');  //条件によってはここ不要

親オブジェクトについて

WatchFaceアプリの場合は UI.Window を使い,通常のアプリの場合は UI.Window か UI.Card を使う感じ.
WatchFaceでは Card UI は使えない.(現在は使えるようです.)

var window = new UI.Window();

// または,以下のような感じ

var main = new UI.Card({
   title: 'Pebble.js',
   icon: 'images/menu_icon.png',
   subtitle: 'Hello World!',
   body: 'Press any button.'
 });

Accel(加速度センサ)について

必須コード

var Accel = require(‘ui/accel');
Accel.init()

加速度確認には2種類の方法がある.

Tap系

Accel.on('tap', function(e){
  console.log('Tap event on axis: ' + e.axis + ' and direction: ' + e.direction);
});

または

Window.or('accelTap', function(e){
  console.log('Tapped the window');
});

上記2種はWinodwを対象にしているかそれ以外(Cardとか?)を対象にTapのイベントトリガを設置するイメージ.

Tapとは叩くイメージがあるが,ここでは加速度が反応する行為(腕,手首を素早く振るなど)とする.
Tapイベントトリガは省電力.

Accel.on

常に加速度センサを監視して情報を取得するイメージ.

Accel.on('data', function(e) {
  console.log('Just received ' + e.samples + ' from the accelerometer.');
});

または

window.on('accelData', function(e) {
 console.log('Accel data: ' + JSON.stringify(e.accels));
});

ミリ秒単位で3軸データを取得する事も可能.
電池消費量は激しいがより細かいデータを収集できる.

また,加速度の反応する基準を Accel.config() で指定する事が可能.
(特定の座標以上の時,特定の動きの時にイベントを走らせるなどの事が可能)


その他いろいろ微妙に苦労した点

Windowフルスクリーン

var window = new UI.Window();
window.fullscree(true);

これで上部バー(hh:mm)部が非表示になり,座標全体が上にズレる!

setTimeout

n秒後にxxさせたい時に使う.

通常のJSの場合

function hogehoge(){
  console.log('hogehoge');
}

setTimeout('hogehoge()', 3000);

となるが,Pebble.jsの場合は以下のように書く必要がある.
メソッドとして外出しできないっぽい…(文字列として書く必要もない…てか書いたらエラー)

Pebble.jsの場合

setTimeout(function(){
  console.log('hogehoge');
}, 3000);

Ajax

必須コード

var ajax = require('ajax');

JSはデフォルト非同期なので色々躓く部分があるが, async オプション false で簡単に同期処理解決できる.

ajax(
    {
      url: URL,
      type: 'json',
      async: false
    },
    function(data) {
      // Success!
      console.log('Successfully fetched weather data!');

      // Extract data
      var location = data.name;
      var temperature = Math.round(data.main.temp - 273.15) + 'C';

      // Always upper-case first letter of description
      var description = data.weather[0].description;
      description = description.charAt(0).toUpperCase() + description.substring(1);

      var date = new Date();
      var hour = date.getHours();
      var minutes = date.getMinutes();
      textField.text(location + ', ' + temperature + '\n' + description + '\n\n' + hour + ':' + minutes);

    },
    function(error) {
      // Failure!
      console.log('Failed fetching weather data: ' + error);

      return error;
    }
  );

font,colorなどの指定方法について

色のサンプル,名前は以下で確認できる.

developer.getpebble.com

Pebble.jsで色指定する際の特徴

  • キャメルケースっぽい.
  • 色の(フォント名も)スペースは詰める.
  • 意味が変わる部分の間は - (ハイフン)を挟む.

* 残念ながら現状ではCSSのように16進数表記の指定ができない.

(ラッパー作ったら需要ありそう,存在しない色は近似値的な色にする的な)

var textField = new UI.Text({
  position: new Vector2(18,55),
  size: new Vector2(144, 168),
  //font: 'gothic-28-bold',
  font: 'gothic-28',
  backgroundColor: 'black',
  color: 'lightGray',
  textOverflow:'wrap',
  //textAlign:'center',
  text: ''
});

デフォルトのシステムフォント

以下は公式のサンプルコード.

github.com

ただし,数値のみしかないフォントも多い.
デザイン重視のアプリはカスタムフォント必須かも.

色について

Pebble C の場合 APLINEBASALT で色を分けないとAPLINE で BUILD ERRORになる.
(APLITEの画面表示は2値なので白か黒以外の色の指定は出来ない)
しかし, Pebble.js の場合, BASALT 向けに lightGray と書いても APLINE で勝手に白になる.便利!

whiteを指定した場合の結果(左が APLINE で右が BASALT
f:id:japanetfutan:20150918162852p:plain f:id:japanetfutan:20150918162854p:plain

lightGrayを指定した場合の結果(左が APLINE で右が BASALT
f:id:japanetfutan:20150918162848p:plain f:id:japanetfutan:20150918162850p:plain


自動で lightGraywhite になっているのが確認できる.

Pebble.jsで使う色一覧を別エントリーにまとめました.

dvorak.hatenablog.com

memo.

APLITE : Pebble Classic,Pebble Watch
BASALT : Pebble Time

Settings(設定・コンフィグ周りの処理)

var Settings = require('settings');
  • Settings.config で設定画面への遷移,戻って来た(設定画面閉じた)時の処理を記述できる
  • 設定画面からの戻りの処理(鯖のWeb)はPebble CのJSと同様, pebblejs://close# な感じ

developer.getpebble.com

何か,勝手にオプション更新してくれる

指定した設定用ページのURLに対して

以下のオプションを勝手に付与してくれる(実際以下は「"{}」も含め,エンコードされてる)
?#{"start":null,"end":null}

デコードして,Hashの頭の # を消して, JSON.parse(HASH_DATA) するとJSONデータとしてサーバー側で扱える.

var h = decodeURIComponent(document.location.hash).substr(1);
//alert(h);
h=JSON.parse(h);
Pebble.js側は本当に勝手に色々やってくれるので,サーバー側でKeyとValueをセットする部分さえ作ればいい.

仕組みとしては,設定にstartとendと言う項目があったとして,
Pebble.js側は何もしなくても勝手にデータを上書きして更新してくれる.
(機能としてはJSのlocalStorageを使っている)

* 初回アクセス時にエラーになるので当然,宣言,初期化は必要.

サーバー側htmlでUIWebを閉じる際に 'pebblejs:close#'+encodeURIComponent(JSON.stringify({start:'value1', end:'value2'})); となっていれば,
あとはPebble.jsが勝手にKeyとValueを保存してくれる.

var options = {start:'value1, end:'value2'};
var location_uri = "pebblejs://close#" + encodeURIComponent(JSON.stringify(options));
document.location = location_uri;

次(2回目以降)に設定htmlを開く際にもPebble.js側は何もする必要がなく,設定ファイルを開くだけでいい.
(勝手に保存されているlocalStorageのKeyとValueを全てJSON StringにエンコードしてURLの語尾に付与する.)

この勝手にURL語尾に付与する部分とか最初気付かなくて色々悩んでた…Pebble.jsさん勝手にやり過ぎてて困る…
凄いんだけど,もっとDocumentにちゃんと記載してくれよ(;・∀・)

Settings.option

データの保存は内部的にJSのlocalStorageの上位層?に作られてるっぽい.
Settings.option を使ってるパラメータの保存,削除などを行う.

 Settings.data

Settings.option に影響させない(パラメータ以外のデータ?)については,
Settings.data を使う.(ネイティブJSのlocalStorageよりも Settings.dataの利用を推奨している)


まとめ的な

ググるとPebble C で Watchfaceアプリ作る的な情報が多い.
Pebble.jsはWebフロントエンドエンジニアやJS触れるデザイナーにも扱えるので是非挑戦して欲しいと思いました.
CloudPebble 使えばエミュレータIDEも込みなので0円で,ブラウザのみでアプリがJavaScriptで作れちゃいます.
Pebble.js自体はまだ一応βですが,最初の「何だかよく分からない」部分がボトルネックになってると思うので,そこを少しでも取り払えたらな,と思いました.

CloudPebbleについてメモ

CloudPebbleはちょっとクセのあるブラウザIDE

CloudPebble

凄く便利な部分もあるけど,ちょっとダメな部分もある.
ハマった部分などをメモっておく.

SETTINGS

項目名 内容
USES LOCATION 位置情報を使う場合,ONチェック
CONFIGURABLE 設定を使う場合.ONチェックするとアプリにギアマーク付く.

Ctrl(Command) + F で検索

使い難い!
現状だとコード長くなるとちょっとつらいですね.

Ctrl(Command) + S で保存できるけど…

なんと実行のショートカットキーがない.
なので Ctrl(Command) + R で実行できるGoogle Chrome Extensions書いた.

github.com

ただ,よく事故ってページ更新になってしまう…Alt + Rとかに置き換えようか…

TIMELINE(PREVIEW)って??

デフォルトでサンプルコード(JSON)が書かれてるだけ.
TIMELINE機能を使わない場合意味はない.

エミュレータは共通

ブラウザの別タブで別PROJECTを開いていてもエミュレータは共通に動いてしまう.
(別ブラウザを使ってもエミュレータは共通になる…IP単位?)

エミュレータで加速度を使う

SDK3.4 から使えるようになった.
母艦のスマフォでエミュレーションする.
Pebble本体がなくてもPebble単体アプリであれば,全ての機能がエミュレータで開発・デバッグできるようになった.
(設定画面の確認レベルでエミュレーションできる…設定値反映は無理っぽい?)

昔のPebble Watchもエミュレータで動かせる

COMPILATIONEMULATOR で BUILD後に INSTALL ON APLITE で実行できる.
INSTALL ON BASALT はPebble Timeのエミュレータになる.

* ただし,アプリの設定でAPLITEにも対応させる必要がある.
  SETTINGSBUILD APLITE にONチェック.
  更にコードでも処理を分ける必要がある…

f:id:japanetfutan:20150914110156p:plain

  Pebble.js の場合,ここを意識する必要はない(項目自体がない)


CloudPebbleの挙動がおかしい時

  • ブラウザのタブ閉じて新規タブから開き直す.
  • iOSのPebbleTiemアプリで開発中のアプリを実行前に削除
  • iOSのPebbleTimeアプリのDEVELOPER(ON) ->  Enable Developer Connections を一旦 OFF にして直ぐ ON

上記3つは結構使った…WatchFaceアプリだと頻発し易かった印象を受ける.


CloudPebbleのビルド,インストール,実行とかのまとめ

SETTINGSを変更した場合,1度 RUN BUILD してから INSTALL AND RUN する必要がある!

INSTALL AND RUN ができるのはコード部分のみ)

先にBUILD!!

と,言うか,普通にコード変更してもまずはビルド.
エディタ画面の実行ボタンが特殊で1ボタンだけで 保存 -> ビルド -> インストール -> 実行 している.