Loading a file into a C++ object from an iOS App / issues with <iostream> on iOS - c++

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.

Related

When using swiftui fileimporter, files in iCloud are grayed out

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

Play partially downloaded video in swift 3

I need to play specific part of a video (eg. form 10 sec to 20 sec).
so I use Alamofire with custom header range to download just this part of the video:
Alamofire.download(videoUrl,
method: .get,
headers: ["Range":String(format: "bytes=%d-%d", startByte, endByte)],
to: destination).response { (response) in
completionHandler(response.destinationURL)
}
so far so good, I can see the downloaded file.
But when I'm trying to play it, avplayer will fail to do that, I'm using
videoAsset.loadValuesAsynchronously(forKeys: [ObserverContexts.urlAssetDurationKey, ObserverContexts.urlAssetPlayableKey])
to load that specific keys async but the duration state can not be loaded:
let durationStatus = self.videoAsset.statusOfValue(forKey: ObserverContexts.urlAssetDurationKey, error: &durationError)
guard durationStatus == .loaded else {
Logger.log.error("durationStatus not loaded: \(self.videoId)")
self.delegate?.onLoadError(error: . durationStatusNotLoaded)
return
}
so this error gets triggered and I can't play the video.
I'm not master in video files, but I think the video file has an header and partially downloading will ruin this header and player can not play it.
so any suggestion or idea (even the simplest) would be appreciated.
tnx in advance.
EDIT: There is definitely something wrong with video header, if I start download 10 second from start of the video, the video get played for 10 sec, BUT the end video time or duration is wrong, how can I fix this?
I don't think IOS or Swift provides this feature of downloading an image from the mid of it and then play it. You can download the video file contents as they are in the form of bits and then can save them with any extension to any file name.
But a video file contains a lots of data in addition to just these bytes that you have downloaded such as header, type, duration, etc. If your downloaded file doesn't contain that data your video will be unplayable.
In this case I think(99% sure) you will have to use native code not just Swift to decode the video from a specific time to your desired time.
A lots of free libraries are available, give them a try. One such kind of library is ffmpeg. Check this as well.
All the best.
This is not the answer to original question, But I managed to achieve my goal by using AVFoundation, now I can download the exact part I want and it is playable :
let downloadUrl = URL(string: videoUrl)!
let asset = AVURLAsset(url: downloadUrl)
let composition = AVMutableComposition()
let videoTrack = composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID:Int32(kCMPersistentTrackID_Invalid))
let audioTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
let tracks = asset.tracks(withMediaType: AVMediaTypeVideo)
guard let assetVideoTrack = tracks.first else {
Logger.log.debug("video track is not available: \(videoId)")
completionHandler(nil)
return
}
let startCM = CMTime(seconds: startSecond, preferredTimescale: 1)
//let endCM = CMTime(seconds: startSecond + duration, preferredTimescale: 1)
//EDITED
let endCM = CMTime(seconds: duration, preferredTimescale: 1)
let timeRange = CMTimeRangeMake(startCM, endCM)
do {
try videoTrack.insertTimeRange(timeRange, of: assetVideoTrack, at: CMTimeMake(0, 1))
} catch let errorInsertingVideo {
Logger.log.debug("can not get video time range: \(videoId) -- \(errorInsertingVideo.localizedDescription)")
completionHandler(nil)
return
}
videoTrack.preferredTransform = assetVideoTrack.preferredTransform
guard let assetAudioTrack : AVAssetTrack = asset.tracks(withMediaType: AVMediaTypeAudio).first else {
Logger.log.debug("audio track is not available: \(videoId)")
completionHandler(nil)
return
}
do {
try audioTrack.insertTimeRange(timeRange, of: assetAudioTrack, at: CMTimeMake(0, 1))
}catch let errorInsertingAudio {
Logger.log.debug("can not get audio time range: \(videoId) -- \(errorInsertingAudio.localizedDescription)")
completionHandler(nil)
return
}
guard let destinationUrl = self.getDestinationUrl(videoId) else {
// can not access video cache dir
Logger.log.error("can not access video cache dir")
completionHandler(nil)
return
}
let exportSession : AVAssetExportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetPassthrough)!
exportSession.outputURL = destinationUrl
exportSession.timeRange = timeRange
exportSession.outputFileType = AVFileTypeQuickTimeMovie
exportSession.exportAsynchronously(completionHandler: {
switch exportSession.status {
case .completed:
let output = exportSession.outputURL!
completionHandler(output)
case .failed:
completionHandler(nil)
break
default :
completionHandler(nil)
break
}
})
With this trick I can download exactly 10 second (optional) of a video starting from any second at the middle (or start) of the video.
EDIT: ok this code can NOT download middle part, it only works it you start from begining of the Video.
EDIT 2: Fix added.

FMDB & Swift, " Optional("no such table: Student info") " in real device but it can be done in simulator

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 import either GoogleAPIClient or GoogleAPIClientForREST

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.

Server-side Swift: Testing Code That Uses Bundle

I'm using server-side Swift, and doing my development in Xcode after doing:
swift package generate-xcodeproj
I have a class that uses Bundle (formerly NSBundle) to load in a .plist file for some settings in the server. It works fine when running in the server itself, but when I create some unit tests for this class, I cannot get access to the directory where the .plist file is located. The relevant snippet of code is:
let bundlePath = Bundle.main.bundlePath as NSString
let plistPath = bundlePath.appendingPathComponent("Test.plist")
plistDict = NSDictionary(contentsOfFile: plistPath)
When I run this in unit XCTests, plistPath is:
/Applications/Xcode-8.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Agents/Test.plist
which is not very useful.
One thing that I've noticed is that there are no options for "Host Application:" under the General tab.
Thoughts?
I've not been able to fully answer this, but came up with a work-around for my situation. I'm using the Perfect File class (see https://github.com/PerfectlySoft/Perfect.git), and just dynamically creating the file I need for my XCTest cases in the setUp() method. Fortunately, I have fairly simple needs for file contents. Here's the initial part of my XCTest file:
import XCTest
import SMServerLib
import PerfectLib
class TestPlistDictLoader: XCTestCase {
var plistFileName:String! = "TestPlistDictLoader.plist"
var pathName:String! = "/tmp"
override func setUp() {
super.setUp()
// A bit of a hack, but I can't figure out a way otherwise to access the install directory where the code is running.
// See also http://stackoverflow.com/questions/41340114/server-side-swift-testing-code-that-uses-bundle
// The only downside is that these tests don't test the `init(plistFileNameInBundle filename:String)` constructor that uses the Bundle.
// Write the .plist file to a known location. Use only pure Swift methods.
let plistPath = (pathName as NSString).appendingPathComponent(plistFileName)
let plist = File(plistPath)
try! plist.open(.write)
try! plist.write(string: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" +
"<plist version=\"1.0\">\n" +
"<dict>\n" +
"<key>MyString</key>\n" +
"<string>Hello World!</string>\n" +
"<key>MyInteger</key>\n" +
"<integer>100</integer>\n" +
"</dict>\n" +
"</plist>\n"
)
plist.close()
}
See https://github.com/crspybits/SMServerLib for the full context.