mrle4w
mrle4w

Reputation: 103

Cannot assign to property: 'self' is immutable in UIViewControllerRepresentable

I have the following code snippet:

struct player : UIViewControllerRepresentable {
    
    var url : String
    var player1: AVPlayer
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<player>) -> AVPlayerViewController {
        let controller = AVPlayerViewController()
        player1 = AVPlayer(url: URL(string: url)!)
        controller.player = player1
        return controller
    }
    
    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<player>) {
    }
    
    func pause() {
        player1.pause()
    }
}

This gives the error:

'Cannot assign to property: 'self' is immutable'

I need to have the AVPlayer outside of the makeUIViewController function because I need to access it from the pause function. How can I do this?

Upvotes: 3

Views: 1404

Answers (2)

LuLuGaGa
LuLuGaGa

Reputation: 14388

The error you are seeing appears due to the fact that structs are value types and any method on them that changes their properties needs to be marked as mutating. Unfortunately you cannot mark makeUIViewController because it is defined in UIViewControllerRepresentable protocol, but there is a fairly easy solution.

You only actually use the url to construct an AVPlayer - there is no need to hold on to it. Write and initializer for your struct that takes a url string and constructs an AVPlayer. I have made the AVPlayer optional as URL(string: String) returns an Optional URL (as you can imagine not all strings are valid urls). The below code works as expected:

struct Player: UIViewControllerRepresentable {

    var player1: AVPlayer?

    public init(url string: String) {
        guard let url = URL(string: string) else {
            self.player1 = nil
            return
        }
        self.player1 = AVPlayer(url: url)
    }

    func makeUIViewController(context: UIViewControllerRepresentableContext<Player>) -> AVPlayerViewController {
        let controller = AVPlayerViewController()
        controller.player = player1
        return controller
    }

    func updateUIViewController(_ uiViewController: AVPlayerViewController,
                                context: UIViewControllerRepresentableContext<Player>) {}

    func pause() {
        player1?.pause()
    }
}

A side note: all type names in Swift (classes, structs, enums) are by convention upper-cased: your struct should be called Player not player. You should also look into Cordinator for UIViewControllerRepresentable - you need something to act as your AVPlayerViewControllerDelegate.

Upvotes: 3

Asperi
Asperi

Reputation: 257819

There are several possibilities to choose from (actually it is not a complete list of options, but hope it would be helpful)

Option1: Do it in initailizer

struct player : UIViewControllerRepresentable {

    private var url : String
    private var player1: AVPlayer

    init(url: String) {
        self.url = url
        self.player1 = AVPlayer(url: URL(string: url)!)
    }

    func makeUIViewController(context: UIViewControllerRepresentableContext<player>) -> AVPlayerViewController {
        let controller = AVPlayerViewController()
        controller.player = player1
        return controller
    }

    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<player>) {
    }
}

Option2: Do it on first request to update controller

struct player : UIViewControllerRepresentable {

    var url : String
    var player1: AVPlayer

    func makeUIViewController(context: UIViewControllerRepresentableContext<player>) -> AVPlayerViewController {
        return AVPlayerViewController()
    }

    func updateUIViewController(_ playerController: AVPlayerViewController, context: UIViewControllerRepresentableContext<player>) {
        if playerController.player == nil {
            playerController.player = AVPlayer(url: URL(string: url)!)
        }
    }
}

Option3: If you need to modify player1 during lifetime, then you need to keep it in some external entity, say ObservableObject, because your player is View struct and cannot modify itself, actually what is said in compiler error.

Upvotes: 1

Related Questions