I'm trying to use reflection in Swift with Core Data entities, but when I execute the following code, my reflected var has only a reference for a super class, it didn't have a reference for any of it's attributes.
func printProperties() {
let mirror = reflect(self)
for var i = 0; i < mirror.count; i++ {
let (propertyName, childMirror) = mirror[i]
println("property name: \(propertyName)")
println("property value: \(childMirror.value)")
}
}
Does anyone have some idea why this happens?
Update: As suggested by Anderson in his answer I tried another approach and ended up with this code:
func loadFromJson(json: JSON) {
for attributeKey in self.entity.attributesByName.keys {
let attributeDescription = self.entity.propertiesByName[attributeKey]!
as! NSAttributeDescription
let attributeClassName = attributeDescription.attributeValueClassName
let jsonValue = json[(attributeKey as! String)]
var attributeValue: AnyObject? = attributeDescription.defaultValue
if jsonValue.type != .Null && attributeClassName != nil {
if attributeClassName == "NSNumber" {
attributeValue = jsonValue.number!
} else if attributeClassName == "NSString" {
attributeValue = jsonValue.string!
}
}
setValue(attributeValue, forKey: (attributeKey as! String))
}
}
I believe that this code can help you.
I wrote this extension to make a dictionary from a NSmanagedObject and it accesses all attributes and values of the object.
extension NSManagedObject {
func toDict() -> Dictionary<String, AnyObject>! {
let attributes = self.entity.attributesByName.keys
let relationships = self.entity.relationshipsByName.keys
var dict: [String: AnyObject] = [String: AnyObject]()
var dateFormater = NSDateFormatter()
dateFormater.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
for attribute in attributes {
if self.entity.propertiesByName[attribute]!.attributeValueClassName != nil && self.entity.propertiesByName[attribute]!.attributeValueClassName == "NSDate" {
let value: AnyObject? = self.valueForKey(attribute as! String)
if value != nil {
dict[attribute as! String] = dateFormater.stringFromDate(value as! NSDate)
} else {
dict[attribute as! String] = ""
}
} else {
let value: AnyObject? = self.valueForKey(attribute as! String)
dict[attribute as! String] = value
}
}
for attribute in relationships {
let relationship: NSManagedObject = self.valueForKey(attribute as! String) as! NSManagedObject
let value = relationship.valueForKey("key") as! String
dict[attribute as! String] = value
}
return dict
}
}
I hope to have helped you.
Related
I have this function searchMoviesOnJson:
func searchMoviesOnJson(imdbTitle: String, completionHandler: #escaping (Dictionary<String, Any>?) -> ()) {
let urlByName: String = "https://www.omdbapi.com/?s=\(imdbTitle)&type=movie"
//returns a list of movies that contains the title searched
//------------------------------------------------
Alamofire.request(urlByName).responseJSON {
response in
switch response.result {
case .success(let value):
let moviesJSON = value
completionHandler(moviesJSON as? Dictionary<String, Any>)
case .failure(_):
completionHandler(nil)
}
}
//------------------------------------------------
}
That gives me this response from api (e.g.: imdbTitle = "arq"):
{
Response = True;
Search = (
{
Poster = "https://images-na.ssl-images-amazon.com/images/M/MV5BMjAxODQ2MzkyMV5BMl5BanBnXkFtZTgwNjU3MTE5OTE#._V1_SX300.jpg";
Title = ARQ;
Type = movie;
Year = 2016;
imdbID = tt5640450;
},
{
Poster = "N/A";
Title = Arq;
Type = movie;
Year = 2011;
imdbID = tt2141601;
},
{
Poster = "N/A";
Title = "A.R.Q.";
Type = movie;
Year = 2015;
imdbID = tt3829612;
}
);
totalResults = 3;
}
So I created a this class MovieByTitle
class MovieByTitle {
var poster : String?
var title : String?
var year : Int?
var imdbID : String?
init(poster: String?, title: String?, year: Int?, imdbID: String?) {
//validation
if let isPoster = poster { self.poster = isPoster }
else { self.poster = nil }
if let isTitle = title { self.title = isTitle }
else { self.title = nil }
if let isYear = year { self.year = isYear }
else { self.year = nil }
if let isImdbID = imdbID { self.imdbID = isImdbID }
else { self.imdbID = nil }
}
}
And now my doubt, I also create this MovieDAO:
class MovieDao {
func getMovies(imdbTitle: String, completionHandler: #escaping (([MovieByTitle]) -> ())) {
//function that conects to the api
searchMoviesOnJson(imdbTitle: imdbTitle, completionHandler: {
moviesJSON in
//array to keep the attributes received by the dictionary
var moviesArray = [MovieByTitle]()
//searchResults is the response from my request as an array
if let searchResults = moviesJSON?["Search"] as? NSArray{
for searchResult in searchResults {
let movieResult = searchResult as! Dictionary<String,Any>
let movieDetail = MovieByTitle()
movieDetail.poster = movieResult["Poster"] as? String
movieDetail.title = movieResult["Title"] as? String
movieDetail.year = movieResult["Year"] as? Int
movieDetail.imdbID = movieResult["imdbID"] as? String
moviesArray.append(movieDetail)
}
}
})
}
}
But the xcode returns an error in line:
let movieDetail = MovieByTitle()
Error message: missing argument for parameter 'poster' in call (and so on with the others)
What is the right sintax for that? What is the better way to cast my dictionary response as an object?
You MovieByTitle init function requires 4 parameters that are missing.
Solution 1: Add a secondary init:
init() {}
Solution 2: Define existing init parameters as optional by giving them default values:
init(poster: String? = nil, title: String? = nil, year: Int? = nil, imdbID: String? = nil)
Solution 3: Call the existing init with the parameters it needs:
let movieDetail = MovieByTitle(poster: movieResult["Poster"] as? String, title: movieResult["Title"] as? String, year: movieResult["Year"] as? Int, imdbID: movieResult["imdbID"] as? String)
Your init function requires 4 parameters. You haven't included any. Try the following
let poster = movieResult["Poster"] as? String
let title = movieResult["Title"] as? String
let year = movieResult["Year"] as? Int
let imdbID = movieResult["imdbID"] as? String
let movieDetail = MovieByTitle(poster:poster, title:title, year:year, imdbID:imdbDB)
In my object Dish xxx.Dish, I want to access the Choice class price and name to display but I failed. dish data load from web API and I tested data loaded success full and put the data to the object dish and it return the object list to viewcontroller to load tableview.
Output of printed console
Optional([xxx.Dish, xxx.Dish])
and in the dish class before append optionList?.append(_obj)
xxx.DishOption
Anyone helps me how can I do that .. I am new to swift and is it right way to implement? Please suggest me?
class Dish {
let dishId : String
var optionList : [DishOption]?
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let dishId = resposne["dishId"] as? String else {
return nil
}
self.dishId = dishId
if let objs = resposne["options"] as? [[String: AnyObject]]{
for obj in objs {
if let _obj = DishOption(fromAPIResponse: obj){
optionList?.append(_obj)
}
}
}
}
class DishOption {
let optionId : String
var choiceList : [Choice]?
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let optionId = resposne["optionId"] as? String else {
return nil
}
self.optionId = optionId
if let objs = resposne["choices"] as? [[String: AnyObject]]{
for obj in objs {
if let _obj = Choice(fromAPIResponse: obj){
choiceList?.append(_obj)
}
}
}
}
}
class Choice{
let choiceId : String
let name : String
let price : String
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let choiceId = resposne["choiceId"] as? String ,
let name = resposne["name"] as? String,
let price = resposne["price"] as? String else {
return nil
}
self.choiceId = choiceId
self.name = name
self.price = price
}
}
UPDATE:
var dishMenuList = [Dish]()
guard let objs = json["menu_list"] as? [[String : AnyObject]] else {
return
}
for obj in objs {
if let _obj = Dish(fromAPIResponse: obj){
print(_obj.optionList) //always print nil
if let options = _obj.optionList {
for data in options {
print(data.displayAsButton)
}
}
dishMenuList.append(_obj)
}
}
From what I can see, you are never initializing both the optionList and choiceList arrays. It would be better to initialize them as empty arrays:
class Dish {
let dishId : String
var optionList = [DishOption]()
...
optionList.append(_obj)
This is the reason that you cannot see any options. Since the optionList is still nil, the line optionList?.append(_obj) does not execute.
Hello I am trying to parse through this json file: http://pastebin.com/TCdkJnvZ
Here is the class I made of the information I want to parse out:
public class Recipe: NSObject {
var recipeID : NSNumber?
var categoryName : String?
var ingredients : [Int : Ingredients]?
var nutrition : [Nutrition]?
var imageName : String?
var instructions : [Int : String]?
}
class Ingredients : NSObject {
var id : NSNumber?
var name : String?
var quantity: NSNumber?
var unit : String?
}
class Nutrition : NSObject {
var serving : String?
var calories : NSNumber?
var fat : String?
var carbs : NSNumber?
}
This image is the current issue.. I am really not sure what I am doing wrong here.. so if I can get any help on fixing my logic/issue it would be appreciated.
func parseToJSON(data: Any) {
// add meals to here
var recipes : [Recipe]
// single meals here
var meals : Recipe
do {
if let json = try JSONSerialization.jsonObject(with: data as! Data) as? [String: Any],
meals.recipeID == json["recipeID"] as! NSNumber? ,
meals.imageName == json["ImageURL"] as! String?,
//meals.instructions == meals.parseInstructions(instructions: (json["Instructions"] as! String)),
meals.categoryName == "Meals" ,
let ingredients = json["Ingredients"] as! [[String: Any]]? {
for items in ingredients {
var i : Int = 0
var groceryItems : Ingredients
groceryItems.id = items["IngredientID"] as? NSNumber
groceryItems.name = items["Name"] as? String
groceryItems.quantity = items["Quantity"] as? NSNumber
groceryItems.unit = items["Unit"] as? String
meals.ingredients?[i] = groceryItems
}
};
let nutritionInfo = json["NutritionInfo"] as! [[String: Any]]? {
for items in nutritionInfo {
var nutrition : Nutrition
nutrition.serving = items["SingularYieldUnit"] as? String
nutrition.calories = items["TotalCalories"] as? NSNumber
nutrition.fat = items["TotalFat"] as? String
nutrition.carbs = items["TotalCarbs"] as NSNumber
meals.nutrition = nutrition
}
};
}
catch{
}
}
It looks like you have a variety of syntax errors, but the compiler can only show one issue at a time. I've cleaned up the code slightly for you, which should push you in the right direction. I can't completely fix it because I don't know what your exact intentions are.
Here is the updated parseToJSON function:
func parseToJSON(data: Any) {
let meals = Recipe()
do {
if let json = try JSONSerialization.jsonObject(with: data as! Data) as? [String: Any] {
meals.recipeID == json["recipeID"] as! NSNumber?
meals.imageName == json["ImageURL"] as! String?
//meals.instructions == meals.parseInstructions(instructions: (json["Instructions"] as! String)),
meals.categoryName == "Meals"
if let ingredients = json["Ingredients"] as! [[String: Any]]? {
for items in ingredients {
let groceryItems = Ingredients()
groceryItems.id = items["IngredientID"] as? NSNumber
groceryItems.name = items["Name"] as? String
groceryItems.quantity = items["Quantity"] as? NSNumber
groceryItems.unit = items["Unit"] as? String
meals.ingredients?.append(groceryItems)
}
}
if let nutritionInfo = json["NutritionInfo"] as! [[String: Any]]? {
for items in nutritionInfo {
let nutrition = Nutrition()
nutrition.serving = items["SingularYieldUnit"] as? String
nutrition.calories = items["TotalCalories"] as? NSNumber
nutrition.fat = items["TotalFat"] as? String
nutrition.carbs = items["TotalCarbs"] as? NSNumber
meals.nutrition?.append(nutrition)
}
}
}
}
catch{
}
}
I also changed the Recipe object's ingredients property to:
var ingredients : [Ingredients]?
The main issue was that a lot of your code was inside of an if let expression and your indentation was off so you couldn't as easily tell.
I have a bunch of users on my message app, fetching my "messages" class I need to get sender's first and last name and profile image from their Parse profile on my app in order to show them in each message on the tableView.
I just want to show in tableView the name of users in class "messages" contained in the column "sender" wich contains pointers to PFUsers (of which I need "first_name", "last_name", "profile_picture")
my users class
my message class
update!
can't get where is the problem, if I downCast something, something else must be changed. here the updated code:
findTimeLineDataQuery.includeKey("sender")
findTimeLineDataQuery.findObjectsInBackgroundWithBlock({
(objects : [AnyObject]?, error : NSError?) in
if let objects = objects where error == nil {
var chats:(timelinechatsData: [String], chatsDate: [NSDate]) = ([], []) //date might be NSDate
var message: (messageObjts: [String], messageSender: [String]) = ([], [])
var datesToString: [String] {
get {
var stringDates:[String] = []
let formatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-yyyy" //change the format as you wish
for date in dates {
stringDates.append(formatter.stringFromDate(date))
}
return stringDates
}
}
for singleObject in objects {
if let user = singleObject["sender"] as? PFObject {
let first_name = user["first_name"] as! String
let last_name = user["last_name"] as! String
//Cast it to how you saved it. I can't see it from the picture so I assumed you saved it as NSData
let picture = user["picture"] as! NSData
self.picturesProfilesArray.append(picture)
//once you fetch the user data, you can save it in a dictionary or however you want and later call it from cellForRowAtIndex
}
if let stringData = singleObject["message"] as? String {
chats.timelinechatsData.append(stringData)
if let messageDate = singleObject.createdAt {
chats.chatsDate.append(messageDate!)
}
}
//new
if let messageObject = singleObject["messageObject"] as? String {
message.messageObjts.append(messageObject)
}
//new senderNickname
if let messageSender = singleObject["senderNickname"] as? String {
message.messageSender.append(messageSender)
}
}
//update self
dispatch_async(dispatch_get_main_queue()) {
self.timelineChatsDataArray += chats.timelinechatsData
self.chatsDateArray += datesToString
self.messageObjectArray += message.messageObjts
self.messageSenderArray += message.messageSender
self.tableView.reloadData()
}
}
})
changes
so my
var chatsDateArray : [NSDate] = []
and
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy HH:mm"
let useDate = dateFormatter.stringFromDate(self.chatsDateArray[indexPath.row])
cell.dateMessageLabel.text = useDate
should become
var chatsDateArray : [String] = []
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy HH:mm"
let useDate = String(self.chatsDateArray[indexPath.row])
cell.dateMessageLabel.text = useDate
but this causes:
Initialization of immutable value 'first_name' was never used; consider replacing with assignment to '_' or removing it
Initialization of immutable value 'last_name' was never used; consider replacing with assignment to '_' or removing it
this disappears by changing var chat (chatsDate) back to string
, but this causes
Cannot convert value of type 'NSDate' to expected argument type 'String'
on
if let messageDate = singleObject.createdAt {
chats.chatsDate.append(messageDate!)
}
}
You can use 'includeKey' to access pointer values of related classes
findTimeLineDataQuery.includeKey("sender")
findTimeLineDataQuery.findObjectsInBackgroundWithBlock({
(objects : [AnyObject]?, error : NSError?) in
if let objects = objects where error == nil {
var chats:(timelinechatsData: [String], chatsDate: [NSDate]) = ([], []) //date might be NSDate
var message: (messageObjts: [String], messageSender: [String]) = ([], [])
var datesToString: [String] {
get {
var stringDates:[String] = []
let formatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-yyyy" //change the format as you wish
for date in dates {
stringDates.append(formatter.stringFromDate(date))
}
return stringDates
}
}
for singleObject in objects {
if let user = singleObject["sender"] as! PFObject {
let first_name = user["first_name"] as! String
let last_name = user["last_name"] as! String
//Cast it to how you saved it. I can't see it from the picture so I assumed you saved it as NSData
let picture = user["picture"] as! NSData
picturesProfilesArray.append(picture)
//once you fetch the user data, you can save it in a dictionary or however you want and later call it from cellForRowAtIndex
}
if let stringData = singleObject["message"] as? String {
chats.timelinechatsData.append(stringData)
if let messageDate = singleObject.createdAt {
chats.chatsDate.append(messageDate)
}
}
//new
if let messageObject = singleObject["messageObject"] as? String {
message.messageObjts.append(messageObject)
}
//new senderNickname
if let messageSender = singleObject["senderNickname"] as? String {
message.messageSender.append(messageSender)
}
}
//update self
dispatch_async(dispatch_get_main_queue()) {
self.timelineChatsDataArray += chats.timelinechatsData
self.chatsDateArray += datesToString
self.messageObjectArray += message.messageObjts
self.messageSenderArray += message.messageSender
self.tableView.reloadData()
}
}
})
I have a struct like
struct Channel {
var id : Int = 0
var name = ""
}
and I am getting json from URL as
{"channel_list":[{"channel_id":0,"channel_name":"test1"},{"channel_id":0,"channel_name":"test2"}]}
However I am not able to get data as
func parseJson(anyObj:AnyObject) -> Array<Channel>{
var list:Array<Channel> = []
if anyObj is Array<AnyObject> {
var b:Channel = Channel()
for json in anyObj as! Array<AnyObject>{
b.id = (json["channel_id"] as AnyObject? as? Int) ?? 0
b.name = (json["channel_name"] as AnyObject? as? String) ?? ""
list.append(b)
}
}
return list
}
//read code
let anyObj: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0),error: nil) as AnyObject?
println(anyObj)
if let myobj=anyObj["channel_list"] as AnyObject {
self.Channellist=self.parseJson(anyObj!)
}
Whats wrong with this?
First, instead of using AnyObject, you should cast the JSON response as a Dictionary: [NSObject:AnyObject] then safe cast the result of anyObj["channel_list"] to an Array of Dictionaries [[NSObject:AnyObject]], because this is your JSON response format.
Then you need to use this type in your parseJSON function. We're also simplifying it while we're at it, because there's no need to do weird castings anymore.
Also, you were passing the wrong argument to your function (you used anyObj instead of myObj).
struct Channel {
var id : Int = 0
var name = ""
}
func parseJson(anyObj: [[NSObject:AnyObject]]) -> Array<Channel>{
var list: Array<Channel> = []
var b: Channel = Channel()
for json in anyObj {
b.id = (json["channel_id"] as? Int) ?? 0
b.name = (json["channel_name"] as? String) ?? ""
list.append(b)
}
return list
}
if let anyObj = NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions(0),error: nil) as? [NSObject:AnyObject] {
if let myobj = anyObj["channel_list"] as? [[NSObject:AnyObject]] {
self.Channellist=self.parseJson(myobj)
}
}
There's still room for improvement: you could create an initializer for your Struct, for example, and also create a typealias for the response types, use map to create the list, etc.
Here's how I would do it with Swift 2:
struct Channel {
var id : Int
var name: String
init?(JSON: [NSObject: AnyObject]?) {
guard let channelID = json["channel_id"] as? Int, let channelName = json["channel_name"] as? String
else { name = ""; id = 0; return nil }
name = channelName
id = channelID
}
}
func parseJSON(array: [[NSObject:AnyObject]]) -> [Channel?] {
return array.map { Channel(JSON: $0) }
// If you don't want to return optionals to channel you can do this instead:
// return array.map { Channel(JSON: $0) }.filter { $0 != nil }.map { $0! }
}
// And in the caller
do {
guard let dict = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [NSObject : AnyObject]
else { throw NSError(domain ... // Setup error saying JSON wasn't parsed. }
guard let arrayContents = dict["channel_list"] as? [[NSObject:AnyObject]]
else { throw NSError(domain ... // Setup error saying array wasn't found. }
let channels = parseJSON(arrayContents)
}
catch {
print(error)
}