「Swiftデ、オ天気アプリヲ作成セヨ」

こんにちは。エンジニアの高島です。
先日、iOS開発にアサインされる前に大体の感覚を掴むために課題を頂きました。
その課題をこなす際に詰まったところを記事にさせて頂こうと思います。(課題未完)

課題の内容

事前講義

  • UIViewController, UITableView, UITableViewCellの説明
  • APIからJson取得してパースする時の流れの説明
  • Podfileの説明 ほか、基礎知識について

テーマ

「お天気アプリ」 ざっくりこんな感じです。 f:id:cluex-developers:20170627171210j:plain slackに送信するボタンをつける、Alertボタン押して都道府県画面に戻る機能は、一度基本機能を作った後で追加課題という事で実装しました。
その後、機能が一通り実装できたところで、クリーンアーキテクチャに則らせる(+RxSwift使う)という、3段階の流れで課題を進めています。

実装

以下、CleanArchitectureでの実装になります。
こういうやり方もあるよ!等ありましたら、是非ご意見頂けると嬉しいです。

詰まった箇所

1. mappingがうまくいかない

今回使用させて頂いたAPIは、livedoor天気情報
タイトル, 天気概況文, 今日の天気アイコンのURL, 今日の最高気温(摂氏), 今日の最低気温(摂氏)を使って実装しています。
敢えて今日のとつけたのには理由があって、これらの情報は今日、明日、明後日の3日分の配列から取得できます。
配列をどのようにObjectMapperでmapするのかは講義で学んだものの、ここでうまくパース出来なくて詰まりました。

解決策

構造体を別途準備して配列に持たせました。

struct ForecastEntity {
    var imageUrl: String?    // map["image.url"]
    var max: String?            // map["temperature.max.celsius"]
    var min: String?             // map["temperature.min.celsius"]
}

struct WeatherEntity {
    var title: String?                             // map["title"]
    var description: String?                 // map["description.text"]
    var forecasts: [ForecastEntity]?   // map["forecasts"]           ※ここに上の構造体を入れています
}

2. Entityが配列の時にいつ分けるのか

練習も兼ねて、都道府県名とIDをDataStoreに置く際に、タプルの配列型にしていました。
DataSotre

let prefectures: [(name: String, id: String)] = [
    ("埼玉", "110010"),
    ("東京", "130010"),
    ("千葉", "120010"),
    ("静岡", "220010"),
    ("茨城", "080010"),
    ("山梨", "190010"),
    ("群馬", "100010"),
    ("岩手", "030010"),
    ("福島", "070010"),
]

Entity

struct PrefectureListEntity {
    var list: [(name: String, id: String)]
}

といった形です。 しかし、Viewではセルに値を渡す為、配列でなく要素を取得する必要があります。
配列から要素に分けるタイミングで少し悩みました。

解決策

ViewModelの段階で要素を取得する様にしました。
ModelからViewで使える様にViewModelに変換しているのですが、その際に、

struct PrefectureListViewModel {
    func PrefectureViewModelProtocol(index: Int) -> PrefectureViewModelProtocol {
        return PrefectureViewModel(prefecture: prefectureList.list[index])
    }
    ...

この様な形で、配列から要素であるPrefectureViewModelを取得出来る様にしています。

3. xibを作らずにUIStoryboardの上にUITableViewCellを作る

都道府県リストだけでなく、天気画面やお問い合わせ画面もUITableViewを配置して、各セルに分けて作成しています。
例えば、お問い合わせ画面の場合、名前ラベルとテキストフィールドで1つ、といった形です。
クリーンアーキテクチャに則る前はそれぞれxibを作成していたのですが、則るタイミングで別途xibを作らずに、UIStoryboard上に全て配置して作成することにしました。 f:id:cluex-developers:20170627171214p:plain 変更してから実行してみたところ、dequeueResusableCellを呼ぶとCould not load NIB in bundleが出てセルが表示されなくなりました。

解決策

xibを使う際は必要なUINibの初期化部分を消しました。

let forecastNib = UINib(nibName: CellNames[Cells.Forecast.rawValue], bundle: nil)
tableView.register(forecastNib, forCellReuseIdentifier: CellNames[Cells.Forecast.rawValue])

4. UITextViewで高さ自動設定時に高さが0になる

天気画面とお問い合わせ画面はheightForRowAt, estimatedHeightForRowAtともにUITableViewAutomaticDimensionを返すようにしています。 この時、UITextViewの高さが0になる事に頭を悩ませていました。
これが実装中一番困ったことだったと言っても過言ではない程度には。

解決策

ConstraintsでHeightを付与しました。
Top, Bottomは与えていたので問題ないのかと思っていましたが、このHeightが重要でした。
なぜそうなのかは以下の記事で学ばせて頂きました。
http://cockscomb.hatenablog.com/entry/uitextview-on-uitableview

UITextView の intrinsic content size はどうかというと、そのままでは width も height も -1 の CGSize が返る。UITextView は UIScrollView を継承しているので、表示する内容に関わらず、ちょうどよいサイズというものを持たないのである。

現状

構成

f:id:cluex-developers:20170627171217p:plain

残り

  • キーボードに合わせてスクロールする量がキーボードの予測変換を考慮できてない(計算の仕方が単純におかしいのかもしれない)
  • Warningが出ている(Alertを二度表示しようとしていて出る, キーボード出した時に出る)

まだあと少し残っていますが、実装中に詰まったところはこのくらいでしょうか。
まだまだ写経してる感があるので、早く使いこなせる様になりたいです。

We’re hiring!!

Cluexではエンジニアサイド、ビジネスサイド共にメンバーを募集しています!
お気軽にご連絡下さいませ!エンジニアの方、ぜひ情報交換しましょう! www.wantedly.com www.wantedly.com