I've added support to import backup files from my app, and I try to show file importer as this:
.fileImporter(isPresented: $showFilePicker, allowedContentTypes: [UTType.init(filenameExtension: "mybackup")!], onCompletion: { (res) in
do {
let fileUrl = try res.get()
if fileUrl.startAccessingSecurityScopedResource() {
onRestoreBackup(file: fileUrl)
fileUrl.stopAccessingSecurityScopedResource()
}
} catch {
...
}
})
It works fine for local files, but if I browse the iCloud folder, all files of "mybackup" extension are grayed out.
What else do I need to do to be able to open those files with the file importer? I've seen others with the same problem, but not any solution.
I've come across a similar issue, but found it wasn't related to an iCloud folder.
I found that by adding a second random UTType it could be coaxed into working:
.fileImporter(isPresented: $showFilePicker, allowedContentTypes: [UTType.init(filenameExtension: "mybackup")!, UTType.init(filenameExtension: "bla")!], onCompletion: { (res) in
do {...
BTW I also included the conforming UUType, because it was a package type file. Something like:
.fileImporter(isPresented: $showFilePicker, allowedContentTypes: [UTType.init(filenameExtension: "mybackup", conformingTo: .package)!, UTType.init(filenameExtension: "bla")!], onCompletion: { (res) in
do {...
Yes - I real hack. Definitely not the best for production code. I had raised with Apple sometime ago...perhaps I will re-send again, with some further clarity: FB8583257
Related
I'm working on integrating a C++ library (the GRT, a machine learning toolkit, to be specific) inside of an iOS app.
I've built the GRT as a framework, including using some Objective-C++ wrapper functions to call between my app and the framework.
At the moment, I'm trying to troubleshoot something involving file loading. Specifically, I'm trying to load a file from my app bundle into a GRT module.
Here's where I get the file I want access to, and initialize the GRT wrapper:
func loadTrainingData(){
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileUrl = documentsUrl.appendingPathComponent("train.grt")
let pipeline = GestureRecognitionPipeline()
let test:Bool = pipeline.load(fileUrl.path)
print(test)
}
And here's the Obj-C++ wrapper code that's called when pipeline.load is called:
- (BOOL)load:(NSString *) path
{
BOOL result = self.instance->load(std::string(path.UTF8String));
if (result) {
std::cout << "GRT config";
std::cout << self.instance->getModelAsString();
std::cout << "GRT info: " << self.instance->getInfo();
}
return result;
}
Finally, here's the actual C++ code that's part of the GRT library, where file loading is handled:
bool GestureRecognitionPipeline::load(const std::string &filename){
std::fstream file;
//Clear any previous setup
clear();
file.open(filename.c_str(), std::iostream::in );
if( !file.is_open() ){
errorLog << __GRT_LOG__ << " Failed to open file with filename: " << filename << std::endl;
return false;
}
...
}
Currently, I'm always failing to have my pipeline object successfully import a file. I don't think it's necessarily something to do with the way I'm accessing the file on the iOS side (though, I could be wrong). Does anyone have any insight? Any appreciated - thanks!
EDIT: I was able to verify that I am loading my file is being loaded properly by this check:
let path = Bundle.main.path(forResource: "acc-orientation", ofType: "grt")
print(path as Any!)
But, I'm still getting the same issues as before.
EDIT 2 I verified that the path is being loaded correctly in the the Obj-C++ wrapper too; which leads me to think it may be something related to the way that is handled in iOS....totally lost here now...
EDIT 3 As suggested by a colleague, I tried using the absoluteString of the file url to pass to my wrapper and the underlying C++ code, since the C++ doesn't have access to the sandboxed environment of iOS. Still the same result:
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let fileUrl = documentsUrl.appendingPathComponent("acc-orientation.grt")
let pipeline = GestureRecognitionPipeline()
let test:Bool = pipeline.load(fileUrl.absoluteString)
EDIT 4 As suggested in the comments, I tried using fileSystemRepresentation, but this also didn't bring success.
- (BOOL)load:(NSURL *) url {
BOOL result = self.instance->load(std::string([url fileSystemRepresentation]));
...
}
EDIT 5: I made a simple test project that attempts to only access the file and load it using Swift->Obj-C++->C++ (no framework files, in other words). Here's a link where it can be downloaded. Any help appreciated!
Well, you are almost there. I have downloaded your sample project and got it working. Your problem has to do with the actual location of the file you want to open. At the moment you are trying to open the file from the Documents folder but you never actually copy the file from the App Bundle to the Documents folder. So there are two solutions:
Solution 1: App Bundle
Alter the loadTrainingData method in ViewController.swift to access the file from the App Bundle:
func loadTrainingData(){
let path = Bundle.main.url(forResource: "acc-orientation", withExtension: "grt")
let wrapper = Wrapper()
let test:Bool = wrapper.load(path)
print(test)
}
Solution 2: Documents folder
Copy the file from your App Bundle to your Documents folder right after the first launch. Therefore, copy the following code snippet to your AppDelegate.swift:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
do {
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("acc-orientation.grt")
let bundleURL = Bundle.main.url(forResource: "acc-orientation", withExtension: "grt")
try FileManager.default.copyItem(at: bundleURL!, to: url)
} catch {
print("File already exists")
}
return true
}
With either of these solutions your FileLoader::load method will return true.
Hope that helps.
I want to copy preloaded realm file, so I do:
Copied my file to project navigator named default.realm. Then check if fileExists::
let bundlePath = Bundle.main.path(forResource: "default", ofType: "realm")
let destPath = Realm.Configuration.defaultConfiguration.fileURL?.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: destPath!) {
//File exist, do nothing
print("File exist")
} else {
do {
//Copy file from bundle to Realm default path
try fileManager.copyItem(atPath: bundlePath!, toPath: destPath!)
print("Copied")
} catch {
print("\n",error)
}
}
And it always returns true, so I can't copy my realm file.
I ran your code in Simulator after resetting the contents and settings (Simulator > Reset Content and Settings...) and it appeared to execute as intended.
If you've previously used Realm in your project (i.e. testing), it's probably seeing the old default.realm file.
I'm newbie, plz help me to solve this out, I still have lots of other things to work on, really thank you thank you very much!
This is a further question after How to use FMDB on the generic iOS device instead of simulator?
When I execute the app on my device and the error threw out: "no such table: Student info", I've print all the path and they all pointed to the same file so I assumed the database has already copied? Console shows like this:
file:///var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/
file:///var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/Data.db
/var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/Data.db
file:///var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/
file:///var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/Data.db
/var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/Data.db
/var/mobile/Containers/Data/Application/B5E42F3C-524E-4BBF-8667-1EED0C963A77/Documents/Data.db
<NSFileManager: 0x17401c1b0>
2017-03-13 16:43:25.446039 Test1.3[16360:5045427] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2017-03-13 16:43:25.457278 Test1.3[16360:5045427] [MC] Reading from public effective user settings.
Insert failed:
Optional("no such table: Student info")
The Data.db is in my bundle resources in target; and the contents in my device is a blank Data.db;
The 2nd question: If you look at the Utility.Swift in the previous question, although the app works good on simulator but after it was loaded, there should be an alertView said "Your database copy successfully", but it didn't. Following is that part of the code:
class func copyFile(_ fileName: NSString){
let dbPath: String = getPath(fileName as String)
let fileManager = FileManager.default
print(dbPath)
print(fileManager)
if !fileManager.fileExists(atPath: dbPath) {
let documentsURL = Bundle.main.resourceURL
let fromPath = documentsURL!.appendingPathComponent(fileName as String)
var error : NSError?
do {
try fileManager.copyItem(atPath: fromPath.path, toPath: dbPath)
}
catch let error1 as NSError {
error = error1
}
if(error != nil){
self.invokeAlertMethod("Error Occured", strBody: "\(error?.localizedDescription)" as NSString, delegate: nil)
}
else{
self.invokeAlertMethod("Successed", strBody: "Your database copy successfully", delegate: nil)
}
}
}
Okay for answering this question I went through your demo.
Where I found couple of mistakes. Let me go through one by one.
1) Your class Utility have a getPath method. What it does it will
keep copying db every time although db is already present in documents
directory and your documents directory db will be replaced with the sample structure. You should always check that if db is already present in documents directory or not.
2) Your db was getting copied into documents directory but structure
wasn't. There was no Student info table in db of documents directory.
3) Please avoid using space or any special characters in table names.
So what I did just corrected your method getPath in utility class.
Please replace your method with this one
class func getPath(_ fileName: String) -> String {
let bundlePath = Bundle.main.path(forResource: "Data", ofType: ".db")
let destPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let fileManager = FileManager.default
let fullDestPath = URL(fileURLWithPath: destPath).appendingPathComponent("Data.db")
if fileManager.fileExists(atPath: fullDestPath.path){
print("Database file is exist")
print(fileManager.fileExists(atPath: bundlePath!))
}else{
do{
try fileManager.copyItem(atPath: bundlePath!, toPath: fullDestPath.path)
}catch{
print("\n",error)
}
}
print(fullDestPath.path)
return fullDestPath.path
}
After changing this piece of code I tried to run in my device and inserted couple of records.
Let me know if you have any more questions.
If you find this answer helpful just accept it.
First trying delete your app and then reinstall it.
OR
I have created a project over FMDB in Swift which you can use to solve your issue. FMDB Wrapper class you can use in Objective C project as well.
https://github.com/LalitKY/Swift-FMDB
Hope this helps.
I'm trying to follow Google's tutorial on making their QuickStart app to learn how to make API calls with Swift. I followed the tutorial completely and ended up with this code
import GoogleAPIClient
import GTMOAuth2
import UIKit
class ViewController: UIViewController {
private let kKeychainItemName = "Drive API"
private let kClientID = "592019061169-nmjle7sfv8i8eahplae3cvto2rsj4gev.apps.googleusercontent.com"
// If modifying these scopes, delete your previously saved credentials by
// resetting the iOS simulator or uninstall the app.
private let scopes = [kGTLAuthScopeDriveMetadataReadonly]
private let service = GTLServiceDrive()
let output = UITextView()
// When the view loads, create necessary subviews
// and initialize the Drive API service
override func viewDidLoad() {
super.viewDidLoad()
output.frame = view.bounds
output.editable = false
output.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 20, right: 0)
output.autoresizingMask = [.FlexibleHeight, .FlexibleWidth]
view.addSubview(output);
if let auth = GTMOAuth2ViewControllerTouch.authForGoogleFromKeychainForName(
kKeychainItemName,
clientID: kClientID,
clientSecret: nil) {
service.authorizer = auth
}
}
// When the view appears, ensure that the Drive API service is authorized
// and perform API calls
override func viewDidAppear(animated: Bool) {
if let authorizer = service.authorizer,
let canAuth = authorizer.canAuthorize, canAuth {
fetchFiles()
} else {
presentViewController(
createAuthController(),
animated: true,
completion: nil
)
}
}
// Construct a query to get names and IDs of 10 files using the Google Drive API
func fetchFiles() {
output.text = "Getting files..."
let query = GTLQueryDrive.queryForFilesList()
query.pageSize = 10
query.fields = "nextPageToken, files(id, name)"
service.executeQuery(
query,
delegate: self,
didFinishSelector: "displayResultWithTicket:finishedWithObject:error:"
)
}
// Parse results and display
func displayResultWithTicket(ticket : GTLServiceTicket,
finishedWithObject response : GTLDriveFileList,
error : NSError?) {
if let error = error {
showAlert("Error", message: error.localizedDescription)
return
}
var filesString = ""
if let files = response.files(), !files.isEmpty {
filesString += "Files:\n"
for file in files as! [GTLDriveFile] {
filesString += "\(file.name) (\(file.identifier))\n"
}
} else {
filesString = "No files found."
}
output.text = filesString
}
// Creates the auth controller for authorizing access to Drive API
private func createAuthController() -> GTMOAuth2ViewControllerTouch {
let scopeString = scopes.joinWithSeparator(" ")
return GTMOAuth2ViewControllerTouch(
scope: scopeString,
clientID: kClientID,
clientSecret: nil,
keychainItemName: kKeychainItemName,
delegate: self,
finishedSelector: "viewController:finishedWithAuth:error:"
)
}
// Handle completion of the authorization process, and update the Drive API
// with the new credentials.
func viewController(vc : UIViewController,
finishedWithAuth authResult : GTMOAuth2Authentication, error : NSError?) {
if let error = error {
service.authorizer = nil
showAlert("Authentication Error", message: error.localizedDescription)
return
}
service.authorizer = authResult
dismissViewControllerAnimated(true, completion: nil)
}
// Helper for showing an alert
func showAlert(title : String, message: String) {
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: UIAlertControllerStyle.Alert
)
let ok = UIAlertAction(
title: "OK",
style: UIAlertActionStyle.Default,
handler: nil
)
alert.addAction(ok)
presentViewController(alert, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
My problem is that for
import GoogleAPIClient
I get the error "No such module GoogleAPIClient", which seems weird to me since GTMOAuth2 doesn't get an error, even though it's part of the same Pod I think (I'm new to this, so I'm probably butchering the terminology).
From researching the problem, I found that GoogleAPIClientForREST should be substituted for GoogleAPIClient. This document on GitHub says to just use GoogleAPIClientForREST in the code instead of GoogleAPIClient, but I get the same error with that as well.
Then I thought maybe I could re-install the pods with some changes to Google's tutorial. In the tutorial, it says to execute this code in Terminal
$ cat << EOF > Podfile &&
> platform :ios, '7.0'
> use_frameworks!
> target 'QuickstartApp' do
> pod 'GoogleAPIClient/Drive', '~> 1.0.2'
> pod 'GTMOAuth2', '~> 1.1.0'
> end
> EOF
> pod install &&
> open QuickstartApp.xcworkspace
So I thought maybe I could replace GoogleAPIClient for GoogleAPIClientForREST in the terminal code, but that landed me with the same error
As you can see in the screenshot, the framework is there on the left-hand side, but I'm still getting the "No such module" error.
Embedded Binaries and Linked Frameworks
Search Paths
I also found some suggestions here that I tried to follow, but I didn't completely understand the explanation. Nevertheless, I tried, and did this (if I did it wrong please tell me):
So I'm trying to get either GoogleAPIClient or GoogleAPIClientForREST to work. Thank you for your help
Use this for your Podfile:
platform :ios, '7.0'
use_frameworks!
target 'QuickstartApp' do
pod 'GoogleAPIClientForREST/Drive', '~> 1.1.1'
pod 'GTMOAuth2', '~> 1.1.0'
end
Change your import to
import GoogleAPIClientForREST
Then follow the instructions here to migrate the project:
Migrating from GoogleAPIClient to GoogleAPIClientForREST
This mostly involves changing GTL calls to GTLR calls with some word swapping. For example, GTLServiceDrive becomes GTLRDriveService.
Regarding framework search paths, this image shows the section you might need to change (note it works for me using the default):
Search paths can be per target, too. Here's an image showing the application target and the framework search paths:
So I followed the Quickstart tutorial exactly as well and was able to get it working. I moved the GoogleAPIClientForRest in Framework Search Paths above GTMOAuth2:
Screenshot
I ran into an error in the code after successfully including the module and had to change this line to get it to build and run:
if (result.files!.count to if (result.files!.count > 0).
Of course now, Google has deprecated GTMOAuth2 and replaced it with GTMAppAuth, which renders this app useless.
Although the solution towards which I am pointing you might be for other library, but it will help you for sure. https://stackoverflow.com/a/25874524/5032645 . Please try and let me know, if I should simplify it more for you.
First, look at the Pods_QuickstartApp.framework in the Frameworks group of your Quickstart project. If it is still red, as it is on your screenshot, then Xcode didn't build it. If Xcode didn't build the framework, Xcode can't import it for you.
Cocoapods builds a workspace including your app project, plus another project that assembles your individual pod frameworks into a larger framework.
It seems cocoapods built your workspace, and you did open the workspace instead of the project. That's good.
Check the contents of the file named "Podfile". It should match:
platform :ios, '7.0'
use_frameworks!
target 'QuickstartApp' do
pod 'GoogleAPIClient/Drive', '~> 1.0.2'
pod 'GTMOAuth2', '~> 1.1.0'
end
If it doesn't, fix it, exit Xcode, delete the .xcodeworkspace file, and then run
pod install
from the console. That may fix your dependencies so that Xcode builds the frameworks.
If you do get it to compile, your problems have just begun. Google has deprecated the OAAuth authorization from an embedded user-agent.
I have a simple Gulp build process setup for testing. I've read the documentation many times but I can't seem to get Gulp-inject to inject the scripts I want into an index.html file.
My Gulp file looks like this:
gulp.task('inject1', function() {
return gulp.src('app/index.html')
.pipe(inject(gulp.src('./app/scripts/app.js', {read : false}))) // Not necessary to read the files (will speed up things), we're only after their paths
.pipe(gulp.dest("dist"));
});
gulp.task('inject2', function() {
return gulp.src('app/scripts/**/*.js', {read : false}) // Not necessary to read the files (will speed up things), we're only after their paths
.pipe(inject("./app/index.html"))
.pipe(gulp.dest("./dist"));
});
This is part of my Index.html:
<!-- inject:js -->
<!-- endinject-->
Both of these are copied from the documentation on github.
When I run either of these tasks the console just says "Started 'inject' Finished 'Inject' '
In my ./dist folder it creates an Index.html file but no js files are injected.
I've tried typing in the src and inject properties many different way but no luck. Any idea what I'm doing wrong?
First of all you have a mistake in your endinject tag:
<!-- endinject-->
should be
<!-- endinject -->
This plugin has worked great for me and others in various settings, so the problem is probably in your configuration.
Because when you are using streaming, you cannot be sure which files you pipe along, always try to use a plugin to see exactly what files you are piping. I recommend using gulp-using. Try this to debug your setup:
var debug = require('gulp-debug');
gulp.task('inject2', function() {
return gulp.src('app/scripts/**/*.js', {read : false})
.pipe(debug())
.pipe(inject("./app/index.html"))
.pipe(gulp.dest("./dist"));
});
Also make sure you use the same method to verify that you match your html file as well.
Other than that - it's just trial and error until you understand piping to get just the right files with the correct path.
If gulp-inject isn't injecting any files, that means you didn't pipe them correctly, or your target inject was not correct. The plugin works, and works great for me.
If you need to see an example working gulp file, check out this this gulpfile.js gist
I had the same problem with the following code:
var injectSrc = gulp.src(['./public/css/*.css', '.public/js/*.js'], {read: false});
var injectOptions = {
ignorePath: '/public'
};
var options = {
bowerJson: require('./bower.json'),
directory: './public/lib',
ignorePath: '../../public'
}
gulp.task('inject', function() {
return gulp.src('./src/views/*.html')
.pipe(debug())
.pipe(wiredep(options))
.pipe(debug())
.pipe(inject(injectSrc, injectOptions))
.pipe(debug())
.pipe(gulp.dest('./src/views'));
});
My index.html had the following:
<!--bower:css-->
<!--endbower-->
<!--bower:js-->
<!--endbower-->
<!--inject:css-->
<!--endinject-->
<!--inject:js-->
<!--endinject-->
Click on this link to see what my file structure was.
The inject was creating the css files correctly but not the js files. Also the bower dependencies were working just fine.
Finally I caught the missing '/' in the array passed to gulp.src(). After fixing that to:
var injectSrc = gulp.src(['./public/css/*.css', './public/js/*.js'], {read: false});
it works correctly.