TTTAttributedLabelで訪問済みのリンクテキストの色を未訪問のものと区別する方法

TTTAttributedLabelで指定した範囲の文字にURLを指定してリンクにするには以下の様にaddLinkToURL:withRangeを利用します。

TTTAttributedLabel *label;
...
[label addLinkToURL:[NSURL URLWithString:@"http://github.com/"] withRange:range]}; 

addLinkToURL:withRangeを利用して適用したリンクには、linkAttributesで指定した属性が自動的に付与されます。

// リンク色をすべて青色にする
label.linkAttributes = @{NSForegroundColorAttributeName : [UIColor blueColor]};

activeLinkAttributesでタップしたり長押しした際のリンク色を設定できます。

// 選択時のリンク色をすべて赤色にする
label.linkAttributes = @{NSForegroundColorAttributeName : [UIColor redColor]};

addLinkToURL:withRangeを使う場合は上記の2つが適用されるため、訪問済みのリンクの場合だけ色を変えたりといったことができません。そこで、addLinkToURL:withRangeを使わずにaddLinkWithTextCheckingResult:attributes:メソッドを使ってリンクを設定することで訪問済みの場合だけ色を変えることができます。

以下、サンプル

NSURL *url = [NSURL URLWithString:@"https://github.com"];
...
NSDictionary *visitedLinkAttributes = @{NSForegroundColorAttributeName : [UIColor purpleColor]}
if ([visitor isVisited:url]) {
    // 訪問済みのリンクはリンク色を紫に
    [label addLinksWithTextCheckingResults:[NSTextCheckingResult linkCheckingResultWithRange:range URL:url] attributes:visitedLinkAttributes];
}
else {
    [label addLinkToURL:url withRange:range];
}

TTTAttributedLabelDelegateattributedLabel:didSelectLinkWithURL:メソッドでリンクがタップされた際に呼ばれる処理を記述できるので、訪問済みかどうかの情報はそのタイミングで保存することができます。

label.delegate = self
...

#pragma mark - TTTAttributedLabelDelegate methods
- (void)attributedLabel:(TTTAttributedLabel *)label  didSelectLinkWithURL:(NSURL *)url {
    // リンクがタップされた際に呼び出される処理
    ...
    // リンクがタップされた情報を保存しておく
    [visitor visited:url];
}

iOSのマルチスレッド入門・UI編

iOSアプリでまあ簡単なアプリだったら良いんですけど、いくつか画面があったり メインスレッド以外で処理をしてなるべくユーザの操作をブロックしたくないってケースは当然あると思います。

最近作ってるアプリでマルチスレッド対応をしたので備忘録というかまあメモ的な感じで残しておきます。 ここに残してある方法以外にも並列プログラミングの方法や必要なシーンなどはたくさんあるので (ネットワーク通信とかいろいろ)そこはまた機会があれば書きます。

NSTimerを使う

タイマーはゲームを作ったりといったシーンなどでも使いたいんじゃ無いでしょうか。 NSTimerを使うことで実現できます。以下の様に実装するだけです。

タイマーを起動し、指定した間隔で処理を実行する

self.timer = [NSTimer
    scheduledTimerWithTimeInterval:1.0f
    target:self selector:@selector(updateText:)
    userInfo:nil repeats:YES];

タイマーを停止する

if (self.timer && [self.timer isValid]) {
    [self.timer invalidate];
}

タイマーを開始(再開)する

if (self.timer && ![self.timer isValid]) {
    [self.timer fire];
}

非常に簡単ですね。 例えばゲームなどでタイムオーバ後に次の画面でスコアを表示したい場合などは、 タイマーで時間をカウントし、必要なタイミングでタイマーを停止->次の処理とすればいいですし、 広告などでタイマーを停止した場合は復帰時にfireで戻してあげるなどすればいいかと思います。

NSThreadをつかう

NSTimerよりもこちらの方が汎用的に使えるので利用シーンは多いと思います。 画像ビュアーなどで画像読み込み中に別の画像をスワイプして表示させたり、 webからデータを読み込んでいるけどユーザの操作は止めたく無いといった場合に必要になります。

別threadを起動して処理を実行する

[NSThread
    detachNewThreadSelector:@selector(asyncLoadData:)
    toTarget:self
    withObject:userinfo];

NSThreadで起動したスレッドはautorelease poolを自分で用意する必要があります autoreleasepoolブロックを利用するだけでいいらしいです。

- (void) asyncLoadData:(id)userinfo {
    @autoreleasepool {
        UIView *hogeView = [[UIView alloc] initWIthFrame:...] autorelease];
        ...
    }
}

複数のスレッドの排他処理を行いたい場合はsynchronizedブロックを利用すると簡単に実装できます

@synchronized (self) {
    self.atomicProp = ...; @synchronizedブロックは同時に実行できない
}

細やかな排他処理を記述することも当然可能です

NSLoc *lockObj = [NSLock alloc] init];
[lockObj lock];   // 一度lockされるとunlockされるまで他の処理は休止する
[lockObj unlock]; // unlockは同じスレッドで行う必要がある

以上、今回自作のアプリで対応した方法のさらっとした紹介でした。 これらは主にUIまわりで利用できると思います。

iPhoneアプリにiAdとAdmobを貼る

iPhoneアプリの収益化の方法はいくつかありますが、個人で作ってるアプリであれば広告を表示するのが手軽な方法だと思います。

本エントリではiAd広告とAdmob広告をハイブリッドに利用する方法を紹介します。他にも結構似たエントリはあるので、自分はこのハイブリッド広告を自作のクラスを使って手軽に貼り付ける方法を紹介します。

開発環境はXcodeが4.5.2、ARCをONにしています。

そもそも何故ハイブリッドにするのか

広告を一つだけにしてしまうとその広告が配信されていない状態が発生してしまいます。そういった無駄を無くすためにいくつかのアプリ内広告を貼り付けて可能な限り広告が表示されている状態にしたいと考えました。

本来ならiAd、Admob以外のアプリ内広告も利用すべきでしょうが今回は見送りました。AdStir( http://ja.ad-stir.com/ )なども今後使ってみたいです。

iAdを利用する

iAdはAppleが提供するアプリ内広告サービスです。 https://developer.apple.com/jp/iad/ iPhoneアプリに導入する広告としては真っ先に思い浮かぶものかなと思います。

実際にiAdに申し込むには結構面倒なのですがそちらは今回省略しています。 (備忘録として書いておけば良かったですが、他にも結構見つかるのでそちらを見てください...)

iAdの利用は標準で用意されている iAd.framework を利用します。

f:id:hayaishi:20121225202855p:plain

iAdを利用するには ADBannerViewDelegate の bannerView:didFailToReceiveAdWithError: (エラー処理)を実装している必要があります。

iAdSample.h

#import <iAd/iAd.h>
....
@interface AdSampleViewController : UIViewController <ADBannerViewDelegate> {
....
}

iAdSample.m

- (void)bannerView:(ADBannerView *)banner didFailToReceiveAdWithError:(NSError * )error{
    // 広告失敗時の処理を書く
    banner.hidden = YES;
} 

このdelegateメソッドを実装したら後は適当にaddSubviewすれば表示されるようです。

...
self_iAd = [[ADBannerView alloc] initWithAdType:ADAdTypeBanner];
self._iAd.delegate = self;
[self.view addSubview:self._iAd];

実際にはADBannerViewDelegateのdelegateメソッドはもっとあって、 広告の読み込み成功時、広告がタップされた後、広告から戻ってきた時に呼び出されるものがあります。

Admobを利用する

AdmobはGoogleが提供するアプリ内広告です。iPhone以外にAndroidなどでも使えるのが良いですね。 単価についてなど、他の選択肢と比べてどうかはちゃんと調べて無いのですが、間を埋める用なので良いかなと思ってます。 https://jp.admob.com/

Admobは申し込んだらすぐに利用できるようになりました。 こちらは標準のframework以外に、Admobを使うためのSDKをダウンロードする必要があります。

ダウンロードしたSDKを解凍したら、その中にある、ヘッダファイルと.aファイルを自分のプロジェクト以下に移します。 Add-onsディレクトリは今回利用しなかったのでコピーしていません。

その他、必要なframeworkなどは以下のドキュメントを参考に設定しました。

また、忘れてはいけないのが

"4. You now need to add -ObjC to the Other Linker Flags of your application target's build setting:"

こちらで紹介されている通り、build settingOther Linker Flags-ObjC フラグを設定する必要があります。 これを忘れるとエラーになるので注意が必要です。 あとは、Introduction通りにコードを書けば表示することができます

  • GADBannerView.h をインポートする
  • アプリの UIViewController で GADBannerView インスタンスを宣言する
  • インスタンスを作成する
  • 広告のユニット ID(AdMob パブリッシャー ID)を設定する
  • ルート ビュー コントローラを設定する
  • ビューを UI に追加する
  • 広告と一緒にビューを読み込む

AdmobSample.h

#import "GADBannerView.h"
....
@interface AdSampleViewController : UIViewController  {
....
}

AdmobSample.m

// XXXXXXXX の部分を 取得したPublisher IDに置き換える
#define MY_BANNER_UNIT_ID   @"XXXXXXXXXXXXXXXXXX"
...
- (void)viewDidLoad
{
    [super viewDidLoad];
    self._admob = [[GADBannerView alloc]
        initWithFrame:CGRectMake(
            0.0,
            0.0,
            GAD_SIZE_320x50.width,
            GAD_SIZE_320x50.height
        )];
    self._admob.adUnitID = MY_BANNER_UNIT_ID;
    self._admob.delegate = self;
    self._admob.rootViewController = self;
    [self.view addSubview:self._admob];

    // 広告をリクエストする
    GADRequest *rq = [GADRequest request];
    [self._admob loadRequest:rq];
}

これで、広告がロードされればAdmobが表示されます。 Admobの読み込み失敗などは GADBannerViewDelegate の delegateメソッドによってハンドリングできます。

複数広告を貼ると結構面倒な処理が増える

まあ、しょうが無いのですがiAdとAdmobはそれぞれ別の広告なので別のdelegateを実装する必要があります。 このまま素直に実装してしまうと表示、非表示の切り替えといった処理やエラーハンドリングが画面ごとに必要になります。

必要な処理をまとめました。

とりあえず広告を貼りたい!というニーズを満たすにもそのままだと結構手間がかかります。 そこで次の様にiAd, Admobの表示を扱うViewクラスを作成しました。

このリポジトリに入っている SimpleAdBannerView というクラスは次の機能を提供します。

  • iAdが読み込めないときにAdmobを表示するViewの提供
  • 広告クリック時のdelegateメソッドの実装
  • 広告から復帰した際のdelegateメソッドの実装
  • Portrait時の上下位置対応

次の様に利用します。

AdSampleViewController.h

#import "SimpleAdBannerView.h"
@interface AdSampleViewController : UIViewController <SimpleAdBannerViewDelegate> {
    SimpleAdBannerView *_adView;
}
@property (nonatomic, retain) SimpleAdBannerView *_adView;
@end

AdSampleViewController.m

#import "AdSampleViewController.h"

@implementation AdSampleViewController
@synthesize _adView;

- (void)viewDidLoad
{
    [super viewDidLoad];

    CGRect mainFrame = [[UIScreen mainScreen] applicationFrame];
    // バナーをアプリ下部に設置する為の設定
    CGRect bannerRect = CGRectMake(
        mainFrame.origin.x,
        mainFrame.size.height,
        320.0,
        50.0
    );
    self._adView = [[SimpleAdBannerView alloc] initWithFrame:bannerRect];
    self._adView.delegate = self;
    [self.view addSubview:self._adView];
    // 広告の呼び出しを開始
    [self._adView requestStart];
}
-(BOOL)adViewActionShoudBegin:(UIView *)banner willLeaveApplication:(BOOL)willLeave {
    NSLog(@"広告クリック時の処理");
    return YES;
}
-(void)adViewActionDidFinish:(UIView *)banner {
    NSLog(@"広告から復帰した際の処理");
}

このクラスを使うことで、UIViewControllerに書くdelegateは広告のクリック時と広告の復帰時の挙動だけでよくなります。 (非常にシンプルなアプリであればそれすら必要無いかもしれません)

あとの広告の表示、非表示といった処理はこのクラスが隠蔽してくれます。 広告をアプリに貼りたいけど面倒だからやってないといった方は是非お試しください。

landscapeに対応していなかったりなどまだまだ微妙なのですが必要になったら手を入れていきます... (pull request歓迎です!)

以上、広告を貼ってみた際の話でした。

TiagraのブレーキシューをULTEGRAの船つきブレーキシューに交換しました。

初めて買ったロードバイク(Bianchi NIRONE7)についていたブレーキがTiagraのものでした。

ブレーキがの効きがあんまり良くないなぁとずっと思っていて、色々調べていたのですが、Tiagraのブレーキは他のものに比べて効きが悪いという評判が割と多く見つかりました。

さすがにブレーキは安全に関わるものなので105以上のものに変えてもらおうかなと思ったのですが、ブレーキシューを船つきのものに変更してブレーキ性能が改善されたというブログをいくつか見つけたのでお金ピンチまずはそちらを試してみることにしました。

交換用のブレーキシューを購入

船つきのシューは渋谷のY's Roadで購入しました。


楽天では1,687円みたいですが、Y's Roadで1,209円で購入しました。サイクルベースあさひだと1,087円で販売されているようです。

色違いでシルバーもあったのですがこちらの方が高かったです。

Tiagraのブレーキは色がシルバーなので少し悩みましたが、ブレーキシューは同じ黒でそんなに変には見えないだろうし節約のために黒を購入しました。

併せて、ブレーキシューの調整の手間を減らすためにブレーキシューチューナーも購入しました。

ちょっと高いなと思ったのですが、ブレーキシューの調整に自信があるわけでも無く、上手に出来るならと思って購入しました。

実際の写真。
 


Tiagraのブレーキシューの位置を確認


まずは、デフォルトで取り付けられていたTiagraのブレーキシューです。取り付け位置の確認用に撮影しておきました。
右上のねじ周辺がブレーキシューの削りカスで汚れていますが、後で使い終わった歯ブラシで掃除したらきれいになりました(^^)

ブレーキシューを取り外して比較

最初から取り付けられていたブレーキシューは結構簡単に外れました。シューは思ったより汚れていて指が真っ黒になってしまいました。
Tiagraの船なしブレーキシューとULTEGRAの船つきブレーキシューを並べてみました。

並べてみると色も似てるし、船がついたことで高級感が増した感じで良いですね。

取り付け完了

ブレーキシューチューナーがありつつも結構何度か調整を繰り返して疲れたそれっぽい位置に固定できたので作業を終了しました。自分は結構不器用な方でチューナーがありつつも1時間ちょいぐらいの時間がんばってました。

取り付け後の画像は以下になります。

チューナーのおかげでハの字に固定するなどはうまく出来たと思います。それよりもブレーキシューのリムへの接触面が適切な位置になるように調整するのが大変でした。実際どれぐらい神経質にしないといけないかはわかりませんが気になり出すととまらない感じで微調整を行いました。。

船つきブレーキシューに交換してみて

実際にブレーキ性能があがったかを確認するためにひとっ走り行ってきたのですが効果はありそうです。Tiagraの船なしブレーキシューと比べて体感ですが、制動力は結構上がったなという印象です。
ブレーキ周りの変更なので何かあっても責任は取れないですがTiagraのブレーキ性能に不安を覚えている人は変えてみる価値があるかと思います。

ブレーキシューだけでなくブレーキキャリパーを105以上に変えて違いを確認したいところですがそれはまた余裕ができたらやります。

初めてのチェーンクリーニングと注油

最近ロードバイクを買いました。初心者なので何をやっても楽しいです。
ママチャリに乗ってた時代はチェーンクリーニングなんて考えたことも無かったですが、先日初めてチェーンクリーニングを行いました。

チェーンクリーニング

クリーナーはググると結構いろんなブログでおすすめされてるPARKTOOLのサイクロンを購入して使いました。店舗で購入しましたが店員さんにもおすすめされました。

PARKTOOL(パークツール) サイクロン CM-5 YD-1297

PARKTOOL(パークツール) サイクロン CM-5 YD-1297

洗浄液はなるしまフレンドでおすすめされたコチラ

FINISH LINE(フィニッシュライン) エコテック2 マルチディグリーザ- 600ml アルミボトル TOS08002

FINISH LINE(フィニッシュライン) エコテック2 マルチディグリーザ- 600ml アルミボトル TOS08002

クリーナーを使ってみた感想としては(やったことないけど)灯油などで洗浄するよりは色々手間もかからなくて楽だと思います。2回すでにチェーンクリーニングをしていますが、2回目に洗浄液を多めに入れたら洗浄中に漏れてしまいました。目安線があるのでその通りに入れるのがベターそうです。(室内でやるなら新聞紙は必須です)

洗浄前の写真を撮っていなかったので、比較画像をお見せできないのが悔やまれますが、チェーン自体も新しかったこともあってピカピカになりました。


洗浄液がチェーンにのってる状態でギアチェンジしつつギア全体に洗浄液を染み渡らせてついでにギアのお掃除もしましたがまだまだ慣れておらずリア、フロントともになかなか大変でした。。
チェーンがきれいになったらウェスでしっかりと拭いてクリーニングは終了です。

注油

チェーンクリーニングが終わったら次は注油です。オイルも店員さんに相談して良さそうなのを紹介してもらいました。自分は通勤用途でロードバイクに乗っていて、たまに会社の同僚とサイクリングに行ったりします。
自分としては、そこまで頻繁にメンテナンスしなくてよくてかつ汚れにくいものを希望しました。

店員さんには次のオイルをおすすめされました。

FINISH LINE(フィニッシュライン) セラミック ウエット ルーブ 60ml プラボトル TOS06601

FINISH LINE(フィニッシュライン) セラミック ウエット ルーブ 60ml プラボトル TOS06601

オイル自体は思ったよりさらさらしていてこんなもんなんだなぁという印象でした。臭いも自分は気になりませんでした。
オイルはリンク部分に一滴づつ垂らしていきました。最初は面倒そうだなという印象でしたがやってみるとそうでも無かったです。
全部のリンクに垂らし終えたら、ギアにも染み渡らせられるようにギアチェンジしつつクランクを回しました。全体に行き渡ったかなと思ったら後はチェーンから余分な油の拭き取りを行いました。あまりオイルは残す必要は無いそうなので全体的に大丈夫かな?と思うくらい(表面がかろうじて濡れてるなー程度)わりとしっかり拭き取りました。

チェーンクリーニング&注油を終えて

初めて注油した日を含め、3日ぐらい天候が良くなくてすぐには走り出せなかったので、3日ほどそのまま室内に保存していました。
3日目ぐらいに気づいたのですがオイルが床に垂れていました。自分は縦置きしているので垂れたオイルは一カ所に集中していましたが拭き取りが甘かったのか?など色々と改善の余地はありそうです。

初めて注油した後に、室内でしばらく保存するなら新聞紙を下に敷いておくのをおすすめします。

今後の反省として

  • オイルをさし過ぎない様に注意する(もしかしたらギアから垂れたのかも)
  • メンテナンス時の画像はおもしろいのでちゃんと撮影しておく

このあたりでしょうか。

チェーンクリーニングでピカピカになったチェーンを見るのは精神衛生上非常に良いです。注油後はなんとなくですが音が静かになった&こぎやすくなった気がします(このあたりの違いはまだ全然わからないです)

メンテナンスに使ったスタンドはコチラ

MINOURA(ミノウラ) ディスプレイスタンド DS-30BLT

MINOURA(ミノウラ) ディスプレイスタンド DS-30BLT

室内保管用の縦置きスタンドはコチラを使ってます。

MINOURA(ミノウラ) ディスプレイスタンド Tancho E'sse ホワイト DS-2100

MINOURA(ミノウラ) ディスプレイスタンド Tancho E'sse ホワイト DS-2100

iOS6のsafariでimgのsrc要素が空の時に自身を読み込んでた問題が修正されてた

最近知ったのですが、iOS5のsafariMacOS X LionのSafari(5.1.7)だと

<img src="" />

のように、srcの中身が空の状態だと自信のurlを読み込もうとする挙動がありました。
a.htmlに上記の様にsrc要素が空のimgタグを書くとchromesafariで次のように挙動が異なります。

chromeだと、a.htmlしか読み込まれないです。

safari(5.1.7)だとa.htmlが2回読み込まれます。

スマートフォンだと初回の画面表示に不要な画像はdataスキームを使って代替画像を表示しておいて必要なタイミングでロードする。といったパフォーマンスチューニングが行われたりしていると思いますが間違ってsrcを空のままにしておくとiPhoneなどのアクセス数が水増しされてしまったりするので注意が必要です。

また、iOS6のsafariで確認しましたがこの問題は修正されていました。
ちょっと怪しいのでもう少し確認してから追記します。

node.js で mixi Graph API を扱うライブラリの紹介

だいぶ前にnode.jsで遊んでいて、そうだ mixiAPIをnode.jsから叩いてみよう!と思って書いたライブラリを思い出したのでそれの紹介。

利用の流れ

READMEそのままだけど。

var api = require('./mixi/graphapi/client');
var client = api.createClient({
    'consumerKey' : 'your consumer key.',
    'secret'            : 'your consumer secret. ',
    'redirectUri'     : 'you setting redirect uri',
    'device'            : 'pc'
});

// 認可画面のURLを取得する
var authPageURL = client.authPageUrl(['r_profile', 'r_profile_status']);

// アクセストークンとリフレッシュトークンを取得
var tokens;
client.getTokens('authCode', function (response) {tokens = response;});

// アクセストークンを再発行
client.refresh(tokens.refresh_token, function (response) {
    tokens = response;
});

// APIへアクセスする
var result;
client.request({
    'accessToken' : tokens.access_token,
     'target'      : 'people/@me/@friends'
 }, function (response) {
     result = response;
});

このコードと、サンプルコードを見れば認証認可まわりは何となくわかると思うので他に必要なAPIを使用したい場合は公式のドキュメントを参考にする。

実際にサンプルを実行して動きを確認する

node.jsをインストール

まあ、OS毎のインストール方法はいろんなところで紹介されていると思うのでここでは簡単に、githubから落として来る方法を紹介

$ git clone https://github.com/joyent/node.git node && cd node
$ ./configure
$ make && make install
$ ./node
> console.log('hello node.');

動いたらOK

mixi Graph APIを利用するサービスを登録する

↓ここの新規サービス追加から。
https://sap.mixi.jp/connect_consumer.pl

リダイレクトURLはとりあえず動くものを見るために http://localhost:8080/callback を指定。

確認→登録まですると確認メールが送信されてくるので、記載されているURLからmixiに移動。
あとは、管理サービス一覧に移動するとさっき登録したサービスが増えているのでタイトルをクリックして詳細へ。

クレデンシャル情報にある Consumer KeyとConsumer SecretがAPIを利用する際に必要になる。

mixi Graph API のクライアントライブラリを使ったサンプルを実行

まずgit cloneする。

$ git clone https://github.com/hayaishi/mixi-graph-api-node.js.git graphapi && cd graphapi

あとは、sampleディレクトリに移動してサンプルコード(friend_list.js)を開いて先ほど発行されたConsumer KeyとConsumer Secretを埋め込む。

埋め込んだら、サンプルコードを実行する。
サンプルコードはport番号8080で起動するのですでに動かしているものがある場合は注意。

$ ../../node/node friend_list.js

無事に起動すればOK
http://localhost:8080にアクセスすると次の画面が表示される。

authって書いてあるリンクを押すとmixiに遷移するので、「同意する」を押す。

無事にマイミクの一覧が表示されていたら成功。
ちなみにマイミクは親密度の順にソートできるオプションがあったのでそれを使って並べ替えてみた。

画面に表示させているニックネームがエスケープされていなかったりするのはとりあえず最小の環境で動くサンプルを用意したかったから。そのうち気が向いたら修正する。