UIKit - TableViewCellの高さについて

しばしブランクが合ったので、UITableViewCellの高さの設定が以前とちがっていました。
忘れないようにメモる。

UITableViewCellの高さが一定ならば、UITableViewのrowHeightプロパティに入れるだけで良い。

let tableView = UITableView()
tableView.rowHeight = 80 // 高さが一定値

UITableViewDelegateのheightForRowAtIndexPathに入れると、
セルの数だけ呼び出されるので無駄が多いとのこと。

qiita.com

UIKit - オリジナル画面遷移を作ってみる(1)

とっても今更だが、オリジナルの画面遷移ができる方法をしっておきたかったので、
テストがてら簡単なものをつくって自分なりにまとめる。

今回はモーダルで開くときの画面遷移をオリジナルにしたが、
同様のやり方でUINavigationControllerのPush/PopやUITabControllerの切り替えにも対応できる。

必要なもの

・MainViewController(遷移元のViewController)
・ModalViewController(遷移先のViewController)
・AnimationController(遷移のアニメーション処理を受け持つクラス。UIViewControllerAnimatedTransitioningを実装)

遷移元と遷移先のViewControllerは当たり前として、
UIViewControllerAnimatedTransitioningプロトコルを実装したクラス一つ作り、 遷移元にUIViewControllerTransitioningDelegateを実測してやれば、
簡単にカスタム遷移を実現することができます。UIKit便利ですね。

アニメーションコントローラーの作成

遷移アニメーション処理を受け持つクラスを作成します。
ここでは、遷移元ビューコントローラーでpresentationViewControllerした時に、
登場時(presenting==true)のときには、右から新たなビューコントローラーが出てくるように、
退去時(presenting==false)のときには、右に表示ビューコントローラーが消えていくようにアニメーションさせています。

class SlideAnimationController: NSObject, UIViewControllerAnimatedTransitioning{
    
    let presenting: Bool
    
    init(presenting: Bool) {
        self.presenting = presenting
        super.init()
    }
    
    // アニメーションにかける時間
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return NSTimeInterval(0.6)
    }
    
    // アニメーション
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        
        guard let containerView = transitionContext.containerView(),
            fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey),
            toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
            else {
            return
        }
        
        let fromView = fromVC.view
        let toView = toVC.view
        
        let inframe = transitionContext.initialFrameForViewController(fromVC)
        let outframe = CGRectOffset(inframe, CGRectGetWidth(inframe), 0)
        
        if presenting {
            
            containerView.addSubview(toView)

            toView.frame = outframe
            
            UIView.animateWithDuration(self.transitionDuration(transitionContext), animations: { 
                toView.frame = inframe
                }, completion: { (finished) in
                    transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
            })
        }
        else {
            
            containerView.insertSubview(toView, belowSubview: fromView)
            
            toView.frame = inframe
            
            UIView.animateWithDuration(self.transitionDuration(transitionContext), animations: { 
                fromView.frame = outframe
                }, completion: { (finished) in
                    transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
            })
        }
    }
}

遷移元ビューコントローラーにUIViewControllerTransitioningDelegateを実装します

// MainViewController

func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return SlideAnimationController(presenting: true)
    }
    
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return SlideAnimationController(presenting: false)
}

あとは遷移するだけ

let modalVC = ModalViewController()
modalVC.transitioningDelegate = self
modalVC.modalPresentationStyle = .FullScreen // .FullScreen or .Customを設定する
presentViewController(modalVC, animated: true, completion: nil)

案外簡単にできたので、次はユーザー操作に合わせて画面遷移させてみたいと思います。 コードはこちら↓

github.com

Swift - 自作クラスをEquatableプロトコルに準拠させる

自作クラスである、以下の様なMemberクラスがあったとします。

class Member {
   let id: String
   let name: String

   init(_ id:String, _ name: String) {
     self.id = id
     self.name = name
   }

}

同一人物かどうかを判断するために、
以下のように「==」や「!=」での比較を行いたいと思いました。

let memberA = Member("01", "Taro")
let memberB = Member("01", "Masami")

if memberA == memberB {
    print("同じ人")
}
else {
    print("違う人")
}

等値演算子に利用するには、
自作のMemberクラスをEqutableプロトコルに準拠させなければなりません。
ということで、以下のように書くと上記の書き方が叶います。
こういうのを「演算子オーバーロード」と言うらしい。

func == (left : Member, right : Member) -> Bool {
   return left.id == right.id
}
extension Member: Equatable {}


class Member {
   let id: String
   let name: String

   init(_ id:String, _ name: String) {
     self.id = id
     self.name = name
   }
}

この例ではイマイチですが、リンク先のように日付先などを比べるにあたって、
コードが見づらくなる時などには良いかもしれませんね。

参考: http://dev.classmethod.jp/smartphone/strideablensdate/

UIKit - AutoLayoutを利用した高さ可変のTableHeaderViewをつくる

表題通りUITableViewのHeaderに高さ可変のHeaderViewをつくります。

TableHeaderViewの中にラベルか何かを表示させていて、
その文字列をレスポンスから引っ張ってきたりして設定するために、
高さを変えなければならない。そんな人向けの記事です。

まずTableHeaderViewをつくりましょう。

当たり前ですが、xibでもコードオンリーでもいいのでHeaderViewをつくりましょう。
その際、下辺が大きさに応じて変化させることができるような制約(Constraint)をつけましょう。 下はかんたんな例。 カスタムビューの最下端にラベルのボトムとの制約をつけています。

class MyTableHeaderView: UIView {

    var label: UILabel! = {

    }()

   override func updateConstraints() {
        super.updateConstraints()
        
        labelDescription.bottomAnchor.constraintEqualToAnchor(bottomView.bottomAnchor, constant: -16).active = true
    } 
}

UITableViewに設定しよう

以下の様な感じで設定してください。 ぼくは以下の処理をself.view.frameの大きさが決まった、viewDidLayoutSubviewsに書いています。
viewDidLayoutSubViewsは複数回呼ばれるので注意。

let headerView = MyTableHeaderView()
headerView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: デフォルトの高さ)

self.tableView.tableHeaderView = MyTableHeaderView()

高さ可変の処理

たとえば、ヘッダーに大量の文字列を入れなければならないとしましょう。
そのタイミングで以下のような処理を書きます。

if let tableHeaderView = self.tableView.tableHeaderView as? MyTableHeaderView {
    // 大量のテキストを設定
    tableHeaderView.label.text = "大量のテキスト"

    // テキスト入れた状態でAutolayoutによる高さの算出
    tableHeaderView.setNeedsLayout()
    tableHeaderView.layoutIfNeeded()
    let size = tableHeaderView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
                
    // 高さの再設定
    tableHeaderView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)

    // 再代入
    self?.tableView.tableHeaderView = tableHeaderView
}

おそらくこの方法でできるはずです。ではでは

参考: iOS7・iOS8の処理分岐なし!UITableViewのCellの高さをAutolayoutで自動計算する方法 - 株式会社エウレカ

UIKit - topLayoutGuide, bottomLayoutGuideとは

実はあんまり分かっていなかったので確認。
だいぶはしょって説明すると、
topLayoutGuideは、ステータスバーやナビゲーションバーなどの上部のUIを考慮してレイアウトするもので、 bottomLayoutGuideは、タブバーなどの下部のUIを考慮してレイアウトするものですね。

下のように書くと、ナビゲーションバーやステータスバー、タブバーに
かぶらないないようにレイアウトしてくれます。

var btn: UIButton!
var label: UILabel!

override func updateViewConstraints() {
        
        btn.topAnchor.constraintEqualToAnchor(self.topLayoutGuide.bottomAnchor, constant: 0).active = true
        btn.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor, constant: 100).active = true
        
        label.bottomAnchor.constraintEqualToAnchor(self.bottomLayoutGuide.topAnchor, constant: 0).active = true
        label.leadingAnchor.constraintEqualToAnchor(self.view.leadingAnchor, constant: 100).active = true
        
        super.updateViewConstraints()
    }

Swift - didSetでgetter,setterがシンプルに

willSet, didSetをあまり使っていなかったが、使ってみたら便利だった話。

「currentIndexはgetできる」
「currentIndexはsetすると、その数字を内部で保持し、あるViewの色が変わる」
というようなものを、willSet, didSetを知らない間こんなふうに書いていました。

private var _currentIndex: Int = 0

var currentIndex: Int {
    get {
         return _currentIndex
    }
    set {
         _currentIndex = newValue
         view.backgroundColor = _currentIndex > 5 ? UIColor().redColor() : UIColor().blueColor()
    }
}

ただこの書き方だと、currentIndexの役割をもったプロパティが2つできてしまい、
なんか心がもやもやします(恋かな?)

したらば、下の書き方にすると非常にシンプルに書くことができます。

var currentIndex: Int {
    didSet {
         view.backgroundColor = currentIndex > 5 ? UIColor().redColor() : UIColor().blueColor()        
    }
}

willSetはまだ使う機会ないのですが、さぞや活躍してくれることでしょう。むふふ