Trying to append JSON items to array but not working - ios

I am trying to serialize a GET request then make a movie object, then appending that movie object to a movies array which I will use to show info on the UI.
I am new and have struggled with this problem for some time now :(
If you look at the self.movies?.append(movie) shouldnt that work? I dont see any reasons as to when i try to get the first item i get fatal error index out of bounds which means I the Array is not filled yet.... Dont know what i am doing wrong :(
import UIKit
class ViewController: UIViewController {
var movies:[Movie]? = []
#IBOutlet weak var uiMovieTitle: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
getMovieData()
print(self.movies?.count)
setUI()
}
#IBAction func yesBtn(_ sender: UIButton) {
print(movies?[5].title ?? String())
}
#IBAction func seenBtn(_ sender: UIButton) {
}
#IBAction func noBtn(_ sender: UIButton) {
}
#IBOutlet weak var moviePoster: UIImageView!
let urlString = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acbfed4b9e5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=12"
func getMovieData(){
//Set up URL
let todoEndPoint: String = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acbfed4b9e5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=12"
guard let url = URL(string: todoEndPoint) else {
print("Cant get URL")
return
}
let urlRequest = URLRequest(url: url)
//Setting up session
let config = URLSessionConfiguration.default
let session = URLSession.shared
//Task setup
let task = session.dataTask(with: urlRequest) { (data, URLResponse, error) in
//Checking for errors
guard error == nil else{
print("Error calling GET")
print(error)
return
}
//Checking if we got data
guard let responseData = data else{
print("Error: No data")
return
}
self.movies = [Movie]()
do{//If we got data, if not print error
guard let todo = try JSONSerialization.jsonObject(with: responseData, options:.mutableContainers) as? [String:AnyObject] else{
print("Error trying to convert data to JSON")
return
}//if data is Serializable, do this
if let movieResults = todo["results"] as? [[String: AnyObject]]{
//For each movieobject inside of movieresult try to make a movie object
for moviesFromJson in movieResults{
let movie = Movie()
//If all this works, set variables
if let title = moviesFromJson["title"] as? String, let movieRelease = moviesFromJson["release_date"] as? String, let posterPath = moviesFromJson["poster_path"] as? String, let movieId = moviesFromJson["id"] as? Int{
movie.title = title
movie.movieRelease = movieRelease
movie.posterPath = posterPath
movie.movieId = movieId
}
self.movies?.append(movie)
}
}
}//do end
catch{
print(error)
}
}
////Do Stuff
task.resume()
}
func setUI(){
//uiMovieTitle.text = self.movies![0].title
//print(self.movies?[0].title)
}
}
my Movie class:
import UIKit
class Movie: NSObject {
var title:String?
var movieRelease: String?
var posterPath:String?
var movieId:Int?
var movieGenre:[Int] = []
//public init(title:String, movieRelease:String, posterPath:String,movieId:Int) {
// self.movieId = movieId
//self.title = title
//self.movieRelease = movieRelease
//self.posterPath = posterPath
//self.movieGenre = [movieGenre]
//}
}

getMovieData calls the network asynchronously. Your viewDidLoad invokes this, then calls setUI() - but the networking is still ongoing when setUI is called.
Instead, call setUI when the networking is complete - after the self.movies?.append(movie) line. The UI code will need to happen on the main thread. So...
for moviesFromJson... // your existing code
...
self.movies?.append(movie)
}
// Refresh UI now movies have loaded.
DispatchQueue.main.async {
setUI()
}

import UIKit
class ViewController: UIViewController {
var movies:[Movie]? = []
#IBOutlet weak var uiMovieTitle: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
getMovieDataCall(completionHandler: {data, error in self. getMovieDataCallBack(data: data, error: error)})
}
func getMovieDataCallBack(data: Data?, error: Error?) {
if error == nil {
let dictionary = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! Dictionary<String, AnyObject>
//do your appending here and then call setUI()
print("dictionaryMovie \(dictionary)")
} else {
showAlertView("", error?.localizedDescription)
}
}
func getMovieDataCall(completionHandler: #escaping (Data?, Error?) -> Void)){
//Set up URL
let todoEndPoint: String = "https://api.themoviedb.org/3/discover/movie?api_key=935f539acbfed4b9e5534ddeed3fb57e&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=1&with_genres=12"
guard let url = URL(string: todoEndPoint) else {
print("Cant get URL")
return
}
let urlRequest = URLRequest(url: url)
//Setting up session
let config = URLSessionConfiguration.default
let session = URLSession.shared
//Task setup
let task = session.dataTask(with: urlRequest) { (data, URLResponse, error) in
if error != nil {
NSLog("GET-ERROR", "=\(error)");
completionHandler(nil, error)
} else {
let dataString = String(data: data!, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
print(dataString!)
completionHandler(data, nil)
}
task.resume()
}
func setUI(){
}

Related

How can I get HTML Code from an URL using Swift 5

how can I get the source code from an URL on the logs?
This code is returning an error message on the logs instead of the HTML data.
Can you please help me and let me know what can I include/change?
Thank you!
import UIKit
class ViewController: UIViewController {
#IBOutlet var textField: UITextField!
#IBOutlet var display: UILabel!
#IBAction func send(_ sender: Any) {
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = URL (string: "https://www.weather-forecast.com/locations/London/forecasts/latest")!
let request = NSMutableURLRequest(url:url)
let task = URLSession.shared.dataTask (with:request as URLRequest) {
data, response, error in
if error != nil {
print (error!)
} else {
if let unrappedData = data {
let dataString = NSString(data: unrappedData, encoding: String.Encoding.utf8.rawValue)
print (dataString!)
}
}
}
task.resume()
}
}
We can get the html code from the URL like below,
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
DispatchQueue.global(qos: .background).async {
// Background Thread
let myURLString = "https://www.weather-forecast.com/locations/London/forecasts/latest"
guard let myURL = URL(string: myURLString) else {
print("Error: \(myURLString) doesn't seem to be a valid URL")
return
}
do {
let myHTMLString = try String(contentsOf: myURL, encoding: .ascii)
print("HTML : \(myHTMLString)")
} catch let error {
print("Error: \(error)")
}
DispatchQueue.main.async {
// Run UI Updates or call completion block
}
}
}
}

dataTask with NSURL instead of URL

I've been following an guide in the hope of learning how to use MYSQL with IOS apps.
However the guide is a little bit outdated, and I'm using swift 3 and I been editing the code to fix a few bugs.
I have come down to a final problem, which is after I changed from using URL to NSURL, I can't use the "DataTask" anymore..
I have no idea how to replace this code of line.
import Foundation
protocol HomeModelProtocol: class {
func itemsDownloaded(items: NSArray)
}
class HomeModel: NSObject, NSURLSessionDataDelegate {
//properties
weak var delegate: HomeModelProtocol!
let urlPath = "http://iosquiz.com/service.php" //this will be changed to the path where service.php lives
func downloadItems() {
let url: NSURL = NSURL(string: urlPath)!
let defaultSession = Foundation.NSURLSessionConfiguration
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
}
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
do{
jsonResult = try NSJSONSerialization.jsonObject(with: data, options: NSJSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
let locations = NSMutableArray()
for i in 0 ..< jsonResult.count
{
jsonElement = jsonResult[i] as! NSDictionary
let location = LocationModel()
//the following insures none of the JsonElement values are nil through optional binding
if let name = jsonElement["Name"] as? String,
let address = jsonElement["Address"] as? String,
let latitude = jsonElement["Latitude"] as? String,
let longitude = jsonElement["Longitude"] as? String
{
location.name = name
location.address = address
location.latitude = latitude
location.longitude = longitude
}
locations.addObject(location)
}
I have come down to a final problem, which is after I changed from using URL to NSURL, I can't use the "DataTask" anymore..
Why did you switch from URL to NSURL? That's moving in the wrong direction. URL is the Swift bridge for NSURL. It should replace NSURL in all new code.
Switch back to URL. If you must use NSURL, you'll have to add an as URL when you use it in dataTask(with:), since that method expects an URL.
There's a deeper problem here. You're using a configuration as though it were a session. Here's the code I believe you mean:
// vvv Changed NSURLSessionDataDelegate to URLSessionDataDelegate
class HomeModel: NSObject, URLSessionDataDelegate {
weak var delegate: HomeModelProtocol? // <-- Avoid ! for this
let urlPath = "http://iosquiz.com/service.php"
func downloadItems() {
let url = URL(string: urlPath)! // <-- Changed NSURL to URL
let defaultSession = URLSession.shared // <-- Use URLSession, not URLSessionConfiguration
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Failed to download data")
}else {
print("Data downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
}

Swift 3/iOS UIView not updating after retrieving remote JSON data

I have a UITableView with a list of users. When you tap on a row, the uid of the user is passed to the UIViewController detail view. A URLRequest is made to retrieve JSON data of the user (username, avatar, etc). However, the detail view inconsistently updates the information. Sometimes it'll show the users' name, avatar, etc but other times it'll show nothing or it'll only show the username or only show the avatar, etc.
In the fetchUser() method, I have a print("Username: \(self.user.username)") that shows the correct data is being retrieved 100% of the time but it won't display it 100% of the time in the view.
Any help would be greatly appreciated.
Thanks!
class ProfileViewController: UIViewController {
#IBOutlet weak var avatarImageView: UIImageView!
#IBOutlet weak var usernameLabel: UILabel!
#IBOutlet weak var networthLabel: UILabel!
var user: User!
var uid: Int?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
fetchUser()
}
func reloadView() {
self.usernameLabel.text = user.username
self.networthLabel.text = "$" + NumberFormatter.localizedString(from: Int((user.networth)!)! as NSNumber, number: NumberFormatter.Style.decimal)
self.avatarImageView.downloadImage(from: user.avatar!)
circularImage(photoImageView: self.avatarImageView)
}
func fetchUser() {
// Post user data to server
let myUrl = NSURL(string: "http://localhost/test/profile")
let urlRequest = NSMutableURLRequest(url: myUrl! as URL);
urlRequest.httpMethod = "POST"
let postString = "uid=\(uid!)"
urlRequest.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: urlRequest as URLRequest) { (data, response, error) in
if (error != nil) {
print("error=\(String(describing: error))")
return
} // end if
self.user = User()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String : AnyObject]
if let parseJSON = json?["data"] as? [[String : AnyObject]] {
for userFromJson in parseJSON {
let userData = User()
if let uid = userFromJson["uid"] as? String,
let username = userFromJson["username"] as? String,
let networth = userFromJson["networth"] as? String,
let avatar = userFromJson["avatar"] as? String {
userData.uid = Int(uid)
userData.username = username
userData.networth = networth
userData.avatar = avatar
self.usernameLabel.text = username
self.networthLabel.text = networth
self.avatarImageView.downloadImage(from: avatar)
circularImage(photoImageView: self.avatarImageView)
} // end if
self.user = userData
} // end for
} // end if
DispatchQueue.main.async {
print("Username: \(self.user.username)")
self.reloadView()
}
} catch let error {
print(error)
}
}
task.resume()
}
Firstly, call fetch user in viewWillAppear like this:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fetchUser()
}
Then, change the code here like I did, don't use the reloadView function you had, instead, update the UI elements on the main thread at the end of the fetchUser function. I also changed it so you weren't updating the UI twice because you have 4 lines at the bottom of the if let uid = ... statement in fetchUser which updated UI elements that wasn't in the main thread which is why in my version I removed those 4 lines of code. Let me know if this worked for you.
let task = URLSession.shared.dataTask(with: urlRequest as URLRequest) { (data, response, error) in
if (error != nil) {
print("error=\(String(describing: error))")
return
} // end if
self.user = User()
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String : AnyObject]
if let parseJSON = json?["data"] as? [[String : AnyObject]] {
for userFromJson in parseJSON {
let userData = User()
if let uid = userFromJson["uid"] as? String,
let username = userFromJson["username"] as? String,
let networth = userFromJson["networth"] as? String,
let avatar = userFromJson["avatar"] as? String {
userData.uid = Int(uid)
userData.username = username
userData.networth = networth
userData.avatar = avatar
} // end if
self.user = userData
} // end for
} // end if
DispatchQueue.main.async {
self.usernameLabel.text = user.username
self.networthLabel.text = "$" + NumberFormatter.localizedString(from: Int((user.networth)!)! as NSNumber, number: NumberFormatter.Style.decimal)
self.avatarImageView.downloadImage(from: user.avatar!)
circularImage(photoImageView: self.avatarImageView)
}
} catch let error {
print(error)
}
}
task.resume()
Two suggestions:
strictly speaking, all accesses to UIView object should be on the main thread. You're dispatching to the main thread to call reloadView, but should probably also do it when you're settings the "username" and "net worth" values on the labels
are you sure that the labels are blank? Could it be an autolayout problem instead? (Try setting the background colour of the labels to yellow, to check that they're the correct size. Sometimes autolayout can squash views down to nothing if there are conflicting constraints)

How do you pass a value to an API in Swift?

I am pretty much a beginner in Swift and I am trying to pass a value to make a call to an API with the value being from an input field.
The API is given below. How do I write code to pass value for ZIP_CODE from a text field?
https://iaspub.epa.gov/enviro/efservice/getEnvirofactsUVDAILY/ZIP/ZIP_CODE/JSON
#IBOutlet var zipLabel: UILabel!
#IBOutlet var zipInput: UITextField!
#IBOutlet var jsondataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
getJSON()
}
#IBAction func btnAction() {
zipLabel.text = zipInput.text
}
func getJSON(){
let url = NSURL(string :"https://iaspub.epa.gov/enviro/efservice/getEnvirofactsUVDAILY/ZIP/10001/JSON")
let request = URLRequest(url: url as! URL )
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: request){(data, response, error ) -> Void in
if error == nil{
DispatchQueue.main.async(execute: {
let jsondata = JSON(data : data!)
print(jsondata)
print("-----")
print(jsondata[0]["UV_INDEX"])
let result = jsondata[0]["UV_INDEX"].stringValue
self.jsondataLabel.text = result
})
}else{
print("There was an error")
}
}
task.resume()
}
func getJSON(_ zipcode : Int){
let url = NSURL(string :"https://iaspub.epa.gov/enviro/efservice/getEnvirofactsUVDAILY/ZIP/\(zipcode)/JSON")
let request = URLRequest(url: url as! URL )
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: request){(data, response, error ) -> Void in
if error == nil{
DispatchQueue.main.async(execute: {
let jsondata = JSON(data : data!)
print(jsondata)
print("-----")
print(jsondata[0]["UV_INDEX"])
let result = jsondata[0]["UV_INDEX"].stringValue
self.jsondataLabel.text = result
})
}else{
print("There was an error")
}
}
task.resume()
}
If your API gives you the response on GET Request you can use the following code -
#IBOutlet var zipLabel: UILabel!
#IBOutlet var zipInput: UITextField!
#IBOutlet var jsondataLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnAction() {
zipLabel.text = zipInput.text
getJSON(zipcode: zipInput.text)
}
func getJSON(zipcode: String){
let url = NSURL(string :"https://iaspub.epa.gov/enviro/efservice/getEnvirofactsUVDAILY/ZIP/10001/JSON&zipcode="+zipcode)
let request = URLRequest(url: url as! URL )
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: request){(data, response, error ) -> Void in
if error == nil{
DispatchQueue.main.async(execute: {
let jsondata = JSON(data : data!)
print(jsondata)
print("-----")
print(jsondata[0]["UV_INDEX"])
let result = jsondata[0]["UV_INDEX"].stringValue
self.jsondataLabel.text = result
})
}else{
print("There was an error")
}
}
task.resume()
}
But Please note that this code won't work if your API gives you the response on POST request.

I can not get the json data to display in my second UIViewController

I have the following two functions in my first ViewController. They load a UITableView with over 300 rows. I call the loadRemoteData function inside the ViewDidLoad. Everything works fine in the first ViewController.
// MARK: - parseJSON
func parseJSON(data: NSData) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
if let rootDictionary = json as? [NSObject: AnyObject], rootResults = rootDictionary["results"] as? [[NSObject: AnyObject]] {
for childResults in rootResults {
if let firstName = childResults["first_name"] as? String,
let lastName = childResults["last_name"] as? String,
let bioguideId = childResults["bioguide_id"] as? String,
let state = childResults["state"] as? String,
let stateName = childResults["state_name"] as? String,
let title = childResults["title"] as? String,
let party = childResults["party"] as? String {
let eachLegislator = Legislator(firstName: firstName, lastName: lastName, bioguideId: bioguideId, state: state, stateName: stateName, title: title, party: party)
legislators.append(eachLegislator)
}
}
}
} catch {
print(error)
}
}
// MARK: - Remote Data configuration
func loadRemoteData() {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let url = "https://somedomain.com/legislators?order=state_name__asc,last_name__asc&fields=first_name,last_name,bioguide_id"
if let url = NSURL(string: url) {
let task = session.dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in
if let error = error {
print("Data Task failed with error: \(error)")
return
}
if let http = response as? NSHTTPURLResponse, data = data {
if http.statusCode == 200 {
dispatch_async(dispatch_get_main_queue()) {
self.parseJSON(data)
self.tableView.reloadData()
}
}
}
})
task.resume()
}
}
In the second ViewController, I want to display more information about the individual listed in the cell that is tapped, for that I use a different URL such as https://somedomain.com/legislators?bioguide_id=\"\(bioguideId)\" which provides me with a lot more detail. (The data being requested from the JSON Dictionary is different)
The code I use in the second ViewController is just like shown above with the only difference being the URL. I can print the url coming from the previous ViewController and it is displayed in the console log but no json data is shown.
I would appreciate any help.
Thanks
Below is the code for my second ViewController:
import UIKit
class DetailViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var bioguideId: String?
var currentLegislator: Legislator? = nil
var currentLegislatorUrl: String?
let reuseIdentifier = "Cell"
#IBOutlet weak var imageView: UIImageView!
#IBOutlet weak var tableView: UITableView!
// MARK: - parseJSON
private func parseJSON(data: NSData) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
if let rootDictionary = json as? [NSObject: AnyObject],
rootResults = rootDictionary["results"] as? [[NSObject: AnyObject]] {
for childResults in rootResults {
if let firstName = childResults["first_name"] as? String,
let lastName = childResults["last_name"] as? String,
let bioguideId = childResults["bioguide_id"] as? String,
let state = childResults["state"] as? String,
let stateName = childResults["state_name"] as? String,
let title = childResults["title"] as? String,
let party = childResults["party"] as? String {
currentLegislator = Legislator(firstName: firstName, lastName: lastName, bioguideId: bioguideId, state: state, stateName: stateName, title: title, party: party)
}
}
}
} catch {
print(error)
}
}
// MARK: - Remote Data configuration
func loadRemoteData(url: String) {
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let url = currentLegislatorUrl
if let url = NSURL(string: url!) {
let task = session.dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in
if let error = error {
print("Data Task failed with error: \(error)")
return
}
print("Success")
if let http = response as? NSHTTPURLResponse, data = data {
if http.statusCode == 200 {
dispatch_async(dispatch_get_main_queue()) {
self.parseJSON(data)
self.tableView.reloadData()
}
}
}
})
task.resume()
}
}
func loadImage(urlString:String) {
let imgURL: NSURL = NSURL(string: urlString)!
let request: NSURLRequest = NSURLRequest(URL: imgURL)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request){
(data, response, error) -> Void in
if (error == nil && data != nil) {
func display_image() {
self.imageView.image = UIImage(data: data!)
}
dispatch_async(dispatch_get_main_queue(), display_image)
}
}
task.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
print(currentLegislatorUrl!)
loadRemoteData(currentLegislatorUrl!)
loadImage("https://theunitedstates.io/images/congress/225x275/\(bioguideId!).jpg")
self.title = bioguideId
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath)
cell.textLabel!.text = currentLegislator?.firstName
return cell
}
}
Thanks to Adam H. His comment made me reevaluate the URL I was using and by adding additional operators, now the data is shown in my second ViewController.

Resources