How would you create a database file in an iOS app? I am using FMDB in Xcode (Swift) and I have created functions to execute queries here:
func query()
{
let DB = FMDatabase(path: dbFilePath as String)
if DB.open()
{
let querySQL = "SELECT * FROM SHOPS"
let results:FMResultSet? = DB.executeQuery(querySQL, withArgumentsInArray: nil)
print(querySQL)
DB.close()
print(results)
}
else
{
print("Error: \(DB.lastErrorMessage())")
}
}
My create database function needs help as in the FMDB documentation states: An FMDatabase is created with a path to a SQLite database file. This path can be one of these three:
A file system path. The file does not have to exist on disk. If it does not exist, it is created for you. But where can I obtain the file path to the database file?
I also have a question for how to continue the create table function:
func create()
{
let createString = "CREATE TABLE Shops(id INT PRIMARY KEY NOT NULL, name char(255);)"
}
Do I use executeUpdate? Please provide concise and clear answer as I am a beginner if it is not too time consuming for you. Thanks in advance.
Xcode 8.0 Swift 3.0 version
In Your Appdelegate.swift Copy the below code
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool
{
// Override point for customization after application launch.
Util.copyFile("<yourDatabaseName>.sqlite")
getDocDir()
return true
}
func getDocDir() -> String
{
print(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
}
And then Run Xcode: Short cut key: build (Cmd + B) -> Run(Cmd + R)
Check console of Xcode get a path like below example:
Sample image: https://i.stack.imgur.com/xH0AV.png
Copy the path from:
/users//tooti/Library/Developer/CoreSimulator/Devices/..../data/Containers/Data/Application/.../Documents/
-> Open Finder-> Go -> Go to folder and paste the path copied from above
Sample image: https://i.stack.imgur.com/DmzH8.png
-> then click Enter. That's it done. You will find a .sqlite file
Also you can open your database file with SQLite manager/Firefox browser/DBManagerExtension tool.
Related
I am building a flutter app for iOS. I have created a file type that my app and another app can share back and forth. I am using the flutter receive_sharing_intent library to achieve this. To test it, I made all the necessary changes to my info.plist file to handle my custom file type, I placed an example file of that file type in the downloads folder of my device (testing on ipad), and I click on it from there to open it up in my app. The OS knows that my app can handle it. So, my app opens, it is receives the path of the shared file, but my app can't open the file. Here is the code in my main.dart that is handling reception of the file:
StreamSubscription _intentDataStreamSubscription =
ReceiveSharingIntent.getTextStream().listen((String value) {
setState(() {
try{
//This was added to test and make sure the directory actually exists.
//The value for the directory path was added after I ran it once to capture where the shared file was coming from.
Directory dir = Directory("/private/var/mobile/Containers/Shared/AppGroup/7DD4B316-3D73-4339-9B11-7516DE52F6FC/File Provider Storage/Downloads/");
bool dirExists = dir.existsSync();
var files = dir.listSync();
File drawingFile = File.fromUri(Uri.parse(value));
bool fileExists = drawingFile.existsSync();
var contents = drawingFile.readAsStringSync();
DrawingCanvas canvas = DrawingCanvas.fromFileContents(contents);
DrawingCanvasBloc canvasBloc = DrawingCanvasBloc(canvas);
Navigator.of(context).push(MaterialPageRoute(builder: (context) => CanvasScreen(canvasBloc)));
}
catch(e){
log(e.toString());
}
});
}, onError: (err) {
print("getLinkStream error: $err");
});
Scenario: I run the application. I go into my downloads folder in files on the ipad. I select my example.fbg file located there (my custom file type). My app opens up.
In the above code it blows up when I try to list the contents of the directory. I put this in here (after previously catching the directory path and hard coding it) to test to make sure I was even getting the right location. dirExists is true but I can't list the files in it. The error I get is:
FileSystemException: Directory listing failed, path = '/private/var/mobile/Containers/Shared/AppGroup/7DD4B316-3D73-4339-9B11-7516DE52F6FC/File Provider Storage/Downloads/' (OS Error: Operation not permitted, errno = 1)
If I take that line out and continue down to the opening of the file (readAsStringSync) I get:
FileSystemException: Cannot open file, path = '/private/var/mobile/Containers/Shared/AppGroup/7DD4B316-3D73-4339-9B11-7516DE52F6FC/File Provider Storage/Downloads/example.fbg' (OS Error: Operation not permitted, errno = 1)
I'm not sure why it won't let me access this file. Is there a permissions thing I'm missing? Let me know if I need to include more information in the question and I will update.
Finally found a work around and got it working using this answer. What I ended up doing was opening the file in the app delegate, saving the file to the apps documents directory, then passing that url to the Flutter application. I opened the iOS module in xcode and changed the AppDelegate.swift to the following:
import UIKit
import Flutter
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
var drawingFilename = ""
do {
let isAcccessing = url.startAccessingSecurityScopedResource()
var error: Error? = nil
let path = url.path
let string = try String(contentsOf: url)
drawingFilename = (url.path as NSString).lastPathComponent
print(drawingFilename)
let filename = getDocumentsDirectory().appendingPathComponent(drawingFilename)
do {
try string.write(to: filename, atomically: true, encoding: String.Encoding.utf8)
} catch {
// failed to write file – bad permissions, bad filename, missing permissions, or more likely it can't be converted to the encoding
}
if isAcccessing {
url.stopAccessingSecurityScopedResource()
}
if #available(iOS 9.0, *) {
return super.application(app, open: filename, options: options)
} else {
return false
}
} catch {
print("Unable to load data: \(error)")
return false
}
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
}
Hope this helps someone in the future.
I am developing an iOS app with RealmSwift by referring here. https://realm.io/docs/swift/latest/#in-memory-realms
And, what I don't understand is, how can I indicate the location(record and column) of the data in the realm file.
I've saved a realm file that named "DicData.realm" on the main folder where the same location as ViewController.swift is saved.
The data of DicData.realm is something like this:
1,face,423
2,rain,435
3,airplane,555
If I run the code below, it only printed like this: "results: Results ( )". It seems the filter method is just neglected. When I want to take out the word "airplane" and store in a variable as a string, how should I modify my code?
override func didMoveToView(view: SKView) {
func test()->Int {
let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "DicData"))
let results = realm.objects(DBData).filter("id == 3")
print("results: \(results)")
}
class DBData: Object {
dynamic var id = 0
dynamic var name = ""
dynamic var code = ""
}
You're referring here to the chapter of in-memory Realms and have setup your configuration to use those. An in-memory Realm is not a file. It lives exclusively in memory and is not actually written to disk.
If you've a prepared file, you want to bundle with your app, you need to make sure, that it is part of the Copy-Files Build Phase of the corresponding target of your Xcode project.
You can then copy the file from the app bundle initially via NSFileManager to a path, where the copy can be modified.
let defaultURL = Realm.Configuration.defaultConfiguration.fileURL!
let bundleURL = NSBundle.mainBundle().URLForResource("DicData", withExtension: "realm")!
do {
try NSFileManager.defaultManager().copyItemAtURL(bundleURL, toURL: defaultURL)
} catch {}
}
I am creating an iOS application through which users can print the files on their device. From my application, I can access the files on the device though the DocumentPicker provided by other apps such as iCloud Drive, Dropbox, etc.
Now, I want to add a functionality where user can share the file with my application through an other application. I created an Action Extension for that.
For example, if I select an Image in the Photos application and select Share I get my extension in the Share sheet and when I select it, I also get the URL of the file. Next, I am creating a zip file of this file to send it to my server. But the issue is, the zip file is always empty. The code I am using is as below:
In Action Extension's viewDidLoad()
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
itemProvider.loadItemForTypeIdentifier(kUTTypeImage as String, options: nil,
completionHandler: { (image, error) in
NSOperationQueue.mainQueue().addOperationWithBlock {
print("Image: \(image.debugDescription)")
//Image: Optional(file:///Users/guestUser/Library/Developer/CoreSimulator/Devices/00B81632-041E-47B1-BACD-2F15F114AA2D/data/Media/DCIM/100APPLE/IMG_0004.JPG)
print("Image class: \(image.dynamicType)")
//Image class: Optional<NSSecureCoding>
self.filePaths.append(image.debugDescription)
let zipPath = self.createZip(filePaths)
print("Zip: \(zipPath)")
}
})
}
And my createZip function is as follows:
func createZipWithFiles(filePaths: [AnyObject]) -> String {
let zipPath = createZipPath() //Creates an unique zip file name
let success = SSZipArchive.createZipFileAtPath(zipPath, withFilesAtPaths: filePaths)
if success {
return zipPath
}
else {
return "zip prepation failed"
}
}
Is there a way that I can create a zip of the shared files?
Your primary issue is that you are blindly adding image.debugDescription to an array that is expecting a file path. The output of image.debugDescription isn't at all a valid file path. You need to use a proper function on the image to obtain the actual file path.
But image is declared to have a type of NSSecureCoding. Based on the output of image.debugDescription, it seems that image is really of type NSURL. So you need to convert image to an NSURL using a line like:
if let photoURL = image as? NSURL {
}
Once you have the NSURL, you can use the path property to get the actual needed path.
So your code becomes:
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
itemProvider.loadItemForTypeIdentifier(kUTTypeImage as String, options: nil,
completionHandler: { (image, error) in
if let photoURL = image as? NSURL {
NSOperationQueue.mainQueue().addOperationWithBlock {
let photoPath = photoURL.path
print("photoPath: \(photoPath)")
self.filePaths.append(photoPath)
let zipPath = self.createZip(filePaths)
print("Zip: \(zipPath)")
}
}
})
}
Hint: Never use debugDescription for anything other than a print statement. Its output is just some string that could contain just about any information and that output can change from one iOS version to the next.
I know about FMdb and use it to my project, but I don't know how use existing DB file and where can I add my DB.sqlite in my project?
In your case:
drop "DB.sqlite" to your project folder (in navigation window)
add following code to your AppDelegate.swift file
func checkDataBase(){
print("check database...")//log
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("DB.sqlite")
// Load the existing database
if !NSFileManager.defaultManager().fileExistsAtPath(url.path!) {
print("Not found, copy one!!!")//log
let sourceSqliteURL = NSBundle.mainBundle().URLForResource("DB", withExtension: "sqlite")!
let destSqliteURL = self.applicationDocumentsDirectory.URLByAppendingPathComponent("DB.sqlite")
do {
try NSFileManager.defaultManager().copyItemAtURL(sourceSqliteURL, toURL: destSqliteURL)
} catch {
print(error)
}
}else{
print("DB file exist")//log
}}
3.Function is ready to use, just call "checkDataBase()" function in "func application" maybe..
I have a routine that when my dataModelObject is initiate, its to copy myDataBase.sqlite file from the App Bundle folder, to the app document directory so that the app can use it. It only copies it if its not there, meaning in live environment it will not replace users existing db. The operation executes with no problem, however the database at the destination location is completely different than the database i designed. Has anyone else run into this, and might know what is causing this?
2 tables in said database one record only in one table with a weird number and a blob.
Yes, i have confirmed that the file in the bundle points to the correct .sqlite file in the project folder.
Here is my routine that does the copy, does something stand out as bad? Should i use move instead?
class func copyFile(fileName: NSString) {
let dbPath: String = getPath(fileName)
println("copyFile fileName=\(fileName) to path=\(dbPath)")
var fileManager = NSFileManager.defaultManager()
var fromPath: String? = NSBundle.mainBundle().resourcePath?.stringByAppendingPathComponent(fileName)
if !fileManager.fileExistsAtPath(dbPath) {
println("dB not found in document directory filemanager will copy this file from this path=\(fromPath) :::TO::: path=\(dbPath)")
fileManager.copyItemAtPath(fromPath!, toPath: dbPath, error: nil)
} else {
println("DID-NOT copy dB file, file allready exists at path:\(dbPath)")
}
}
class func getPath(fileName: String) -> String {
return NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)[0].stringByAppendingPathComponent(fileName)
}