This is a follow up on this thread:
SwiftUI 2.0: export images with .fileExporter modifier
Goal: export a group of images in SwiftUI
What I did:
I am using the .fileExporter modifier, with the FileDocument struct.
Also open to other approach, like . fileMover modifier for example.
Problem:
When setting the FileDocument for multiple images struct I am getting am error on func fileWrapper (check code bellow).
Question:
How can I export multiple images in SwiftUI (could be any method)?
//file exporter
.fileExporter(isPresented: $exportFile, document: ImageDocument(
image: UIImage(data: product.cover ?? Data())!,
image2: UIImage(data: product.cover2 ?? Data())!)
,
contentType: .jpeg, onCompletion: { (result) in
if case .success = result {
print("Success")
} else {
print("Failure")
}
})
//export group of images
struct ImageDocument: FileDocument {
static var readableContentTypes: [UTType] { [.jpeg] }
var image: UIImage
var image2: UIImage
init(
image: UIImage?,
image2: UIImage?
) {
self.image = image ?? UIImage()
self.image2 = image2 ?? UIImage()
}
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let image = UIImage(data: data),
let image2 = UIImage(data: data)
else {
throw CocoaError(.fileReadCorruptFile)
}
self.image = image
self.image2 = image2
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
return FileWrapper(regularFileWithContents:
image.jpegData(compressionQuality: 0.80)!,
image2.jpegData(compressionQuality: 0.80)!//<----- getting an "extra argument error here
)
}
}
Update:
Tried to rewrite Max answer to UIImage, but this only exports 1 image:
import SwiftUI
class AppContext: ObservableObject {
#Published var fileSaveDialogShown = false
}
#main
struct FocalApp: App {
#StateObject var appContext = AppContext()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.appContext)
.fileExporter(
isPresented: $appContext.fileSaveDialogShown,
documents: [
ImageDocument(image: UIImage(named: "1")),
ImageDocument(image: UIImage(named: "2"))
],
contentType: .jpeg // Match this to your representation in ImageDocument
) { url in
print("Saved to", url) // [URL]
}
}
}
}
import SwiftUI
import UniformTypeIdentifiers
struct ImageDocument: FileDocument {
static var readableContentTypes: [UTType] { [.jpeg, .png, .tiff] }
var image: UIImage
init(image: UIImage?) {
self.image = image ?? UIImage()
}
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let image = UIImage(data: data)
else {
throw CocoaError(.fileReadCorruptFile)
}
self.image = image
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
// You can replace tiff representation with what you want to export
return FileWrapper(regularFileWithContents: image.jpegData(compressionQuality: 1)!)
}
}
struct ContentView: View {
#EnvironmentObject var appContext: AppContext
var body: some View {
VStack {
Button(action: {
appContext.fileSaveDialogShown.toggle()
}, label: {
Text("Button")
})
}
.frame(width: 200, height: 200)
}
}
You need to use fileExporter with documents instead of document, which takes in a Collection
Here's how I'm doing it on macOS, adapting it should be straightforward:
import SwiftUI
import UniformTypeIdentifiers
struct ImageDocument: FileDocument {
static var readableContentTypes: [UTType] { [.jpeg, .png, .tiff] }
var image: NSImage
init(image: NSImage?) {
self.image = image ?? NSImage()
}
init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents,
let image = NSImage(data: data)
else {
throw CocoaError(.fileReadCorruptFile)
}
self.image = image
}
func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper {
// You can replace tiff representation with what you want to export
return FileWrapper(regularFileWithContents: image.tiffRepresentation!)
}
}
#main
struct FocalApp: App {
#StateObject var appContext = AppContext()
var body: some Scene {
WindowGroup {
MainView()
.environmentObject(self.appContext)
.fileExporter(
isPresented: $appContext.fileSaveDialogShown,
documents: [
ImageDocument(image: NSImage(named: "testimage1")),
ImageDocument(image: NSImage(named: "testimage2"))
],
contentType: .tiff // Match this to your representation in ImageDocument
) { url in
print("Saved to", url) // [URL]
}
}
}
}
Related
I'm creating simple CRUD operations in an app and I came across a hairy quirk, given SwiftUI's Image design.
I'm trying to upload an image to Firebase, except I need to convert an Image to a UIImage, and further into Data.
Here's my code:
Image Uploader
import UIKit
import Firebase
import FirebaseStorage
import SwiftUI
struct ImageUploader {
static func uploadImage(with image: Data, completion: #escaping(String) -> Void) {
let imageData = image
let fileName = UUID().uuidString
let storageRef = Storage.storage().reference(withPath: "/profile_images/\(fileName)")
storageRef.putData(imageData, metadata: nil) { (metadata, error) in
if let error = error {
print("Error uploading image to Firebase: \(error.localizedDescription)")
return
}
print("Image successfully uploaded to Firebase!")
}
storageRef.downloadURL { url, error in
guard let imageUrl = url?.absoluteString else { return }
completion(imageUrl)
}
}
}
View Model
import SwiftUI
import Firebase
func uploadProfileImage(with image: Data) {
guard let uid = tempCurrentUser?.uid else { return }
let imageData = image
ImageUploader.uploadImage(with: imageData) { imageUrl in
Firestore.firestore().collection("users").document(uid).updateData(["profileImageUrl":imageUrl]) { _ in
print("Successfully updated user data")
}
}
}
ImagePicker
import SwiftUI
import PhotosUI
#MainActor
class ImagePicker: ObservableObject {
#Published var image: Image?
#Published var imageSelection: PhotosPickerItem? {
didSet {
if let imageSelection {
Task {
try await loadTransferrable(from: imageSelection)
}
}
}
}
func loadTransferrable(from imageSelection: PhotosPickerItem?) async throws {
do {
if let data = try await imageSelection?.loadTransferable(type: Data.self) {
if let uiimage = UIImage(data: data) {
self.image = Image(uiImage: uiimage)
}
}
} catch {
print("Error: \(error.localizedDescription)")
}
}
}
View
if let image = imagePicker.image {
Button {
viewModel.uploadProfileImage(with: image)
} label: {
Text("Continue")
.font(.headline)
.foregroundColor(.white)
.frame(width: 340.0, height: 50.0)
.background(.blue)
.clipShape(Capsule())
.padding()
}
.shadow(radius: 10.0)
.padding(24.0)
}
As you can see, I have a problem in the view: Cannot convert value of type 'Image' to expected argument type 'Data', which makes sense. The images are all of type Data.
How can I do it?
don't use #Published var image: Image? in your class ImagePicker: ObservableObject, Image is a View and is for use in other Views.
Use #Published var image: UIImage? and adjust your code accordingly.
Then use image.pngData() to get the Data to upload.
This is my Json file and i don't understand how to fetch data and set
the Image in our SwiftUI code. please help me resolve this problem.
And this is My Model, is this model correct?
This is My API and wants to fetch only value images array
import Foundation
public struct BannerImages {
public let images: [String]
public init(images: [String]) {
self.images = images
}
}
try this approach to fetch your images and display them in a View:
import Foundation
import SwiftUI
struct ContentView: View {
#StateObject var vm = ViewModel()
var body: some View {
VStack {
Text("Fetching the data...")
List (vm.images, id: \.self) { url in
AsyncImage(url: URL(string: url)) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 111, height: 111)
} placeholder: {
ProgressView()
}
}
}
.task {
await vm.getData()
}
}
}
class ViewModel: ObservableObject {
#Published var images = [String]()
func getData() async {
guard let url = URL(string: "apiurl") else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url)
Task{#MainActor in
let results = try JSONDecoder().decode(APIResponse.self, from: data)
self.images = results.images
}
} catch {
print("---> error: \(error)")
}
}
}
struct APIResponse: Codable {
let images: [String]
}
1. First you need to have a variable with data type of Data like this: var imageData: Data?
2. Then you have to fetch the image data from the link in the array like this:
func getImageData() {
// Check image url isnt nill
guard imageUrl(your image url) != nil else {
return
}
// Download the data for the image
let url = URL(string: imageUrl(your image url)!)
if let url = url {
let session = URLSession.shared
let dataTask = session.dataTask(with: url) { data, response, error in
if error == nil {
DispatchQueue.main.async {
self.imageData = data
}
}
}
dataTask.resume()
}
}
3. Once this is done go to the view file where you want to display the image and create
let uiImage = UIImage(data: put the var in which you stored the image data in previous step ?? Data())
Image(uiImage: uiImage ?? UIImage())
I am trying to use flash when taking an image using AV Foundation in Swift UI. However, when I try to take a picture, I get the following error code.
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x28004e790 {Error Domain=NSOSStatusErrorDomain Code=-16800 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-16800), AVErrorRecordingFailureDomainKey=4, NSLocalizedDescription=The operation could not be completed}
Below is the code that I am using for my camera that is generating this issue. I have gone through and commented some areas that I thought might be the source of the issue as I was trying to figure this out, but I may be wrong.
import SwiftUI
import AVFoundation
struct Camera: View {
var body: some View {
CameraView()
}
}
struct Camera_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// Test code: Ignore
struct globalVariable {
public var isBack = false
}
class GlobalModel : ObservableObject {
#Published var isBack = false
func get() -> Bool{
return isBack
}
func setTrue() {
isBack = true
}
func setFalse() {
isBack = false
}
}
//
struct CameraView: View { // Creates the camera preview elements
#StateObject var camera = CameraModel()
#State var img : UIImage? = nil
#State var navigated = false
#ObservedObject var nextScreen = GlobalModel()
var body: some View{
ZStack{
CameraPreview(camera: camera)
.ignoresSafeArea(.all, edges: .all)
VStack{
Spacer()
HStack{
if camera.isTaken {
Button(action: {
camera.reTake()
self.nextScreen.setFalse()
print(nextScreen.get())
}, label: {
Text("Retake").foregroundColor(.black)
.fontWeight(.semibold)
.padding(.vertical, 10)
.padding(.horizontal, 30)
.background(Color.white)
.clipShape(Capsule())
}).padding(.trailing)
Spacer()
ZStack{
NavigationLink("", destination: Classify(originalImage: img, label: "", confidence: 0.0), isActive: $navigated)
Button(action:
{if !camera.isLoaded{
img = camera.savePic()
if img != nil{
print("is not nil")
}
self.navigated.toggle()
self.nextScreen.setTrue()
print(nextScreen.get())
}
}, label: {
Text("Continue").foregroundColor(.black)
.fontWeight(.semibold)
.padding(.vertical, 10)
.padding(.horizontal, 30)
.background(Color.white)
.clipShape(Capsule())
}).padding(.leading).opacity(nextScreen.get() ? 0.01 : 1)
}
}
else{
Button(action: camera.takePic, label: {
ZStack{
Image(systemName: "camera.circle")
.frame(width: 70, height: 75).font(.system(size: 60))
}
})
}
}.frame(height: 75)
}
}.onAppear(perform: {
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation") // Forcing the rotation to portrait
AppDelegate.orientationLock = .portrait // And making sure it stays that way
//UITabBar.appearance().isHidden = true
camera.Check()
})
.onDisappear(){
AppDelegate.orientationLock = .all
UITabBar.appearance().isHidden = false
}
}
}
class CameraModel: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate {
#Published var isTaken = false
#Published var session = AVCaptureSession()
#Published var alert = false
#Published var output = AVCapturePhotoOutput()
#Published var preview : AVCaptureVideoPreviewLayer!
#Published var isLoaded = false
#Published var picData = Data(count: 0)
var flashMode: AVCaptureDevice.FlashMode = .on // set the camera to on
var device : AVCaptureDevice? // for camera device
private func getSettings(camera: AVCaptureDevice, flashMode: AVCaptureDevice.FlashMode) -> AVCapturePhotoSettings {
let settings = AVCapturePhotoSettings() // get the default settings
and change them to enable flash
if camera.hasFlash {
settings.flashMode = self.flashMode
}
return settings
}
func Check() {
switch AVCaptureDevice.authorizationStatus(for: .video){
case .authorized:
setUp()
return
case .notDetermined:
AVCaptureDevice.requestAccess(for: .video) { (status) in
if status{
self.setUp()
}
}
case .denied:
self.alert.toggle()
return
default:
return
}
}
func setUp(){
do{
self.session.beginConfiguration()
self.device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)
let input = try AVCaptureDeviceInput(device: self.device!)
if self.session.canAddInput(input){
self.session.addInput(input)
}
if self.session.canAddOutput(self.output){
self.session.addOutput(self.output)
}
self.session.commitConfiguration()
}
catch{
print(error.localizedDescription)
}
}
func takePic(){
DispatchQueue.global(qos: .background).async {
let currentSettings = self.getSettings(camera: self.device!, flashMode: self.flashMode)
self.output.capturePhoto(with: currentSettings, delegate: self) // Capture photo with flash settings; doesn't work
DispatchQueue.main.async {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false){
(timer) in self.session.stopRunning()
//isBack.setTrue()
}
}
}
DispatchQueue.main.async {
withAnimation{
self.isTaken.toggle()
}
}
}
func reTake() {
DispatchQueue.global(qos: .background).async {
self.session.startRunning()
DispatchQueue.main.async {
withAnimation{
self.isTaken.toggle()
self.isLoaded = false
}
}
}
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) // not sure if there is something wrong here that is messing up the program but need this function to work ultimately{
if error != nil{
print(error!)
}
print("photoOuput function")
print(photo)
guard let imageData = photo.fileDataRepresentation() else{return }
self.picData = imageData
}
func savePic () -> UIImage{
let image = UIImage(data: self.picData)!
self.isLoaded = true
return image
}
}
struct CameraPreview: UIViewRepresentable {
#ObservedObject var camera : CameraModel
func makeUIView(context: Context) -> some UIView {
let view = UIView(frame: UIScreen.main.bounds)
camera.preview = AVCaptureVideoPreviewLayer(session: camera.session)
camera.preview.frame = view.frame
camera.preview.videoGravity = .resizeAspectFill
view.layer.addSublayer(camera.preview)
camera.session.startRunning()
return view
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
I noticed that if I set the following line from above
var flashMode: AVCaptureDevice.FlashMode = .on
to
var flashMode: AVCaptureDevice.FlashMode = .off
the app doesn't produce the above error (but flash stays off). I am asking this because I need to save the output of the camera (with flash) as an image, however, with flash enabled, the picData is nil which leads to an unwrapping error (see the savePic() and photoOutput() functions for reference). Ultimately, I need the savePic() function to work
Any help with this will be appreciated.
I found why the cache prompt will not be shown.
If I use the ImageView directly on the ContentView, the cache prompt will not show.
If I wrap the ImageView with a View, then use the wrapper view on the ContentView, the cache prompt will show.
Here is the working code in the ContentView.swift
struct ContentView: View {
var links =
[NewsItem(urlString: "https://www.jobyme88.com/wp-content/uploads/2020/11/50d0-kj-classroom-0.jpg"),
NewsItem(urlString: "https://www.jobyme88.com/wp-content/uploads/2020/11/50d0-kj-classroom-1.jpg"),
NewsItem(urlString: "https://www.jobyme88.com/wp-content/uploads/2020/11/50d0-kj-classroom-2.jpg"),
NewsItem(urlString: "https://www.jobyme88.com/wp-content/uploads/2020/11/50d0-kj-classroom-3.jpg"),
NewsItem(urlString: "https://www.jobyme88.com/wp-content/uploads/2020/11/50d0-kj-classroom-4.jpg"),
NewsItem(urlString: "https://www.jobyme88.com/wp-content/uploads/2020/11/50d0-kj-classroom-5.jpg"),
NewsItem(urlString: "https://www.jobyme88.com/wp-content/uploads/2020/11/50d0-kj-classroom-6.jpg")]
var body: some View {
List(links) { news in
// working
NewsListItemView(item: news)
// not working
//NewsImageView(urlString: news.urlString)
}
}
}
This is the NewsListItemView which is just a wrapper
struct NewsListItemView: View {
var item: NewsItem
var body: some View {
NewsImageView(urlString: item.urlString)
}
}
This is my cache prompt location.
NewsImageViewModel.swift
class NewsImageViewModel: ObservableObject {
static var placeholder = UIImage(named: "NewsIcon.png")
#Published var image: UIImage?
var urlString: String?
init(urlString: String) {
self.urlString = urlString
loadImage()
}
func loadImage() {
if loadImageFromCache() {
return
}
loadImageFromURL()
}
func loadImageFromCache() -> Bool {
guard let cacheIamge = TemporaryImageCache.getShared()[urlString!] else {
return false
}
print("load from cache")
self.image = cacheIamge
return true
}
func loadImageFromURL() {
print("load from url")
guard let urlString = urlString else {
return
}
let url = URL(string: urlString)!
let task = URLSession.shared.dataTask(with: url, completionHandler: getResponseFromURL(data:response:error:))
task.resume()
}
func getResponseFromURL(data: Data?, response: URLResponse?, error: Error?) {
guard error == nil else {
print("Error \(error!)")
return
}
guard data != nil else {
print("No founded data")
return
}
DispatchQueue.main.async {
guard let loadedImage = UIImage(data: data!) else {
print("Not supported data ")
return
}
self.image = loadedImage
TemporaryImageCache.getShared().cache.setObject(loadedImage, forKey: self.urlString! as NSString)
}
}
}
NewsImageView.swift
import SwiftUI
struct NewsImageView: View {
#ObservedObject var model: NewsImageViewModel
init(urlString: String) {
model = NewsImageViewModel(urlString: urlString)
}
var body: some View {
Image(uiImage: model.image ?? NewsImageViewModel.placeholder!)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 100, alignment: .center)
}
}
NewsListItemView.swift
struct NewsListItemView: View {
var item: NewsItem
var body: some View {
NewsImageView(urlString: item.urlString)
}
}
This is ImageCache.swift
protocol ImageCache {
subscript(_ urlString: String) -> UIImage? {get set }
}
struct TemporaryImageCache: ImageCache {
subscript(urlString: String) -> UIImage? {
get {
cache.object(forKey: urlString as NSString)
}
set {
newValue == nil ? cache.removeObject(forKey: urlString as NSString) : cache.setObject(newValue!, forKey: urlString as NSString)
}
}
var cache = NSCache<NSString, UIImage>()
}
extension TemporaryImageCache {
private static var shared = TemporaryImageCache()
static func getShared() -> TemporaryImageCache {
return shared
}
}
This is NewsItem.swift
struct NewsItem: Identifiable {
var id = UUID()
var urlString: String
}
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
}