ラベル iOS の投稿を表示しています。 すべての投稿を表示
ラベル iOS の投稿を表示しています。 すべての投稿を表示

2019年3月22日金曜日

SwiftからMetalへFloatの値を渡す(仮)

縦横比は無視(こういう演出なので)

(仮)がついているのは、「とりあえず動いたけど正しいかわからない」というツッコミ待ちフラグを示します。堤修一さんの「Metal入門」を読んでいることが前提です。

最初に結果を書きます。そのあとはほぼ雑談です。

まず、swift側のコード。drawの中です。valueが渡したい値で最初にバッファを定義して
        // バッファを作成
        var valueBuffer: MTLBuffer!
        let value:[Float] = [0.5]
        let size = MemoryLayout.size
        valueBuffer = device.makeBuffer(bytes: value, length: size)
次に生成したエンコーダーにセットします。
        renderEncoder.setFragmentBuffer(valueBuffer, offset: 0, index: 0)
.metal側
fragment float4 fragmentShader(ColorInOut       in       [[ stage_in ]],
                               texture2d texture  [[ texture(0) ]],
                               device float     *fBuffer [[ buffer(0) ]])
{
    float f = fBuffer[0];

fに値が入ります。めでたしめでたし。

[[ ]] の中の修飾子がどういう働きをしているのか理解できていなかったんですが…これ、GPUとCPUの共有メモリにtexure[0], [1], [2]....、vertex_id、buffer[0], buffer[1], buffer[2]....のような領域があって、
device float     *fBuffer [[ buffer(0) ]]
って書いた場合、「0番目のbuffer領域のアドレスをfloatのポインタfBufferにセットする」って意味なんですね。で、Swift側ではサイズとx番目かを示してデータをセットすれば、metal側ではそれをポインタ渡しみたいな形で受け取れる。

わかってしまえば簡単なんですが…いやー、苦労したたたた。

■背景■

「Metal入門」という本を読みつつ、Metalのお勉強をしています。お勉強中ですが、同僚のM氏から「作って」と言われている課題をこなさなければいけないので必死です。

その課題は撮影した画像をRGBとグレイに分割して、それらを重ねつつ動かすというやつです。CIFilterなどを探してみたのですが、どうにも上手く行かず、Metal様におすがりしました。

で、「Metal入門」を拾い読みして(それがいかんのだ)、fragment shaderを使えば画像の任意の点の色情報を取得し、それを最終的な描画位置の色として加工して返す、という処理が記述できることがわかった…のは良いですが、アニメーションとして処理したいのでCPUからパラメータを渡す必要があり、その方法を探していた次第です。

この記事が誰かの役に立ちますよーに。

■余談■

MSLでもif文書けるんですね。やってみたらエラーでないで意図した通りに動いたので思わずユーレカーと叫びながら表を走ってしまうところでした。ベクトルプロセッサだから無理だろと思って無駄な苦労してました。もちろん、GPUを使う上ではもっと効率のいい方法なんかもあるんでしょうけども、不勉強な凡夫として助かっております。

とりあえずfragmentShaderの実装については「今度、ここの座標の色を教えろや」って上位からドットの数だけ呼ばれるので、textureとして受け取った画像からsamplerを使って元画像の色の値を取り出し、そのカラー値(r, g, b, a)を返せば元画像がそのまま描画されるし、color = float4(red, 0, 0, 1)を返せば赤プレーンだけの値が返される…ということがわかって、そこから何とか書けるようになった感じです。

いやー…奥が深い。私基本的に業務系アプリ屋なんで、Metalのお勉強してもあんまり役に立たないんですけどねw でも、何かあったらお仕事いただければ幸いですm(_ _)m

2019年2月6日水曜日

iPhoneアプリのお手伝い

真面目にiOSの勉強をしていたのはiOS 8の頃までだったので、最近のSwift4なんて全然浦島太郎状態なのですが、こういう変なことを頼めるのは倉橋さんしかいない!と同僚さんに言われてお手伝いしました。

…「難しいこと」じゃなくて「変なこと」指名ですかい…ありがたいことですw

やったのは、動画にエフェクトを入れる処理。何本かCIFilterを使って、その後作ったのがこれです。


CIDetectorはかなり重い処理なのでCIDetectorAccuracyLowで使えたらと思うのですが、Highじゃないとそもそも結果が返ってこないんですよね…。描線と角度を一致させるためにfaceAngleを見ているのですが、たまにしか値が返ってこないので、左右の目の位置からatan2で角度を求め、線画の座標を変換してベジェ曲線で書き込みました。

何年かに一度Swiftを使うんですが…そのたびにバージョンが上がっていて、まずは差分の習得からやることになるので、手間取ります。

2016年10月25日火曜日

ESP+Tilt & Panステージ+放射温度計


新しいESP基板が出来たので、以前作ったものを作り直してみました。上の写真は新しいモジュール、下が旧バージョンです。

旧バージョン
旧バージョンはTilt & Panステージの上にセンサーだけ載せていたのでESPとの間に配線が10本走っていたのですが、今度はESP基板もステージ上に置けるので外部からの配線は電池ボックスからの電源2本だけです。

なお、このPan&TiltはElecrowで$12で売ってます。同じお店の$11のものとどこが違うのかわかりません(笑)。

サーボの線をもっと短くすればよかった…

ステージとESP+センサーの固定、前作ではテグスでグラグラと縛っていただけでしたが、今度は3Mの「ツマミを引っ張ると簡単に剥がれる両面テープ」を使いました。コンクリートの壁などにも貼れる強力なテープですが、基板の下からハミ出ている白いベロを引っ張ると簡単に剥がれます。

iOSアプリは前回と同じ。iOSテザリングでESPと接続しています。データ転送はUDPです。


センサーはMXL90614、赤外線放射温度センサーです。今回使った基板はVin, Gnd, SCL, SDAの順で並んでいてESP基板の並びと合わないので、隣接するIO12とIO13から digitalWrite(12, Low); digitalWrite(13, HIGH); で電源供給しています。サーボはIO15, IO16です。



■CM■

ここで使用した新ESP-WROOM-02基板Board4、スイッチサイエンス社から発売中です。

なお、Board2とBoard3はお星様になりました(違

2016年2月21日日曜日

ESP-WROOM-02+放射温度計+サーボ = 遠隔温度計測


autolayout面倒くさい

■やりたいこと■

赤外線放射温度計というものがあります。リフロー用に2000円で買ってきて、リフローだけでなく餃子を焼くときのフライパン温度やチャーハンに卵を投入する前のフライパンの温度やカラアゲを作る際の油の温度計測などに幅広く利用しております。

このセンサー部分が手に入りました。センサーを以前使ったサーボモータのチルト&パン台(Crowtail- Pan- Tilt)に固定しておき、遠く離れたところからiPhoneで角度を変えるとその方向に首を振り、計測した温度を送り返してくれる…というものを作ります。

以前サーボモータを動かした時には、単に首振ってるだけで考えてみればESP-WROOM-02を使うメリットというのはまったくなかったのですが、今度はしっかりとセンサーを駆動します。

リモートセンシングやで。

■要素技術の確認■

赤外線放射温度センサーMLX90614のライブラリ、いろいろ試しましたが、一発で動いたのはコレでした。
https://github.com/adafruit/Adafruit-MLX90614-Library

MLX90614のピンの並びは残念ながら手持ちのESPボードと異なっているので、変換ケーブル?を作ります。単にGND-VCC-SDA-SCLをそれぞれの並びに合わせてヘッダピンにハンダ付けしてスミチューブで固定するだけです。

サーボは以前にも使ったElecrowのtilt&panです。サーボのコネクタはマイコンでは使いにくいのでぶった切ってピンヘッダと付け替えます。その際に2ch分をコンパクトに使えるように3ピンと4ピンのヘッダを使い、GNDとVCCは共通、信号線は1つずらして接続します(GND-VCC-SIGNALとGND-VCC-NC-SIGNAL)。こうすればブレッドボードに並べて挿して共通の電源とそれぞれのSIGNAL入力をつなぐだけで済みます。

制御入力と表示にはiPhoneを使います。最初iPhoneをうにうに傾けるとそれに合わせてtilt&panが動くようにしたのですが、表示が大変見づらいという欠点が発覚したので画面上の●をドラッグする方式に変更しました。●の座標をTCP/IPで値を送ります。


あとはESPとiPhoneの通信ですが、ゼロコンフィグとかやると死ぬので固定IP決め打ち一発でTCP/IP接続してデータを送受信します。TCP/IP通信にはCFNetworkのinput/output streamを使ってます。

■サーボとの接続■

ESP-WROOM-02にサーボとMXL90614を接続します(前回記事:ESP + 2軸サーボ)。

ただ、前回ワイヤ直結したら抜けやすくて大変不便だったので、crowtail仕様のコネクタを切断してピンヘッダを取り付けました。その際、3ピンのピンヘッダと4ピンのピンヘッダを用意し、4ピンのは「信号」「空き」「Vcc」「Gnd」、3ピンは「信号」「Vcc」「Gnd」の並びで作りました。こうすれば、ブレッドボードに挿した時に配線が少なくてすみます。
手前が4p、奥が3p。右からVccとGndを引いてます

IO14, IO15はジャンパ経由でESPと接続
テープで剥がれる机の塗装orz

■MLX90614との接続■


これはI2Cなので、Vcc, Gnd, SDA, SCLをそれぞれ接続します。今回購入したモジュールはプルアップ抵抗付きなので、ESPとつなげばおしまいです。これもヘッダピンとワイヤをハンダ付けして専用ケーブルを作りました。ESPのIO4とSDA、ESPのIO5とSCLを接続してください。

なお、私はワイヤの色をSCL白、SDA緑、Vcc赤、Gnd黒と決めています。

こっち見んなw(お約束


■MLX90614とサーボ■

3Dプリンタが届くのを待っていたのですが、3月一杯に延期されてしまったので、とりあえずテグスで括りつけました。ははは(力ない笑い

■ESP用プログラム■

ESP_WiFiTelnetToSerialというサンプルプログラムを参考にしました。大まかな構造としては、

  • iPhoneからデータが届いたら、それを目標座標とする
  • 1秒間に100回のタイマーで、サーボの目標座標目指してゆっくり指示座標を変えていく。これはいきなりiPhoneからの値が飛ぶとサーボが高速で動いて電力消費やらメカ強度やらに悪影響を与えるので、それを防止するためです。通信でメカを動かす場合の基本ですね。
  • 1秒に1回、MLXからデータを読み込んでiPhoneへ送信する

サーボの制御と温度データの送信を切り離しています。こうすることでプログラムが複雑にならないようにしています。



■iPhone用プログラム■

最初swiftで書いてSIOSocketがうまく組み込めずに挫折した(設定ミスorz)とか、その後obj-cでSIOSocket使ってみたけどしっくりこないので捨てた、というのは割愛します。

プロジェクトは後日githubに公開します。とりあえずViewControllerと座標ドラッグ用のControlViewをgistにまとめて貼っておきます。今気づいたけど、ControlViewじゃなくてControllViewだな(恥


2015年1月24日土曜日

スマートフォン クイックジャック ソリューション



スマートフォン クイックジャック ソリューション (Quick-Jack Solution) OM13069は秋月などで売っているスマートフォンのマイク/ヘッドフォン端子に接続するI/Oモジュールです。モジュール上にはLPC812が乗っていて、音声端子を通じてマンチェスター符号(TJさん、ご教示ありがとうございました)で通信するだけではなく、音声出力から電力ももらおうという素晴らしいデバイス。

最初、つないでも反応しない!?と焦ったのですが「ボリュームを上げないとダメよ」とのこと。ボリューム上げたらLEDが点滅始めました。なるほど、信号だけじゃなくて電力としても使っているんですね。

動作を確認したところで、さてiOS用のSDKを落としてこようと思ったら…iOS用は公開されていませんでしたorz Android用は提供されているのですが、iOS用はサポートに連絡してくれ、とのこと。

週末遊ぼうと思ったのに…まぁ買う前に調べておけって話なんですけどもw

2014年9月12日金曜日

Chilkat Softwareさんのftp2ライブラリ for iOS objective-c

まぁ、いろいろあって、iPad用に急遽日本語エンコーディングを明示的に指定できるライブラリが必要になった次第です。

以前から目をつけていたのはChilkat Softwareさんのライブラリ。まぁ通信系のライブラリを探していてこのバナーを見たことのあるヒトは少なくないのでは。


当該プロジェクトでも当初コレを使おうと提案していたのですが、当時はsftpを使う予定でSecureな部分が輸出規制にひっかかるためボツになったという経緯がありました。

で、今回背に腹は変えられず、ともかく動作確認のためのプロジェクトを作ってみました。

■評価版ライセンス■

static libraryの形で配布されており、30日間、シミュレータ上でだけ動作させることができます。

このページからダウンロードリファレンスなどへ飛べます。とりあえずはリファレンスObjective-C FTP Examplesのページを見るのが一番手っ取り早いと思います。

■リファレンスをざっと見る■

よくある通信系ライブラリと同様、まず処理のためのインスタンスを作り、そこにメッセージを送っていくというタイプです。Propertiesを眺めていくと、ftpってこんなに色々あるんかいwってぐらい機能が沢山あります。

今回問題になったencodingに関しても、CommandCharsetとDirListCharset propertyがあります。ディレクトリ作成などに作用するCommandCharsetにansi, SJIS, UTF8などを代入すれば、FEATでサーバと相談した上で適切なエンコーディングを実施してくれます。lsでディレクトリを取ってくる場合のencodingはDirListCharsetを使います。

ftp.CommandCharset = @"SJIS";
ftp.DirListCharset = @"SJIS";
NSString *xml = [ftp GetXmlDirListing:@"*.jpg"];
if  (xml == nil) {
    // エラー処理
}

って感じです。ディレクト作成やget/putなどに関しては、予めカレントディレクトリを設定してから操作することも、フルパスで指定することもできます。今回は同期しか試しませんでしたが、非同期も用意されています(何でもかんでも非同期にしてしまうのではなく、getfile/putfileが対象…かな? AcyncGetFileStart/AsyncPutFileStart)。

■動かして見る■

例によってロクにドキュメントも読ますに、「ダウンロードしてきたライブラリをプロジェクトにドラッグ&ドロップすりゃ動くだろw」とやっていたら案の定ハマりました。

上のライセンス条項に「シミュレータ上でだけ」とあります。つまり評価版として使えるのはi386 static libraryだけなのでプロジェクトにドラッグ&ドロップするのは「cpp_include」「include」「lib/i386」だけにします。TARGETS > Build Settings > Architectures > Valid Architecturesには「i386」だけ設定します。

なお、プロジェクトへのライブラリのドラッグ&ドロップで試行錯誤してたら、Linkerへのpathにゴミが溜まってwarningの嵐になったりしますのでご注意を。ライブラリを一旦削除してもpathの設定がそのまま残っちゃうんですよね。

今回評価目的だったので使いませんでしたが、ダウンロードしたファイルには各バイナリ向けを1つに束ねたunivarsal static libraryにするためのshスクリプトも付属しています(リンク先の5)。コマンドライン一発でエラーもでません。

【追記】その他、c++を通すための設定が必要なのですが、サポートページにも書いてあるようにダミーのcppクラスを追加するのが一番手っ取り早いです。File▶New...▶File...▶iOS/C and C++▶C++ Classあとは「DummyClassForChilkat」など適当な名前を付けて保存します。

あとはコードを書いて動かすだけ。API名やエラー戻り値もすっきりしていて、サクサク書けます。余談ですが、API名に美しい規則性と論理性があると書いてて指が止まらないよね。好きなライブラリです。

今のところ私が個人でこのライブラリを使う予定はないのですが、何か通信系の案件が立ち上がったら真っ先にこれ使います。

ちゃんとしたftpサーバが相手なら、AppleのSimpleFTPSampleでも使われているCFFTPStream系でも用が足りるんですけどね…。あー疲れた。MetaWareとかで遊んで疲れを癒やしたい。

MetaWareをJack-inする電脳たれぱんだ

■余談ですが■

今週の水曜日が最終出社日だったんですが、本番サーバでのエンコーディング問題が発覚したのが先週木曜日。それから怒涛の日々だった。水曜日の午後から退職前の有給休暇消化期間だったけど、「それなに?美味しいの?」状態。PMのYさんがいい人なので、とりあえず今週は無報酬でお手伝いするって伝えたんですが…しんどかった。

ちなみに会社都合で有給休暇を取り消して作業させた場合には、休日出勤とみなされて6割増し以上の賃金を支払う義務があるんですよね(苦笑)。

2014年8月18日月曜日

@autoreleasepool { }

久しぶりのiOSネタ。

ALAssetから引っ張ってきたPNG(スクリーンショット)画像をUIImageViewに表示して、これをボタンで高速でめくっていくと「メモリ足りない」って落ちる現象に遭遇。

ゆっくりページめくりしている時は大丈夫。

【ALAssetからdefaultRepresentationを取得し、その後UIImageに変換してUIImageViewにセットする】だけの簡単な処理だけど、高速でめくるとreleaseする前に次の画像を取ってきてしまうので落ちる。落ちる前に手を止めると、メモリのグラフがガタガタと下がっていく。

一瞬、「めくる速度を制限しようか」という悪魔の声が聞こえたけど、それは最後の手段として取っておくことにした。

でも、__weak試したりnil代入しても状況は変わらない。

最終的には上記【】の処理を@autoreleasepool {}で囲って強制的に掃除したらスクリプトで超高速めくりを実行してもメモリサイズは安定したままで落ちなくなった。

いやはや。

それにしても、どうしてこういう問題は、定時で帰る5分前に発生するのでしょうか。15分ぐらいで解決できたから良いんだけど。

2014年2月1日土曜日

Force.com mobile SDK for iOS 2.1でusernameを取得するには?

長野新幹線の車窓から、浅間山?
Chatterへ「いいね!」を送る場合などにはuser idが必要です。そのために以前のSDKでは、
AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *userId = [[[app coordinator] credentials] userId];
なんて書いていましたが、今は
#import "SFAccountManager.h"
#import "SFIdentityData.h"
(略) 
NSString *userId = [SFAccountManager sharedInstance].idData.userId;
と書くそうです(Query for username in Salesforce iOS SDK)。

SFAccountManagerなんてSalesforceのClass Referenceに出ていないですが、SFIdentityDataは掲載されています。

ではでは。

2014年1月31日金曜日

Force.com mobile SDK 2.1.0 for iOS(追記)

■まずインストール■

インストールの方法と基本的な操作はここに書いてあります。

forceios
Utilities for creating mobile apps based on the Salesforce Mobile SDK for iOS

2.0まではgitからのcloneでしたが、2.1になってnpmが用意されたようです。自分のアカウント用として作るのであれば、適当なディレクトリに移動してから
    $ sudo npm install forceios
を実行すると直下にnode_modulesディレクトリが出来、その下にインストールされます。

Macの全てのユーザから使えるようにするには
    $ sudo npm install forceios -g
いずれもターミナルから実行します。この場合は/usr/local/lib/node_modules下にパッケージがインストールされます。

以下、-gの付いている方を前提とします…Terminalでパスなど切らなくて良いのでラクですし。インストールが終わったら、
    $ forceios version

    forceios version 2.1.0
と表示されればインストールOKです。

■まずはSamples■

もうSampleは見なくていいよ、という方は次の段落へどうぞ。これもターミナルでの作業です。まず、適当なディレクトリに移動してから
    $ forceios samples -outpoutDir=Samples
とすれば、直下にSamplesというディレクトリが作られ、さらにその下にサンプルプロジェクトが並びます。



あとは各フォルダの中にある .xcodeproj をダブルクリックしてXcodeで開き、どういう作りになっているか覗いてみてください。

■プロジェクトの作り方■

これもターミナル上での作業です。プロジェクトを置くディレクトリに移動します。少し長いですが、forceiosのcreateコマンドを使います(実際は1行です…nativeの場合apptypeは省略可能です)。
    $ forceios create --apptype=native --appname=TestForce210
    --companyid=com.your.domain --organization=あなたの会社名 

リターンキーを押すと、プロジェクトを作るディレクトリ、App ID、App Callback URI()を聞いてきます。全部リターンキーでOKです。
    Enter the output directory for your app (defaults to the current directory): 
    Enter your Connected App ID (defaults to the sample app's ID): 
    Enter your Connected App Callback URI (defaults to the sample app's URI): 
数秒間メッセージが流れて、最後にこれが出ればOKです。
    Congratulations!  You have successfully created your app.
できました。では、TestForce210/TestForce210.xcodeprojをダブルクリックします。さっきリターンキーで飛ばしたApp IDはProject情報で設定し直すことができます。また、Callback URIとRemoteAccessConsumerKeyも今までと同様、Classesフォルダ下のAppDelegate.mで正しい値に設定することができます。cmd+Rでビルド&実行してみます。スプラッシュの後、認証画面が出て、さらにその数秒後にさっき認証した組織のユーザ名一覧が表示されれば完了です。

さっくり見てみましたが、ARCは前回対応されたものの、Storyboardはやっぱりまだですね…。Storyboardを使うには、AppDelegateの - (void)setupRootViewControllerを変更します。以前書いた方法と少し違いますが、基本的なやり方は同じです。

追記2014/01/31:Storyboard上ではUITableViewControllerにnavigation controllerをEmbed InしてAppDelegate.mの -(void)setupRootViewControllerで以下のようにrootViewControllerにセットするとStatusBar / TopBarが正しく動作します。

- (void)setupRootViewController
{
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard"
                                                         bundle:[NSBundle mainBundle]];
 
    UINavigationController *navi = [storyboard instantiateViewControllerWithIdentifier:@"naviView"];
    self.window.rootViewController = navi;
}

さて、世界がぶっとぶようなアプリでも作るかねw

2013年7月10日水曜日

iPhoneのキーボードが出た時にUITextFieldの高さを変える方法(iOS 6)

よく、ブログなどに書かれているのは、「出てきたキーボードの高さ分をUITextFieldのheightから引いてやる」って感じだけど…それだと英語キーボードから日本語キーボードに切り替え、さらに予測変換窓が出た時に過剰に小さくなってしまったりします。

ということで、それを回避できるコードを張っておきます。UITextFieldが画面一杯に貼ってあるのが前提です。

■準備:Notificationの設定■

UIKeyboardWillShowNotificationでkeyboardWillShowを呼び出すよう設定します。


- (void)viewDidLoad
{
    [super viewDidLoad];

    NSNotificationCenter *center;
    center = [NSNotificationCenter defaultCenter];
    [center addObserver:self
               selector:@selector(keyboardWillShow:)
                   name:UIKeyboardWillShowNotification
                 object:nil];
}


■本体■

UIKeyboardWillChangeFrameっていうnotificationが別にあるんですが、willShowだけでキーボードが現れる時、英語と日本語切り替えた時、日本語キーボードで予測変換窓が現れた時に呼び出されてしまいます。良いのかそれで?

ミソは、convertRectです。keyboardFrameEndから返ってくるのはwindowの座標系で、ツールバーの高さやOrientationなどが配慮されていません。ので、現在のviewの座標系に変換します。これで「キーボードのorigin.y座標=使える画面の下限座標」となりますから、これでtext fieldの高さを設定し直します。

同じViewの上でキーボードを出したり消したりする必要のある時にはUIKeyboardWillHideNotificationを使います。ノリは同じで、viewのframeを取得してその下限を元にtext fieldなどの高さを調整するだけです。



- (void)keyboardWillShow:(NSNotification*)notification
{
    CGRect keyboardFrameEnd = [[notification.userInfo 
           objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    keyboardFrameEnd = [self.view convertRect:keyboardFrameEnd
                                     fromView:self.view.window];
    
    originFrame = self.textInput.frame;    
    float newHeight = keyboardFrameEnd.origin.y - originFrame.origin.y;

    [self.textInput setFrame:CGRectMake(originFrame.origin.x, 
                                        originFrame.origin.y, 
                                        originFrame.size.width, 
                                        newHeight)];
}


■後始末■

最後にnotificationを消し忘れないように。私はよく忘れます。ではでは。


- (void)dealloc {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    [center removeObserver:self 
                      name:UIKeyboardWillShowNotification 
                    object:nil];

    [textInput release];
    [super dealloc];
}

2013年7月9日火曜日

古いアプリをiOS6対応にしたらupside downにならない件を解決

■症状■

古いプロジェクトをiOS 6対応にしたら、portrait upside downにならない。NotificationでOrientationが変わったことは検出できるけど、Viewが回転しないので横向きになったまま。

iOS6のデフォルトではiPhoneではupside down以外が許可、iPadでは全方向が許可されていますので、それがそのまま効いていて変更できていない、ということになります。


■一般的な対策■

iOS 6ではshouldAutorotateToInterfaceOrientationが廃止になったので、対象ViewControllerでsupportedInterfaceOrientationsとshouldAutorotateを使う。


- (BOOL)shouldAutorotate {
    return YES;
}

-(NSUInteger)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskAll;
}

iOS 6対応として最初から作ったプロジェクトはこれで動きます。しかし、viewやらcontrollerをいろいろいじっている古くからのプロジェクトはこれではダメでした。

個人的困ったのはSalesforce対応アプリです。Salesforce mobile SDK for iOSの最新版ver 1.5でも未だにStoryboardやARCに対応していないので、中身は結構古いまま。テンプレートからiOS 6 targetにしても、upside downは働いてくれません。

その一方で…iOS 5をターゲットにすると、Web ViewのログインでID入力欄をタップしただけでSIGABRTするのは私だけでしょうか?


■原因■

root view controllerが別にあるためview controller上で上記shouldAutorotate / suportedInterfaceOrientationsをいじったとしても、標準のUINavigationController等で上書きされてしまう。


■解決策■

私のプロジェクトでは、Storyboard上のUINavigationControllerからメインviewを呼び出していましたので、UINavigationControllerのサブクラスNavigationControllerを作り、上記shouldAutorotate / supportedInterfaceOrientationsを貼ります。その上で、Storyboard上のnavigation controllerのcustom classをNavigationControllerに変更します。これでうまく行きました。いやはや。



■Salesforce mobile SDK for iOSの場合■

上記Salesforce mobile SDKの場合、テンプレートから作成されるプロジェクトはAppDelegate.hのnewRootViewControllerでViewとUINavigationControllerを生成しています。

上記と同じ手順でUINavigationControllerのサブクラスNavigationControllerを作り、AppDelegate.mにインクルードしてから、newRootViewControllerを書き換えます(書き換え箇所は赤字)。


- (UIViewController*)newRootViewController {
    RootViewController *rootVC = [[RootViewController alloc] initWithNibName:nil bundle:nil];
    NavigationController *navVC = [[NavigationController alloc] initWithRootViewController:rootVC];
    [rootVC release];
    
    return navVC;
}



2013年5月26日日曜日

SalesforceからiPhoneへPush notificationを送る

とんかつ専門店のご飯が妙に美味いのはなぜ?
上の写真はまったく関係ありません。

週末、何かネタを探していたら、www.parse.comというサービスに関する記事が見つかりました。最近ではPush Notification代行サーバもいろいろあるのですが、これは余計な機能がないぶんシンプルで安い。

そんなわけで、chatter上に「$go message」と書くと、triggerが作動してForce.comのhttp calloutからparse.comのAPIを叩き手元のiPhoneにメッセージが届く、というのを作ってみました。

■まず、Push Notificationを試す■

試すと言いますか、こちらの記事を参考に、いや、そのまま実行してみます。

Remote Push Notification ASPサービスを試す

Parse、一発で動いてPushされました。いやー、いままでこんなに簡単にPushできたのって初めてかもしれない。10分か15分くらいで動いてしまいました。



■FeedItemのTriggerからParseのREST APIを呼ぶ■

さて、ここからはForce.com上での作業。TriggerからREST APIを呼ぶことはできないので、Triggerからバッチを起動、バッチからcall outする作戦。

忘れないうちに、まずはお約束のリモートサイトの設定。
  管理者設定>セキュリティのコントロール>リモートサイトの設定
これでhttps://api.parse.com/を登録しておきます。

■Batchを作る■

Developer ConsoleからClass CallParseを定義、IterableなBatchApexでParse.comのAPIを叩きます(コードは末尾)。

■Triggerを作る■

例によってDeveloper ConsoleからFeedItem上のキーワード $go を検出してBatch Apexを呼び出すコードを書きます(コードは末尾)。

■動かない■

一応、Salesforce上で
  管理者設定>監視>デバッグログ
を設定してから、Chatter上にメッセージを書いてみる。

…動かない。デバッグログを見ると、

05:50:02.039 (39869000)|CALLOUT_REQUEST|[29]|System.HttpRequest[Endpoint=https://api.parse.com/1/push, Method=POST]
05:50:02.175 (175302000)|HEAP_ALLOCATE|[EXTERNAL]|Bytes:661
05:50:02.175 (175393000)|CALLOUT_RESPONSE|[29]|System.HttpResponse[Status=Bad Request, StatusCode=400]
05:50:02.175 (175420000)|HEAP_ALLOCATE|[29]|Bytes:130
05:50:02.175 (175438000)|SYSTEM_METHOD_EXIT|[29]|System.Http.send(ANY)

と出ていました。ちくしょう、どうせ一発で動くと思ってねぇですよ←涙目 なので、Apex側にコードを入れて
    System.debug(res.getBody());
    System.debug(res.getStatus());
    System.debug(res.getStatusCode());
検証してみたところ
    16:43:08.153 (153194000)|USER_DEBUG|[36]|DEBUG|{"code":107,"error":"This endpoint only supports Content-Type: application/json requests, not : application/json."}
という結果が。

ソースを改めて見なおしたら、

    req.setHeader('Content-Type : ', 'application/json');

なんてバカなコーディングを発見。直したら無事動作。




以下コードを張っておきます。iPhone側はparse.comのサンプルを貼っただけです。

■Trigger■


trigger KickFromChatter on FeedItem (before insert) {

    FeedItem[] feeds = Trigger.new;
    List<FeedItem> arrayFeedItem = new List<FeedItem>();
    
    for (FeedItem feed : feeds) {
        String strBody = feed.Body;
        if (strBody != null && strBody.contains('$go')) {
            arrayFeedItem.add(feed);
        }
    }
    
    if (arrayFeedItem.size() > 0) {
        CallParse batch = new CallParse();
        batch.arrayFeedItem = arrayFeedItem;
        
        Database.executeBatch(batch);
    }
}


■Batch Apex■


global class CallParse implements Database.batchable<FeedItem>, Database.AllowsCallouts {
    
    global List<FeedItem> ArrayFeedItem;
    
    global Iterable<FeedItem> start(Database.BatchableContext info) {
        Iterable<FeedItem> i = arrayFeedItem;
        
        return i;
    }
    
    global void execute(Database.BatchableContext info, List<FeedItem> feeds) {
        for (FeedItem feed : feeds) {
            String str = feed.Body;
            String strPayload = '{"channels" : [""], "data" : {"alert" : "' + str + '"}}';
            Blob blobPayload = Blob.valueOf(strPayload);
            
            // Instantiate a new http object
            Http h = new Http();
            
            HttpRequest req = new HttpRequest();
            req.setEndpoint('https://api.parse.com/1/push');
            req.setMethod('POST');
            
            req.setHeader('X-Parse-Application-Id', 
                                   'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa');
            req.setHeader('X-Parse-REST-API-Key',
                                   'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb');
            req.setHeader('Content-Type',
                                   'application/json');
            
            req.setBody(strPayload);
            
            // Send the request, and return a response
            HttpResponse res = h.send(req);
            
            System.debug(res.getBody());
            System.debug(res.getStatus());
            System.debug(res.getStatusCode());
        }
    }
    
    global void finish(Database.BatchableContext info) {
    }
    
}


2012年11月23日金曜日

今日はムービーアプリ

■例によってハマり方面■

お友達からの依頼で、ムービー再生系のiPhoneアプリを作ってます。ムービーアプリなんてSymantec C++以来かもしれない。あまりにも久しぶりなんで、どのFrameworkが最新だかわからないレベルです。MediaPlayer.frameworkですが。

簡単なアプリですがハマったのは以下2点。まぁね…ハマる以前だけどね。

画面真っ黒になるだけで何もでない
▶MPMoviePlayerViewControllerではなくMPMoviePlayerControllerを使っていた。

参考にしたサンプル・コードにはMPMoviePlayerViewControllerって書いてあったのですが、Xcodeの入力予測を使った時に間違ったようです。MPMoviePlayerViewControllerに変えたらムービーのコントローラなどがちゃんと表示されました。でも、ムービー表示領域は真っ黒で「Loading...」が表示されたままです。

Loadingのままで先に進まないし、play直後にfinishしてしまう
▶fileURLWithPathではなくURLWithStringを使っていたorz

困ったときのStackOverFlowさんありがとう。

Storyboard + ARCで作っているけど、Objective-C 2.0をちゃんと勉強した後となっては正直ARCにあんまりメリット感じません。Javaとは言わないけど、せめてC++的にスタックで自動的にお掃除してくれるなら文句ないんですが。

このところ会社ではずっとTitanium mobileをいじってます。JavaScriptなんでメモリの後始末を考えなくて良いという点「だけ」は良いものの、やっぱりネイティブの方が楽です。APIなどが確実に動く(Titaniumはbound関係のAPIで値が返って来なかったりすることが多い)し、何よりInterfaceBuilderが使える。Titaniumだと「画面上に配置したオブジェクトを全部50dp下げる」なんてことが起こると座標をいじらないといけないので面倒くさくていけません。

2012年8月16日木曜日

SplitViewControllerで右へのSwipeが効かない

iPad用のデモアプリをMaster-Detailテンプレートから作っていて、Swipe Gesture Recognizerを貼った。実機で試すと右へのSwipeが認識されない。

1時間ぐらいバインド先や順番を変えてみるなど試行錯誤するものの結果は変わらない。

試しにSingle Viewで試すとうまく認識される。

ググったら、Stack Overflowで同じ現象に悩んでいる人が見つかった。

結論:iOS 5.1のバグでした。

デモアプリはSplitViewControllerを使わない方向で作ることにした。単純なView上にTableとViewを並べるという原始的解決法。UITableViewControllerが使えないと何かとめんどくさいけど、そこは根性でカバーしますよ。とほほ。

2012年5月6日日曜日

Native iOSアプリをChatterの「いいね!」に対応

■FourChatter絶賛改良中■

Chatter上の文言をサクっと検索する倉橋屋謹製アプリFourChatterを改良してます。今更ですが。主な改良のポイントは以下の2点:
  1. iPad対応
  2. 「いいね!」の実装
iPad対応は、同じGUIそのままならそんなに苦労はないけれど、このFourChatterは「iPhoneの向きを変えるだけでキーワードを切り替えて検索できる」ってのがウリです(電車の中ではちょっと恥ずかしいけどね)。が。iPadで向きを変えるのは結構ウザい。というよりも、五十肩の私にはしんどい。というわけで、Segmented Controlを導入しました。

…iPhone版もこれでいいんじゃね? Segmented Controlなら4つに限らなくても良いんじゃない??という声もありますが。


■「いいね!」の実装■

Chatterの「いいね!」はFeedLikeというオブジェクトを使ってます。NewsFeedまたはFeedCommentへのto one relationshipになっていて

  • CreatedByID - 「いいね!」をクリックしたユーザのID
  • FeedItemID - 「いいね!」の対象となるNews Feed ID
  • FeedEntityID - 「いいね!」の対象となるFeedまたはCommentのID
  • InsertedById - このオブジェクトを作ったユーザのID
もともとFeedEntityIDはなかったと思うけど、最近FeedCommentへの「いいね!」がサポートされたことにともなって追加された、ような気がします。


■まず「いいね!」はこんな感じ■

SFRestRequest *requestInsert;

NSString *fId = [feed objectForKey:@"Id"];

NSDictionary* dic = [NSDictionary 
    dictionaryWithObjectsAndKeys:fId, @"FeedItemId", nil];

requestInsert = [[SFRestAPI sharedInstance] 
    requestForCreateWithObjectType:@"FeedLike" fields:dic];
[[SFRestAPI sharedInstance] send:requestInsert delegate:self];

これでOK。実行結果は例によって以下が呼び出されます。

- (void)request:(SFRestRequest *)request didLoadResponse:(id)jsonResponse ;
- (void)request:(SFRestRequest*)request didFailLoadWithError:(NSError*)error ;
- (void)requestDidCancelLoad:(SFRestRequest *)request ;
- (void)requestDidTimeout:(SFRestRequest *)request ;

要するに「いいね!」をしたいfeedのidを"FeedItemId"にセットしたNSDictionaryを用意して、それをFeedLikeとしてinsertするだけです。


■いいね!を取り消すには■

上記で作ったFeedLikeを削除します。最初、SELECTで該当するFeedItemIDとCreatedByIDを持つFeedLikeを検索しようとしたのですが、FeedLikeは直接fetchできないというエラーが出ました。ので、NewsFeedをfetchする時に、一緒にFeedLikesもfetchしておきます。

NSString *strQuery = [NSString stringWithFormat:
     @"SELECT Id, (SELECT Id, CreatedById From FeedLikes) 
       From NewsFeed Where Id = '%@' Limit 1", fId];

で、FeedLikesの中から自分のUser IDと同じCreatedByIdを持つFeedLikeを探し出して消します。

fId = (NSString *)[feedLike objectForKey:@"Id"];
requestDeleteLike = [[SFRestAPI sharedInstance] 
    requestForDeleteWithObjectType:@"FeedLike" objectId:fId];
[[SFRestAPI sharedInstance] send:requestDeleteLike delegate:self];

とても簡単。なお、自分以外のユーザの「いいね!」を消せるかどうかは試してません。

2012年5月5日土曜日

Force.com SDK iOS - ログインUser IDの取得

小ネタですが。

Force.com mobile SDK for iOS Nativeアプリで、ログインしているユーザのIDを取得するには、

AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *userId = [[[app coordinator] credentials] userId];

てな感じで。SFOAuthCoordinatorSFOAuthCredentialsは、他にも使えるメンバーを持っているのでリファレンスをブックマークしておくと良です。

で。上記で取得したのは15文字のuser idなので、18文字のCreatedByIDと比較する場合は

if ([createdById hasPrefix:userId]) { ... }

と書かないとダメです。

--

余談ですが。

考え事をしながら引き出しから爪切りを取り出し、床に広げた新聞紙に座り込んでさて爪を切ろうかと思ったらUSBメモリだった。そんな経験をした人はこの世の中に何人ぐらいいらっしゃるのでしょうか。
色とサイズは似ているけど…

2012年1月18日水曜日

Salesforce mobile SDK for iOS +1

■テンプレート以外で作業する場合のTips 1■

SFAuthorizingViewControllerにこんなのが定義されてますが。

    @property (nonatomic, retain) IBOutlet UIView *oauthView;  

これ、使われていません。テンプレートについてる「Authorizing」の画面があまりにもスゴいので当然置き換えを考えるわけですが、置き換えたxibでちょっと小細工をしたものの、どうも思ったとおりの動作をしてくれない。何だかんだ調べたら、oauthViewは全然使われていませんでした。

普通にviewをFile's Ownerにしてください。

2011年12月31日土曜日

Salesforce mobile SDK正式版(GA)

■βが取れました■

まぁとりあえず、めでたいです。今後はドキュメントなども充実してくれますよーに。



■では試してみましょう■

というわけで、例によってGitHubから最新版を落としてきます。もしXcodeが立ち上がっていたら、念のため終了しといてください。なお、私の環境ではGitHub for Macでの同期がうまく働きませんでした。ので、zipを落としてきます。

SalesforceMobileSDK-iOS

落としたら、zipを解凍したディレクトリに移り、 ./install.shを実行します。例によって数分後、BUILD SUCCESSFULが出たら無事終了です。

さて、Xcode 4.2を起動します。New Projectを選ぶと、以前よりちょっと派手なテンプレートアイコン「Hyblid Force.com App」と「Native Force.com REST App」が追加されています。今回はサクっと Native を選びます。


次の「Choose options for your new project」でProduct Nameに適当なプロジェクト名、iTunes connectなどに登録したあなたの会社ID、Consumer KeyにはあなたのSalesforce/Database.com組織のリモートアクセスキー、Redirect URLにはその戻り先URLを入力します。

Xcode 4.2だとその下の「Use Automatic Reference Counting」をonにしたいのですが、残念ながらMobile SDKが対応していないのでここはチェックを外してください。外さないとビルドエラーの塊になります。すべて入力したらNextをクリックし、プロジェクト保存先を指定し、プロジェクトを作ります。

以前はここでOther linker flagを設定したりFrameworkを追加する一手間が必要だったのですが、さすがはβでないGA、さくっとそのままcmd+Rで実行できます。起動すると、黄土色の「Authorizing」画面が表示され、その後Salesforce.comのOAuth認証画面になります。ID, Passなどを入力すると、その組織のユーザが10名分表示されます。


■βとの差異■

βはAppDelegateに認証関連の処理を細々と書かなければいけませんでしたし、「認証中です」の画面も自分でイチから作ってやらなければいけませんでした。でも、1.0では、その辺の処理はSFAuthorizingViewControllerとSFNativeRestAppDelegateに実装されていて、AppDelegateにはごく僅かなコードを書くだけで処理してもらえます。本来のAppDelegateに書きたい処理と分離することができて、非常にすっきりしました。

ViewControllerに関しては元々テンプレートが作ってくれるコードは非常に単純なので、あんまりRESTがどうこう考えないで実装することができます。


■そして、ハマる(お約束)■

以下は余談です。

とりあえず、プロジェクトの構造などを検証するために今度はMobile SDKのテンプレートを使わずにアプリを作ってみることにしました。
  1. Single View Appテンプレートでプロジェクトを作る
  2. さっきNative Force.com REST Appで作ったプロジェクトからDependenciesフォルダとNativeRestAppフォルダをコピー
  3. 必要なFrameworkを追加
  4. AppDelegateとViewControllerに必要なコードをコピペ
  5. cmd+Rでビルド&RUN
アプリは無事シミュレータ上で起動し、ドドメ色の「Authorizing」から認証を得て…オチますorz

これが3日ほど前でした。それから他の作業もしつつ、いろいろ原因究明したんですが、どうやっても同じところで落ちます。以前のパターンで同じようなエラーが出た時にはdelegateのスペルが間違っていたのが原因だったので、今回も何かdelegateを抜かしているのではないかと思い、ソースをにらめっこする日々でした。でも治らない。

その辺のウダウダを書いてもしょうがないので、例によって最終的に解決に至った課程のみ書きます。
  • エラーが出ているのは、viewDidLoadの下記の行
[[SFRestAPI sharedInstance] send:request delegate:self];
  • SIGABRTで止まった時点でのスタックトレースを見る←最初にやっとけ
  • [SFRestRequest description」でググる→SFRestRequest.mのソースがヒット
SFRestRequest.m
  • ソースに以下の記述を発見
  • _queryParamsはNSDictionaryなので当然JSONRepresentationなどというメソッドはない。遅まきながら、ここで「カテゴリ」が実装されてないことに気づく

原因は…上にヒントが書いてあります。

Other Linker Flagsに-ObjC -all_loadがセットされてない

でした。お粗末さまでした…。

言い訳をするとね…以前これらのLinker Flagsがセットされていない時にはビルドエラーで止まったのよ…。

2011年12月4日日曜日

Resizeしたい

■ごく単純なこと(そして落とし穴)■

現在開発中のiPhoneアプリはTableViewにカスタマイズしたCellを置いてそこにdatabase.comから取ってきたデータを表示するだけのごく単純なものなのです。ええごく単純。

要件としてはorientation(画面の向き)に応じてcellの幅を変える必要があるぐらい。Autoresizingを使えばほんと簡単。

あと表示するデータが可変長文字列なので、cellの高さを変える必要があります。これも単純な話で、以下のコードをview controllerに書いてやればおk

- (CGFloat)tableView:(UITableView *)tableView 
     heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    MyCell *cell = (MyCell *)[self tableView:self.tableView
                       cellForRowAtIndexPath:indexPath];
    
    float width = cell.status.frame.size.width;
    CGSize bounds = CGSizeMake(width, self.tableView.frame.size.height);
    
    CGSize size = [cell.status.text sizeWithFont: cell.status.font 
                               constrainedToSize: bounds 
                                   lineBreakMode: cell.status.lineBreakMode];
    
    return MAX(size.height, ConstMinCellHeight);
}

なのですが…。このboundsがくせ者でした。当然、当然cell上に置いたUILabelの幅を使います。このLabelにもautoresizingを設定してますから、iPhoneを横向きにすると当然リサイズされます。しかし…boundsに入ってくるwidth、つまりUILabelのサイズは初期設定値のままでリサイズ後のwidthを返してくれません。


■毎度おなじみ試行錯誤地獄■

そこから約4時間ほど試行錯誤を繰り返しました。覚えている範囲で列挙すると:

  • とりあえずautoresizingのモードをあれこれ試す
  • 友人のアドバイスに従いboundsへのkey value notificationを使用
  • UILabelのサブクラスを作ってsetBounds/setFrame/drawRectなどをオーバーライド

で、最終的にわかったことは、「cellの中に置いたLabelの幅がわかるのは、tableViewのheightForRowAtIndexPathが呼ばれた後」ってことでした。

描画の仕組みから考えて、そんなことはないと思うのですが…現状でそれを知る方法が見つかりませんでした。とほほ。


■美しくない妥協案■

というわけで、deviceOrientationDidChangeでiPhoneの向きが変わったことを検出したら、そこで定数からラベル幅を算出する、という方法にしました…。一応、デバイスの規定値が変わってもそのまま動くようにはしてあるけど…どうも美しくないっす。

問題は、この方法は最初からわかっていたということで…「美しくないから」という理由だけで4時間試行錯誤してしまったのはどーなの?というあたり。まぁ時間単価で仕事しているわけじゃないから良いんだけど…困ったもんだ。

まぁ、あれだ、4時間の試行錯誤でいろいろ分かったこともあったから、そのうちそれらが花開く日も来るだろう、ということで…。