Search code examples
swiftuimodelvideo-player

Cannot use instance member 'videoName' within property initializer; property initializers run before 'self' is available


I'm quite new to SwiftUI and I'm running into this problem as I'm trying to display a video using videoName from Model. In the player = AVPlayer(...)(line 4), instead of finding the resource by the string "squats", I want to use videoName from Model. If I replace them I get the error

Cannot use instance member 'videoName' within property initializer; property initializers run before 'self' is available

Can anyone please help me?

Here is my code:

struct ExercisingSessionView: View {
    
    let exerciseName: String
    let videoName: String
    
    @State var player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: "squats", ofType: "mov")!))
    @State var isplaying = false
    @State var showcontrols = false
    
    var body: some View {
        
        CustomVideoPlayer(player: $player)
            .frame(width: 390, height: 219)
            .onTapGesture {
                self.showcontrols = true
                
            }
    }
    
    struct CustomVideoPlayer : UIViewControllerRepresentable {
        
        @Binding var player: AVPlayer
        
        func makeUIViewController(context: UIViewControllerRepresentableContext<CustomVideoPlayer>) -> AVPlayerViewController {
            
            let controller = AVPlayerViewController()
            controller.player = player
            controller.showsPlaybackControls = false
            return controller
        }
        
        func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<CustomVideoPlayer>) {
            
        }
    }
}

Solution

  • Option 1:

    Create an initializer for your View that creates your @State initial value:

    struct ExercisingSessionView: View {
        
        let exerciseName: String
        let videoName: String
        
        @State var player : AVPlayer
        @State var isplaying = false
        @State var showcontrols = false
        
        init(exerciseName: String, videoName: String) {
            self.exerciseName = exerciseName
            self.videoName = videoName
            self._player = State(initialValue: AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: videoName, ofType: "mov")!)))
        }
        
        var body: some View {
            CustomVideoPlayer(player: $player)
                .frame(width: 390, height: 219)
                .onTapGesture {
                    self.showcontrols = true
                }
        }
    }
    

    The downside to this is if ExercisingSessionView gets initialized often (even if it doesn't get actually re-rendered to the view hierarchy), you're doing heavy lifting inside init, which is generally a pretty bad idea for performance.

    Option 2:

    Declare player as optional and load the initial value in onAppear:

    
    
    struct ExercisingSessionView: View {
        
        let exerciseName: String
        let videoName: String
        
        @State var player : AVPlayer?
        @State var isplaying = false
        @State var showcontrols = false
        
        var body: some View {
            Group {
                if let player = player {
                    CustomVideoPlayer(player: player)
                        .frame(width: 390, height: 219)
                        .onTapGesture {
                            self.showcontrols = true
                        }
                }
            }.onAppear {
                player = AVPlayer(url: URL(fileURLWithPath: Bundle.main.path(forResource: videoName, ofType: "mov")!))
            }
        }
    }
    
    struct CustomVideoPlayer : UIViewControllerRepresentable {
        var player: AVPlayer
        
        func makeUIViewController(context: UIViewControllerRepresentableContext<CustomVideoPlayer>) -> AVPlayerViewController {
            
            let controller = AVPlayerViewController()
            controller.player = player
            controller.showsPlaybackControls = false
            return controller
        }
        
        func updateUIViewController(_ uiViewController: AVPlayerViewController, context: UIViewControllerRepresentableContext<CustomVideoPlayer>) {
            
        }
    }
    

    This avoids the issue in option 1 because onAppear will only be called once.

    Note that here, I've made player inside CustomVideoPlayer a regular, non-binding property -- because AVPlayer is an class, passed by reference, there's no reason to have a @Binding with it.