ろきメモ【ROKI MEMO】- ろきsanの備忘録 -

ろきさんの備忘録。プログラミング学習記録や開発記録、および学んだ知識等のアウトプットとシェアを目的に書いています。たまに普通のことも書きます。

【はじめてのiOSアプリ作成とその詳細手順】音楽再生の簡単iOSアプリ作成記録(Swift, Xcode, iPhone)

スポンサーリンク

こんにちは。最近、業務に関係ないけど、幅を広げるとの興味本位でiOSアプリを作ってみようとしているぼくです。

先日、簡単なアプリを作成し、手持ちのスマホで実機テストをしました。
その時のテストの手順を備忘録として残したのが以下の記事です。
blog.rokisan.com


このとき起動した、簡単なアプリについて
アプリの作成方法も知りたい!

…という声は特にないですが、せっかくなので記録することにしました。


なかなか丁寧に書いたので長くなってますが、これを作ってみて、さらに応用してみると、簡単なアプリは作れるようになると思います。

では、始めます。

目次

スポンサーリンク


1. プロジェクト作成

Xcodeを起動し、プロジェクト作成までいきます。

Xcodeを起動
f:id:ahrk-izo:20190120105638p:plain:w200


「Create a new Xcode project」を選択
f:id:ahrk-izo:20190120104535p:plain:w400


「Single View App」を選択して「Next」
f:id:ahrk-izo:20190120105814p:plain:w400


必要事項を入力して「Next」
f:id:ahrk-izo:20190120111957p:plain:w400
Product Name : 任意(アプリ名)
Team : Apple ID(とりあえずPC上でのみならNoneでよい。後から変更可能)
Organization Name : アプリ作成者名や会社名
Organization Identifier : 開発者や会社名の定義(ローカルでやるには何でもよい)
Language : Swift(開発言語)


保存先を指定して「Create」
f:id:ahrk-izo:20190120112032p:plain:w400



こんな画面になりました。
f:id:ahrk-izo:20190120111858p:plain:w400


2. 素材を用意する

このアプリにはボタンとなる画像や再生する音源が必要なので、フリー素材から集めます。
※自分で作ってもいいですが、あるものを使ってみましょう


ボタンの画像は「ICON FINDER」から取得しました。
www.iconfinder.com


「再生ボタン」を探したいので「play」と検索
f:id:ahrk-izo:20190120112426p:plain:w400


詳細検索を設定します
f:id:ahrk-izo:20190120112933p:plain:w400
PRICEを「Free」
LICENSE TYPEを「For commercial use」
※アプリを商用利用する場合

お好きな画像、お好きなサイズを「ダウンロード」
※サイズはXcode内でリサイズできるので何でも良い

同様に、「停止ボタン」も「stop」で検索して取得

これらの画像の取得が完了
f:id:ahrk-izo:20190120131552p:plain:w200



画面上部の画面はおなじみ「いらすとや」さん
こちらもフリーで商用利用可。
www.irasutoya.com

こんな画像を取得
f:id:ahrk-izo:20190120131631p:plain:w300



音源もフリーのものを取得しましょう。
音源はこちらから
maoudamashii.jokersounds.com


ほしい音源を探して、MP3をダウンロード
f:id:ahrk-izo:20190120152308p:plain:w400


3. 素材をXcodeに取り込む

ではXcodeに戻り、取得した画像や音源をXcodeに取り込みましょう。

まず画像から。

Assets.xcassetsにドラッグ・アンド・ドロップ
f:id:ahrk-izo:20190120154552p:plain:w400
※ダウンロードした後、ファイル名は変更してます(「play.png」「stop.png」「game01.png」)


他の画像も同様に取り込み完了
f:id:ahrk-izo:20190120162033p:plain:w400



次に音源も取り込みます。

音源はこちらの位置にドラッグ・アンド・ドロップ
f:id:ahrk-izo:20190120155537p:plain:w400
※ダウンロードした後、ファイル名は変更してます(「8bit27.mp3」)

素材は残しておきたいので、「Copy items if needed」にチェックを入れて「Finish」
f:id:ahrk-izo:20190120155746p:plain:w400


これで素材は揃いました。
f:id:ahrk-izo:20190120160118p:plain:w400


ようやく、アプリ作成に入ります。

4. デバイスの設定(UI画面とシミュレーター)

実装に入る前に、アプリを作成していく画面デバイスと、実行(シミュレーション)するデバイスを揃えておきます。
ご自分のスマホでも試してみたいと考えている場合は、その機種に合わせておくとよいでしょう。
f:id:ahrk-izo:20190120161030p:plain:w400
上部が実行するデバイス
下部が作成していく画面のデバイス

※左にあるDocument Outlineはこのボタンで非表示にできます。
f:id:ahrk-izo:20190126110056p:plain:w300


5. 画像を設置

UI画面上(Main.stroyboard)にライブラリペインの部品を設置し、そこに画像をリンクさせます。

まず上部のゲームっぽい画像(いらすとやさんの画像)を設置します。
左の項目で「Main.stroyboard」を選択してUI画面を表示
右上の「◎」(ライブラリペインの表示)ボタンをクリック
「Image View」を作成画面にドラッグ・アンド・ドロップ
f:id:ahrk-izo:20190120161539p:plain:w400
※検索を使うと便利です(「image」などで検索)

※ちなみに、UI画面はピンチで拡大縮小できます。


先ほど置いた部品(UIImageView)を選択
Image View > Image に画像名(拡張子なし)を指定
f:id:ahrk-izo:20190120162437p:plain:w400
※Assets.xcassetsに取り込んだ画像


無事、画像が挿入できました。
f:id:ahrk-izo:20190120163001p:plain:w400



次にボタンを設置します。

右上の「◎」(ライブラリペインの表示)ボタンをクリック
「Button」を作成画面にドラッグ・アンド・ドロップ
f:id:ahrk-izo:20190121192351p:plain:w400
※検索で「bu…」とやるとすぐ見つかる


文字を削除して、画像を設置
f:id:ahrk-izo:20190121193741p:plain:w400
Title : 空にする
Image : stop(保存したstop.png。拡張子はなし)


サイズ(WidthとHeight)を指定する
f:id:ahrk-izo:20190121194044p:plain:w400


再生ボタンも同様に。
f:id:ahrk-izo:20190121194233p:plain:w400
※先に作った停止ボタンをコピペして、「Image」だけを変更するとラク
Title : 空にする
Image : play(保存したplay.png。拡張子はなし)


さらにボタンを2つ用意します。
これは、それぞれのボタンが押されたときに非活性(グレーにして押せない)にするためのものです。

Image(画像)は削除し、
Alpha(透明度)と、Background(背景)を指定する
f:id:ahrk-izo:20190121194748p:plain:w400
※現在はわかりやすいようにずらしていますが、後ほど各ボタンに重ねます。


次に再生時間を示す、ラベルを設置します。

右上の「◎」(ライブラリペインの表示)ボタンをクリック
「Label」を作成画面にドラッグ・アンド・ドロップ
f:id:ahrk-izo:20190121195420p:plain:w400
※検索で「la…」とやるとすぐ見つかります。


文字やフォントサイズ、配置を変更します。
f:id:ahrk-izo:20190121195908p:plain:w400
Text : 0
Font : 20(「T」をクリックして指定)
Alignment : 中央


ここいらで一旦、シミュレーターで実行して確認してみましょう。

実行ボタンをクリック
f:id:ahrk-izo:20190121200609p:plain:w300



無事、成功しました。
f:id:ahrk-izo:20190121200713p:plain:w400

command + q でシミュレーターが終了します。

では、次からいよいよコードを書いていきます。

スポンサーリンク



6. 実装1 - 各部品と変数を紐づける。必要な変数を宣言する。

まずこのUI画面(Main.stroyboard)に紐付いている「ViewController.swift」というファイルを並べて表示します。
UI画面上の「◎」をクリックして、右上の「2つの輪っか」をクリックすると、2つ並んで表示されます。
f:id:ahrk-izo:20190122194048p:plain:w400



まずラベル(カウントアップ用)をコードと紐付けます。

設置したLabelをcontrol を押しながらドラッグ・アンド・ドロップ。
f:id:ahrk-izo:20190123193423p:plain:w400


こんな画面がでるので、変数の情報を設定します。
f:id:ahrk-izo:20190123193524p:plain:w400
各情報は画像を参考にしていただき、「Connect」を押します。
※Nameは「countLabel」とします

以下のコードが生成されました。

@IBOutlet var countLabel: UILabel!

f:id:ahrk-izo:20190123193620p:plain:w400


2つのカバー用のボタンも同様に紐付けます。(実際には1つずつです)
f:id:ahrk-izo:20190123194211p:plain:w400
※Nameは「stopIconCover」「playIconCover」とします

以下のコードが生成された

@IBOutlet var stopIconCover: UIButton!
@IBOutlet var playIconCover: UIButton!

コードの上部に、音声再生に必要な「AVFoundation」をインポートする

@IBOutlet var countLabel: UILabel!

を追加します。

さらに以下のコードを追加

var count: Int = 0               // カウント
var audioPlayer: AVAudioPlayer!  // 音声オブジェクト
var timer : Timer!               // タイマーオブジェクト

※現時点では各コードの説明は割愛します。後々でてくる全体のコードを見れば、なんとなくわかるでしょう。


現時点での全コードはこちら
(ViewController.swift)

import UIKit
import AVFoundation

class ViewController: UIViewController {
    @IBOutlet var countLabel: UILabel!
    @IBOutlet var stopIconCover: UIButton!
    @IBOutlet var playIconCover: UIButton!
    var count: Int = 0               // カウント
    var audioPlayer: AVAudioPlayer!  // 音声オブジェクト
    var timer : Timer!               // タイマーオブジェクト
    
    override func viewDidLoad() {
        super.viewDidLoad()

    }
}

※作成時にあった、不要なコメント文は削除してます。

7. 実装2 - ボタンタップ時の挙動をコードと紐づけてメソッドを作り、実行確認する

停止ボタンや再生ボタンを押したら、それぞれボタンが非活性になるようにします。
つまり、ボタンを押したときのメソッドを作り、その中の処理としてグレーのカバーを掛けます。


停止ボタンをControlを押しながらドラッグ・アンド・ドロップ。
f:id:ahrk-izo:20190123200057p:plain:w400
※設置する場所に注意

以下のように情報を設定します。
f:id:ahrk-izo:20190123201436p:plain:w300
※Nameは「stopMusic」
※Eventの「Touch Up Inside」はタップして指が離れたときのこと

「Connect」を押すと、以下のコードが生成されます。

@IBAction func stopMusic(_ sender: Any) {
}

これでボタンを押したときのメソッドができました。
※停止ボタンが押されたときに、この中に書いた処理が実行されます。


同様に、再生ボタンもコードを紐づけ、メソッドを作ります。
f:id:ahrk-izo:20190123202727p:plain:w400

Nameは「playMusic」とし、以下のコードが生成されます。

@IBAction func playMusic(_ sender: Any) {
}

できたら、viewDidLoad()も含めた3つの関数に以下のようなコードを書いていきます。

    // アプリ起動時
    override func viewDidLoad() {
        super.viewDidLoad()
        stopIconCover.isHidden = false // 表示
        playIconCover.isHidden = true  // 非表示
    }
    
    // 停止ボタンタップ時
    @IBAction func stopMusic(_ sender: Any) {
        stopIconCover.isHidden = false // 表示
        playIconCover.isHidden = true  // 非表示
    }
    
    // 再生ボタンタップ時
    @IBAction func playMusic(_ sender: Any) {
        stopIconCover.isHidden = true  // 非表示
        playIconCover.isHidden = false // 表示
    }

それぞれの動作時に、カバーとなるグレーのボタンの表示・非表示を切り替えています。
※viewDidLoadはアプリ起動時に実行される関数です。

これで全体のコードは、以下のようになりました。

import UIKit
import AVFoundation

class ViewController: UIViewController {
    
    @IBOutlet var countLabel: UILabel!
    @IBOutlet var stopIconCover: UIButton!
    @IBOutlet var playIconCover: UIButton!
    var count: Int = 0               // カウント
    var audioPlayer: AVAudioPlayer!  // 音声オブジェクト
    var timer : Timer!               // タイマーオブジェクト

    // アプリ起動時
    override func viewDidLoad() {
        super.viewDidLoad()
        stopIconCover.isHidden = false // 表示
        playIconCover.isHidden = true  // 非表示
    }
    
    // 停止ボタンタップ時
    @IBAction func stopMusic(_ sender: Any) {
        stopIconCover.isHidden = false // 表示
        playIconCover.isHidden = true  // 非表示
    }
    
    // 再生ボタンタップ時
    @IBAction func playMusic(_ sender: Any) {
        stopIconCover.isHidden = true  // 非表示
        playIconCover.isHidden = false // 表示
    }
    
}

では、カバーとなるボタンを停止・再生ボタンに重ねて、シミュレーターを実行してみましょう。

f:id:ahrk-izo:20190123205420p:plain:w400
※Document Outlineは表示しなくてもいいです。


オブジェクトの順序入れ替え方法とハマったこと

もしここで、カバーが停止・再生ボタンよりも後ろになっている場合は、オブジェクトの順序を入れ替えます。
※Document Outline(ドキュメントアウトライン)で、下になっているのが前面になります。

移動したいオブジェクトを選択し、Editor > Arrange > Send Forward(前面にする)などで移動します。
f:id:ahrk-izo:20190123210347p:plain:w400

ここで若干、私はハマりました。。。

私の場合は、オブジェクトを選択しただけでは、このArrangeの選択肢が非活性で選択できず、順序を入れ替えられませんでした。
解決策は、オブジェクトをダブルクリックすると、移動できるよう(選択肢が活性)になりました。
(なんでしょ?Xcodeのバグ?仕様?まぁできたのでよしとしましょう。)


実行ボタンをクリック
f:id:ahrk-izo:20190121200609p:plain:w300
※ command + r でもOK

それぞれボタンを交互に押すと、互いにボタンが活性、非活性と切り替わります。
f:id:ahrk-izo:20190123210915p:plain:w400


では、音声を再生・停止させ、ラベルもカウントアップさせるコードを書いて仕上げます。

8. 実装3 - 音声を再生・停止するコードを書く

音声の再生と停止のコードを書きます。

停止ボタンと再生ボタンのタップ時の関数部分に、コードを追加して以下のようにします。

    // 停止ボタンタップ時
    @IBAction func stopMusic(_ sender: Any) {
        stopIconCover.isHidden = false // 表示
        playIconCover.isHidden = true  // 非表示
        
        audioPlayer.stop()             // 音声stop <- 追加
    }
    
    // 再生ボタンタップ時
    @IBAction func playMusic(_ sender: Any) {
        stopIconCover.isHidden = true  // 非表示
        playIconCover.isHidden = false // 表示
        
        // 音声再生 <- ここから以下追加
        if let url = Bundle.main.url(forResource: "8bit27", withExtension: "mp3") {
            do {       // 音声再生する
                audioPlayer = try AVAudioPlayer(contentsOf: url)
                audioPlayer?.play()
            } catch {  // プレイヤー作成失敗時
                audioPlayer = nil
            }
        } else {       // ulrがnilの場合など
            fatalError("Url is nil.")
        }
    }

※「<- 追加」や「<- ここから以下追加」の部分を注目してください。

ここで、

Bundle.main.url(forResource: "8bit27", withExtension: "mp3")

の部分の「"8bit27"」と「"mp3"」は、音声ファイルのファイル名と拡張子になっています。
ファイル名が違う場合は、それぞれファイル名に変えてください。


これは必須では無いですが、せっかくなので(なんの?)音声再生部分を関数化して、playMusicの中をすっきりさせます。
※関数で「引数」を使う勉強として

playMusicFileという関数を作って、引数にファイル名と拡張子を渡してあげます。

    // 再生ボタンタップ時
    @IBAction func playMusic(_ sender: Any) {
        stopIconCover.isHidden = true  // 非表示
        playIconCover.isHidden = false // 表示
        
        playMusicFile(file_name: "8bit27", ext: "mp3") //音声再生 <- 修正
    }
    
    // 音声再生(引数でファイル名と拡張子を指定) <- 以下追加
    func playMusicFile( file_name: String, ext: String) {
        if let url = Bundle.main.url(forResource: file_name, withExtension: ext) {
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: url)
                audioPlayer?.play() // 音声再生する
            } catch {      // プレイヤー作成失敗時
                audioPlayer = nil
            }
        } else {           // ulrがnilの場合など
            fatalError("Url is nil.")
        }
    }

ここで関数を呼び出しているところのコード

playMusicFile(file_name: "8bit27", ext: "mp3")

の"8bit27"や"mp3"を変更するだけで簡単に修正できたり、追加できたりします。
※ここでは1つしか音は再生しませんが

では残りはカウントアップの表示です。

9. 実装4 - 変数をインクリメント(カウントアップ)して、ラベルに表示させる。

最後にカウントアップの表示を実装します。
もちろん停止ボタンを押したら、カウントはリセットします。

    // 停止ボタンタップ時
    @IBAction func stopMusic(_ sender: Any) {
        stopIconCover.isHidden = false // 表示
        playIconCover.isHidden = true  // 非表示
        audioPlayer.stop()             // 音声stop
        
        if timer.isValid == true {      // タイマー起動中なら <-以下追加
            timer.invalidate()          // タイマーを停止
        }
        count = 0                       // リセット
        countLabel.text = String(count) // ラベルに表示
    }
    
    // 再生ボタンタップ時
    @IBAction func playMusic(_ sender: Any) {
        stopIconCover.isHidden = true  // 非表示
        playIconCover.isHidden = false // 表示
        playMusicFile(file_name: "8bit27", ext: "mp3") //音声再生
        
        // 1.0秒おきにcountUp関数を呼び出す <- 以下追加
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countUp), userInfo: nil, repeats: true)
    }
    
    // 変数をインクリメントし、countLabelに表示 <- 以下追加
    @objc func countUp() {
        count += 1
        countLabel.text = String(count) // ラベルに表示
    }

簡単な説明としては、

timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countUp), userInfo: nil, repeats: true)

の部分で、
1.0秒ごとに、countUp関数を呼び出して(その関数でインクリメントしてラベルの文字を更新して)います。
詳細は、公式ドキュメントを読んだり、書籍を見たり、ググってみましょう。

さらに、ここの部分

countLabel.text = String(count) // ラベルに表示

では、ラベルにインクリメント(+1)した変数countを表示していますが、countはInt型なので表示するためにString型に型変換(キャスト)しています。


では最後に動作を確認して終わります。(全コードは最後に載せています。)

10. シミュレーターの最終確認と全コードの確認

最後に動作を確認します。

実行ボタンをクリック
f:id:ahrk-izo:20190121200609p:plain:w300
※ command + r でもOK

再生ボタンを押すと音声が再生され、カウントアップされます。
停止ボタンで音声が止まり、カウントが「0」に戻ります。
f:id:ahrk-izo:20190125160119p:plain:w400

command + q で終了

音が再生できましたか?
ラベルがカウントアップされましたか?


これで完成です!
お疲れさまでした!


これにいろいろと改良を加えて、学んでみるのもいいと思います。

例えば、
・音声を一時停止できるようにする(再生ボタンが一時停止に変わる)
・カウントアップの表示を「00:00:00」の表示にする
などなど

また、こちら参考に手持ちの実機(スマホ)でも確認できるので試して見てください。
blog.rokisan.com


全コードは以下になります。

import UIKit
import AVFoundation

class ViewController: UIViewController {
    
    @IBOutlet var countLabel: UILabel!
    @IBOutlet var stopIconCover: UIButton!
    @IBOutlet var playIconCover: UIButton!
    var count: Int = 0               // カウント
    var audioPlayer: AVAudioPlayer!  // 音声オブジェクト
    var timer : Timer!               // タイマーオブジェクト

    // アプリ起動時
    override func viewDidLoad() {
        super.viewDidLoad()
        stopIconCover.isHidden = false // 表示
        playIconCover.isHidden = true  // 非表示
    }
    
    // 停止ボタンタップ時
    @IBAction func stopMusic(_ sender: Any) {
        stopIconCover.isHidden = false // 表示
        playIconCover.isHidden = true  // 非表示
        audioPlayer.stop()             // 音声stop
        
        if timer.isValid == true {      // タイマー起動中なら
            timer.invalidate()          // タイマーを停止
        }
        count = 0                       // リセット
        countLabel.text = String(count) // ラベルに表示
    }
    
    // 再生ボタンタップ時
    @IBAction func playMusic(_ sender: Any) {
        stopIconCover.isHidden = true  // 非表示
        playIconCover.isHidden = false // 表示
        playMusicFile(file_name: "8bit27", ext: "mp3") //音声再生
        
        // 1.0秒おきにcountUp関数を呼び出す
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(countUp), userInfo: nil, repeats: true)
    }
    
    // 変数をインクリメントし、countLabelに表示
    @objc func countUp() {
        count += 1
        countLabel.text = String(count) // ラベルに表示
    }
    
    // 音声再生(引数でファイル名と拡張子を指定)
    func playMusicFile( file_name: String, ext: String) {
        if let url = Bundle.main.url(forResource: file_name, withExtension: ext) {
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: url)
                audioPlayer?.play() // 音声再生する
            } catch {      // プレイヤー作成失敗時
                audioPlayer = nil
            }
        } else {           // ulrがnilの場合など
            fatalError("Url is nil.")
        }
    }
}

以上、記録でした。