Add an environment object to UIDocumentPickerViewController - swift

I'm trying to import mp3 files into my app from outside the app's sandbox using the file browser. I have a "store" environment object where i have the folder URL to which I'd like to move the selected file. I want to add that store as an environment object to the file picker.
Here's how I call the document picker from a view (here's just the relevant code):
struct libraryView: View {
#State var filePicker : DocumentPicker
#EnvironmentObject var store : MusicStore
#State var showPicker = false
func presentDocumentPicker() {
let viewController = UIApplication.shared.windows[0].rootViewController!
let controller = filePicker.viewController
viewController.present(controller, animated: true)
}
var body: some View {
Button(action: {showPicker = true
presentDocumentPicker()
}, label: {
Image(systemName: "plus").imageScale(.large)
})
}}
And here's the document picker code:
final class DocumentPicker: NSObject, ObservableObject {
#EnvironmentObject var store : MusicStore
lazy var viewController: UIDocumentPickerViewController = {
let vc = UIDocumentPickerViewController(documentTypes: types, in: .import)
vc.delegate = self
vc.allowsMultipleSelection = self.allowsMultipleSelection
return vc
}()
}
extension DocumentPicker: UIDocumentPickerDelegate {
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
callback(urls)
do {
let filePath = try FileManager.default.contentsOfDirectory(at: url.deletingLastPathComponent(), includingPropertiesForKeys: nil, options: [])[0]
let audioFile = try Data(contentsOf: filePath)
// let destinationURL = store.folderURL!.appendingPathComponent(filePath.lastPathComponent)
// try FileManager.default.moveItem(at: filePath, to: destinationURL)
// print("File moved to documents folder")
}
catch {
print(error)
}
}
}
Here's the store code:
class MusicStore : ObservableObject {
var folderURL : URL?
init(){
do{
self.folderURL = try FileManager.default.url(
for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: false
)} catch(let error) {
print(error.localizedDescription)
}
}
So I want to import the file to my app's sandbox within the documentPicker function, like in the commented code. But I can't add an environment object to the view controller. I don't know whether it's possible at all to do it because I'm using the root view controller to show the document picker.

Related

Using PDFKit to Open PDF in App Automatically after Downloading

Missing some connection which I am unable to figure out (also googled a lot still no success) in how to open PDF file in app. I am using PDFKit. This is the PDFKitView struct in which URL should be passed:
struct PDFKitView: View {
#State var reportId: ReportResponse
var url:URL
var body:some View
{
PDFKitRepresentedView(url)
}
}
struct PDFKitRepresentedView: UIViewRepresentable{
func updateUIView(_ uiView: UIView, context:
UIViewRepresentableContext<PDFKitRepresentedView>) {
//
}
let url: URL
init(_ url:URL)
{
self.url = url
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) ->
PDFKitRepresentedView.UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
pdfView.autoScales = true
return pdfView
}
}
This is the Report Row in which I am trying to pass URL to state object pdfDonwload:
struct ReportRow: View {
var report : ReportResponse
#StateObject var pdfDownload:URL = URL(fileURLWithPath: "")
var body: some View {
VStack{
HStack{
Text(report.name)
//formatting
}.frame(maxWidth: .infinity, alignment: .leading)
HStack{
Text("P.Id:")
//formatting
Text(report.patientID)
//formatting
Spacer()
Text("Status")
//formatting
Text(report.status)
//formatting
}}
.onAppear{
Task{
do{
try await getPath()
}catch{Alert(title:"Text")}
}
}}
func getPath() async throws
{
var urlComponents = URLComponents()
//add other components
urlComponents.queryItems = [URLQueryItem(name:
"uniquePackageId", value:
uniqueReportId)]
let url = urlComponents.url
let downloadTask = URLSession.shared.downloadTask(with: url!)
{
urlOrNil, responseOrNil, errorOrNil in
guard let fileURL = urlOrNil else {return}
do
{
let documentURL = try FileManager.default.url(for:
.documentDirectory, in:
.userDomainMask, appropriateFor: nil, create: false)
let savedURL = documentURL.appendingPathComponent("\
(self.patientName)_\(UUID().uuidString).pdf")
print(savedURL)
try FileManager.default.moveItem(at: fileURL, to:
savedURL)
DispatchQueue.main.async {
pdfDownload = url!
}
}
catch
{
print("Error")
}}
downloadTask.resume()
}}
However, no value is passed to PDFKitView() it is blank. The first issue is PDFKitView should get the updated pdfDownload:URL value from the func call. Neither the value is updating nor passing updated value to PDFKitView.
This is the list struct:
struct ReportList: View{
#ObserveObjecct var reportLink:ReportViewModel
#State var pdfDownload:URL = URL(fileURLWithPath="")
var body:some view{
NavigationView{
List{
ForEach(reportLink.trackReport)
{report in
VStack{NavigationLink
(destination:PDFKitView(url:pdfDownload,
pdfDownload:pdfDownload,report:report))
{
ReportRow(report:report)
}
}}}}}}
I want file should open automatically after downloading. Running the app in simulator.

SwiftUI User selects image and saves it within app to be retrieved later

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()
}
}

How to display data related to the same row in CoreData at IntentConfiguration Widget view iOS 14?

I'm currently developing an application using SwiftUI and trying to make a widget ios 14 user can choose a data to check its detail using IntentConfiguration
An outline I want to do in this App is like below:
A user adds some data at an adding view (id: UUID, task: String, status: String, and some...) to
CoreData in Host App
The user chooses data the user wants to check its detail in the edit widget
The user can check a brief detail in the widget view
If the user taps the widget view, can check detailed data in a detailed view in the host App
In my codes, I could implement almost all the functions I explain above.
But I don't know How can I display task data in the edit widget...
So far, I specify UUID data in CoreData as a Parameter in Configuration. Because WidgetEntryView needs UUID(or something unique value) to filter which row requests to CoreData and to make URL for DeepLink to detail view in host App.
So the list view in the widget displays UUID data like below.
But I want to display task data instead of UUID in that list view keeping to give UUID data to the Widget view as well.
If I specify task data in CoreData as a Parameter in Configuration, the widget displays tasks as a list. But in that case a filter for request data to Coredata (NSPredicate(format: "id == %#") and widgetURL() don't work...
How could I implement this?
Here are the codes:
TimerIntentWidget.swift
import Foundation
import WidgetKit
import SwiftUI
import CoreData
struct Provider: IntentTimelineProvider {
typealias Intent = ConfigurationIntent
var moc = PersistenceController.shared.managedObjectContext
init(context : NSManagedObjectContext) {
self.moc = context
}
func placeholder(in context: Context) -> SimpleEntry {
var timerEntity:TimerEntity?
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
do{
let result = try moc.fetch(request)
timerEntity = result.first
}
catch let error as NSError{
print("Could not fetch.\(error.userInfo)")
}
return SimpleEntry(configuration: ConfigurationIntent(), date: Date(), timerEntity: timerEntity!)
}
func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: #escaping (SimpleEntry) -> ()) {
var timerEntity:TimerEntity?
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
do{
let result = try moc.fetch(request)
timerEntity = result.first
}
catch let error as NSError{
print("Could not fetch.\(error.userInfo)")
}
let entry = SimpleEntry(configuration: configuration, date: Date(), timerEntity: timerEntity!)
completion(entry)
}
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
var timerEntity:TimerEntity?
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
request.predicate = NSPredicate(format: "id == %#", UUID(uuidString: configuration.UUID!)! as CVarArg)
do{
let result = try moc.fetch(request)
timerEntity = result.first
}
catch let error as NSError{
print("Could not fetch.\(error.userInfo)")
}
var entries: [SimpleEntry] = []
let currentDate = Date()
for hourOffset in 0 ..< 5 {
let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
let entry = SimpleEntry(configuration: configuration, date: entryDate, timerEntity: timerEntity!)
entries.append(entry)
}
let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}
}
struct SimpleEntry: TimelineEntry {
let configuration: ConfigurationIntent
let date: Date
let timerEntity:TimerEntity?
}
struct TimerIntentWidgetEntryView : View{
var entry: Provider.Entry
var body: some View {
VStack{
Text(entry.timerEntity!.id!.uuidString)
Divider()
Text(entry.timerEntity!.task!)
Divider()
Text(entry.timerEntity!.status!)
Divider()
Text(entry.date, style: .time)
}
.widgetURL(makeURLScheme(id: entry.timerEntity!.id!))
}
}
#main
struct TimerIntentWidget: Widget {
let kind: String = "TimerIntentWidget"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider(context: PersistenceController.shared.managedObjectContext)) { entry in
TimerIntentWidgetEntryView(entry: entry)
.environment(\.managedObjectContext, PersistenceController.shared.managedObjectContext)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
func makeURLScheme(id: UUID) -> URL? {
guard let url = URL(string: "timerlist://detail") else {
return nil
}
var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)
urlComponents?.queryItems = [URLQueryItem(name: "id", value: id.uuidString)]
return urlComponents?.url
}
IntentHandler.swift
import WidgetKit
import SwiftUI
import CoreData
import Intents
class IntentHandler: INExtension,ConfigurationIntentHandling {
var moc = PersistenceController.shared.managedObjectContext
func provideUUIDOptionsCollection(for intent: ConfigurationIntent, with completion: #escaping (INObjectCollection<NSString>?, Error?) -> Void) {
let request = NSFetchRequest<TimerEntity>(entityName: "TimerEntity")
var nameIdentifiers:[NSString] = []
do{
let results = try moc.fetch(request)
for result in results{
nameIdentifiers.append(NSString(string: result.id?.uuidString ?? ""))
}
}
catch let error as NSError{
print("Could not fetch.\(error.userInfo)")
}
let allNameIdentifiers = INObjectCollection(items: nameIdentifiers)
completion(allNameIdentifiers,nil)
}
override func handler(for intent: INIntent) -> Any {
return self
}
}
TimerIntentWidget.intentdefinition
Persistence.swift (Host App)
import CoreData
class PersistenceController {
static let shared = PersistenceController()
private init() {}
private let persistentContainer: NSPersistentContainer = {
let storeURL = FileManager.appGroupContainerURL.appendingPathComponent("TimerEntity")
let container = NSPersistentContainer(name: "ListTimer")
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: storeURL)]
container.loadPersistentStores(completionHandler: { storeDescription, error in
if let error = error as NSError? {
print(error.localizedDescription)
}
})
return container
}()
}
extension PersistenceController {
var managedObjectContext: NSManagedObjectContext {
persistentContainer.viewContext
}
}
extension PersistenceController {
var workingContext: NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
context.parent = managedObjectContext
return context
}
}
import Foundation
extension FileManager {
static let appGroupContainerURL = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.com.sample.ListTimer")!
}
Xcode: Version 12.0.1
iOS: 14.0
Life Cycle: SwiftUI App
You can create a custom type for your configuration parameter. Currently you're using String which limits you to one value only.
Instead create a custom type, let's call it Item:
Now you have the identifier and displayString properties for your Item which can be mapped to the UUID and task properties of your model.
Then, in the IntentHandler instead of INObjectCollection<NSString>? you need to provide INObjectCollection<Item>? in the completion.
Assuming you already have your results fetched from Core Data, you only need to map them to the Item objects:
let results = try moc.fetch(request)
let items = results.map {
Item(identifier: $0.id.uuidString, display: $0.task)
}
completion(items, nil)
This way you can use the display property to show readable information to the user but also have the identifier property which can be later used to retrieve the Core Data model.

how to save pdf file from Firebase to document directory

Hello I'm try to save a file test.pdf from firebase storage to the document directory of my app
but unfortunately not working.
here my content view with a button to run the task:
import SwiftUI
import Firebase
import WebKit
struct ContentView: View {
var body: some View {
VStack {
Button(action: {
let storage = Storage.storage()
let storageRef = storage.reference()
let islandRef = storageRef.child("test.pdf")
// Create local filesystem URL
let localURL = URL(string: self.cartellaDocuments())!
let downloadTask = islandRef.write(toFile: localURL) { (url, err) in
if err != nil {
debugPrint(" // Uh-oh, an error occurred!")
} else {
debugPrint("\(String(describing: url))")
}
}
}) {
Text("esegui")
}
}
}
func cartellaDocuments() -> String {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
debugPrint(paths[0])
return paths[0]
}
}
my storage in firebase:
I have try to follow the google firebase instruction but Im getting a following warning:
failed because it was passed an URL which has no scheme
how can I solve this issue.
thanks for the help
You can try this :
let pdfView = PDFView()
pdfView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(pdfView)
pdfView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor).isActive = true
pdfView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor).isActive = true
pdfView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
pdfView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor).isActive = true
if let document = PDFDocument(url: URL.init(string: "https://your storage download url")!) {
pdfView.document = document
}

Swift3 file preview not working

I think something changed within Swift that disabled me from previewing my files. It worked fine previously. If I click on say a PDF file in my app, I see the title of the PDF, but the content of PDF (preview) area does not show.
Below is my code & logs & also the screenshot. If anyone has an idea of where I can fix the issue, any help would be greatly appreciated.
// When file is clicked this method is called
#objc private func handleTapped() {
guard let url = self.file.fileUrl else { return }
if self.file.isDownloaded {
self.showDocumentController(url: self.file.urlInDocumentsDirectory! as NSURL)
return
}
SVProgressHUD.showProgress(0)
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileURL = documentsURL.appendingPathComponent("pig.png")
return (documentsURL, [.removePreviousFile, .createIntermediateDirectories])
}
Alamofire.download(url, to: destination)
.downloadProgress { (download) in
DispatchQueue.main.async() {
SVProgressHUD.showProgress(Float(download.fractionCompleted))
}
}.validate(statusCode: 200..<300)
.response { (response) in
SVProgressHUD.dismiss()
guard response.response?.statusCode == 200 else { return }
let directoryURL = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let pathURL = URL(fileURLWithPath: directoryURL, isDirectory: true)
//pathURL: file:///var/mobile/Containers/Data/Application/6DDCCC30-107C-4613-B63D-18962C3D06D3/Documents/
guard let fileName = response.response?.suggestedFilename else { return }
//fileName: 05_기조강연_RobertMankin_BETTER+OFFICES+GREATER+INNOVATION.pdf
let fileURL = pathURL.appendingPathComponent(fileName)
//fileURL: file:///var/mobile/Containers/Data/Application/6DDCCC30-107C-4613-B63D-18962C3D06D3/Documents/05_%E1%84%80%E1%85%B5%E1%84%8C%E1%85%A9%E1%84%80%E1%85%A1%E1%86%BC%E1%84%8B%E1%85%A7%E1%86%AB_RobertMankin_BETTER+OFFICES+GREATER+INNOVATION.pdf
self.saveFileURL(url: fileURL as NSURL)
self.showDocumentController(url: fileURL as NSURL)
}
}
private func saveFileURL(url: NSURL) {
self.file.urlInDocumentsDirectory = url as URL
let realm = RealmService.defaultRealm
try! realm?.write {
realm?.add(self.file, update: true)
}
self.file = self.file.copyFromRealm()
}
private func showDocumentController(url: NSURL) {
let docController = UIDocumentInteractionController(url: url as URL)
docController.delegate = self
docController.presentPreview(animated: true)
}
// MARK: UIDocumentInteractionControllerDelegate methods
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
if let controller = UIApplication.shared.keyWindow?.topMostViewController() {
return controller
}
return UIViewController()
}
this is how the preview shows
Here Is The Code
import UIKit
import Alamofire
class ViewController: UIViewController, UIWebViewDelegate
{
#IBOutlet weak var WebView: UIWebView!
var NewsURL: String = ""
override func viewDidLoad()
{
super.viewDidLoad()
Self.LoadPdf()
}
func LoadPdf()
{
let url = NSURL (string: "\(http://)") //Your Pdf URL Here
let requestObj = NSURLRequest(URL: url!);
WebView.loadRequest(requestObj)
}
}