How to access Data in multipleviewControllers in Swift? - ios

I will try to explain the scenario as best as I can.
Lets say if viewControllerA tells the model to hold some data(for example data from a json response) and then viewControllerD(or any other viewcontroller) needs the data, how do i access the data from the model. Creating an instance of the model in viewControllerD creates a fresh instance without any data.
Below code explains the scenario.
ViewControllerA
let userdetails = UserDetails(json: self.userDetailsList!)
userdetailsarray.append(userdetails) //a global array
//Model
class UserDetails: NSObject {
var name : String?
var profession : String?
var id: String?
init(json: NSDictionary) {
let name = json["name"] as? String
let profession = json["profession"] as? String
let id = json["id"] as? String
self.name = name
self.profession = profession
self.id = id
super.init()
}
}
Possible Solution I know: Creating a global variable
var userdetailsarray = [UserDetails]
and appending UserDetails(model) into this array and using this array across multiple viewControllers. An alternative solution could be the model class being singleton.
I am looking for a more optimistic solution. Thankyou

If the data is more make it encrypted using NSKeyedArchiever and store it in user defaults. Whenever you required the data you can fetch it from user defaults and un archive it using NSKyedUnarchiever.
Note: This is the best solution only if you want to persist the data.

Related

what is the best way to save json data in swift model

I previously made a post about how to save json data in a model here .
Good now I am developing a project on iOS with swift, and I face the following problem, it happens that sometimes the database administrators change the names of the columns constantly, I only consume services with the help of Alamofire, to save data in models, use camal case and snake case, At the moment everything is fine but I would like to know what is the best way to save json data in a swift model, in my experience with Android I used retrofit with #Serializename and it worked great because if the json attribute of the service it was modified I only had to update a line of code and my variable could be kept the same, this helped me maintain a better order and it made it scalable.
In some cases the json comes to me.
{
"price": "385.000000",
"nameusser": null,
"favorite": 43,
"short_nameProduct": "Génifique Repair Sc",
"description_product": "Génifique repair sc es la crema de noche antiedad de lancôme. Despiértese con una piel fresca y rejuvenecida con nuestra crema de noche.",
"alt": null,
"photo": "https://url/020021000112-1.png"
}
in swift it would generate my model in the following way.
struct Product : Codable {
let price : String?
let nameusser : String?
let favorite : Int
let shortNameProduct : [Product]
let description : [Galery]
let alt : Product
let success : Bool
}
The problem here is that my variables must fit the json I get to use the JSONDecoder() and the convertFromSnakeCase, I can not define them myself.
while in java android I just have to do it like that.
#SerializedName("price")
private String price;
#SerializedName("nameusser")
private String name;
#SerializedName("favorite")
private Int favorite;
#SerializedName("short_nameProduct")
private String shortName;
#SerializedName("description_product")
private String descriptionProduct;
#SerializedName("altitude")
private String altitude;
#SerializedName("photo")
private String photo;
I just have to create the get and set and I would be ready to use the model.
I need to know how to do in swift the same, maybe a library that helps me store data json in the same way that I do in android.
Any comment would be very appreciated.
The best way is to use the Coding Keys:
struct Product : Codable {
let price : String?
let nameusser : String?
let favorite : Int
let shortNameProduct : [Product]
let description : [Galery]
let alt : Product
let success : Bool
enum CodingKeys: String, CodingKey {
case price = "price"
case nameusser = "nameusser"
case favorite = "favorite"
case shortNameProduct = "short_nameProduct"
case description = "description_product"
case alt = "alt"
case success = "success"
}
}
The name of the enum case has to match the property name on the struct. This way you can define whatever keys you want without having to write any custom encoding or decoding code.
Feel free to use my gist here:
Storage
You can use it like this:
let fileName = "Product.json"
extension Product {
func store() {
Storage.store(self, to: .documents, as: fileName)
}
static func retrieve() -> Product? {
guard let product = Storage.retrieve(fileName, from: .documents, as: Product.self) else {
return nil
}
return product
}
}
With Alamofire you will get a key-value, an Dictionary<String,Any> or [String:Any]
So you can do the following with your dictionary:
var myProduct : Product?
if let price = myDictionary["price"] as? String{
myProduct.price = price
}
With that example you can create a method to mapping the entire JSON into your struct. Maybe if you want to make it more scalable, you can create an enum with String raw values and use it as the key for the dictionary, some like:
enum productProperty : String{
case Price = "price"
}
var myProduct : Product?
if let price = myDictionary[productProperty.Price] as? String{
myProduct.price = price
}
And maybe create a more complex class to iterate trough the dictionary and check the key using the enum, but that implementation depends of your own skills.
Edit1:
To use't with alamofire, you need to get the jsonResponse of the request, some like that:
.request(yourURL, method: .get, parameters: yourParameter).responseJSON(options: .allowFragments , completionHandler: { response in
if let object = response.result.value as? Dictionary<String,Any> {
yourMethodToSave(object)
}
})
and inside yourMethodToSave(_ object: Dictionary<String,Any>) you need to put the logic above.
Ps: The #sendtobo answer have the enum example that i tell that you can use to a more scalable mapping for your object

Adapting to a constantly changing model Swift iOS?

class User {
var uid : String
var profileImageURL : String
init(uid : String, profileImageURL : String) {
self.uid = uid
self.profileImageURL = profileImageURL
}
}
If my project were to start out with a User model such as the one above, and I have a huge-scaled application where this User is being initialized in over 20 files, if I were to go in and add a new required property such as age, I would have to fix my initializers for every single file. Worse off, I would have to go in after each initializer and set the new property on its own line.
If I were required to add in 25 new properties over the course of production, this would be a nightmare.
What is the best way to handle large models like this that might change in the future?
I would not initialise an object in 20+ location. That makes the code fragile. Put an extra layer like user management where you can ask the current user / ask for any user. And make the init there, in one single place.
You do something like this
class User {
var uid : String
var profileImageURL : String
init(all : [String:Any]) {
self.uid = all["uid"] as? String ?? ""
self.profileImageURL = all["profileImageURL"] as? String ?? ""
}
}
or write a Codable class to decode the dictionary directly

Populate List in Swift 2.0 and display results

I've been learning iOS development for the past three weeks, I'm currently following a course on Udemy so far so good.
However I'm following one of the lectures whereby we build an Instagram Clone.
The instructor is using three arrays which are as follows:
var usernames = [""] // Stores all usernames
var userIds = [""] // Stores all Id's of the given usernames
var isFollowing = [false] // Stores where or not you're following that user
To me trying to keep track of what userId goes with what username using two arrays is basically an accident waiting to happen so I decided to set off and find a more feasible approach. I reverted back to my .Net days and decided to create a list so I went and created a class as follows:
class Users{
var Username : NSString = ""
var UserId : NSString = ""
var Following : Bool = false
}
Now inside my ViewController I make a call to Parse which returns me a list of users and I'm basically trying to loop through the response, and add them to the list class as shown here:
var t = [Users]() // After googling the web, this seems to be the syntax for a list declaration ?
let u = Users()
for object in users{
if let o = object as? PFUser {
u.Username = o.username!
u.UserId = o.objectId!
u.Following = o.IsFollowing!
self.t.append(u)
}
}
print(self.t)
Now when I print this to the console I see the following:
ParseStarterProject_Swift.Users
As I have one user at present, however when I try to loop through T and display the username in the console it doesn't display anything.
for x in t {
print(x.Username)
}
Your basic intuition is correct, it's better to have an array of custom objects, not multiple arrays.
Regarding making it more Swifty, consider your Users type. You might want something like:
struct User {
let username: String
let userId: String
let following: Bool
}
Note,
property names should start with lowercase letter;
Users should probably be called User, as it represents a single user;
we don't generally initialize values to default values like that, but rather specify them in the initializer;
we probably use String not NSString;
if a property cannot change, you'd use let, not var;
properties begin with lower case letters;
Then you can do something like:
var t = [User]()
for object in users {
if let o = object as? PFUser {
t.append(User(username: o.username!, userId: o.objectId!, following: o.IsFollowing!)
}
}
print(t)
Clearly, with all of those ! forced unwrapping operators, you'd want to be confident that those fields were populated for all of those properties.
Using struct is nice because (a) it's a value type; (b) you get the initializer for free; and (c) you can just print them. If you really wanted User to be a reference type (a class), you'd do something like:
class User {
let username: String
let userId: String
let following: Bool
init(username: String, userId: String, following: Bool) {
self.username = username
self.userId = userId
self.following = following
}
}
And if you wanted to be able to just print them, you'd define it to conform to CustomStringConvertible:
extension User: CustomStringConvertible {
var description: String { return "<User; username = \(username); userId = \(userId); following = \(following)>" }
}
With the class, you can feel free to change that description computed property to show it in whatever format you want, but it illustrates the idea.
You are correct in considering that keeping track of what userId goes with what username using two arrays is dangerous, you in the correct direction with your approach.
First, I would just like to suggest that you use correct naming convention:
Classes should be singular (except in very specific cases).
Variable/property names should begin with lowercase.
This would mean that your user class should look like this:
class User {
var username : NSString = ""
var userId : NSString = ""
var following : Bool = false
}
I will keep your existing naming use for the next part. The main problem with your code is that the variable "u" is a object which you create only once and then modify it. You should be creating a new "Users" object for each user instead of modifying the original. If you don't do this you will just have an array with the same user multiple times. This is how your code would look now:
var t = [Users]()
for object in users {
if let o = object as? PFUser {
let u = Users()
u.Username = o.username!
u.UserId = o.objectId!
u.Following = o.IsFollowing!
self.t.append(u)
}
}
print(self.t)
Next you mention that when you print to console you see the text: ParseStarterProject_Swift.Users, that is because Swift does not automatically print a pretty text with the content of your object. In order for it to print something more detailed, your "Users" object would need to implement the CustomStringConvertible. You can see a more detailed answer about that here: how-can-i-change-the-textual-representation-displayed-for-a-type-in-swif.
Lastly, you mention that when you loop trough "t" and display the username in the console it does not display anything. This is caused by one of two things:
Because there are no users being returned from parse, so the "t" array is actually empty. Try print(t.count) to see how many objects are in the array.
Because your "Users" object declares an empty string "" as the default username and the username is not being set correctly when getting the data from the parse. Which means that it IS actually printing something, just that it is an empty string. Try defining a different default value like var username : NSString = "Undefined" to see if it prints something.
Good luck learning swift!

A Good design in Swift for a model that has many aspects?

I would like to ask a question about a good example of how to define a model with many aspects in swift, especially when the project gets bigger and bigger and one model has many aspects. The questions is pretty long but I just would like to know how ppl design a model in a big project. Any comments or thought would be appreciated.
Let's say there is a model called "Book" and it is defined like below:
class Book {
var id: String
var title: String
var author: String
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
Book has a failable initialiser that parses properties from JSON sent from the server.
On view controller A, it describes the more detailed information about the mode Book, so some properties are added to the model when it is used on view controller r A:
class Book {
var id: String
var title: String
var author: String
// required on View Controller A
var price: Int
var seriersName: String
var reviewNumber: Int
var detailedDescription: String
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
On another view controller B, we want to show the history of book purchase. So the model needs additional properties as below:
class Book {
var id: String
var title: String
var author: String
// required on View Controller A
var price: Int
var seriersName: String
var reviewNumber: Int
var detailedDescription: String
// required on View Controller B (Not required on VC A)
var purchasedDate: NSDate
var expireDate: NSDate
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
This definition of Book lacks flexibility because the JSON passed to the failabel initialiser must have all of the properties even on an VC that uses only some of the properties.
Solution A:
I think the simplest solution for this is just declaring those additional properties as optional, but I personally think this is not so cool because whenever those optional properties are used they need to be checked if they are not nil.
if let seriesName = book.seriesName {
self.seriesNameLable.title = seriesName
}
This kind of optional binding code will be overflowed all over the codes I assume. Implicit optional binding might be able to used but that is not really safe to use.
Solution B:
Another solution might be to define different models that inherits Book, like BookA and BookB. But what if we need a model that has BookA and BookB's aspects at the same time?
I guess there is no single solution for this kind of problem but I would like to know how other ppl define a model in a big project. (I wonder if someone would have a cool solution using some "swift specific" features like protocol and its extension :). I would appreciate any kind of opinions... Thank you.
Disclaimer: I'm not a Swift programmer, this are extrapolations I make from other languages with the same features and my Swift syntax might not be 100% accurate
Using protocols I would do something like:
class EBook: Book, HasOnlineSource {
...
}
class OtherKindOfBook: Book, WithCollectorEditions, HasBleh, WithFoo {...}
But you must ask yourself:
Do I need this to change dinamically?
If that is the case, you would need to go with Delegation through composition.
Are different parts of the application using the core models differently?
Or in other words, are there different users of those models needing different behavior? In that case, extensions are very useful since allow to expose different behaviors for the same model depending on their context. For instance, the Reporting module can send the message numberOfReaders while the Selling module could ask for promotionalCodes. Both are using the same model, but interacting with different protocols. In your case, you have different controllers wanting different things, so this might apply.
Using delegates
This follows the Composition over inheritance principle, but after reviewing how delegates work in Swift, I understood that they are not a native implementation but still a design pattern (a feature request you might say), the delegation is being made by hand.
Other languages allow you to make a JSSONSerializableBook with a BookProtocol, but instead of implementing what is required on BookProtocol you can set a delegate upon initialization which will implement such protocol. This delegate will be an internal collaborator of JSSONSerializableBook and all messages that are part of BookProtocol received by JSSONSerializableBook will be delegated to it.
Or in other words, the message passing is handled automatically (here is the Kotlin documentatin on delegates if you want to check how other language implements it).
If you want to do the same on Swift, you must explicitly make the message passing to your delegate.
This design has several advantages but since there is no native support for message passing it becomes very verbose to implement.
For further reference you can check the papers on mixins and traits to have some insight on the design decisions behind this features.
This is why Swift has optionals.
If not all Books have, say, a purchasedDate then that should be an optional and then your initialiser doesn't need to fail if that field isn't present in the JSON.
It is your view controllers that will enforce the business logic - so if ViewControllerB gets a Book without a purchasedDate it should generate some sort of error.
It also may be that your base class needs to be decomposed into smaller objects.
For example, you could probably have a PurchaseEvent that is associated with a Book rather than storing that data in the Book itself.
Think of the characteristics of a book in the real world; it has the basic properties you first listed and may even have a price (on the back cover) but it doesn't tell you when it was sold and for how much.
However, #Logain's idea of using Protocols is also good, although it may be tricky when it comes to keeping the server JSON side in sync with the app code.
First of all, if you have a view controller responsible for presenting “more detailed information” about a book, then let's call it BookDetailsViewController, instead of the meaningless “View Controller A”. Similarly we should talk about BookHistoryViewController, not “View Controller B”.
If you want to treat the “more detailed information” as all-or-nothing, then encapsulate it as such, in a Details object that the Book object optionally reference. Same with the history. So here's a model:
class Book {
let id: String
let title: String
let author: String
// These can't be lets because each requires self to initialize.
private(set) var details: Details?
private(set) var history: History?
init?(json: [String: AnyObject]) {
guard let
id = json["id"] as? String,
title = json["title"] as? String,
author = json["author"] as? String
else {
// These assignments will be unnecessary in the next version of Swift.
self.id = ""
self.title = ""
self.author = ""
return nil
}
self.id = id
self.title = title
self.author = author
self.details = Details(book: self, json: json)
self.history = History(book: self, json: json)
}
class Details {
unowned let book: Book
let price: Int
let seriesName: String
let reviewNumber: Int
let detailedDescription: String
init?(book: Book, json: [String: AnyObject]) {
self.book = book
guard let
price = json["price"] as? Int,
seriesName = json["seriesName"] as? String,
reviewNumber = json["reviewNumber"] as? Int,
detailedDescription = json["detailedDescription"] as? String
else {
// These assignments will be unnecessary in the next version of Swift.
self.price = 0
self.seriesName = ""
self.reviewNumber = 0
self.detailedDescription = ""
return nil
}
self.price = price
self.seriesName = seriesName
self.reviewNumber = reviewNumber
self.detailedDescription = detailedDescription
}
}
class History {
unowned let book: Book
let purchasedDate: NSDate
let expireDate: NSDate
init?(book: Book, json: [String: AnyObject]) {
self.book = book
guard let
purchasedDate = (json["purchasedDate"] as? Double).map({ NSDate(timeIntervalSince1970: $0) }),
expireDate = (json["expireDate"] as? Double).map({ NSDate(timeIntervalSince1970: $0) })
else {
// These assignments will be unnecessary in the next version of Swift.
self.purchasedDate = NSDate()
self.expireDate = NSDate()
return nil
}
self.purchasedDate = purchasedDate
self.expireDate = expireDate
}
}
}
Given this model, you can allow the user to ask for a BookDetailsViewController or a BookHistoryViewController only when the appropriate property isn't nil.
class BookViewController: UIViewController {
#IBOutlet var detailsButton: UIButton!
#IBOutlet var historyButton: UIButton!
var book: Book!
override func viewDidLoad() {
super.viewDidLoad()
detailsButton.hidden = book.details == nil
historyButton.hidden = book.history == nil
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
switch segue.identifier {
case .Some("details"): prepareForDetailsSegue(segue)
case .Some("history"): prepareForHistorySegue(segue)
case let other: fatalError("unknown segue identifier \(other)")
}
}
private func prepareForDetailsSegue(segue: UIStoryboardSegue) {
let destination = segue.destinationViewController as! BookDetailsViewController
destination.details = book.details!
}
private func prepareForHistorySegue(segue: UIStoryboardSegue) {
let destination = segue.destinationViewController as! BookHistoryViewController
destination.history = book.history!
}
}
class BookDetailsViewController: UIViewController {
var details: Book.Details!
// etc.
}
class BookHistoryViewController: UIViewController {
var history: Book.History!
// etc.
}

Passing an object between controller Swift

I have a storyboard which start with a login page. And I have a User class like this
class User {
// Properties
var firmCode : String
var firmId : String
var userId : String
// Default Initializer
init() {
self.firmCode = ""
self.firmId = ""
self.userId = ""
}
// Defult Initializer with prams
init(firmCode: String, firmId: String, userId: String) {
self.firmCode = firmCode
self.firmId = firmId
self.userId = userId
}
}
I am generating some values in login page, and I can create my User object with these values.
Now I want to make this object global and use this user object in all scene.
How to pass this object to other scenes? For example I want to pass this values with my object to an embed static table view controller like this
Note: Does it matter, the embed controller has a tab bar controller? I guess I need to use my delegate file?
EDIT:
Now I have created my global object like this:
// test value
let firmId = "Company Id"
// Creating user object without user class declaration
NSUserDefaults.standardUserDefaults().setObject(firmId, forKey: "testFirmId")
And trying to get my object like this:
// Creating user variable in other scene by globally
let getValue : AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey("testFirmId")
// checking
println(getValue!)
AND done, return my value on termanal. All work until here.
But now the value return to me as AnyObject, I need the convert it String to apply like this:
someLabel.text = getValue!
You can use NSUserDefaults for that like you can store your object this way:
NSUserDefaults.standardUserDefaults().setObject(YourObject, forKey: "YouKey")
After that you can access it in any scene like:
let yourObject: AnyObject? = NSUserDefaults.standardUserDefaults().objectForKey("YouKey")
Hope It will help you.
EDIT:
NSUserDefaults is limited in the types it can handle: NSData, NSString, NSNumber, NSDate, NSArray, and NSDictionary. Thus no Swift objects or structs can be saved.
From the Apple NSUserDefaults Docs:
A default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any
other type of object, you should typically archive it to create an
instance of NSData.
And if you are using NSUserDefaults then you can directly store your instance like shown below:
NSUserDefaults.standardUserDefaults().setObject(test, forKey: "firmCode")
NSUserDefaults.standardUserDefaults().setObject(test, forKey: "firmId")
NSUserDefaults.standardUserDefaults().setObject(test, forKey: "userId")
You Don't need model class for that.
var user = User()
self.user.firmCode = 1
//Assign value to other variables
//Save User object in NSUserDefaults as like this
User.saveUserObject(user)
//Function For Save User
class func saveUserObject(user : User){
let userKey = "UserData \(user.userID)"
NSUserDefaults.standardUserDefaults().setObject(data, forKey: userKey)
}
//Get User Object from NSUserDefaults
class func getUserObject(userIDValue : NSNumber) -> User? {
let userKey = "UserData \(userIDValue)"
if let data = NSUserDefaults.standardUserDefaults().objectForKey(userKey) as? NSData {
return data as? User
}
return nil
}

Resources