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.
Related
I'm writing an app using Realm data persistence of certain objects.
In an attempt to clean up/remodel my code (getting realm.writes out of the Views and Controllers), I tried to put them directly in the persisted object class.
The logic is basically this:
class PersistedObject: Object {
public var data: String {
get { _saved_data }
set {
do { try realm?.write { _saved_data = newValue }
} catch { print(error) }
}
}
#objc dynamic private var _saved_data = "hello there"
}
This way, I'd be able to access and rewrite realm object properties from view controllers, without needing realm.writes directly in there. That's the idea, anyway.
This works sometimes. Other times, the app crashes with the error...
"Realm accessed from incorrect thread"
...which is what I'm currently trying to solve.
This is my first iOS app and my first time using Realm.
Does it make sense to organize the code like this (I've found little in terms of support in this approach, but also generally little at all, in terms of MVC best-practices when working with Realm)
If it does make sense, how can I solve the problem with accessing Realm from the incorrect thread, while still doing the realm.writes directly in the object class?
Thanks in advance! :)
Simon
There is no sense to organize code like this. You will be able to write only from same thread it was created
to modify objects from different thread you can use ThreadSafeReference for example
You're not going to want to do that.
There's no reason not to realm.write whenever you want to write to realm - that's what it's there for. This pattern works:
// Use them like regular Swift objects
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
// Get the default Realm
let realm = try! Realm()
// Persist your data easily
try! realm.write {
realm.add(myDog)
}
Obviously there should be better error catching in the above code.
Another downside is if you want to write 10 objects, they are written as soon as the data property is set - what if there are three vars you want to set and heep in memory before writing it? e.g. your user is creating a list of items in your app - if the user decides not to do that and hit's Cancel, you would then have to hit the database again to delete the object(s).
Consider a case where you want to write 10 objects 'at the same time'?
realm.add([obj0, obj1, obj2...])
is a lot cleaner.
Another issue comes up if you want to guarantee objects are written within a transaction - either it all succeeds or all fails. That can't be done with your current object.
The last issue is that often you'll want to instantiate an object and add some data to it, populating the object before writing to realm. With the code in the question, you're writing it as soon as data is populated. You would have to add that same code to every property.
TL;DR: Is there a way to programmatically read/recall (NOT write!) an instance of a Core Data entity using the p-numbered "serial number" that's tacked on to the instance's x-coredata:// identifier? Is this a good/bad idea?
I'm using a method similar to the following to retrieve the instances of an Entity called from a Core Data data store:
var managedContext: NSManagedObjectContext!
let fetchRequest : NSFetchRequest<TrackInfo> = TrackInfo.fetchRequest()
fetchResults = try! managedContext.fetch(fetchRequest)
for (i, _) in Global.Vars.numberOfTrackButtons! {
let workingTrackInfo = fetchResults.randomElement()!
print("current track is: \(workingTrackInfo)")
The list of tracks comes back in fetchResults as an array, and I can select one of them at random (fetchResults.randomElement()). From there, I can examine the details of that one item by coercing it to a string and displaying it in the console (the print statement). I don't list the code below, but using workingTrackInfo I am able to see that instance, read its properties into other variables, etc.
In the console, iOS/Xcode lists the selected item as follows:
current track is: <MyProjectName.TrackInfo: 0x60000374c2d0> (entity:
TrackInfo; id: 0xa7dc809ab862d89d
<x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22>;
data: <fault>)
The line beginning with x-coredata: got my attention. It's formatted like a URL, consisting of what I assume is a UUID for the specific Core Data store associated with the current build of the app (i.e. not a stable address that you could hardcode; you'd need to programmatically look up the Core Data store, similar to the functions we use for programmatically locating the Documents Folder, App Bundle, etc.) The third item is the name of the Entity in my Core Data model -- easy enough.
But that last number is what I'm curious about. From examining the SQLite database associated with this data store, it appears to be a sort of "instance serial number" associated with the Z_PK field in the data model.
I AM NOT interested in trying to circumvent Core Data's normal mechanisms to modify the contents of a managed object. Apple is very clear about that being a bad idea.
What I AM interested in is whether it's possible to address a particular Core Data instance using this "serial number".**
In my application, where I'm randomly selecting one track out of what might be hundreds or even thousands of tracks, I'd be interested in, among other things, the ability to select a single track on the basis of that p-number serial, where I simply ask for an individual instance by generating a random p-number, tack it on to a x-coredata:// statement formatted like the one listed above, and loading the result (on a read-only basis!) into a variable for further use elsewhere in the app.
For testing purposes, I've tried simply hardcoding x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22 as a URL, but XCode doesn't seem to like it. Is there some other data Type (e.g. an NSManagedObject?) that allows you to set an x-coredata:// "URL" as its contents?
QUESTIONS: Has anyone done anything like this; are there any memory/threading considerations why grabbing instance names in this manner is a bad idea (I'm an iOS/Core Data noob, so I don't know what I don't know; please humor me!); what would the syntax/method for these types of statements be?
Thanks!
You are quite close.
x-coredata://2B5DDCDB-0F2C-4CDF-A7B9-D4C43785FDE7/TrackInfo/p22
is the uriRepresentation() of the NSManagedObjectID of the record.
You get this URL from an NSManagedObject with
let workingTrackInfo = fetchResults.randomElement()!
let objectIDURL = workingTrackInfo.objectID.uriRepresentation()
With this URL you can get the managed Object ID from the NSPersistentStoreCoordinator and the coordinator from the managed object context.
Then call object(with: on the context to get the object.
let persistentStoreCoordinator = managedContext.persistentStoreCoordinator!
if let objectID = persistentStoreCoordinator.managedObjectID(forURIRepresentation: objectIDURL) {
let object = managedContext.object(with: objectID) as! TrackInfo
print(object)
}
I am developing an iOS app using Swift (still new to swift).
I have a databank saved as a csv file and that is primarily the core of the app. The databank contains a table divided into a list of all countries (rows) each with many attributes/descriptors (the columns). Really there is no relationship between the countries.
What I would like to do is to be able to display this information for the (selected) country in my app. As well as do other things like search/filtering/sorting based on the attribute/property chosen.
The databank might be updated in the future with the release of app updates but other than that I think I want it to be read once from the csv file and then the app uses that data.
Am I right in thinking that the best way to implement this is by using Core Data with ONE entity (country) with the different attributes? So then all I have to do is read the csv file once and persistent store all the data and use it from there.
Or is Core Data not the best implementation? Shall I just create a class Country with the many attributes and use that? But then that means the app will have to read the csv every time it opens and save the data to arrays etc if I wanted to filter or sort?
So I guess in summary my questions are:
1. Use Core Data or not? If not then what do you think the best implementation is?
2. If using Core Data, am I just creating ONE entity (so no relationships etc).
What would see this as is that you would want to load this into your database of choice; Thus CoreData or SQLite or even Realm would work.
In case you don't have the data available for any of them yet and you want to load them in using swift this is a parser that I wrote for a similar situation where CSV is my raw datasource that I need to parse:
if let listFile = try? String(contentsOfFile: path) {
let lines = listFile.components(separatedBy: "\n")
// NOTE: There is also a CSV format that uses ',' as a separator
// So verify if yours uses ';' or ','
let rows: [[String: String]] = lines.flatMap() {
(item) -> [String: String]? in
let components = item.components(separatedBy: ";")
if components.count != 3 {
return nil
}
// Modify this part to parse the X amount of columns
// you have in your countries.csv
let dict = [
"identity": components[0],
"description": components[1].lowercased(),
"points": components[2]
]
return dict
}
}
}
Then after you have the data in an [[String: String]] format you could parse that row by row and insert them into the database you chose to use.
I believe in Swift4 it should be possible to use the new Codable protocols in order to achieve a result that would look a lot cleaner; but I have not written an Encoder/Decoder for CSV files ( yet ) that would allow you to do this. If there is one available I'll post it here.
There is also a CSV parser available on github here:
SwiftCSV
I have never used it, but who knows; might be helpful to you.
Assume we have simple data model with single entity User; simple tableView_friends with fetchedResultsController_friends for show users - friends.
Assume we have search bar for searching all (not only friends) users in service, and for every typed in it character we perform search request to server, which return to us somehow filtered by character User objects. Some of this objects can already be inside local database. By app logic we don't really must save all this results in local database forever (but ok, we can, we can clear local database time to time); on other hand, if we will perform any action on some searched user, we must store this user. We want to show list of searched user in other tableView_search with fetchedResultsController_search.
Question: should I use same context for fetchedResultsController_friends and fetchedResultsController_search? If no, how can I handle situation, when I wish to edit searched user, which already exists in database and probably already local edited? If yes, how can I setup predicate for fetchedResultsController_search (server perform its own logic for search by character, which can be changed) for show exactly same result as from server?
We recently implemented a search feature in our application and had a similar issue, We had local data in core data and also remote data from our API.
You have a few options that we explored:
Save your data into core data from the API as it is retreived and
then the fetched results controller will do the rest
Manage the merge of the data yourself, you can still use NSFetchedResults controller to an extent but need to do more work
We didn't want to save all of the information returned from the API unless it was needed (the user selected it), so we come up with a simple solution that worked for our app. This may not work directly for your app, you may need a completely different solution or change some of the things we done to suit.
Firstly, To explain what we are dealing with, we had a Article entity in core data which contains around 25 properties, the API returns article objects as JSON data with the same data.
What we decided to do was to create a class which represents a simple version of an article (just enough data to show in a list view and reference it later in the API or core data) which looked something like this:
class SearchResult: NSObject {
var id:String?
var title:String?
var imageUrl:String?
var url:String?
// core data entity
init(article:Article) {
self.id = content.contentId
self.title = content.title
self.featuredImageURL = content.absoluteImagePath()
self.urlAlias = content.urlAlias
self.publishedAt = content.publishedAt
}
init(articleDictionary:NSDictionary) {
self.id = articleDictionary.objectForKeyNotNull("id") as? String
self.title = articleDictionary.objectForKeyNotNull("title") as? String
self.url = articleDictionary.objectForKeyNotNull("url") as? String
if let imageUrl = articleDictionary.objectForKeyNotNull("imageUrl") as? String {
self.imageUrl = imageUrl
}
}
}
Now using this, we can create once of these from either the core data results or from the API results. Our tableview datasource is just an array
var dataSet = [SearchResult]()
We use the NSFectchResultsController delegate methods to add/remove/re-order core data elements from the dataSet after the initial load and when we get API data we'll do something like:
dataSet = Array(Set(apiResponseArray + dataSet))
This will take an array of SearchResult items from the API, merge them with the current result set and remove duplicates. casting to a set and then back to an array will give you an array of unique results as a Set is made of unique values only.
See this reference which should help with how the delegate methods would work
I'm fairly new to programming but at the moment. I can view my calculations in my stack on the second screen. Once the application project resets the stack is clear. My question is how I can keep the stack in the second view once the application has exited.
class ViewController: UIViewController {
var name = Array<String>()
#IBOutlet weak var labelDisplay: UILabel!
var calcEngine : CalculatorEngine?
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject?) {
if segue.identifier == "pushToNextView"
{
let secondVC: SecondVCViewController = segue.destinationViewController as! SecondVCViewController
secondVC.data = self.name
}
}
second view
class SecondVCViewController: UIViewController {
#IBOutlet weak var textData: UITextView!
var data = Array <String>()
override func viewDidLoad() {
super.viewDidLoad()
for(var i = 0; i < self.data.endIndex; i++ ){
self.textData.text = self.textData.text! + self.data[i] + "\n"
}
// Do any additional setup after loading the view.
}
There are many tools available to you to persist data from an app. Choosing an appropriate one requires some understanding of your needs, or at least making a guess. Here's where I would start but keep an eye out in case my assumptions don't match your needs.
What type of data do you want to persist?
Presumably you want to store the array of Strings seen in var name = Array<String>() but maybe you have some other data type in mind.
When do you need to read/write it (do you need to worry about multiple systems trying to write at the same time, is loading the data going to be very expensive because it is very large, do you only need to load a small piece at a time, ...)?
Sounds like you're fine reading/writing all the data at once and only on app launch/termination. There could be a reasonable maximum size to this calculation history to total storage size is probably fairly small.
How long does it need to be persisted?
Calculation histories are usually nice to have but not a catastrophic loss if they go missing. It would be nice to keep this data around but the app will work without it. Depending on use it may or may not cost the user time and frustration if the data is deleted unexpectedly.
Who needs access to it (is it also show on a web site, should it sync to all of a user's devices, ...)?
It's probably enough to keep this history just on the local device.
So what should we do?
For a small amount of data loaded all at once and only used locally I would write this data to a file.
Since you're just working with an array of strings we can use a plist (https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/PropertyLists/AboutPropertyLists/AboutPropertyLists.html)
NSArray (note: not a Swift array) has arrayWithContentsOfURL: and writeToURL:atomically: which can be a convenient way to read and write plist compatible data.
Using a file means we need to decide where to store this file. Take a look at Where You Should Put Your App’s Files. In this case it seems reasonable to write this data to either Documents or Library/Caches depending on how you plan to use it.
With all that covered we could save this stack something like:
let array = names as NSArray
guard let cachesURL = NSFileManager.defaultManager().URLsForDirectory(.CachesDirectory, inDomains: .UserDomainMask).first else {
print("Unable to find Caches directory, cannot save file.")
return
}
let fileURL = cachesURL.URLByAppendingPathComponent("history.plist")
array.writeToURL(fileURL, atomically: false)
Reading the file is similar. You'll need the same URL so decide which component is responsible for deciding where to save/load this data and let it supply the URL to use. You'll also need to check if any saved history exists or not (note that arrayWithContentsOfURL: can return nil).
At some point you might find it useful to model this data as a more specific data type, perhaps defining your own struct to capture these operations. A custom type can't be automatically written to or read from a file so you'll need to do a little more work. Apple has a nice example of how you might use NSCoding to do that in their tutorials: https://developer.apple.com/library/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/Lesson10.html
Preserving and Restoring State
Feature you are looking for is called State Restoration. From the docs:
State preservation records the configuration of your app before it is
suspended so that the configuration can be restored on a subsequent
app launch.
How to tackle it:
Tag your view controllers for preservation. Assign restoration identifiers.
Restore view controllers at launch time. Encode and decode their state.
Topic is very broad but the docs explain it very well.
What you're trying to do is keep persistent data between launches of your application, right?
For that you should use core data, there are many useful recourses on the web to help you with that, heres a few helpful ones.
https://developer.apple.com/library/watchos/documentation/Cocoa/Conceptual/CoreData/index.html
https://www.raywenderlich.com/934/core-data-tutorial-for-ios-getting-started