はじめに
こんぬづは、講師の田中です。
このQiitaは 2017/02/19 開催の【男性参加可!学生無料】TwitterAPIとSwiftを使ってiOSアプリを作ろう! − presents by dots.女子部 #dotsgirls - dots.[ドッツ] のハンズオン用の教材になります。
- 後編: TwitterAPIとSwiftを使ってiOSアプリを作ろう! - 後編 - #dotsgirls - Qiita
- カスタマイズ編: TwitterAPIとSwiftを使ってiOSアプリを作ろう! - カスタマイズ編 - #dotsgirls - Qiita
- Xcode編: TwitterAPIとSwiftを使ってiOSアプリを作ろう! - Xcode編 - #dotsgirls - Qiita
前編でやること
- ゴールの説明
- 作るものの説明
- 環境の説明
-
- プロジェクトを作る
-
- データモデルを作る
-
- UIを作る
-
- ダミーデータをUIに反映させる
ゴール
- Swift / iOSアプリ開発の基本がわかる
- AutoLayoutを使ったレイアウト作成の方法がわかる
- SwiftでAPI通信からモデル生成までの流れがわかる
作るもの
こんな感じのTwitterクライアントアプリです。
自分のタイムラインを表示してみましょう!
サンプルプロジェクトはこちら。→ ktanaka117/DotsSwiftTwitterClient
こちらのプロジェクトからファイルをコピーすることも適宜発生するので、事前にダウンロードしておいてください。
環境
- Swift 3.0.2
- Xcode 8.2.1
- iPhone 7 iOS Simulator
1. プロジェクトを作る
1-1. プロジェクトの新規作成
LaunchpadなどからXcodeアイコンをクリックし、Xcodeを起動します。
すると以下のようなウィンドウが開くので、 Create a new Xcode project
をクリックします。
もしくはXcodeを起動した状態でMacのツールバー上から File > New > Project
を選択します。
1-2. 作成するプロジェクトのテンプレートを選択する
選択するテンプレートに応じて、プロジェクト作成時に作られるファイルやソースコードが切り替わります。
今回はiOSアプリを作るのでiOSタブの Single View Application
という一番シンプルな構成のものを選択してプロジェクトを作成します。
1-3. プロジェクトに関する情報を入力する
プロジェクトを作成する時には以下の情報を入力する必要があります。
- Product Nameは、今回は悩んだらとりあえず
DotsSwiftTwitterClient
と名付けましょう。 - Languageは
Swift
で選択してください。 - Devicesは、今回作るのはiPhoneアプリなので
iPhone
を選択してください。
1-4. 保存する場所を選択する
どこでもオーケーです。悩んだらデスクトップ!!!
1-5. プロジェクトの作成完了
プロジェクトの作成が完了したら、以下の画面が表示されます。
プロジェクト立ち上げ時に生成されるファイルの解説
今回扱うもののみ紹介していきます。
ViewController.swift
プロジェクト作成時に自動的に作られるUIViewControllerのサブクラス(とそのファイル)です。Main.storyboard上のViewControllerと紐付いています。
UIViewControllerというのは、画面遷移やViewの更新などを一挙に受け持つ役割のクラスです。多くの場合はこのUIViewControllerがアプリの一画面につき一つ必要なので、最初はUIViewController = 画面と考えるとわかりやすいです。
Main.storyboard
StoryboardというのはUIViewController同士の画面遷移や、Viewのレイアウトを作っていく役割のファイルです。
この後の UIを作る の項でアプリのレイアウトを作る際に触れていくファイルになります。
2. データモデルを作る
今回扱うデータモデルは二つ。TweetとUserのデータモデルです。Tweetの中でUserを扱うので、Userから先に作っていきます。
2-1. 新しいファイルを作る
Command + n
のショートカットを使うと新しくファイルを作成することができます。
以下の画像のように、iOSタブの Swift File
を選択して Next
を押します。
Save as
にファイル名を User
と指定して Create
を押します。
すると、作成した User.swift
ファイルが追加されたのが確認されます。
ここにUserのデータモデルを定義していきましょう。
2-2. Userのデータモデルを作る
// User.swift
import Foundation
struct User {
// ユーザーのid
let id: String
// (@)ktanaka117
let screenName: String
// ダンボー田中
let name: String
// プロフィール画像URL
let profileImageURL: String
}
structはletで宣言したプロパティのみの場合はinitを書かなくてもオーケーです。自動でinitが作られて、呼び出し元で補完してくれます。
2-3. Tweetのデータモデルを作る
User同様、Tweetのデータモデルも作りましょう。
// Tweet.swift
import Foundation
struct Tweet {
// Tweetのid
let id: String
// Tweetの本文
let text: String
// このTweetの主
let user: User
}
3. UIを作る
さてお待ちかね、アプリのUIを作っていきます。
3-1. TimelineViewController.swiftを作成する
まずメインとなる画面を作成します。
2-1と同じように新規ファイルを追加から、Cocoa Touch Class
を選択します。
先に2段目のSubclass of
に UIViewController
と入力します。(UIViewC
辺りでサジェストされるはず)
ClassはTimelineViewController
としましょう。
3-2. Main.storyboardのUIViewControllerにTimelineViewControllerを指定する
次にMain.storyboard上でViewControllerと指定していたUIViewControllerを、先ほど改名したTimelineViewControllerに指定し直します。
3-3. UITableViewを配置する
StoryboardのTimelineViewController上にUITableViewを探してきて、ドラッグ&ドロップで配置します。
配置後。
3-4. UITableViewにAutoLayoutを適用する
AutoLayoutとは
AutoLayoutとその使い方の説明をします。
iPhoneには複数の端末サイズが存在します。その端末サイズ差に対応するための仕組みがAutoLayoutです。AutoLayoutではあるViewが他のViewと比較してどのくらいの位置に配置されるかを設定することができます。
AutoLayoutを設定していくときに設定するViewの相対位置のルールのことを Constraint(制約)
といいます。
先ほど配置したUITableViewに制約を追加していきます。
制約を追加するには制約を追加したいViewを選択してStoryboard画面右下の右から二番目のアイコンをクリックします。
すると制約を追加する画面が表示されるので、ここに選択したViewに対して追加したい設定値を入力していきます。
ここは少し詳しく見ていきましょう。
Spacing to nearest neighbor
に、上下左右の隣接する他のViewから、選択しているViewがどれくらいのポイント数を離すかを設定します。
今回UITableViewは画面いっぱいに表示したいので、上下左右のマージンを0で設定します。
このとき注意して欲しい点は三つあります。画像を参照してください。
注意点1. 上下左右のマージンの数値は正しいか
加えたい制約の間隔を意味する、入力した数値が意図した数値になっているかどうかを確認してください。もし間違えても後から修正できるので問題はありませが、あとで数値を入力し直すのが面倒だったりするので、制約を追加するときは確認してください。
注意点2. 制約が有効になっているか
入力した数値の内側の赤い線が、点線ではなく実践になっているかを確認してください。これが点線の場合は制約が追加されないので確認してください。有効/無効の切り替えは線をクリックすることで行えます。
注意点3. Constrain to margins
のチェックが外れているか
ここのチェックが入っている場合はチェックを外してください。
このチェックが入っていると、意図しないマージンが取られることがあります。
三点が確認できたら、ウィンドウの一番下にある Add 4 Constraints
ボタンを押して制約を追加しましょう。
制約が追加できたら、TableViewの周りに黄色い線が表示されるでしょう。
これは追加した制約に対してViewの見た目が反映されていないことを意味しています。
見た目を制約通り反映させるには、Viewを選択してStoryboard画面の右下の一番左のアイコンをクリックして Update Frames
します。
TableViewが画面いっぱいにマージン0で広がりました!
ここまででTableViewのAutoLayout適用は終了です。
(以下の画像のように表示がおかしい場合がありますが、これはおそらくXcodeのバグ。ファイルを開き直すと正しい状態に直ります。)
3-5. TableViewをTimelineViewController.swiftにIBOutletで紐付ける
このTableViewをソースコード上で扱えるようにするためにIBOutletでTableViewとTimelineViewControllerの紐付けを行います。IBOutletというのは、Storyboard上の要素をソースコード上で扱うための機能になります。
紐付けを行うにはStoryboard上でTimelineViewControllerを選択して、Assistant Editorを開きます。
Assistant Editorというのは画面を分割する機能のことで、開き方はXcode右上の6つ並んだアイコンの左から二番目をクリックします。
Storyboard上でTimelineViewControllerを選択した状態でAssistant Editorを開くと、TimelineViewController.swiftを開くことができます。
なお、下の画像ではXcode内に要素が多くなってきたので、Utilitiesを非表示にしています。
IBOutletの紐付けの方法は、Storyboard上のTableViewを選択して、その上で control + ドラッグ
して線を引きます。引いた線はソースコード上のクラス内でドロップします。ドロップする場所は class TimelineViewController: UIViewController {
のすぐ下の行がよいでしょう。
ドロップするとポップアップが表示されます。
ここではTableViewをどのようにソースコード上に紐付けるかの設定を行います。以下の画像の通りに入力して、 Connect
ボタンを押しましょう。
Storyboardと紐づいたことを意味する黒丸と、tableViewの宣言が追加されれば完了です。
Storyboard上のtableViewの設定はここまで!
3-6. UITableViewCellを配置する
UITableViewCellを作っていく前に、Standard EditorとUtilitiesを表示してXcodeの表示を元に戻しましょう。
Xcodeが元の表示に戻ったら、UITableViewの時と同様にUITableViewCellを検索して、それをUITableView上に配置します。
配置後。
3-7. UITableViewCellにAutolayoutを適用する
今回実装していくレイアウトは以下のようなものです。
要素を分解すると以下の四つの要素が含まれています。
- アイコンを表示するためのUIImageView
- 名前(ここでは「ダンボー田中フレンズ」)を表示するためのUILabel
- スクリーンネーム(ここでは「@ktanaka117」)を表示するためのUILabel
- ツイート本文を表示するためのUILabel
それぞれのレイアウトは画像の通りですが、文字に起こすと以下の通りです。
- 上と左にそれぞれ8ptずつの余白を持ち、サイズが50 * 50ptとなっています。
- 上に0ptと右に8ptの余白、そしてアイコンのUIImageViewに対して8ptの余白を持ちます。
- 名前のUILabelに対して8pt、 アイコンのUIImageViewに対して8pt、右に8ptの余白を持ちます。
- 上下と右にそれぞれ8ptの余白、そしてアイコンのUIImageViewに対して8ptの余白を持ちます。
さきほどUITableViewに対して行ったように、制約を追加していきましょう。順番は上の数字の通り、1, 2, 3, 4の順で進めましょう。
すると3つ目のラベルの制約追加を終えたときにエラーが表示されるはずです。
赤丸矢印をクリックしてエラーの内容を見てみましょう。
エラーの詳細が表示されました。
エラーを見てみると、 vertical hugging priority
と compression registance priority
という二つのキーワードが見受けられます。今回修正するのは3つ目のツイート本文を表示するUILabelの vertical hugging priority
(= Content Hugging Priority)
このエラーが起こるのは「セルの高さが未確定だから」です。Twitterの公式クライアントアプリを見ればわかる通り、ツイートのセルの高さは本文の長さによって可変です。それに対し、三つ並んだラベルがある中でどのラベルの高さを優先して高くしたらいいのかがわからないことでこのエラーが起こっています。(= Content Hugging Priorityの曖昧さ)
Content Hugging Priorityとは
Content Hugging Priorityというのは、Viewの「大きくなりにくさ」を指定する数値です。この数値が大きければ大きいほどViewは大きくなりにくくなります。逆に優先して大きくしたい場合は数値を小さくしてあげればOKです。
今回優先して大きくしたいのは3つ目のラベルです。以下の画像に従って3つ目のラベルの水平方向のContent Hugging Priority (= vertical hugging priority) の数値を小さくしましょう。
Content Hugging Priorityの数値を修正すれば、エラーは無くなるはずです。
配置したUIの各要素にUpdate Framesを行えば、UITableViewCellに対するAutoLayoutの適用は終了です。
UILabelを設定する
ついでなのでUILabelにデフォルトの文字列を入力する設定と、ツイート本文のラベルを可変にする設定を行いましょう。
Attributes inspector
を表示して設定を行いたいラベルを選択します。
以下の画像の該当箇所にデフォルトに設定したい文字列を入力します。すると、Storyboard上のUILabelにもその変更が反映されます。
同じ調子で他のUILabelにも文字列を入力していきましょう。
↓な感じ。
一定以上の文字数を超えて入力するとツイート本文を入力するラベルが見切れてしまいました。本来ここは "..." となるのではなく高さが変わって欲しいところ。
UILabelの高さを可変にするには Lines
の項目を1から0に設定する必要があります。
Lines
はそのUILabelが表示する行数を表しています。0に設定することで行数の指定を無くし、高さを可変にすることができます。
もしかするとこの時、エラーが表示されるかもしれません。
これは表示上のUITableViewCellの高さが足りないことが原因で起こるエラーです。
UITableViewCellの下端をドラッグ&ドロップして表示上の高さを調節してあげれば解決します。
その他フォント、フォントサイズ、フォントカラー、などはカスタマイズ編で紹介します。
3-8. TweetTableViewCell.swiftとそのクラスを作成し、IBOutletで紐付ける
データモデルを作ったときと同様に、Swiftファイルを作成してUITableViewCellを継承したTweetTableViewCellクラスを宣言します。
// TweetTableViewCell.swift
import UIKit
class TweetTableViewCell: UITableViewCell {
}
そして先ほどまで作成していたMain.storyboardのUITableViewCellのclass指定に、今作成したTweetTableViewCellを指定します。
ついでに後ほど描画設定の際に使うことになるのでIdentifierも指定しておきます。
次にTimelineViewControllerのときと同様に、Assistant Editorを開いてTweetTableViewCellにUI要素をソースファイルに紐付けていきます。
Assistant Editorで開くファイルを指定するには、以下の画像のように Automatic
をクリックして、
Automatic > Manual > DotsSwiftTwitterClient > DotsSwiftTwitterClient > TweetTableViewCell.swift
と階層を潜って指定します。
Assistant EditorでTweetTableViewCell.swiftを開けたら、 control + ドラッグ&ドロップ
でIBOutletを紐付けていきます。
変数名は以下の画像の通り。
// TweetTableViewCell.swift
import UIKit
class TweetTableViewCell: UITableViewCell {
// アイコンを表示するUIImageView
@IBOutlet weak var iconImageView: UIImageView!
// 名前(ダンボー田中フレンズ)を表示するUILabel
@IBOutlet weak var nameLabel: UILabel!
// スクリーンネーム(@ktanaka117)を表示するUILabel
@IBOutlet weak var screenNameLabel: UILabel!
// ツイート本文を表示するUILabel
@IBOutlet weak var textContentLabel: UILabel!
}
3-9. Delegateを設定する
Delegateとは?
Delegateは日本語で「委譲」という意味を持ちます。
Delegateというのはprotocolで実装されていて、そのprotocolに宣言されている処理をどこで実行するかをプログラマが指定することのできる仕組みです。
これをどこで使うかというと、例えば「UITableViewのセルがタップされたのを検知してなにかしたいとき」などです。
UIViewControllerにはもともと、UITableViewがタップされたのを検知する機能はありませんが、Delegateを使って処理の委譲先をUIViewControllerに指定してあげればそれができるようになります。
コードで書くとこんな風に指定します。
class TimelineViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// delegateの指定を自分自身(self = TimelineViewController)に設定
tableView.delegate = self
}
}
extension TimelineViewController: UITableViewDelegate {
// cellがタップされたのを検知したときに実行する処理
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("セルがタップされたよ!")
}
}
cellの高さを自動計算させる
UITableViewDelegateにはセルタップなどのユーザーイベントを検知する機能の他にも、UITableViewの見た目や挙動に関する機能も含まれています。
見た目の中にはセルの高さの指定も含まれています。今回はツイート本文の量に応じてUILabelの高さを自動的にCellに反映させたいので、 estimatedHeightForRow
と UITableViewAutomaticDimension
の値を設定します。
// TimelineViewController.swift
--- 省略 ---
extension TimelineViewController: UITableViewDelegate {
// cellがタップされたのを検知したときに実行する処理
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("セルがタップされたよ!")
}
// セルの見積もりの高さを指定する処理
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
// セルの高さ指定をする処理
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// UITableViewCellの高さを自動で取得する値
return UITableViewAutomaticDimension
}
}
3-10. TimelineViewControllerにTweetの配列をプロパティとしてもつ
このあと説明するdataSourceを設定するために、UITableViewを表示するためのデータソースとしてTweetの配列を宣言しておきます。
// TimelineViewController.swift
import UIKit
class TimelineViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
// テーブル表示用のデータソース
var tweets: [Tweet] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
}
}
3-11. dataSourceを設定する
DataSourceとは?
DataSourceもDelegateと仕組みは同じで、protocolで実装されたものになります。DataSourceの役割はViewを描画するのに必要な情報を渡す処理の実装先を決めて、実装することです。例えば「どんなcellを描画するか」、「cellは何個描画するか」、「セクションの数は何個にするか」などです。
コードで書くとこんな感じ。
// TimelineViewController.swift
import UIKit
class TimelineViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
var tweets: [Tweet] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
// dataSourceの指定を自分自身(self = TimelineViewController)に設定
tableView.dataSource = self
}
}
// --- UITableViewDelegateを省略 ---
extension TimelineViewController: UITableViewDataSource {
// 何個のcellを生成するかを設定する関数
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// tweetsの配列内の要素数分を指定
return tweets.count
}
// 描画するcellを設定する関数
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// TweetTableViewCellを表示したいので、TweetTableViewCellを取得
let cell = tableView.dequeueReusableCell(withIdentifier: "TweetTableViewCell") as! TweetTableViewCell
return cell
}
}
tableView.dequeueReusableCell(withIdentifier:)
とは?
Storyboardから指定したIdentifierのセルを取得する処理です。先ほどTweetTableViewCellをStoryboardで作成した際に指定したIdentifierの文字列がこの関数の引数に渡す文字列と一致している必要があります。
そしてこの関数の戻り値はUITableViewCellなので、 TweetTableViewCellとして扱うために as! TweetTableViewCell
としてキャストしています。
そしてUITableViewで利用されているqueueという仕組みについても軽く説明します。
例えばUITableViewに1000件のデータソースがあったとして、その件数に応じてセルを生成するとしたらメモリをとても圧迫してしまいます。それを解決するために使われているのがUITableViewCellの描画におけるqueueです。
1画面に描画できるセルの数は、画面に表示できるだけしかありません。UITableViewのスクロールに合わせて、消えていくセルとこれから表示されるセルがありますが、生成されたセルはそのままに、セルに表示するための中身のデータだけを入れ替えて描画しているというのがこのqueueの仕組みになります。
[画像]
4. ダミーデータをUIに反映させる
4-1. TweetTableViewCellのUIにTweetの値をセットする関数を作る
TweetTableViewCellに、渡されたTweetの値をUIに反映させる func fill(tweet: Tweet)
という関数を定義します。
let downloadTask = URLSession.shared.dataTask(with: URL(string: tweet.user.profileImageURL)!)
は渡されたURLをもとに通信を行います。
DispatchQueue.main.async
や [weak self]
などについてはとりあえず今は「おまじない」として覚えておいてください。また後ほど登場するのでその時紹介します。
// TweetTableViewCell.swift
import UIKit
class TweetTableViewCell: UITableViewCell {
@IBOutlet weak var iconImageView: UIImageView!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var screenNameLabel: UILabel!
@IBOutlet weak var textContentLabel: UILabel!
func fill(tweet: Tweet) {
// profileImageURLから画像をダウンロードしてくる処理
let downloadTask = URLSession.shared.dataTask(with: URL(string: tweet.user.profileImageURL)!) { [weak self] data, response, error in
if let error = error {
print(error)
return
}
DispatchQueue.main.async {
// iconImageViewにダウンロードしてきた画像を代入する処理
self?.iconImageView.image = UIImage(data: data!)
}
}
downloadTask.resume()
// tweetから値を取り出して、UIにセットする
nameLabel.text = tweet.user.name
textContentLabel.text = tweet.text
// screenNameには "@" が含まれていないので、頭に "@" を入れてあげる必要がある
screenNameLabel.text = "@" + tweet.user.screenName
}
}
4-2. ダミーデータを作成してTableViewのデータソースにする
ダミーデータの内容はなんでもOKです!
self.tweet
に配列に格納したダミーデータを代入したら、 tableView.reloadData()
を呼ぶことでtableViewを再度描画(更新)させることができます。
indexPath
というのはそのセルの行を表した型になります。 indexPath.row
からはInt型の行番号を取り出すことができるので、配列の中からindexPath.rowで指定したindexのTweetを取得することができます。
// TimelineViewController.swift
import UIKit
class TimelineViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
var tweets: [Tweet] = []
override func viewDidLoad() {
super.viewDidLoad()
// ダミーデータの生成
let user = User(id: "1", screenName: "ktanaka117", name: "ダンボー田中", profileImageURL: "https://pbs.twimg.com/profile_images/832034247414206464/PCKoQRPD.jpg")
let tweet = Tweet(id: "01", text: "Twitterクライアント作成なう", user: user)
let tweets = [tweet]
self.tweets = tweets
// tableViewのリロード
tableView.reloadData()
}
}
extension TimelineViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("セルがタップされたよ!")
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
}
extension TimelineViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tweets.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TweetTableViewCell") as! TweetTableViewCell
// TweetTableViewCellの描画内容となるtweetを渡す
cell.fill(tweet: tweets[indexPath.row])
return cell
}
}
4-3. 実行して確認する
Command + R
のショートカットでプロジェクトを実行(Run)することができます。
前編の終わり
ここで前編は終わりです。休憩しましょう。
次は後編。
TwitterAPIとSwiftを使ってiOSアプリを作ろう! - 後編 - #dotsgirls - Qiita