まずお伝えしたいのは、著者はiOSアプリ開発の初心者である、ということ。
つまり、これから開発するアプリは紛れも無く初めてのiOSアプリであり、動作こそするものの、エラー処理だのレイアウト調整だのは一切やっていない、ってことです。
まずは動かす、話はそれからだ。
[toc]
完成品の音楽視聴アプリ
完成品はこんな感じ。検索バーにアーティスト名を入れると、ヒットした楽曲がリストに表示され、タップすると遷移先でプレーヤーが再生をする、というもの。
iTunes APIについて
概要
公式サイトより引用。
The Search API allows you to place search fields in your website to search for content within the iTunes Store, App Store, iBooks Store and Mac App Store. You can search for a variety of content; including apps, iBooks, movies, podcasts, music, music videos, audiobooks, and TV shows.
要約すると、「iTunesとかApp Storeとかのコンテンツを検索してウェブサイトに載せられるような検索APIがあるよ。アプリとか書籍、映画、ポッドキャスト、音楽とかを探せるよ。」って感じです。
リクエスト例
http://itunes.apple.com/search?term=aiko&country=JP&lang=ja_jp&media=music&entity=song&attribute=artistTerm&limit=30
- term: aiko
- country: JP
- lang: ja_jp
- media: music
- entity: song
- attribute: artistTerm
- limit: 30
パラメータには様々な組み合わせがあるので、それは公式サイトを読んでみてください。
レスポンス例
{
"resultCount": 30,
"results": [
{
"artistId": 635154255,
"artistName": "aiko",
"artistViewUrl": "http://itunes.apple.com/jp/artist/aiko/id635154255?uo=4",
"artworkUrl100": "http://is5.mzstatic.com/image/thumb/Music2/v4/78/3b/4c/783b4c75-8f55-c585-ec28-fbb086c56840/source/100x100bb.jpg",
"artworkUrl30": "http://is5.mzstatic.com/image/thumb/Music2/v4/78/3b/4c/783b4c75-8f55-c585-ec28-fbb086c56840/source/30x30bb.jpg",
"artworkUrl60": "http://is5.mzstatic.com/image/thumb/Music2/v4/78/3b/4c/783b4c75-8f55-c585-ec28-fbb086c56840/source/60x60bb.jpg",
"collectionCensoredName": "4月の雨 - Single",
"collectionExplicitness": "notExplicit",
"collectionId": 634853356,
"collectionName": "4月の雨 - Single",
"collectionPrice": 250.0,
"collectionViewUrl": "http://itunes.apple.com/jp/album/4yueno-yu/id634853356?i=634853563&uo=4",
"country": "JPN",
"currency": "JPY",
"discCount": 1,
"discNumber": 1,
"isStreamable": false,
"kind": "song",
"previewUrl": "http://a1939.phobos.apple.com/us/r20/Music2/v4/c5/22/db/c522dbb4-f263-d714-1626-f965a354ae6d/mzaf_8138279883609072773.aac.m4a",
"primaryGenreName": "J-Pop",
"releaseDate": "2013-04-17T07:00:00Z",
"trackCensoredName": "4月の雨",
"trackCount": 1,
"trackExplicitness": "notExplicit",
"trackId": 634853563,
"trackName": "4月の雨",
"trackNumber": 1,
"trackPrice": 250.0,
"trackTimeMillis": 335799,
"trackViewUrl": "http://itunes.apple.com/jp/album/4yueno-yu/id634853356?i=634853563&uo=4",
"wrapperType": "track"
},
以下略
今回は、曲名にあたる「collectionName」と、試聴URLにあたる「previewUrl」を使ってアプリを開発していきます。
プロジェクトの作成
Xcodeの起動
起動メニューから「Create a new Xcode project」を選択します。
左メニューの「iOS > Application」から、「Single View Application」を選択します。ここまではお決まりの手順。
アプリに関する情報の入力
- Product Name(アプリ名)
- Organization Name(作者名とか)
- Organization Identifier(独自ドメインがあるなら逆順で入力、ないなら適当に)
- Language(Swift確定)
- Devices(今回はiPhone)
その他の項目は、アプリデータベースを使うか等の設定なので今回はFALSEでOKです。
Projectが作成されると以下のような初期画面が開きます。本稿ではXcodeの各画面やウィンドウの呼称については割愛します。
開発スタート
左メニューから「Main.stroryborad」を開くと、ViewControllerがひとつ配置された状態になっています。iPhoneにしてはちょっと横広な気がするのでそれは後で直します。
Navigation Controllerの準備
今回は初期状態のView Controllerではなく、Navigation Controllerというものを使いたいので、右メニューから「Navigation Controller」を探して(上のほうにある。なければ検索を。)storyborad上にドラッグします。
Entry Pointの変更
Navigation Controllerをstoryboradに配置すると、以下のように初期状態のViewにだけ「→」が接続されているのがわかります。これがEntry Pointとなります。
起動画面は別で用意できるので、起動画面直後のホームってところでしょうか。
今回は追加したNavigation Controllerをホーム画面としたいのでこの「→」を移動します。Navigation Controllerを選択し右メニューから「Is Initial View Controller」にチェックを入れます。
すると、以下のように「→」が移動します。
storyboard上をすっきりさせるためにも初期のView Controllerは消してしまいます。
sizeの変更
厳密にはサイズというか、シミュレータ時のサイズの変更です。(iPhoneの様々なサイズやiPadにまで対応させるにはもっと複雑な設定をしていくことになるみたいなので)。
右メニューのSimulated MetricsからSizeのプルダウンメニューを開き、今回は「iPhone 4-inch」とします。
Navigation Controllerを選択しているので、全体が縦長のiPhoneらしいサイズ感になります。もちろん、これで4-inchのiPhoneのみに対応するアプリになる、というわけではありません。
検索バーの準備
Search Barを設置
iOSアプリ開発は画面レイアウトのパーツを配置して、そこに挙動・制御となるコードを紐付けていくので、まずはユーザからの入力を受ける検索バーを配置します。
Navigation Controllerと同じように、右メニューからSearch Barを探します。そして、それをNavigation Controllerの右のViewに配置します。
結果表示テーブルのidentifierを設定
ここで、検索した結果を表示するテーブルに識別子となるidentifierを設定しておきます。検索バー下の「Prototype Cells」を選択し、右メニューのTable View CellからIdentifierに「cell」と入力します。
Navigation Controller用のSwiftファイルを追加
と、その前にデフォルトのView Controller.swiftを削除しておきます。これをそのまま使っても(がんばれば)できなくはなかったですが、UITableViewControllerの継承がうまくできなかったので。。
メニューの「File > New > File...」を選択し、表示された以下のダイアログで「Cocoa Touch Class」を選択します。
Class名は「RootViewController」とし、UITableViewControllerを継承します。他はデフォルトのままで、他のファイルと同階層に保存します。
Navigation Controllerと紐付ける
Main.storyboard上で、Navigation Controllerを選択し、右メニューのCustom Classプルダウンから「RootViewController」を選択します。(作成時にUITableViewControllerを継承していないと出現しないようです)
Search Barの実装
Search Barのoutletを作成
いよいよ、コーディングに入っていくのですが、その前にstoryboardに配置した検索バーをコード内で扱うため、outletを作成します。
検索バーを選択し、controlキーを押しながらソース上にドラッグします。
- Name: searchBar
- Type: UISearchBar
- Storage: Weak
以下のような感じになっていればOKです。
class RootViewController: UITableViewController {
@IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
また、outletが作成されているかの確認として、コードの左にある黒い丸をクリックすると、storyboard側の検索バーが青くハイライトされます。
json.swiftの作成
Search Barの動きをコーディングする前に忘れてはいけないのは、今回はiTunes APIを叩いたレスポンスを使うということです。
APIのレスポンスがJSON形式なので、それをSwiftで扱いやすくするためのライブラリを追加します。いろいろとライブラリは存在しているのですが、今回は「swift-json」を使います。
ファイルの作成で、Swiftファイルを選択し、ファイル名はjson.swiftとします。作成した後、swift-jsonのGithubから落としたソースをペタッと貼り付けます。
落としたソースを先にディレクトリに置いて、Xcodeから読み込んでもたぶん同じです。
Search Barの検索ボタンが押された時の挙動
ようやく本格的なコードを書きます。ユーザが何かキーワードを入力し、検索を行った時の制御です。
- trackNames: 曲名を格納する可変配列
- previewUrls: 試聴用URLを格納する可変配列
searchWordには日本語が入力されることも想定して、エンコーディングも忘れずに。
class RootViewController: UITableViewController {
@IBOutlet weak var searchBar: UISearchBar!
var trackNames = NSMutableArray()
var previewUrls = NSMutableArray()
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
searchBar.resignFirstResponder()
let searchWord: String? = searchBar.text?.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
if let searchWord = searchWord {
let urlString:String = "http://itunes.apple.com/search?term=\(searchWord)&country=JP&lang=ja_jp&media=music&entity=song&attribute=artistTerm&limit=30"
let url:NSURL! = NSURL(string:urlString)
let task = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: {data, response, error in
let json = JSON(data: data!)
for var i = 0; i < json["results"].length; i++ {
self.trackNames[i] = "\(json["results"][i]["trackName"])"
self.previewUrls[i] = "\(json["results"][i]["previewUrl"])"
}
})
task.resume()
}
self.tableView.reloadData()
}
Search Barのdelegateを設定
delegate、の意味は未だに理解しきれていないのだけど、英語で「代表、派遣」といった意味みたいです。一応、その部品の挙動や制御をコードに一任できるようにする、みたいな理解でいます。
設定は簡単で、検索バーからcontrolを押しながらドラッグして、下図のようにRoot View Controller自身にoutletを繋ぎます。すると、delegateという選択肢が出るのでこれを設定します。
ちなみに、storyboardを開いた状態で、左にあるDocument Outlineから以下のように繋げることもできます。結果は同じです。
実行する
Xcodeの左上にある「実行」のボタンを押します。以下では、iPhone6sのシミュレータが起動します。
試しに検索してみる
例として、上部の検索バーに「aiko」と入力し、検索を行います。
ヒットした曲名がずらっと並ぶ
APIのパラメータにlimit=30を入れているので最大で30曲表示されます。また、右のように日本語での検索もできます。
isLoadNowフラグで状態管理
画面キャプチャでは伝わりづらいのですが、ここでひとつ問題が発生します。実は、検索バーにキーワードを入力して検索を走らせても一発では検索結果は得られないのです。
この時点では、二回のEnterで検索が始まります。これはiTunes APIからの返り値を待たずにテーブルの更新が終わってしまうために起こっている現象だと思われます。よって、上記のコードを以下のように修正します。
class RootViewController: UITableViewController {
@IBOutlet weak var searchBar: UISearchBar!
var trackNames = NSMutableArray()
var previewUrls = NSMutableArray()
var isLoadNow = false
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
searchBar.resignFirstResponder()
let searchWord: String? = searchBar.text?.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
if let searchWord = searchWord {
self.isLoadNow = true
let urlString:String = "http://itunes.apple.com/search?term=\(searchWord)&country=JP&lang=ja_jp&media=music&entity=song&attribute=artistTerm&limit=30"
let url:NSURL! = NSURL(string:urlString)
let task = NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: {data, response, error in
let json = JSON(data: data!)
for var i = 0; i < json["results"].length; i++ {
self.trackNames[i] = "\(json["results"][i]["trackName"])"
self.previewUrls[i] = "\(json["results"][i]["previewUrl"])"
}
self.isLoadNow = false
})
task.resume()
while isLoadNow {
usleep(5)
}
}
self.tableView.reloadData()
}
曲名をタップしてみる
テーブル上のセルがタップされた時の制御を何も実装していないので、ただグレーになるだけで何も起きません。次はここを実装していきます。
AVKit Player View Controllerの設置
音楽を再生するためのViewとして、AVKit Player Controllerを使います。これまでと同じように右メニューから検索し、storyboard上に配置します。
配置したてでは以下のようにバランスが悪いので、これも同じようにSimulated MetricsからSizeのプルダウンメニューを開き、「iPhone 4-inch」とします。
画面の遷移(segue)を作成
View間の画面遷移は、segueと呼ばれるもので繋ぐことで実現できます。今回はテーブルのセルをタップすることで遷移させたいので、以下のようにPrototype Cellsからcontrolを押しながら繋ぎます。
segueにはいろいろと種類があるようなのですが、とりあえず「Show」としておきます。
作成されたsegueをクリックし、以下のようにセルが青くハイライトされればOKです。
音楽を再生するための実装
AVKit Player View Controllerを制御するコードとして、DetailViewController.swiftを追加します。RootViewController.swiftと同様ですが、継承するのはAVPlayerViewControllerです。
継承できない?エラーが発生
ここであれ?となるのですが、おそらくはAVPlayerViewControllerを継承するための何かが足りないのではないかと。その辺りは詳しく調べてみないとわからないのだけど。
とりあえず、以下のようにAVFoundationとAVKitのimport文を記述することで解決しました。
import UIKit
import AVFoundation
import AVKit
class DetailViewController: AVPlayerViewController {
}
これをstoryboard上の、DetailViewControllerに紐付けるため、RootViewControllerの時と同じように右メニューのCustom Classからプルダウンで選択します。
DetailViewControllerのコード
次に、DetailViewControllerの中身を書いていきます。やりたいことは、前の画面から再生用のURL(previewUrl)を受け取って再生する、という動きです。
class DetailViewController: AVPlayerViewController {
var previewUrl: String?
override func viewDidLoad() {
super.viewDidLoad()
if let previewUrl = previewUrl {
player = AVPlayer(URL: NSURL(string: previewUrl)!)
player?.play()
}
}
}
RootViewControllerにコード追加
受け取る側の実装ができたので、送る側にもコードを追加します。segueで画面遷移する際、タップされたセル(indexPath.row)のpreviewUrlを送ります。
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let vc = segue.destinationViewController as? DetailViewController {
if let indexPath = tableView.indexPathForSelectedRow {
vc.previewUrl = previewUrls[indexPath.row] as? String
}
}
}
再生の確認
キャプチャではわかりませんが、タップした楽曲が十数秒程度流れます。
Githubに完成品を置いています
恥ずかしながらせっかくここまでブログとして備忘録を残したので、プロジェクトごとGithubに置くことにしました。記事執筆時点では動作しています。