I converted a csv file to a realm file and I want to use it in my app.
This is my code atm:
func inLibrarayFolder(fileName: String) -> URL {
return URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0], isDirectory: true)
.appendingPathComponent(fileName)
}
func copyPreBundleDataCompeletely() {
let mainRealmUrl = inLibrarayFolder(fileName: "main.realm")
let bundleUrl = Bundle.main.url(forResource: "treesFull", withExtension: "realm")!
//After launch after fresh install (if main.realm never created)
if (!FileManager.default.fileExists(atPath: mainRealmUrl.path)){
//copy bundled data into writable location compeletely
try! FileManager.default.copyItem(
at: bundleUrl, to: mainRealmUrl)
print(mainRealmUrl)
}
}
During the first launch, it creates the new file, but the file is a bit different from the original:
original db
copied db
the Tree object:
class Tree: Object {
#objc dynamic var id: Int32 = 0
#objc dynamic var br = ""
#objc dynamic var nm1 = ""
#objc dynamic var nm2 = ""
#objc dynamic var nm3 = ""
#objc dynamic var longitude = 0.0
#objc dynamic var latitude = 0.0
// override static func primaryKey() -> String? {
// return "id"
// }
}
It looks like I have 2 databeses in the new file, how can I access the second one with the data or how can I copy the file properly?
Also, whats gonna happen when I make the id to a primary key? Obviously I dont have a parameter like that in the original downloaded file, so I guess I will need to migrate the data somehow...
When it comes to importing, the file being imported has to be in a very specific format along with a specific file name
Your Realm object name is Tree, so the imported file name needs to match
Tree.csv
along with that the first line of the file needs to match the classes property names, comma separated
id,br,nm1...
I would suggest creating a very small test file to import with 3-4 lines to get it working. Then, once you mastered that then import the big file.
Related
I am trying to write a piece of code that instead of checking file extensions for the many different types of image files, but instead looking at the file attributes. What I can’t figure out from searching the docs is if KIND:Image is really a file attribute or simply a construct Apple created in the FinderApp to make things easier for the user.
I wrote a snippet that pulls the attributes for files with an extension of jpeg and for each file the fileType is returned as NSFileTypeRegular.
let attr = try filemanager.attributesOfItem(atPath: pathConfig+"/"+file) as NSDictionary
if file.hasSuffix(ext) {
print ("Adding \(file) [ \(attr.fileSize()) \(attr.fileType())]")
print ( attr.fileModificationDate() )
}
Does anybody know if MacOS retains an attribute for the category a file falls in to. e.g. IMAGE, DOCUMENT etc.
To achieve a functionality similar to the Kind search tag in Finder you can use UTType (Link to reference).
You can get the UTType of a file by initialising it with the file extension:
let fileType = UTType(filenameExtension: fileURL.pathExtension)
The cool thing about UTTypes is that they have a hierarchy, for example, UTType.jpeg is a subtype of UTType.image, along with others like .png.
If you want to check if a file is any kind of image, you can do it like this
let isImage = fileType.conforms(to: .image)
You can check the list for the kind of types you want to support as "Kinds", and filter using those UTTypes
This was my final solution based on the information provided by #EmilioPelaez I am not completely comfortable with Swift especially the unwrapping operations so if the code looks weird that might be why.
func imagesInDir(path: String?) -> [String] {
if let path {
let filemanager: FileManager = FileManager()
let files = filemanager.enumerator(atPath: path)
var array = [String]()
var urlFile: NSURL
while let file = files?.nextObject() as? String {
urlFile = NSURL(fileURLWithPath: file, isDirectory: false)
if (urlFile.isFileURL) {
if let pathExt = urlFile.pathExtension {
if let fileType = UTType(filenameExtension: pathExt) {
let isImage = fileType.conforms(to: .image)
if (isImage){
array.append(file)
print ("\(array.count) \(fileType)")
}
}
}
}
}
}
return array
}
Thanks in advance for your help.
I want to persist data such as a user's stats. Let's say I have a data model, a class 'Stats' with a few properties, and it gets saved to the user's device. Supposing that I've released the app, users are recording their stats but then later on I want to make changes to the class - more or fewer properties, maybe even renaming them (etc.), ahead of a new build release. But after these changes have been made, the type 'Stats' is now different to the one the users have saved on their device, so it won't be able to decode and it seems like all the user's previous data up until that point would be lost/unattainable.
How can I add make these kinds of changes to the class in a way in which the PropertyListDecoder will still be able to decode the stats that are still on the user's device?
This is basically what I have:
class Stat: Codable {
let questionCategory = questionCategory()
var timesAnsweredCorrectly: Int = 0
var timesAnsweredFirstTime: Int = 0
var timesFailed: Int = 0
static func saveToFile(stats: [Stat]) {
let propertyListEncoder = PropertyListEncoder()
let encodedSettings = try? propertyListEncoder.encode(stats)
try? encodedSettings?.write(to: archiveURL, options: .noFileProtection)
}
static func loadFromFile() -> [Stat]? {
let propertyListDecoder = PropertyListDecoder()
if let retrievedSettingsData = try? Data(contentsOf: archiveURL), let decodedSettings = try? propertyListDecoder.decode([Stat].self, from: retrievedSettingsData) {
return decodedSettings
} else {
return nil
}
}
}
static let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
static let archiveURL = documentsDirectory.appendingPathComponent("savedVerbStats").appendingPathExtension("plist")
It seems that even just adding a new property to 'Stat' will cause the user's previous persisted data to become un-decodable as type 'Stat', and loadFromFile() will return nil.
Any advice would be great! I'm sure I'm going about this the wrong way. I figured that the array [Stat] would be too big to persist in UserDefaults but even then I think this problem would still exist... Can't find anything about it online; it seems that once you've got your users using a persisted class you can't then alter it. I tried using default values for the new properties but the result is the same.
The only solution I can think of is breaking down the class into literals and saving all these in some kind of a tuple/dictionary form instead. Then I would decode that raw data, and have a function to assemble and create the class out of whatever relevant data can still be taken from the old version of the 'Stat' type. Seems like a big workaround and I'm sure you guys know a much a better way.
Thanks!!
Removing a property is easy enough. Just delete its definition from the Stat class and existing data for that property will be deleted when you read and save stats again.
The key to adding new properties is to make them optional. For example:
var newProperty: Int?
When a previously existing stat is decoded the first time, this property will be nil, but all the other properties will be set correctly. You can set and save the new property as needed.
It may be a minor inconvenience to have all new properties as optional, but it opens the door to other possible migration schemes without losing data.
EDIT: Here is a more complicated migration scheme that avoids optionals for new properties.
class Stat: Codable {
var timesAnsweredCorrectly: Int = 0
var timesAnsweredFirstTime: Int = 0
var timesFailed: Int = 0
//save all stats in the new Stat2 format
static func saveToFile(stats: [Stat2]) {
let propertyListEncoder = PropertyListEncoder()
let encodedSettings = try? propertyListEncoder.encode(stats)
try? encodedSettings?.write(to: archiveURL, options: .noFileProtection)
}
//return all stats in the new Stat2 format
static func loadFromFile() -> [Stat2]? {
let propertyListDecoder = PropertyListDecoder()
//first, try to decode existing stats as Stat2
if let retrievedSettingsData = try? Data(contentsOf: archiveURL), let decodedSettings = try? propertyListDecoder.decode([Stat2].self, from: retrievedSettingsData) {
return decodedSettings
} else if let retrievedSettingsData = try? Data(contentsOf: archiveURL), let decodedSettings = try? propertyListDecoder.decode([Stat].self, from: retrievedSettingsData) {
//since we couldn't decode as Stat2, we decoded as Stat
//convert existing Stat instances to Stat2, giving the newProperty an initial value
var newStats = [Stat2]()
for stat in decodedSettings {
let newStat = Stat2()
newStat.timesAnsweredCorrectly = stat.timesAnsweredCorrectly
newStat.timesAnsweredFirstTime = stat.timesAnsweredFirstTime
newStat.timesFailed = stat.timesFailed
newStat.newProperty = 0
newStats.append(newStat)
}
return newStats
} else {
return nil
}
}
static let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
static let archiveURL = documentsDirectory.appendingPathComponent("savedVerbStats").appendingPathExtension("plist")
}
class Stat2: Stat {
var newProperty: Int = 0
}
Following on from Mike's response, I came up with a migration scheme that seems to solve the issue of the optionals and doesn't require any new classes every time the data model is altered. Part of the issue is that a developer may change or add properties to a class that gets persisted and Xcode would never flag this as an issue, which may result in your user's app trying to read the previous data class saved to the device, returning nil and in all likelihood overwriting all the data in question with a reformatted model.
Instead of writing the class (e.g. Stat) to the disk (which is what Apple suggests in its teaching resources), I save a new struct "StatData" which comprises only optional properties of the data that I want to write to the file:
struct StatData: Codable {
let key: String
let timesAnsweredCorrectly: Int?
let timesAnsweredFirstTime: Int?
let timesFailed: Int?
}
That way I can read the properties from the file and any added or deleted properties from the struct would just return nil instead of making the entire struct unreadable. Then I have two functions to convert 'StatData' into 'Stat' (and back), providing default values in case any have been returned nil.
static func convertToData(_ stats: [Stat]) -> [StatData] {
var data = [StatData]()
for stat in stats {
let dataItem = StatData(key: stat.key, timesAnsweredCorrectly: stat.timesAnsweredCorrectly, timesAnsweredFirstTime: stat.timesAnsweredFirstTime, timesFailed: stat.timesFailed)
data.append(dataItem)
}
return data
}
static func convertFromData(_ statsData: [StatData]) -> [Stat] {
// if any of these properties weren't previously saved to the device, they will return the default values but the rest of the data will remain accessible.
var stats = [Stat]()
for item in statsData {
let stat = stat.init(key: item.key, timesAnsweredCorrectly: item.timesAnsweredCorrectly ?? 0, timesAnsweredFirstTime: item.timesAnsweredFirstTime ?? 0, timesFailed: item.timesFailed ?? 0)
stats.append(stat)
}
return stats
}
I then call these functions when reading or saving the data to disk. The advantage of this is that I can choose which properties from the Stat class that I want to save, and because the StatData model is a struct, the memberwise initializer will warn any developer who changes the data model that they will also need to account for the change when reading old data from the file.
This seems to do the job. Any comments or other suggestions would be appreciated
I have written code that retrieves CSV paths in the documents directory and loads them into a tableview. I am trying to sort the files by creation date so that they list from newest to oldest in the tableview. Anyone got any advice on how to accomplish this?
I've not tried anything yet, because I am a little stuck
override func viewDidLoad() {
super.viewDidLoad()
csvFiles = listCsvs()
tblViewDataCSV.dataSource = self
tblViewDataCSV.delegate = self
}
func listCsvs() -> [URL] {
let fileManager = FileManager.default
let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let files = try? fileManager.contentsOfDirectory(
at: documentDirectory,
includingPropertiesForKeys: nil,
options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles]
).filter {
$0.lastPathComponent.hasSuffix(".csv")
}
print(files as Any)
return files ?? []
}
I need the array sorted by .creationdate and not alphanumerically. Many thanks for your help.
You need to declare a struct that has a URL (or filename String) and a Date. Populate an array of this struct from the files (and their creation dates) you query from FileManager.
Use that array of struct as your data model for the table view. You can sort the array on filename or date (or any other attributes you might add in the future).
You can get the creation date of each file by first adding [.creationDateKey] to the includingPropertiesForKeys parameter of contentsOfDirectory. Then access the creation date using resourceValues on each URL. See How can I get the file creation date using URL resourceValues method in Swift 3? for more details on getting the creation date.
It may help to use the enumerator method of FileManager instead of contentsOfDirectory. This will make it easier to get the needs URL attributes and populate the array of struct.
You can build a struct like the below:
struct yourStruct {
var path:URL
var filedate:Date
}
For each file in folder, append your struct into array.
let s = yourStruct(
path: csvUrl,
filedate: csvFileDate)
myArray.append(s)
At this point, you have an array with your files and filedates.
And finally sort the array with:
let newArr = myArray.sorted { $0.filedate < $1.filedate }
I am looking for a way to load files from disk within an XCTestCase that does not depend on Bundle.
Bundle works well when running the tests from Xcode (or with xcodebuild on the terminal), but bundles are part of the Xcode project and not available to Swift Package Manager (when running swift test), neither on the Mac nor in Linux.
Is there a way to specify the current directory where tests should be run that works in all platforms? Or maybe there is a way to determine where the tests are located that also works on all platforms?
FileManager.default.currentDirectoryPath only returns the current execution path (working directory).
This method seems to work for me, and should work with both Xcode 11 and from the command line. Copy of the answer I just wrote here.
struct Resource {
let name: String
let type: String
let url: URL
init(name: String, type: String, sourceFile: StaticString = #file) throws {
self.name = name
self.type = type
// The following assumes that your test source files are all in the same directory, and the resources are one directory down and over
// <Some folder>
// - Resources
// - <resource files>
// - <Some test source folder>
// - <test case files>
let testCaseURL = URL(fileURLWithPath: "\(sourceFile)", isDirectory: false)
let testsFolderURL = testCaseURL.deletingLastPathComponent()
let resourcesFolderURL = testsFolderURL.deletingLastPathComponent().appendingPathComponent("Resources", isDirectory: true)
self.url = resourcesFolderURL.appendingPathComponent("\(name).\(type)", isDirectory: false)
}
}
Usage:
final class SPMTestDataTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(SPMTestData().text, "Hello, World!")
let file = try Resource(name: "image", type: "png")
let image = UIImage(contentsOfFile: file.url.path)
print(image)
}
}
I found the key of using #file here
Seems like when running tests with Swift Package Manager (swift test), the working directory is the root of the project. This allows for easily loading resources from disk using a relative path (eg. ./Tests/Resources).
After evaluating different options, I wrote the following class to retrieve the path both from the test bundle, if available, or from a given path.
class Resource {
static var resourcePath = "./Tests/Resources"
let name: String
let type: String
init(name: String, type: String) {
self.name = name
self.type = type
}
var path: String {
guard let path: String = Bundle(for: Swift.type(of: self)).path(forResource: name, ofType: type) else {
let filename: String = type.isEmpty ? name : "\(name).\(type)"
return "\(Resource.resourcePath)/\(filename)"
}
return path
}
}
Resources must be added to all test targets for them to be available in the bundle. The above class could be updated to support multiple resource paths, by file extension, for instance.
Resources can be then loaded from an XCTest as needed:
let file = Resource(name: "datafile", type: "csv")
let content = try String(contentsOfFile: file)
let image = try UIImage(contentsOfFile: Resource(name: "image", type: "png"))
Extensions can be added to Resource to load the contents in different formats.
extension Resource {
var content: String? {
return try? String(contentsOfFile: path).trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
}
var base64EncodedData: Data? {
guard let string = content, let data = Data(base64Encoded: string) else {
return nil
}
return data
}
var image: Image? {
return try? UIImage(contentsOfFile: path)
}
}
And be used as:
let json = Resource(name: "datafile", type: "json").contents
let image = Resource(name: "image", type: "jpeg").image
I want to ship my app with several databases.
1) I have a random generated ".csv" file converted with Realm Browser to "default.realm".
2) I put it in /project_name/project_name/Resources and drop it inside Xcode to project files.
3) I checked "Copy bundle resources"
4)Created
import RealmSwift
class CarItem: Object {
dynamic var id = String()
dynamic var first_name = String()
}
In ViewDidLoad wanted to return results from file
override func viewDidLoad() {
super.viewDidLoad()
let conf = Realm.Configuration(
fileURL: NSBundle.mainBundle().URLForResource("default", withExtension: "realm"),
readOnly: true)
let realm = try! Realm(configuration: conf)
let results = realm.objects(Item.self)
print(results)
}
5) But results are empty (database have 1000 rows all filled with data).... what am i down wrong?
CarItem.self replace Item.self ?
Open default.realm by Realm Browser(Your can download it from App Store).
Open .realm file by Realm Browser screenshot
Check Class Name and Class Members.
Example