UILabel の行数を可変にする

Storyboard で UILabel を選択し、 Attributes inspector から Lines を 0 にするだけです。

f:id:komaji504:20160731171427p:plain

この値が UILabel の行数で、デフォルトの 1 のままだと 1 行固定となり、 テキストが複数行にわたる場合は、 ... と省略されて表示されるようになります。

今回のように 0 にすると、行数にかかわらず複数行で表示することが可能となります。


ちなみに、UILabel の height が固定になっていると、その height で表示できる行数だけしか表示されず、以降のテキストは ... と省略されてしまいます。

そのため、この設定とは別に高さが可変になるような Auto Layout も設定しなければならないと思うので注意が必要です。

Objective-C の Category と Swift の Extension

既存のクラスを拡張したいときに、 Objective-C であれば Category 、 Swift であれば Extension により実装していきます。 それぞれの実装方法の例です。

Objective-C

NSDate を拡張して sampleMethod というメソッドを実装したい場合、 Objective-C では以下のように記述します。

// NSDate+SampleProject.h

#import <Foundation/Foundation.h>

@interface NSDate (SampleProject)

- (void)sampleMethod;

@end
// NSDate+SampleProject.m

@implementation

- (void)sampleMethod
{
    // hoge
}

@end

Swift

上記と同じメソッドを Swift の Extension で記述すると以下のようになります。

// NSDate+SampleProject.swift

import Foundation

extension NSDate {

    func sampleMethod() {
        // hoge
    }

}

Objective-C から Swift の Extension を利用する

ちなみに、 Objective-C から Swift の Extension を利用したいときは、 Objective-C ファイルで #import "sampleProject-Swift.h" を記述しておくだけで大丈夫です。

Swift でページングを実装する

UICollectionView を使って一覧画面を作成していて、画面を一番下までスクロールしたら次ページをロードして表示する、というようにページング処理をさせたいときには、 UICollectionReusableView と UIActivityIndicatorView を使うと良いそうです。

これらを使ってページング処理をさせるときには、 UICollectionReusableView でフッターとなる View を作成し、 UIActivityIndicatorView でローディングインジケータを作成することになります。

ページング処理の大まかな流れは以下の通りです。

  1. 画面を一番下までスクロールするとフッターが表示される。
  2. フッターが表示されたらインジケータを表示する。ぐるぐるさせる。
  3. 次ページを読み込む処理を実行する。
  4. 次ページを読み込む処理が完了したらぐるぐるやめる。インジータを隠す。

フッターが表示されたら という部分は、 collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) を使うことで実装できます。

具体的には以下のように記述します。

func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView {
    if kind == UICollectionElementKindSectionFooter {
        let reusableView = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: "UICollectionReusableView に設定した Idenfier", forIndexPath: indexPath)
        // インジケータをぐるぐるさせる処理
        // 次ページを読み込む処理

        return reusableView
    }

    return UICollectionReusableView()
}

Alamofire で APIClient を書いてみた

APIClient はどういう設計が良いのかあとずっと悩んでいたのですが、なんとなく形になったような気がしたので書いてみます。
Swift は型安全な言語なのですが、 API から取得される値は様々な型を取り得るので、それを汎用的に扱えるようにするためにはどうしたら良いなかあ、型の扱い方が難しいなあと感じました。

初心者なので鵜呑みにはしないでくださいな。

APIClient

サーバ側で 404 等のエラーが 発生した際は ["error": "エラーメッセージ"] というレスポンスボディが返ってくることを想定しています。

Alamofire では、リクエストメソッドを Alamofire.Method で指定する必要があり、APIClient を使う Class を記述したファイル内で、毎回 import Alamofire しなければならなそうで嫌だなあと思ったので、 Enum を使って Alamofire.Method に変換できるようにしました。

サーバ側でエラーが発生した際には、リクエスト自体には成功しているので response.result.isSuccess() が true となります。
そのため、 response.result.isSuccess() が false となるのはリクエストそのものに失敗しているときだと思うのですが、アプリ側では、サーバ側のエラーとリクエストそのものが失敗したときのエラーを同じエラーとして扱いたいなあと思ったので、 APIResponse という Struct を用意して、 response の値を APIResponse に変換するようにしてみました。

import Alamofire

class APIClient {
    static let host = "http://localhost:3000/"

    static func httpRequest(method: RequestMethod, endpoint: String, parameters: [String: AnyObject]?, handler: (APIResponse) -> Void) {
        Alamofire.request(method.toAlamofile(), host + endpoint, parameters: parameters)
            .responseJSON { response in
                if response.result.isSuccess {
                    handler(APIResponse(code: response.response!.statusCode, value: response.result.value!))
                } else {
                    handler(APIResponse(value: ["error": "通信に失敗しました"]))
                }
            }
    }
}

enum RequestMethod {
    case GET
    case POST
    case PUT
    case DELETE

    func toAlamofile() -> Alamofire.Method {
        switch self {
        case .GET:
            return .GET
        case .POST:
            return .POST
        case .PUT:
            return .PUT
        case .DELETE:
            return .DELETE
        }
    }
}

struct APIResponse {
    var status: APIStatus
    var value: AnyObject?
    var errorMessage: String?

    enum APIStatus {
        case Success
        case Failure

        func isSuccess() -> Bool {
            switch self {
            case .Success:
                return true
            case .Failure:
                return false
            }
        }
    }
    
    init(code: Int = 0, value: AnyObject) {
        switch code {
        case 200...299:
            status = APIStatus.Success
            self.value = value
        default:
            status = APIStatus.Failure
            self.errorMessage = value["error"] as? String
        }
    }
}

使うとき

リクエスト先となるエンドポイントと、リクエストパラメータを用意します。 response には APIResponse のインスタンスが入ってきます。 response.status.isSuccess() でリクエストに成功したかを判断し、成功 or 失敗 の処理を分けて記述します。

func test() {
    let endpoint = "accounts" // エンドポイント
    let parameters = ["page": 1] // リクエストパラメータ
    APIClient.httpRequest(.GET, endpoint: endpoint, parameters: parameters) { response in
        if response.status.isSuccess() {
            print(response.value!) // 通信に成功したときの処理
        } else {
            print(response.errorMessage!)  // 通信に失敗したときの処理
        }
    }
}

UICollectionViewCell をタップしたときにセルの色を変える

UICollectionView のセルをタップしたときに何のリアクションもないと寂しいので、セルの色を変える方法を調べました。

UICollectionViewDelegate を継承した Class に以下のメソッドを追加することで、セルをタップしている間は色を変え、離したら元の色に戻るという挙動になります。

    func collectionView(collectionView: UICollectionView, didHighlightItemAtIndexPath indexPath: NSIndexPath) {
        let cell = collectionView.cellForItemAtIndexPath(indexPath)!
        cell.backgroundColor = UIColor.clearColor() // タップしているときの色にする
    }

    func collectionView(collectionView: UICollectionView, didUnhighlightItemAtIndexPath indexPath: NSIndexPath) {
        let cell = collectionView.cellForItemAtIndexPath(indexPath)!
        cell.backgroundColor = UIColor.darkGrayColor()  // 元の色にする
    }

MVP と MVVM

MVP と MVVM の違いがよくわからなかったのですが、 調べているうちに違いがなんとなく分かった気がしたので、自分の理解での処理の流れ書いてみます。

MVP

2 種類ある

Passive View

  1. View で入力がある。
  2. View が入力があったことを知る。
  3. 受け取った入力に対する処理を Presenter へ委譲する。
  4. Presenter が処理を行い、 Model を操作する。
  5. Model の値に変更があれば、 その結果を View に渡す。

Supervising Controller

  1. View で入力がある。
  2. View が入力があったことを知る。
  3. 受け取った入力に対する処理を Presenter へ委譲する。
  4. Presenter が処理を行い、 Model を操作する。
  5. Model の値に変更があれば、その変更が View に反映される。

MVVC

  1. View で入力がある。
  2. View が入力があったことを知る。
  3. 受け取った入力に対する処理を ViewModel へ委譲する。
  4. ViewModel が処理を行い、 Model を操作する。
  5. ViewModel で Model の状態を保持しておき、変更があれば View に反映される。

違いは、 5 番目の処理の、 Model の変更をどうやって View へ知らせるかという部分だけ、というように理解しております。

Swift で早期リターン

早期リターンを使うと、コードが読みやすくなります。

例えば、 nil をとりうる値 hoge があって、 nil のときは処理を終了させたいというメソッドの場合、Ruby ではこんな感じになります。

# 早期リターンなし
def test
  if hoge 
    # hoge が nil でないときの処理
  else
    return
  end
end

# 早期リターンありその 1
def test
  if hoge == nil
    return
  end

  # hoge が nil でないときの処理
end

# 早期リターンその 2
def test
  return if hoge == nil

  # hoge が nil でないときの処理
end

hogenil のときは、 return if hoge == nil 以降の処理が実行されないということがすぐに分かります。

Swift の場合、 こんな感じでかけるかと思います。

// 早期リターンなし
func test() -> Void {
  if hoge != nil {
    // hoge が nil でないときの処理
  } else {
    // hoge が nil のときの処理
  }
}

// 早期リターンあり
func test() -> Void {
  if hoge == nil {
    return
  }

  // hoge が nil でないときの処理
}

Swift2 からは guard というのがあり、これを使うこともできそうです。

func test() -> Void {
  guard hoge != nil {
    return
  }

  // hoge が nil でないときの処理
}

guard を使うことで、早期リターンをしているんだなということがわかりやすくなりそうです。

また、 guard ではアンラップもしてくれるようなので、 hoge の値を使いたいというときにも便利そうです。

// guard なしその 1
func test() -> Void {
  if let unwrappedHoge = hoge {
    print(unwrappedHoge)
  } else {
    return
  }
}

// guard なしその 2
func test() -> Void {
  if hoge == nil {
    return
  }

  print(hoge!)
}
  
// guard あり
func test() -> Void {
  guard let unwrappedHoge = hoge else {
    return
  }

  print(unwrappedHoge)
}