Get value from array objects with AlamofireObjectMapper/ObjectMapper (swift - iOS) - ios

I'm new with this mapper thing and too confused. I have an API request, given a Title, the API return this:
{
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've created a mappable class for this result:
class SearchResponse: Mappable {
var isSuccess : String?
var searchArray: [Movie]?
var searchCount: String?
required init?(map: Map) {
}
func mapping(map: Map) {
isSuccess <- map["Response"]
searchArray <- map["Search"]
searchCount <- map["totalResults"]
}
}
class Movie: Mappable {
var posterURL : String?
var title : String?
var runtime : String?
var director : String?
var actors : String?
var genre : String?
var plot : String?
var production : String?
var year : String?
var imdbID : String?
var imdbRating : String?
required init?(map: Map) {
}
func mapping(map: Map) {
posterURL <- map["Poster"]
title <- map["Title"]
runtime <- map["Runtime"]
director <- map["Director"]
actors <- map["Actors"]
genre <- map["Genre"]
plot <- map["Plot"]
production <- map["Production"]
year <- map["Year"]
imdbID <- map["imdbID"]
imdbRating <- map["imdbRating"]
}
}
Question: I mapped this movie class like this, but for the search by title I'll only have 4 of this attributes. But for the next search I'll need all of them. Is that right? Or should I create two separate classes to deal with each kind of response?
Ok! I'm showing the result of this search on my SearchTableViewController. Now I want to show more details of this movie (any movie of the "Search" array on this previous response). To do that, the API offers another type of search, that is search by imdbID. So I've created a segue on my SearchTableViewController to get this ID and pass to my MovieViewController (the view that will show these details):
let searchSegue = "segueFromSearch"
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let searchIndex = tableView.indexPathForSelectedRow?.row
let movie = movies?[searchIndex!]
let selectedImdbID = movie?.imdbID
print("|Table View Controler| Segue. IMDB_ID: \(String(describing: selectedImdbID))")
if segue.identifier == searchSegue {
if let destination = segue.destination as? MovieViewController {
destination.imdbID = selectedImdbID!
print("|Table View Controler| Inside of if let. Debug print: I get til here. imdbID = \(selectedImdbID!)")
}
}
}
My function for this API request is:
//The movieSearched variable is the text typed on my searchBar
let URL = "https://www.omdbapi.com/?s=\(movieSearched)&type=movie"
Alamofire.request(URL).responseObject{ (response: DataResponse<SearchResponse>) in
print("response is: \(response)")
switch response.result {
case .success(let value):
let searchResponse = value
self.movies = (searchResponse.searchArray)
self.searchTableView.reloadData()
case .failure(let error):
let alert = UIAlertController(title: "Error", message: "Error 4xx / 5xx: \(error)", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
OK, given this overview of what I have, let's talk about my problem...
When I search by ID, the Json response is that:
{
Actors = "Robbie Amell, Rachael Taylor, Shaun Benson, Gray Powell";
Awards = "N/A";
BoxOffice = "N/A";
Country = "USA, Canada";
DVD = "16 Sep 2016";
Director = "Tony Elliott";
Genre = "Sci-Fi, Thriller";
Language = English;
Metascore = "N/A";
Plot = "Trapped in a lab and stuck in a time loop, a disoriented couple fends off masked raiders while harboring a new energy source that could save humanity.";
Poster = "https://images-na.ssl-images-amazon.com/images/M/MV5BMjAxODQ2MzkyMV5BMl5BanBnXkFtZTgwNjU3MTE5OTE#._V1_SX300.jpg";
Production = Netflix;
Rated = "N/A";
Ratings = (
{
Source = "Internet Movie Database";
Value = "6.4/10";
},
{
Source = "Rotten Tomatoes";
Value = "60%";
}
);
Released = "16 Sep 2016";
Response = True;
Runtime = "88 min";
Title = ARQ;
Type = movie;
Website = "N/A";
Writer = "Tony Elliott";
Year = 2016;
imdbID = tt5640450;
imdbRating = "6.4";
imdbVotes = "17,481";
}
My problem
I did this alamofire request for the search by ID:
func getMovieById() {
let URL = "https://www.omdbapi.com/?i=\(String(describing: imdbID!)))"
Alamofire.request(URL).responseObject{ (response: DataResponse<SearchResponse>) in
print("|MovieController| Response is: \(response)")
let Result = response.result.value
print("Result for search by id: \(String(describing: Result!.searchArray))")
// Have to get the values here, right?
}
}
Obviously, I'm not getting the data that I want it here. So...
Questions:
How can I get the values of the Json["Search"] with mappable classes?
Do I have to change the classes that I have? If yes, how and why?
I'm too confused by so many layers. Also, I'm beginner with swift and I'm using this ObjectMapper for the first time. Sorry for so much code here, but I think I had to explain my scenario.

You have to map each property with correct data type of that property. One of the objects in your response contains a Boolean value ( For ex. "Response"), but you are declaring it as a String. We have to match the data type of the property exactly, else the object will be nil and will not be mapped.
Also search by id response does not match your mapper class.
let Result = response.result.value is wrong. response.result.value would yield you SearchResponse object.
Bottom line
You have to get the mapping part right first. Any mismatching types will be not be mapped. Using response object will give you the whole object with all the mappings instead of the JSON obviously. So it should be: let movie = response.result.value. Then you access movie's properties like for ex. movie.actors

Related

Displaying PyMongo and MongoDB Realm Results in Swift Code

I'm using MongoDB Realm and the Atlas UI for a Swift app mapping US States with congressional districts (I've used Region to refer to states to avoid confusion with the swift keyword #State), so each region should have a list of districts. I can insert starting data on regions and districts into the Atlas UI using PyMongo and display those states and districts individually when pulling realm results in my swift app, but when I pull the region it does not show any associated districts in its list of districts.
For reference, here is how I defined my region and district model
class Region_Preset: Object {
#objc dynamic var id_number = 0
#objc dynamic var _id = ObjectId.generate()
#objc dynamic var _partition: String = ""
#objc dynamic var Abbreviation: String = ""
#objc dynamic var Name: String = ""
let Population: Int? = 0
let Districts = List<District_Preset>()
let Senators = List<PoliticianUnverified_Preset>()
let Representatives = List<PoliticianUnverified_Preset>()
let Voters = List<Voter_Preset>()
override class func primaryKey() -> String? {
return "_id"
}
convenience init(id_number: Int, partition: String, Abbreviation: String, Name: String) {
self.init()
self.id_number = id_number
self._partition = partition
self.Abbreviation = Abbreviation
self.Name = Name
}
}
class District_Preset: Object {
#objc dynamic var id_number = 0
#objc dynamic var district_id: String = ""
#objc dynamic var _id = ObjectId.generate()
#objc dynamic var _partition: String = ""
#objc dynamic var Number: String = ""
let Population: Int? = 0
let Zipcodes = List<Zipcode_Preset>()
let Voters = List<Voter_Preset>()
#objc dynamic var Representative: PoliticianUnverified_Preset?
override class func primaryKey() -> String? {
return "_id"
}
convenience init(id_number: Int, district_id: String, partition: String, Number: String, Representative: PoliticianUnverified_Preset) {
self.init()
self.id_number = id_number
self.district_id = district_id
self._partition = partition
self.Number = Number
self.Representative = Representative
}
}
And here are the Pymongo functions
def upload_regions():
for region in region_file:
new_region = { "id_number": region[0], "_partition": PARTITION_VALUE, "Abbreviation": region[1], "Name": region[2]}
db.Region_Preset.insert_one(new_region)
def upload_districts():
for district in district_file:
new_district = { "id_number": district[0], "district_id": district[1], "_partition": PARTITION_VALUE, "Number": str(district[2]) }
db.District_Preset.insert_one(new_district)
returned_district = db.District_Preset.find_one({"id_number": district[0]})
db.Region_Preset.update_one({"id_number": district[4]}, {"$push": {"Districts": returned_district}})
And like I said, the results of the Pymongo insertion appear in the Atlas UI:
As you can see, both that regions and districts appear inserted in Atlas UI and even (from the 2nd picture) that the districts appear to be adequately inserted into the region's array of districts.
When I pull this data and print it in my Swift code, however, as shown in this code
Realm.asyncOpen(configuration: userconfiguration) { result in
switch result {
case .failure(let error):
print("Failed to open realm: \(error.localizedDescription)")
// handle error
case .success(let newrealm):
print("Successfully opened realm: \(newrealm)")
// Use realm
if let user_id = app.currentUser?.id {
let regions = newrealm.objects(Region_Preset.self)
print(regions)
I get the following output from the print statement
Results<Region_Preset> <0x7f931b325330> (
[0] Region_Preset {
id_number = 1;
_id = 5fe0f0c6474d38a0a1c11166;
_partition = vote;
Abbreviation = NY;
Name = New York;
Districts = List<District_Preset> <0x60000095b520> (
);
Senators = List<PoliticianUnverified_Preset> <0x6000009440a0> (
);
Representatives = List<PoliticianUnverified_Preset> <0x6000009441e0> (
);
Voters = List<Voter_Preset> <0x600000944280> (
);
}
)
Which shows the existence of the region in Atlas UI (and the districts when I print that instead individually), but does not show any districts associated with the region.
Am I doing something wrong here in the python code, the Atlas UI, the swift code or somewhere else? I assume this functionality should definitely be supported by Pymongo/MongoDB Realm but I can just not get it to show - any help is greatly appreciated!

Cannot use mutating member on immutable value: 'n' is a 'let' constant

I am getting data from a website using rss. I want to set those datas to the variables in struct but I am getting an error in for loop.
struct News {
var title: String
var link: String
}
class HaberTableViewController: UITableViewController, XMLParserDelegate {
var NewsArray:[News] = []
override func viewDidLoad() {
let url = "http://www.ensonhaber.com/rss/ensonhaber.xml"
Alamofire.request(url).responseRSS() { (response) -> Void in
if let feed: RSSFeed = response.result.value {
for item in feed.items {
print(item.title!)
for n in self.NewsArray
{
n.title.append(item.title)
n.link.append(item.link)
}
}
}
}
}
.
.
.
}
I think you are trying to populate the NewArray array with new News instances using values from feed.items. If that's the case then your code should be:
if let feed: RSSFeed = response.result.value {
for item in feed.items {
if let title = item.title, let link = item.link {
let news = News(title: title, link: link)
NewsArray.append(news)
}
}
}
Note that this also deals with item.title (and presumably item.link) being optional.
Please note that variable names should start with lowercase letters so NewsArray should be newsArray.

Connect Google Places Api ( rating stars)

The basic idea of the ViewController is to show the name of the restaurant and its rating stars. The name of the restaurants comes from my API in JSON and the rating is from Google places API.
I first get the names from the server:
func getData (){
let urlStr = "http://myserver.com/api/"
let url = URL(string: urlStr)
Alamofire.request(url!, method: .get ,encoding: URLEncoding.default).responseJSON { response in
if let value : AnyObject = response.result.value as AnyObject {
let shops = JSON(value)
for (key,subJson):(String, JSON) in shops["families"] {
if subJson["disable"] == "0"{
let shopName = subJson["name"].stringValue
self.performSereach(shopName: shopName)//do search
// take the name and
var info = Shops(shopname: shopName,
Logo: logoString, family_id: familiy_id , design : designImage)
// save the data to show in TableVIewCell
Now I perform a search in Google places with the link
https://maps.googleapis.com/maps/api/place/textsearch/json?query=\(shopName)+in+SanFrancisco&key=MyKey
The function to do the search :
func performSereach(shopName : String ) {
let formattedString = shopName.replacingOccurrences(of: " ", with: "+")
let urlStr = "https://maps.googleapis.com/maps/api/place/textsearch/json?query=\(formattedString)+in+ SanFrancisco&key=MyKey"
Alamofire.request(urlStr).responseJSON { response in
if let value : AnyObject = response.result.value as AnyObject {
let data = JSON(value)
for (key,subJson):(String, JSON) in data["results"] {
print(subJson["rating"])
print(subJson["name"])
But when I do this I feel like I'm doing it wrong because:
1- There are so many Starbucks in my area so the response will have more than one rating ( in this case I would like to take the first one) And the print statements prints all the Starbucks and its rating
2- how can I connect the shopName with its rating star at the end of the search in Shops ?
Shops is:
import Foundation
class Shops {
var _familiy_id: String?
var _logo : String?
var _design: String?
var _rate : Int
var _shopname : String?
var rate : Int{
return _rate
}
var familiy_id : String{
return _familiy_id!
}
var shopname : String{
return _shopname!
}
var Logo : String{
return _logo!
}
var design : String {
return _design!
}
init(shopname : String , Logo : String , family_id : String, design : String , rate : Int) {
self._shopname = shopname
self._logo = Logo
self._familiy_id = family_id
self._design = design
self._rate = rate
}
}

How to parse and categorize JSON response from AlamoFire in xcode

I am trying to get NBA standings from this website https://erikberg.com/api
To do this, I am using AlmoFire to make a Get request:
Alamofire.request(.GET, "https://erikberg.com/nba/standings.json")
.responseJSON { response in
print(response.2.value)
}
What this is returning is a response array with a NSURLRequest at index 0, an NSHTTPURLResponse at index 1, and a result of type AnyObject at index 2. The AnyObject, when printed, results in this (I'm only showing one team because the array is very long):
Optional({
standing = (
{
"away_lost" = 14;
"away_won" = 21;
conference = EAST;
"conference_lost" = 13;
"conference_won" = 29;
division = CEN;
"first_name" = Cleveland;
"games_back" = 0;
"games_played" = 71;
"home_lost" = 6;
"home_won" = 30;
"last_five" = "4-1";
"last_name" = Cavaliers;
"last_ten" = "7-3";
lost = 20;
"ordinal_rank" = 1st;
"playoff_seed" = 1;
"point_differential" = 437;
"point_differential_per_game" = "6.2";
"points_against" = 6948;
"points_allowed_per_game" = "97.9";
"points_for" = 7385;
"points_scored_per_game" = "104.0";
rank = 1;
streak = W2;
"streak_total" = 2;
"streak_type" = win;
"team_id" = "cleveland-cavaliers";
"win_percentage" = ".718";
won = 51;
},exc...
I would like to know how I can parse this data so I can extract and categorize each team based on their standings.
Thank you
You can use ObjectMapper to parse JSON. Make a Standing class which contains all the JSON to object mapping code.
class Standing: Mappable {
var awayLost: Int?
var awayWon: Int?
..... // Define all variables
required init?(_ map: Map) {}
// This function is used to map all variables to corresponding JSON strings
func mapping(map: Map) {
awayLost <- map["away_lost"]
awayWon <- map["away_won"]
...
}
Similarly, make a parent class to hold array of standing.
class ParentJson: Mappable {
var standingsDate: String?
var standing: [Standing]
}
func mapping(map: Map) {
standing <- map["standing"]
}
And then in your alamofire response, map the corresponding class
Alamofire.request(.GET, "https://erikberg.com/nba/standings.json")
.responseJSON { response in
let parentJson = Mapper<ParentJson>().map(response.2.value)
let standingsArray: [Standing] = parentJson.standing
standingArray will now have all the data required to categorize.
I use the SwiftyJSON package for all things JSON related. Then, you can use this idiom
.responseJSON { response in
switch response.result {
case .Success:
if let value = response.result.value {
let json = JSON(value)
if let datum = json["field"].string { // Depending on expected type; see docs
}
case .Failure(let error):
print(error)
}

swift: cast incoming json array to dictionary and object

I am toying around/learning Swift and JSON atm and have problems correctly casting around my responses.
So, what I am trying to achieve is displaying a list of "posts" via Alamofire request. I have no problems displaying "first-level" json items like the post-ID or the "message", but when it comes to the author array I am kinda lost.
the json response comes in as an Array with no name, hence the first [
[
-{
__v: 1,
_id: "54428691a728c80424166ffb",
createDate: "2014-10-18T17:26:15.317Z",
message: "shshshshshshshhshshs",
-author: [
-{
_id: "54428691a728c80424166ffa",
userId: "543270679de5893d1acea11e",
userName: "foo"
}
]
}
Here is my corresponding VC:
Alamofire.request(.GET, "\(CurrentConfiguration.serverURL)/api/posts/\(CurrentConfiguration.currentUser.id)/newsfeed/\(CurrentConfiguration.currentMode)",encoding:.JSON)
.validate()
.responseJSON {(request, response, jsonData, error) in
let JSON = jsonData as? NSArray
self.loadPosts(JSON!)
}
tableView.delegate = self
tableView.dataSource = self
}
func loadPosts(posts:NSArray) {
for post in posts {
let id = post["_id"]! as NSString!
let message = post["message"]! as NSString!
var authorArray = post["author"]! as? [Author]!
println(authorArray)
var author:Author = Author()
author.userName = "TEST ME"
var postObj:Post = Post()
postObj.id = id
postObj.message = message
postObj.author = author
uppDatesCollection.append(postObj)
}
println(self.uppDatesCollection.count)
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
my Models for Post
class Post {
var id:String!
var message:String!
var createDate: NSDate!
var author:Array<Author>!
init () {
}
}
and Author
class Author {
var id:String?
var userId:String?
var userName:String?
init () {
}
What is the best Approach here? Should you recast the returning Array as a Dictionary and then Access it via .valueforkey? Do you somehow iterate over the array to get this stuff out?
Obviously you can not say
author.name = authorArray[3] as String
Let's say you had Post class like so:
class Post : Printable {
var identifier:String!
var message:String!
var createDate: NSDate!
var authors:[Author]!
var description: String { return "<Post; identifier = \(identifier); message = \(message); createDate = \(createDate); authors = \(authors)" }
}
Your JSON has only one author in it, but given that it appears to be an array in the JSON, I assume it should be an array in the object model, too, as shown above. The Author class might be defined as so:
class Author : Printable {
var identifier:String!
var userId:String!
var userName:String!
var description: String { return "<Author; identifier = \(identifier); userId = \(userId); userName = \(userName)>" }
}
I wasn't sure why you made some of your optionals implicitly unwrapped (defined with !) and others not (defined with ?), so I just made them all implicitly unwrapped. Adjust as appropriate to your business rules.
Also, let's say your JSON looked like so (I wasn't quite sure what to make of the - in the JSON in your question, so I cleaned it up and added a second author):
[
{
"__v": 1,
"_id": "54428691a728c80424166ffb",
"createDate": "2014-10-18T17:26:15.317Z",
"message": "shshshshshshshhshshs",
"author": [
{
"_id": "54428691a728c80424166ffa",
"userId": "543270679de5893d1acea11e",
"userName": "foo"
},
{
"_id": "8434059834590834590834fa",
"userId": "345903459034594355cea11e",
"userName": "bar"
}
]
}
]
Then the routine to parse it might look like:
func loadPosts(postsJSON: NSArray) {
var posts = [Post]()
for postDictionary in postsJSON {
let post = Post()
let createDateString = postDictionary["createDate"] as String
post.message = postDictionary["message"] as String
post.identifier = postDictionary["_id"] as String
post.createDate = createDateString.rfc3339Date()
if let authorsArray = postDictionary["author"] as NSArray? {
var authors = [Author]()
for authorDictionary in authorsArray {
let author = Author()
author.userId = authorDictionary["userId"] as String
author.userName = authorDictionary["userName"] as String
author.identifier = authorDictionary["_id"] as String
authors.append(author)
}
post.authors = authors
}
posts.append(post)
}
// obviously, do something with `posts` array here, either setting some class var, return it, whatever
}
And this is my conversion routine from String to NSDate:
extension String {
/// Get NSDate from RFC 3339/ISO 8601 string representation of the date.
///
/// For more information, see:
///
/// https://developer.apple.com/library/ios/qa/qa1480/_index.html
///
/// :returns: Return date from RFC 3339 string representation
func rfc3339Date() -> NSDate? {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
return formatter.dateFromString(self)
}
}

Resources