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月17日土曜日

FlashでPDF・続き

■続き、っていうか本番■

前回はFetchしてデータを一覧表示するところまででした。

FlashでPDF

「そもそもなんでFlashなのか?」という話が抜けていました。
  1. クライアントサイドの有り余るリソース(メモリ, CPU)を使える
  2. きめ細かく罫線などを制御できる
  3. Force.comでは実現不可能なグラフも作れる
  4. Rich GUIで余計な苦労を負わないで済む
  5. PDF添付メールやファイル出力の制約が少ない
PDF生成そのものに関しては「セルその他の描画を細かく制御できるので、基本的にどんな帳票でもおk」ってのがFlash / purePDFでPDFの長所の一つです。クライアントサイドでは数GBのメモリも当たり前なので、ガバナーの制約を気にしないで好きなことできるのもメリットです。「数万件のデータをドーンと読み込んできてループまわしながら集計し、役員会に出せるような美しい罫線の入ったPDFを作る」ようなケースで使えます。

あとグラフ。Flashのグラフは制約が少ないので複雑なグラフを作りたい場合にはForce.comよりもラクできます。

また最近はWebアプリケーションであってもrichなGUIが求められる案件が増えてきました。Force.comでも売掛入金の消し込み画面で設定した条件が間違っていたらボタンをdisableしたり、jQueryで関連するデータをアニメーション表示したりお客様の多彩なご要望に苦労する日々でございます。

でもFlashはrich GUIの本家です。

先日もあるコンサルタント会社から「100近い検索条件から案件を抽出する検索画面」という依頼を受けましたが、設定した条件が埋もれてしまわないようわかりやすく表示するのもFlashなら自由自在です(簡単です、と書かないところに私の誠実さを感じてください)

 そうそう、それにFlashなら開発の途中でオブジェクトの項目を変更する必要が生じても「それAPEX/VFで使ってるから変更できねぇよw」とForce.comから怒られることもありません(笑)。

■purePDFで■

FlashでPDFを生成するためのライブラリでメジャーなのはAlivePDFとPurePDF。今回はPurePDFを使います。PurePDFはJavaのPDFライブラリiTextをFlashに移植したもので、私はiTextには散々痛い目にあったお世話になったことがあるので、まぁ学習コストがかからないだろう、と。

使ってみたら日本語が文字化けたりしてまた散々痛い目にあったのですが、そういう経緯は全部スキップします。ここからライブラリをダウンロードします。

Player10.0 でも purePDF で日本語PDFを作る

FlashBuilder上のプロジェクトのlibsフォルダには前回force-air.swcを追加しましたが、今回ダウンロードしたpurePDF.swcとpurePDFont.swcも追加します。

APIなどに関してはFlashとJavaとで実装上の違いはありますが、iTextの方がサンプルも多く用意されていて便利です。


■解説■

ソースは最後に貼ります。基本的な処理の流れとしては

  1. サイズと出力先(バッファ先)を指定してDocumentを生成する
  2. ベースフォントを作る
  3. ベースフォントにサイズなどを指定してフォントを生成する
  4. テキスト、イメージ、別のテーブルなどをセットしたセルを作る
  5. セルをテーブルにセットする
  6. テーブルをドキュメントにセットする
  7. 必要ならページ送り(document.newPage)

では、実際の処理を見ていきます。

>queryHandler
前回見たようにarrayDataにfetchしたデータを溜めていきます。result.doneになったらfetch完了ですので、dataGridにarrayDataをセットし、pdfの生成を始めます。

PdfWriterでバイトストリームをセットしつつ用紙サイズを設定します。ここではPageSize.A4でA4縦を指定していますが、PageSize.A4.rotate(90)と書くとA4横になります。

>initPdfFont
ベースフォントを生成します。いろいろなフォントを試してみたのですが、結局これ平成明朝しか使えませんでした。ゴシックも指定できるのですが、日本語フォント部分は同じで英数字部分が変わるのみでした。

>createPdf
実際に使用するサイズのフォントを生成してからPDFを生成していきます。vecという配列を用意していますが、これはTableのrowをどういう比率で生成するかを指定するためのものです。値が合計いくつになってもかまわないので、私はお客さんからもらってきたサンプル帳票に物差しあててその数字を使ったりしていますw

あとは文字列を元にしてセルを作ってテーブルにせっせと詰め込んでいくだけです。終わったらnewPageでページを閉じて、最後に document.close(); でドキュメントを閉じます。

>cellFromStringFontAndAlignment
セルを生成しています。関数名がやたらと長いのは私が長年MacOS系プログラマだったからです。生成したcellオブジェクトにはデフォルトで罫線がついてます。top, border, right, leftごとに太さ、色などを制御することができます。逆にいえば、セルを作る時点で罫線のことを考えないと意図した表になりません。まぁこれがメリットになるかデメリットになるかは状況次第っすね。

>savePdf
保存ダイアログを表示して、ファイル名や出力先を指定し、ファイルを保存します。

>formatDateMeasure
ここがFlashの困ったちゃんで、DataGridにDateやNumberを表示する際の書式指定でいちいちコールバック関数などを書かないといけないのです。VisualBASICが懐かしいぜ。

>convertToLocaltime
RESTで帰ってくる日付はGMTなのでこれを日本時間に変換してやります。60000はtimezoneOffsetで取ってくる時差が分単位なのでこれをミリセカンド単位に換算するためです。

>formatDecimal
3桁ごとのカンマ区切り、もっと簡単な方法あったら教えてください^^;


■注意■

今回サンプルとして掲載したソースにはID / Passwordが文字列定数として埋め込まれていますが、実際にFlashで作る場合この方法は避けてください。ダンプしてソースをリバースエンジニアリングすることが可能ですので。

FlashとしてForce.com環境上で使うのであればセッションを引き継いで認証できるのでID / Passwordは不要ですし、AIRでデスクトップアプリとして配布する場合はGUIからID / Passwordを入力してもらいpreferenceとしてPC上に保存すればOKです。


■Flash業界のみなさんへ■

データベースにお悩みではありませんか?

force.com / database.comの世界にどうぞ。database.comなら最高にセキュアでスケーラブルなRESTデータベースが3アカウント, 10万レコード, 月5万トランザクションまで無料で使えます。サーバのセットアップもバックアップも不要です。

お願い:ソースでお気づきの通り私はFlash初心者です。いろいろツッコミどころ満載だと思いますので、ご指導ご鞭撻のほどよろしくお願いいたしますm(_ _)m。


■おまけ■

個人事業としてiPhoneアプリを書いてます。

現職でForce.comに携わる前はWebObjectsというフレームワークで仕事をしていましたが、WO屋から見るとApexはイマイチ美しくありません(もちろんマルチテナントとしての安全性安定性などを確保するための現実的な実装という面もあると思いますが)

そんな私にとってForce/Database/Chatter.comをRESTでアクセスするiOSアプリはほぼ理想郷です。


■最後に■
このブログはForce.com Advent Calendarに参加しています。


■ソース■

というわけでソースです。

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
        xmlns:s="library://ns.adobe.com/flex/spark" 
        xmlns:mx="library://ns.adobe.com/flex/mx"
        xmlns:salesforce="http://www.salesforce.com/"
        applicationComplete="init()" height="560" width="800">
 <fx:Declarations>
  <salesforce:AIRConnection id="force"/>
 </fx:Declarations>

 <fx:Script>
  <![CDATA[
   import com.salesforce.AsyncResponder;
   import com.salesforce.objects.LoginRequest;
   import com.salesforce.results.Fault;
   import com.salesforce.results.LoginResult;
   import com.salesforce.results.QueryResult;
   
   import mx.collections.ArrayCollection;
   import mx.formatters.DateFormatter;
   
   import org.purepdf.Font;
   import org.purepdf.colors.RGBColor;
   import org.purepdf.elements.Element;
   import org.purepdf.elements.Phrase;
   import org.purepdf.elements.RectangleElement;
   import org.purepdf.pdf.PageSize;
   import org.purepdf.pdf.PdfDocument;
   import org.purepdf.pdf.PdfPCell;
   import org.purepdf.pdf.PdfPTable;
   import org.purepdf.pdf.PdfWriter;
   import org.purepdf.pdf.fonts.BaseFont;
   import org.purepdf.pdf.fonts.cmaps.CJKFontResourceFactory;
   import org.purepdf.pdf.fonts.cmaps.CMapResourceFactory;
   import org.purepdf.resources.BuiltinCJKFonts;
   import org.purepdf.resources.CMap;
   import org.purepdf.resources.ICMap;
   import org.purepdf.utils.IProperties;
   import org.purepdf.utils.Properties;

   
   
   [Bindable]
   private var arrayData:ArrayCollection = null;
   private var dateTimeFormatter:DateFormatter = null;
   
   private const MaginLeft:int    = 20;
   private const MarginRight:int  = 20;
   private const MarginTop:int    = 30;
   private const MarginBottom:int = 30;
   
   private function init():void 
   {
    dateTimeFormatter = new DateFormatter();
    dateTimeFormatter.formatString = "YYYY-MM-DD HH:NN:SS";

    var lr:LoginRequest = new LoginRequest();
    status = "login by id/password";
    
    lr.username = "youraccount@your.domain";
    lr.password = "password"+"securitytoken";
    
    lr.callback = new AsyncResponder(loginHandler, faultHandler);
    force.login(lr);
   }  
   
   private function loginHandler(result:LoginResult):void 
   {
    if (result.userInfo != null) {
     status = "login success, query";
     
     force.autoSyncEnabled = false;
     force.doCache = false;
     
     var strQuery:String = "Select DateMeasure__c, High__c, Low__c, Beat__c From BloodPressure__c order by DateMeasure__c desc";
     
     force.query(strQuery, new AsyncResponder(queryHandler, faultHandler));
    }
   }
   
   
   
   private function queryHandler(result:QueryResult):void
   {
    if (result != null && result.records != null && result.records.length > 0) 
    {
     if (arrayData == null)
      arrayData = new ArrayCollection();
      arrayData.addAll(result.records);
    }
    
    if (!result.done)
    {
     trace("query again");
     force.queryMore(result.queryLocator, new AsyncResponder(queryHandler));
    }
    else
    {
     status = "query result";
     force.syncConnection
     
     dataGrid.dataProvider = arrayData;
     
     var byteArray:ByteArray = new ByteArray();
     
     var pdfWriter:PdfWriter =  PdfWriter.create(byteArray, PageSize.A4);
     var document:PdfDocument  =  createPdf(pdfWriter, initPdfFont());
     savePdf(byteArray, "血圧報告.PDF");
     
     document  = null;
     pdfWriter = null;
     byteArray = null;
    }
   }
   
   private function faultHandler(result:Fault):void
   {
    trace(typeof(result));
    trace(result.faultcode);
    trace(result.faultstring);
   }
   
   
   private function initPdfFont() : BaseFont
   {
    var map: ICMap = new CMap( new CMap.UniJIS_UCS2_H() );
    CMapResourceFactory.getInstance().registerCMap( BaseFont.UniJIS_UCS2_H, 
                                                    map );
    
    var prop: IProperties = new Properties();
    prop.load( new BuiltinCJKFonts.HeiseiMin_W3() );
    
    CJKFontResourceFactory.getInstance()
                          .registerProperty(
        BuiltinCJKFonts.getFontName( BuiltinCJKFonts.HeiseiMin_W3 ), prop);
    
    var bf: BaseFont = BaseFont.createFont( 
        BuiltinCJKFonts.getFontName( BuiltinCJKFonts.HeiseiMin_W3 ), 
        BaseFont.UniJIS_UCS2_H, BaseFont.NOT_EMBEDDED, true );
    
    return bf;
   }
   
   private function createPdf(inWriter:PdfWriter, inBf:BaseFont) : PdfDocument 
   {
    var fontHeader:org.purepdf.Font 
        = new org.purepdf.Font( org.purepdf.Font.BOLD, 12, -1, null, inBf );
    var font:org.purepdf.Font       
        = new org.purepdf.Font( -1.   -1,              10, -1, null, inBf );

    var document:PdfDocument = inWriter.pdfDocument;
    document.open();
    document.setMargins(MaginLeft, MarginRight, MarginTop, MarginBottom);
    
    var table:PdfPTable = null;
    var cell:PdfPCell = null;
    
    var lc:int = 99;
    var lpp:int = 40;
    var i:int = 0;
    var n:int = arrayData.length;
    var monthBreak:int = 0;
    
    var vec:Vector.<Number> = new Vector.<Number>;
    vec.push(50);
    vec.push(25);
    vec.push(25);
    vec.push(25);
    
    while (i < n) {
     // 行を取り出し
     var line:Object = arrayData[i++];
     var dateMeasure:Date = convertToLocaltime(line.DateMeasure__c);
     
     // 改ページ
     if (lc >= lpp) {
      if (table != null) {
       document.add(table);
       document.newPage();
       table = null;
      }
      lc = 0;
     }
     
     //ヘッダ
     if (table == null) { 
      table = new PdfPTable(vec);
      table.addCell(cellFromStringFontAndAlignment("測定日時", fontHeader));
      table.addCell(cellFromStringFontAndAlignment("最高血圧", fontHeader));
      table.addCell(cellFromStringFontAndAlignment("最低血圧", fontHeader));
      table.addCell(cellFromStringFontAndAlignment("心拍",     fontHeader));
     }
     
     table.addCell(cellFromStringFontAndAlignment(
                       dateTimeFormatter.format(dateMeasure), font));
     table.addCell(cellFromStringFontAndAlignment(
                       formatDecimal(line.High__c), font, Element.ALIGN_RIGHT));
     table.addCell(cellFromStringFontAndAlignment(
                       formatDecimal(line.Low__c),  font, Element.ALIGN_RIGHT));
     table.addCell(cellFromStringFontAndAlignment(
                       formatDecimal(line.Beat__c), font, Element.ALIGN_RIGHT));
     lc++;
    }
    
    if (table != null) {
     document.add(table);
     table = null;
     
     document.newPage();
    }
    
    document.close();
    
    return document;
   }
   
   private function cellFromStringFontAndAlignment(
                                        inStr:String, 
                                        inFont:org.purepdf.Font, 
                                        inAlignment:int = Element.ALIGN_CENTER)
                                      : PdfPCell {
    var str:String = (inStr == null) ? "" : inStr;
    
    var cell:PdfPCell = PdfPCell.fromPhrase(new Phrase(str, inFont));
    cell.horizontalAlignment = inAlignment; 
    
    return cell;
   }
   
   private function savePdf(inByteArray:ByteArray, inFileName:String) : void
   {
    var fr:FileReference = new FileReference();
    fr.save(inByteArray, inFileName);
   }

   private function formatDateMeasure(inObj:Object, 
                                      inDataGridColumn:DataGridColumn)
                                    : String
   {
    return (inObj != null && inObj.DateMeasure__c != null) 
           ? dateTimeFormatter.format(convertToLocaltime(inObj.DateMeasure__c))
           : "";
   }
   
   private function convertToLocaltime(inStr:String):Date {
    var date:Date = DateFormatter.parseDateString(inStr);
    var time:Number = date.getTime() - date.timezoneOffset * 60000;
    date.setTime(time);
    
    return date;
   }
   
   private function formatDecimal(inDecimal:Number):String
   {
    if (isNaN(inDecimal))
     return "";
    else {
     var pattern:RegExp = /(\d)(?=(\d{3})+(?!\d))/g;
     return String(inDecimal).replace(pattern, "$1,");
    }
   }

  ]]>
 </fx:Script>

 <mx:DataGrid id="dataGrid" top="10" left="23" right="23" bottom="10" 
              editable="false" enabled="false">
  <mx:columns>
   <mx:DataGridColumn headerText="測定日時" dataField="DateMeasure__c" 
                      labelFunction="formatDateMeasure" textAlign="center"/>
   <mx:DataGridColumn headerText="最高血圧" dataField="High__c" 
                      textAlign="center"/>
   <mx:DataGridColumn headerText="最低"  dataField="Low__c"  textAlign="center"/>
   <mx:DataGridColumn headerText="心拍"  dataField="Beat__c" textAlign="center"/>
  </mx:columns>
 </mx:DataGrid>



</s:WindowedApplication>

2011年12月13日火曜日

FlashでPDF

■別に専門というわけではないけれど■

最近は変わりつつありますが、Webアプリにとって「カチッ」とした帳票というのはやや鬼門で、皆さんかなり苦労を強いられてきました。

私自身を振り返ってみてもPDF関連の実装が割と大きな比重を占めています。前職のWebObjectsでもiTextというJavaのライブラリを使っていましたし、現職Force.com屋でもほぼすべての案件にPDF生成が含まれている状況です。

これまでForce.com上で部門別部課別集計、罫線の各種アレンジ、複数ページ請求書などいろいろな帳票を作ってきましたが、日本ビジネス特有のきめ細かい帳票要求については、以下の2点:

  • 日本語フォントが1種類しか使えない
  • ひらがなカタカナの拗促音がbaselineに揃わない

を除けば、特に不自由なく生成できるので、ネタとしては面白くありません。


■FlashでPDF■

さて、Flash toolkit for Force.comつかっている人います? 日本のBBSを見てもほとんど話題が出ていないですし、海外の掲示板でもFlashBuilder for Force.comが中心になってしまったようで、大変寂しい思いをしています。toolkitの開発もすっかり止まってしまいましたし、このままfade outしちゃうんですかね…。

というわけで、この機会に少しでも開発者が増えることを願って、Flash+Force.comでのPDF生成について書きます。今回はFlash+Force.comについて、次回はFlash上でのPDF生成について書きます。


■Flashでの開発■

Force.com Toolkit for Adobe AIR and Flex

何よりFlashがないと話しになりません。とりあえず30日間無料でお試しができますので、Adobeから「FlashBuilder 4.6 Standard Edition」をダウンロードしてきてください。

余談ですが、4.5からはiPhone/Androidアプリも書くことができます。試してみたらホントに15分くらいでForce.com対応iPhoneアプリを書けて大笑いしたんですが、GUIがネイティブと異なりすぎることとメモリをバカ食いするので、今のところ本気では使っていません。

さて、お次は、FlashからForce.comをアクセスするためのライブラリが必要です。これは、このページの「download toolkit」から落としてきます。

では、FlashBuilderを起動してください。ユーザ登録の手続きなどは端折ります。起動遅いっす。私の嫌いなEclipse様がベースなので諦めてください。

ファイル>新規プロジェクト>Flexプロジェクト

適当な場所に適当な名前でプロジェクトを作ります。とりあえず「Advent」にしました。アプリケーションの種類をデスクトップにする以外はデフォルトのままでOKです。

できたプロジェクトにForce.comのライブラリを追加します。さっきダウンロードしてきたライブラリの中から「force-air.swc」をプロジェクトのlibsにドラッグ&ドロップしてください。

パッケージエクスプローラの中から

Adbent>src>(default package)>Advent.mxml

を開きます。ソースの先頭にあるs:WindowedApplicationとfx:Declarationsを以下のソースと置き換えてください。

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" 
 xmlns:s="library://ns.adobe.com/flex/spark" 
 xmlns:mx="library://ns.adobe.com/flex/mx"
 xmlns:salesforce="http://www.salesforce.com/"
 applicationComplete="init()" height="560" width="800">
 
 <fx:Declarations>
  <salesforce:AIRConnection id="force"/>
 </fx:Declarations>


その後にはコード部分のタグが来て

 <fx:Script>
  <![CDATA[


   
  ]]>
 </fx:Script>

最後にデータを表示するためのグリッドが来て最初のタグを閉じます。

 <mx:DataGrid id="dataGrid" 
   top="10" left="23" right="23" bottom="10" 
   editable="false" enabled="false">
  <mx:columns>
      <mx:DataGridColumn headerText="id"  dataField="Id"/>
      <mx:DataGridColumn headerText="氏名" dataField="Name"/>
      <mx:DataGridColumn headerText="電子メール" dataField="Email"/>
  </mx:columns>
 </mx:DataGrid>

</s:WindowedApplication>

以上が基本的なソース構造です。で、「CDATA[」の次の行からコードを書いていきます。

■まずはインポートとグローバル変数■

 import com.salesforce.AsyncResponder;
 import com.salesforce.objects.LoginRequest;
 import com.salesforce.results.Fault;
 import com.salesforce.results.LoginResult;
 import com.salesforce.results.QueryResult;
   
 import mx.collections.ArrayCollection;

 [Bindable]
 private var arrayUser:ArrayCollection = null;


■初期化/ログイン■

冒頭部分にあるapplicationCompleteから呼び出される初期化ルーチン、ここでログイン処理をします。昔ながらの処理でOAuthなどはありませんorz。なお、これはデスクトップアプリなのでIDとPasswordでログイン処理を行いますが、FlashとしてVisualforceに埋め込む場合にはセッションidなどからそのまま認証を引き継ぐことができるようになっています。


 private function init():void 
 {
  var lr:LoginRequest = new LoginRequest();
  status = "login by id/password";
  
  lr.username = "your.account@your.domain";
  lr.password = "password" + "token";
  
  lr.callback = new AsyncResponder(loginHandler);
  force.login(lr);
 }  


■ログイン成功■

ログインが成功すると、この関数が呼び出されます。ここではすぐに馴染み深いSOQLを呼び出していますが、検索処理などを行う場合には、ここでGUIの「検索ボタン」をenableにする、などの処理を書きます。

 private function loginHandler(result:LoginResult):void 
 {
  if (result.userInfo != null) {
   var strQuery:String = "Select Id, Name, Email From User";
     
   force.query(strQuery, 
        new AsyncResponder(queryHandler, 
             faultHandler));
  }
 }


■Query成功■

Query成功時にこのコードが呼び出されます。ここは1回に最大2000レコードずつ渡されます。つまり逆にいえば、数万件のレコードがヒットするようなQueryを発行しても、ここでarrayUserにひとまとめにすることができる、ということです。まぁ遅いですけどね。それにFlashは基本的にシングルタスクなので何も配慮しないで数万件のPDF帳票を出力しようとするとUIが一切操作できなくなります。不憫な奴。

で、最後にdataGridに読み込んだデータを一括してセットすれば一丁上がり。

 private function queryHandler(result:QueryResult):void
 {
  if (result != null && result.records != null 
   && result.records.length > 0) 
  {
   if (arrayUser == null)
    arrayUser = new ArrayCollection();
   arrayUser.addAll(result.records);
  }
    
  if (!result.done)
  {
   trace("query again");
   force.queryMore(result.queryLocator, 
     new AsyncResponder(queryHandler));
  }
  else
  {
   status = "検索または実行開始をどうぞ";
   force.syncConnection
     
   dataGrid.dataProvider = arrayUser;
  }
 }


■エラー処理■

SOQLに誤りがあった場合などにこの関数が呼び出されます。

 private function faultHandler(result:Fault):void
 {
  trace(typeof(result));
  trace(result.faultcode);
  trace(result.faultstring);
 }

■基本編終了■

以上でForce.com + Flexの基本はおしまいです。次回はPDF生成編です。それまでよろしかったらSOQLを書き換えたりDataGridのdataFieldをいじったりして遊んでみてください。なお、"Select Id, Name, CurrentStatus From User"などと書いてもAPIが古いのでこのライブラリでは対応していません。あしからず。

--

この記事はForce.com Advent Calendarに参加しています。

Force.com Advent Calendar 2011 : ATND

PhoneGapかiOS SDKについて書くと予告していたのにFlashになったのは準備していたネタが土壇場になって不調だったからさ。以前書いた記事もあるし。

PhoneGap 1.2.0 + Salesforce SDK(hyblid)

以上、50歳と2日のくらはしがお送りしました。

#Bloggerさんがインデントをつぶしてしまったけど、まぁ読めるのでご勘弁

2011年12月6日火曜日

湯たんぽ10日目

■年寄りは夜眠れない■

もともと30歳以降は睡眠時間少なくて、その分仕事が片付く…と喜んでいました。

しかし40歳を過ぎてからは疲れていても2-3時間で目が覚めてしまい、そのまま寝直すこともできずに朝まで、そして昼頃にはまた眠気が来る…というのが基本的な生活パターンとして定着する始末。

自営業なら昼寝すればいいものの、2年前からサラリーマンになったので、そういうわけにもいきません。一度会社近所のカプセルホテルで「昼寝パック」というのを試して見ましたが、寝過ごすのが怖くてよく眠れません。それにさすがに昼寝に着替えてベッド、というのは大げさです。


■対策1■

レスタミンというアレルギーの薬があります。アレルギー薬としては古いタイプで、いわゆる「眠くなる風邪薬の成分」、睡眠改善薬「ドリエル」と同じ成分です。私はYS11以外の機内ではあまり眠れないのですが(何その特異体質w)、一度渡米時の機内で食後に飲んでみたら朝(?)まで爆睡してしまって、それ以来愛用していました。ただドリエルは少し成分量が多くて私は頭が痛くなったり12時間ぐらいぼんやりしてしまいます。

でも、レスタミンは1錠あたりの成分がドリエルの1/3なので調整できます。何より値段が遙かに安い。今夜寝ておかないと明日が辛いぞという時に1-2錠のんでからベッドに入るとわりと熟睡できます。寝起きは少しボーッとする程度ですが、すぐに普通のペースで行動できます。

ただ、最近の研究によると服用後24時間ぐらいは反応などが鈍くなっているそうなので、クルマの運転などをする方は週末などに様子を見てから試してください。

このクスリ、私には効果絶大なのですが、やっぱりあまりクスリには頼りたくないです。


■で、湯たんぽ■

私のベッドは北向きの部屋にありまして、そうでなくてもうちは田舎の9Fなので冬は見事に寒い。かといって朝までエアコン付けっぱなしというのも勿体ないし喉痛い。

というわけで、湯たんぽ買ってみました。無印良品のプラスチック製2リットルと一番安いフェイクファーのカバー、合わせて2000円ちょっと。

たれぱんだは付いてきません。

ほどよい保温性能で熱湯を入れても熱くならないし朝になってもまだ少し温かい。暖かさに関しては期待通りで、かなり寒い日でも暖房なしで過ごせています。自宅作業の時にはヨメを見送った後エアコンを切って足元に置いてます。

とても温かい。

で、睡眠に関しては期待していなかったのですが…湯たんぽを使っている夜は、今のところ5-6時間寝られています。私はもともと高脂肪高血圧で冷え性とは縁遠いので、湯たんぽの必要性はぜんぜん感じていなかったんですが…頭寒足熱ってやっぱり身体に良いんですかね。

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時間の試行錯誤でいろいろ分かったこともあったから、そのうちそれらが花開く日も来るだろう、ということで…。

2011年11月18日金曜日

Visualforceが憎いいいい



■簡単なこと■


てな感じの罫線入った表を表示したいとします(今回はグラデーション入れませんが)。まぁ日本のWeb屋さんにはごく日常的な作業で、処理系によって違いはあるけど

  • ループを回しながらコントロール・ブレークで行を出力していく
  • 先にコントロール・ブレーク済みの行データを配列にいれておいて出力

のどっちかで対応しますわな。

Visualforceだとループ内側では動的な評価ができないので後者で作ります。作りました。


■Visualforce困ったちゃん■

で、分類と詳細の行はレイアウトが違うので、分類の行だったら分類用のタグ、詳細行には詳細用のタグ…を出力させます。

そのために多くのフレームワークには「この内側のタグsを出力するか否か」をスイッチする機能が用意されてます。史上最高のWebフレームワークたるWebObjectsにはWOConditionalがあって(以下長いので省略

しかし。

Force.comってかVisualforceにはありません。ので、<apex:include>とか<apex:component>などを用いてコンポーネントとして作った要素を埋め込みrenderedでon/offします。

これでHTMLを生成すると…見事に表が崩れます。ソースを読んでみると…componentの前後に勝手に<span></span>が挿入されており、結果として以下のようなソースが生成されてしまいます。
<Table>
  <TR><SPAN><TH>品目</TH><TH>金額</TH></SPAN></TR>
  <TR><SPAN><TD>Mac</TD><TD>128,000</TD></SPAN></TR>
</Table>
そりゃ崩れるわなorz。


■しかし天才はいる■

で、困った時のGoogle先生ってことで apex:component span で検索してみたら、一発でworkaroundが見つかりました。「apex:component rendered in SPAN」、いやー、その手は思いつかなかった。まさに天才です。

さっそく実装してみたら、

<Table>
  <TR>
    <SPAN></SPAN>
    <TH>品目</TH><TH>金額</TH>
    <SPAN></SPAN>
  </TR>
  <TR>
    <SPAN></SPAN>
    <TD>Mac</TD><TD>128,000</TD>
    <SPAN></SPAN>
  </TR>
</Table>
となり、ブラウザ上では意図した通りの表示が出ました。

喜び勇んでrenderAs="PDF"を実行したら…。



…はい、終了。お疲れ様でした。まぁ、そらそうよね、<TR>の内側だしねorz

さて、TABLEを全部DIVで書き直すかな。CSSも全部再定義だ…。ああもう朝だ…。

私の夜はまだ明けそうにないがorz

2011年11月13日日曜日

PhoneGap 1.2.0 + Salesforce SDK(hyblid)


■Salesforce mobile SDK 2011-11版■

ちょっと試してみたいことがあって久しぶりにSalesforce mobile SDK for iOS / hyblidを使ってみようと思ったら、11月の頭にSalesforce mobile SDKのアップデータが出ていました(リビジョン番号とかないのかしら)

そのまま放置していたらPhoneGapからは1.2.0が出てました。ついでなので、先にこっちをアップデートします。


■PhoneGap 1.2.0■

それにしてもOpenSourceはバージョン進むの速いな。おっさんは着いていくだけで精一杯だわ。というわけで、1.2.0を落とします。Xcodeは終了しておくこと。

iOSフォルダの中を見ると例によってdmgパッケージになっているのでダブルクリックしてマウント、Uninstall PhoneGap.appで古いヤツを消してから、インストーラを起動します。

#uninstallするのは必須ではないですが、まぁ古いMacOSユーザの習性です#

インストールはサクサク進む、はず。あとは例によって
  • Xcodeを起動
  • PhoneGap-Based Applicationをテンプレートとして新プロジェクトを作る
  • できたプロジェクトをFinderで開いて中のwwwフォルダをXcodeの新プロジェクト上にDrag & Dropして"create folder reference"で追加
  • PhoneGap.plistのExternal hostsに*を追加
  • ビルド
ってことで、無事デモアプリが起動します。「あれkitchen sinkはどこだっけ…?」と思ってしまった自分が悔しい。それはTitanium mobileやがな。PhoneGapでもああいうの欲しいなぁ。


■あらためてSalesforce mobile SDK、の前に■

SDKはgithubに公開されていますので、メンテナンス情報などを取得する意味でもgithubアカウントがあると便利です。というか無いと不便すぎてストレス溜まります。無料なので、取ってしまいましょう。かくいう私もつい先日作ったのですが、便利すぎて今までの苦労があほらしくなりました。githubアカウントを使うには公開鍵が必要ですので、もしまだ作っていなければ、作ります。
で、ここから無料のアカウントを作ります。特に難しいこともないので、省略しますが、さっき作った公開鍵はgithubにログインしてAccount Settings>SSH Public Keysに登録します。あなたのマシンとgithubはsshで通信するので、そのための暗号キーを設定するわけですね。なお、ものすごく蛇足ですが、秘密キーは絶対公開したりヒトに見せたりしてはいけません。

さて、次。Macからgithubを扱うのであれば、GitHub for Macがあると圧倒的に便利ですので、これも入れます。インストールしてセットアップを終えると、あら不思議、githubのsalesforce SDKのページにさっきまで無かった「Clone in Mac」のアイコンが出てきます。クリックするとGitHub for Macがダウンロードを開始します。便利すぎて笑えます。

次に、もしまだgit入れてない人は、入れましょう。サブモジュールなどをgitから取ってくる形式になったので、gitないとうまく使えません。そしてgitを入れるにはMacPortってのがあると簡単です。MacPortをインストールして、それからgitをインストールし、そこからSalesforce mobile SDK 2011年11月版を入れましょう。




■今度こそSDK…のビルド■

…いやー、準備が長かったw というわけで、GitHub for Macのおかげで安全かつ迅速に、SalesforceMobileSDK-iOSが用意できました。ターミナル.appを起動して、SDKをインストールします。とりあえず私は安易にDesktopに置きましたが、そうでない方は水色の部分を変えてください。
cd
cd Desktop/SalesforceMobileSDK-iOS
./install.sh
だーっと滝のように文字が流れます。マシンの性能によって違いますが、数分間続きます。最後に:
all:

BUILD SUCCESSFUL
Total time: 10 seconds
と表示されればOKです(secondsは変わると思いますが)。お疲れ様でした。


■ようやくサンプル(今度こそ)■

SalesforceMobileSDK-iOS/hyblid/SampleApps/ContactExplorerの中にあるContactExplorer.xcodeprojをダブルクリックするとXcodeが起動します。

さくっとビルドすると警告が出てしまいます。そうです、Consumer Keyなどが未登録ですね。というわけで、AppDelegate.hを開き、RemoteAccessConsumerKey, OAuthRedirectURI, OAuthLoginDomainを登録します。
  • RemoteAccessConsumerKey
ここに説明が書いてありますが詳細すぎてわかりにくいですね。Salesforceにログインして 設定>アプリケーションの設定>リモートアクセス>新規 で適当なアプリケーション名、コールバックURL(後述)、連絡先メールアドレスを登録してください。コンシューマ鍵として長い記号のような文字列が表示されますので、それを設定します。
  • OAuthRedirectURI
上記コールバックURLと同じものです。OAuthの認証には暗唱方式とコールバックURL方式があって…と説明すると長いですが、mobileアプリの場合には https://login.salesforce.com/services/oauth2/success を設定します。これでないと、認証を中断した時などに二度とログイン画面が出てこなくなります(経験者は語る)。
  • OAuthLoginDomain
これはSalesforceにログインする時にお馴染みの login.salesforce.com をセットします。デフォルトではサンドボックスに接続するために test.salesforce.com になっていますが、サンドボックスを使う場合と本環境とではここで切り替わるようになっているようです。ただし、本番とサンドボックスを切り替える場合には、上記コンシューマ鍵もそれぞれの環境で作った別のものが必要ですのでご注意ください。
これでようやくcmd+Rできます。長かった…orz

ビルドが終わるとエミュレータが起動し、スプラッシュ画面の後、Salesforceのログイン画面になります。ここであなたのSalesforce idとpasswordを入力します。ログインに成功すると、このアプリからSalesforceへのアクセスを許可するかどうか確認する画面になります。認証すれば、このページの冒頭に貼ったような画面に切り替わるはずです。

お疲れ様でした。

いやー、読むのも大変だったと思いますが、いろいろググって正しい記述かどうかを確認しながらこれを書いたので、休憩を除いて4時間ぐらいかかりっきりでした。

…冒頭、「調べたいことがあったので」と書きましたが…今日はもういいや。忘れないようにモニタにポストイットを貼って(アナログだな)、また後日。

お疲れ様でした(大事なことなので(ry

2011年11月12日土曜日

MacBook proメモリ増設

■8GBで4980円■

というVintage Computerの広告を見て反射的に注文してしまいました。注文の時「送料ヤケに高いな」と思ったらカリフォルニアの会社だったw ともかく税金などあわせて7000円弱なので、まぁOKかなと。注文してから約1週間で届きました。


■さっそく、開腹■

何はなくともアップルのサポートページで段取りを確認しておきます。iPadなどに表示しておくと安心です。

システム終了後、バッテリ抜いて、電源(MagSafe)はもちろんつながっているコネクタも全部外して、8本のネジを全部外します。紛失は論外ですが、3種類ありますのでどこからどのネジを外したか間違えないように。



特殊工具っぽいのが写ってますが、ただの精密ドライバです。昔のMacは特殊なドライバーを使わないと分解できなかったんですが、民主的になったものです。しみじみ。ちなみに手前左側にあるのはHDD、バッテリベイからアクセスできます。そういや、このMacBookはまだHDDのままだった…。

開いたついでにブロワなどでホコリを掃除しておきます。ユニボディは密封構造なのでほとんどホコリはついてないと思いますが。

金属部分に触れて静電気を逃がした後、メモリの両側のレバーを指で広げるとメモリが起きてきますので(この時のメモリの起きてきた角度を忘れないように)、メモリ板をそっと引っ張り出します。昔と違ってゴリゴリやらなくても出て来ます。力の目安としてはUSBコネクタなどより弱い力で抜けますので、それより力を入れても抜けないようであれば、レバーの開き方が悪いなど何か間違っている、ということです。よくレバーの爪などを観察して、どうすれば抜けるかを考えてから再挑戦してください。


■復旧へ■

届いたメモリを静電気除去袋から取り出し、切り欠きの位置を合わせてさっき取り出した時の角度でメモリを差し込みます。これもUSBコネクタより弱い力で、メモリの金色の端子部分が隠れる程度まで差し込めばOKです。差し込んだら真下に水平になるまで押し下げると左右のレバーとメモリの切り欠きがだいたい一致して固定されます。一枚終わったら同じ手順でもう一枚。

終わったら、アルミフタをかぶせて、ネジ締めます。かぶせた状態だと周辺に少し隙間がありますが、0.5-1mmぐらいの均一の隙間なら問題ありません。ユニボディは立て付けが良いので、よほど間違った場所に置いたりゴミが挟まったりしていない限りはOKです。蛇足ですが、アルミフタが薄いので、ネジを一気に締めてはいけません。「☆」の一筆書きのような順番でアルミがひずまないように順次気長に少しずつネジ締めていきます。

バッテリを戻して(私はリチウム電池の劣化防止のため50%充電状態で外してますが)フタを締め、コネクタも元通りにつなぎます。ここで一つお祈りをしてから、電源スイッチON。起動して「このMacについて」で意図した通りのメモリサイズになっていればOK。


さて、朝飯にしよう。

#サポートページのリンクが切れていたので訂正しました(2011-11-18)

2011年11月9日水曜日

画像が消える…?

所沢航空記念公園 YS-11 2011-10-06
何度か続けて、Bloggerに貼った画像が消えるという現象が起きて困っておりました。

スクリーンショットが消えてしまうので、「こうすると治りますよ」の説明がとてもわかりにくいものに…。


■現象■

これまでは、スクリーンショットをプレビューで開いて全選択してコピー、Blogger上にそのままペーストしていました。ずっとそれで特に問題なかったのですが…10月後半ぐらいからは貼って数時間は問題なくても、その後消えて「?」になってしまうという現象が起きるようになりました。


■解消方法■

編集画面の画像アイコンをクリックして画像ファイルをアップロードし、アップロードした画像から選択する…というごく当たり前の手順ならば、上記の現象は起きないようです。

htmlソースを見ると、コピペで貼った場合には画像データがシリアライズされてそのままテキストに挿入されています。以前はそれでも問題なかったんですが、サーバにかかる負担は大きいので使わない方が良いですわね。

ということで。

2011年11月6日日曜日

Xcode 4.2 + iOS 5 + Force.com mobile SDK


■今週もまたXcode三昧■

このところ休日はXcode上でobjective-c三昧です。ようやくツールに馴染んできて、昔WebObjectsで散々使っていたクラスたちにも再会し、Google先生頼みですがとりあえず「iOSアプリとはこういうもの」というのが分かってきました。まぁ、過去30年+に及ぶプログラミング経験上、この時期が一番楽しいのですけどね。

過去の経験上、何か新しいツールを始める時には、とりあえず動くものをベースにしてイジり倒し、次にテンプレートなどを使わないで同じものを作ってみる、という段階を踏むと手っ取り早く習得できるように思います。一応本も読むのですが、それは前にも書いたようにそのツールの世界観をつかむためで、習得するためには動くものを少しずつ改造していくのが良です。


■しかしまたハマる:まずXcode 4.1■

さて、とりあえずメシの種にしようと思っているForce.com / Database.com + iOS方面、一応Salesforce mobile SDKのテンプレートから作ったプロジェクトに関しては、re-authentificationやCRUDはひと通り動き、DescriptionなどFlash toolkitでハマった一連の処理も片付いたので、次の段階としてまっさらなプロジェクトを作りその上でSalesforce対応アプリを書いていく練習に移りました。

まぁ真っさらなプロジェクトとはいえ、Xcodeだとテンプレートが基本的な構成作ってくれるんですけどね。考えてみりゃGUIをプログラムで生成してたのはMS-DOS上でcでアプリ書いてた頃ぐらいのもので、そのあとのVisualbasic, VisualC++, Symantec C/C++, Xcode+WOBuilderなど、ずっとGUI Builder頼みの人生だったな(遠い目)。

で、Xcode 4.1にアップデートしたのですが…ここの追記に書きましたが、Xcode 4.1 + iOS5では、Salesforce mobile SDKから生成したプロジェクトがうまく動いてくれませんでした。世間様でもiOS5になって日本語入力できなくなったアプリが何本がありましたが、これもそれと似たような感じでOAuthの認証画面でキーボードが出てこなくてidもpasswordも入力できないという現象です。

4.0.2に戻すのも面倒だし、せっかくなのでダメ元でXcode 4.2にアップデートしました。Salesforce mobile SDKテンプレートからのプロジェクト生成でも一発で動きました。やれやれ。

そういえば上記SDKのドキュメントには「このSDK使う時には Other Linker Flags-Obj_c -all_load と設定しろ」って書いてありますが、ありましたが、これは「-ObjC -all_load」の間違いですね。ええハマりましたともさ。
#修正されていました(2011/11/13)


■次にStoryboardにハマった■

先に書いておきますがStoryboardさんが悪いわけではないです、その特性などをよく調べないままに使って、私が勝手に「動かねー」と泣いていただけです。はい。

経緯は
  1. Xcode 4.2でMaster-DetailテンプレートからStoryboard対応のアプリを生成
  2. Salesforce mobile SDKテンプレートから生成したプロジェクトから必要箇所をコピペ
  3. Table View Cellを従来のnibからカスタマイズして実装 →It just works.
  4. 次に、また新しくMaster-DetailテンプレートからStoryboard対応のアプリを生成
  5. 上記2と同じく必要箇所をコピペ
  6. Table View CellをStoryboard上のTable View Cellに対してカスタマイズ
  7. OAuthが終わり、requestSOQLが返って来て、TableViewをreloadしようとしてSIGABRT
上記1-6については2-3時間でしたが、7は金曜日の夜一晩徹夜しても解決できませんでした。

Master-Detail + Storyboardの真っさらなプロジェクトを作り、手作りのArray of Dictionaryを単純にTable View Cell上に表示するだけなら動くので、問題はmobile SDKが何かdelegateを呼び出していて、それをインプリメントしていない等の原因で落ちているのだろう、と結論付けました。

とりあえずStoryboardにあるTableView下のTableView SectionとCellを削除すれば普通にTable Viewとして動きますし、Cellをカスタマイズする際には以前と同様にnib+implement classで実装できます。Cellをカスタマイズする際にStoryboard上に直接TableViewCellを置くことができればStoryboardの画面遷移が使えて今後何かと便利だと思うのですが、できないっすね。

まぁそのうち、何かwork aroundが出てくるでしょう。

この週末ずっとXcode 4.2をいじって、プロダクツという意味では何も成果なかったんですが、上記7を調べるためにアプリの挙動をずっと追いかけたりググりまくったりしたので、とりあえず、勉強にはなった、はず。

先週は本業でずっとApex+Visualforceを書いていて、週末はXcode三昧、体重は増えたけど、久しぶりに充実した日々でした。今週は…要件聴取が多いなぁ…。

#追記:スクリーンショットが消えるなぁ…PNGじゃなくてJPEGで貼って見ました(11月8日)。
#また消えた…:コピペじゃなくてアップロードしてみた(11/08 19:00)

2011年10月29日土曜日

PhoneGap 1.1.0でもちょっとハマる

■小さな落とし穴

PhoneGap入門といえば「古籏一浩のJavaScriptラボ」です。第65回以降PhoneGapが取り上げられています。記事が比較的新しく説明が丁寧で初心者の陥りがちなミスをちゃんとふさいでくれて、テーマも一通り網羅している。何度読み返したかわかりません。

この度ちょっと検証したいことがあって、久しぶりにPhoneGap-1.1.0をインストールしました。0.9から1.0に進んだのは知っていましたが、いつの間にか1.1なんすね。


で、試したのはForce.com Mobile SDK Developer PreviewのHTML5Application関連のことですが、以前0.9.5で動いていたはずのコードが動いてくれません。




■「PhoneGap 0.9.xでは動いたのに1.xが動かない」という方

以下PhoneGap 1.1.xのテンプレートから新規プロジェクトを作ることが前提です。テンプレートを動かしてみてシミュレータ上でスプラッシュとアラートは出て一応PhoneGapのコードは動いているけどクリックしてもPhoneGapのページに飛ばない、という方はXcode上のコンソールに表示されるエラーをご覧下さい。Xcode4ならWindow右上の3つ並んだViewアイコンの真ん中をクリックしてログを表示させます。


2011-10-22 04:52:39.620 PhoneGap[30146:2103] ERROR whitelist rejection: url='http://...


というような行があるはずです。参照先のホストがリストに登録されておらん、というPhoneGapからのお叱りです。これを解消するにはXcodeのProject Navigatorで「PhoneGap」グループの中にある「Supporting Files」グループ下の「PhoneGap.plist」をクリックします(PhoneGap-info.plistではありません…私は間違えたさ)。下記のような項目が表示されます。



ここでExternalHostsのArrayの左付近にカーソルを移動すると「+」「−」アイコンが表示されますのでプラスをクリックします。




(1 item)の下の入力欄に「*」と入力してください。これでどのホストとも接続できるようになります。ファイルを保存しプロジェクトを実行すると今度はちゃんと動いているはずです。




■しかしこの直後…


さあ、これでPhoneGapでいろいろ試せるぞ…と思った直後風邪でダウンしました。これを書いている10月29日でもう8日目、4日目にムリして会社に出たのと5日目に同じくムリして客先に出たのが決定打になりました…。自営業という引きこもりを長年やっていた50歳は免疫がないので感染症に弱いのです。


皆さんもどうぞご自愛くださいませ…。


#貼り付けたスクリーンショットが消えていたので貼り直しました(2011/11/06)
#貼り付けたスクリーンショットがまた消えていた(ry JPEGで貼った(2011/11/08)
#貼り付けたスクリーンショットが(ry  GIFにしてみた(2011/11/08 18:51)
#貼り付け(ry コピペでなく画像アップロードしてみた (2011/11/09 06:06)

2011年10月22日土曜日

Salesforce Mobile SDK for iOSでハマる


■例によってハマりました

Salesforce Mobile SDKはiOS / AndroidからREST経由で簡単にSalesforceにアクセスできちゃうよ!っていう有り難いSDKです。OAuth認証も簡単で、Native, Hyblid, HTML5版に対応しています。

ここのページにあるPDFとiOS Mobile Templateは一発で動くんですよね、Xcodeのバージョンなどに間違いがなければ。で、もてる知識を総動員してChatterのFeedから特定のキーワードを引っ張ってくるアプリなんぞを作って喜んでいたものでした。で、テンプレートではできることにも限りがあるので、新規にiOSプロジェクトを作って、そこにMobile SDKを入れて作った1本目は奇跡的にか何かの冗談か、一発でうまく動きました。

しかし。 その次、わりと本格的なというか倉橋屋の社運(社じゃないが)をかけたプロジェクトを作ろうとしたところ、開発以前のところでビルドが通らなくなってしまいました。この間にiOS5へアップデート、Google Data APIsに浮気、Xcode 4.0.2から4.2にアップデートする…等いろいろと狼藉を働いたので、何が原因かわかりません。この辺がiOSシロウトの哀しいところです。

Xcodeがはき出すエラーは「missing required architecture i386 in file」が26個、同じ原因でワーニングが6個ほど出てきます。まぁこういうエラーの山は大抵1カ所つぶすだけで直るか激減するものです。今回もそうでした。

結果から申しますと、Force.com SDKからDependenciesをdrag & dropで追加した際にXcodeプロジェクトのFramework Search Pathsに勝手に余計なパスが追加されていたことが原因でした。"$(SRCROOT)"とか$(inherited)が入ってたら消します。プロジェクト>Build Settingsから「framework」で検索してSearch pathsの下のFramework Search Pathsを見てください。

いやはや…。

2011年10月8日土曜日

iOSアプリ開発ツール


■趣味プログラミング■

長らく自営業で土日も趣味だか仕事だかわからないような状態でMacに向かっていたのですが、せっかくサラリーマンになって土日はしっかり休めるのだからと30年ぶりぐらいに趣味のプログラムを再開しております。

で、iOSアプリなんかをじわじわ書いています。昔はiPhoneアプリといえばXcodeでobjective-cで書くしかありませんでした。しかし、私はどうもこのobjective-cというヤツが苦手でした。メモリ管理みたいな足回りの処理を書きたくねぇ、ってかいうか頭悪いから書けないorz

そんなこんなで、もっとラクのできるツールはないか探してみました。

条件としては:
  • ラク
  • メモリリークで苦しむのはいや
  • できればAndroid / iOSクロスプラットフォーム
  • できればAppStoreで公開したい
  • Force.com/Database.comを比較的楽に使える
  • WYSIWYGなGUI build toolがある
てな感じで。


■Flash Builder 4.5■

ラクと言えば一番ラク。通常のFlashより使えるGUI部品が少なかったりメモリ消費大きいけど「とりあえず15分でforce.comから取ってきたデータを表示するアプリ書け」って言われても不可能ではない。

ただ、やっぱり重い。なるべくラクをして生きていきたい私としては、ごく初歩的なアプリですら重くなってしまう環境はカンベンしていただきたい。


■混迷■

この辺から何故か「どうせならAndroidとiPhoneクロスプラットフォーム」を目指すようになりまして。


■PhoneGapとTitanium Mobile■

TitaniumはHTML5+JavaScriptのWebアプリをラップしてスタンドアローンアプリにしてくれるツール、Titanium MobileはJavaScript+CSSっぽい言語でソースを書くとネイティブコードを吐いてくれるツール…っていう定義で良いのかしら。

どっちのツールもGUI Builderがない。ただし、PhoneGap+jQueryについては、Dreamweaver CS 5.5でGUIを使えるようになったし、この当時重視していたクロスプラットフォームという条件を無視すれば、PhoneGap+DashCodeという選択肢もある。

しかし、どっちも(少なくともこの時点では)例えば変数名を間違えた状態でもそのままビルドできてしまい、コンパイル系言語でおなじみの「そんな変数ねえよ」っていうエラーが出てくれない。実はPhoneGapのチュートリアル、それで半日ツブしてしまいまして…試して見たらTitaniumも同じだったので、「これは私にはムリだ」と離脱。そういうのを防ぐためにもTestってものがあるんですが、私はテストコード上でも同じ間違いをするという奇跡的なボケをかます持ち主なのでダメです。

とりあえずヒトに頼まれたアプリを1本リリースしてみたもののしんどくて見事に挫折しました。


■DashCode■

これはもちろんiOS専用のツールなんですが、HTML5+JavaScript+CSSでほとんとネイティブと見た目変わらないWebアプリを作ることができて、上記PhoneGapを使ったり、ちょっとしたラッパを使えばAppStoreで売り出すようなネイティブアプリも作れる。

DashCodeはApple製の専用開発ツールでEclipse系と違ってサクサク動くし細かいところに気が利いていて使いやすい。Eclipseの見た目も使用感も嫌いな私としてはまさに手に馴染むツール。iOSアプリを書く上でできないことはほとんどない。言語もJavaScriptなのでメモリリークの心配いらないし、何より一番得意な言語なので少なくとも言語習得の負荷がないのはありがたい。

ただ、Force.comにアクセスするためのコードは自分で書かなければいけない。私はやっぱりデータベース系アプリ屋なので、クラウド上に置いたリレーショナルデータベースをあれこれいじりたい。そうなった場合、制約はあるもののWebアプリやFlashアプリとの連携を考えるとForce.com / Database.comへのアクセスがラクにできた方が良い。

そんなわけでDashcodeのお勉強も現在は保留中です。


■で結局Xcode■

Xcodeでobjective-cでiPhoneアプリ書いてます。最初の条件は忘れてくださいははははは。

悩んでいる最中にSalesforceからiOS用SDKが正式にリリースされたことが決定打になりました。OAuthに対応しているし、queryもさくっと書けてNSArray of NSDictionary(おお懐かしい、WebObjects的に)に落ちるので、扱いもラク。タイポもちゃんとコンパイラが拾ってくれるw

何より現在のXcode 4.1ではRhapsodyやWebObjects 4の頃とは違い、操作上のへんな制約も減ってSeaser2のひがさん的な意味で「流れるように」作業ができる。繰り返しになるけど、Eclipseのどうももっさりした動作とへんな色使いとずんぐりしたGUIが好きになれない自分としてはXcodeは違和感なく操作ができて手に馴染みます。
 objective-cも付き合ってみればそんなに悪いやつじゃないしw

まぁしかし一つのFrameworkを手足のように操作できるようになるのは簡単なことではないので、テキストだらけの地味な画面を相手に遊んでます。

入門書はXcode4に対応しているやつが良いってんでとりあえず左の書籍を買ってみました。開発の流れを把握しやすい作りになっていてわかりやすいです。入門書の役割はこの「作業の流れを知る」ってことだと思ってます。ググれば何でも調べられるこのご時世、ピンポイントで知りたいAPI情報などはいくらでも探すことができますが、初心者が一番最初に越えるべき壁はツールに慣れることですからね。


■追記(2011/10/30)■

上記iOS用SDKは、Xcode 4.1 + iOS5では動きません。起動しても1回目はスプラッシュが出た後でSalesforce認証画面の後白い画面に変わりそのまま、2回目以降はスプラッシュ出た後にクラッシュします。


■追記(2011/11/06)■

Xcode 4.2にしたら動くようになりました。よかったよかった。