AlwaysATK

10 コメント

2014/01/01
あけましておめでとうございます。
Substrateがめでたく64bit対応しましたので、更新です。
iOS7の仕様変更でSafariとメッセージに対応出来たようです。

AlwaysATKは、URLスキームを搭載した全てのアプリケーションにATOK Padと連携するボタンを出現させます。
設定からAutomaticSwitchingや、アプリ毎の無効化が設定できます。

・免責事項
このアプリの使用によるいかなる損害にも、責任は負いかねます。
Jailbreakは自己責任です。
上記をよく読み、理解したという方だけダウンロードしてください。
バグ報告はご自由にどうぞ。気が向いたら更新します。


iOS7以降 ダウンロード

iOS6以前 ダウンロード

※ 「ATOK」および「ATOK Pad」は、株式会社ジャストシステムの登録商標または商標です。

アプリの終了を検知して通知を表示する。

0 コメント

アプリの終了と書きましたが、バックグラウンド動作の終了ですね。
広義的には、バックグラウンドで停止=アプリの終了と見ることが出来ると思います。

具体的な事例はSySightに組み込んでいるコレです。



SySightの終了通知は、
・アプリのバックグラウンド動作の限界時間を超過した場合。
・アプリがメモリ不足等で不意にkillされた場合。
・アプリをタスクスイッチャーから消した場合。
以上の3通りにおいて、表示されます。

バックグラウンド動作を組み込んでいるアプリで、終了通知を実装しているアプリは数あれど、
何やら限界時間が来ないと通知されないようになっている印象を受けました。
iOS7と6とで限界時間は違うわけですし(3 or 10分)、限界時間を測って表示するのもなんだかなぁと思いましたので、
僭越ながら私の実装方法を紹介する次第でございます。

バックグラウンド動作は、それを実現するためにループとスリープを組み合わせているものが多いと思います。

while (application.applicationState != UIApplicationStateActive) {
    ...
    [NSThread sleepForTimeInterval:1];
}

大凡このような感じだと思いますが…
このループをforで書いて、限界時間を秒単位で測ってループの外で通知を実装するのも、アリだと思います。

for (int i = 0 ; i < 170; i++) {
    ...
    [NSThread sleepForTimeInterval:1];
}
UILocalNotification *notification = [[UILocalNotification alloc] init];
...
[application presentLocalNotificationNow:notification];
※applicationStateのチェックは省略

ただ、アプリのバックグラウンド動作は、時間が来れば勝手に止まるものです。
その際、確率的にほぼ確実にスリープの箇所で停止します。
追記するならば、他の要因で停止する時もスリープの箇所で停止する、と考えて良いと思います。
なので、通知を予約し、スリープの後で通知をキャンセルすることで、通知をキャンセル出来なかった場合に通知が出現するようにプログラムを組むことが可能となります。

while (application.applicationState != UIApplicationStateActive) {
    notification.fireDate = [NSDate dateWithTimeIntervalSinceNow:3];
    [application scheduleLocalNotification:notification];
    ...
    [NSThread sleepForTimeInterval:1];
    [application cancelLocalNotification:notification];
}

幾つかのシチュエーションにおいて、この手法は優位性があると考えています。
・監視中にフォアグラウンドに戻った場合に通知を表示しない。
・アプリケーションの終了を検知する手法への応用。

当然、弱点も存在します。
・OSがハングした場合に意図せず通知が表示→削除される。
・極々稀に通知が表示されない。(ほぼありえませんが…)

ということで、広義的なアプリケーションの終了を通知する方法の紹介でした。
色々応用が効くと思うので、試してみる価値はあると思います。

iOS7/Xcode5時代のUIWebViewと正しく付き合う

0 コメント

みんな大好きUIWebView、iOS7/Xcode5な現在も相変わらずのツンデレ具合で僕はお腹いっぱいです。
解禁されたことですし、未だに残っている注意することを(どう考えてもバグな挙動も)見つけているので、さらっと解決方も含めてメモメモ。

1. webView.scrollView.scrollIndicatorInsetsがぶっ壊れる。
これはどう考えてもバグです。
INPUTタグ等にフォーカスを合わせ、キーボードを出現させると、
scrollIndicatorInsets(スクロールバーの位置)がぶっ壊れます。
純正のUIWebViewをUIViewControllerのviewにセットしただけで壊れるので、バグで間違いないでしょう。

ぶっ壊れたスクロールバー

しかし、ページのどまんなかにスクロールバーが出現するのは非常にUXが悪いです。
ポイントは、キーボード出現時に壊れるという点。
キーボードが出現した場合、UIWebViewは隠れた部分も表示させるために、scrollViewのscrollIndicatorInsetsとcontentInsetの値を変更します。
幸いなことにcontentInsetは壊れていないようなので、この値をscrollIndicatorInsetsにトレースすることで、このバグは解決することが出来ます。
値をトレースする時は、KVOを使うと便利ですね。

とりあえず、UIWebViewのサブクラスを作成して、initに以下の二つを仕込みます。


[self.scrollView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil];
[self.scrollView addObserver:self forKeyPath:@"contentInset" options:NSKeyValueObservingOptionNew context:nil];

そして、observeValueForKeyPath:ofObject:change:context:メソッドを実装します。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
  [self.scrollView removeObserver:self forKeyPath:@"scrollIndicatorInsets"];
  self.scrollView.scrollIndicatorInsets = self.scrollView.contentInset;
  [self.scrollView addObserver:self forKeyPath:@"scrollIndicatorInsets" options:NSKeyValueObservingOptionNew context:nil];
}


これで、スクロールバーは健康になるはずです。ヤッター

2013/09/22 00:32 追記
KVO監視下でUINavigationController以下にUIWebViewを置いた上で、
UINavigationController#toolbarHidden=NOとした場合、見事にexc_bad_accessしました。
これは使いものにならないハックのようだ。しのう

2013/09/22 00:41 追記
対案として、キーボードの出現を見張る方法があります。
JavaScriptが使えますね。

document.addEventListener('selectionchange', function(){open('hoge://fuga');});

UIWebViewと親密なお付き合いをされている方は、上のスクリプトの意味は解るはずですが、
一応解説しておくとwebView:shouldLoadRequest:navigationType:でhoge://fugaなURLを検知して何かをするためのスクリプトです。
つまり、キーボードが出現、消滅した際にwebView:shouldLoadRequest:navigationType:にhoge://fugaが飛んでくるので、

self.scrollView.scrollIndicatorInsets = self.scrollView.contentInset;

をする、ということですね!これにて完結くぅ疲

2. contentInsetを変更すると、Reload、Back、ForwardでcontentOffsetがズレる。
これは仕様なのかバグなのかわからないですが、大凡バグと見ていいでしょう。
contentInsetを変更することで、Safariのような裏側にUIWebViewが回りこむUIが作れるわけですが、
が、この状態で上記ブラウザ三種の神器を利用すると、contentOffsetがズレます。
つまり勝手にスクロールされるわけです。


原因は、読み込み時にcontentInset分、最初のcontentOffsetをずらす処理です。
これは組み込めば解るのですが、地味にありがたい処理です。
代償として大きな禍根を残してくれましたが…

この程度なら〜と思うかもしれませんが、これは徐々に、そして確実に、無意識化にストレスとして蓄積されていきます。
根本から回避するためには、contentInsetのtopは0で固定する他ありません。
つまり、裏側に回り込むUIは作れませんし、UINavigation以下にUIWebViewを配置することも出来ません。クソゲー。
contentInsetを利用しつつ、回避する方法はナキニシモアラズなのですが、
今のところ僕は次の原始的な方法しか見つけていないです。
「ページ読み込み後に逆向きに補正をかける」です。
正直言ってこれしか方法無いです。
contentInset.topを一時的に0にする方法も試しましたが、これをするとcontentOffsetやcontentSizeにも影響が出るので全く安定しませんでした。
逆向きに補正を掛ける場合は、UIWebViewDelegateのwebViewDidFinish:を使うのが一番楽ですが、
複数回呼び出されるので一度だけ補正をかけるようにしないと、さらに酷いことになるので注意が必要です。※参考


ちなみに上記二つの問題はSafariでは起きませんでした!!!
scrollIndicatorInsetsに関しては、SafariのnavigationBar, toolBarが特殊仕様であること、
contentInsetに関しては、Reload, Back, Forwardすら特殊仕様であることが関係してると思います。絶対に許さない。


3. UIApplication#openURL:についての注意。
これはUIWebViewとは(直接どころか間接的にも)関係ありませんが、
貴兄らにおかれましてもきっとお役立ちになると存じますので、記しておきます。
他アプリへのリダイレクト機能を持つアプリが対象ですが、Xcode5でビルドしたそのアプリ、
起動が遅くありませんか?
これが原因です。
バグなのか仕様なのかは存じませんが、Xcode5においては、
openURL:をアプリケーション起動時に逐次実行すると固まります。
SySightが重い原因もこれでした。
回避方法としては、dispatch_asyncなんかを使ってちょいとだけ待って上げるのが簡単で良いのではないでしょうか。

フラットデザインへの対応だけでも死にそうなのに、至る所に罠が仕込んであってボクのHPはそろそろ尽きそうです。
ユーザーのレビューが辛い…お前ら、お願いだから更新したら書きなおしてくれよ…
ということで、皆々様方、ここが踏ん張りどころです。頑張りましょう。

それでは、ばいびー

iPhone5S買ったった

0 コメント

買いましたiPhone5s。
4s買ったのが一昨年の発売日。
二年間大切に使いました。一週間目で背面割ったのはいい思い出。
思えば、一昨年も体調崩して死にそうでしたね。一時間ほどサーバーが落ちて待たされて、
auだけ復帰して天神AppStoreで3番目だか何番目だかにiPhone4sを持って出てきて、
待ち構える取材陣に「お腹痛いんで勘弁して下さい」みたいな事を言いました。懐かしい。

ということで、丁度二年ですのでiPhone5s買ってきました。auで。

一昨年の因果でしょうかね。今年はauが鯖落ちして3時間程待たされました。
そして案の定、体調を崩しました。
前日20時から並んだというのにこの仕打!クソ庭め、絶対に許さない。

それはそうと、auの5sはLTEが自慢みたいですね。
ということでちょっとだけ、自慢のLTEの威力を確認してきました。

天神から飯塚までは、この経路を使って帰りました。
八木山峠を通る高速バスですが、この経路は筑穂IC付近でトンネルを潜ります。

問題はこのトンネルを潜る箇所ですが、従来iPhone5ではLTEを見失い、その後3G回線で固定されたりパケ詰まりをしたりと散々だったそうです(友人談)。

一方ボクの5sは、この筑穂IC付近でこそ3Gだったものの、トンネルを除く全ての道中でLTE
お家に帰ってきた今もバッチリLTEにつながってます。
なんかもうずっとLTE。すごい。

ということで、auの5sがLTEはすごい、というのは福岡県内ではマジなようです。
ちなみにお家からの速度はこのくらい。




残念ながら、来月にはボクのLTEは使えなくなってますが…
auでパケ詰まりに悩む方は、5s/cへの買い替えを検討してみるのも良いかもしれません。


後は思ったよりTouchIDがいい感じでした。
AppleStoreのモックではサンプルアプリがあるので勘違いしがちですが、
実機では[設定] → [一般] → [パスコードと指紋認証]の位置にあります。
やっぱりパスコード入れないでいいのは楽。API公開はよ。

カメラは試してません。僕は目が腐っているので。

そして最重要項目である、僕のアプリの動作状況です。
※既知のバグによる動作不良は除きます。
Libing ○ ヾ(*´∀`*)ノ
SySight × (ヽ'ω`)
Blaster ○
FlickCalc ○

SySightで、メモリ解放が出来ません!
5sでメモリ解放?んんん~?って話ですが、出来るだけ早く対応したいと思います。
5cで解放出来ない場合は、そっと教えて下さい。
之を確認出来ただけでも、買った甲斐がありました。

だがau貴様は許さん

それでは、ばいびー

MyScriptsからATOK Padを利用する。

0 コメント


Change log


2013/09/18
ATOK.editor()の引数からscriptを削除。
ATOK.initialize()を追加。
フラグの変更位置を少しだけエレガントに。
スクリプトのタイトルによって動作しないバグを修正。
再インストール後は再起動が必要です。


2013/09/18
MyScripts用関数ATOKを作成。
return valueな関数として、ATOK.editor()を作成。


今までずっと、あんな事こんな事をしてきた訳ですが、、
遂に、遂に遂に!
ATOK PadがURLスキームのみで他アプリと連携できるようになりました!!
という訳でやることはひとつ!

ATOK PadをMyScriptsから利用します。
とりあえず、return valueな関数として作ってみました。


ATOK

サンプルコード
┗ クリップボードをATOK Padで編集します。


以下マニュアル(スクリプト内にも同じものがあります)

(1)
#IMPORT ATOKをスクリプト先頭に置き、ATOK.initialize()を呼び出してATOKオブジェクトを初期化します。
ATOK.initialize()の引数は、呼び出し元のスクリプトのタイトルです。

(2)
ATOK Padと連携したい箇所でATOK.editor(title,text)を呼び出します。
titleはATOKに表示するタイトル(必要なければ空文字列'')、
textはATOKに初期入力するテキスト(同上)を入れます。

(3)
ATOK.editor()の返り値はATOK Padで入力した文字列、またはnullです。
ATOK Padに連携する前は、nullが返ります。

(4)
必ず、スクリプト終了前にATOK.finalize()を呼び出して下さい。
ATOK.editor()からnullが帰ってきてスクリプトを中断する場合も同様です。

(5)
ATOK Pad連携後は、再び最初からスクリプトが実行されます。
複数回連続して連携し、変数毎にそれぞれの値を格納することも可能ですが、
処理中にATOK.editor()を呼ぶ事はお勧めしません。
また、連携時にTEXTを使用するので、クリップボードを利用したり、
何らかの引数を持つスクリプトはATOK連携前にグローバル変数に退避させるなどして対策を行って下さい。


※ 「ATOK」および「ATOK Pad」は、株式会社ジャストシステムの登録商標または商標です。

UIWebViewのBackForwardListを復元する。

0 コメント

見てる人いるのかな?お久しぶりです。

さて、ここで言う、BackForwardListというのは戻る進むのタブ履歴の事ですね。

現在、iOSに存在するブラウザアプリで、このBackForwardListが復元可能なのは、
管見の及ぶ範囲では、Safari、Chrome、他は拙作のLibing、Blasterだけなのではないでしょうか。

閉じたタブを復元する機能や、見てないタブをリリースしてメモリを節約する機能など、いくつかの場面でBackForwardListを復元したくなります。
が、UIWebViewそのものには当然そのようなAPIは実装されていません。
Safariの特権ですかぁ?ムカツキますねー(#^ω^)

F某社の某記事によるとこの復元は出来ないみたいなことが書いてありましたが、まあそんなことは無いので簡単にロジックを公開します。

あ、ただしBackForwardListそのものは、生成出来ているものとします。
UIWebViewDelegateや、goBack、goForward等をフックして、頑張って作成して下さい。
JavaScriptのhistory系もフックする必要があるので、多少面倒です。

1.プロトコルの上書き
先ずは、NSURLProtocolのサブクラスを利用して、startLoadingしたら次の動作を行うプロトコルを作成します。

NSURLResponse *response = [[NSURLResponse alloc] initWithURL:self.request.URL MIMEType:@"text/html" expectedContentLength:-1 textEncodingName:@"utf-8"];
NSData *data = [NSData data];
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[self.client URLProtocol:self didLoadData:data];
[self.client URLProtocolDidFinishLoading:self];
これは、ネットワークを上書きして、接続せずに空のHTMLファイルを読み込む様にしています。
この状態で生成済みのBackForwardListを次々と読み込んでいきます。

2.BackForwardListの読み込み
BackForwardListを読み込む場合に、連続でNSURLRequestを発行して読み込んでしまうと、
復元中のUIWebViewのBackForwardListに追加される前に、次のrequestを処理してしまうケースがあります。
そこで、webViewDidFinishLoad:を監視しても良いのですが、これはちょっと遅いです。
NSTimer、NSRunLoop等を利用してJavaScriptのhistory.lengthを監視すると良いでしょう。
読み込みたいListのインデックスよりhistory.lengthが大きければ次を読み込んでも大丈夫です。

3.インデックスの調整
BackForwardListを無事読み込み終ったら、次は元いた位置に戻らなければいけません。ここはJavaScriptのhistory.goで良いのですが、その前に1で追加したNSURLProtocolサブクラスを破棄しておきましょう。
なお、インデックスが末尾である場合は、history.go(0)より、普通にreloadを叩くほうが安定しているようです。

4.空のHTMLを読み込んだ時の処理
無事BackForwardListを復元出来ましたが、中身は全て空のHTMLなので、webViewDidFinishLoad:あたりに空のHTMLを読み込んだ場合にreloadする処理を書いておきましょう。
[webView stringByEvaluatingJavaScriptFromString:@"document.body.innerHTML"].length
なんかで判定出来ます。
ただ、この方法は少し重いので、予め読み込むHTMLファイルに戻る進むで再読み込みするJavaScriptなんかを仕込めるのであれば、そっちの方が良いです。
僕はわからなかったのでやりませんでした。


※注意
この方法でBackForwardListを復元する場合に注意が必要なのが、同じURLを連続して読み込む事は出来ないという点です。
IFRAMEを利用しているページで遷移すると、ほぼ確実に同じURLが連続するので、そういうケースではBackForwardListから連続するURLを排除してあげるなどの工夫が必要になります。
あとやっぱり、history.goをチェックするからには仕方ないのですが、多少遅いです。
NSURLProtocolを上書きしているのに、一部キャッシュページが点滅するのも地味に気になりますね。


とまあ、ぐだぐだのHackですが、LibingとBlasterはこんな感じの実装をしていました。
少しでも参考になれば幸いです。
WebKitの、WebBackForwardListや、WebHistoryItemが使えればこんな苦労はしないんですけどねー。
無理やり使う事はできますが、そっちが審査に通るかは知りません。
それではー。

倍額セールをやった結果ンゴwwww

2 コメント

とりあえずまとめ。

以下の持論に基づき、倍額セールを狂行しました。
1.リリース記念半額セールはセール解除後に長期的な打撃を受ける。
2.倍額セールで炎上商法成立www

その結果、、

Libing for iPadにそこまで需要がないということが(ry

倍額セールでも一部の方に普通にご購入頂けました。ありがとうございます。

えーっと、色々資料があるので、貼っておきます。
まずはランキングの推移。出展 http://iphone.308413110.com/ranking/ 様




なぜか倍額セールなのにいきなりランキングに出現。総合には出て来ませんでしたが。
そして、倍額セール解除後、一気にランキングが上昇し、総合にもランクイン!

まあその後は普通に落ち着きました。
現在ではカテゴリ100位くらいをウロウロしています。(日当たりの個数は一桁台)

で、今日までの売上のうち、倍額でご購入頂いた方の割合は、20%くらいでしょうか。
絶対値に関しては、序盤は日当たりの売上がSySightと同程度だった、とだけ記しておきます。
最近はry

倍額セールが上手く行ったかは読者様方のご判断に委ねるところですが、僕は上手く行ったのかなーと思っています。

倍額セールといえば響きはアレですが、逆に考えれば2日目以降恒久半額セールってことですからね。
なので、必然的に倍額→標準に切り替わった瞬間に売上が伸びます。
逆に、倍額時にダウンロードして頂ければ、その分の甘味もあると。コレに関しては恩返しをプランしておく必要がありますが。

あ、炎上商法は成立しませんでしたm9(^Д^)
僕では拡散力が足りなかったようです。口惜しや。

とまあ、こんな感じでした。やってよかったんじゃないでしょうか。
ンゴるほどひどい結果では無かったと思います。おわり。

Powered by Blogger.