- 投稿日:2020-08-24T21:02:10+09:00
Xcodeの開発で、ファイルを保存するだけで動的に中身が書き換えられるようにしてみた
Xcodeの開発で、通常であれば、ファイルを保存し、ビルドし、プログラムを起動するという手順をとる。プログラムを起動した状態で、終了させずに、その中身を書き換えるということは、通常はできない。だが、injectionという仕組みを使うと、できるようになる。
1. 「InjectionIII」を使ってみる
John Holdsworth氏により、「InjectionIII」というアプリが公開されている。これを使うと、Injectionを簡単に実行できるようになっている。
https://github.com/johnno1962/InjectionIII
以下、使ってみる。1.1. アプリをインストール
InjectionIIIアプリをインストールする。
https://apps.apple.com/jp/app/injectioniii/id1380446739?mt=12インストールされたら、実行する→Status menuにInjectionIIIアプリのアイコンが表示されたことを確認する→「Help/README」を選ぶと、以下のページに接続される。
https://github.com/johnno1962/InjectionIII1.2. サンプルをダウンロードする
以下、Injection IIIのホームページである。
http://johnholdsworth.com/injection.html以下より、サンプルプログラムをダウンロードできる。
http://johnholdsworth.com/GettingStarted.zip
解凍し、「~/dev/GettingStarted/
」となるようにする。1.3. Xcodeからサンプルを開く
GettingStarted.xcodeproj
をダブルクリックして起動。→Open1.4. InjectionIIIアプリから、GettingStartedを接続する
Status menuのInjectionIIIから「Open project」を選択→「
~/dev/GettingStarted/
」を選択→「Select Project Directory」1.5. 実行する
Cmd-Rで実行→シミュレーターに「Master」と表示される→「+」を押すと、現在時刻が表示される。それをクリックする→現在時刻と「CHANGEME」が表示される。
Xcodeに戻る→Cmd-1→
DetailViewController.swift
を選択→" CHANGEME"
の個所を、たとえば" CHANGED!"
に変更する→Cmd-Sでセーブとすると、即座に画面上の「CHANGEME」だったところが、「CHANGED!」に変わる。これがインジェクションである。2. 独自のプログラムからインジェクションしてみる
2.1. なにかアプリを作る
まずは、なにかシンプルなアプリを開発する。文字が表示されるものが良い。
Xcodeを起動→Create a new Xcode Project→iOS→「Single View App」→Next→Product Name:「InjectionTest」、User Interface: Storyboard→Next→「~/dev」を指定→Create
ViewController.swiftに、以下のようにshow()を追加。viewDidLoad()から呼ばれるようにする。ViewController.swiftclass ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() show() } func show() { let button = UIButton(frame: CGRect(x: 40, y: 100, width: 200, height: 100)) button.backgroundColor = .cyan button.setTitle("Hello, world!", for: .normal) button.setTitleColor(.black, for: .normal) view.addSubview(button) } }まずはこの段階で実行してみる。Cmd-R→ビルドされ、実行される。Simulatorが起動して、「Hello, world!」が表示される。
ViewController.swiftに戻り、試しに
"Hello, world!"
を"Hello, Japan!"
に修正してCmd-Sで保存しても、反映されない。当然である。再度Cmd-Rすると、一旦シミュレーター上のアプリが終了し、再度立ち上げられ、今度は
"Hello, Japan!"
に修正されている。3秒程度で立ち上がる。通常は、このようにアプリを終了し、再読み込みするという手順で開発する。ビルドが早いので、3秒程度でこのサイクルは実行できる。実用上はあまり不満は持たれないかもしれない。2.2. Linker Flagsを設定する
Xcodeに戻る。Cmd-1→InjectionTestのプロジェクトを選択→PROJECT: InjectionTest→Build Settings→Linking→Other Linker Flags→ここにカーソルを乗せると左に三角が表示される→クリックする→Debugの右の「+」をおす→Debug→Any Architecture | Any SDK:「-Xlinker -interposable」→リターンを押すと確定する
2.3. Bundleを追加
AppDelegate.swiftにBundleを追加する。
AppDelegate.swift#if DEBUG Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load() #endif参考までに、AppDelegate.swiftの該当するメソッド全体である。
AppDelegate.swiftfunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { #if DEBUG Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load() #endif return true }2.4. injected()を追加
ViewController.swift
に、injectedというメソッドを追加する。ViewController.swift@objc func injected() { show() }参考までに、ViewController classの全体である。
ViewController.swiftclass ViewController: UIViewController { @objc func injected() { show() } override func viewDidLoad() { super.viewDidLoad() show() } func show() { let button = UIButton(frame: CGRect(x: 40, y: 100, width: 200, height: 100)) button.backgroundColor = .cyan button.setTitle("Hello, world!", for: .normal) button.setTitleColor(.black, for: .normal) view.addSubview(button) } }2.5. InjectionIIIにProjectを指定する
Status menuのInjectionIIIから「Open project」を選択→「~/dev/InjectionTest」を選択→「Select Project Directory」
2.6. 起動する
Cmd-R→Simulatorが起動して、「Hello, world!」が表示される。以下のように出力される。
? Injection connected ? ? Watching /Users/eto/dev/InjectionTest/**2.7. 編集する
ViewController.swiftで、該当する行を以下のように編集してみる。
ViewController.swiftbutton.setTitle("Hello, Japan!", for: .normal)Cmd-Sで、ファイルをセーブする。そうすると、シミュレーター上の「Hello, world!」が、即座に(1秒以内くらいで)「Hello, Japan!」に変わる。以下のように出力される。
? *** Compiling /Users/eto/dev/InjectionTest/InjectionTest/ViewController.swift *** ? Loading .dylib ... objc[31231]: Class _TtC13InjectionTest14ViewController is implemented in both /Users/eto/Library/Developer/CoreSimulator/Devices/97670822-70F9-46B8-87F7-5545DF54E516/data/Containers/Bundle/Application/82DDD3CB-9924-4E5C-BCCC-1AE2A8A9E3AD/InjectionTest.app/InjectionTest (0x107b53b40) and /var/folders/94/shwk5bk14l5fx43cggr_n04m0000gn/T/com.johnholdsworth.InjectionIII/eval106.dylib (0x110d9c280). One of the two will be used. Which one is undefined. ? Loaded .dylib - Ignore any duplicate class warning ^ ? Injected 'ViewController' ? Replacing InjectionTest.ViewController.__allocating_init(coder: __C.NSCoder) -> Swift.Optional<InjectionTest.ViewController> ? Replacing InjectionTest.ViewController.__allocating_init(nibName: Swift.Optional<Swift.String>, bundle: Swift.Optional<__C.NSBundle>) -> InjectionTest.ViewController ? Replacing InjectionTest.ViewController.viewDidLoad() -> () ? Replacing InjectionTest.ViewController.show() -> () ? Replacing InjectionTest.ViewController.injected() -> () ? Class ViewController has an @objc injected() method. Injection will attempt a "sweep" of all live instances to determine which objects to message. If this crashes, subscribe to the global notification "INJECTION_BUNDLE_NOTIFICATION" to detect injections instead.内部で起こっていることを説明すると、ViewController.swiftを常にウォッチし、編集されたのを検知したら、即座にそれをCompileし、動的にロードし、メソッドを置き換えている。その後、injected()が呼ばれ、表示が切り替わる。このようにすると、プログラムを実行している間に、動的にメソッドを書き換えできるため、プログラム開発効率が高まると考えられる。
ここまでのファイルを、以下に置く。このまま実行できるはずである。
https://github.com/eto/InjectionTestdone!
- 投稿日:2020-08-24T19:23:00+09:00
iOSアプリで音楽や動画のバックグラウンド再生の実装方法(他アプリ音声を停止しないやり方も)
AVPlayerやAVAudioPlayerなどで音声再生や動画再生の実装をする場合デフォルトのままだとアプリをバックグラウンドへ移行したりスリープをした時に再生が自動で停止します。
以下の実装を行うことで再生を継続させることができます。簡単な手順でOKです。
手順1
- プロジェクトファイルを開く
- メニュー -> Editor -> Add Capability -> Background Modesを選択
- Audio, AirPlay, and Picture in PictureのチェックマークをONにする
手順2
プロジェクト内の任意の場所に以下のコードを書きます
// アプリ初期化時等 do { try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback) } catch _ { NSLog("audio session set category failure") } // 音声、動画再生時 do { try AVAudioSession.sharedInstance().setActive(true) } catch _ { NSLog("audio session active failure") }注意点として、上記の実装をしているともし別のアプリで音声を再生している場合
上記のsetActiveが呼び出された時点で別アプリの音声再生が停止します。
なのでsetActiveは自分のアプリでの音声再生を開始する時点で呼ぶのが望ましいです。他アプリの音声再生を停止せずに再生する方法
AVAudioSessionのsetCategory時にoptionを設定します
do { try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, options: [AVAudioSession.CategoryOptions.mixWithOthers]) } catch _ { NSLog("audio session set category failure") }上記はmixWithOthersを指定しましたが、duckOthersというのも存在します
違いは
- mixWithOthers → 他アプリの音声の音量を下げない
- duckOthers → 他アプリの音声の音量を下げる
場面に応じて使い分けましょう!他のoption
- allowBluetooth → 音声再生時にはこのoption指定は無視される(bluetoothイヤホンを利用できるようになるとかそういうものではない)
- defaultToSpeaker → イヤホンやヘッドセットを利用しない場合にいわゆる電話の耳を当てる部分から音が出るのがデフォルトになっているが、それをスピーカーから音が出る状態にするoption公式ドキュメント:
AVAudioSession終わりです
Brewus,Inc.
株式会社ブリューアス
https://brewus.co.jp
- 投稿日:2020-08-24T19:04:10+09:00
[Swift] textFieldのclearButtonMode
textField
は中身を全部消すことができるclearButton
が実装できます。
画像の右端にあるバツマークです
これの表示パターンは
clearButtonMode
の設定で変更することができます。.always
.always
はtextFieldに文字列があれば常に出現します。textField.clearButtonMode = .always.never
.never
はボタンを表示しませんtextField.clearButtonMode = .never.unlessEditing
.unlessEditing
はtextFieldに文字列があり、textFieldが選択されていない時に出現しますtextField.clearButtonMode = .unlessEditing.whileEditing
.whileEditing
はtextFieldに文字列があり、textFieldが選択されている時に出現しますtextField.clearButtonMode = .whileEditingstoryboard上での実装
textFieldのClearButtonの部分を変更すれば実装できます
- 投稿日:2020-08-24T18:58:18+09:00
【iOS14】WidgetKitのテンプレートを読み解く
はじめに
9月リリースが噂されているiOS14から、Widgetが追加されます。
表現が固定されていたホーム画面での体験が大きく変わる機能であり、
多くの人が注目しているかと思います?✨よし!自分のアプリにもWidgetを追加しよう!と手を動かそうとしている皆さんに向けて、
既存アプリへの追加手順の紹介と、テンプレートコードの読み解きをします。本記事は、最低限の挙動と実装を確認したい人に向けての解説記事です。
WidgetKitの利用シーンやWidgetKitの詳しい解説はこちらの記事に丁寧にまとまっているので、割愛させていただきます。
https://qiita.com/shiz/items/309349d9cdb75084e74e※本記事はXcode12 beta5時点の情報です、beta版の開発画面のスクリーンショットはNDA締結により掲載しておりません。
1.追加手順
Xcodeを開き
- File > New > Target を選択
- Application Extension group セクションから Widget Extension を選択
- Extension の名前をつける
リファレンスには
4. If the widget provides user-configurable properties, check the Include Configuration Intent checkbox.
とありますが、beta5では選択肢出てこず(自分だけ…?)、デフォでuser-configurableのテンプレートが生成されました。公式リファレンス: https://developer.apple.com/documentation/widgetkit/creating-a-widget-extension
また、Application ExtensionごとにiOS Deployment Targetを設定できるので
iOS14未満をサポートしている場合も対応できそうです。
自分はiOS13対応アプリで正常挙動を確認しました。
(もちろんiOS13ではWidgetは利用できません、iOS12以下未検証)CharacterWidgetと命名して追加すると
CharacterWidget.swift
CharacterWidget.intentdefinition
Assets.xcassets
Info.plist
のファイルが用意されます。
(Character画像をランダムに表示するWidgetを実装しようと思った命名)2.テンプレートコードの読み解き
CharacterWidget.swift
のコード内に日本語コメントを含めながら
Widgetを構成する要素を説明します。Widget
まずはWidgetを定義します。
テンプレートでは端末に基づいた時間が表示されるよう実装されていました。@main struct CharacterWidget: Widget { let kind: String = "CharacterWidget" // widgetの識別子、何をするwidgetか説明すべき var body: some WidgetConfiguration { // Configuration // StaticConfigration: ユーザが設定不可(ex.株価, ニュース) // IntentConfiguration: ユーザが設定可(ex. 場所ごとの天気, 荷物追跡): ← テンプレはこちらが実装されているが時間の表示だけであればStaticで良い IntentConfiguration( kind: kind, intent: ConfigurationIntent.self, // ユーザが設定可能なプロパティを定義するカスタムインテント、.intentdefinition からコード生成される provider: Provider() // Timeline セクションで解説 ) { entry in // SwiftUIのViewを含むクロージャー、ProviderからTimelineEntryパラメーターが渡される CharacterWidgetEntryView(entry: entry) } .configurationDisplayName("? Widget") // Widgetギャラリー(設定画面)にてユーザに表示するタイトル .description("This is ? widget.") // 同上メッセージ // サイズの設定も加えることができる、デフォルトは3つ全て .supportedFamilies([.systemSmall, .systemMedium, .systemLarge]) } }
ConfigurationIntent
は、例えばユーザがアプリ内で選択したキャラクターのWidgetを表示したい場合などに実装します。
テンプレートではデフォルトで実装してあるが特に利用していないので、ユーザ設定が必要ない場合はStaticに書き換えてください。StaticConfiguration( kind: kind, provider: StaticProvider() ) { entry in CharacterWidgetEntryView(entry: entry) }公式リファレンス: https://developer.apple.com/documentation/widgetkit/intentconfiguration
TimelineProvider
WidgetではTimelineという、いつどのViewを表示するか管理する仕組みを実装します。
まずはEntryを定義します。struct SimpleEntry: TimelineEntry { let date: Date // 表示する時間 let configuration: ConfigurationIntent }続いて、IntentTimelineProviderの実装です。
テンプレートでは1時間おきに5回、時間を更新して表示されるよう実装されていました。struct Provider: IntentTimelineProvider { /// ユーザ設定可能の場合、初めて表示する時に表示されるプレースホルダー /// ウィジェットが何を表示するかについて一般的な考えをユーザに提供するべき func placeholder(in context: Context) -> SimpleEntry { SimpleEntry(date: Date(), configuration: ConfigurationIntent()) } /// 表示するSnapshotを定義 func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) { let entry = SimpleEntry(date: Date(), configuration: configuration) // context.isPreview = true の時、widget galleryに表示されているので、サーバーからの取得がある場合はフラグを立ててローディング表示など実装するとよい // ex) if context.isPreview && !hasFetchedGameStatus { ... } completion(entry) } /// タイムラインを定義 func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { var entries: [SimpleEntry] = [] // 現在の時間から開始して、1時間おきに切り替わる5つのEntryで構成されるタイムラインを生成 let currentDate = Date() for hourOffset in 0 ..< 5 { // hourOffset時間後の時間を生成 let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! let entry = SimpleEntry(date: entryDate, configuration: configuration) entries.append(entry) } // 配列に含めてTimelineにする let timeline = Timeline(entries: entries, policy: .atEnd)// policy: .atEnd タイムライン終了後新しいタイムラインを要求するポリシー completion(timeline) } }Timelineのpolicyは.adEndの他に
- never: 新しい要求がくるまで何もしない
- after(_ date: Date): タイムラインをリクエストする日時を指定
を選択できます。
こちらもIntentを利用しない場合はStaticに書き換えてください。
struct StaticProvider: TimelineProvider { typealias Entry = SimpleEntry func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> Void) { let entry = SimpleEntry(date: Date()) completion(entry) } func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) { var entries: [SimpleEntry] = [] let currentDate = Date() for hourOffset in 0 ..< 5 { let entryDate = Calendar.current.date(byAdding: .minute, value: hourOffset, to: currentDate)! let entry = SimpleEntry(date: entryDate) entries.append(entry) } let timeline = Timeline(entries: entries, policy: .never) completion(timeline) } }View
最後に、実際に表示するWidgetを実装します。
テンプレートではシンプルにTextでentry.dateを表示するだけだったので、
3つのWidget family(サイズ)ごとの実装について追記しました。/// widgetのView、SwiftUIで実装する struct CharacterWidgetEntryView : View { @Environment(\.widgetFamily) var family: WidgetFamily// Widget familyをサポートする var entry: Provider.Entry @ViewBuilder // viewにバリエーションがある時宣言 var body: some View { // サイズごとにViewを指定する実装 switch family { case .systemSmall: Text(entry.date, style: .time); Text("small") case .systemMedium: Text(entry.date, style: .time); Text("medium") case .systemLarge: Text(entry.date, style: .time); Text("large") @unknown default: fatalError() } } }SwiftUIで実装するのでもちろんPreviewもできます。
struct CharacterWidget_Previews: PreviewProvider { static var previews: some View { CharacterWidgetEntryView(entry: SimpleEntry(date: Date())) .previewContext(WidgetPreviewContext(family: .systemSmall)) } }以上です。大まかな流れが掴めていると幸いです?♂️
3.intentdefinition
CharacterWidget.intentdefinition
からテンプレートで実装されているConfigurationIntent周りのコードが自動生成されます。
Siri Shortcutsを実装したことがある方なら馴染みがあるかもしれません。テンプレでは用意されているだけで利用していないので割愛しますが、
需要があればこちらについても解説する投稿をしようかなと思います。
https://developer.apple.com/documentation/widgetkit/making-a-configurable-widgetおわりに
Widgetのサンプルコードも用意されているのでぜひぜひ一度動かしてみて、
ユーザにとって需要のある機能実装の模索をしてみましょう?
- 投稿日:2020-08-24T18:00:11+09:00
iPhoneX系の画面の下のバー(Home Indicator)を非表示にする方法
画面の下にいつもいるバー
Home Indicatorという名前です。
iPhoneXが発売されて数年、私は知りませんでした。
全画面でコンテンツを表示したい時にこのバーだけ表示されてしまっていると見た目が悪いですよね。但し常に非表示にしておくということはできませんが操作をしていない場合に非表示になってくれる実装方法があります。
非表示にしたいViewControllerに以下の一行を追加します。
ViewController.swiftoverride var prefersHomeIndicatorAutoHidden: Bool { true }公式ドキュメント:
prefersHomeIndicatorAutoHidden常に表示するという状態と、操作していない場合は非表示にしたい、という二つの状態を切り替えたいというときは以下のような実装をします。
ViewController.swiftprivate var indicatorAutoHidden:Bool = false { didSet { self.setNeedsUpdateOfHomeIndicatorAutoHidden() } } override var prefersHomeIndicatorAutoHidden: Bool { return self.indicatorAutoHidden }setNeedsUpdateOfHomeIndicatorAutoHiddenを呼び出すことですぐさま表示、非表示状態の更新を切り替えることができます。
公式ドキュメント:
setNeedsUpdateOfHomeIndicatorAutoHidden終わりです
Brewus,Inc.
株式会社ブリューアス
https://brewus.co.jp
- 投稿日:2020-08-24T16:44:54+09:00
Literal について
- 投稿日:2020-08-24T10:27:06+09:00
ViewとしてのSwiftUI
SwiftUIを今までのXibやStoryboardの代わりとして使うための方法をまとめました。
Playgroundで実行します。
ViewControllerを使わない、SwiftUIとModelだけのアーキテクチャにはまだ慣れないですが、コードレイアウトとしてのSwiftUIはメリットが大きいので、取り入れていきたいです。登場人物と関係
クラス 説明 MyViewController SwiftUIで作ったViewを表示するためのViewController ContentView SwiftUI ContentViewDelegate ボタンをタップした時のdelegate ViewModel タップ回数の変数を格納する ObservableObject
それぞれの主な役割は以下の図です。
基本的なMVCです。
実装
import UIKit import PlaygroundSupport import SwiftUI class ViewModel: ObservableObject { @Published var count: Int = 0 func increment() { count += 1 } } protocol ContentViewDelegate: AnyObject { func didTapAdd() } struct ContentView: View { @ObservedObject var viewModel: ViewModel = .init() weak var delegate: ContentViewDelegate? var body: some View { VStack(spacing: 16) { Text("count : \(self.viewModel.count)") Button.init("Add") { debugPrint("Add") self.delegate?.didTapAdd() } } } } class MyViewController : UIViewController { private var contentView = ContentView.init() override func loadView() { super.loadView() view.backgroundColor = .white contentView.delegate = self let hostingVC = UIHostingController.init(rootView: contentView) hostingVC.view.frame = view.bounds view.addSubview(hostingVC.view) debugPrint(view.subviews) self.view = view } } extension MyViewController: ContentViewDelegate { func didTapAdd() { debugPrint("didTapAdd") contentView.viewModel.increment() } } // Present the view controller in the Live View window PlaygroundPage.current.liveView = MyViewController()ソース
https://gist.github.com/usk2000/394d70cba327e095f2b8002542721697
- 投稿日:2020-08-24T02:06:38+09:00
AppleMusic連携で曲の固有のIDからMPMediaItemを取得したり曲を再生する時の知見
Media Player のFrameworkを使ってAppleMusic連携のアプリを作りたい。
しかしこんなんドキュメント見ても分からんだろっていう落とし穴があったりするので、いろいろ試してわかった知見を書く。曲固有のID
単一の曲を表すMPMediaItemにはいろいろとプロパティがあるが、曲を区別するためのIDは次の二つがある。似ているようで役割が違う。
persistentID
自分のライブラリ内の固有のID(と思われる)
このIDがあれば、MPMediaItemは取得できる。次のように、
MPMediaPropertyPredicate
,MPMediaQuery
を使ってpersistentIDからMPMediaItemを取得する。
MPMediaItemPropertyPersistentID
というプロパティを使って絞り込もう。private func findMPMediaItem(persistentIDString: String) -> MPMediaItem? { let predicate = MPMediaPropertyPredicate(value: persistentIDString, forProperty: MPMediaItemPropertyPersistentID) let songQuery = MPMediaQuery() songQuery.addFilterPredicate(predicate) if let items = songQuery.items { return items.first } else { return nil } }playbackStoreID
こちらはApple MusicやiTunes全体で固有のID(と思われる)
iTuneSearchAPIを例にすると、このようにAPIを叩けば曲の情報が取得できる。
$ curl "https://itunes.apple.com/lookup?id=1513201190" | jqまた、Apple Music APIでも曲の情報を取得できるが、こちらはtokenが必要だったりするので省略する。
ただしこのIDからMPMediaItemは取得できない!!?
↑わたしが調べる限り2020年8月時点では不可能っぽい。使えるようになったら教えてほしい。?
MPMediaItemPropertyPlaybackStoreID
といういかにも使えそうなプロパティはあるのだが、使うと次のようなエラーで怒られるぞ。stackoverflowにある通り、現状は取得する手段がない。"MPMediaPropertyPredicate cannot filter using the playbackStoreID property"
曲を再生したい場合は?
persistentIDを使う場合
曲を再生する時はMPMusicPlayerControllerのこれをつかう
open func setQueue(with itemCollection: MPMediaItemCollection)次のようにMPMediaItemからMPMediaItemCollectionを作って、セットすれば再生できる
//itemsは↑みたいな方法で取得しとく let mediaItemCollection = MPMediaItemCollection(items: items) let musicPlayer: MPMusicPlayerController = MPMusicPlayerController.systemMusicPlayer musicPlayer.setQueue(with: mediaItemCollection)playbackStoreIDを使う場合
こちらの場合、MPMediaItemは取得できない
しかし曲の再生はできる。
MPMusicPlayerControllerのこれをつかうopen func setQueue(with storeIDs: [String])次のように書けばok
let musicPlayer: MPMusicPlayerController = MPMusicPlayerController.systemMusicPlayer musicPlayer.setQueue(with: ["1513201190"])まとめ
persistentID
は自分のライブラリ固有のIDで、MPMediaItemを取得できる。
playbackStoreID
はApple MusicやiTunes全体で固有のIDで、API利用して曲の情報を取得できる。Apple Music APIを使ったアプリを作りたい時は
playbackStoreID
を使うと良さそう。
Swiftでネイティブアプリを作りたい時は、persistentID
とplaybackStoreID
どう使うか考えると良さそう。参考