UILabel の行数を可変にする
Storyboard で UILabel を選択し、 Attributes inspector から Lines を 0 にするだけです。
この値が 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 でローディングインジケータを作成することになります。
ページング処理の大まかな流れは以下の通りです。
- 画面を一番下までスクロールするとフッターが表示される。
- フッターが表示されたらインジケータを表示する。ぐるぐるさせる。
- 次ページを読み込む処理を実行する。
- 次ページを読み込む処理が完了したらぐるぐるやめる。インジータを隠す。
フッターが表示されたら という部分は、 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
- View で入力がある。
- View が入力があったことを知る。
- 受け取った入力に対する処理を Presenter へ委譲する。
- Presenter が処理を行い、 Model を操作する。
- Model の値に変更があれば、 その結果を View に渡す。
Supervising Controller
- View で入力がある。
- View が入力があったことを知る。
- 受け取った入力に対する処理を Presenter へ委譲する。
- Presenter が処理を行い、 Model を操作する。
- Model の値に変更があれば、その変更が View に反映される。
MVVC
- View で入力がある。
- View が入力があったことを知る。
- 受け取った入力に対する処理を ViewModel へ委譲する。
- ViewModel が処理を行い、 Model を操作する。
- 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
hoge
が nil のときは、 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) }