UIDocument not working - ios

I'm trying to open document file by following way but nothing happens.
I tried other ways too but it didn't work for me. What is the correct way open Document file (doc,docx,pdf,ppt, etc.). I read somewhere by UIWebView() also this can be done so what is the better way?
let fileURL = URL(fileURLWithPath: documentMessage.fileUrl)
let doc = DocumentPreview(fileURL)
doc.startPreview()
DocumentPreview.swift
class DocumentPreview : NSObject, UIDocumentInteractionControllerDelegate {
var url : URL?
var document : UIDocumentInteractionController?
var previewVC : UINavigationController?
init(_ url: URL) {
super.init()
self.url = url
document = UIDocumentInteractionController(url:url)
document?.delegate = self
}
func startPreview(){
document?.presentPreview(animated:true)
}
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return previewVC!
}
func documentInteractionControllerDidEndPreview(_ controller: UIDocumentInteractionController) {
print("end Preview")
previewVC!.popViewController(animated: true)
}
}

Take a look at documentMessage. If the .fileUrl property is a URL, then this code is wrong:
let fileURL = URL(fileURLWithPath: documentMessage.fileUrl)
The simplest fix would be this:
let fileURL = documentMessage.fileUrl

Related

Is there a way to pass a ModelEntity to ARQuickLook preview controller?

I'm working on a project where I have to download a USDZ file from a URL, preconfigured with white materials, then customize it in runtime and finally view it in AR with ARQuickLook.
At the moment, I thought the best way was to download the asset using the ModelEntity download method, change its properties and then show it with the ARQuickLook preview.
Currently, I am completely stuck in the last step where I am looking for the way to pass the modified model entity to the ARQuickLook preview controller, but it only accepts a URL and no other data types.
A simple code example below:
var modelURL: URL?
override func viewDidLoad() {
super.viewDidLoad()
self.downloadUSDZ()
}
#IBAction func arQuickLookButtonPressed(_ sender: Any) {
guard modelURL != nil else { return }
let previewController = QLPreviewController()
previewController.dataSource = self
present(previewController, animated: true, completion: nil)
}
func downloadUSDZ() {
modelURL = URL(string: "https://developer.apple.com/augmented-reality/quick-look/models/drummertoy/toy_drummer.usdz")!
guard let entity = try? ModelEntity.loadModel(contentsOf: modelURL!) else {
print("Entity download failed")
return
}
for child in entity.children {
var newMaterial = SimpleMaterial()
newMaterial.color.tint = UIColor.cyan
child.model?.materials = [newMaterial]
}
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int { return 1 }
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
let previewItem = ARQuickLookPreviewItem(fileAt: modelURL!) //<---- HERE I NEED TO DISPLAY THE MODIFIED MODEL ENTITY
previewItem.canonicalWebPageURL = URL(string: "https://developer.apple.com/augmented-reality/quick-look/models/drummertoy/")
previewItem.allowsContentScaling = false
return previewItem
}
Can anyone give me some advice on how to proceed?
Other ways to reach the goal are also accepted.
I'm not sure if this is doable with ARQuickLook. But we can use either SceneKit or RealityKit ARView and modify the ModelEntity at runtime. You could do something like this, Using ARView in RealityKit:
#IBOutlet var arView: ARView!
override func viewDidLoad() {
super.viewDidLoad()
let modelURL = URL(string: "https://developer.apple.com/augmented-reality/quick-look/models/drummertoy/toy_drummer.usdz")!
guard let entity = try? ModelEntity.loadModel(contentsOf: modelURL!) else {
print("Entity download failed")
return
}
for child in entity.children {
var newMaterial = SimpleMaterial()
newMaterial.color.tint = UIColor.cyan
child.model?.materials = [newMaterial]
}
let anchor = AnchorEntity(plane: .horizontal)
anchor.addChild(entity)
arView.scene.addAnchor(anchor)
}
Please keep in mind that you will have to manually add the transform/scale actions that you get automatically with ARQuickLook.

Delegate is not invoked from DispatchQueue.global() thread

I have a scenario where I need to load data from a JSON object, I've created an helper class that does that and it looks like this
protocol JSONDumpHelperDelegate {
func helper(_: JSONDumpHelper, didFinishFetching: [Link])
func helper(_: JSONDumpHelper, completionProcess: Double)
}
struct JSONDumpHelper {
static let pointsOfInterest = OSLog(subsystem: "com.mattrighetti.Ulry", category: .pointsOfInterest)
var delegate: JSONDumpHelperDelegate?
func loadFromFile(
with filemanager: FileManager = .default,
from url: URL,
decoder: JSONDecoder = JSONDecoder(),
context: NSManagedObjectContext = CoreDataStack.shared.managedContext,
dataFetcher: DataFetcher = DataFetcher()
) {
os_signpost(.begin, log: JSONDumpHelper.pointsOfInterest, name: "loadFromFile")
let data = try! Data(contentsOf: url)
let dump = try! decoder.decode(Dump.self, from: data)
var tagsHash: [UUID:Tag] = [:]
if let tagsCodable = dump.tags {
for tagCodable in tagsCodable {
let tag = Tag(context: context)
tag.id = tagCodable.id
tag.name = tagCodable.name
tag.colorHex = tagCodable.colorHex
tagsHash[tag.id] = tag
}
}
var groupHash: [UUID:Group] = [:]
if let groupsCodable = dump.groups {
for groupCodable in groupsCodable {
let group = Group(context: context)
group.id = groupCodable.id
group.name = groupCodable.name
group.colorHex = groupCodable.colorHex
group.iconName = groupCodable.iconName
groupHash[group.id] = group
}
}
var links: [Link] = []
if let linksCodable = dump.links {
let total = linksCodable.count
var completed = 0.0
delegate?.helper(self, completionProcess: 0.0)
for linkCodable in linksCodable {
let link = Link(context: context)
link.id = linkCodable.id
link.url = linkCodable.url
link.createdAt = linkCodable.createdAt
link.updatedAt = linkCodable.updatedAt
link.colorHex = linkCodable.colorHex
link.note = linkCodable.note
link.starred = linkCodable.starred
link.unread = linkCodable.unread
if let uuidGroup = linkCodable.group?.id {
link.group = groupHash[uuidGroup]
}
if let tags = linkCodable.tags {
link.tags = Set<Tag>()
for tagUUID in tags.map({ $0.id }) {
link.tags?.insert(tagsHash[tagUUID]!)
}
}
links.append(link)
completed += 1
delegate?.helper(self, completionProcess: completed / Double(total))
}
}
os_signpost(.end, log: JSONDumpHelper.pointsOfInterest, name: "loadFromFile")
}
}
This could potentially be a very long running task, just imagine an array with 1k records that also need to fetch data from the internet (not shown in implementation, error still exist with posted code) and you can easily end up with 10s in execution time.
What I'm trying to achieve is to show the user an alert that will show him the progress of the import process, by updating the values with the delegate protocols below.
extension BackupViewController: JSONDumpHelperDelegate {
func helper(_: JSONDumpHelper, didFinishFetching: [Link]) {
DispatchQueue.main.async {
self.completionViewController.remove()
}
}
func helper(_: JSONDumpHelper, completionProcess: Double) {
DispatchQueue.main.async {
self.completionViewController.descriptionLabel.text = "Completed \(Int(completionProcess * 100))%"
}
}
}
The import method is fired from a UITableView, immediately after the user choses a file from a DocumentPickerViewController
extension BackupViewController: UIDocumentPickerDelegate {
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
self.initImport(for: urls)
}
}
private func initImport(for urls: [URL]) {
if let url = urls.first {
completionViewController.add(self, frame: self.view.bounds)
completionViewController.descriptionLabel.text = "Fetching"
DispatchQueue.global(qos: .userInteractive).async {
self.dumpHelper.loadFromFile(from: url)
}
}
}
The problem I am facing is that when the user initiates the import process, the UI is not updated until the process itself is finished.
If I place breakpoints both at the protocol implementations and at the delegate calls in the helper class I can see that the delegate is not called immediately but they all get fired when the process ended (but the alert controller does not update its values).
Just to place some more info I'm going to replicate an import of N elements from JSON:
User clicks import process
initImport is executed (nothing is shown on UI even if I add the custom vc to view)
JSONDumpHelper.loadFromFile is executed entirely, calling delegate N times, nothing called in the delegate implementation
loadFromFile finishes execution
delegate implementation of helper(_:, completionProcess) is executed N times, UI always shows "Completed 0%"
delegate implementation of helper(_:, didFinishFetching) is executed, controller is removed from view
Can anybody point out what is wrong with this implementation? It seems like the loadFromFile function is not working in a separate Queue and UI is stuck and can't update as expected.

Create Files,Debug Step By Step,Crash--EXC_BAD_ACCESS

I want to create test.txt file, sample codes.
I run this app, well done.
But when I debug app step by step, it crash, report
(EXC_BAD_ACCESS(code=EXC_l386_GPFLT)),method:
FileManager.default.createFile()
SWIFT CODES:
import UIKit
class ViewController: UIViewController {
#IBAction func CreateFileButton(_ sender: UIButton) {
createFiles()
}
private func createFiles() {
let manager = FileManager.default
let urlForDocument = manager.urls( for: .documentDirectory,
in:.userDomainMask)
let url = urlForDocument[0]
createFile(name:"test.txt", fileBaseUrl: url)
}
func createFile(name:String, fileBaseUrl:URL){
let manager = FileManager.default
let file = fileBaseUrl.appendingPathComponent(name)
print("f: \(file)")
let exist = manager.fileExists(atPath: file.path)
if !exist {
let data = Data(base64Encoded:"aGVsbG8gd29ybGQ=" ,options:.ignoreUnknownCharacters)
let createSuccess = manager.createFile(atPath: file.path,contents:data,attributes:nil)
print("f: \(createSuccess)")
}
}
}
^ Crash step method:FileManager.default.createFile()
^Crash report
^Just run app, do not debug, it well done

iOS11 How to handle a custom file dropped in a custom view

I've been messing around with the drop feature of iOS11. I found a lot of examples with the standard UIImage etc but nothing with custom files.
The drop part works pretty well, I use it to drop a custom file and import it into my app.
The problem is that I only get the Data of this file and I would like to get its name and extension for example.
I don't know if my way of handling the custom file dropped is how it should be to obtain this information. And if so, how do you get this information from the file?
I have a DropFile class conforming to the NSItemProviderReading protocol.
class DropFile : NSObject, NSItemProviderReading {
let fileData:Data?
required init(data:Data, typeIdentifier:String) {
fileData = data
}
static var readableTypeIdentifiersForItemProvider: [String] {
var documentTypeArray: [String] = []
for ext in FileImportProcessHelper.sharedInstance.getImportDocumentType() {
let UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext as CFString, nil)
documentTypeArray.append((UTI?.takeRetainedValue() as String?)!)
}
return documentTypeArray
}
static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> Self {
return self.init(data: data, typeIdentifier: typeIdentifier)
}
}
This is my ViewController conforming to the UIDropInteractionDelegate
// MARK: Handling Drop
#available(iOS 11.0, *)
// We refuse the dropped items based on their UTI
func dropInteraction(_ interaction: UIDropInteraction, canHandle session: UIDropSession) -> Bool {
var documentTypeArray: [String] = []
for ext in FileImportProcessHelper.sharedInstance.getImportDocumentType() {
let UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext as CFString, nil)
documentTypeArray.append(UTI?.takeRetainedValue() as! String)
}
return session.hasItemsConforming(toTypeIdentifiers: documentTypeArray) && session.items.count == 1
}
#available(iOS 11.0, *)
func dropInteraction(_ interaction: UIDropInteraction, sessionDidUpdate session: UIDropSession) -> UIDropProposal {
// Copy file from source app
return UIDropProposal(operation: .copy)
}
#available(iOS 11.0, *)
func dropInteraction(_ interaction: UIDropInteraction, performDrop session: UIDropSession) {
// Consume drag items
session.loadObjects(ofClass: DropFile.self) { items in
if let fileItems = items as? [DropFile] {
DragnDropManager.sharedManager.createDropObject(fileItems: fileItems)
}
}
}
And then how I write my file.
func createDropObject(fileItems: [DropFile]) {
let documentsPathString = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
let inbox = URL(fileURLWithPath: documentsPathString!).appendingPathComponent("UserDropInbox/")
do {
try FileManager.default.createDirectory(atPath: inbox.path, withIntermediateDirectories: true, attributes: nil)
} catch let error as NSError {
NSLog("Unable to create directory \(error.debugDescription)")
}
for file in fileItems {
do {
let dropFilePath = inbox.appendingPathComponent("File").appendingPathExtension("pdf")
try file.fileData?.write(to:dropFilePath)
} catch {
NSLog(error as! String)
}
}
FileImportInboxManager.shared.hasReceivedFiles = true;
}
I admit, it's not straightforward :
you can list all the UIDragItems dragged by the user in the UIDropSession items member
each of these items have a NSItemProvider itemProvider member
this item provider has an optional String? suggestedName member that may not be nil
A simple loop to print all the session's items suggested names :
for item in session.items {
if let name = item.itemProvider.suggestedName {
print(name)
}
}
Practically, when files come from the File app it will provide the name of the file without its extension, but given you can easily access its UTI, you can append an extension that will be close to the original one (jpeg instead of jpg for example).

Crosswalks on ios - can not find the XWalkView module

I am trying to use crosswalk to make my web app into iOS app
and I following the steps of Quickstart in this page https://github.com/crosswalk-project/crosswalk-ios
but I keep facing the problem:
in the ViewController.swift
When I import XWalkView, It always appears that It can't find the XWalkView module which makes me stop from my development...
Somebody help me.
Here's the code of ViewController.swift:
import UIKit
import WebKit
import XWalkView
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var start_url = "index.html"
var xwalk_extensions = ["Extension.load"]
if let plistPath = NSBundle.mainBundle().pathForResource("manifest", ofType: "plist") {
if let manifest = NSDictionary(contentsOfFile: plistPath) {
start_url = manifest["start_url"] as? String ?? start_url
xwalk_extensions = manifest["xwalk_extensions"] as? [String] ?? xwalk_extensions
}
}
let webview = WKWebView(frame: view.frame, configuration: WKWebViewConfiguration())
webview.scrollView.bounces = false
view.addSubview(webview)
for name in xwalk_extensions {
if let ext: AnyObject = XWalkExtensionFactory.createExtension(name) {
webview.loadExtension(ext, namespace: name)
}
}
if let root = NSBundle.mainBundle().resourceURL?.URLByAppendingPathComponent("www") {
var error: NSError?
let url = root.URLByAppendingPathComponent(start_url)
if url.checkResourceIsReachableAndReturnError(&error) {
webview.loadFileURL(url, allowingReadAccessToURL: root)
} else {
webview.loadHTMLString(error!.description, baseURL: nil)
}
}
}
override func prefersStatusBarHidden() -> Bool {
return true
}
}
perhaps you forget to link the XWalkView.framework in your app target?
Try this step:
Open Xcode, select your app project, in 'General' -> 'Linked Frameworks and Libraries', select '+', choose 'XWalkView.framework' to add it into the linking frameworks. Then shot another build. It's ok if the XWalkView.framework turns red.

Resources