検索キーワード「���������������」に一致する投稿を関連性の高い順に表示しています。 日付順 すべての投稿を表示
検索キーワード「���������������」に一致する投稿を関連性の高い順に表示しています。 日付順 すべての投稿を表示

2009年2月12日木曜日

jweeklyforecast (server) のリポジトリを公開しました

http://github.com/akisute/jweeklyforecast/tree/master

先日話題にいたしました、天気予報用API提供用サーバーjWeeklyForecastのリポジトリを作成いたしましたので、取り急ぎご報告だけさせていただきます。


ちなみに中身はスッカスカです。まだGoogle App Engineの本番サーバーにもアップできていない状況なので、何とか今週末までにはアップしたいです!

2009年1月1日木曜日

iPhoneでMapView route-meを使ってみよう! RMMapContents詳細編

  • 利用する地図の提供元を変更したい場合には、tileSourceプロパティを変更する。利用できるtileSourceはMapViewプロジェクト内の/Map/Tile Sourceグループの中にある
  • 地図画像(Tile)が更新されたときに呼び出すためのdelegate(RMTilesUpdateDelegate)を持っている
  • 投影方法(Projection)をメルカトル図法以外に変更できるようなプロパティがあるが、実際に変更できるかどうかは未調査

前回の記事ではroute-meライブラリのRMMapViewクラスを調査して、地図の初期表示座標やズーム倍率を設定する方法を調べてみました。
今回はroute-meライブラリのうち、地図の実体そのものを表現するRMMapContentsクラスを調査してみようと思います。

さっそくRMMapContents.hを読んでみます。利用できるプロパティとメソッドの一覧はこんな感じです。
@property (readwrite) CLLocationCoordinate2D mapCenter;
@property (readwrite) RMXYRect XYBounds;
@property (readonly) RMTileRect tileBounds;
@property (readonly) CGRect screenBounds;
@property (readwrite) float scale;
@property (readwrite) float zoom;

@property (readwrite) float minZoom, maxZoom;

@property (readonly) RMTileImageSet *imagesOnScreen;

@property (readonly) RMProjection *projection;
@property (readonly) id mercatorToTileProjection;
@property (readonly) RMMercatorToScreenProjection *mercatorToScreenProjection;

@property (retain, readwrite) id tileSource;
@property (retain, readwrite) RMMapRenderer *renderer;

@property (readonly) CALayer *layer;

@property (retain, readwrite) RMMapLayer *background;
@property (retain, readwrite) RMLayerSet *overlay;
@property (retain, readonly) RMMarkerManager *markerManager;
@property (nonatomic, retain) id tilesUpdateDelegate;
@property (readwrite) NSUInteger boundingMask;

- (id) initForView: (UIView*) view;
- (id) initForView: (UIView*) view WithLocation:(CLLocationCoordinate2D)latlong;

// Designated initialiser
- (id)initForView:(UIView*)view WithTileSource:(id)tileSource WithRenderer:(RMMapRenderer*)renderer LookingAt:(CLLocationCoordinate2D)latlong;

- (void) didReceiveMemoryWarning;

- (void)moveToLatLong: (CLLocationCoordinate2D)latlong;
- (void)moveToXYPoint: (RMXYPoint)aPoint;

- (void)moveBy: (CGSize) delta;
- (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center;
- (void)zoomInToNextNativeZoomAt:(CGPoint) pivot animated:(BOOL) animated;
- (void)zoomByFactor: (float) zoomFactor near:(CGPoint) center animated:(BOOL) animated;

- (void)zoomInToNextNativeZoomAt:(CGPoint) pivot;
- (float)adjustZoomForBoundingMask:(float)zoomFactor;
- (void)adjustMapPlacementWithScale:(float)aScale;
- (void)setZoomBounds:(float)aMinZoom maxZoom:(float)aMaxZoom;

- (void) drawRect: (CGRect) rect;

// During touch and move operations on the iphone its good practice to
// hold off on any particularly expensive operations so the user's
+ (BOOL) performExpensiveOperations;
+ (void) setPerformExpensiveOperations: (BOOL)p;

- (CGPoint)latLongToPixel:(CLLocationCoordinate2D)latlong;
- (CGPoint)latLongToPixel:(CLLocationCoordinate2D)latlong withScale:(float)aScale;
- (CLLocationCoordinate2D)pixelToLatLong:(CGPoint)aPixel;
- (CLLocationCoordinate2D)pixelToLatLong:(CGPoint)aPixel withScale:(float)aScale;

- (void)zoomWithLatLngBoundsNorthEast:(CLLocationCoordinate2D)ne SouthWest:(CLLocationCoordinate2D)se;
- (void)zoomWithRMMercatorRectBounds:(RMXYRect)bounds;

- (RMLatLongBounds) getScreenCoordinateBounds;
- (RMLatLongBounds) getCoordinateBounds:(CGRect) rect;

- (void) tilesUpdatedRegion:(CGRect)region;

長い!長いです!
RMMapContentsクラス独自のメソッドだけではなくて、RMMapViewクラスが持っている機能と同じ、地図の表示位置を動かしたり、拡大倍率を操作するメソッドも持っています。
どういうことかと調べてみると・・・
-(void) moveToXYPoint: (RMXYPoint) aPoint
{
if (delegateHasBeforeMapMove) [delegate beforeMapMove: self];
[contents moveToXYPoint:aPoint];
if (delegateHasAfterMapMove) [delegate afterMapMove: self];
}
-(void) moveToLatLong: (CLLocationCoordinate2D) point
{
if (delegateHasBeforeMapMove) [delegate beforeMapMove: self];
[contents moveToLatLong:point];
if (delegateHasAfterMapMove) [delegate afterMapMove: self];
}

どうやらRMMapViewクラスのメソッドの実装は、デリゲートのメソッドの呼び出しに加えて、RMMapContentsクラスのメソッドを呼び出しているだけみたいですね。

閑話休題。
たくさん利用できるプロパティとメソッドがありますが、今回はその中でも「利用する地図の提供元を制御する」方法を調べてみようと思います。

RMMapViewの標準の地図提供元はOpenStreetViewです。
http://www.openstreetmap.org/
OpenStreetViewとは一般のユーザーがGPSを利用して測定した道などの地理情報を自由にアップロードして作るオープンソースな地図プロジェクトです。
このOpenStreetView、ヨーロッパなどのプロジェクト参加者の多い地域では非常に精度がよいのですが、
日本ではまだそれほど定着していないようで、ちょっと田舎に入ると地図の精度が絶望的なことになってきます。

ということで、地図の提供元をOpenStreetViewから別の提供元に変更する必要があります。
route-meでは、OpenStreetView以外にも標準で、
Microsoft VirtualEarth(http://www.microsoft.com/japan/virtualearth/ http://maps.live.com/
またはCloudMade(http://www.cloudmade.com/ http://maps.cloudmade.com/
の地図を利用することができます。

やり方は簡単で、RMMapViewのcontentsプロパティのtileSourceプロパティに、
MapViewプロジェクト内の/Map/Tile Sourceグループ内のクラスを指定してやるだけです。
たとえばMicrosoft VirtualEarthを利用するときは、以下のようにします。
    RMMapView *mapView = [[[RMMapView alloc]
initWithFrame:[UIScreen mainScreen].applicationFrame WithLocation:initialLocation.coordinate]
autorelease];
mapView.contents.tileSource = [[[RMVirtualEarthSource alloc] init] autorelease];

これを実行すると以下のようになります。


見事VirtualEarthになりました。これで日本地図を拡大しても安心です。

※2009/01/11追記
本記事で紹介している、RMMapViewクラスのcontentsプロパティのtileSourceプロパティを書き換える方法を用いると、
新しくサーバーからロードされてくるタイルに関しては変更後のtileSourceからロードされてくるのですが、
既存のロード済みのタイルに関しては変更前のtileSourceからロードされてキャッシュされている画像がそのまま表示されてしまいます。

従って、以下のような実装ができません。
  • 最初からOpenStreetMap以外の地図提供元を利用した実装ができない。RMMapViewのinitと同時に最初の地図情報がOpenStreetMapからロードされてしまうため。
  • あとからユーザーの操作に応じて地図の提供元を変更するような実装ができない。
この問題はroute-meプロジェクトのバグトラッカーにも掲載されています。
http://code.google.com/p/route-me/issues/detail?id=12#c5
現在のところは残念ながら改善されていないようです。とりあえず今のところは、以下のような対応を心がけましょう。
  • 途中で地図の提供元を変更する場合には、動的に画像キャッシュをクリアする。ただし、簡単にキャッシュをクリアできるような構造になってはいない。
  • 最初からOpenStreetMap以外の地図提供元を利用するときには、ダウンロードしてきたMapViewプロジェクトのソース自体を直接修正する。
具体的にはRMMapContents.mの56行目の、以下の箇所を書き換えることで実現できます。
- (id) initForView: (UIView*) view WithLocation:(CLLocationCoordinate2D)latlong
{
//この行のRMOpenStreetMapsSourceを変更する
id _tileSource = [[RMOpenStreetMapsSource alloc] init];
RMMapRenderer *_renderer = [[RMCoreAnimationRenderer alloc] initWithContent:self];

id mapContents = [self initForView:view WithTileSource:_tileSource WithRenderer:_renderer LookingAt:latlong];
[_tileSource release];
[_renderer release];

return mapContents;
}

バグトラッカーにも高い優先順位で掲載されているので、近い将来に対応されることが期待できますので、それまではちょっと様子見という感じでしょうか?

2008年12月27日土曜日

iPhoneでMap Viewを使いたいので、ライブラリを探してみました

  • iPhone Google Maps Component
  • route-me
  • TouchMap
  • 個人的にはroute-meがおすすめ、ただしMicrosoft Virtual Earthを使うことになる
  • ストリートビューが欲しい、またはどうしてもGoogle Mapで実装したいという人はAppleがCocoa Touchに組み込んでくれるのを期待しつつ待つしかない

iPhoneで開発をしている人なら、誰しも一度はこう思うでしょう。
「標準のGoogle Mapアプリみたいに、地図を使ったアプリが作りたい」と!
GPS・加速度センサー・タッチ操作に強力な通信機能、おまけに3D描画もできると、
こんなにすてきな機能と地図がくみ合わさったら、その可能性は無限大に違いありません。

が。しかし、なんということでしょう。
皆様ご存知の通り、Cocoa Touch Frameworkに地図機能は存在しないのです!
ライバルのAndroidにはあんなにすてきな地図機能があって、自由に使えるというのに!

さてさて前置きが長くなってしまいましたが、要するに、

「iPhoneでMap Viewが使いたいんだけどどうすりゃいいの」

ということです。
調べてみたところ、以下の3つのオープンソースライブラリが見つかりました。

○iPhone Google Maps Component
http://code.google.com/p/iphone-google-maps-component/
その名の通り、Google Mapを利用したMap Viewライブラリです。
実装にはiPhoneのUIWebViewを利用しており、JavaScriptを用いてGoogle Mapにアクセスし、
描画を行っているようです。
画面上に描画点(プロットとか画像とか)を自由に配置することができます。

○route-me
http://code.google.com/p/route-me/
こちらはObjective-Cネイティブ実装のMap Viewライブラリです。
ネイティブ実装であるため、きわめて軽快で高速な動作が特徴です。
ただし地図の提供元がOpenStreetMapか、Microsoft Virtual Earthに限られてしまいます。

○TouchMap
http://toxicsoftware.com/touchmap/
全く未知数です。
Objective-CまたはC言語による実装で、
地図の提供元がMicrosoft Virtual Earthであるということ以外何もわかりません。

いずれもGoogle Map StreetViewには対応していません。

さて、この中のどれを選ぼうか・・・というところですが
まずiPhone Google Maps Componentは真っ先に除外されます。
JavaScriptで実装されているため実機では重すぎて使い物になりません。唯一のGoogle Mapsを用いた実装だけに非常に残念です。
残る二つからroute-meをとるか、それともTouchMapをとるか悩みましたが、
TouchMapのほうがドキュメントが少なく、また開発頻度が悪い(10月から一度も更新されていない)ため、
現在最も勢いのありそうなroute-meを今回採用することにしました。


次回はroute-meのページに用意されているインストールの手引きを見ながら、
実際に自分のアプリの中で地図を動かしてみようと思います。
http://code.google.com/p/route-me/wiki/EmbeddingGuidev2

2008年12月6日土曜日

githubに自分のリポジトリを作ってコミットしてみる

  • gitとは分散リポジトリ、分散リポジトリについてはこちらのページを参照
  • 要するにローカル上のリポジトリ+github上のマスターリポジトリを用意してくれる感じらしい
  • 手元のリポジトリにコミットするのがcommit、リモート(他人の)リポジトリにコミットするのがpush
  • githubへのpushには公開鍵を利用したSSHによる認証が必要
  • 自分のマシンで公開鍵と秘密鍵を作成し、公開鍵をgithubに追加。秘密鍵をssh-agentとやらを使ってローカルマシンにインストールする

私の周りのTwitter界隈でgithubなる良く分からないシロモノが大流行しているので、私も便乗してみることにしました。
バージョン管理システムの経験はCVSとSubversionのみ、しかも両方ともEclipseから使ったことがあるだけでコマンド操作なんて全く分からないど素人ですが、なに、使ってみれば分かるさ!

まずはgitをMacBook Airにインストール。MacPortを使えば一発です。
sudo port install git-core

これだけなのですが、依存関係がひっじょーーーに多いのでインストールに1時間ぐらい待たされました。

次、githubに自分のアカウントを作ります。これは説明不要。

次、githubに自分用の楽しい楽しいリポジトリを作ります。これも説明不要。分かりやすいです。


できました。名前はakisute_cs193p。そのまんま。

次、画面に表示された念仏を、そのまま何も考えずに実行します。
git config --global user.name "akisute"
git config --global user.email "this_is_my_boomstick@gmail.com"
cd ~/Documents/Xcode/
mkdir akisute_cs193p
cd akisute_cs193p
git init
touch README
git add README
git commit -m 'first commit'
git push origin master

と、最後のコマンドを入力したところでなにやら訳の分からないエラーが出て停止。困りました。
とりあえず何をやっているのかを少しずつ把握していくことにします。

まず最初の2行でコンフィグ。名前とメアドを決めてね、ということらしいので、適当に入力。
次、自分のXcodeプロジェクトがある場所に移動して、リポジトリ用のディレクトリを作って、
git initコマンドを実行してその場にリポジトリを作りました。その場に、と言うのがポイント。
これでgithub上とローカル上に2個の同じリポジトリが完成したことになります。
touchコマンドでREADMEファイルを作成。
READMEファイルがあると、github上でREADMEファイルの中身が表示されるみたいです。
Licenseとか表示するのに便利そうですね。
次のgit add READMEで作ったファイルをgit様の管理下におきました。
git addはファイルを管理下に置くときだけではなくて、コミットの前にも実行する必要がある?みたいです。
git add .(ピリオドを忘れずに)とするとカレントディレクトリ以下の全ての存在をgit様の管理下に置くのだハハハハーらしいです。便利ですね。
変更追加を管理下においたら、git commit -m 'first commit'でコミット。
どうやら-mは引数でコメントを入力するオプションみたいです。
-mオプションを指定しなければ、自動的にvimが立ち上がってコメントを求められます。
コメントなしのコミットは出来ません。

最後、問題のgit push origin master。一体全体何をしているのか分からなかったので、まず調査してみました。
http://github.com/guides/git-cheat-sheet
ここでgit pushの例を見てみると、リモートリポジトリoriginに対して、ブランチ名masterをコミットしているらしいです。
要するにここでは、github上に一番最初に用意したリポジトリに対して、既存のブランチmasterをコミットするということかな。
どうして自動的にgithubに対してpushしてくれるのかはわかりませんが、きっと最初の設定がそうなっているんでしょう。

では分かったところで、よく分からない理由でgit push出来ない問題を解決しましょう。まずは適当に検索・・・
git/github - TOBY SOFT wiki
ヽ( ・∀・)ノくまくまー(2008-06-02)
すると偉大な先人たちが既に答えを導いておられました。素晴らしい!
要するに、git pushの際にSSHの認証をしたいんだけど、
秘密鍵と公開鍵がないから認証できないよ、ということらしいです。

まずは鍵を作ります。以下、@ITからコピペ&一部改変。
@IT:sshでパスワードなしにログインするには
ssh-keygen -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/akisute/.ssh/id_rsa): ←[Enter]キー
Enter passphrase (empty for no passphrase): ←パスワードを入力
Enter same passphrase again: ←同じパスワードを入力
Your identification has been saved in /Users/akisute/.ssh/id_rsa.
Your public key has been saved in /Users/akisute/.ssh/id_rsa.pub.

できました。
次、githubに戻って、accountページから公開鍵を登録します。
less ~/.ssh/id_rsa.pub

この結果を何も考えずコピーして以下の画面に貼り付ける。


できました。改行とか入っていないかだけは注意。改行したらダメらしいです。

@ITの例では、やれchmodしろだの鍵を作ったらどうのこうのしろだのとか書いてますが、無視。
要するにサーバー側に公開鍵を置いて、クライアント側に秘密鍵を置いておけばいいようです。
今回はgithubがサーバー様なので、githubに公開鍵を渡せばいいってことですね。

次。以下のコピペ呪文を詠唱します。何も考えずに力を抜いて楽にして。
eval `ssh-agent`
Agent pid 12345
ssh-add ~/.ssh/id_rsa
Enter passphrase for /home/.ssh/id_rsa: ←パスワードを入力
Bad passphrase, try again for /home/.ssh/id_rsa: ←同じパスワードを入力
Identity added: /home/.ssh/id_rsa (/home/.ssh/id_rsa)

できた。凡人凡妖怪の私には何が起こったのかすらわかりません。
注意点はただひとつ、`ssh-agent`の`は'じゃなくて`です。Shift+@。

このコマンド入力によってgithubへの認証が可能になる!らしいです。早速試してみます。
git push origin master

おお!今度は成功!
調子に乗っていろいろ追加してみます。
git add Presense
git commit -m 'first commit'
git push origin master

これで俺もgitマスターだぜ!と調子に乗っていたら、


出ました、Mac OS Xの恥部(と勝手に自分が思っている)、.DS_Storeです。
これはリポジトリに含めたくないですね。どうすればいいんでしょう?
答えは簡単、.gitignoreというファイルを作って、お守り代わりにリポジトリのルートに配置すればいいらしいです。
build
.DS_Store
*.o
*.ob
*.pbxuser
*.tmproj
*.model*
*.mode*
*.build

このお守りを置けば、git add時にここに書いてあるパターンにマッチするファイルはaddされないらしいのですが、
すでにaddされてしまったものについては自分で消すしかありません。
git rm .DS_Store
git commit -m 'removed DS_Store'
git push origin master

まぁ、ざっとこんなもんよ、なんてね。

CVSやSubversionと比べると、ソースコードを公開するのが非常に楽でいい感じです。
ネットワーク環境が無くてもローカルのリポジトリに対してコミットできると言うのも地味に嬉しいところ。
過去の変更履歴を見たりブランチを切ったりするコマンドについては、おいおい学んでいこうと思います。

2010年2月7日日曜日

Mercurial の、 hg revert / hg rollback / hg backout の使い分け

以前からgitを使っていたのですが、最近は職場のバージョン管理システムがMercurial hg になっているので、もっぱらhgばかり使っています。ということで、いくつか覚えたhgネタ。

Mercurialやgitに限らず、いかなるバージョン管理システムを使用していても、人間が使う以上運用中にミスが発生することは避けられません。今回はMercurial使用中に間違ったコミットやプッシュを行ってしまった際の対処法を調べてみました。

参考文献はこちら。
4798021741入門Mercurial Linux/Windows対応
秀和システム 2009-01

by G-Tools


間違いを修正するためのコマンドは、大きく分けて以下の3つがあります。
  • hg revert
  • hg rollback
  • hg backout
またコマンドを用いて修正する意外にも、ローカル作業する際に作業用リポジトリを別に作って、問題に気づいたらリポジトリごと削除するという運用もありますが、今回は説明を省きます。


■hg revert
適用可能なのは「ローカルのリポジトリ上で、コミットをする前に問題に気づいたとき」。
作業中にちょっと間違った際など、ファイル単位でコミット前の状態(parentの状態)に戻すことができ大変便利です。コミットしてしまった場合は、次のhg rollbackを使用します。


■hg rollback
適用可能なのは「ローカルのリポジトリ上で、コミットをした直後に問題に気づいたとき」。
直前の1回分だけ、commitを取り消すことができます。マージ作業だろうがなんだろうが跡形もなく消してくれるため、後から見ても痕跡がなく綺麗です。操作も簡単で安全なため、これが使用可能なときはこちらを使用すると便利です。

一応、直前の1回だけならリモートの共有リポジトリへのpushも取り消すことができるのですが、これは後述の理由からお勧めしません。一度リモートにpushしてしまったら、次のhg backoutを使って取り消すことになります。


■hg backout
適用可能なのは「ローカルのリポジトリ上で、複数回コミットした後にに問題に気づいたとき」か、または「リモートの共有リポジトリにプッシュしてしまった後に問題に気づいたとき」。
どうしようもなくなってしまったときの最後の手段がこれ。指定したリビジョンのcommitの内容を完全に打ち消す新しいリビジョンを作ってくれます。あとは、手動でこの自動で作られたリビジョンを元のリビジョンにmergeしてcommitすることで、いかなる問題であろうとも無理矢理打ち消す事が可能になります。

もっぱら間違ってプッシュしてしまった際に使用します。
一応hg rollbackでも直前のpushを取り消すことが可能ですが、一度pushされてしまった内容はどこかの誰かのリポジトリにpullされてしまっている可能性があり、そうなると共有リポジトリだけをrollbackしても効果がありません。(このように、間違いも分散して広まってしまうのが分散バージョン管理の恐ろしいところ)

このようなときは、hg backoutで修正リビジョンを作りマージしたあと、再度共有リポジトリにpushすれば、他の誰かのリポジトリに問題が取り込まれてしまっていても次のpullで無事修正されます。

2010年5月30日日曜日

Core Data のパフォーマンスをちょっとだけ調べてみた

ちょっと仕事で触ってみて分かった範囲のことを書きます。断りがない限り、 iPhone 3GS で Wifi 接続環境下においてテストしました。

■キャッシュ無し vs キャッシュ有り

executeFetchRequest:error: メソッドを用いて、 Entityのプロパティで一件だけ絞り込んで返すようなクエリは大変遅いということが分かりました。Indexを付けて実行してもほとんど速くなりません。どうやらそもそもバックエンドに使っているSqliteが大変遅い、特にコネクションを生成したり破棄したりするのが遅い感じがするので、ループで一件ずつ取得するなどのときはたくさんのSQLが実行されないようにする必要があります。 objectWithID: メソッドは試していないのでちょっと不明です。

回避策として、アプリが起動したタイミングで当該エンティティの全オブジェクトをあらかじめ取ってきて、 NSMutableDictionary にでも突っ込んでおく。次回以降のフェッチはその NSMutableDictionary から行う、と言うようにすると凄く速くなりました。

実測値は以下の通り。
pre loading time というのが自前のNSDictionaryキャッシュの事前生成、parseResponseBodyというのがXMLの解析で、この中に大量のCore DataオブジェクトをDBから引っ張ってくる処理が含まれています。
//////////////////
プリキャッシュなし
//////////////////

2010-05-25 12:11:37.254  FetchAllModelA parseResponseBody time : -1.214856
2010-05-25 12:11:39.674  FetchAllModelB parseResponseBody time : -2.416063
2010-05-25 12:11:41.097  FetchAllModelC parseResponseBody time : -1.384185

//////////////////
プリキャッシュあり
//////////////////

2010-05-25 14:12:14.788  pre loading time : -0.039540
2010-05-25 14:12:17.518  FetchAllModelA parseResponseBody completed. time : -0.836754
2010-05-25 14:12:20.719  FetchAllModelB parseResponseBody completed. time : -1.312910
2010-05-25 14:12:19.169  FetchAllModelC parseResponseBody completed. time : -0.902832
ほんの0.03秒のプリキャッシュ処理のおかげで、XML解析が最大で1秒以上短縮できています。とにかくCore DataがSQLを飛ばさないように調整すると効果があるみたいです。


■DB書き込み速度

NSManagedObject の生成はメモリ上 (NSManagedObjectContext) で行われるためなかなか高速なのですが、それを save するのがとにかく iPhone 3G で顕著に遅く、 300件程度のデータを保存するのに5秒以上かかってアプリが正常終了できないという事態が発生しました。リレーション張りすぎたかなーと思います>< iPhone 3GS なら2秒3秒程度で間違いなく完了するので特に問題になっていません。対策としては暇なときに逐一DBにsaveするか、どうでもいいデータはiPhone 3Gでは保存しないとか、一時エンティティにするとか。

2008年11月26日水曜日

CS193P Cocoa Programming - 6日目おまけ、多角形をくるくる回せるようにしてみた

  • CS193P(http://www.stanford.edu/class/cs193p/cgi-bin/index.php)のチュートリアルで作っているHelloPolyプロジェクトを自分なりにアレンジしてみた
  • 自分なりにアレンジしてみた=ニコニコ動画だと駄作フラグ
  • UIViewはhiddenプロパティをYESにした瞬間に消えてしまうので、アニメーションでフェードアウトさせたいときは、まずアニメーションだけ実行>アニメーション終了時のデリゲータ(- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag)で実際にUIViewをhiddenにする
  • タッチ動作を取得するには、UIView(正確にはUIResponder)のtouchBeganメソッドやtouchMovedメソッドをオーバーライドする
  • デフォルトではマルチタッチ不可能(最初の1タッチのみ感知する)、マルチタッチしたければ設定を変えること
  • UITouch.tapCountでタップした数を取得できる。これを使ってダブルタップを検出できるが、連打すると3とか4とか2より大きい数が取得されてしまうので注意
  • CGPointはクラスではなくて構造体、頭にCGがつくCore Graphics系はすべて純粋C言語であるところに注意!
  • CGPointなどを作成するときはCGPointMake()関数を使う
  • CGPointやCGRectなどをObjective-Cのクラスとして扱いたいときはNSValueクラスを使う、たとえば[NSValue valueWithCGPoint:(CGPoint)point]など
  • CGRectGetMidX()関数とか地味に超便利
  • C言語の変数の有効範囲について:http://www.cis1.c.dendai.ac.jp/c_master/C_14.htm
    CGPointMake()で作ったCGPointなどは自動変数なので、別のメソッドに渡すときはポインタ渡しではなくてそのまま値で渡す

ただ単にカリキュラムにそって進めていくだけでは面白くないので、この辺りでちょっとチャレンジングなことをしてみることにしました


まずは新しくサブビューを追加。


ON OFFスイッチでビューを出したり消したり。出したり消したりするときはアニメーションします。


UISliderを使って、線の太さを変えてみましたよ。
UISliderのvalueプロパティはdouble型なので注意です。ずっとNSString型だと思ってました。
それから、タップした向きに多角形を回転できるようにしました。赤線は中心からタップした点への線分です。
タップしてドラッグするとスムー(?)ズに回転しますよ。


破線への切り替えもできるようにしました。
UISegmentedControlを使っています。このUISegmentedControl、取得できる値が選択されているセグメントのインデックス番号(selectedSegmentIndex)だけなのでちょっと厄介です。HTMLのラジオボタンみたいに好きな値をセグメントごとに持たせられればいいのに。


今回作成したアプリのプロジェクトファイルを公開してみました。
面白いことをやっている点は何一つないのですが、まぁ一応。
http://sites.google.com/site/akisutesama/files/HelloPoly-06b.zip?attredirects=0

今後はgithubとかで公開できるようにします。

2009年10月5日月曜日

Evernote APIためしてみた


API Keyくださいとお願いしたらすぐに発行してもらえたので、試しにEvernote APIを触ってみました。


■準備
http://blog.lilyx.net/2008/11/03/evernote-api/を見ると発行するときに手間がかかったとの記述がありましたが、私の場合は半日で発行されました。まぁ1年たってますし、状況が良くなったのかもしれませんね。申請時にアプリの詳細を事細かに書いたのが良かったのかもしれません。


■Hello Worldを動かす
まず最初にsandbox環境に自分用のアカウントを作成します。http://sandbox.evernote.com/login.jspにアクセスして、いつも通りアカウントを作成。忘れがちなので注意です。
アカウントが出来たら、ダウンロードしてきたSDKに最初っからサンプルスクリプトがついてきているので、こちらを実行するだけです。今回はPythonのサンプルスクリプトを試してみました。
cd sample/python
cp * ../../lib/python
cd ../../lib/python
python EDAMTest.py username password


■ENMLを試してみる
今回の最終目的は「はてブみたいにURLだけ渡したら勝手にEvernoteに取り込んでくれる」ことなので、まずはどのぐらい気軽にHTMLを取り込めるかを調べてみました。Evernoteの本文はENMLというHTMLに似たマークアップ言語で記述されています。
http://www.evernote.com/about/developer/api/evernote-api.htm#_Toc200272588
こちらの記載を読んでみたところ、かなーーーーーり面倒くさいということが判明。htmlタグやbodyタグが含められないのはまだいいとして、id属性もclass属性も許可されません。さらに困ったことに・・・

pタグ閉じてないからダメー。


brタグ閉じてないからダメー。

さすがXML、厳しい。どうやらHTMLとは全く互換性がないと考えた方が良さそうです。
HTMLをENMLに変換するライブラリとかないかなー?

ENMLは死ぬほど面倒ですが、それ以外のところは今のところ簡単です。

2009年12月27日日曜日

OmniFocus を軽く快適に使えるようにメンテする技

普段からGTD用ツールとしてOmniFocusを便利に活用しております。iPhoneと3G経由でいつでも同期できたり、タスクレビュー機能があったり、何でもできて凄く助かるのですが、このOmniFocusには弱点がありまして・・・

そう

重すぎ

定期的にデータベースをメンテしてあげないととんでもない重さになってしまいます。
具体的には、サーバーと同期しようとしたiPhoneが10分間固まって帰ってこなくなったり
mobileme上に置いてあるデータベースファイルを開こうとしてFinderが固まって帰ってこなくなったり
とにかく、相当な破壊力を持つ代物です。その上公式サイトにも正しいメンテナンスの仕方が書いてありません(一番大事なところが書いてない)。そこでOmniFocusユーザーの方必見(?)、OmniFocusデータベースのメンテナンス方法をまとめてみました。

参考にしたページは以下の通り。
http://forums.omnigroup.com/showthread.php?t=14631&highlight=compact
http://forums.omnigroup.com/showpost.php?p=70253&postcount=3


■予備知識:OmniFocusのデータベースの構造
OmniFocus.ofocusというファイルがOmniFocusのデータベースファイルです。その正体はMac Bundle(わかりやすく言えば単なるディレクトリ)で、中に以下のような形式でZIPファイルが入っています。
0000000000-日付.zip
ランダムな文字列-日付.zip
ランダムな文字列-日付.zip
ランダムな文字列-日付.zip
以下たくさん・・・
このZIPファイルを解凍するとcontent.xmlというXMLファイルになります。このcontent.xmlの集合がOmniFocusデータベースの基本構造になります。
content.xmlには「一番最初のデータベースの状態」か「前回保存時からのデータベース操作内容」が記載されています。要するに先ほどのZIPファイルの集合には以下のようにデータが格納されています。
このデータベースにはタスクが10個入ってるよ - 2009/11/10.zip
タスクBを完了したよ - 2009/11/10.zip
タスクXYZを新規作成したよ - 2009/11/10.zip
タスクDを削除したよ、あとコンテキストXXXを作成したよ - 2009/11/12.zip
これらのZIPファイルが自動的に削除されることはありません。要するにこのままではデータベースの領域が増える一方なわけです。これが重さの原因です。


■公式で推奨されている方法:定期的に"Move Old Data to Archive"を実行する
実はこれ全然役に立ちません。
なぜか?
このコマンドを実行すると何が行われるかって、先ほどのデータベースに
タスクAとBとCはArchiveしたからもう二度と表示しなくていいよ.zip
これが追加されるだけなんです。

・・・え、かえってデータベースのサイズ増えてね?


■本当に有効な方法:定期的に"Compact DataBase"を実行する
で、こっちが本命です。このコマンドを実行すると、操作内容を格納しているZIPファイルをすべて削除して、一つの新しい「現在の状態を表すZIPファイル」だけが存在するデータベースを作り直してくれます。効果は劇的で、たとえば私の場合は11.5MBあったデータベースファイルのサイズが25KBになりました。
問題はこのコマンド、実は「同期設定を行っている最中は実行できません」。この辺マニュアルや公式FAQに書いてなくて困りました。

ということで、いったん環境設定からiPhone等との同期を「OFF」にしてから再度"Compact DataBase"を実行すると良いです。


■対策3:余計なバックアップを作成しない
初期設定ではクライアントが終了するたびにデータベースのバックアップを取るようになっていますが、これを放置しているととんでもない量のバックアップファイルが作成されてしまうので、設定を適当に変更しておくと良いと思います。

2010年5月3日月曜日

Byline 3 の新機能まとめ



ちょっとした宣伝ポストになりますが、私が日本語翻訳を手がけてます iPhone の RSS リーダーアプリ Byline のバージョン3が近日リリースされます。すでにリリース候補ビルドが完成しており、特に何の問題もなければ来週月曜日 (5/10) にリリースされる予定です。既存の Byline 2 ユーザーの方はそのまま無料でアップデートすることが出来ます。

バージョン2にくらべて高速で動作し、文字エンコードの取り扱い方法を変更したため以前よりクラッシュしない・・・はずです。少なくとも最新バージョンになってからは私のクライアントでは一度もクラッシュしておらず、安定性は上がっていると思います。

Byline 2 からの変更点とか新機能とかを以下にまとめてみました。


■Google Reader との同期速度向上

Google Reader と同期する際のスピードがさらに高速化しました。特にすべてのフィードのインデックスを生成する速度が劇的に改善されています。具体的にどれぐらい早くなったのか、私の iPhone 3GS 上で実際に測定してみました。

Byline 3


Byline 2


使用したバージョンアイテム件数同期にかかった時間アイテム/秒
Byline 2.5.6511件340.43秒1.50
Byline 3.0f1275件135.99秒2.02

ごらんのように、およそ3割程度速度が向上していることがわかります。アイテム一件一件のキャッシュ速度はそれほど変わりませんが、最初のインデックス取得が劇的に高速化している分だけ全体にかかる時間が短縮されているようです。


■アイテムのキャッシュ機能強化

目玉であるWebページのキャッシュ機能も強化されています。今回のバージョンから、 途中で省略されているアイテムのみをキャッシュする ことが可能になりました。途中で省略されているアイテムというのは、たとえば このような ニュース本文全体を乗せない RSS フィードの記事のことです。 Byline はこのような省略されている記事のみを判定して自動的にキャッシュしてくれます。


■インターフェース変更

はい、 Byline のバージョンアップといえば毎度おなじみのUI変更とアイコンの変更です>< 2までの操作に慣れた方(自分含む)には申し訳ありませんが、また慣れてください・・・その代わり前のインターフェースより優れているところも結構あります。



メイン画面はこんな感じです。フォルダの中の個別のフィードごとに記事を読めるようになりました。



記事一覧。リストの一番下にあった「すべて既読にする」がなくなり、その代わり右上に「編集」ボタンがついてそちらから一度に既読に出来るようになっています。



記事詳細も変わっています。画面左下にあったキャッシュを見るボタンが右上に移動しました。



こんな具合に記事詳細を左右にスワイプすることでつぎつぎと記事をめくっていくことが出来ます。

■Twitter / Instapaper / Read it Later との連携



最近の RSS リーダーではおなじみの外部連携が強化されています。 Twitter / Instapaper / Read it Later と連携することが出来ます。写真は Twitter への投稿画面。結構良くできています。リンクが長すぎる場合には自動的に短くしてくれたりします。


■まとめ

最近は Byline よりも多機能でより多くの外部サービスと連携できる RSS リーダーが登場してきていますが、今回のアップデートで外部連携が強化されたり個別のフィードを読めるようになったりと、それらの最新アプリと対抗できるだけの機能がついてきたかなと思います。目玉の Web ページキャッシュ機能もさらに強化されています。あとは安定動作してくれればまず負けないと思うのですが・・・こればっかりはリリースして皆さんに使っていただかないとわかりません >< とはいえ私の環境では500件以上のフィードを一度に読み込んでも安定しており、前バージョンよりさらに高速化しています。ご期待ください!

2015年11月29日日曜日

iOS でヒラギノフォントが明示的に指定された時に描画サイズの計算が正しくならない問題を修正する

タイトルからして出落ち感が少々ありますが・・・

iOSのフォントサイズ計算には長年修正されないバグというか仕様がございまして、「ヒラギノフォント(ヒラギノ角ゴシック、ヒラギノ明朝等)」が明示的に[UIFont fontWithName:size:]で指定されたとき、そのフォントを使ったUILabelやUITextViewなどの描画サイズの計算が正しくならない問題があります。iOS 6からiOS 9.1現在に至るまでずっとなので今後も直ることはないと思います。

詳細についてはこちらの記事が詳しいです。
http://qiita.com/yusuga/items/2be8c55ca561bba44702
一番下のリンク先の記事でも同様の問題が訴えられていまして、それぞれ対策が記載されていますので合わせてご参照ください。

でまぁ、対処法としてはいくつかあります。
  • UIControl.contentVerticalAlignmentFillにする
  • sizeThatFitsおよびintrinsicContentSizeの実装を差し替える
  • ヒラギノフォントを明示的に指定するのをやめる。システムフォントを使えばいいじゃない\(^o^)/
  • システムが提供するヒラギノフォントを使うのをやめて、モリサワさんなどからまともなヒラギノフォントを買ってきてそちらを使う

今回は私が実際に使っているsizeThatFitsの実装を差し替える方法を紹介したいと思います。といってもまぁ結構簡単です。以下の様な実装になっています。

見ての通り、フォントファミリーがヒラギノ系であったら、
  • widthはceilする
  • heightはもともとの高さにfontのdescenderをfabsしてから足したうえでceilする
だけです。簡単でしょう? Apple爆発しろ

2009年7月19日日曜日

CGGradientを用いてUITableViewCellを描画し、テーブルをカッコよく見せる方法

デフォルトのUITableViewCellの背景が白くてのっぺりでいまいち味気ないと思い、背景にグラデーションを付けてかっこよく見せる方法を調べてみました。単純に別途用意した背景画像をbackgroundViewに表示してもよいのですが、Cocoa Touchの2Dグラフィックスライブラリにはグラデーションを描画するためのCGGradientというクラスが最初から用意されています。さっそく私もパクってインスパイアされてやってみました。

参考にしたページはこちら。
http://developer.apple.com/documentation/graphicsimaging/conceptual/drawingwithquartz2d/dq_shadings/dq_shadings.html#//apple_ref/doc/uid/TP30001066-CH207-TPXREF101


■どこで描画するか
  • UITableViewCellのdrawRectで直接描画。
    少しでも高速に描画したい場合にはこの方法
  • 新規にUIViewを継承した背景用Viewを作成しセルのbackgroundViewに設定。そのViewのdrawRectで描画
    複数のUITableViewCellで同じ背景を適用したいときはこの方法が便利

■まずは実際に描画してみる
drawRectの中でCGContextを作成して、続いてCGGradientを生成。CGGradientを作るためにはCGColorSpaceとか色を指定する配列とかが必要になるのでそれらも生成。最後に生成したCGGradientオブジェクトを描画するという流れになります。ということでソースを見てみましょう。
- (void)drawRect:(CGRect)rect
{
// CGContextを用意する
CGContextRef context = UIGraphicsGetCurrentContext();

// CGGradientを生成する
// 生成するためにCGColorSpaceと色データの配列が必要になるので
// 適当に用意する
CGGradientRef gradient;
CGColorSpaceRef colorSpace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 1.0, // Start color
0.79, 0.79, 0.79, 1.0 }; // End color
colorSpace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColorComponents(colorSpace, components,
locations, num_locations);

// 生成したCGGradientを描画する
// 始点と終点を指定してやると、その間に直線的なグラデーションが描画される。
// (横幅は無限大)
CGPoint startPoint = CGPointMake(self.frame.size.width/2, 0.0);
CGPoint endPoint = CGPointMake(self.frame.size.width/2, self.frame.size.height);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);

// GradientとColorSpaceを開放する
CGColorSpaceRelease(colorSpace);
CGGradientRelease(gradient);
}
このコードを実行すると・・・


はい!もうグラデーションができました。嘘みたいに簡単です。

2010/05/24追記:注意点として、CGGradientとCGColorRefのオブジェクトは手動でリリースしないとメモリリークが発生します!


■UIColorを元にグラデーションを作る
先ほどの例では配列にRGBA要素を渡してグラデーションを作りましたが、UIColorが使えるともっとお手軽で、しかもRGBAだけではなくてHSBAで色が指定できて何かと便利です。ということで、次はUIColorからグラデーションを作ってみます。

UIColorから直接CGGradientを作ることは出来ないので、途中でCGColorとCFArrayRefを作り、それを元にCGGradientを生成してみます。コードはこちら。
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *currentColor = [UIColor colorWithHue:currentBackgroundColorHSBA[0]
saturation:currentBackgroundColorHSBA[1]
brightness:currentBackgroundColorHSBA[2]
alpha:currentBackgroundColorHSBA[3]];
// gradient background
CGGradientRef grad;
CGColorSpaceRef colorSpace;

// UIColorからCGColorを取り出すのはとっても簡単
CGColorRef currentColorRef = [currentColor CGColor];
CGColorRef voidColorRef = [[UIColor colorWithHue:0.0
saturation:0.0
brightness:0.17
alpha:1.0] CGColor];
CGColorRef colorArray[2] = {voidColorRef, currentColorRef};
CGFloat locations[2] = { 0.0, 1.0 };

// CFArrayRefを作る。
// もっと簡単に作りたければ、NSArrayを作ってからCFArrayRefにキャストするだけでもよい。(未確認)
CFArrayRef colors = CFArrayCreate(kCFAllocatorDefault, (const void **)colorArray, 2, &kCFTypeArrayCallBacks);

colorSpace = CGColorSpaceCreateDeviceRGB();
// Gradientを生成する
grad = CGGradientCreateWithColors(colorSpace, colors, locations);

// 描画する
CGFloat progress = currentTime / allottedTime;
CGFloat height = self.frame.size.height + HEIGHT_GRADIENT;
CGPoint startPoint = CGPointMake(self.frame.size.width/2, progress * height - HEIGHT_GRADIENT);
CGPoint endPoint = CGPointMake(self.frame.size.width/2, progress * height);
CGContextDrawLinearGradient(context,
grad,
startPoint,
endPoint,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
// GradientとColorSpaceを開放する
CGColorSpaceRelease(colorSpace);
CGGradientRelease(grad);
}
CGContextDrawLinearGradientの第4引数にkCGGradientDrawsBeforeStartLocationフラグとkCGGradientDrawsAfterEndLocationフラグを設定しています。これらのフラグを指定すると、startPoint以前、およびendPoint以降をグラデーションの開始色と終了色でべた塗りしてくれます。実行結果は以下の通り。


こんな感じで、画面中央から下が全部べた塗りになっているのが分かると思います。

他にも、CGGradientを作る際にlocationsを増やせば2色だけではなくて多色のグラデーションを作成することが出来たりします。このあたり、Appleが公開しているドキュメントだけでも相当詳しく紹介されているので、そちらを見ればほぼ間違いないかと。


■サンプルソース
今回のサンプルはgithubですべてソースを公開して居ますので、より詳しく学びたい方はそちらも併せてご参照ください。
http://github.com/akisute/YourTurn/tree/master
これとかこれを見るとよいかと思います。

2010年2月15日月曜日

Flash の SWC は使ってはいけない

タイトルからしてなかなかひどいですが、調査結果もなかなかひどいです。

■結論:fl.controls(最初からFlashに付属しているコンポーネント)を含むSWCを作ると正しく読み込まれない
まずはこちらに検証用のプロジェクトを用意しましたので、ご覧になってみてください。
package {
import flash.display.MovieClip;

public dynamic class Abesi extends MovieClip
{
public function Abesi() {
trace("abesi");
trace(this.button, this.textArea);
}
}
}
package {
import flash.display.MovieClip;

public dynamic class Hidebu extends MovieClip
{
public function Hidebu() {
trace("hidebu");
trace(this.button, this.textArea);
}
}
}
とまぁ、なんの変哲もないタダのFlashコードをswcとして出力し、TestMainの中でnewして出力しているだけなのですが、なんとswcに書き出すタイミングに応じて確実にエラーになって落ちるというひどい問題があるようなのです。


■検証結果
以下のスプレッドシートにまとめてみました。
http://spreadsheets.google.com/ccc?key=0AoXhhCSOuqOtdE5rNy1wc2N2Z2JuV1NPUFBjdlRNeHc



たぶん私の書き出し方に何か問題があるのだろうとは思っているのですが、何処を見てもそれに関する情報が見あたらなかったので解決できず、結局swcの使用はあきらめることにしました><

2009年1月19日月曜日

英語の発音を練習するアプリ「iはつおん」をAppBank様から頂戴いたしました

  • こちらのキャンペーンに応募→http://www.appbank.net/2009/01/13/iphone-application/6376.php
  • 見事当選!(AppBank様ありがとうございます)
  • 数ある英語の発音23種類を、お手本を聞きながら実際に自分で発音して練習できるアプリ
  • 「録音」機能がついていて、自分の発音を録音し、お手本と聞き比べられるのがすごくよい
  • 一音単位だけではなくて、単語単位での発音練習も可能
  • バージョン1.1現在、'U'の発音方法の説明がバグっていて、'Z'のものになっている
  • 単語単位だけではなくて、実際の文章単位での発音練習もできるようにして欲しい
毎日欠かさずチェックしている、iPhoneアプリレビューサイトのAppBank様。そのAppBank様の「英語発音練習iPhoneアプリ「iはつおん」無料プレゼントキャンペーンのお知らせ。」を見て、まさにこれぞチャンス!と思い速攻で申し込んだところ、なんと当選してしまいました。

当選してアプリとiTunes Store (US)のアカウントという飛び切りのお年玉をいただくことになりました。本当にありがとうございます。
受かったからには気合いを入れてレビューを書かねば!

まず最初に1点だけバグが見つかったので、その報告だけ。

なるほど、Aの発音はお腹から「ズッ」っと発声すればいいのかー・・・って違うわ!


【2009/01/22】
iはつおんの機能のレビューにつきましてはこちらを参考にしていただくとして、
ここでは、私の英語学習体験とiはつおんによる英語学習を照らし合わせたレビューをしてみます。

まずは私自身の英語学習歴から簡単にご説明。
  • 12歳で塾(公文式だったかな)に通い始めて、そこで英語を覚える。たいしたことはしていないけれども、「英語は日本語じゃないから日本語とは違うようにしゃべろう」と決心したことだけは覚えている。おかげで変な日本人発音だけはしないようになりました。
  • 13歳で中学に入る。中1の英語のセンセイがとてもよい女性の先生だったためめきめき覚える。ちなみに2年3年のころのセンセイは大変生徒から不評であった。
  • 14歳でウルティマオンラインを始める。当時のUOはAsukaとかYamatoとか日本人向けサーバーができはじめてはいたが、まだNPCは平気で英語をしゃべったりしていた。そのため必然的に英語を取得。"vender buy bank guards i ban thee"などといった日常ではあまり使わないであろう単語を中心に読み書きできるようになりました。
  • 15歳ぐらいでInfantryという神ゲーに出会う。こちら英語版しかなく、音声も画面メッセージもすべて英語。味方も敵も外人。必然的に英語で"n00b lamers you can't even stop my single assault =P"などとののしりあうことができるようになりました。
  • 16歳ぐらいでDiablo2という宇宙最強のRPGに出会う。こちら英語版しか(ry)・・・なのですが、Diablo2はInfantryと違ってRPGであったため、キャラが英語でストーリーをしゃべってくれます。おかげである程度英語が聴けるようになりました。
  • 17歳ぐらいで世界遺産級のRTSゲームことWarcraft3に出会う。こちら今でもプレイしていますが、これまたキャラが英語でしゃべるので(ry)。またこのゲーム、キャラの台詞がいちいちカッコイイ(例:これとか)ので、ついつい口ずさんでしまう。端から見るとただのキモいオタクでしたが、この実際に声に出したところが後々の英語力として残ったのではないかと思ってます。
  • その他World of Warcraftなどプレイして今に至る。
ごらんの通り、英語は完全にPCゲームから覚えています。
まともに発音記号の読み方がわかるようになったのは大学に入ってからだったり、英単語帳とか大学受験の年になって慌てて買ったというような具合ですので、
いわゆる「英語学習」というような型にはめる勉強法をほとんどしていないのです。

そんな私がiはつおん、早速試してみました。



私が見つけたiはつおんの最大のメリットは、「正しい発声方法」の「イラスト」と「解説」と「お手本発声」と「自分へのフィードバック(録音)」の4点セットが1画面で提供されていることでした。
普通の英語教材って、
  • CDだったら音はありますが発声方法がない
  • 本だと発声方法はあるけど音がない
  • そもそも自分の発音が正しいのか聴くことができない、テープレコーダーとか大げさで面倒すぎる
などなど、この4点を同時にまとめてカバーしてくれる教材なんて皆無だったのです。
ところがiはつおんなら、発声方法を見ながら、正しい音を同時に聞きつつ、さらに自分で同時に発声して練習もでき、再生ボタンを押せばすぐにフィードバックが返ってきます。
この4つの機能が1画面にまとまっている!というのは他にないアドバンテージですね。



逆に、問題点として以下の2点が浮かびました。
  • 文章単位での練習ができない。せいぜい単語単位までの音の流れしかつかめず、単語同士の接続の流れ(極端に言えばan appleがあんあっぷるじゃなくてあなぽーになる)が身につかないのではないか
  • やればやるほど目に見えて増える項目がないので達成感がない、楽しくない、したがって続かないのではないか
私の経験と照らし合わせると、
  • 聴く内容は文章、またはストーリー
  • 楽しいので毎日毎日朝から晩までやっていた
という要素があり、これが学習効果としてすごくよかったのではないのかと今になって思うからです。

また、例となる単語の数がそれほど多くない(1発音記号につき12個しかない)ので、やはり継続性に欠けると思います。
現在の私は、もっぱらiはつおんを発音方法のリファレンスにしながら、YouTubeの英語の動画の台詞をまねして発音してみたりという勉強方法にシフトしており、iはつおん単品での学習は行っていません。

現在のままでも発音方法のリファレンスとして使う分には100点満点クラスのアプリだと思いますが、せっかくならやはりよい学習コンテンツと組み合わせていただきたい!と感じてます。

そこでもしiはつおん2を出す予定がおありなら、是非ストーリー形式で文章を読み上げていくような形にして欲しいです。
英単語帳にDUOというシリーズがありますが、まさにこのシリーズをiはつおんのシステムでやれば、これまでの英語学習教材にはない学習体験が得られるかも!と考えているのですが、いかがでしょう?

RakuRaku Technologies さん、どうですか!?次はDUOでやっていただけませんか!?

2015年4月10日金曜日

Apple Watch の実機を触ってわかった、アプリ開発者が抑えておくべきポイント


本日からついにApple Watchの実機がお目見えとなりました。私も早速Apple Storeに行って試着・試用してきたのですが、予想以上にアプリ開発に影響がありそうな点が多数見つかりましたので、思うところをブログ記事にまとめて公開しようかと思います。

■小さい、とにかく小さい

Apple Watchの実機を身につけてまず最初に感じるのがその圧倒的な小ささです。この小ささというのは

  • これまでのAndroid Wearデバイスのどれと比べても感じる相対的な小ささ
  • Apple Watch上で表示されているUIを見て感じる絶対的な小ささ

の2つの要素から感じられます。

試しに私が身につけているAndroid WearデバイスとApple Watch Standard 42mmを並べて写真をとってみたのですが、見ての通り42mmモデルですら表示領域がずいぶんと小さいのがわかります。

その上Apple WatchのUIは全体的にAndroid WearのUIと比べて密度が高い用に感じられます。こちらのブログに具体的な例があるのでぜひ参照していただきたいのですが、見ての通り同じアプリでもApple Watchのほうが詰まったUIになっています。ただでさえデバイス自体が小さめな上に密度の高いUI、具体的にはアプリ内で常時上にナビゲーション領域が表示されたりする、ということでなおさら小ささが際立っているわけです。

小ささをより体感するために、iPhone 6の画面でApple Watch 38mmの画面サイズを表現してみました。

アイコンが一つにラベルが一つプラスアルファ程度がせいぜいの大きさしか無い、というのがよく分かるかと思います。

したがって繰り返し繰り返し随所で述べられていると思いますが、画面上に表示する要素は徹底的に少なくする必要があります。私も十分に少ない要素だけを画面に表示するように心がけていたつもりでしたが、実際にデバイスに触れたあとに見返すとまだまだ要素が多すぎるぐらいです。少なすぎるのではと心配になるぐらいまで減らしてちょうどいいのではないでしょうか。

Appleの標準のアプリなどでかなり高密度なUIを採用しているものもありますが、そこは真似しないほうが良いと考えています。具体的には標準のマップアプリなどは38mmモデルの上では細かすぎて地図を読み取るのが極めて困難でした。

■Glanceこそがすべて

Apple Watchのインターフェースのナビゲーションは以下の図のようになっています。

基本は時計フェイスが表示されていて、そこから竜頭を押すとHomeに遷移してアプリを選択して起動することができます。時計フェイスを下にスワイプすると上からNotification Centerが表示され、上にスワイプすると下からGlanceが表示されます。Glanceは左右スワイプで次々に閲覧することができます。感覚的にはGlanceはiOSデバイスにおけるWidgetのようなもので、常時Widgetが時計フェイスの下に並んでいるようなイメージをするとわかりやすかったです。

このインターフェースの中でアプリができることで、最も重要になってくるのがGlanceです。操作してみてわかったのですが、Home画面からアプリを起動するのはただでさえ小さいWatchの画面上に無数の小さいアイコンが並ぶため困難苦痛を極めます。したがって必然的にアプリの状態を確認したりアプリを起動するのはGlanceを使うのが最も楽でスピーディで良いということになります。GlanceこそがApple Watchアプリにおけるすべての窓口と言えそうです。ここをどれだけ便利に使いやすく見せるかによってアプリの価値が変わってくるかもしれません。

■ネイティブアプリは速いが転送は遅い?

気になるApple Watchの動作速度ですが、まず通常の用途ですとかなりサクサクと動作しました。時計フェイスから通知センターに遷移したりGlanceを見たり、Glance間を切り替えたりするぶんには素晴らしい応答速度で、手元のAndroid Wearデバイスよりも機敏に感じました。

これがアプリとなってくるとだんだんと遅さが感じられる場面が出てきます。気になった点としては、

  • いくつかのGlanceについてロードが終わらない、ないしロードが遅すぎる。マップ・天気・株価が該当。
  • GlanceまたはHomeからのアプリの起動が遅いときがある。フィットネスで該当。
  • フィットネスで「開始」ボタンを選択してから実際に開始するまでに明らかに感じられる遅れがあった。
  • マップアプリについてはロードが遅く、地図が表示されるのも遅い。

これらから推測するに、おそらくネイティブでアプリが動作している部分に関しては素晴らしいパフォーマンスが得られているものの、本体側のiPhoneからデータを転送している箇所に関しては顕著にパフォーマンスが落ちているのではないか思われます。

今回体験する事ができた実機にはサードパーティ製のアプリが入っていなかったので、我々開発者が作ったアプリに関してどの程度のパフォーマンスが得られるのかは全く不明ですが、この調子ではあまり良い結果が得られないかもしれません。今後のSDKの拡張でApple Watchネイティブのアプリが作れるようになるまでは、本体からデータを転送する頻度および転送量を少しでも削減できるようなつくりを目指すしかなさそうです。

■妄想とか将来の話

その他現状はサードパーティ開発者からは使えないのですが、将来的に面白くなりそうだと思った点を挙げます。

まず竜頭コントロールについてですが、現状竜頭コントロール入力をアプリ側から取得することができないのはみなさんご存知のとおりです。触った感じ竜頭自体は非常に良く出来ていたのですが、画面のどの箇所が竜頭でコントロールできるのか出来ないのかがいまいちよくわからないという問題があるように思えます。最も顕著な例は時計フェイスのカスタマイズUIで、これはカスタマイズする箇所をタップしてから竜頭で項目を選択するという仕組みになっているのですが、直感的に非常にわかりづらかったです。きちんと考えて統一的に使われていれば便利かもしれませんが、画面のタッチとの併用がほぼ必須なため竜頭だけでコントロールできなかったりなど、課題が山積みのように感じます。

逆にForce Touch(強く力を込めて押しこむようにタップする)機能ですが、これは非常に優れているように感じました。ダブルタップと特性は似ていますが、精密動作を必要とせずTapticエンジンによるフィードバックによって入力成功が伝わる点を考えてもダブルタップより圧倒的に優れている入力方式と言えます。現状Force Touchは自由に使うことができずメニューの表示用途に限定されていますが、これはまずForce Touchという操作の存在を確実にユーザーに理解してもらうという意味で良いと思います。この操作が広まればゆくゆくはiPhoneやiPadにもForce Touch搭載されることが確実でしょうし、アプリは積極的にForce Touchを取り入れていくべきと思いました。

将来的にForce TouchがiPhoneに導入されるとなると、iOSのAndroidに対する現在の弱点であるメニューボタンの不在を補う重要な役割になってくるかもしれませんね。さらにゲームでも大変有効に使える入力方式に間違いありません!夢が広がります。

Force Touchといえばその対となるTapticエンジンも非常に素晴らしかったです。Force Touch時のフィードバック、通知時のフィードバック、友人へのハートビートの送信、すべてで全く異なる触覚が伝わってくるのがまさに見事でした。まったく画面を見なくても触覚の違いだけで何が起こっているのかを判別できるほどです。現状Tapticエンジンを自由に触ることはできませんが、もし開放されたらTapticからのフィードバックだけで画面を全く見ないでも十分に使えるアプリが作れるかもしれません。全くユーザーを煩わせることない究極のUIになりうるかもしれませんね。

最後にGlanceについてちょっと触れます。基本的に現在サードパーティのアプリがGlanceでできるのは情報を表示するだけで、Glanceがタップされた時の挙動もWatchアプリが起動するだけに固定されてしまっています。

ところがAppleのネイティブアプリである心拍数Glanceについては、なんとGlanceが表示された瞬間に心拍数が自動的に計測開始され、さらにボタンをタップするとダイアログが表示されるというつくりになっていました。すなわち機能が開放されていないだけでリッチなGlanceを作ることも可能なようです。先にGlanceこそがすべてだと述べましたが、このリッチなGlanceを作る機能が開放されたらApple Watchのサードパーティアプリの可能性は更に広がると思います。

2010年12月29日水曜日

アプリのビルド時に CSSMERR_TP_NOT_TRUSTED エラーが発生したときの対処法

http://d.hatena.ne.jp/drill256/20090820/1250752178
http://discussions.apple.com/thread.jspa?threadID=1630090

このエラーは、以下の証明書がすべて存在しないか、または Keychain Access 内でのステータスが、「この証明書は信頼されています。」ではないときに発生するようです。
  • Apple Worldwide Developer Relations Certification Authority
  • iPhone Developer または iPhone Distribution


上記の画像のように、「この証明書は信頼されています。」と緑色のチェックマーク付きで表示されている必要があります。そうでない場合は何らかの問題があります。

対処法は、
  1. まず何はなくともこれらの証明書がすべて存在するか確認する。 Apple Worldwide Developer Relations Certification Authority を忘れているケースが良くあります。
  2. 「この証明書は信頼されています。」になっていない場合には、証明書を選択して、「情報を見る」 -> 「信頼」 -> 「システムデフォルトを使う」 を選択する。「常に信頼する」ではダメです、エラーになります。
証明書をどのキーチェイン項目に入れていても問題はなさそうです。私の場合は WWDR をシステムに、 iPhone Developer をログインに入れていますが正常に動作しています。

2016年11月14日月曜日

UNNotificationAttachment.init(identifier: String, url: URL, options: [AnyHashable : Any]? = nil)に拡張子のないURLを渡す際の注意

iOS 10で導入されたUserNotifications.frameworkは大変便利ですが、落とし穴が早速幾つか見つかっておりますのでご紹介いたします。
参考: iOS 10で画像つきのNotificationを配信する - Qiita

表題にありますUNNotificationAttachment.init(identifier: String, url: URL, options: [AnyHashable : Any]? = nil)ですが、第二引数のurlに画像や動画などのメディアのURLを渡してUNNotificationContentに付与すると言う使い方をするのが一般的かと思います。

このときどうやらUserNotifications.frameworkはurlのpathExtension、要するに拡張子の情報を利用してメディアの種類を判別しているようで、以下のような状況が発生すると実機でのみUNNotificationAttachment.initの実行が失敗してnilが返却されます。

  • 第二引数のurlに拡張子が付与されておらず、かつoptionsに[UNNotificationAttachmentOptionsTypeHintKey: "<当該URLのメディアの適切なMIME Typeを表す文字列>"]が指定されていない場合。
  • または間違った拡張子やMIME Typeが指定されている場合。

なおシミュレータでは問題ありません。いきなり実機に持っていって困るということが有るかと思いますので十分ご注意ください。

対処法は2つあります。

  1. URLに適切な拡張子を付与する
  2. optionsに[UNNotificationAttachmentOptionsTypeHintKey: "<当該URLのメディアの適切なMIME Typeを表す文字列>"]を付与する

何れにせよURLが指し示すメディアの種類が正確にわかっていないと問題になるため、その点は注意が必要です。

2011年11月18日金曜日

iOS 5の日本語キーボードの高さに対応する (iOS 3, 4, 5全対応)

iOS 5より日本語キーボードの高さが変わっているので、今まで決め打ちで高さ216pxとかやってレイアウトしていたビューが軒並み使えなくなってしまいました。今後はキーボードが出たり引っ込んだり種類が切り替わったりのタイミングできちんとキーボードの大きさを調べて適切にビューをレイアウトしてやる必要があります。ということでその対応をしたのでメモ。

前提条件として、以下の要件を満たすように作りました。
  • iOS 3, 4, 5全てで正常に動作すること。iOS 3.0でも動作しなければならない。
  • キーボードのframeを適切に取得できること
  • キーボードが出てくるタイミング、消えるタイミング、キーボードの種類が変わるタイミング、全て取れること

■まずはログを見てみる
キーボードの動作のタイミング、およびキーボードのframeは、NSNotificationを使って取得することができます。使用するNotification名はUIWindowのドキュメントに以下のように定義されています。
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIWindow_Class/UIWindowClassReference/UIWindowClassReference.html
// Available from iOS 2
UIKeyboardWillShowNotification
UIKeyboardDidShowNotification
UIKeyboardWillHideNotification
UIKeyboardDidHideNotification
// Available from iOS 5
UIKeyboardWillChangeFrameNotification
UIKeyboardDidChangeFrameNotification
で、これらのNotification名でNSNotificationCenterにobserverを追加すると、通知が飛んできます。飛んできたNSNotificationオブジェクトのuserInfoプロパティに特定のキーでキーボードのframeが格納されているというしくみです。使えるキーは以下のとおり。
// Available from iOS 3.2
UIKeyboardFrameBeginUserInfoKey
UIKeyboardFrameEndUserInfoKey
// Available from iOS 3.0
UIKeyboardAnimationCurveUserInfoKey
UIKeyboardAnimationDurationUserInfoKey
// Available from iOS 2.0 ~ 3.2 (Deprecated in newer versions)
UIKeyboardCenterBeginUserInfoKey
UIKeyboardCenterEndUserInfoKey
UIKeyboardBoundsUserInfoKey
iOS 3, 4, 5すべてできちんと動作しなければならないので、これらをふまえて、以下のように実装します。
  • Notification名にはUIKeyboardWillShowNotification, UIKeyboardDidShowNotification, UIKeyboardWillHideNotification, UIKeyboardDidHideNotificationを使う。
  • userInfoのキーには、iOS 3.0/3.1のみUIKeyboardBoundsUserInfoKeyを使い、それ以外のバージョンではUIKeyboardFrameEndUserInfoKeyを使う。

■実装してみる
ということでまずはサンプルアプリを作って動かしてみて、実際に動作を見てみることにしました。大体こんな感じのコードです。
- (void)viewWillAppear:(BOOL)animated
{
  // Notification observerを追加する
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onUIKeyboardWillShowNotification:) name:UIKeyboardWillShowNotification object:nil];
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onUIKeyboardDidShowNotification:) name:UIKeyboardDidShowNotification object:nil];
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onUIKeyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil];
  [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onUIKeyboardDidHideNotification:) name:UIKeyboardDidHideNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
  // Notification observerを削除する
  [[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)onUIKeyboardWillShowNotification:(NSNotification *)notification
{
  NSLog(@"%s", __func__);
  NSLog(@"  * userInfo = %@", notification.userInfo);
  // UIKeyboardFrameEndUserInfoKeyが使える時と使えない時で処理を分ける
  CGRect bounds;
  if (&UIKeyboardFrameEndUserInfoKey == NULL) {
    // iOS 3.0 or 3.1
    // bounds
    bounds = [[notification.userInfo objectForKey:UIKeyboardBoundsUserInfoKey] CGRectValue];
  } else {
    // それ以外
    // frameだがoriginを使わないのでbounds扱いで良い
    bounds = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
  }
  // boundsを使った処理をここに書く
}

ログはこんな感じに。

iOS 5.0, iPhone 4S
2011-10-19 10:35:15.007 SampleApp[5675:707] -[SampleViewController viewWillAppear:]
// 前の画面の英字キーボードが一旦引っ込んで出てきている
2011-10-19 10:35:15.502 SampleApp[5675:707] -[SampleViewController onUIKeyboardDidHideNotification:]
2011-10-19 10:35:15.506 SampleApp[5675:707]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = "0.25";
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 372}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 588}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
    UIKeyboardFrameChangedByUserInteraction = 0;
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 480}, {320, 216}}";
}
2011-10-19 10:35:15.509 SampleApp[5675:707] -[SampleViewController onUIKeyboardWillShowNotification:]
2011-10-19 10:35:15.510 SampleApp[5675:707]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = "0.35";
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 372}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 372}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
    UIKeyboardFrameChangedByUserInteraction = 0;
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
}
2011-10-19 10:35:15.513 SampleApp[5675:707] -[SampleViewController onUIKeyboardDidShowNotification:]
2011-10-19 10:35:15.514 SampleApp[5675:707]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = "0.35";
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 372}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 372}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
    UIKeyboardFrameChangedByUserInteraction = 0;
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
}
2011-10-19 10:35:16.162 SampleApp[5675:707] -[SampleViewController viewDidAppear:]
// キーボード引っ込める
2011-10-19 10:35:44.246 SampleApp[5675:707] -[SampleViewController onUIKeyboardWillHideNotification:]
2011-10-19 10:35:44.247 SampleApp[5675:707]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = "0.25";
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 372}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 588}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
    UIKeyboardFrameChangedByUserInteraction = 0;
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 480}, {320, 216}}";
}
2011-10-19 10:35:44.509 SampleApp[5675:707] -[SampleViewController onUIKeyboardDidHideNotification:]
2011-10-19 10:35:44.511 SampleApp[5675:707]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = "0.25";
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 372}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 588}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
    UIKeyboardFrameChangedByUserInteraction = 0;
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 480}, {320, 216}}";
}
// キーボード出す
2011-10-19 10:35:55.135 SampleApp[5675:707] -[SampleViewController onUIKeyboardWillShowNotification:]
2011-10-19 10:35:55.136 SampleApp[5675:707]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = "0.25";
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 588}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 372}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 480}, {320, 216}}";
    UIKeyboardFrameChangedByUserInteraction = 0;
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
}
2011-10-19 10:35:55.397 SampleApp[5675:707] -[SampleViewController onUIKeyboardDidShowNotification:]
2011-10-19 10:35:55.398 SampleApp[5675:707]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = "0.25";
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 588}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 372}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 480}, {320, 216}}";
    UIKeyboardFrameChangedByUserInteraction = 0;
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
}
// 日本語キーボードに変更
2011-10-19 10:35:58.167 SampleApp[5675:707] -[SampleViewController onUIKeyboardWillShowNotification:]
2011-10-19 10:35:58.168 SampleApp[5675:707]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = 0;
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 252}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 390}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 354}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
    UIKeyboardFrameChangedByUserInteraction = 0;
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 228}, {320, 252}}";
}
2011-10-19 10:35:58.170 SampleApp[5675:707] -[SampleViewController onUIKeyboardDidShowNotification:]
2011-10-19 10:35:58.171 SampleApp[5675:707]   * userInfo = {
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 252}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 390}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 354}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
    UIKeyboardFrameChangedByUserInteraction = 0;
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 228}, {320, 252}}";
}
// 英語キーボードに変更
2011-10-19 10:36:00.483 SampleApp[5675:707] -[SampleViewController onUIKeyboardWillShowNotification:]
2011-10-19 10:36:00.484 SampleApp[5675:707]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = 0;
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 336}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 372}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 228}, {320, 252}}";
    UIKeyboardFrameChangedByUserInteraction = 0;
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
}
2011-10-19 10:36:00.485 SampleApp[5675:707] -[SampleViewController onUIKeyboardDidShowNotification:]
2011-10-19 10:36:00.486 SampleApp[5675:707]   * userInfo = {
    UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 216}}";
    UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 336}";
    UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 372}";
    UIKeyboardFrameBeginUserInfoKey = "NSRect: {{0, 228}, {320, 252}}";
    UIKeyboardFrameChangedByUserInteraction = 0;
    UIKeyboardFrameEndUserInfoKey = "NSRect: {{0, 264}, {320, 216}}";
}
// 画面を抜ける、キーボードが隠れる通知より先にviewWillDissappearされるので通知が来ない
2011-10-19 10:36:03.570 SampleApp[5675:707] -[SampleViewController viewWillDisappear:]
2011-10-19 10:36:03.986 SampleApp[5675:707] -[SampleViewController viewDidDisappear:]
iOS 3.1.3, iPhone 3G
2011-10-19 10:43:46.548 SampleApp[352:207] -[SampleViewController viewWillAppear:]
// 前の画面の英字キーボードが一旦引っ込んで出てきているのだが、iOS 3.1.3では引っ込む側の挙動が見られない。出てくるだけになっているようだ。
// さらにDidShowの通知がキーボードがviewDidAppearの呼び出しのあとに行われるようになっている。
// どうやらキーボードがどこに属しているのかが違うみたいだな。
2011-10-19 10:43:47.202 SampleApp[352:207] -[SampleViewController onUIKeyboardWillShowNotification:]
2011-10-19 10:43:47.210 SampleApp[352:207]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = 0.35;
    UIKeyboardBoundsUserInfoKey = NSRect: {{0, 0}, {320, 216}};
    UIKeyboardCenterBeginUserInfoKey = NSPoint: {480, 372};
    UIKeyboardCenterEndUserInfoKey = NSPoint: {160, 372};
}
2011-10-19 10:43:48.253 SampleApp[352:207] -[SampleViewController viewDidAppear:]
2011-10-19 10:43:48.315 SampleApp[352:207] -[SampleViewController onUIKeyboardDidShowNotification:]
2011-10-19 10:43:48.336 SampleApp[352:207]   * userInfo = {
    UIKeyboardBoundsUserInfoKey = NSRect: {{0, 0}, {320, 216}};
    UIKeyboardCenterBeginUserInfoKey = NSPoint: {160, 588};
    UIKeyboardCenterEndUserInfoKey = NSPoint: {160, 372};
}
// キーボード隠す
2011-10-19 10:43:52.854 SampleApp[352:207] -[SampleViewController onUIKeyboardWillHideNotification:]
2011-10-19 10:43:52.862 SampleApp[352:207]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = 0.300000011920929;
    UIKeyboardBoundsUserInfoKey = NSRect: {{0, 0}, {320, 216}};
    UIKeyboardCenterBeginUserInfoKey = NSPoint: {160, 372};
    UIKeyboardCenterEndUserInfoKey = NSPoint: {160, 588};
}
2011-10-19 10:43:53.183 SampleApp[352:207] -[SampleViewController onUIKeyboardDidHideNotification:]
2011-10-19 10:43:53.188 SampleApp[352:207]   * userInfo = {
    UIKeyboardBoundsUserInfoKey = NSRect: {{0, 0}, {320, 216}};
    UIKeyboardCenterBeginUserInfoKey = NSPoint: {160, 372};
    UIKeyboardCenterEndUserInfoKey = NSPoint: {160, 588};
}
// キーボード出す
2011-10-19 10:43:54.621 SampleApp[352:207] -[SampleViewController onUIKeyboardWillShowNotification:]
2011-10-19 10:43:54.629 SampleApp[352:207]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = 0.300000011920929;
    UIKeyboardBoundsUserInfoKey = NSRect: {{0, 0}, {320, 216}};
    UIKeyboardCenterBeginUserInfoKey = NSPoint: {160, 588};
    UIKeyboardCenterEndUserInfoKey = NSPoint: {160, 372};
}
2011-10-19 10:43:54.972 SampleApp[352:207] -[SampleViewController onUIKeyboardDidShowNotification:]
2011-10-19 10:43:54.977 SampleApp[352:207]   * userInfo = {
    UIKeyboardBoundsUserInfoKey = NSRect: {{0, 0}, {320, 216}};
    UIKeyboardCenterBeginUserInfoKey = NSPoint: {160, 588};
    UIKeyboardCenterEndUserInfoKey = NSPoint: {160, 372};
}
// キーボードの種類をここで英語→日本語、日本語→英語に変えているのだが、通知が来ない
// だがiOS 3ではキーボードの種類によって高さが違うということが(基本)ないので気にしなくて良い
[Switching to process 11779 thread 0x2e03]
warning: No copy of  found locally, reading from memory on remote device.  This may slow down the debug session.
// 画面抜ける、viewWillDisappearより先にKeyboardが隠れる通知が来る
2011-10-19 10:44:07.442 SampleApp[352:207] -[SampleViewController onUIKeyboardWillHideNotification:]
2011-10-19 10:44:07.454 SampleApp[352:207]   * userInfo = {
    UIKeyboardAnimationCurveUserInfoKey = 0;
    UIKeyboardAnimationDurationUserInfoKey = 0.35;
    UIKeyboardBoundsUserInfoKey = NSRect: {{0, 0}, {320, 216}};
    UIKeyboardCenterBeginUserInfoKey = NSPoint: {160, 372};
    UIKeyboardCenterEndUserInfoKey = NSPoint: {480, 372};
}
2011-10-19 10:44:07.472 SampleApp[352:207] -[SampleViewController viewWillDisappear:]
2011-10-19 10:44:08.374 SampleApp[352:207] -[SampleViewController viewDidDisappear:]
問題なさそうですね。キーボードの種類が切り替わったタイミングにもUIKeyboardWillShowNotificationとUIKeyboardWillHideNotificationがきちんと呼ばれているようです。これならUIKeyboardWillChangeFrameNotificationを使う必要はあんまり無いように思えます。実際、UIKeyboardWillChangeFrameNotificationを使わないでUIKeyboardWillShowNotificationとUIKeyboardWillHideNotificationだけを使ったアプリをリリースしていますが、特に問題なさそうです。

2019年6月8日土曜日

SwiftUI 未解決問題まとめ

一通り試してほぼほぼ理解できたのですが、現状どこをどのように調べてもわからなかった内容がいくつかあるので未解決問題としておいておきます。誰か教えて\(^o^)/

Transition

type-eraseされたAnyTransitionはSwiftUI.frameworkに居るのですが、元のProtocolが見えない上にドキュメンテーションも一切ありません。おそらくはNavigationViewの中で使われているのだと思いますが詳細不明。

ScrollView / Listの現在のスクロール位置 (UIKitにおけるcontentOffset) に相当する値を参照する/コード上から指定する方法

今の所、一番怪しいのが以下のpreferenceの仕組みではないかと睨んでいるのですが、
問題は肝心要のPreferenceKeyの具体実装がSwiftUI.framework上には一切見つからず、したがってキーが指定できないためonPreferenceChange(_:perform:)がうまく利用できません。特定位置までスクロールしたら発火、とかビューが50%スクロールして隠れたら発火、とか普通に使いたいのですが、困りました。それとかあとはenvironment経由なりイニシャライザ経由なりでinitialScrollPositionのようなプロパティを用意してScrollViewのスクロール初期位置を与える、みたいなテクも使いたいですし、普通に必要だと思うんですけど(´・_・`)

ちなみにView.offsetではない・・・と思います、たぶん。一応念のためにList.offset()で試してみましたが、ドキュメンテーションにもある通り、全く違う挙動になります。

2010年3月20日土曜日

IE6, IE7 上で flash.net.URLLoader を使って Comet (Long Poling) によるプッシュ通知を行う際の注意点

Flashは一度書いたらどのブラウザでも同じように動く、そう信じていた時が私にもありました。その私の考えを裏切るかのように、 またIEか と言いたくなる現象が発見されました。

調査の際に参考にしたページはこちら。
http://saruzaurus.blogspot.com/2008/07/comet_10.html


■問題
IE6, IE7 で、 flash.net.URLLoader / flash.net.Loader を使用すると、同時に2本しかコネクションが張れません。通常の使い道でしたらこの制限で何ら問題はないのですが、問題になるのは CometTornado のように所謂ロングポーリングと呼ばれるHTTP通信の仕方をするアプリケーションを書いたときです。ロングポーリングではいったんHTTPリクエストをサーバーに投げた後、サーバーで何かイベントが発生するまでそのレスポンスをひたすら待ちます。従って常時HTTPコネクションが1本消費されている状態になるため、事実上1本しかHTTPコネクションを使うことができません。

またIE6, IE7 では、 flash.net.URLLoader.close() の実装にも問題があるようです。
http://www.kirupa.com/forum/showthread.php?t=335691
http://stackoverflow.com/questions/455656/urlloader-gets-stuck-when-polling
きちんと使用後に URLLoaderclose しているのですが、それでも何故かメモリ使用量が減らなかったり、コネクションが閉じられなかったりして貴重なコネクション数が消費されっぱなしになったりするなど、どうにもこうにも不安定です。



これらが原因となって、常時ロングポーリングでイベントを待ち、イベントを受け取ったら処理を行ってまたすぐロングポーリングを張りに行くようなアプリケーションを書くと、その他の通信を行った際にロングポーリングが止まってしまったりその他の通信がコネクション数限界で実行されなかったりします。


■原因
Flashは内部的にはブラウザの機能を用いて実装されています。したがってブラウザ側のHTTP接続数が2本であれば自動的にFlash側のHTTP接続限界も2本になります。当たり前と言えば当たり前なのですが、盲点でした。

Flash側の接続数とブラウザ側の接続数が共有されるかどうかは未知数・未検証ですが、おそらく共有されるのではないかと思っています。従って(まずこんな構成にすることはないと思いますが)FlashでCometによる通知待ちを行い、Ajaxでも同様の通知待ちを行うと、それだけで接続限界になってしまいます。


■対策
今のところ最良の方法がまだ分かっていません・・・ごめんなさいorz 一応いくつか思いついている作戦は、
  1. Windowsのレジストリを操作してIE6/IE7の同時接続数を増やす
  2. 同時接続数制限は同一ドメインに対してのみ発生するらしいので、サブドメインを切って別々のドメインに分ける
  3. 他の通信が発生しているときはロングポーリングを切断する
などなど。上手い対策が発見されましたらまた書きます。

2010/03/31 23:59 追記:サブドメインを切って別々のドメインに分けたら上手く動作しましたので、この方法を一応お勧めしておきます。