UIDocumentPickerViewController iOS13 not Working - ios13

On my application, i use UIDocumentPickerViewController to allow the user to pick files (import), but starting from iOS 13 that functionality stop working, basically the document picker is open, but the user can't choose a file (taping the file does nothing).
I made a simple sample just to isolate the code:
class ViewController: UIViewController, UIDocumentPickerDelegate {
#IBAction func openDocumentPicker(_ sender: Any) {
let types = [String(kUTTypePDF)]
let documentPickerViewController = UIDocumentPickerViewController(documentTypes: types, in: .import)
documentPickerViewController.delegate = self
present(documentPickerViewController, animated: true, completion: nil)
}
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
print("Cancelled")
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
print("didPickDocuments at \(urls)")
}
}
Sample project:
https://github.com/Abreu0101/document-picker-iOS13-issue
Reference:

When I got this issue, I realised that it's working when choosing files from "Browse" tab because I implemented the method "didPickDocumentAt", but it was not working when I tapped on files from "Recent" tab.
To make it work on "Recent" tab, I'd to implement the method "didPickDocumentsAt", which makes the same thing, but it handles an array of URLs.

On Mojave there's the problem, make sure you upgrade your os to Catalina.
https://github.com/Elyx0/react-native-document-picker/issues/246
UIDocumentBrowserViewController error "Cannot create urlWrapper for url" on iOS13 simulator

I enountered this issue on iOS 13.2.2. Updated to iOS 13.2.3 fixed this issue without any code changes.

Related

How do I use cordova firebase.dynamiclinks plugin in Ionic 4?

I want to use Cordova Firebase Dynamiclinks plugin : https://github.com/chemerisuk/cordova-plugin-firebase-dynamiclinks#installation in my Ionic 4 App.
There is an Ionic-native-plugin usage for this too : npm install #ionic-native/firebase-dynamic-links and usage :
import { FirebaseDynamicLinks } from '#ionic-native/firebase-dynamic-links/ngx';
constructor(private firebaseDynamicLinks: FirebaseDynamicLinks) { }
...
this.firebaseDynamicLinks.onDynamicLink()
.subscribe((res: any) => console.log(res), (error:any) => console.log(error));
Issue is : I want to use createDynamicLink(parameters) method available in Cordova Firebase Dynamiclinks plugin but Ionic-native-plugin says
Property 'createDynamicLink' does not exist on type 'FirebaseDynamicLinks'.
Therefore, I need to use Cordova Firebase Dynamiclinks directly and I tried doing using it like
import { cordova } from '#ionic-native/core';
...
cordova.plugins.firebase.dynamiclinks.createDynamicLink({
link: "https://google.com"
}).then(function(url) {
console.log("Dynamic link was created:", url);
});
But got error
Property 'plugins' does not exist on type '(pluginObj: any, methodName: string, config: CordovaOptions, args: IArguments | any[]) => any'.
Also tried removing import
cordova.plugins.firebase.dynamiclinks.createDynamicLink({
link: "https://google.com"
}).then(function(url) {
console.log("Dynamic link was created:", url);
});
And got this
Property 'firebase' does not exist on type 'CordovaPlugins'.
What is the correct usage of cordova plugins?
Update
Ionic-native-plugin now contains all the methods available in Cordova Firebase Dynamiclinks plugin.
I believe this is more fitting of a comment, but I don't quite have the reputation for it yet.
Currently, there is a PR open in the #ionic-team/ionic-native repo (here). This exposes the extra methods, but until then you can use the original repo here to get your desired effect. In order to install the repo you will have to follow the directions in the Developer guide here. Cheers!
I have developed an ionic 5 app that uses Firebase Dynamic Links and it works great but it took some effort. I watched videos to understand how Firebase Dynamic Links work but there is certainly much that is not shown.
To answer the original question you can always manually create a dynamic link which is what I do in our solution. We created a dynamic link that would help users onboard (register an account). Our dynamic link has custom onboardingId which originates from the backend process and the link is presented to the user via SMS text message.
This is in app.component.ts constructor
Here is some code that happens when the user clicks the dynamic link:
// Handle the logic here after opening the app (app is already installed) with the Dynamic link
this.firebaseDynamicLinks.onDynamicLink().subscribe((res: any) => {
console.log('app was opened with dynamic link');
console.log(res);
/* This only fires on subsequent calls and not on app start 20220208 STR
console.log(JSON.stringify(res)); //"{"deepLink":"https://localhost/onboard?onboardingId=8ed634b0-53b7-4a0f-b67e-12c06019982a","clickTimestamp":1643908387670,"minimumAppVersion":0}"
var dynamicLink = JSON.parse(JSON.stringify(res));
var deepLink = dynamicLink.deepLink;
console.log(deepLink);
if (deepLink.indexOf("onboard")>=0){
this.isOnboarding = true;
}
alert("deepLink ="+ deepLink);
*/
}, (error:any) => {
console.log(error)
});
I originally thought that Firebase handles all of the magic if the user doesn't have the app installed. I was wrong! You MUST also handle the code to pickup the dynamic link after the app is installed.
The code below will read the dynamic link from the clipboard and survives the app install process. Placed in app.component.ts ngOnInit().
this.platform.ready().then(() => {
this.firebaseDynamicLinks.getDynamicLink().then((data) => {
//added 20220208 STR try to help open the deep link if app is just installed
if (data != null) {
console.log(JSON.stringify(data));
//alert("initializeApp():"+JSON.stringify(data));
var dynamicLink = JSON.parse(JSON.stringify(data));
var deepLink = dynamicLink.deepLink;
console.log("initializeApp():"+deepLink);
if (deepLink != "") {
if (deepLink.indexOf("onboard")>=0){
this.isOnboarding = true;
this.deepLinkToOnboard(deepLink);
}
}
}
});}
So to handle dynamic links after you have the Firebase plugin installed, you must have two sections of code; one for handling if the app is already installed and another for handling the dynamic link if the app is not installed.

Download a file from a URL to a user accessible location

I am building an app using Nativescript/Angular 2
I want to be able to download a file from a URL and save it to the device in a location the average user would have no problems finding it. I believe downloads would be the best place for this on both iOS and Android. Please correct me if I am wrong.
The file can be any file type, not just an image. So mainly spreadsheet, word document, pdf, png, jpg, etc.
I have searched online and through the documentation. The documentation describes a method called getFile which gets a file and saves it to your device.
I have implemented this in my code as follows:
download (id) {
console.log('Download Started');
getFile("https://raw.githubusercontent.com/NativeScript/NativeScript/master/apps/tests/logo.png").then(function (r) {
console.log(r.path);
}, function (e) {
//// Argument (e) is Error!
});
}
The problem with this is that it saves it to a non-user accessible location such as:
/data/user/0/com.myapp.example/files/logo.png
Update:
I have also tried specifying the path directly with:
fs.knownFolders.documents();
However, this method gets the documents folder for the current application that is NOT accessible by the user or external applications
After some unsuccessful attempts, I finally found how to save file to user "Downloads" folder (something like sdcard/Download). You can use android.os.Environment method to get this folder.
Add this in your component:
import { getFile } from 'tns-core-modules/http';
import * as fileSystem from "tns-core-modules/file-system";
import { isAndroid } from "tns-core-modules/platform";
import { alert } from "tns-core-modules/ui/dialogs";
declare var android;
<...>
public download (url, fileName) {
if (isAndroid) {
const permissions = require("nativescript-permissions");
permissions.requestPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, "I need these permissions because I'm cool")
.then(() => {
let downloadedFilePath = fileSystem.path.join(android.os.Environment.getExternalStoragePublicDirectory(android.os.Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), fileName);
getFile(url, downloadedFilePath).then(resultFile => {
alert({
title: 'Saved!',
okButtonText: 'OK',
message: `File saved here:\n${resultFile.path}`
});
}, error => {
alert({
title: 'Error',
okButtonText: 'OK',
message: `${error}`
});
});
});
}
}
What else you should know:
1) There is no any kind of download indicator, standard system download bar also not appears, and I don't know how to solve this.
2) For iOS you may try to use
const filePath = fileSystem.path.join(fileSystem.knownFolders.ios.downloads().path, fileName);
getFile(url, filePath).then((resultFile) => {}, (error) => {});
I think, it's the shame that NS docs don't talk straight, that you can't save files in user accessible location only with NS functions. I figured it out only when I read comments in file /node_modules/tns-core-modules/file-system/file-system.d.ts
Hope this helps you.
To get it working on iPhone, you can do the following (TypeScript):
import { knownFolders, path } from "tns-core-modules/file-system";
let destination = path.join(knownFolders.documents(), "file_name.txt");
// logic to save your file here ...
// the important thing is that you have to save your file in knownFolders.documents()
Then in Info.plist, you have to add the following permissions:
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
Now if you go to your iPhone's Files app > On My iPhone > Your App's Name, you should see the file there.
Basically, the Documents folder is a private folder inside your application's directory that only you can see. However, when you enable the two permissions above, it allows file sharing so that your user can access the folder and its contents.
The same documentation says that you can specify the file location like this:
download (id) {
console.log('Download Started');
var folder = fs.knownFolders.documents();
var file = fs.path.join(folder.path, "logo.png");
var url = "https://raw.githubusercontent.com/NativeScript/NativeScript/master/apps/tests/logo.png"
getFile(url, file).then(function (r) {
console.log(r.path);
}, function (e) {
//// Argument (e) is Error!
});
}
disclaimer: never tried it myself, just read the docs ...
You can specify a filesystem path directly, like this:
var folder = fs.Folder.fromPath('/sdcard/Download');
Note that /sdcard/Download will only work on Android; you can replace it with whatever (publicly accessible) folder you want to save your data to.
There doesn't yet seem to be a cross-platform way to choose a folder path, so you'll have to work out something manually. See this GitHub thread for more.
I realize that this is an older thread, but perhaps this can help someone:
If you use currentApp(), instead of documents(), you can access the folder you need. For example:
var directories = fs.knownFolders.currentApp();
var folder = directories.getFolder('./nameofaccessiblefolder');
I know this thread is 3 years ago but in case you have the same issue, I hope this solution will save time for you.
I solved the same issue by adding android:requestLegacyExternalStorage="true" inside the AndroidManifest.xml file
follow the thread here

Meteor : "console" variable undefined inside require call

I'm facing a strange problem on Meteor and I can't resolve it :
I'm developping a WebRTC app using Meteor, PeerJS and AdapterJS (which give an WebRTC plugin for unsupported browser like Safari or IE). These two libs are downloaded using NPM : meteor npm install peerjs/adapterjs
So in my view's controller I have :
view.js
//import Peer from 'peerjs'; => same error with "import"
//import AdapterJS from 'adapterjs';
Template.view.onRendered(function(){
AdapterJS = require("adapterjs");
Peer = require("peerjs");
//var peerkey="..."
var peer = new Peer({
key: peerkey, // get a free key at http://peerjs.com/peerserver
debug: 3,
config: {'iceServers': [
{ url: 'stun:stun.l.google.com:19302' },
{ url: 'stun:stun1.l.google.com:19302' },
]}
});
But when I run my controller, I get an exception because "console" is undefined inside peerjs/util.js function when calling the peerjs constructor :
Uncaught TypeError: Cannot read property 'log' of undefined
Strangly, when I only require "peerjs", there is no exeption...
I tried to change the order of require functions but it won't work.
Other variable like "alert", "window.console" work and are defined inside the module but "console" not.. :/
Any suggestion can help ^^
Thanks in advance.
EDIT : If I add a breakpoint on the first line of node_module/peerjs/lib/util.js, I see that the "console" variable is "undefined" inside util.js but .... it is defined inside the caller function (fileEvaluate) !
EDIT2 : I tried something else to check if the code inside adapterjs redefine or change something : I put 'require("adapterjs")' inside a timeout function with a long delay (10 seconds) and .... console is still undefined inside peer module ! But when I comment require("adapterjs"), no error, console is defined ! I think that Meteor do something special before running the controller script depending on require functions...
EDIT3 : Here is a git repo to test the project : gitlab.com
If you display the dev console, you will see exceptions.
I found a solution, although I don't fully understand it myself. It's something to do with the way that Meteor imports modules, and Peerjs runs afoul of that.
Basically I copied node_modules/peerjs/dist/peer.js into the client directory, so that Meteor will load it "as is".
A small change to main.js as follows:
import './main.html';
// I placed peer.js from the node_modules/peerjs/dist/peer.js into the client folder and it works fine
// import {Peer} from 'peerjs';
import {AdapterJS as Adapter} from 'adapterjs';
Template.hello.onCreated(function helloOnCreated() {
// counter starts at 0
window.peer = new Peer({
and it works fine :)
I see in line 860+ of adapter.js that console is being defined (part of the shim) from https://github.com/Temasys/AdapterJS/blob/master/source/adapter.js
// IE 9 is not offering an implementation of console.log until you open a console
if (typeof console !== 'object' || typeof console.log !== 'function') {
/* jshint -W020 */
console = {} || console;
// Implemented based on console specs from MDN
// You may override these functions
console.log = function (arg) {};
console.info = function (arg) {};
console.error = function (arg) {};
This code defines the console if it doesn't find one to it's liking? Does this mean you are using IE9 or some other incompatible browser?
Try pasting this into your console and see what it tells you:
if (typeof console !== 'object' || typeof console.log !== 'function') alert("Console not present, needs to be shimmed?"); else console.log("console is ok");
Presumably the reason you are using adapter.js is for compatibility purposes - this will help you trouble shoot it. Please let me know what you find, as I will be following you down this path :)

QT Installer Controller Script crash while "addWizardPage"

I got some problems with the QT Installer - already done all tutorials (especially http://doc.qt.io/qtinstallerframework/noninteractive.html) but i am still a real newbie
What i need:
an installer with a language Selection as first page
adding a second (save) path in TargetDirectory
What i have:
an installscript.js (package root)
an ControlScript.js (path in config.xml)
What i tried in the ControlScript.js:
function Controller()
{
QMessageBox.information("DEBUG", "DEBUG", "DEBUG", QMessageBox.Ok);
installer.addWizardPage(component, "Start", QInstaller.Introduction);
QMessageBox.information("TEST", "TEST", "TEST", QMessageBox.Ok);
}
the Start.ui i placed at the config path and package root but nothing happens... the second MessageBox is never shown - the installer seems to be crashed
function Controller()
{
}
Controller.prototype.IntroductionPageCallback = function()
{
installer.addWizardPageItem(component ,"lineEdit",QInstaller.TargetDirectory);
}
same - nothing happens here installer crashed
hope you can help me to fix the code =)
and can someone please tell me how to change the language or setting a new pixmap (form an existing ressource) while the installer is running?
i wrote this incomplete code in installscript.js:
NewLanguageSeted = function()
{
var widget = gui.pageWidgetByObjectName("DynamicLanguageSelection");
QMessageBox.information("DEBUG", "DEBUG", "DEBUG", QMessageBox.Ok);
widget.Icon.setPixmap("");
installer.languageChanged();
QMessageBox.information("LanguageSelec", "LanguageSelec", "LanguageSelec", QMessageBox.Ok);
}
installer.languageChanged();
will Change all texts based on *.qm files - but how can i get / set the actual language?
widget.Icon.setPixmap("");
changes the pixmap - but i need to know what i have to insert in ""
for the ui file i use a resource file:
<property name="pixmap">
<pixmap resource="../../../resource/resource.qrc">:/DuerrPictures/watermark.png</pixmap>
As far as I know, installer pages canno't be added from the controller script. If you run the installer from QtCreator, you will see the corresponding debug output, which says something like component type is not defined.
To add the page you have to do it inside of the component script (e.g. the constructor). This one will be executed immediatly after you selected one of the 3 checkboxes. The .ui-file has to be part of the package, too:
Regarding the second linedit - It is the same problem! The function takes a component as argument - it has to be done inside the installscript.js.
installscript.js:
function Component()
{
QMessageBox.information("DEBUG", "DEBUG", "DEBUG", QMessageBox.Ok);
installer.addWizardPage(component, "Start", QInstaller.Introduction);
installer.addWizardPageItem(component ,"lineEdit",QInstaller.TargetDirectory);
QMessageBox.information("TEST", "TEST", "TEST", QMessageBox.Ok);
}
The pixmap should be settable by using the very same path you used in your .ui-file, i.e. :/DuerrPictures/watermark.png. Have your tried that?
And for your language problem - sorry, but I don't know anything about that. Check out the Scripting API - all script classes are listed there, maybe you can find something.

Marionette js itemview not defined: then on browser refresh it is defined and all works well - race condition?

Yeah it's just the initial browser load or two after a cache clear. Subsequent refreshes clear the problem up.
I'm thinking the item views just aren't fully constructed in time to be used in the collection views on the first load. But then they are on a refresh? Don't know.
There must be something about the code sequence or loading or the load time itself. Not sure. I'm loading via require.js.
Have two collections - users and messages. Each renders in its own collection view. Each works, just not the first time or two the browser loads.
The first time you load after clearing browser cache the console reports, for instance:
"Uncaught ReferenceError: MessageItemView is not defined"
A simple browser refresh clears it up. Same goes for the user collection. It's collection view says it doesn't know anything about its item view. But a simple browser refresh and all is well.
My views (item and collection) are in separate files. Is that the problem? For instance, here is my message collection view in its own file:
messagelistview.js
var MessageListView = Marionette.CollectionView.extend({
itemView: MessageItemView,
el: $("#messages")
});
And the message item view is in a separate file:
messageview.js
var MessageItemView = Marionette.ItemView.extend({
tagName: "div",
template: Handlebars.compile(
'<div>{{fromUserName}}:</div>' +
'<div>{{message}}</div>' +
)
});
Then in my main module file, which references each of those files, the collection view is constructed and displayed:
main.js
//Define a model
MessageModel = Backbone.Model.extend();
//Make an instance of MessageItemView - code in separate file, messagelistview.js
MessageView = new MessageItemView();
//Define a message collection
var MessageCollection = Backbone.Collection.extend({
model: MessageModel
});
//Make an instance of MessageCollection
var collMessages = new MessageCollection();
//Make an instance of a MessageListView - code in separate file, messagelistview.js
var messageListView = new MessageListView({
collection: collMessages
});
App.messageListRegion.show(messageListView);
Do I just have things sequenced wrong? I'm thinking it's some kind of race condition only because over 3G to an iPad the item views are always undefined. They never seem to get constructed in time. PC on a hard wired connection does see success after a browser refresh or two. It's either the load times or the difference in browsers maybe? Chrome IE and Firefox on a PC all seem to exhibit the success on refresh behavior. Safari on iPad fails always.
PER COMMENT BELOW, HERE IS MY REQIRE BLOCK:
in file application.js
require.config({
paths: {
jquery: '../../jquery-1.10.1.min',
'jqueryui': '../../jquery-ui-1.10.3.min',
'jqueryuilayout': '../../jquery.layout.min-1.30.79',
underscore: '../../underscore',
backbone: '../../backbone',
marionette: '../../backbone.marionette',
handlebars: '../../handlebars',
"signalr": "../../jquery.signalR-1.1.3",
"signalr.hubs": "/xyvidpro/signalr/hubs?",
"debug": '../../debug',
"themeswitchertool": '../../themeswitchertool'
},
shim: {
'jqueryui': {
deps: ['jquery']
},
'jqueryuilayout': {
deps: ['jquery', 'jqueryui']
},
underscore: {
exports: '_'
},
backbone: {
deps: ["underscore", "jquery"],
exports: "Backbone"
},
marionette: {
deps: ["backbone"],
exports: "Marionette"
},
"signalr": {
deps: ["jquery"],
exports: "SignalR"
},
"signalr.hubs": {
deps: ["signalr"],
exports: "SignalRHubs"
},
"debug": {
deps: ["jquery"]
},
"themeswitchertool": {
deps: ["jquery"]
}
}
});
require(["marionette", "jqueryui", "jqueryuilayout", "handlebars", "signalr.hubs", "debug", "themeswitchertool"], function (Marionette) {
window.App = new Marionette.Application();
//...more code
})
Finally, inside the module that uses creates the collection views in question, the list of external file dependencies is as follows:
var dependencies = [
"modules/chat/views/userview",
"modules/chat/views/userlistview",
"modules/chat/views/messageview",
"modules/chat/views/messagelistview"
];
Clearly the itemViews are listed before collectionViews. This seems correct to me. Not sure what accounts for the collectionViews needing itemViews before they are defined. And why is all ok after a browser refresh?
The sequence in which you load files is most likely wrong: you need to load the item view before the collection view.
Try putting all of your code in the same file in the proper order, and see if it works.
The free preview to my book on Marionette can also guide you to displaying a collection view.
Edit based on calirification:
The dependencies listed for the module are NOT loaded linearly. That is precisely what RequireJS was designed to avoid. Instead the way to get the files loaded properly (i.e. in the correct order), is by defining a "chain" of dependencies that RequireJS will compute and load.
What you need to do is define (e.g.) your userlistview to depend on userview. In this way, they will get loaded in the proper order by RequireJS. You can see an example of a RequireJS app here (from by book on RequireJS and Marionette). Take a look at how each module definition decalre which modules it depends on (and that RequireJS therefore needs to load before). Once again, listing the modules sequentially within a dependecy array does NOT make them get loaded in that sequence, you really need to use the dependency chain mechanism.

Resources