SwiftUI 中的 MPMediaPickerController?

MPMediaPickerController in SwiftUI?

我正在 SwiftUI 重建我的一个应用程序,但我遇到了障碍。

我的问题是..我的应用程序是一个 AudioVisual 测试生成器,目前能够 select 用户设备音乐库中的测试歌曲,并将该歌曲设置为应用程序的测试歌曲.其他测试包括左扬声器、右扬声器等,但此功能允许用户 select 从他们设备的库中选择他们自己的自定义测试歌曲,(或者默认为默认测试歌曲。)

我的应用程序目前是用 Swift 编写的,并使用 MPMediaPickerController 来 select 这首歌,它工作得很好。但是,我真的很难让音乐库选择器控制器与我的 SwiftUI 重建一起工作。任何人都知道在 SwiftUI?

中访问用户音乐库的可靠方法

我一直被不符合 class 协议 'NSObjectProtocol' 的对象所困扰,这让我相信有更多的 SwiftUI-y 方式来做到这一点?或者,也许我可以对我的大部分应用程序使用 SwiftUI,但过渡到歌曲 selection?

的 UIView

这里有一些代码还不能工作,但很漂亮。我只是尽我所能解决这个问题,并计划在我让它工作时进行重构。

//可能导入的东西比我需要的多。

import UIKit
import SwiftUI
import AVFoundation
import AVKit
import MediaPlayer



class SongPickerController: UIViewControllerRepresentable, MPMediaPickerControllerDelegate {
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let imagePickerController = UIImagePickerController()
        imagePickerController.delegate = context.coordinator
        return imagePickerController
    }
    
    func makePickerController(context: Context) -> MPMediaPickerController {

        var picker = MPMediaPickerController()
        picker = MPMediaPickerController(mediaTypes: .anyAudio)
        //picker.delegate = self
        picker.allowsPickingMultipleItems = false
        picker.showsCloudItems = false
        picker.prompt = NSLocalizedString(Texts.pickerDetail, comment: Texts.pickerComment)
        return picker
    }
           
        func songSelectButtonClicked () {
           picker = MPMediaPickerController(mediaTypes: .anyAudio)
            picker?.delegate = self
            picker?.allowsPickingMultipleItems = false
           picker?.showsCloudItems = false
           picker?.prompt = NSLocalizedString(Texts.pickerDetail, comment: Texts.pickerComment)

            self.present(picker!, animated: false, completion: nil)
    
    
    func updateUIViewController(_ uiViewController: MPMediaPickerController, context: Context) {
    }
    
    class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        var parent: SongPickerController
        
        init(_ imagePickerController: SongPickerController) {
            self.parent = imagePickerController
        }
        
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            picker.dismiss(animated: true, completion: nil)}}}}

struct SongUIView: UIViewController, MPMediaPickerControllerDelegate {

    var picker: MPMediaPickerController?

    func songSelectButtonClicked () {

        picker = MPMediaPickerController(mediaTypes: .anyAudio)
        picker?.delegate = self
        picker?.allowsPickingMultipleItems = false
        picker?.showsCloudItems = false
        picker?.prompt = NSLocalizedString(Texts.pickerDetail, comment: Texts.pickerComment)

        self.present(picker!, animated: false, completion: nil)
    }


    func mediaPicker(_ mediaPicker: MPMediaPickerController,
        didPickMediaItems mediaItemCollection: MPMediaItemCollection){
            let selectedSong = mediaItemCollection.items
            if (selectedSong.count) > 0 {
                let songItem = selectedSong[0]
                let songURL = songItem.value(forProperty: MPMediaItemPropertyAssetURL)
                let saveString = "\(songURL!)"
                let saveTitle = "\(songItem.title!) by \(songItem.artist!)"
                saveDefaultSong(saveString as NSString, title: saveTitle as NSString)
                mediaPicker.dismiss(animated: true, completion: nil)

                ///selectSongButton.setTitle("Custom song assigned!", for: UIControl.State())

            }
    }

    func mediaPickerDidCancel(_ mediaPicker: MPMediaPickerController) {
        mediaPicker.dismiss(animated: true, completion: nil)
    }

    func saveDefaultSong (_ name: NSString, title: NSString) {
        UserDefaults.standard.set(name, forKey: "Default Song")
        UserDefaults.standard.set(title, forKey: "Default Song Title")

    }
}

UIViewControllerRepresentable 是 SwiftUI 视图,所以必须是一个结构

struct SongPickerController: UIViewControllerRepresentable

但是 UIViewController 是 UIKit class,所以继承的实体必须是 class

class SongUIView: UIViewController

在用户 Asperi 将我推向正确的方向后,我想分享我的最终代码以在 SwiftUI 中呈现音乐库。这是第一部分...

import SwiftUI
import MediaPlayer

//MUSIC PICKER.. WORKING
struct MusicPicker: UIViewControllerRepresentable {
    @Environment(\.presentationMode) var presentationMode
    @Binding var song: Song?

    class Coordinator: NSObject, UINavigationControllerDelegate, MPMediaPickerControllerDelegate {
        var parent: MusicPicker

        init(_ parent: MusicPicker) {
            self.parent = parent
        }
    }

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

    func makeUIViewController(context: UIViewControllerRepresentableContext<MusicPicker>) -> MPMediaPickerController {
        let picker = MPMediaPickerController()
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController: MPMediaPickerController, context: UIViewControllerRepresentableContext<MusicPicker>) {

    }
}

然后为了在我的 SwiftUI 视图中调用它,我声明了一对 @State 变量

@State private var song: Song?
@State private var showingSongPicker = false

并使用此按钮显示媒体选择器

    Button(action: {
        self.showingSongPicker = true
    }
    ){
        Text("Edit Test Song")
    }
    .sheet(isPresented: $showingSongPicker) {
        MusicPicker(song: self.$song)
    }

注意:我仍在处理一些 Apple Music Token 问题,新测试歌曲的选择还没有用。但是呈现音乐选择器的部分是关于这个问题的,现在正在工作。

此解决方案几乎完全基于 Dave 在 7 月 28 日的 post。之前的代码还没有办法选择歌曲并将其传递给视图。在下面的示例中,它将选定的歌曲传递给 @EnvironmentObject,但它可以像 Dave 所建议的那样轻松地与 @Binding 一起使用。

import SwiftUI
import MediaPlayer

struct MusicPicker: UIViewControllerRepresentable {

    @Environment(\.presentationMode) var presentationMode
    @EnvironmentObject var player: AudioPlayer

    class Coordinator: NSObject, UINavigationControllerDelegate, MPMediaPickerControllerDelegate {
    
        var parent: MusicPicker

        init(_ parent: MusicPicker) {
            self.parent = parent
        }
    
        func mediaPicker(_ mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
                    
            let selectedSong = mediaItemCollection.items
        
            if (selectedSong.count) > 0 {
                let songItem = selectedSong[0]
                parent.setSong(song: songItem)
                mediaPicker.dismiss(animated: true, completion: nil)
            }
        }
    }

    func setSong(song: MPMediaItem) {
        player.setAudioTrack(song: song)        
    }

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

    func makeUIViewController(context: UIViewControllerRepresentableContext<MusicPicker>) -> MPMediaPickerController {
        let picker = MPMediaPickerController()
        picker.allowsPickingMultipleItems = false
        picker.delegate = context.coordinator
        return picker
    }

    func updateUIViewController(_ uiViewController: MPMediaPickerController, context: UIViewControllerRepresentableContext<MusicPicker>) {
    }

}