I'm developping an app, which make an request to a webservice, once the webservice answer (in JSON) I decode the result in an other view (repository.swift)
I just need to use the value which are stored in repository, in one of my other view (Swype.swift)
This is my code to decode JSON :
if let json = NSJSONSerialization.JSONObjectWithData(urlData!, options: nil, error: nil) as? [String:AnyObject] {
for (_, value) in json {
if let dict = value as? [String:AnyObject] {
let repo = Repository(jsonData: dict)
repos.append(repo)
}
}
}
And my "repository" class :
class Repository {
var idobjet: String?
var title: String?
var price: String?
var description: String?
var added: String?
var userid: String?
var user_name: String?
var user_zipCode: String?
var category_id: String?
var category_label: String?
var subcategory_id: String?
var subcategory_label: String?
var picture_url: String?
init(jsonData: [String:AnyObject]) {
self.idobjet = jsonData["id"] as? String
self.title = jsonData["title"] as? String
self.price = jsonData["price"] as? String
self.description = jsonData["description"] as? String
self.added = jsonData["addedDate"] as? String
self.userid = jsonData["userid"] as? String
self.user_name = jsonData["user_name"] as? String
self.user_zipCode = jsonData["user_zipCode"] as? String
self.category_id = jsonData["category_id"] as? String
self.category_label = jsonData["category_label"] as? String
self.subcategory_id = jsonData["subcategory_id"] as? String
self.subcategory_label = jsonData["subcategory_label"] as? String
self.picture_url = jsonData["picture_url"] as? String
}
}
var repos = [Repository]()
I need to get "picture_url", "title" and "Description" value in my other view, to create an Imageview and some other fields :
//Construct the imgUrl to get an image URL for the pages
let urlString: NSString = "picture_url" Values
if let url = NSURL(string: urlString as String) {
if let data = NSData(contentsOfURL: url) {
newCard.image = UIImage(data: data)!
newCard.content = "title" value
newCard.desc = "price" value
self.data.append(newCard)
NSLog("fetch new data")
}
}
}
Depending on how many view controllers make use of this fields, probably the simplest solution is to define a property that holds your Repository object in your destination view controller and then set it in the parent view controller before the segue (using the (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender method).
On the other hand, if these fields have to be accessed frequently, it's probably more convenient to set a data model class which view controllers can rely on.
Okay, so there are multiple ways to handle this.
Here is what I suggest:
initialize a Repository object as a global variable.
var repo:Repository?
class SomeViewControllerOne: UIViewController {
func initializeRepository() {
repo = Repository(....)
// continue with initialization
}
}
class AnotherViewControllerTwo: UIViewController {
func useRepository() {
if let repository = repo {
print(repository.title)
}
}
}
the object "repo" is global, and can be used application wide. just be carful, because the object is an optional.
Related
I'm new to iOS development and I understand that allowing optional values when an object is initialized is not a 'good citizen' technique. That being said, I've read that it is good practice to always have values set, like this:
class Item{
var name: String
var color: String
init(name: String, color: String) {
self.name = name
self.color = color
}
}
This looks nice and tidy but how can I do something like that working with Firebase? Look what I've got so far:
private func loadPosts(){
databaseHandle = ref.child("users/\(self.user.uid)/posts").observe(.value, with:{(snapshot) in
var newPosts = [Post]()
for itemSnapShot in snapshot.children {
let post = Post(snapshot: itemSnapShot as! FIRDataSnapshot)
newPosts.append(post!)
}
self.posts = newPosts
self.tableView.reloadData()
})
}
This guy is placed in my PostsViewController where I have my table view. This is my model:
class Post {
var ref: FIRDatabaseReference?
var title: String?
var answer: String?
var contentUrl: String?
var photoUrl: String?
var createdAt: String?
var feeling: String?
var kind: String?
var text: String?
var uid: String?
var measurements: Dictionary<String, String>?
//MARK: Initialization
init?(snapshot: FIRDataSnapshot){
ref = snapshot.ref
let data = snapshot.value as! Dictionary<String, Any>
title = data["title"]! as? String
answer = data["answer"] as? String
contentUrl = data["content_url"] as? String
photoUrl = data["photo_url"] as? String
createdAt = data["created_at"] as? String
feeling = data["feeling"] as? String
kind = data["kind"] as? String
text = data["text"] as? String
uid = data["uid"] as? String
measurements = data["measurements"] as? Dictionary<String, String>
}
}
I don't know exactly why but those question marks doesn't feel quite right and now and then I get some nil pointer error, which I think I should be able to avoid by using the 'good citizen' technique.
So, does anybody know how can I use Firebase following Swift best practices?
Either you wish to allow the properties of your Post class to be nil or you don't.
If you do, that's fine. The code you posted allows any of them to be nil. You just need to safely access each property every time you need it.
If you don't, then don't make them optional. Then in your init you need to ensure none of the properties are set to nil by giving each a default if there is no value in the snapshot.
class Post {
var ref: FIRDatabaseReference
var title: String
var answer: String
var contentUrl: String
var photoUrl: String
var createdAt: String
var feeling: String
var kind: String
var text: String
var uid: String
var measurements: [String : String]
//MARK: Initialization
init?(snapshot: FIRDataSnapshot) {
if let data = snapshot.value as? [String : Any] {
self.ref = snapshot.ref
title = data["title"] as? String ?? ""
answer = data["answer"] as? String ?? ""
contentUrl = data["content_url"] as? String ?? ""
photoUrl = data["photo_url"] as? String ?? ""
createdAt = data["created_at"] as? String ?? ""
feeling = data["feeling"] as? String ?? ""
kind = data["kind"] as? String ?? ""
text = data["text"] as? String ?? ""
uid = data["uid"] as? String ?? ""
measurements = data["measurements"] as? [String : String] ?? [:]
} else {
return nil
}
}
}
Note how this ensures there is a proper snapshot. Note how a default value is set to each property if there is no value in the snapshot. Obviously you can assign any default you wish. I use the empty string as an example.
Even if you want to allow the properties to be nil, you should at least update your code to check for a valid snapshot like in the code above.
Of course you can have a combination where some properties can't be nil and some can. That's up to your needs.
First it is fine for you to have optionals in your data model, as long as you assign value to it later on in the future.
I would recommend to use ObserveSingleEvent() and you should make use of completion handler to make it easy. If you don't know completion handler: Link
I recommend:
• not to put database ref in your class model, and instead of using Dictionary<String, String>? just use [String: AnyObject]?
• make your post array public so that it can be accessed into the tableview.
Here's example:
class func getPosts(uid: String, _ completion: #escaping (_ posts: [Post]?, _ error: Error?) -> Void) {
//update inside users node
var posts = [Post]()
Firebase.databaseRef.child("users").child(uid).child("posts").observeSingleEvent(of: FIRDataEventType.value, with: { (dataSnapshot) in
guard let postsDictionary = dataSnapshot.value as? [String: AnyObject] else {
completion(nil, nil)
return
}
let n = postsDictionary.count
for postDictionary in postsDictionary {
let post = Post()
post.userID = uid
if let content = postDictionary.value["content"] as? String {
post.content = content
}
if let imageURL = postDictionary.value["imageURL"] as? String {
post.imageURL = imageURL
}
if let timeStamp = postDictionary.key as String! {
if let date = timeStamp.convertToDate() {
post.timeStamp = date
}
post.postIdentifier = timeStamp
}
posts.append(post)
if posts.count == n {
// Sort the array by the newest post
let sortedPosts = posts.sorted(by: { $0.timeStamp.compare($1.timeStamp) == .orderedDescending })
completion(sortedPosts, nil)
}
}
}) { (error) in
completion(nil, error)
}
}
Assigning to tableview be like:
getPosts(uid: Current.user.userID!) { (posts, error) in
guard error == nil else {
print(error.debugDescription)
return
}
cell.label.text = posts[indexPath.item].content
A long time since I have written iOS code but I have the following Model in an iOS app and works great but now we are finding out that detail is optional and we should allow nil values. How would I adjust the initializer to support this? Sorry, I find the optionals a bit difficult to grasp (concept makes sense - executing it is difficult).
class Item{
var id:Int
var header:String
var detail:String
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let header = dictionary["header"] as? String,
let detail = dictionary["detail"] as? String else {
return nil
}
self.id = id
self.header = header
self.detail = detail
}
and creating:
var items = [Item]()
if let item = Item(dictionary: dictionary) {
self.items.append(item)
}
As in above answer by #AMomchilov, you could assign the value only if it exists in your init method.
But also you could check for the value and then access it like below:
class Item {
var id:Int
var header:String
var detail: String?
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let header = dictionary["header"] as? String else {
return nil
}
self.id = id
self.header = header
self.detail = dictionary["detail"] as? String //if there is value then it will assign else nil will be assigned.
}
}
let dictionary = ["id": 10, "header": "HeaderValue"]
var items = [Item]()
if let item = Item(dictionary: dictionary) {
items.append(item)
print(item.id)
print(item.detail ?? "'detail' is nil for this item")
print(item.header)
}else{
print("No Item created!")
}
And the console is :
10
'detail' is nil for this item
HeaderValue
And if there is `detail' value present then:
let dictionary = ["id": 10, "header": "HeaderValue", "detail":"DetailValue"]
var items = [Item]()
if let item = Item(dictionary: dictionary) {
items.append(item)
print(item.id)
print(item.detail ?? "'detail' is nil for this item")
print(item.header)
}else{
print("No Item created!")
}
Console:
10
DetailValue
HeaderValue
Remove detail from the guard (as now a nil value is acceptable), and assign self.detail to dictionary["detail"] as? String.
class Item {
var id: Int
var header: String
var detail: String?
init?(dictionary: [String: AnyObject]) {
guard let id = dictionary["id"] as? Int,
let header = dictionary["header"] as? String else {
return nil
}
self.id = id
self.header = header
self.detail = dictionary["detail"] as? String
}
Edit: Improved based on Santosh's answer.
I am try to implement search bar to my table view. But I am getting this error in one function. Don't know how to solve??
Value of type '[Businessdata]' has no member 'objectAtIndex'
My code
var arrDict = [Businessdata]()
func searchMethod(notification:NSNotification)
{
isSearching = true;
let text:String = notification.userInfo!["text"] as! String;
arrSearch = [];
for(var i=0;i<arrDict.count;i++)
{
if((arrDict.objectAtIndex(i).objectForKey("name")?.lowercaseString?.containsString(text.lowercaseString)) == true)
{
arrSearch.addObject(arrDict.objectAtIndex(i));
}
}
TableViewList.reloadData();
}
Edited :
import UIKit
class Businessdata: NSObject {
var BusinessName: String?
var BusinessEmail: String?
var BusinessLatLng: NSArray?
var Address: String?
var ContactNumber: String?
var WebsiteUrl: String?
var Specialities:Array<String>?
var StoreImages: NSArray?
var Languages:Array<String>?
var PaymentMethod:Array<String>?
var OpenHours: [NSDictionary]?
var Rating: Float?
var Updated_date: String?
var FeaturedBusiness: NSDictionary?
init(json: NSDictionary)
{
self.BusinessName = json["business_name"] as? String
self.BusinessEmail = json["business_email"] as? String
self.BusinessLatLng = json["latlng"] as? NSArray
self.Address = json["location"] as? String
self.ContactNumber = json["phone_no"] as? String
self.WebsiteUrl = json["website_url"] as? String
self.Specialities = json["specialities"] as? Array<String>
self.StoreImages = json["images"] as? NSArray
self.Languages = json["languages"] as? Array<String>
self.PaymentMethod = json["method_payment"] as? Array<String>
self.OpenHours = json["opening_hours"] as? [NSDictionary]
self.Rating = json["__v"] as? Float
self.Updated_date = json["updated_at"] as? String
if((json["featured_business"]) != nil)
{
self.FeaturedBusiness = json["featured_business"] as? NSDictionary
}
}
}
Here i have posted the Bussinessdata class code.Now how to solve for my problem
Help me out!!
There is no objectAtIndex in an array. You need to do something like this:
arrDict[i]
Instead of
arrDict.objectAtIndex(i)
Edit
As we discussed in the comments this is what you need
if((arrDict[i].name.lowercaseString?.containsString(text.lowercaseString)) == true)
Try this one:
func searchMethod(notification:NSNotification)
{
isSearching = true;
let text:String = notification.userInfo!["text"] as! String;
arrSearch = [];
for(var i=0;i<arrDict.count;i++)
{
if((arrDict[i].BusinessName.lowercaseString?.containsString(text.lowercaseString)) == true)
{
arrSearch.addObject(arrDict[i]);// or arrSearch.append(arrDict[i])
}
}
TableViewList.reloadData();
}
objectAtIndex: belongs to NSArray and objectForKey: belongs to NSDictionary.
Both are not available for the Swift native types.
But there are two fatal issues:
Businessdata is a custom class which does not respond to objectForKey: at all, and there is no property name in the class.
Assuming you are talking about the property BusinessName and the logic is supposed to filter all Businessdata instances whose lowercase string of BusinessName contains the search string you might write
arrSearch = [Businessdata]()
for item in arrDict {
if let businessName = item.BusinessName as? String where businessName.lowercaseString.containsString(text.lowercaseString) {
arrSearch.append(item)
}
}
or swifiter
arrSearch = arrDict.filter({ (item) -> Bool in
if let businessName = item.BusinessName as? String {
return businessName.lowercaseString.containsString(text.lowercaseString)
}
return false
})
And please conform to the naming convention and use always variable names starting with a lowercase letter.
I've created a wizard for user's to sign up using my app, however, I'm having some doubts as to how I should store their information along the way.
I have a User model, which is filled out when users are pulled from the database, however, there are some required fields on this model that wouldn't be filled out if I were to use it as the object that is passed along as the user goes through the the wizard.
Here is my User model:
final class User: NSObject, ResponseObjectSerializable, ResponseCollectionSerializable {
let id: Int
var facebookUID: String?
var email: String
var firstName: String
var lastName: String
var phone: String?
var position: String?
var thumbnail: UIImage?
var timeCreated: CVDate
init?(response: NSHTTPURLResponse, var representation: AnyObject) {
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [String: AnyObject]) {
representation = dataRepresentation
}
self.id = representation.valueForKeyPath("id") as! Int
self.facebookUID = (representation.valueForKeyPath("facebook_UID") as? String)
self.email = (representation.valueForKeyPath("email") as? String) ?? ""
self.firstName = (representation.valueForKeyPath("first_name") as? String) ?? ""
self.lastName = (representation.valueForKeyPath("last_name") as? String) ?? ""
self.phone = (representation.valueForKeyPath("phone") as? String)
self.position = (representation.valueForKeyPath("position_name") as? String)
self.thumbnail = UIImage(named: "ThomasBaldwin")
if let timeCreated = representation.valueForKeyPath("time_created") as? String {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
if let date = formatter.dateFromString(timeCreated) {
self.timeCreated = CVDate(date: date)
} else {
self.timeCreated = CVDate(date: NSDate())
}
} else {
self.timeCreated = CVDate(date: NSDate())
}
}
static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [User] {
var users: [User] = []
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [NSDictionary]) {
if let dataRepresentation = dataRepresentation as? [[String: AnyObject]] {
for userRepresentation in dataRepresentation {
if let user = User(response: response, representation: userRepresentation) {
users.append(user)
}
}
}
}
return users
}
}
Notice the variables id and timeCreated. These are both generated when a new row is added to the Users table in the database, therefore, I wouldn't have values for those variables until the user is actually created.
Also, I would like to add some methods to the model, such as validateUser which will be a method that makes sure all the fields are filled out, and validateEmail which will be a method that makes sure the email is in proper syntax, and so on...
My question is, should I
A. just make those constants optional and add those methods to my current User model
B. make another model called CreateUserModel that only has variables for the information the user will be filling out and put the extra methods in there
UPDATE
I updated my User class to use a dictionary as the storage mechanism and it already looks a lot cleaner. However, the issue that comes to mind is, how will another programmer know which fields he can grab from the User model since I'm not individually storing them as variables anymore. Would they just have to check the DB and look at the structure of the table?
Here's my updated User class:
final class User: NSObject, ResponseObjectSerializable, ResponseCollectionSerializable {
var properties = NSDictionary()
init?(response: NSHTTPURLResponse, representation: AnyObject) {
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [String: AnyObject]) {
properties = dataRepresentation
}
properties = representation as! NSDictionary
}
static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [User] {
var users: [User] = []
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [NSDictionary]) {
if let dataRepresentation = dataRepresentation as? [[String: AnyObject]] {
for userRepresentation in dataRepresentation {
if let user = User(response: response, representation: userRepresentation) {
users.append(user)
}
}
}
}
return users
}
}
I would make them Optionals. That is the beauty of Optionals - you can use nil to mean exactly "no data here".
The other grand strategy that comes to mind is to use a dictionary as the storage mechanism inside your model, because that way either it has a certain key or it doesn't. You could make your User object key-value coding compliant, and thus effectively transparent, by passing keys on to the dictionary.
I'm using Alamofire and am parsing the returned JSON into an object as shown below:
final class User: NSObject, ResponseObjectSerializable {
var id: Int
var facebookUID: String?
var email: String
var firstName: String
var lastName: String
var phone: String?
var position: String?
var timeCreated: CVDate
init?(response: NSHTTPURLResponse, var representation: AnyObject) {
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [String: AnyObject]) {
representation = dataRepresentation
}
if let id = representation.valueForKeyPath("id") as? Int {
self.id = id
} else {
self.id = 0
}
if let facebookUID = representation.valueForKeyPath("facebook_UID") as? String {
self.facebookUID = facebookUID
}
if let email = representation.valueForKeyPath("email") as? String {
self.email = email
} else {
self.email = ""
}
if let firstName = representation.valueForKeyPath("first_name") as? String {
self.firstName = firstName
} else {
self.firstName = ""
}
if let lastName = representation.valueForKeyPath("last_name") as? String {
self.lastName = lastName
} else {
self.lastName = ""
}
if let phone = representation.valueForKeyPath("phone") as? String {
self.phone = phone
}
if let position = representation.valueForKeyPath("position_name") as? String {
self.position = position
}
if let timeCreated = representation.valueForKeyPath("time_created") as? String {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
if let date = formatter.dateFromString(timeCreated) {
self.timeCreated = CVDate(date: date)
} else {
self.timeCreated = CVDate(date: NSDate())
}
} else {
self.timeCreated = CVDate(date: NSDate())
}
}
}
My question is, is this style the best way to decode JSON and set the non-optional instance variables? For example, in this statement:
if let id = representation.valueForKeyPath("id") as? Int {
self.id = id
}
I am required by the compiler to add an else clause and set the id to something otherwise xCode throws an error saying: self.id is not initialized at implicitly generated super.init call.
But at the same time, intializing self.id with a value of 0 is wrong and doesn't help me at all.
But at the same time, intializing self.id with a value of 0 is wrong and doesn't help me at all.
If having a default value for self.id feels wrong, then you should make this property an Optional. That way you wouldn't have to add an else clause:
final class User: NSObject, ResponseObjectSerializable {
var id: Int?
var facebookUID: String?
var email: String
var firstName: String
var lastName: String
var phone: String?
var position: String?
var timeCreated: CVDate
init?(response: NSHTTPURLResponse, var representation: AnyObject) {
if let dataRepresentation = ((representation as! NSDictionary).valueForKey("data") as? [String: AnyObject]) {
representation = dataRepresentation
}
if let id = representation.valueForKeyPath("id") as? Int {
self.id = id
}
...
Update
You said in the comments:
I always need to have an id for the user object though.
If you have to have this id property then the question is moot, you just have to do
let id = representation.valueForKeyPath("id") as! Int
and guarantee earlier that this value will exist.
Because if your object needs an ID, then you can't initialize it anyway if this value doesn't exist and if you don't want a default value.
You could use ?? to provide default values like this:
self.id = (representation.valueForKeyPath("id") as? Int) ?? 0
While the ResponseObjectSerializable code is a great example from the Alamofire project, it's really a better idea to use a dedicated JSON parsing library that has actual error states. This is far better than using optionals to represent error states, or having to provide a default value for every field just in case the response isn't correctly formed.
Although it has a bit of learning curve, I prefer to use Argo for my JSON parsing. Once you get the hang of it it makes JSON parsing practically bulletproof. Better yet, it's easy to integrate with Alamofire, especially version 3 that was released today.
To address your concern about not having an ID being an error condition, you could use a failable initializer. I did that in a recent project. Looks something like this:
let id: Int!
init? (inputJson: NSDictionary) {
if let id = inputJson["id"] as? Int {
self.id = id
} else {
// if we are initing from JSON, there MUST be an id
id = nil
cry(inputJson) // this logs the error
return nil
}
}
Of course, this means your code will need to accept that the initialization of your entire object may fail ..