Issue with Image picker in swiftui for ios 16 - swiftui

Good day,
I am new to ios development and trying to make an image picker to print out the path of the images to eventually store in a json file but I keep getting this error
Cannot convert value of type 'Binding' to expected argument type 'Binding'
Here is the code that I am busy with.
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let images = info[.originalImage] as? [UIImage] {
for image in images {
print(image)
}
}
parent.$presentationMode.wrappedValue.dismiss()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
#Binding var presentationMode: PresentationMode
#Binding var images: [UIImage]
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = .photoLibrary
picker.allowsEditing = false
picker.modalPresentationStyle = .fullScreen
picker.mediaTypes = ["public.image"]
picker.videoQuality = .typeHigh
picker.videoMaximumDuration = TimeInterval(30)
picker.modalPresentationStyle = .popover
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
uiViewController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 0, height: 0))
}
}
struct ImagePickerView: View {
#State private var showImagePicker = false
#State private var images = [UIImage]()
var body: some View {
Button("Select Images") {
self.showImagePicker = true
}
.sheet(isPresented: $showImagePicker, onDismiss: {
self.showImagePicker = false
}, content: {
ImagePicker(presentationMode: self.$showImagePicker, images: self.$images)
})
}
}
struct ImagePickerViewPreviews: PreviewProvider {
static var previews: some View {
ImagePickerView()
}
}

There are a couple of issues:
You're trying to pass a Binding<Bool> to a Binding<PresentationMode>
You are trying to convert to a [UIImage] instead of UIImage
You actually don't need the PresentationMode at all -- you can just use the single Bool binding for showImagePicker since all you're doing is trying to effect whether the view is shown or not.
See comments for the changed lines:
struct ImagePicker: UIViewControllerRepresentable {
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
// <-- Here
if let image = info[.originalImage] as? UIImage {
self.parent.images = [image]
}
parent.showImagePicker = false // <-- Here
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
#Binding var showImagePicker: Bool // <-- Here
#Binding var images: [UIImage]
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = .photoLibrary
picker.allowsEditing = false
picker.modalPresentationStyle = .fullScreen
picker.mediaTypes = ["public.image"]
picker.videoQuality = .typeHigh
picker.videoMaximumDuration = TimeInterval(30)
picker.modalPresentationStyle = .popover
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
uiViewController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 0, height: 0))
}
}
struct ImagePickerView: View {
#State private var showImagePicker = false
#State private var images = [UIImage]()
var body: some View {
Button("Select Images") {
self.showImagePicker = true
}
// <-- Here
.sheet(isPresented: $showImagePicker) {
ImagePicker(showImagePicker: $showImagePicker, images: $images)
}
.onChange(of: images) { newValue in
print("Images: ", newValue)
}
}
}

Related

How to select multiple images and print the paths of the images after a button is pressed in swiftui

Good day,
I am trying to make an image picker that you can choose multiple images and then print out the paths of the images once a button has been pressed on the screen.
But I could only manage to get it to pick on image at a time and it prints out the path when the image it picked.
Here is my code for the image picker
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let parent: ImagePicker
init(_ parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
var selectedImages = [UIImage]()
for item in info {
if let image = item.value as? UIImage, let imageURL = info[.imageURL] as? URL {
selectedImages.append(image)
print("Image URL: ", imageURL)
}
}
self.parent.images.append(contentsOf: selectedImages)
parent.showImagePicker = false
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
#Binding var showImagePicker: Bool
#Binding var images: [UIImage]
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = .photoLibrary
picker.allowsEditing = false
picker.modalPresentationStyle = .fullScreen
picker.mediaTypes = ["public.image"]
picker.videoQuality = .typeHigh
picker.videoMaximumDuration = TimeInterval(30)
picker.modalPresentationStyle = .popover
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
uiViewController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: 0, height: 0))
}
}
struct ImagePickerView: View {
#State private var showImagePicker = false
#State private var images = [UIImage]()
var body: some View {
VStack{
Button("Select Images") {
self.showImagePicker = true
}
.sheet(isPresented: $showImagePicker) {
ImagePicker(showImagePicker: $showImagePicker, images: $images)
}
}
}
struct ImagePickerViewPreviews: PreviewProvider {
static var previews: some View {
ImagePickerView()
}
}
}

SwiftUI UIIMagePickerController Find/Search Field Strange Behaviour

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}

Keeping flash on while Camera is open SwiftUI + UIImagePickerController

I have a UIViewControllerRepresentable struct as follows.
struct ImagePicker: UIViewControllerRepresentable {
#Binding var isShown: Bool
#Binding var image: UIImage?
#Binding var didTapCapture: Bool
#Binding var flash: Bool?
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
uiViewController.showsCameraControls = false
if (didTapCapture == true){
uiViewController.takePicture()
didTapCapture = false
}
}
func makeCoordinator() -> ImagePickerCoordinator {
return ImagePickerCoordinator(isShown: $isShown, image: $image)
}
func toggleFlash() {
guard let device = AVCaptureDevice.default(for: AVMediaType.video) else { return }
guard device.hasTorch else { return }
do {
try device.lockForConfiguration()
if (device.torchMode == AVCaptureDevice.TorchMode.on) {
device.torchMode = AVCaptureDevice.TorchMode.off
} else {
do {
try device.setTorchModeOn(level: 1.0)
} catch {
print(error)
}
}
device.unlockForConfiguration()
} catch {
print(error)
}
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
self.toggleFlash() // does not work
picker.delegate = context.coordinator
picker.sourceType = .camera
picker.showsCameraControls = false
picker.modalPresentationStyle = .fullScreen
return picker
}
}
For reference, here is the corresponding ImagePickerCoordinator.
class ImagePickerCoordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#Binding var isShown: Bool
#Binding var image: UIImage?
init(isShown: Binding<Bool>, image: Binding<UIImage?>) {
_isShown = isShown
_image = image
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
//self.toggleFlash()
let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
image = uiImage
isShown = false
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
isShown = false
}
}
When I open the camera, the flash briefly blinks on and then turns off when the camera view appears. I want to keep it on while the camera is showing. How can I do this with my current code?

Add a different modifier to each Action Sheet button

This is probably quite a simple question, but I can't find an answer.
I'm trying to build an ActionSheet with two buttons (as well as a cancel button):
Button "Select from Gallery" opens an imagePicker with sourceType set to .photoLibrary.
Button "Take a new picture" opens an imagePicker with sourceType set to .camera.
I've made the ActionSheet and the imagePicker successfully, but can't work out where to add the modifier to tell which sourceType should be used to each button. I managed to add it outside of the ActionSheet in a sheet() modifier in a normal button like this and everything worked well:
Button(action: {
self.show.toggle()
})
{Text("Take a new picture")}
.sheet(isPresented: self.$show, content: {
ImagePicker(sourceType: .camera, show: self.$show, image: self.$imageTemp)
})
However I can't see where to include this information in the ActionSheet. Many thanks to anyone who can help, I hope this is clear :-)
Here is my code:
struct ContentView: View {
#State private var showingActionSheet = false
#State var imageTemp : Data = (UIImage(systemName: "photo.on.rectangle.angled")?.jpegData(compressionQuality: 1))!
var body: some View {
NavigationView {
Image(uiImage: UIImage(data: imageTemp)!)
.onTapGesture {
self.showingActionSheet = true
}
.actionSheet(isPresented: $showingActionSheet) {
ActionSheet(title: Text("Image selector"), message: Text("Select an image"), buttons: [
.default(Text("Select from Gallery"))
{
self.show.toggle()
},
.default(Text("Take new picture")) {
self.show.toggle()
},
.cancel()
]
)
}
}
}
}
And, just in case, here is the code for my imagePicker, although I think it's probably not necessary.
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Binding var show: Bool
#Binding var image: Data
func makeCoordinator() -> ImagePicker.Coordinator {
let imagePicker = UIImagePickerController()
return ImagePicker.Coordinator(child1: self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = sourceType
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var child : ImagePicker
init(child1: ImagePicker) {
child = child1
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.child.show.toggle()
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
let image = info[.originalImage]as! UIImage
let data = image.jpegData(compressionQuality: 0.45)
self.child.image = data!
self.child.show.toggle()
}
}
}
Your question pretty much boils down to "How can I present multiple sheets?", so this thread might be helpful.
Define a new enum to contain possible sheet types (gallery/take photo)
Declare a #State property to hold the current sheet type. It's optional because when it's nil, there will be no sheet presented.
Set the property to the type that you want
Use sheet(item:onDismiss:content:) instead of sheet(isPresented:onDismiss:content:). isPresented is best for static sheets. item is for when you have multiple sheet types, which is what you want.
enum PhotoSheetType: Identifiable { /// 1.
var id: UUID {
UUID()
}
case gallery
case picture
}
struct ContentView: View {
/// 2.
#State private var showingType: PhotoSheetType?
#State private var showingActionSheet = false
#State var imageTemp : Data = (UIImage(systemName: "photo.on.rectangle.angled")?.jpegData(compressionQuality: 1))!
var body: some View {
NavigationView {
Image(uiImage: UIImage(data: imageTemp)!)
.onTapGesture {
self.showingActionSheet = true
}
.actionSheet(isPresented: $showingActionSheet) {
ActionSheet(
title: Text("Image selector"),
message: Text("Select an image"),
buttons: [
.default(Text("Select from Gallery")) {
showingType = .gallery /// 3.
},
.default(Text("Take new picture")) {
showingType = .picture /// 3.
},
.cancel()
]
)
} /// 4.
.sheet(item: $showingType) { type in
if type == .gallery {
ImagePicker(sourceType: .photoLibrary, showingType: $showingType, image: self.$imageTemp)
} else {
ImagePicker(sourceType: .camera, showingType: $showingType, image: self.$imageTemp)
}
}
}
}
}
You'll also need to modify your ImagePicker so that the Binding takes in a PhotoSheetType? instead of Bool. To dismiss the sheet, just set showingType to nil.
struct ImagePicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Binding var showingType: PhotoSheetType?
#Binding var image: Data
func makeCoordinator() -> ImagePicker.Coordinator {
let imagePicker = UIImagePickerController()
return ImagePicker.Coordinator(child1: self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.sourceType = sourceType
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker>) {
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var child : ImagePicker
init(child1: ImagePicker) {
child = child1
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.child.showingType = nil /// set to nil here
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
let image = info[.originalImage] as! UIImage
let data = image.jpegData(compressionQuality: 0.45)
self.child.image = data!
self.child.showingType = nil /// set to nil here
}
}
}

SwiftUI: How to Properly Code AVPlayer After Loading A Video From the Device with an ImagePickerController?

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
}