Alternative to loading large text file - Swift - ios

I am attempting to make a word game for iOS where I need to check words against a dictionary to determine if the word is valid.
Currently I am loading the dictionary from a text file and storing it in a set. However it takes a few seconds to load every time I start the app. The text file has approximately 250K lines.
Can I have the data available in an array or set without having to read a text file each time the app starts?

You should consider persisting your word file using, for example, Core Data.
On the first load of your app, you can populate your Core Data store (maybe in the background while the rules are being explained to the user).
Once this has been done you can query your words store using an NSFetchRequest
To use a pre-populated Core Data database you could start by populating in development…
let count = try! moc.count(for: request)
if count = 0 {
loadWords()
}
func loadWords() {
// run through your words.txt file and create object(s)
// for each word
}
Once you're ready to release, add your database files into the app's resources, then add amend your loadWords() func to copy these files from the app bundle to the Core Data store's url

Related

How do I use Realm swift to store assets on device locally?

I am trying to make a simple iOS app that has loads of text, video links, etc. I want to store this data using realm .I'm not trying to store user data but assets like text, thumbnails and even some 3D files. How can I do this locally ?
Realm is an offline first database so by it's nature, all data is stored locally. See the Quick Start to get started with local storage
You would need to add additional code to sync and store in the cloud - see Sync Quick Start
Here's a simple example for storing data locally
class TaskClass: Object {
#Persisted var task: String = ""
}
let task = TaskClass()
task.task = "Go Shopping"
let localRealm = try! Realm()
try! localRealm.write {
localRealm.add(task)
}
The above code will store a Task object locally only.
That being said, Realm object properties are limited to 16Mb - which is great for textual data.
However, it's not ideal for image storage as images can easily surpass that. If you are storing images, MongoDB Realm offers other solutions for the image data and there are other solutions as well like Firebase Storage.
Note that Realm can handle small thumbnails or graphics as they are usually a couple hundred K, and 3D files, which are generally Vector based could probably be stored in realm as well as it's textual data.
For more reading see my answer to this question

Swift - Variable scope with loading external data

I am using the Mapbox framework. I have used their Studio (web app) to create some map shapes (polygons and coordinates) that I have downloaded as a GeoJSON file. This file is bundled with my iOS app. All provided examples are very small and have a simple structure, like within viewDidLoad will all the remaining code.
I am trying to design the app so that the data is loaded once, and then this variable will be accessible to be able to add and / or remove some of these mapping items as needed.
Should I use a global variable? If so, where should I declare the data variable, so it can be accessed anywhere? Or is this bad practice, and I should load the data variable once somewhere with less scope access and pass the object itself around within appropriate function calls? Does this not also get confusing? Where would the best location be for the initial data load, viewDidLoad()?
do {
let url = URL(fileURLWithPath: Bundle.main.path(forResource: "features", ofType: "geojson")!)
let data = try Data(contentsOf: url)
} catch {
print(error)
}
Then I will have other functions that can then filter this data set, and only add or remove specific map objects.
func loadSomeData(forGroup name: String, withData data: Data) {
let shapeCollection = try MGLShape(data: data, encoding: String.Encoding.utf8.rawValue) as! MGLShapeCollectionFeature
for shape in shapeCollection.shapes {
if shape.attribute(forKey: "group") as! String == name {
if let point = shape as? MGLPointAnnotation {
// ADD ITEM TO MAP
} else if let polygon = shape as? MGLPolygon {
// ADD ITEM TO MAP
}
}
}
}
var group = "group1"
loadSomeData(forGroup: group)
So this would filter the same original data source for all objects with a property of "group1" and only load those (it will be preceeded by removing existing objects, and may do many other things - I just need to grasp the basics...)
Ultimately, I would prefer to parse the GeoJSON data file, and create custom objects for grouped items. But the file's structure is totally random and means the object class' properties would need to be entirely optionals, but some are lazy loaded / computed properties which don't work well with optionals, from my early testing...
Sounds like you have a couple of issues: Loading data from your bundle, and making it globally accessible.
If the data will never change then simply reading it from your bundle should be fine. If it might change during the life of your app, you might want to write code that's run at launch that will check for the file in documents at launch, and copy it from the bundle to the documents directory if it's not found, then open it from documents.
Then you'd need to query your server to see if your data is current (using a version number or update timestamp.) If it's changed, you'd download the new data, save the changes to documents, and also update the version/timestamp.
As to making your data available app-wide, this might be a good use case for a data container singleton. The singleton would have methods to query your map data, and it would be responsible for the loading/updating logic I described above.
Alternately you might decide that the data would map well to Core Data.
As to how to design your data model, that's a separate conversation and you'd need to provide more information.

Swift, Xcode 6 Beta 4: How do I retrieve the file path of an image file that is currently being displayed using the Photos framework's assets?

I am currently writing an iOS app in which my intent is to save data objects associated with the app in a DB created in Core Data. I have successfully created the DB, and plan to synchronize the data objects among different devices logged in to the same iCloud-account through iCloud. In the middle of all this, I would also like media files to be associated with the distinct data objects. However, I do not wish to save the binary data constituting a media file directly to the DB. Thus I need some kind of way to preserve the reference to the correct media file in the DB. My immediate thought was to place every media file in a specific folder, turn on iCloud sync for that folder, and save the media files' filenames to the DB. However, I am not able to retrieve the file path of any media files.
Below is a code snippet from the app. When that code is run, an assetCollection as well as a photosAsset with an index exists, and is displaying an image in the view controller. Despite this, none of the if-sentences prints any output (all of the references are nil).
if let a: String = self.photosAsset[self.index].bundleIdentifier {
println(a)
}
if let a = self.photosAsset[self.index].bundleURL {
println(a)
}
if let a: String = self.photosAsset[self.index].resourcePath {
println(a)
}
if let a = self.photosAsset[self.index].resourceURL {
println(a)
}
Any thoughts on how to retrieve the file path of an image file that is being displayed in this way? Any solutions on how to retrieve file paths in general that could be applicable to this problem?
Any thoughts on my approach in general would also be greatly appreciated.
I think that if you are storing the photos as xcassets, then you may not be able to access a path for them, as this type can sometimes be compressed (so it seems Apple don't allow it) e.g. see the answer posted for Access Asset Catalog pathForResource
I would create a custom directory structure to store the photos, rather than store them in the bundle, e.g. in /Documents/
This would allow you to use classes such as NSFileManager to find all photos of certain types etc and load them when need be.
For information about where Apple recommends you store things see https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html

Working with static text data on iPhone

So,
I have a large set of static text that I will be using over and over again in the app such as postcodes, suburb names and etc which will get occasionally downloaded. To ease the need to download data whenever I need it, I'm downloading all the data and saving it locally in a plist format which is around 3MB size. I'm now thinking of a better way to handle that. So, I'm just wondering what would be the best way to handle large data in my case.
For plist, I have a static class which loads all the data into array for example,
+(instanceof) sharedInstance {
.....
self.myarray = [NSKeyedUnarchiver unarchiveObjectWithFile:PLISTPATH(#"suburbs.plist")]
.....
}
Thanks
I would suggest as 3 MB file when load with tons of objects every time it make your app very poor performing and might get killed by iOS, you should create a sqlite database for all this info and load the specific data based on ids.

Best practice to process big plists?

I'm using a plist file which contains all app my data. The file is quite big and currently I'm loading all the stuff into Arrays and Dictionaries at first launch and save them into UserDefaults so that I don't have to touch the plist again. As this takes about 10 secs (iP4) I wonder if there is an even faster (better) way to process the plist. I checked the whole startup with Instruments and going through the hundreds of entries is actually the fastest part. It takes very long to save these processed stuff into NSUserDefaults.
You might benefit from saving the plist to your own file. That way you control the reading/writing, don't have any overhead associated with NSUserDefaults, and, most importantly, can ensure the format. That is, if reading/writing is producing the slow down, then you'll have to minimize the plist file size. Likely using a plist format of NSPropertyListBinaryFormat_v1_0 will do that:
See:
+ (NSInteger) writePropertyList: (id) plist
toStream: (NSOutputStream *) stream
format: (NSPropertyListFormat)format
options: (NSPropertyListWriteOptions) opt
error: (NSError **) error
From Apple's Property List Programming Guide:
The first approach [using NSDictionary or NSArray writeToFile] is
simpler—it requires only one method invocation instead of two—but the
second approach [as above] has its advantages. It allows you to convert the
runtime property list to binary format as well as an XML property
list. When you convert a static representation of a property list
back into a graph of objects, it also lets you specify with more
flexibility whether those objects are mutable or immutable.
Several points.
NSUserDefaults is probably just a big plist, so why use it? Stick your entries into a singleton that holds the in-memory structure.
If you're doing this on first load because you want it to be mutable, put the defaults into your resource folder. When you want to load it, check if you have it in the documents folder, and if you don't ( first load), copy it from the resource bundle to the documents.
If you're using NSUserDefaults for persistence, just write out your data to your plist in applicationShouldResignActive, and at any other times where you make important changes.
Write it in a background thread, but you probably need to do some locking here.
Best practise when load and save times become to big is probably move to core data, but 1-4 should give you some more mileage before you need to do that.

Resources