お仕事でiPhoneのテザリングを使ってみた

12月に行われたRuby on Railsの講習で、iPhone5 (au) のテザリングを使ってみました。


使い方

12月に3日間 x 4回のRuby on Rails講習を行い、ネット接続はテザリングだけで試してみました。

講習中に使うネット接続は

  1. 弊社のサポートサイト(redmineWiki)にある、補助教材へのアクセス
  2. Ruby, Ruby on Railsのドキュメント閲覧
  3. ライブラリーとして使うGitHubのアクセス
  4. DHHのRailsカンファレンスのビデオを短時間上映
  5. ScreenCastを短時間上映
  6. 自分のGmailチェック

などで、大量のアクセスはありませんが、接続出来ないと円滑な講習は出来ません。

結果

通信量

12月のネット通信量は 約 1.7Gbyte でした。 7Gbyte制限は余裕でした! ちなみに通信量には、講習以外のメール、facebook等の通信量も入っています。

接続状況

第2〜4週目は、窓際の会議室だったので、LTEで接続でき、ネット接続は、まったく問題ありませんでした。


しかし、第1週目は、ビル内側の会議室で、1〜2 時間くらいするとMacのネット接続が切れてしまいました ^^);
接続は 3G ですが、アンテナ5本立っていていました、iPhone 自身からのネット接続は快適に出来ましたが Macからは接続が切れていました。
ちなみに、MaciPhoneの接続はUSB接続を使いましたが、Wifiでも接続が切れました。

接続が切れてしまった時の対処ですが、「インターネット共有」(テザリング) を OFFにして再度ON にすると接続できるようなる事が分かったので、講習はなんとかなりました。

テザリングのON/OFFで再接続できるので、電波やハードの問題ではなくソフト(OS)の問題だと思います。OSのバージョンアップで直ってくれる事を期待します。

現在お手伝いしているサービス Real Coach (リアルコーチ) が日経新聞に紹介されました

現在お手伝いしている Ruby on Rails 3.1 + iPhone + iPad を組み合わせたサービス Real Coach が日経新聞に紹介されました
→記事

iPhoneで撮影した動画を専門のコーチがiPadで診断するというとても興味深いサービスです! RealCoachホームページ にある動画を是非ご覧下さい、このサービスの良さと応用範囲が広い事を感じ取って頂けると思います。



システムの方は、現在絶賛開発中です。

Tweetなう Version 2.0のソースコードを公開しました

心を入れかえて大幅バージョンアップした 「Tweetなう」 こと Tweetなう Version 2.0のソースも GitHub で公開しました。


今回ためしに AdMobを組み込んでみました、AdMob-SDKやIDの取得は AdMobのページから行って下さい。不要な場合は消して下さい。

心を入れかえて大幅バージョンアップした 「Tweetなう」

以前作ったiPhoneアプリ Tweetなう (無料)は、とりあえず作ってみました感の強いものでしたが、心を入れかえ大幅バージョンアップを行い、大変に便利なアプリになりました。


使い方は、駅に着いたときに Tweetなう を起動すると、"その駅名 なう" と つぶやけるようになります。
ここで、そのままつぶやいても良いですし、キーボードで言葉を足すこともできます。



今回のバージョンアップの目玉は、キーボードの上に登録された言葉の一覧が表示される事です。そこから選ぶだけで、移動中でも瞬間的に Tweet 出来ます!



現在いる駅の情報はiPhoneGPS機能で取得するのですが、地下鉄の駅などでは正しい駅名が表示されない事がよくおきます。そんな時は地図をスクロールし駅の上のピンをタップすることで、その駅名を出す事が出来ます、下の画像は札幌までスクロールしてみたところです :-)



つぶやく言葉は、直前につぶやいた(正確には入力されていた)言葉が記憶されているの言葉メモの+(追加)ボタンで簡単に登録できます。


そのほかにも色々と改良を加えてみました。 世界で一番 Tweetなう を使っている私で自身を持って人にもお勧め出来るアプリになったと思っています。
是非ダウンロードしてみて下さい m(_ _)m


P.S
ソースコードの方も近々 GitHubにアップします。

「iPhoneアプリ設計の極意」は良いiPhoneアプリを作りたいと思っている人の必読書!

iPhoneアプリ設計の極意 ―思わずタップしたくなるアプリのデザイン は良いiPhoneアプリを作りたいと思っている デザイナー、プログラマー、プロデューサーの必読書です! 内容や感想はたくさん書かれているので、ここではここでは、この本の使い方を書きます。

iPhoneアプリ設計の極意 ―思わずタップしたくなるアプリのデザイン

iPhoneアプリ設計の極意 ―思わずタップしたくなるアプリのデザイン

1. 買ったら、一通り眺める

買ったら、一通り読んで下さい。時間が無いという人は、サブセクションのタイトルとそこで取り上げられているアプリの画面と説明だけ読むだけでも、かなりのアプリ設計の極意を知ることができるとと思います。

2. アプリのリリース前に目次を見直し気になる部分を読み返す

アプリのアルファ版が完成し、開発者や関係者内でアプリの評価やテストを行う際には、この本を取り出し目次を眺めます。
そして、気になるセクションを読んでみましょう。
きっと、そのアプリの問題点や改善点が見つかると思います。


現在、私もあるアプリをバージョンアップ中だったので、アプリの UI上の問題点が次々に発見できました!
良いiPhoneアプリを作るための、素晴らしく有用な本だと思います。

南東京iPhone勉強会でEvernote APIの話をしました

先週末に行われた 南東京iPhone開発者勉強会 9回目 でEvernoteAPIの入門的な話をしました。

内容は、以前にこのブログに書いた内容をまとめた感じのもです。Evernoteデータの表示はそれほど難しいものではないので試してみて下さい。

iOS で Evernote API を使う(後編)

iOS で Evernote API を使う(前編) の続きです。今回はEvernoteからデータ(ノート)を取得し表示してみます。

http://www.evernote.com/about/media/img/logos/evernote_logo_4c-lrg.gif

Evernoteデータ形式

Evernoteのデータは Evernote Markup Language (ENML) と名づけられたXMLファイルです。しかし下の例から解るようにほぼHTMLです。

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd">
<en-note style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;">
<div style="padding-top: 0px; padding-right: 0px; padding-bottom: 0px; ・・・">
 ・・・
<h2 style="padding-top: 0px; padding-right: 0px; ・・・">問題解決型の教育</h2>
<p style="・・・ color: rgb(85, 85, 85); line-height: 20px;">EY-Officeの教育は知識の取得を目的とした学校的な教育ではなく、 実務で必要とされる技術を、
<br style="・・・ color: rgb(85, 85, 85); line-height: 20px;"/>
お客様のニーズや開発メンバーのスキルに応じ柔軟な教育プランを組み立てご提案させて頂く
<strong style="padding-top: 0px; padding-right: 0px; ・・・ ">「問題解決型の教育」</strong>&#160;です。</p>
 ・・・
<en-media title="EY-Officeの教育" alt="EY-Officeの教育" style="padding-top: 0px; ・・・"
   type="image/jpeg" hash="819b7511534b5ecb92ef4a6bc8650350"/>
  ・・・
</en-note>

HTMLとの違いは、

  • 開始タグ、終了タグ
  • 使われるタグは HTMLの一部
  • スタイル要素は全てstyle属性内に展開されている
  • 画像など(リーソースと呼ばれています)は独自のen-mediaタグが使われている、このタグの属性は
    • type: mineタイプ
    • hash: リソースのファイル名(Hash値)

なので、簡単に HTMLに変換できます。詳細は Evernote API Overview に書かれています。

通常のEvernoteデータへのアクセス

前編 で取得したキーではSandboxのデータしかアクセスできませんが、本物のデータをアクセスしたい場合は Evernote API Key Activation ページで申請することでキーがアクティベートされます。アクティベートはたぶん人手で行われている様で、USのビジネス時間になるとメールで返事が返ってきます。

テストアプリの改造

前編 で作ったアプリで表示されるノートの一覧をクリックすると、そのノートが別のページに表示されるようにします。ソースコード GitHub で公開しています。

→ クリック →


また、複数のページから Evernote APIをアクセスする必要があるので、Evernoteを扱うシングルトンのモデルを作りEvernoteとのやり取りはまとめます。

Evernoteモデル

Evernoteモデルの仕様(.h)は以下のようにしました
@interface Evernote : NSObject {
  @private
    NSString *authToken;
    EDAMNoteStoreClient *noteStore;
}
+ (Evernote *)sharedInstance;   // シングルトンインスタンスの取得
- (BOOL)authentication;  // 認証を実行
- (NSArray *)listNotebooks;  // ノートブックの一覧を取得
- (NSArray *)listNotesInNotebook:(NSString *)notebookName; // 指定されたノートブック内の全ノートの一覧を取得
- (NSURL *)getHtmlNoteByGuid:(NSString *)guid;  //指定されたID(guid)のノートデータを取得し、 HTMLに変換
@end
認証、ノートブックの一覧を取得の部分

前編作った認証、ノートブックの一覧の部分は以下のようなメソッドにしてみました。

|#import "Evernote.h"

#define USER_STORE_URL  @"https://www.evernote.com/edam/user"  //本当のデータをアクセスします
#define NOTE_STORE_URL  @"http://www.evernote.com/edam/note/" //本当のデータをアクセスします

#define USERNAM         @"XXXXXX"  //自分のログイン、 APIキーを入れて下さい
#define PASSWORD        @"XXXXXX"
#define CONSUMER_KEY    @"XXXXXX"
#define CONSUMER_SECRET @"XXXXXX"

#define RAISE_EXCEPTION_IF_ERROR(error) if (error) [NSException raise:@"Error" format:[error description]]

@interface Evernote ()
@property (nonatomic, retain) EDAMNoteStoreClient *noteStore;
@property (nonatomic, retain) NSString *authToken;
- (void)relaceMutableString:(NSMutableString *)s pattern:(NSString *)p replace:(NSString *)r;
- (NSString *)hexStringFromData:(NSData *)data;
@end

static Evernote *sharedInstanceDelegate = nil;

@implementation Evernote
@synthesize noteStore;
@synthesize authToken;

- (BOOL)authentication {
    self.authToken = nil;
    self.noteStore = nil;

    THTTPClient *userStoreHTTPClient = [[[THTTPClient alloc] initWithURL:[NSURL URLWithString:USER_STORE_URL]] autorelease];
    TBinaryProtocol *userStoreProtocol = [[[TBinaryProtocol alloc] initWithTransport:userStoreHTTPClient] autorelease];
    EDAMUserStoreClient *userStore = [[[EDAMUserStoreClient alloc] initWithProtocol:userStoreProtocol] autorelease];
	
    BOOL versionOK = [userStore checkVersion:@"EDMATest" :[EDAMUserStoreConstants EDAM_VERSION_MAJOR] :[EDAMUserStoreConstants EDAM_VERSION_MINOR]];
    if (!versionOK) {
        NSLog(@"checkVersion error");
        return NO;
    }
    @try {
        EDAMAuthenticationResult *authResult = [userStore authenticate:USERNAM :PASSWORD :CONSUMER_KEY :CONSUMER_SECRET];
        EDAMUser *user = [authResult user];
        self.authToken = [authResult authenticationToken];
        NSURL *noteStoreURL = [NSURL URLWithString:[NOTE_STORE_URL stringByAppendingString:[user shardId]]];

        THTTPClient *noteStoreHTTPClient = [[[THTTPClient alloc] initWithURL:noteStoreURL] autorelease];
        TBinaryProtocol *noteStoreProtocol = [[[TBinaryProtocol alloc] initWithTransport:noteStoreHTTPClient] autorelease];
        self.noteStore = [[[EDAMNoteStoreClient alloc] initWithProtocol:noteStoreProtocol] autorelease];
        return YES;
    }
    @catch (NSException *e) {
        NSLog(@"Exception %@  %s", [e description], __FUNCTION__);
        return NO;
    }
}

- (NSArray *)listNotebooks {
    @try {
        return [noteStore listNotebooks:authToken];
    }
    @catch (NSException * e) {
        NSLog(@"Exception %@  %s", [e description], __FUNCTION__);
        return nil;
    }
}
ノート一覧取得

今回は、指定されたノートブック内の全ノートの一覧を取得するメソッドを作りました。
また、ノートの一覧(findNotes)は最大取得件数(findNotes) に大きな値を設定しても適当な件数(50件)しか戻しませんので、全ノートが取得できてない場合は続きを取得するようにしてみました。
このメソッドで戻るのはノートデータ(EDAMNote)の配列ですが、findNotesの戻すEDAMNoteにはノートのコンテンツは含まれていませんので、表示する際には改めてコンテンツを取得する必要があります。

- (NSArray *)listNotesInNotebook:(NSString *)notebookName {
    @try {
        NSString *words = notebookName ? [@"notebook:" stringByAppendingString:notebookName] : nil;
        EDAMNoteFilter *allFileter = [[[EDAMNoteFilter alloc] initWithOrder:0 ascending:NO words:words notebookGuid:nil tagGuids:nil
                                                                   timeZone:nil inactive:NO] autorelease];
        NSMutableArray *list = [NSMutableArray array];
        EDAMNoteList *subList;
        NSArray *notes;
        int offset = 0;
        do {
            subList = [noteStore findNotes:authToken :allFileter :offset :1000];
            notes = [subList notes];
            // NSLog(@"notes count %d, %d, %d", [notes count], [subList totalNotes], [subList startIndex]);
            [list addObjectsFromArray:notes];
            offset += [notes count];
        } while (offset < [subList totalNotes]);
        
        // NSLog(@"list count %d", [list count]);
        return list;
    }
    @catch (NSException * e) {
        NSLog(@"Exception %@  %s", [e description], __FUNCTION__);
        return nil;
    }
}
ノートデータを取得しHTMLに変換するメソッド

このメソッドではテンポラリーディレクトリーに1つディレクトリーを作り、そこにコンテンツのhtml、リソースの画像データを格納しています。戻り値はそのディレクトリーのURLです。これを使って UIWebViewでコンテンツを表示できます。

  • ノートコンテンツの取得はgetNote:メソッドを使います。引数の指定でノート内に含まれている画像等のリソースを一度に取得する事もできますが、今回はリソースは別にしました。
  • 上で説明したように、取得された XMLの一部を書き換える事で HTMLに出来ます。ただし、en-mediaタグを画像に変換する部分はEvernote側の都合で属性の順番が変わると動作しなくなるので注意して下さい :-)
  • 画像は ハッシュ.image.png のようなファイル名になります。
  • リーソースはEDAMNoteのresourcesの情報を使いgetResource:メソッドで取得しています。
  • コンテンツ内に <div ... /> というタグがある場合、表示がおかしくなるので <div ...></div>に変換しています。(DOCTYPE とかでなんとかならないのかな?)
- (NSURL *)getHtmlNoteByGuid:(NSString *)guid {
    EDAMNote *note;
    @try {
        note = [noteStore getNote:authToken :guid :YES :NO :NO :NO];
        NSLog(@"- note %@ : %@  : %d %d", [note guid], [note title], [[note content] length], [[note resources] count]); 

        NSMutableString *html = [NSMutableString stringWithString:[note content]];
        // NSLog(@"--\n%@\n", html);
        [self relaceMutableString:html pattern:@"^.*<en-note.*?>" 
                          replace:@"<html><head><title>note</title><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" /></style></head><body>"];
        [self relaceMutableString:html pattern:@"<\\/en-note>" replace:@"</body></html>"];
        [self relaceMutableString:html pattern:@"<en-media.*?style=\"(.*?)\".*?type=\"(\\w+?)/(\\w+?)\".*?hash=\"(\\w+?)\"/>"
                          replace:@"<img src=\"$4.$2.$3\" style=\"$1\" />"];
        [self relaceMutableString:html pattern:@"<en-media.*?style=\"(.*?)\".*?hash=\"(\\w+?)\".*?type=\"(\\w+?)/(\\w+?)\">.*?</en-media>"
                          replace:@"<img src=\"$2.$3.$4\" style=\"$1\" />"];
        [self relaceMutableString:html pattern:@"<div ([^>]*?)/>" replace:@"<div $1></div>"];
        //NSLog(@"--  html --\n%@\n", html);
        
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSError *error = 0;
        NSString *contentsPath = [NSTemporaryDirectory() stringByAppendingString:@"contents"];
        if ([fileManager fileExistsAtPath:contentsPath]) {
            [fileManager removeItemAtPath:contentsPath error:&error];
            RAISE_EXCEPTION_IF_ERROR(error);
        }
        [fileManager createDirectoryAtPath:contentsPath withIntermediateDirectories:NO attributes:nil error:&error];
        RAISE_EXCEPTION_IF_ERROR(error);
        
        NSString *htmlPath = [contentsPath stringByAppendingString:@"/index.html"];
        [html writeToFile:htmlPath atomically:NO encoding:NSUTF8StringEncoding error:&error];
        RAISE_EXCEPTION_IF_ERROR(error);
        NSLog(@"write html %@", htmlPath);
        
        for (EDAMResource *resourceInfo in [note resources]) {
            EDAMResource *resource = [noteStore getResource:authToken :resourceInfo.guid :YES :NO :NO :NO];
            NSString *hash = [self hexStringFromData:[[resource data] bodyHash]];
            NSString *ext = [resource.mime stringByReplacingOccurrencesOfString:@"/" withString:@"."];
            NSString *path = [contentsPath stringByAppendingFormat:@"/%@.%@", hash, ext];
            [[[resource data] body] writeToFile:path options:0 error:&error];
            RAISE_EXCEPTION_IF_ERROR(error);
            NSLog(@"write res  %@", path);
        }

        return [NSURL fileURLWithPath:htmlPath isDirectory:NO];
    }
    @catch (NSException *e) {
        NSLog(@"Exception %@  %s", [e description], __FUNCTION__);
        return nil;
    }
}
- (void)relaceMutableString:(NSMutableString *)s pattern:(NSString *)p replace:(NSString *)r {
    NSError *error = NULL;
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:p
                                                                           options:NSRegularExpressionDotMatchesLineSeparators
                                                                             error:&error];
    [regex replaceMatchesInString:s options:0 range:NSMakeRange(0, [s length]) withTemplate:r];
}

- (NSString *)hexStringFromData:(NSData *)data {
    NSMutableString *hex = [NSMutableString string];
    
    unsigned char *buff = (unsigned char *)[data bytes];
    for (int i = 0; i < [data length]; i++) {
        [hex appendFormat:@"%02x", buff[i]];
    }
    
    return  hex;
}

その他のコード

モデルのその他の部分、ビュー・コントローラ等のコードは、ソースコードGitHub https://github.com/yuumi3/EvernoteTest で公開していますので、それを見て下さい。