I'm trying to figure out how to load videos with PHPicker. I know how to do it with Images but I can't seem to figure out how to do it with videos. This code works only with some videos and not with others.
Here's my code
struct MediaPicker: UIViewControllerRepresentable {
#Binding var video: URL?
typealias UIViewControllerType = PHPickerViewController
class Coordinator: NSObject, PHPickerViewControllerDelegate {
var parent: MediaPicker
init(_ parent: MediaPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
if results.isEmpty {
picker.dismiss(animated: true)
return
}
let provider = results.first!.itemProvider
if provider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
provider.loadItem(forTypeIdentifier: UTType.movie.identifier) { url, err in
if err != nil {return}
if let url = url {
DispatchQueue.main.sync {
self.parent.video = url as! URL?
picker.dismiss(animated: true)
}
}
}
}
}
}
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration()
configuration.preferredAssetRepresentationMode = .automatic
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
return MediaPicker.Coordinator(self)
}
}
AVPlayer
Related
I want to update the image in firebase storage when my State changes. Currently I call my function to upload the picked image to storage in the onAppear:
#State var shouldShowImagePicker: Bool = false
#State var image: UIImage?
Button(action: {
shouldShowImagePicker.toggle()
}) {
if let image = self.image {
Image(uiImage: image)
.resizable()
.frame(maxWidth: 300, maxHeight: 300)
.scaledToFit()
.onAppear() {
persistImageToStorage()
}
} else {
Text("Select Image")
.frame(width: 300, height: 300)
}
}
.fullScreenCover(isPresented: $shouldShowImagePicker, onDismiss: nil) {
ImagePicker(image: $image)
}
This is the function to upload the image to storage:
private func persistImageToStorage() {
let ref = FirebaseManager.shared.storage.reference(withPath: "image")
guard let imageData = self.image?.jpegData(compressionQuality: 0.5)
else { return }
ref.putData(imageData, metadata: nil) { metadata, err in
if let err = err {
print(err)
return
}
ref.downloadURL { url, err in
if let err = err {
print(err)
return
}
}
}
}
This is my ImagePicker component:
import SwiftUI
struct ImagePicker: UIViewControllerRepresentable {
#Binding var image: UIImage?
private let controller = UIImagePickerController()
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let parent: ImagePicker
init(parent: ImagePicker) {
self.parent = parent
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
parent.image = info[.originalImage] as? UIImage
picker.dismiss(animated: true)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true)
}
}
func makeUIViewController(context: Context) -> some UIViewController {
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
Doing it this way works fine the first time I open the ImagePickerbut once an image is selected it does not call the function again since the onAppear does not call again.
I tried setting onChange on the binding of the image but it seemed to not work for me.
You could create a Binding that explicitly persists the image when the value is set:
.fullScreenCover(isPresented: $shouldShowImagePicker, onDismiss: nil) {
let imageBinding = Binding(
get: { image },
set: { value in
image = value
persistImageToStorage()
}
)
ImagePicker(image: imageBinding)
}
Note: Since imageBinding is already a Binding, you do not use a $ here.
Tested in Xcode 13.4.1
You said using onChange with image did not work for you, but I also found this to work:
ImagePicker(image: $image)
.onChange(of: image) { _ in
persistImageToStorage()
}
Below is my main view controller. The user selects images of clothing which are then categorized using CoreML and given a filename. Then, data is saved to Realm. When I call the function loadClothing(), the array is empty even though items were added during func detect. Any help is much appreciated!
import UIKit
import PhotosUI
import RealmSwift
import CoreML
import Vision
class ViewController: UIViewController, PHPickerViewControllerDelegate {
#IBOutlet weak var shoesImageView: UIImageView!
#IBOutlet weak var shirtImageView: UIImageView!
#IBOutlet weak var pantsImageView: UIImageView!
var documentsUrl: URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}
let realm = try! Realm()
var clothing: Results<Clothing>?
override func viewDidLoad() {
super.viewDidLoad()
loadClothing()
let clothingArray = Clothing()
print(clothingArray)
}
#IBAction func addClothesButton(_ sender: UIBarButtonItem) {
pickPhotos()
}
#IBAction func randomizeButton(_ sender: UIBarButtonItem) {
loadClothing()
let clothingArray = Clothing()
print(clothingArray)
shirtImageView.image = load(fileName: clothingArray.shirtImages.randomElement()!)
pantsImageView.image = load(fileName: clothingArray.pantsImages.randomElement()!)
shoesImageView.image = load(fileName: clothingArray.shoesImages.randomElement()!)
}
//MARK: - PHPickerViewController
#objc func pickPhotos() {
var config = PHPickerConfiguration()
config.selectionLimit = 25
config.filter = PHPickerFilter.images
let pickerViewController = PHPickerViewController(configuration: config)
pickerViewController.delegate = self
self.present(pickerViewController, animated: true, completion: nil)
}
// MARK: - PHPickerViewControllerDelegate
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true, completion: nil)
for result in results {
result.itemProvider.loadObject(ofClass: UIImage.self) {(object, error) in
if let image = object as? UIImage {
DispatchQueue.main.async {
guard let fileName = result.itemProvider.suggestedName else {
fatalError("Could not retrieve file name.")
}
print(fileName)
guard let ciImage = CIImage(image: image) else {
fatalError("Could not convert to CI Image.")
}
self.detect(image: ciImage, fileName: fileName)
}
}
}
}
}
// MARK: - Core ML
func detect(image: CIImage, fileName: String) {
guard let model = try? VNCoreMLModel(for: ClothingClassifier(configuration: MLModelConfiguration()).model) else {
fatalError("Loading CoreML Model failed.")
}
let request = VNCoreMLRequest(model: model) { (request, error) in
guard let results = request.results as? [VNClassificationObservation] else {
fatalError("Model failed to process image.")
}
let newClothing = Clothing()
if let firstResult = results.first {
let uiImage = UIImage(ciImage: image)
if firstResult.identifier.contains("shirts") {
newClothing.shirtImages.append(fileName)
} else if firstResult.identifier.contains("pants"){
newClothing.pantsImages.append(fileName)
} else if firstResult.identifier.contains("shoes") {
newClothing.shoesImages.append(fileName)
}
self.save(clothing: newClothing)
print(newClothing)
}
}
let handler = VNImageRequestHandler(ciImage: image)
do {
try handler.perform([request])
}
catch {
print(error)
}
}
// MARK: - Data Manipulation Methods
func save(clothing: Clothing) {
do {
try realm.write {
realm.add(clothing)
}
} catch {
print("Error saving uploaded clothing. \(error)")
}
}
func loadClothing() {
clothing = realm.objects(Clothing.self)
print("loaded")
}
private func load(fileName: String) -> UIImage? {
let fileURL = documentsUrl.appendingPathComponent(fileName)
do {
let imageData = try Data(contentsOf: fileURL)
return UIImage(data: imageData)
} catch {
print("Error loading image : \(error)")
}
return nil
}
}
Clothing Class
import Foundation
import RealmSwift
class Clothing: Object {
let shirtImages = List<String>()
let pantsImages = List<String>()
let shoesImages = List<String>()
}
Goal: To capture an image and provide it to a parent view via a binding to display it in the UI.
My code:
struct ImageShooter: UIViewControllerRepresentable {
#Binding var image: UIImage?
var captureSesssion = AVCaptureSession()
var output = AVCapturePhotoOutput()
func makeUIViewController(context: Context) -> UIViewController {
let camera = AVCaptureDevice.default(for: .video)
let input = try! AVCaptureDeviceInput(device: camera!)
captureSesssion.addInput(input)
captureSesssion.addOutput(output)
let preview = AVCaptureVideoPreviewLayer(session: captureSesssion)
preview.videoGravity = .resizeAspectFill
let vc = UIViewController()
vc.view.layer.addSublayer(preview)
preview.frame = vc.view.frame
return vc
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, AVCapturePhotoCaptureDelegate {
var parent: ImageShooter
init(_ parent: ImageShooter) {
self.parent = parent
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
if let imageData = photo.fileDataRepresentation() {
guard let image = UIImage(data: imageData) else { return }
self.parent.image = image
}
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
parent.output = output
parent.captureSesssion.stopRunning()
}
}
}
Problem: The screen remains black and no camera preview is shown. The sheet appears. The sheet can be dismissed.
I need to allow the user to select the image they want to save to the app that will be retrieved later. I already have the photo picker within the code, I just don’t know how to save and retrieve the image.
struct PhotoPicker: UIViewControllerRepresentable {
#Binding var Badge: UIImage
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
picker.allowsEditing = true
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
func makeCoordinator() -> Coordinator {
return Coordinator(photoPicker: self)
}
final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
let photoPicker: PhotoPicker
init(photoPicker: PhotoPicker){
self.photoPicker = photoPicker
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.editedImage] as? UIImage{
photoPicker.Badge = image
BadgeStatus.toggle()
}
picker.dismiss(animated: true)
}
}
}
here is some code that writes an image to file, then reads it again.
From this you should be able to "... save and retrieve the image."
struct ContentView: View {
#State var image = UIImage(systemName: "globe")! // <-- test image
#State var fileURL: URL?
var body: some View {
VStack (spacing: 55) {
Button(action: { saveImage() }) { // <-- first save the image to file
Text("1. write image to file")
}
Button(action: { image = UIImage() }) { // <-- second clear the image from the view
Text("2. clear image")
}
Button(action: { image = loadImage() }) { // <-- third read the image from file
Text("3. read image from file")
}
Image(uiImage: image)
}
}
func saveImage() {
do {
let furl = try FileManager.default
.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("imageFile")
.appendingPathExtension("png")
fileURL = furl
try image.pngData()?.write(to: furl)
} catch {
print("could not create imageFile")
}
}
func loadImage() -> UIImage {
do {
if let furl = fileURL {
let data = try Data(contentsOf: furl)
if let img = UIImage(data: data) {
return img
}
}
} catch {
print("error: \(error)") // todo
}
return UIImage()
}
}
I'm building a really simple app, which displays PDF documents. However, how to know when PDF document is rendered?
Here's how my app looks like:
Now, I have a ProgressView which shows while the document is being downloaded. How do I hide the ProgressView when the document is downloaded and begins to render? I'm doing all of this in SwiftUI, I connected the PDFKit using UIKit in a SwiftUI app. Now, how do I. do it? I found this, but this applies only to UIKit: How to know when a PDF is rendered using PDFKit
My code:
PDFRepresentedView:
import SwiftUI
import PDFKit
struct PDFRepresentedView: UIViewRepresentable {
typealias UIViewType = PDFView
let url: URL
let singlePage: Bool
init(_ url: URL, singlePage: Bool = false) {
self.url = url
self.singlePage = singlePage
}
func makeUIView(context _: UIViewRepresentableContext<PDFRepresentedView>) -> UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: url)
pdfView.autoScales = true
if singlePage {
pdfView.displayMode = .singlePage
}
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context: UIViewRepresentableContext<PDFRepresentedView>) {
pdfView.document = PDFDocument(url: url)
}
}
PDFReaderView:
import SwiftUI
struct PDFReaderView: View {
var url: URL
var body: some View {
ProgressView("pdf.downloading")
}
}
struct PDFReaderView_Previews: PreviewProvider {
static var previews: some View {
PDFReaderView(url: URL(string: "https://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU19600300168/U/D19600168Lj.pdf")!)
}
}
HomeView:
import SwiftUI
struct HomeView: View {
#AppStorage("needsAppOnboarding") private var needsAppOnboarding: Bool = true
var body: some View {
NavigationView {
List(deeds) { deed in
NavigationLink(destination: PDFReaderView(url: deed.url)) {
Text(deed.name)
}
}
.navigationTitle("title")
}
.sheet(isPresented: $needsAppOnboarding) {
OnboardingView()
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
}
}
Seems like a better option than letting PDFDocument do the loading from the URL would be to load the data yourself. Then, you can respond appropriately to errors, etc.
import PDFKit
import SwiftUI
import Combine
class DataLoader : ObservableObject {
#Published var data : Data?
var cancellable : AnyCancellable?
func loadUrl(url: URL) {
cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map { $0.data }
.receive(on: RunLoop.main)
.sink(receiveCompletion: { (completion) in
switch completion {
case .failure(let failureType):
print(failureType)
//handle potential errors here
case .finished:
break
}
}, receiveValue: { (data) in
self.data = data
})
}
}
struct ContentView : View {
#StateObject private var dataLoader = DataLoader()
var body: some View {
VStack {
if let data = dataLoader.data {
PDFRepresentedView(data: data)
} else {
Text("Loading")
}
}.onAppear {
dataLoader.loadUrl(url: URL(string: "https://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU19600300168/U/D19600168Lj.pdf")!)
}
}
}
struct PDFRepresentedView: UIViewRepresentable {
typealias UIViewType = PDFView
let data: Data
let singlePage: Bool = false
func makeUIView(context _: UIViewRepresentableContext<PDFRepresentedView>) -> UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(data: data)
pdfView.autoScales = true
if singlePage {
pdfView.displayMode = .singlePage
}
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context: UIViewRepresentableContext<PDFRepresentedView>) {
pdfView.document = PDFDocument(data: data)
}
}
In this example, DataLoader is responsible for getting the Data from the URL. Note the comment I've left in about where you might respond to errors.
Back in the main view, "Loading" is displayed unless there's Data available, in which case PDFRepresentedView is now shown, which now takes a Data object instead of a URL.