TTTAttributedLabelに”containslinkAtPoint"メソッドが追加されていたのでメモ

TTTAttributedLabelに”containslinkAtPoint"メソッドが去年の11月に追加されていたようなのでメモ。


Squashed commit of the following: · 3515373 · TTTAttributedLabel/TTTAttributedLabel · GitHub


このメソッドでなにができるようになったかというと、リンクを有効にしたTTTAttributedLabelにおいて、"リンクを選択したとき”と”リンク以外のTTTAttributedLabelにおいてもジェスチャイベントを拾う”の処理分けが可能になったことだ。

具体的なコードとしては、

protocol tableViewCellDelegate {
    func tableViewCellLinkTapped(linkurl :String)
    func tableViewCellTapped(cell :TableViewCell)
}

class TableViewCell: UITableViewCell,
    TTTAttributedLabelDelegate,
    UIGestureRecognizerDelegate {

    @IBOutlet weak var label: TTTAttributedLabel!
    
    var delegate :tableViewCellDelegate?
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
        self.label.delegate = self
        self.label.enabledTextCheckingTypes = NSTextCheckingType.Link.rawValue
        self.label.linkAttributes = [kCTForegroundColorAttributeName : UIColor.greenColor()]
        self.label.activeLinkAttributes = [kCTForegroundColorAttributeName : UIColor.orangeColor()]
        
        setupGesture()
    }

    //一部省略
    
    private func setupGesture() {
        let selector : Selector = "pressed:"
        let tapGesture = UITapGestureRecognizer(target: self, action: selector)
        tapGesture.delegate = self
        self.addGestureRecognizer(tapGesture)
    }
    
    func pressed(gestureRecognizer: UITapGestureRecognizer) {
        delegate?.tableViewCellTapped(self)
    }
    
    // MARK: - TTTAttributedLabelDelegate
    
    func attributedLabel(label: TTTAttributedLabel!, didSelectLinkWithURL url: NSURL!) {
        self.delegate?.tableViewCellLinkTapped(url.absoluteString!)
    }
    
    // MARK: - UIGestureRecognizerDelegate
    
    override func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
        return !self.label.containslinkAtPoint(touch.locationInView(self.label))
    }
    
}

こんな感じで書けば、リンクをタップしたときは"tableViewCellLinkTapped"のデリゲートメソッドが実行され、リンク以外の部分(TTTAttributedLabelを含む)をタップしたときには"tableViewCellTapped"のデリゲートメソッドが呼ばれるようにすることができる。

あんま使うことないかもなのですが、業務でちょっと必要になりそうなのでとりあえず良かったです。

一応動くものは以下に。


ushisantoasobu/ContainslinkAtPoint-sample · GitHub

iOSのカスタム画面遷移にFacebookのPopを使ってみた

Tumblrのリアクションを押したときに表示されるビヨ〜ンっとしたモーダルっぽいものをつくりたく(下動画)、iOSのカスタム画面遷移にFacebookPopを使ってみた。


Tumblr's modal from reaction - YouTube


つくったサンプルはこちら。


ushisantoasobu/ModalWithPop · GitHub


f:id:ushisantoasobu:20150131135115g:plain


まあまあそれぽいはず。
Tumblrのと同じように、押したボタンの中心点からモーダルが出てきて、その中心点へと閉じていくみたいにしている(上のgifだと10fpsなのでわかりづらいかも)。
UIViewControllerAnimatedTransitioningプロトコルを実装しているほうにそのCGPointを渡しているだけ。

ちなみにはじめてgifアニメーションをシミュレータからキャプチャしたけれど、色々方法はあるのかもですが、自分は下の記事を参考に簡単に作成できた。ありがとうございます。


Xcode - Macを使ってiPhoneアプリのスクリーンショットを撮る - Qiita

iOS開発でデザイナー/エンジニアのデザイン情報のやりとりにSketchを使ってみた

特にナウい方法ではないんだろうけど、現在携わっているiOSの開発では、デザイナー/エンジニア間でのデザイン情報(UIパーツをどう置くかみたいな)のやりとりは”Sketch”を用いることにした。

Bohemian Coding - Sketch 3

一連の流れ

  1. デザイナーがSketchでデザインする
  2. Sketchのデザインファイルを随時(Dropboxの共有フォルダとかに)アップする
  3. エンジニアがそのファイルをSketch上で開いてそれを参考に、IB上で作業したりプログラミングしていく


「Sketch上で開いてそれを参考に」というのは、例えばUIパーツを選択するとそのx座標、y座標、width、heightが表示されるのでそれをみたり(下画像の枠部分)、あとマージンに関しては、今時のデザインツールだと当たり前ぽいけれど、Sketchの場合だとあるパーツを選択した状態でAltキーを押すと以下の画像のようにマージンを表示してくれる(下画像の矢印部分)。

f:id:ushisantoasobu:20150119002244p:plain


デザイナーの負担を減らす目的がメイン

よくデザイン情報のやりとりといったら以下のような画像でやりとりすることもあったと思う。

f:id:ushisantoasobu:20150119002620p:plain

要は、デザイナーがデザインしたあとに、追加でそのデザイン情報のドキュメントをエンジニアのためにつくるというもの。
このある種追加的な作業(もちろんデザイナーがつくる)の負担を減らすという目的も、今回のSketch使ってみようという流れに至った経緯の1つだ(よって提案したのはデザイナーの偉い人)

課題

  • Sketchは現在ほぼ1万円もするので会社から稟議通るか?
  • Sketchとかを入れることに抵抗があるエンジニアもいる??自分は元々Flashとかをやっていたこともあって全く抵抗はないけど、嫌がるエンジニアもいるかも
  • 少し動作が重い。現在のプロジェクトでは、アプリの画面全部(比較的画面数は少ない)を1つのファイルにまとめているんだけど(アプリ全体を俯瞰できる)、レンダリングが重いときがあって少しストレスがある


あと余談だけど、改めてデザイナー/エンジニア間でのデザイン情報のやりとりで難しいなと思ったのも忘れないよう2つ書いておく

ラベルまわり

UILabelって標準だと縦方向の上寄せってないので、普通にやったらUILabelのframeの上と文字自体の上の線って合わないと思う(自分が知らないだけかも)。でもSketchでデザイナーからもらうデータだとそこがピッタし合ったものがきて困るということがある(下画像)。で、ここに関しては今回は時間なく目で調整でやってしまっている。

f:id:ushisantoasobu:20150119005202p:plain

AutoLayout

AutoLayoutにおけるデザイン情報の使え方でいまでも疑問なのが、たとえばあるアイコンがあったとき、それはどの解像度でみたときでもwidth/heightは固定なのか、それとも縦横比を保ったまま解像度によって拡大/縮小するのか、の区別の伝え方だ。
自分はすぐ側にデザイナーがいるので口頭でサクッと聞けるような環境だからいいのだけど、リモートだったり外注するときなんかはどうやって伝えるのかなーというのが今も疑問。まあ「何となくわかるでしょ」みたいなものなんだけど、それを全ての人に適用していいかは微妙


トータル何かアドバイスいただけることあればいただけたら幸いです。

AutoLayoutがオンの状態でのアニメーション処理の書き方

AutoLayoutをオンにした状態だと、以下のようなアニメーションの処理が動かないというのを知らなかった 汗

[UIView animateWithDuration:1.0f
                      delay:0.0f
                    options:UIViewAnimationOptionCurveEaseOut
                 animations:^{
                     _btnTest.y = 200;
                 }
                 completion:^(BOOL finished) {
                     //
                 }];

まあ理屈から考えるとその通りであって、で今後は(まあこれまでもそうかもだけど)iPhone6 plusみたいなものも出てきたのでAutoLayoutはこれまで以上に必須だと思うので、AutoLayoutにおけるアニメーションの書き方を勉強しておく。

参考サイト:
ios - AutoLayout, Constraints and Animation - Stack Overflow

どんなのやりたいかというと、たとえばTumblr

f:id:ushisantoasobu:20140921014548p:plain

のタブ中央の鉛筆アイコンをタップしたときに

f:id:ushisantoasobu:20140921014634p:plain

の6つのメニューボタンが下から飛び出るようなアニメーションででてくるんだけど、それをAutoLayoutがオンの状態でどうやってやるか。

サンプルコードはgithubに。

ushisantoasobu/TestAnimationInAutoLayout · GitHub

ポイントとしては以下のところ。

//1. 制約を除外してあげる
[self.view removeConstraints:@[_constraintHoge]];

//2. アニメーション先の制約を設定する(ここでいうとbottomから-1000)
_constraintHoge = [NSLayoutConstraint constraintWithItem:self.view
                                               attribute:NSLayoutAttributeBottom
                                               relatedBy:0
                                                  toItem:_btnHoge
                                               attribute:NSLayoutAttributeBottom
                                              multiplier:1
                                                constant:-1000];

//3. 2で設定した制約を追加してあげる
[self.view addConstraint:_constraintHoge];

//4. 以下のような書き方でアニメーションされる(layoutIfNeededがミソ)
[UIView animateWithDuration:1.0f delay:0.0 options:0 animations:^{
    [self.view layoutIfNeeded];
} completion:^(BOOL finished) {
    //
}];

書きながらいろいろサイトみてたらいくらでも記事あったな、、、

【iOS8】UIAlertControllerで文字がなぜか太くなる?

メモ書き程度に。

UIAlertViewはiOS8でもとりあえず動いているようですが、deprecatedになったためいずれ痛い目にあわないよう、バージョンで分岐してiOS8未満でなければUIAlertController使うように対応しました。

で、対応後の不具合報告に「文字が太くなってる」みたいなものがあがってきてなんのことやらで調べてみた。

結論からいうと、"title"にnilを渡すと"message"に指定したものがtitleとして表示されてしまうという現象によるものだった。

以下キャプチャとコード。

titleにnilを指定したとき

f:id:ushisantoasobu:20140920181111j:plain

UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
                                                               message:@"message"
                                                        preferredStyle:UIAlertControllerStyleAlert];

[alert addAction:[UIAlertAction actionWithTitle:@"close"
                                         style:UIAlertActionStyleCancel
                                        handler:nil]];

[self presentViewController:alert animated:YES completion:nil];

titleに@""を指定したとき

で、解決策はtitleに@""を渡してあげれば正しく表示されるっぽい。

f:id:ushisantoasobu:20140920181122j:plain

UIAlertController *alert = [UIAlertController alertControllerWithTitle:@""
                                                               message:@"message"
                                                        preferredStyle:UIAlertControllerStyleAlert];

[alert addAction:[UIAlertAction actionWithTitle:@"close"
                                         style:UIAlertActionStyleCancel
                                        handler:nil]];

[self presentViewController:alert animated:YES completion:nil];

とりあえずこの問題はOKそう。


あとUIAlertViewはクラスメソッドで表示していたから良かったけど、UIAlertControllerの引数に指定しなくちゃいけないので、UIViewControllerを継承していないユーティリティクラスだったりマネージャクラスから呼び出すときにはどうするのがいいのかがわかっていない・・・。

とりあえず以下のようなコードで最上位のUIViewControllerを取得して・・・みたいなことしてるけど、

UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
while (topController.presentedViewController) {
    topController = topController.presentedViewController;
}

キーボードが表示されているときにはクラッシュしたしダメなんだろうなぁ。これってどうするのがベストプラクティスなんだろう。(設計思想を把握できていない)

【iOS8】WKWebViewに対応したときの覚え書き

業務でWebViewの部分をWKWebViewにも対応するラッパクラス(内部でOSのバージョン毎にWKWebViewとUIWebViewを切り替えるもの)をつくったのでそのメモ。
といっても特に大したことはしていない汗

(注:XCode6 GM Seed)

WebKit.frameworkをリンクする

Build PhasesのLink Binary with LibrariesでWebKit.frameworkをリンクする

xibからは置けないぽい

UIWebViewはxib上で配置できたけど、WKWebViewはそもそも見当たらなかったのでコードで書いた

delegateをセットする

delegateが2つになった

_webView.navigationDelegate = self;
_webView.UIDelegate = self;

認証まわり

ハマりポイント1で、認証まわりがそのままだと動かなくて、下記のnavigationDelegateのメソッドを書いてあげないと動かなかった

- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
    //空のままでok
}

参考URL
ios - WKWebView and authentication - Stack Overflow

delegateまわり

フック

ハマりポイント2で、webから処理をフックするところに書いてた処理は

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    //
}

から

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    //
}

あとその中で

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
{
    decisionHandler(WKNavigationActionPolicyAllow); //これいれないとcrashする
}

これをいれないと以下のようなエラーでクラッシュするので注意

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Completion handler passed to -[NTENoteDetailWebViewController webView:decidePolicyForNavigationAction:decisionHandler:] was not called'

というかUIWebViewのほうで返り値でBOOLを返していたものに該当するもの(という認識であってるはず)

- (void)webViewDidStartLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

から

- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

【iOS8】presentViewControllerで透過viewを表示する

iOS8以前(いつからかは知らない)はpresentViewControllerで透過viewを表示するには以下のようなコードを書いていたと思う。

UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
rootViewController.modalPresentationStyle = UIModalPresentationCurrentContext;

以下参考サイト。

presentViewControllerで元の画面も見えるようにするのがうまくいかない - uokumuraの日記

UIViewのbackgoundcolorをclearcolorにしたのに背景が透明にならない : iPhoneアプリ開発・Objective-C勉強まとめ


ただし現時点(2014/09/11)でXCode6 GM Seedでシミュレータ上で動かしる限りだと正しく動いていない模様(真っ黒になってしまう)。

stackoverflowにも同じようなことありました。

ios8 - How to present a semi-transparent (half-cut) viewcontroller in iOS 8 - Stack Overflow

ただしここに書いてあるサンプルがよくわからずどうしようかと思っていたところ、もくもくiOS勉強会@Rettyで"WWDCのビデオにそれぽいものあったよ"とアドバイスいただいたので調べてみた。


WWDC 2014 Session Videos - Apple Developer
"A Look Inside Presentation Controllers"というセッションですね。

で、UIPresentationControllerあたりの新しいAPI使わなくちゃいけないかななんて身構えていたけど、以下のような感じであっさりできた。

HogeViewController *vc = [[HogeViewController alloc] init];
vc.modalPresentationStyle = UIModalPresentationOverFullScreen;
[self presentViewController:vc animated:NO completion:nil];

とはいえUIPresentationControllerまわりもiOS8では重要になってきそうなのでキャッチアップできるようにはしよう。