LoginSignup
4

More than 1 year has passed since last update.

SwiftUIを使って学習がてらタイマーアプリを作ってみよう5

Last updated at Posted at 2021-07-05

SwiftUIを使って学習がてらタイマーアプリを作ってみよう Part5

前置き 

前回投稿したPart4の続きです。
今回のゴールとしては
端末にプリインストールされているミュージックアプリで再生できる音楽を選択して、計測終了時に流せるようになるところまでです。
次回はアプリがバックグランドに行ってもタイマー処理が止まらないようにする所をやっています

開発環境

Mac (OS Big Sur version:11.3.1)
Xcode (version:12.5)
Swift (version: 5.4)
対象IOS(version:14.5以上)

今回の作業

音を再生させる処理を調べてみた。

簡単に調べてみたところ、方法としては下記の三つが見つかった。
・MPMusicPlayerController
・AVAudioPlayer
・AVPlayer
このうち
MPMusicPlayerControllerはOS 3.0の時から使える物で
他二つはOS 6.0以降に使えるようになった比較的新しいやつ。

それぞれの違いをざっくり上げてみると
MPMusicPlayerController →

ミュージックアプリみたいに端末にDLしてないApple Musicで「マイミュージックに追加」したアイテムも再生できる。
ただし、ホームシェアリングのアイテムを除く。

AVAudioPlayer →

再生速度とかループ回数等の設定を指定して再生ができる。
しかし、端末にDLしてあるアイテムしか再生ができない(オンラインで購入済みのアイテムでも端末にDLしてないと使えない)

AVPlayer →

音と画像が再生できる。
assetと言う単位を扱っている。動画のの一部分を複数くっつけてassetとして扱ったりできる
しかし、AVAudioPlayerと同様に端末にDLしてあるアイテムしか再生ができない(オンラインで購入済みのアイテムでも端末にDLしてないと使えない)

今回は別に音のみ再生できれば良く、DLされていない音を使えなくとも良いのでAVAudioPlayerを使ってみました。

どうやってミュージックアプリみたいに選曲をさせるか

調べるとMPMediaPickerControllerを使えばできるっぽいのでこれを試してみた。
*実機じゃないとデバックできないっぽい(少なくともデフォルト状態のエミュレータとかでは出来なかった)

Step1 権限追加

info.plistで権限要求を定義して起動後に権限を認証しないといけない

・手順
  1. Xcodeでinfo.plistを開く
  2. 項目KeyにあるInformation Property List を選択
  3. 手順2をを行うことで表示される「+’ボタンをタップ
  4. Information Property Listに項目を追加することができるようになるので下記の二つを追加する
    1. Privacy - Media Library Usage Description
    2. Privacy - Music Usage Description

今回は手順4で追加したものだけでOK

Step2 音データを選択できるようにする

端末内の音楽データ(ミュージックアプリとかで使えるやつ)を取得するのにMPMediaPickerControllerが使えるらしいので実装
*ここからは実機がないとテストできなくなるので注意

MusicPicker.swift
import SwiftUI
import MediaPlayer

struct MusicPicker: UIViewControllerRepresentable {
    @Binding var song: String

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    // View作成
    func makeUIViewController(context: UIViewControllerRepresentableContext<MusicPicker>) ->  MPMediaPickerController {
        let picker = MPMediaPickerController()
        // 複数選択可否設定 true:可(複数) false: 不可(単体)
        picker.allowsPickingMultipleItems = false
        // クラウドアイテムの表示設定
        picker.showsCloudItems = false

        // delegate ざっくり言うとイベントが発火された時に呼ばれる処理
        picker.delegate = context.coordinator
        picker.prompt = "test"
        return picker
    }

    // View更新
    func updateUIViewController(_ uiViewController: MPMediaPickerController, context: Context) {

    }

    class Coordinator: NSObject, UINavigationControllerDelegate, MPMediaPickerControllerDelegate{
        var parent: MusicPicker
        init(_ parent: MusicPicker){
            self.parent = parent
        }

        // MediaPicker が閉じた時に呼ばれるぽい
        func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
            let selectedSongs = mediaItemCollection.items
            // 選択された曲数チェック()
            if selectedSongs.count > 0 {
                //allowsPickingMultipleItemsで単体指定にしているから必ずIndex:0 にアイテムがある想定
                if selectedSongs[0] != nil {
                    parent.song = selectedSongs[0].title ?? "no title"
                }else{
                    parent.song = "not select Music"
                }
            }
            // 単体指定の場合選択するたびに呼ばれるのでここでpickerを閉じる 
            mediaPicker.dismiss(animated: true)
        }        
    }

}

・手順

1.SwiftUIでUIViewControllerRepresentableを継承した構造体を作成する

struct MusicPicker: UIViewControllerRepresentable

その際にMediaPlayerをimport しないと MPMediaPickerControllerとかUIViewControllerRepresentableが使えない
2.MPMediaPickerControllerを実装する
UIViewContorollerをSwiftUIで使う方法とかは公式のチュートリアルを見てください。

MPMediaPickerControllerのパラメータのざっくりとした説明

パラメータ 説明
allowsPickingMultipleItems アイテムを複数選択か単体選択か設定する
showsCloudItems クラウドアイテムを選択肢として表示するか否か

選択した曲情報は

func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection)

内の

 mediaItemCollection.items

に格納されている
3.SettingView.swiftからMusicPicker()を呼び出す
MusicPickerを呼び出すときはStatusタグをつけた変数の参照渡しをしてやる必要がある。

Step3 取得した音情報から再生を行う

AVAudioPlayerを実装して再生を実行させる。

・手順
  1. MusicPicker.swiftで内の曲のタイトルを入れていた変数をStringからBindingタグを付けたMPMediaItem型(nil許容)に変更する

@Binding var song: String

@Binding var songInfo: MPMediaItem?

これはTimerModelで音再生処理を行なうために音声データ情報を共有するために行う
2.TimerModelにAVAudioPlayerを実装

TimerModel.swift
              
    // Playerの作成
    func createAudioPlayer(){
        if let url: URL = songInfo?.assetURL {
            do{
                avAudioPlayer = try AVAudioPlayer(contentsOf: url)
            } catch{
                // エラーが発生してプレイヤーが生成失敗時
                avAudioPlayer = nil
            }
        } else {
            // URLが存在していない
            avAudioPlayer = nil
        }
    }


    // 音楽の再生
    func playSound(){
        if let player = avAudioPlayer {
            // 音楽再生中なら止める
            if player.isPlaying {
                player.stop()
            }
            player.play()
        }
    }

    // 音楽の停止
    func stopSound(){
        if let player = avAudioPlayer {
            // 音楽再生中なら止める
            if player.isPlaying {
                player.stop()
            }
        }
    }

AVAudioPlayerはURLを使ってインスタンスを生成する必要がある。
ざっくり調べた限りでは生成後にURLをセットする関数が見つかんなかったので(getterはあった)

avAudioPlayer = try AVAudioPlayer(contentsOf: url)

参考元だとNSURLを使っているが自分の環境だとNSURLではなくてURLが必要となっていた。

3.任意の画面で初期化処理と再生停止処理の呼び出しを行う


引用・参考元

[iOS][Swift]ミュージックライブラリにアクセスして音楽を再生する(MPMusicPlayerController使用)
[iOS][Swift]ミュージックライブラリにアクセスして音楽を再生する(AVAudioPlayer使用)
Using MPMediaItems with AVAudioPlayer
SwiftUIでMPMediaPickerController

シリーズ:「SwiftUIの学習でタイマーアプリを作ってみよう」のリンク

part1:プロジェクトの作成とGitHubに登録
part2: Timerを利用したカウント処理の実装
part3:計測画面でのプログレスバーの実装
part4:スピナー実装
part5:ライブラリから音楽を選択して再生 ←今ここ
part6:タイマー処理のバックグランド対応
part7:音楽のバックグランド再生対応
part8:PageViewを作成
part9:PageViewのインジゲータを作成

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
4