How to pass json data in swift app between two viewcontrollers - ios

I need help with passing json data.I have json data in this array
var TableData:Array< String > = Array < String >()
In this Array I have Name,Address,Latitude, Longitude
I show Name and Address in a tableView , but I would like to create annotation in different viewController with Latitude and Longitude depending on which cell user taps(Name,Adress,latitude,Longitude shoulld be equal) , so I am asking you if there is some good tutorial in swift , or if you have just some advice.
Thank you.

There are many different ways to pass data from one swift file to another. In the case that there is a rootViewController and a button is clicked to open a new ViewController. Having previously defined an array in the new ViewController, the json parsed array can be passed along using the prepareForSegue method.
Another popular way to pass information between different swift files would be using the AppDelegate. Here you can create instances of different swift classes using a method known as instantiateViewControllerWithIdentifier("identifier"). This can be done by creating a storyboard variable then calling this method by doing storyboard.instantiateViewControllerWithIdentifier("identifier").
let newvc = self.storyboard?.instantiateViewControllerWithIdentifier("newvcIdentifier") as! (UIViewController extended class)
newvc.data = TableData
self.navigationController?.pushViewController(newController, animated: true)
where newvc has a variable declared as follows:
var data: Array <String>!
Another method that can be used is having shared data among all of the classes using a singleton. A singleton can be created very simply in swift3, take a look here for details.
class JSONData {
var json: Array <String>
static let sharedInstance = JSONData()
}
Before segueing to the next vc, you should store the data in the sharedInstance class. This should be done by overriding the prepare method. Documentation on this method can be found here
sharedInstance.json = self.json
Once the data is set in the shared instance, after the new view controller is loaded, this data can be accessed through sharedInstance.json.
Hope this works for you and just comment if you have any other questions!

I would do something like this:
let newController = self.storyboard?.instantiateViewControllerWithIdentifier("newControllerIdentifier") as! NewControllerClassName
newController.data = array
self.navigationController?.pushViewController(newController, animated: true)
It also appears you are using a array of string type that are comma separated.
I would rather create a variable like below
var jsonArray:[[String:String]]?

so I am asking you if there is some good tutorial in swift
http://matteomanferdini.com/how-ios-view-controllers-communicate-with-each-other/
He covers clean code and best practices. Better read this before implementing anything.
or if you have just some advice
Your code needs to be clean. Passing latitude as String it's going to create you a problem later (conversions, code understanding, or even bugs).
Make a new account here and watch uncle Bob. His lessons are valuable. matteomanferdini above follow uncle bob practices.
https://www.safaribooksonline.com/library/view/clean-code/9780134661742/

Related

Loading data and singleton for IOS

I am making an instagram clone but I am having abit of question about what would be the best way to pass data arround.
I have 3 post arrays and three VCs as shown below
myPosts = [Post]()
nearbyPosts = [Post]()
followingPosts = [Post]()
myPostVC
nearbyPostVC
followingPostVC
The interesting part is when I download a post that is close by and it happens to be one of my post that I am also following. So when I download that post, I will need to put it in all three arrays so they are the same reference. This is so that if the user "likes" that post, it will be reflected in all three array because they point to the same post. (If they dont have the same reference, and the user likes or perform any sort of action on a post in myPosts array, I will have to manually loop through each nearbyPosts and followingPosts to update their "like count" individually)
Initially, my code looks this
myPost -> Initiated inside myPostVC
nearbyPost -> Initiated inside nearByVC
followingPostVC -> Initiated inside followingPostVC
So in myPostVC, I would need to create an instance of nearbyVC and followingPostVC so that everytime I download a post inside myPostVC, I loop through nearbyVC.nearbyPosts and followingPostVC.followingPosts and update anyting that has the same poast as the one that was just downloded. And I do this for all three VC. Imagine if I have moreVC, this gets very messy as each VC is linked to other VC (In my case, the threeVC as three tabs)
This is when I changed to Singleton... I made myPosts, nearbyPosts and followingPosts to be all inside a singleton as shown below. This allows the three VC to be disconnected and they just need to access whichever array they wish to by calling something like PostService.ps.myPosts (Without the need to create a refrence to other VC)
My question is...
I know it "might" be good to use the first method because it is not singleton, but is that actually a method people use in this case? (It makes all VC linked together. Very messy)
Is singleton the right way to solve this problem or is there a much better way? (Note that similar to instagram, I dont really need to store these posts on the phone to look at in future, so no coredata?)
class PostService {
static let ps = PostService()
private var _myPosts = [Post]()
private var _nearbyPosts = [Post]()
private var _followingPosts = [Post]()
var myPosts: [Post] {
return _myPosts
}
var nearbyPosts: [Post] {
return _nearbyPosts
}
var followingPosts: [Post] {
return _followingPosts
}
So many ways to approach this.
Create a new class (such as your PostService) to represent your data model, outside of any view controller. This data model class could be a singleton. I would put all of the posts into a single Set (not an array) named allPosts. Using a Set rather than an array will prevent any duplicates. Each Post object could have boolean vars indicating if it belongs to specific subsets or not. This could be stored or calculated depending on your data.
Then to access a specific subset, use the filter function on the set, such as:
var allPosts: Set<Post> = ...
var myPosts: [Post] {
return allPosts.filter { $0.isMine }
}
var nearbyPosts: [Post] {
return allPosts.filter { $0.isNearby }
}
The advantage here is no duplication of objects and a shared resource for each of your view controllers.
Note: Since this is a Set rather than an Array, it will not have any order to the items it contains. But you can always use the sort() function to do that.

Update dictionary with previously deleted items

I have two dictionaries. Both declared in a viewController, both based on a model structure class.
// ItemDictionary
var ItemDictionary = ItemModel()
var JSONDictionary = ItemModel()
JSON data is fed into the JSONDictionary and then this data is passed to ItemDictionary which feeds a table within ViewDidLoad.
self.ItemDictionary = self.JSONDictionary
All good. The table is nicely populated from JSON data. I can also delete items from the table and the ItemDictionary. However, when I try and add items back by referring to the original dictionary (JSONDictionary) the item has gone.
I understand this is expected. If Dictionary1 = Dictionary2, a new dictionary is not actually created. Only an second address. So if you change Dictionary1, Dictionary2 also changes.
A practical example could be setting a price range. You can reduce the range and have less items displayed on the table. But you can't replace previously deleted items if you wanted to increase the price range. I do not want to recall the JSON script each time I edit the range. Advice appreciated.
As confirmed by the OP, ItemModel is a class and not a Dictionary. To fix this you need to make ItemModel a real Dictionary and thus a value type. This is probably the preferred choice but will be more work.
An alternative would be to add an convenience initializer to the ItemModel class that instantiates a new copy of itself and call that instead of setting self.ItemDictionary = self.JSONDictionary.
Something like this.
init(model: ItemDictionary) -> ItemDictionary {
// copy properties
}
Then create the new copy.
self.ItemDictionary = ItemDictionary(self.JSONDictionary)
Later you can reinitialize ItemDictionary with the same call.
Try this code out-
var dictionary1 = ["name": "Magnus"]
var dictionary2 = dictionary1
dictionary2.removeAll()
print("\(dictionary2) \(dictionary1)")
The output is :-
[:] ["name": "Magnus"]
Thus 2 new dictionaries are being created. If you refer to the Swift documentation, You will find that in swift, references are hardly present.
Some other portion of code might be responsible. Hope this helps :-)

Is it ok to use NSUserDefaults to persist and pass data between ViewControllers

I recently came across a website where the blogger recommends not to use NSUserDefaults to pass data between viewControllers which is the method I have been using.
This is how I'm using NSUserDefaults to store the user's settings values of an app.
1- The first thing I do in my main ViewController is add constant variables for all of my keys to be able to read them globally.
let kOne= "keyOne"
let kTwo = "keyTwo"
class ViewController: UIViewController{
//code...
}
2- In AppDelegate.swift I initialize all of my key values in the didFinishLaunchingWithOptions method.
// set default values for NSUserdefaults keys
NSUserDefaults.standardUserDefaults().registerDefaults([ kOne : "0", kTwo:"0" ])
3- Then in my ViewController I read the values.
func keyOneFromDisc()->Double{
let keyOneValue:String = NSUserDefaults.standardUserDefaults().stringForKey(kOne)!
return Double(keyOneValue)!
}
// do something with the value...
4- In a SettingsViewController I read and save NSUserDefaults again.
func keyOneFromDisc()->Double{
let keyOneValue:String = NSUserDefaults.standardUserDefaults().stringForKey(kOne)!
return Double(keyOneValue)!
}
// more functions...
// save my settings
#IBAction func saveSettings(sender: AnyObject) {
NSUserDefaults.standardUserDefaults().setObject(someValueFromAnInputField.text, forKey: kOne)
// more keys
}
I is this a bad practice?
What would be a better way to accomplish what I described above?
EDIT: Sorry about the confusion, I forgot to mention that I actually
need to persist the data I'm passing around. I do use prepareForSegue when passing data around that do not need to be persisted.
You can use NSUSerdefault for storing less amount of data, like for login purpose or BOOL flag etc. But every time you can't store data for transition of ViewController.
For That Purpose you can use
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
NSUserDefault are used to keep data between app execution. The problem is there is nothing certain about when the data will be saved (if i remember correctly).
The way i do it is either through a shared Model object for siblings VCs, or throught relationships & delegation between the VCs when there are parent/sub viewcontrollers.
it better to make variables in both of controllers and transfer data in prepareForSegue method. But if this data is one and only for the whole application - you can just store it in AppDelegate. It makes possible reading and saving this data from any place of your app.

How to get access to constants from separate .swift file

I'm learning swift and this seems like a simple problem yet I cannot figure it out. I'm making a trivia game to learn the basics and would like to store my question bank in a separate .swift file.
I can see the class come up in xcodes intellisense, but have no clue how to actually use the constants I have made in that file. The constants are of type dictionary and they contain the question, answers, correct answer, and question number within their key and value pairs.
In my main ViewController, I have an empty array I create and would like to populate it with the constant dictionary questions I have contained within my separate .swift file:
var BasicQuestionBank: [[String:String]] = [] // Array for multiple dictionary storage
func AddingToQuestionBankOne() {
BasicQuestionBank.append(Questions.????????) // Can't figure out how to put this in array from separate file.
}
My separate .swift file looks simply like this but has multiple questions:
public class Questions {
let question1 = [
"question": "A square has how many sides?",
"answerA": "1",
"answerB": "2",
"answerC": "3",
"answerD": "4",
"correctAnswer": "answerD",
"questionNumber": "A-001-001"]
}
Also, to populate question banks arrays programmatically by the "questionNumber" key, is this possible? Say I have 20 questions with 10 being "A-001" prefix and 10 being "A-002" prefix, how would I choose to pick the 10 out of my choice? I have tried putting these in a for-in loop, but again, cannot figure out how to access this other .swift file.
Any help and insight would be greatly appreciated.
In your example, you've defined a class, Questions, that has a constant member variable. However, you need to create an "instance" of that class in order to access its member variable:
public class Questions {
let question1 = [ etc. ]
}
var basicQuestionBank: [[String:String]] = []
func addToQuestionBankOne() {
// qs is an instance of the Questions class
let qs = Questions()
// and has a member variable, question1
basicQuestionBank.append(qs.question1)
}
Classes are like templates. You create instances of classes (these are called objects) that all have the same member variables and methods, but that can hold different values in their member variables.
In this case, it might seem a bit confusing, because you've created a class that has only one member, which is a let (so it can never hold anything other than its initial value), and no init method (which is the only place you could set question1 to be anything different than the default value you've given it). But nonetheless, you can only access that value by creating an instance of Questions, then accessing the question1 property on that instance.
By the way, for your next step you might want to try representing your questions as a struct, rather than a dictionary of strings:
struct QandA {
let question: String
let answers: [String]
let correctAnswer: Int
let questionNumber: String
}
let question1 = QandA(
question: "A square has how many sides?",
answers: ["One","Two","Three","Four"],
correctAnswer: 4,
questionNumber: "A-001-001")
Putting your data in a structure like this makes it much easier to operate on:
println(question1.question)
for (number, answer) in enumerate(question1.answers) {
println("\(number): \(answer)")
}
Just like above, this is creating an instance of the QandA struct (structs are similar to classes – the Swift book has more details on the differences), called question1, then accessing the properties of this instance.
You can enable Core Data in your project, then create a new Core Data object called "Question". Add fields for question, answers, correct answer, etc. Then, in AppDelegate, create the questions and add them to the ManagedObjectContext first time that app loads. You can find a good tutorial for Core Data in Swift here and here. If you ever want to change the questions, just change them in AppDelegate.swift and have the app reload them the next time it opens.

Swift and core data - Array and cell basics

If I define my array as:
var myList: Array <AnyObject> = []
and then use the viewDidLoad override function to populate this array like this:
override func viewDidAppear(animated: Bool) {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let freq = NSFetchRequest(entityName: "List")
myList = context.executeFetchRequest(freq, error: nil)!
tableView.reloadData()
}
and my "List" only contains usernames as strings. What exactly is being stored in "myList"? Is it just as simple as an array of strings with the usernames?
Any thoughts and explanations would be highly appreciated.
Your myList would contain subclasses of NSManagedObject or List instances if you have defined that class. Each object would have attributes that you have defined for that core data object.
The indexPath is a class that is used to represent the row and section of the UITableView. This is also used to identify the item and section of a UICollectionView.
executeFetchRequest returns an array of managed objects from the CoreData data store. There are basically two ways to handle them.
The first is to use them as-is. They will all be instances of NSManagedObject, and you can use methods like valueForKey: to get their values.
The second way is to define your own subclass of NSManagedObject, in your case probably named List, and then define properties on that object allowing you to access the values directly.
Core Data as a whole is both insanely powerful and insanely complex. I strongly recommend you work through a tutorial, either Apple's or otherwise, to get a hang of it. (Note that some of Apple's docs recommend starting with something called Core Data Starting Point. Helpfully, Apple has retired that document, but hasn't yet removed the references to it from other documents.)

Resources