I have followed two tutorials on UIViewRepresentable and thought the following would work, yet it didn't and I think my situation is more complex than in the tutorials.
Hello, I am trying to turn this code
import SpriteKit
import AVFoundation
class ViewController: NSViewController {
#IBOutlet var skView: SKView!
var videoPlayer: AVPlayer!
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.skView {
// Load the SKScene from 'backgroundScene.sks'
guard let scene = SKScene(fileNamed: "backgroundScene") else {
print ("Could not create a background scene")
return
}
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
// Add the video node
guard let alphaMovieURL = Bundle.main.url(forResource: "camera_city_animated", withExtension: "mov") else {
print("Failed to overlay alpha movie on the background")
return
}
videoPlayer = AVPlayer(url: alphaMovieURL)
let video = SKVideoNode(avPlayer: videoPlayer)
video.size = CGSize(width: view.frame.width, height: view.frame.height)
print( "Video size is %f x %f", video.size.width, video.size.height)
scene.addChild(video)
// Play video
videoPlayer.play()
videoPlayer?.actionAtItemEnd = .none
NotificationCenter.default.addObserver(self,
selector: #selector(playerItemDidReachEnd(notification:)),
name: .AVPlayerItemDidPlayToEndTime,
object: videoPlayer?.currentItem)
}
}
#objc func playerItemDidReachEnd(notification: Notification) {
if let playerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: CMTime.zero, completionHandler: nil)
}
}
}
Into a SwiftUI View by placing it inside the func makeUIView(context: Context) -> UITextView {} of my struct TransparentVideoLoop: UIViewRepresentable {} struct.
What am I missing?
Full code:
struct TransparentVideoLoop: UIViewRepresentable {
func makeUIView(context: Context) -> UITextView {
#IBOutlet var skView: SKView!
var videoPlayer: AVPlayer!
override func viewDidLoad() {
super.viewDidLoad()
if let view = self.skView {
// Load the SKScene from 'backgroundScene.sks'
guard let scene = SKScene(fileNamed: "backgroundScene") else {
print ("Could not create a background scene")
return
}
// Set the scale mode to scale to fit the window
scene.scaleMode = .aspectFill
// Present the scene
view.presentScene(scene)
// Add the video node
guard let alphaMovieURL = Bundle.main.url(forResource: "camera_city_animated", withExtension: "mov") else {
print("Failed to overlay alpha movie on the background")
return
}
videoPlayer = AVPlayer(url: alphaMovieURL)
let video = SKVideoNode(avPlayer: videoPlayer)
video.size = CGSize(width: view.frame.width, height: view.frame.height)
print( "Video size is %f x %f", video.size.width, video.size.height)
scene.addChild(video)
// Play video
videoPlayer.play()
videoPlayer?.actionAtItemEnd = .none
NotificationCenter.default.addObserver(self,
selector: #selector(playerItemDidReachEnd(notification:)),
name: .AVPlayerItemDidPlayToEndTime,
object: videoPlayer?.currentItem)
}
}
#objc func playerItemDidReachEnd(notification: Notification) {
if let playerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: CMTime.zero, completionHandler: nil)
}
}
}
func updateUIView(_ uiView: UITextView, context: Context) {
}
}
I have to return the view, but this is more complex than in the tutorials.
Use UIViewControllerRepresentable instead, e.g.
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
#Binding var selectedImage: UIImage?
#Environment(\.presentationMode) var presentationMode
func makeCoordinator() -> ImagePicker.Coordinator {
Coordinator()
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
func makeUIViewController(context: Context) -> some UIViewController {
context.coordinator.imagePicker
}
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
lazy var imagePicker: UIImagePickerController = {
let imagePickerController = UIImagePickerController()
imagePickerController.sourceType = .photoLibrary
imagePickerController.delegate = self
return imagePickerController
}()
var imageSelected: ((UIImage) -> Void)?
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
DispatchQueue.main.async {
if let selectedImage = (info[.editedImage] ?? info[.originalImage]) as? UIImage {
imageSelected?(selectedImage)
}
//self.parent.presentationMode.wrappedValue.dismiss()
}
}
}
}
Note this is inspired by ImagePicker.swift from an Apple sample but the developer got the Coordinator wrong so I have corrected that. It also needs the update func fixed.
Related
I'm using a UIImagePickerController to select an image from the user's photo library. All works fine except when the user taps on the field with the magnifying glass icon to search the library. Sometimes it dismisses ImagePicker, other times it does nothing and occasionally it works as expected... any ideas?
Code to display the sheet:
Button(action: {}) {
ZStack {
Image(systemName: "photo")
.foregroundColor(darkMode ? .white : .black)
}
}
.frame(width: 44, height: 44)
.onTapGesture {
showPhotoLibrary.toggle()
}
.sheet(isPresented: $showPhotoLibrary) {
ImagePicker(sourceType: .photoLibrary, selectedImage: self.$image)
}
The ImagePicker:
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Binding var selectedImage: UIImage
#Environment(\.presentationMode) private var presentationMode
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let imagePicker = UIImagePickerController()
imagePicker.sourceType = sourceType
imagePicker.delegate = context.coordinator
return imagePicker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
internal func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
parent.selectedImage = image
}
parent.presentationMode.wrappedValue.dismiss()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
I've seen many struggle with this, it's all over the Apple dev forums so I thought I would have a go at solving it. From my own testing I believe the reason for the problems is UIImagePickerController is designed to be presented using the present method of a parent View Controller, not used as the root view controller of a SwiftUI presented sheet, as Apple (in their sample code) and everyone else seem to be doing. Below is my incomplete test code, which shows a working image picker where the search works and it can also be dragged down to dismiss, so I think this might be the correct solution.
The idea is we use UIViewControllerRepresentable to present our own custom view controller that presents or dismisses the image picker view controller when the present boolean changes.
struct ImagePickerTest: View {
#State var show = false
#State var image1: UIImage?
var body: some View {
VStack {
Button("Hello, World!") {
show.toggle()
}
Text("image: \(image1?.description ?? "" )")
}
.imagePicking(isPresented: $show, selectedImage: $image1)
}
}
extension View {
// this could be refactored into a `ViewModifier`
func imagePicking(isPresented: Binding<Bool>, selectedImage: Binding<UIImage?>) -> some View {
ZStack {
ImagePicking(isPresented: isPresented, selectedImage: selectedImage)
self
}
}
}
class MyViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
var dismissPicker: (() -> Void)?
var selectImage: ((UIImage) -> Void)?
func showPickerIfNecessary() {
if self.presentedViewController != nil {
return
}
let imagePickerController = UIImagePickerController()
imagePickerController.sourceType = .photoLibrary
imagePickerController.delegate = self
present(imagePickerController, animated: true)
}
func hidePickerIfNecessary() {
if let vc = self.presentedViewController {
vc.dismiss(animated: true)
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
print("imagePickerControllerDidCancel")
dismissPicker?()
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
print("didFinishPickingMediaWithInfo")
if let image = (info[.editedImage] ?? info[.originalImage]) as? UIImage {
selectImage?(image)
dismissPicker?()
}
}
}
struct ImagePicking: UIViewControllerRepresentable {
#Binding var isPresented: Bool
#Binding var selectedImage: UIImage?
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
uiViewController.dismissPicker = {
isPresented = false
}
uiViewController.selectImage = { image in
selectedImage = image
}
if isPresented {
uiViewController.showPickerIfNecessary()
} else {
uiViewController.hidePickerIfNecessary()
}
}
func makeUIViewController(context: Context) -> some MyViewController {
MyViewController()
}
}
Before I started, I verified the OP's code was failing when typing a search, it crashed with this error:
2022-08-31 22:39:17.808899+0100 Test[66967:5425868] [UI] -[PUPhotoPickerHostViewController viewServiceDidTerminateWithError:] Error Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "(null)" UserInfo={Message=Service Connection Interrupted}
I have an AVPlayer that plays a video in the background of my SwiftUI app which works fine.
But I need to allow the users to change the video on a button tap/click.
This is my code for playing video:
var player = AVPlayer()
var bgVideoURL = "https://www.w3schools.com/html/movie.mp4"
struct PlayerView: UIViewRepresentable {
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
}
func makeUIView(context: Context) -> UIView {
return PlayerUIView(frame: .zero)
}
}
class PlayerUIView: UIView {
private let playerLayer = AVPlayerLayer()
override init(frame: CGRect) {
super.init(frame: frame)
let url = URL(string: bgVideoURL)!
player = AVPlayer(url: url)
player.actionAtItemEnd = .none
player.play()
playerLayer.player = player
playerLayer.videoGravity = .resizeAspectFill
NotificationCenter.default.addObserver(self,
selector: #selector(playerItemDidReachEnd(notification:)),
name: .AVPlayerItemDidPlayToEndTime,
object: player.currentItem)
layer.addSublayer(playerLayer)
}
#objc func playerItemDidReachEnd(notification: Notification) {
if let playerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: .zero, completionHandler: nil)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
}
and this is how I play the video:
var body: some View {
PlayerView()
.edgesIgnoringSafeArea(.all)
}
I need to change the video on button click/tap so I tried this:
.onTapGesture {
player.pause()
player.seek(to: .zero)
bgVideoURL = "https://media.w3.org/2010/05/sintel/trailer.mp4"
player.play()
}
The above code will restart the player but it doesn't change the video/source of the player!
Is there something else I need to do?
First, create a proper PlayerUIView class (Remove global variables, etc.)
PlayerUIView
class PlayerUIView: UIView {
// MARK: Class Property
let playerLayer = AVPlayerLayer()
// MARK: Init
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(player: AVPlayer) {
super.init(frame: .zero)
self.playerSetup(player: player)
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: Life-Cycle
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
// MARK: Class Methods
private func playerSetup(player: AVPlayer) {
playerLayer.player = player
player.actionAtItemEnd = .none
layer.addSublayer(playerLayer)
self.setObserver()
}
func setObserver() {
NotificationCenter.default.removeObserver(self)
NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidReachEnd(notification:)),
name: .AVPlayerItemDidPlayToEndTime,
object: playerLayer.player?.currentItem)
}
#objc func playerItemDidReachEnd(notification: Notification) {
if let playerItem = notification.object as? AVPlayerItem {
playerItem.seek(to: .zero, completionHandler: nil)
self.playerLayer.player?.play()
}
}
}
Now, use #Binding to bind your player in PlayerView
PlayerView
struct PlayerView: UIViewRepresentable {
#Binding var player: AVPlayer
func makeUIView(context: Context) -> PlayerUIView {
return PlayerUIView(player: player)
}
func updateUIView(_ uiView: PlayerUIView, context: UIViewRepresentableContext<PlayerView>) {
uiView.playerLayer.player = player
//Add player observer.
uiView.setObserver()
}
}
in last, inside the content view make AVPlayer object, change your new URL with AVPlayer.
struct PlayerContentView: View {
#State private var player = AVPlayer(url: URL(string: "https://media.w3.org/2010/05/sintel/trailer.mp4")!)
var body: some View {
PlayerView(player: $player)
.onTapGesture {
player.pause()
player.seek(to: .zero)
player = AVPlayer(url: Bundle.main.url(forResource: "temp_video", withExtension: "mp4")!) // or AVPlayer(url: URL(string: "https://media.w3.org/2010/05/sintel/trailer.mp4")!)
player.play()
}
.onAppear {
player.play()
}
.edgesIgnoringSafeArea(.all)
}
}
I tried to using QuickLook framework.
For editting PDF, I implemented "previewController(_: editingModeFor: )" in Coordinator.
In Xcode(ver 11.6) simulator, quicklook view has pencil markup tool.
In Xcode simulator
But in my iPad(PadOS 13.6), there is no markup tool.
In my iPad device, PadOS 13.6
Is there any bugs in QuickLook framework?
Here is my code.
PreviewController.swift
import SwiftUI
import QuickLook
struct PreviewController: UIViewControllerRepresentable {
let url: URL
#Binding var isPresented: Bool
func makeUIViewController(context: Context) -> UINavigationController {
let controller = QLPreviewController()
controller.dataSource = context.coordinator
controller.delegate = context.coordinator
let navigationController = UINavigationController(rootViewController: controller)
return navigationController
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, QLPreviewControllerDelegate, QLPreviewControllerDataSource {
let parent: PreviewController
init(parent: PreviewController) {
self.parent = parent
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return parent.url as NSURL
}
/*
Return .updateContents so QLPreviewController takes care of updating the contents of the provided QLPreviewItems whenever users save changes.
*/
func previewController(_ controller: QLPreviewController, editingModeFor previewItem: QLPreviewItem) -> QLPreviewItemEditingMode {
return .updateContents
}
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
let fileUrl = Bundle.main.url(forResource: "LoremIpsum", withExtension: "pdf")!
#State private var showingPreview = false
var body: some View {
Button("Preview File") {
self.showingPreview = true
}
.sheet(isPresented: $showingPreview) {
PreviewController(url: self.fileUrl, isPresented: self.$showingPreview)
}
}
}
I'm trying to get an AVPlayer layer in my SwiftUI interface.
Google doesn't have many answers on the subject, in fact, there was a tutorial that looked promising see: https://medium.com/#chris.mash/avplayer-swiftui-b87af6d0553. But it was full of bugs. So, I tried going about this my own way.
The plan: create a UIView subclass and add an AVPlayerLayer to it, then, wrap the UIView for SwiftUI.
The results: nothing.
Here's what I've got so far:
struct PlayerView : UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
return PlayerViewSwift()
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}
And then the PlayerViewSwift class:
class PlayerViewSwift : UIView {
private let playerLayer = AVPlayerLayer()
init() {
super.init(frame: .infinite)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
// This attribute hides `init(coder:)` from subclasses
#available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
override func layoutSubviews() {
super.layoutSubviews()
// playerLayer.player =
print("hmmm")
let player = AVPlayer(url: URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u80")!)
player.play()
playerLayer.player = player
layer.addSublayer(playerLayer)
}
}
Remove the trailing "0" from the URL path extension, so that it is "m3u8" instead of "m3u80".
Also, set playerLayer's frame equal to PlayerViewSwift's bounds in the end of layoutSubviews():
self.playerLayer.frame = self.bounds
Swift 5.1, iOS 13.
I read Chris Mash tutorials too [there for 4 of them] and put this together. I certainly don't recall them being full of bugs, maybe a few typos.
It plays either an embedded video or a streaming one in SwiftUI navigation controller managed page.
import Foundation
import SwiftUI
import AVFoundation
import Combine
let timePublisher = PassthroughSubject<TimeInterval, Never>()
let videoFinished = PassthroughSubject<Void, Never>()
let nextFrame = PassthroughSubject<Void, Never>()
struct PlayerTimeView: View {
#State private var currentTime: TimeInterval = 0
var body: some View {
Text("\(currentTime)")
.onReceive(timePublisher) { time in
self.currentTime = time
}.statusBar(hidden: true)
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
}
class PlayerUIView: UIView {
private var timeObservation: Any?
private let playerLayer = AVPlayerLayer()
override init(frame: CGRect) {
super.init(frame: .zero)
let url = Bundle.main.url(forResource: "AppDemo", withExtension: "mov")
// let url = URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!
// let url = URL(string: "https://www.youtube.com/watch?v=XK8METRgK_U")!
let player = AVPlayer(url: url!)
player.play()
timeObservation = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 0.5, preferredTimescale: 600), queue: nil) { [weak self] time in
guard let self = self else { return }
// Publish the new player time
print("time.seconds ", time.seconds)
timePublisher.send(time.seconds)
NotificationCenter.default.addObserver(self, selector: #selector(self.finishVideo), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
}
playerLayer.player = player
layer.addSublayer(playerLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = CGRect(x: 0, y: -115, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
}
#objc func finishVideo() {
print("Video Finished")
NotificationCenter.default.removeObserver(NSNotification.Name.AVPlayerItemDidPlayToEndTime)
videoFinished.send()
nextFrame.send()
}
}
struct PlayerView: UIViewRepresentable {
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
}
func makeUIView(context: Context) -> UIView {
PlayerUIView(frame: .zero)
}
}
struct PlayerPage: View {
#EnvironmentObject var env: MyAppEnvironmentData
#Environment(\.presentationMode) var presentation
var body: some View {
VStack {
PlayerView().onReceive(videoFinished) { (_) in
self.presentation.wrappedValue.dismiss()
}
HStack {
Spacer()
PlayerTimeView()
Spacer()
}
}
}
}
I used SwiftUI to create a Video Player, which loads a video using an imagePickerController, and then it is suppose to play the video once retrieved from the device. I found that the Video Player was not refreshing after retrieving the video. I am not sure how to give the appropriate #State|#Binding which is necessary to refresh it.
I learned how to code a Video Player using available online resources. And I have found a way to load a video from my device and load it to my video player. However, when I press the play button, after I have loaded the video, only the sound was played. I have tried to make the video player #State|#Binding but cannot find the solution as it does not appear to be intuitively done so.
Can anyone suggest how to update my code for the Video Player using SwiftUI?
P.S. 1) You must use an actual device to load video; and 2) The slider does not work yet. I will work on that next.
Disclosure:
I have adapted this code from online resources.
The original source code for the majority of this work can be found at these links:
How to open the ImagePicker in SwiftUI?
https://www.raywenderlich.com/5135-how-to-play-record-and-merge-videos-in-ios-and-swift
https://medium.com/#chris.mash/avplayer-swiftui-part-2-player-controls-c28b721e7e27
import SwiftUI
import AVKit
import PhotosUI
import MobileCoreServices
struct ContentView: View {
#State var showImagePicker: Bool = false
#State var url: URL?
var body: some View {
ZStack {
VStack {
Button(action: {
withAnimation {
self.showImagePicker.toggle()
}
}) {
Text("Show image picker")
}
// The video player will needs to be a #State??? as it is not updated with UIView changes but works when no view changes occur.
PlayerContainerView(player: AVPlayer(url: url ?? URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!))
}
if (showImagePicker) {
ImagePicker(isShown: $showImagePicker, url: $url)
}
}
}
}
struct PlayerView: UIViewRepresentable {
let player: AVPlayer
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
}
func makeUIView(context: Context) -> UIView {
return PlayerUIView(player: player)
}
}
class PlayerUIView: UIView {
private let playerLayer = AVPlayerLayer()
init(player: AVPlayer) {
super.init(frame: .zero)
playerLayer.player = player
layer.addSublayer(playerLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
}
struct PlayerContainerView : View {
#State var seekPos = 0.0
private let player: AVPlayer
init(player: AVPlayer) {
self.player = player
}
var body: some View {
VStack {
PlayerView(player: player)
PlayerControlsView(player: player)
}
}
}
struct PlayerControlsView : View {
#State var playerPaused = true
#State var seekPos = 0.0
let player: AVPlayer
var body: some View {
HStack {
Button(action: {
self.playerPaused.toggle()
if self.playerPaused {
self.player.pause()
}
else {
self.player.play()
}
}) {
Image(systemName: playerPaused ? "play" : "pause")
.padding(.leading, 20)
.padding(.trailing, 20)
}
Slider(value: $seekPos, from: 0, through: 1, onEditingChanged: { _ in
guard let item = self.player.currentItem else {
return
}
let targetTime = self.seekPos * item.duration.seconds
self.player.seek(to: CMTime(seconds: targetTime, preferredTimescale: 600))
})
.padding(.trailing, 20)
}
}
}
struct ImagePicker: UIViewControllerRepresentable {
#Binding var isShown: Bool
#Binding var url: URL?
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#Binding var isShown: Bool
#Binding var url: URL?
init(isShown: Binding<Bool>, url: Binding<URL?>) {
$isShown = isShown
$url = url
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let info = convertFromUIImagePickerControllerInfoKeyDictionary(info)
guard let mediaType = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaType)] as? String,
mediaType == (kUTTypeMovie as String),
let uiURL = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaURL)] as? URL
else { return }
url = uiURL
isShown = false
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
isShown = false
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(isShown: $isShown, url: $url)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.mediaTypes = [kUTTypeMovie as String]
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
fileprivate func convertFromUIImagePickerControllerInfoKeyDictionary(_ input: [UIImagePickerController.InfoKey: Any]) -> [String: Any] {
return Dictionary(uniqueKeysWithValues: input.map {key, value in (key.rawValue, value)})
}
fileprivate func convertFromUIImagePickerControllerInfoKey(_ input: UIImagePickerController.InfoKey) -> String {
return input.rawValue
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView(showImagePicker: true)
}
}
#endif
The video player does not play the video as expected, it only played the sound which indicates to me that it is playing the video; however, I cannot see it being played. It remains a black box.
UPDATE: The following is the fully edited code that works as expected (except for the slider), which was answered in comments below:
import SwiftUI
import AVKit
import PhotosUI
import MobileCoreServices
struct ContentView: View {
#State var showImagePicker: Bool = false
#State var url: URL?
var body: some View {
ZStack {
VStack {
Button(action: {
withAnimation {
self.showImagePicker.toggle()
}
}) {
Text("Show image picker")
}
PlayerContainerView(player: AVPlayer(url: url ?? URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")!))
}
if (showImagePicker) {
ImagePicker(isShown: $showImagePicker, url: $url)
}
}
}
}
struct PlayerView: UIViewRepresentable {
let player: AVPlayer
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
(uiView as? PlayerUIView)?.updatePlayer(player: player)
}
func makeUIView(context: Context) -> UIView {
return PlayerUIView(player: player)
}
}
class PlayerUIView: UIView {
private let playerLayer = AVPlayerLayer()
init(player: AVPlayer) {
super.init(frame: .zero)
playerLayer.player = player
layer.addSublayer(playerLayer)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
playerLayer.frame = bounds
}
func updatePlayer(player: AVPlayer) {
self.playerLayer.player = player
}
}
struct PlayerContainerView : View {
#State var seekPos = 0.0
private let player: AVPlayer
init(player: AVPlayer) {
self.player = player
}
var body: some View {
VStack {
PlayerView(player: player)
PlayerControlsView(player: player)
}
}
}
struct PlayerControlsView : View {
#State var playerPaused = true
#State var seekPos = 0.0
let player: AVPlayer
var body: some View {
HStack {
Button(action: {
self.playerPaused.toggle()
if self.playerPaused {
self.player.pause()
}
else {
self.player.play()
}
}) {
Image(systemName: playerPaused ? "play" : "pause")
.padding(.leading, 20)
.padding(.trailing, 20)
}
Slider(value: $seekPos, from: 0, through: 1, onEditingChanged: { _ in
guard let item = self.player.currentItem else {
return
}
let targetTime = self.seekPos * item.duration.seconds
self.player.seek(to: CMTime(seconds: targetTime, preferredTimescale: 600))
})
.padding(.trailing, 20)
}
}
}
struct ImagePicker: UIViewControllerRepresentable {
#Binding var isShown: Bool
#Binding var url: URL?
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#Binding var isShown: Bool
#Binding var url: URL?
init(isShown: Binding<Bool>, url: Binding<URL?>) {
_isShown = isShown
_url = url
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let info = convertFromUIImagePickerControllerInfoKeyDictionary(info)
guard let mediaType = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaType)] as? String,
mediaType == (kUTTypeMovie as String),
let uiURL = info[convertFromUIImagePickerControllerInfoKey(UIImagePickerController.InfoKey.mediaURL)] as? URL
else { return }
url = uiURL
isShown = false
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
isShown = false
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(isShown: $isShown, url: $url)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.mediaTypes = [kUTTypeMovie as String]
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController,
context: UIViewControllerRepresentableContext<ImagePicker>) {
}
}
fileprivate func convertFromUIImagePickerControllerInfoKeyDictionary(_ input: [UIImagePickerController.InfoKey: Any]) -> [String: Any] {
return Dictionary(uniqueKeysWithValues: input.map {key, value in (key.rawValue, value)})
}
fileprivate func convertFromUIImagePickerControllerInfoKey(_ input: UIImagePickerController.InfoKey) -> String {
return input.rawValue
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView(showImagePicker: true)
}
}
#endif
You left your updateUIView empty. You should implement it:
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PlayerView>) {
(uiView as? PlayerUIView)?.updatePlayer(player: player)
}
And also add the following method to your PlayerUIView:
func updatePlayer(player: AVPlayer) {
self.playerLayer.player = player
}