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>) {
}
}
}
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.
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.