Passing Json object based on user selection in a tableview swift - ios

ScreenshotHi all I'm working on a project where when the user selects a particular cell in a table view, it should show them the data( which is a JSON Decoded object), Now I did all the networking stuff but not sure how to pass the values in such a way that when the user selects a particular cell, corresponding values should appear.
This is the contents of my tableview
var items = [Canada,Delhi,Mumbai,London]
When the user selects pen then the first value(kindly see my screenshot) should be displayed, Also I don't want to store these values as a hardcoded value, I want it to update whenever the user taps on that cell, I've added my networking struct, I'm new here so forgive me if there is any error in my question.
Thank You!
struct StateManager {
func geturl(){
let url = "https://api.covidindiatracker.com/state_data.json"
networking(stateUrl: url)
}
func networking(stateUrl : String){
if let url = URL(string: stateUrl){
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil{
print("Error fetching Data")
return
}else{
//This is the decoded Data
if let safeData = data{
do{
let json = try JSON(data: safeData)
for i in 0..<38{
if let total = json[i]["confirmed"].int{
print(total)
}
}
}catch{
print("Error ")
}
}
}
}
task.resume()
}
}
}

Related

Unable to load the UIImageView and UILabel immediately with the data from Firebase in Swift

I have a view controller called ProfileViewController and it contains a UIImageView for displaying the user avatar and a UILabel for displaying the username. However, it takes a while to show the avatar and username. If I go to other view controller and reopen it again, it actually can show them immediately. Also, I have tried to print out the username and avatar url and I can receive the value from Firebase instantly as well.
Here is the code:
func setUp(){
//load avatar and other informations
databaseRef.child("profiles").child(Auth.auth().currentUser!.uid).child("information").observeSingleEvent(of: .value, with: {(snapshot) in
let value = snapshot.value as? NSDictionary
self.username.text = value?["username"] as? String
if let avatarURL = value?["avatar"] as? String {
if let url = URL(string: avatarURL){
let task = URLSession.shared.dataTask(with: url){
(data, response, error) in
if let data = data{
DispatchQueue.main.async {
self.avatar.image = UIImage(data: data)
}
}
}
task.resume()
}
}
})
}
There is no way to optimize this , as you have nested needed asynchronous calls , what you can change is to use SDWebImage to cache the image for upcoming visits to that vc

Swift 5 : Escaping closure captures 'inout' parameter

I already have the response data that I received from the server. This response data have some bakers data.
Now I want to calculate the distance of the user and bakery and then store it in the same modal class. I have created a function for it. And as this function need to be used in 4,5 view controllers, my plan is to create as an extension of UIViewController
func getDistanceUserBakery(bakeryData : inout [BakeryRecord], completion : #escaping (Int?) -> () ) {
for index in 0...(bakeryData.count-1) {
//1
let googleApiAdd = "https://maps.googleapis.com/maps/api/distancematrix/json?units=imperial&"
//2
let origin = "origins=\(UserLocation.coordinates.latitude),\(UserLocation.coordinates.longitude)"
//3
let destination = "&destinations=\(bakeryData[index].location?.coordinates?[1] ?? 0.0),\(bakeryData[index].location?.coordinates?[0] ?? 0.0)"
//4
let googleKey = "&key=\(GOOGLE_KEY)"
//5
let url = googleApiAdd + origin + destination + googleKey
let request = URLRequest(url: URL(string: url)!)
//6 - this line is showing the error.
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
guard let data = data else {
completion(nil)
Toast.show(message: "Unable to calculate distance from user to bakery", controller: self)
return }
let stringResponse = String(data: data, encoding: .utf8)!
let dictData = stringResponse.convertToDictionary()
do {
let jsonData = try JSONSerialization.data(withJSONObject: dictData as Any, options: .prettyPrinted)
let decoder = JSONDecoder()
let model = try decoder.decode(GoogleDistance.self, from: jsonData)
bakeryData[index].disanceInMiles = model.rows?[0].elements?[0].distance?.text ?? "NaN"
completion(index)
} catch let parsingError {
print("Error data :", parsingError)
completion(nil)
}
}
task.resume()
}
This is how I call this function once I have received the data from my server,
self.getDistanceUserBakery(bakeryData: &self.bakeryData) { index in
if index != nil {
DispatchQueue.main.async {
// here I am thinking as the bakeryData will hold the new value for distanceInMiles, the collectionView will start showing up that result on reload.
self.resultCollection.reloadItems(at: [IndexPath(item: index!, section: 0)])
}
}
}
Now the Question:
As I know, when you pass parameters as inout, there values can be changed from inside your function, and those changes reflect in the original value outside the function.
But when I try the code , it says Escaping closure captures 'inout' parameter 'bakeryData'. In my code , //6 is producing the error.
How to fix this error?
As #Paulw11 suggested in comments,
Is BakeryData a struct? If so then simply make it a class. If you make
BakerData a class then the array contains reference types and you can
update the element's properties
I changed the struct to class and it did work.

Add an image as an accessory in your UITableView in Swift 3

In my project, I show a UITableView, which currently has text describing a show's name and genre loading from a remote JSON file.
That all works. What I want next is to use the URL from the JSON file and load a thumbnail next to each show.
Using a tutorial, I have added a function to download the remote image with a print to test if it's successful.
if let shows_list = json as? NSArray
{
for i in 0 ..< data_list.count
{
if let shows_obj = shows_list[i] as? NSDictionary
{
let show_name = shows_obj["show"] as? String
let show_genre = shows_obj["genre"] as? String
let show_image = shows_obj["thumbnail"] as? String
TableData.append(show_name! + " | " + show_genre!)
let testPictureURL = URL(string: show_image!)!
let session = URLSession(configuration: .default)
// Here's the download task where I'm grabbing the image
let downloadPicTask = session.dataTask(with: testPictureURL) { (data, response, error) in
// The download has finished.
if let e = error {
print("Error downloading cat picture: \(e)")
} else {
// No errors found.
if let res = response as? HTTPURLResponse {
print("Downloaded picture with response code \(res.statusCode)")
if let imageData = data {
// Now I know I have data, so I think I can use UIImage to convert it into an image
let image = UIImage(data: imageData)
} else {
print("Couldn't get image: Image is nil")
}
} else {
print("Couldn't get response code for some reason")
}
}
}
downloadPicTask.resume()
}
There are three items in the JSON array, and I get three printed statements that the picture was download: but the image does not appear.
My theory: since this is a table, maybe I have to add this as an accessory, but there isn't an image accessory subclass.
I am new to Swift -- do you have any ideas about how I should append this uploaded image to the table.
This is probably being caused by the asynchronous behavior of URLSession so when the requested image returns the view is already loaded.
To solve that, you can use a callback, for instance:
func myFunction(completion: (returnedImage -> UIIMage) -> Void){
//...
let downloadPicTask = session.dataTask(with: testPictureURL) { (data, response, error) in
//...
let image = UIImage(data: imageData)
completion(returnedImage: image)
//...
}
downloadPicTask.resume()
}
}
By using a callback, let's say that you have a method called myFunction(completion:), so now when you call the method you can handle whatever comes back from completion:
myFunction { (image) in
DispatchQueue.main.async { cell.imageView.image = image }
}

Images loading in incorrectly even with cache

if let toID = message.chatPartnerId() {
firebaseReference.child(toID).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: Any] {
cell.nameLabel.text = dictionary["displayname"] as? String
let pic = dictionary["pictureURL"] as! String
print("THIS IS THE URL FOR EACH DISPLAYNAME")
print(dictionary["displayname"] as? String)
print(pic)
if let imageFromCache = MainPageVC.imageCache.object(forKey: pic as NSString) {
cell.pictureLabel.image = imageFromCache
} else {
let requested = URLRequest(url: URL(string: pic )!)
URLSession.shared.dataTask(with: requested) {data, response, err in
if err != nil {
print(err)
} else {
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
MainPageVC.imageCache.setObject(imageToCache!, forKey: pic as NSString)
//cell.pictureLabel.image = nil
cell.pictureLabel.image = imageToCache
}
}
}.resume()
}
}
})
}
return cell
}
I'm running this code in my cellForRowAtIndexPath and I'm getting a ton of really bad behavior. I'm also getting similar behavior on other pages but for some reason this block of code with about a 90% consistency returns incorrect information for cells.
I get a lot of duplicate pictures being used, displaynames in the wrong places, but when I'm actually clicking into a person, my detail page shows the correct information every single time. That code is the typical didSelectRowAtIndexPath and passing the person.
What I don't understand is why on the initial load of this page all of the information is screwed up, but if I click into someone and come back the entire tableview has correct names and pictures. The names/pics also fix if I scroll a cell off the screen then come back to it.
I'm getting this behavior all over my app, meanwhile I see caching/loading done like this everywhere. Is it because I'm running the code in my cellForRowAtIndexPath? The only difference I see is that I'm running it there instead of creating a function inside of my Person class that configures cells and running it like that. What I don't understand is why that would make a difference because as far as I'm aware running a function within cellforRowAtIndexpath would be the same as copy-pasting that same code into there?
Any ideas/suggestions?
Edit: I'm getting a very similar situation when I'm running the following code:
self.PersonalSearchesList = self.PersonalSearchesList.sorted{ $0.users > $1.users }
self.tableView.reloadData()
Where I'm sorting my array before reloading my data. The information sometimes loads in incorrectly at first, but once I scroll the cell off the screen then come back to it it always corrects itself.
if you are using swift 3 here are some handy functions that allow you to save an image to your apps directory from an URL and then access it from anywhere in the app:
func saveCurrentUserImage(toDirectory urlString:String?) {
if urlString != nil {
let imgURL: URL = URL(string: urlString!)!
let request: URLRequest = URLRequest(url: imgURL)
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
if (error == nil && data != nil) {
func display_image() {
let userImage = UIImage(data: data!)
if let userImageData = UIImagePNGRepresentation(userImage!) {
let filename = self.getDocumentsDirectory().appendingPathComponent("userImage")
try? userImageData.write(to: URL(fileURLWithPath: filename), options: [.atomic])
}
}
DispatchQueue.main.async(execute: display_image)
}
})
task.resume()
}
}
and then access it with any view controller using this:
extension UIViewController {
func getImage(withName name: String) -> UIImage {
let readPath = getDocumentsDirectory().appendingPathComponent(name)
let image = UIImage(contentsOfFile: readPath)
return image!
}
}
and finally calling it like this:
cell.pictureLabel.image = getImage(withName: "userImage")
If you can run the saveCurrentUserImage function prior to running cellForRowAtIndexPath then you can just check if the photo is nil in the directory before attempting to download it. You might be getting funny behavior when the page initially loads because you have multiple network calls going on at once. I wouldn't recommend making any network calls in cellForRowAtIndexPath because every time the cells are re-initialized it's going to make that network call for each cell.
Hope it helps!
EDIT: This method of image saving and retrieval is for images that you want to persist. If you want to erase them from memory you'll have to delete them from your directory.

Swift: Refresh Button functionality is loading the new data including the existing data

I am loading data on collection view from a set of JSON Data. I have added a refresh button on the navigation bar. I have used the same function as used in Viewdidload to the refresh button. When I tap on the refresh button, The new data is displayed after the existing data in the collection view rather than displaying just the new data. Can someone please help me on how to erase the existing data and display only the new data when refresh button is tapped.
func Dataparsed() {
let quizurl = NSURL(string:"Your URL")
let task = NSURLSession.sharedSession().dataTaskWithURL((quizurl)!, completionHandler: { (data, response, error) -> Void in
do{
let string = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as! NSDictionary
let swapLibs = string as? NSDictionary
let QuizData = swapLibs!.valueForKey("Your Key") as! NSArray
self.YourMutableArray.removeAllObjects()
for dict in QuizData {
self.playQuizArray.addObject(playInfo.PlayQuizInfo(dict as? NSDictionary))
}
self.colletionView.reloadData()
print(self.playQuizArray)
}
catch {
print("json error: \(error)")
}
})
task.resume()
}
Hope It Helps.Thanks.
when you recieve the json data on button click try to clear the list first
var someArray = [string]()
someArray.RemoveAll();
then add the new data loaded from json after that reload the collection
collectionview.ReloadData()

Resources