Swift NSURL giving error on valid urls - ios

Look at this code:
let url = NSURL(string: physicst.image as String)
if let url = url {
let request = NSMutableURLRequest(URL: url)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let data = data {
cell.imageView?.image = UIImage(data: data)
}
})
task.resume()
}else {
print ("nill URL \(physicst.image)")
}
so I have a string, and that string is a url, and I want to load it.
that code is in a table view cells, so it is being called for each cell.
as you see, i am checking if the url is nil or not, and if not, i am making a print statement. almost all the urls are not nil, exception the following ones (which are complemetly valid)
http://commons.wikimedia.org/wiki/Special:FilePath/Kai_Manne_Börje_Siegbahn.jpg?width=300
http://commons.wikimedia.org/wiki/Special:FilePath/Виталий_Лазаревич_Гинзбург.jpg?width=300
http://commons.wikimedia.org/wiki/Special:FilePath/赤崎記念研究館.jpg?width=300
http://commons.wikimedia.org/wiki/Special:FilePath/Kai_Manne_Börje_Siegbahn.jpg?width=300
http://commons.wikimedia.org/wiki/Special:FilePath/赤崎記念研究館.jpg?width=300
The first thing you may argue is to encode the url, and that is what I did like this:
var image = oneBinding["p"]!!["value"] as! NSString
image = image.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
but then even the urls that were working, stopped working. what i am missing please ?
Update
Here is the whole code of my UITableViewController (it is easy)
class PhysicistTableViewController: UITableViewController {
var physicsts : [Physicst]?
#IBOutlet var physicstsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
loadDataFromDBPedia()
}
func loadDataFromDBPedia() {
let session = NSURLSession.sharedSession()
var url = "http://dbpedia.org/sparql/"
let query = "http://dbpedia.org&query=select * {?o dbo:thumbnail ?p . ?o dbo:award dbr:Nobel_Prize_in_Physics}"
url = url + "?default-graph-uri=" + query.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
url = url + "&format=JSON&CXML_redir_for_subjs=121&CXML_redir_for_hrefs=&timeout=30000&debug=on"
let request = NSMutableURLRequest(URL: NSURL(string: url)!)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response ,error) in
if let error = error {
print ("\(error)")
}
if let response = response {
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
print("Status code = \(statusCode)")
}
if let data = data {
do {
let jsonResponse = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
let binding = jsonResponse["results"]!!["bindings"] as! NSArray
for oneBinding in binding {
let name = oneBinding["o"]!!["value"] as! NSString
let image = oneBinding["p"]!!["value"] as! NSString
//image = image.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
let physicst = Physicst(image: image, name: name)
if self.physicsts == nil {
self.physicsts = [Physicst]()
}
self.physicsts!.append(physicst)
}
self.physicstsTableView.reloadData()
}catch _ {
print ("not well json-formatted response")
}
}
})
task.resume()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.physicsts == nil {
return 0
}else {
return self.physicsts!.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("physicstCell")! as UITableViewCell
let row = indexPath.row
let physicst = self.physicsts![row]
cell.textLabel?.text = physicst.name as String
if (physicst.imageData == nil) {
let session = NSURLSession.sharedSession()
let url = NSURL(string: physicst.image as String)
if let url = url {
let request = NSMutableURLRequest(URL: url)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let data = data {
let imageData = UIImage(data: data)
cell.imageView?.image = imageData
physicst.imageData = imageData
self.physicstsTableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
}
})
task.resume()
}else {
print ("nill URL \(physicst.image)")
}
}else {
cell.imageView?.image = physicst.imageData!
}
return cell
}
}

URLHostAllowedCharacterSet only contains this "#%/<>?#\^{|}
And your URL string contains : .. So make custom set for this
let yourString = "http://commons.wikimedia.org/wiki/Special:FilePath/Kai_Manne_Börje_Siegbahn.jpg?width=300"
let customSet = NSCharacterSet(charactersInString:"=\"#%/:<>?#\\^`{|}").invertedSet
let finalString = yourString.stringByAddingPercentEncodingWithAllowedCharacters(customSet)
For more info. check this answer

The method you should use is
stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
Taken from the playground:
NSURL(string:"http://commons.wikimedia.org/wiki/Special:FilePath/Kai_Manne_Börje_Siegbahn.jpg?width=300".stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!)
Gives the result
http://commons.wikimedia.org/wiki/Special:FilePath/Kai_Manne_B%C3%B6rje_Siegbahn.jpg?width=300
Which is valid :)
Adapted to your code it gives:
let url = NSURL(string: (physicst.image as String).stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()!)
Beware of the forced unwrapping tho !

Related

TableView with labels, images, gifs and video hangs / gets stuck incorrect while fetch from firestore in iOS, Swift

I have tableview with label, imageView (for image, gif & video thumbnail). I am sure that doing something wrong and I can't handle its completion handler due to which the app is hanged and gets stuck for a long time.
My model is like,
struct PostiisCollection {
var id :String?
var userID: String?
var leadDetails : NSDictionary?
var company: NSDictionary?
var content: String?
init(Doc: DocumentSnapshot) {
self.id = Doc.documentID
self.userID = Doc.get("userID") as? String ?? ""
self.leadDetails = Doc.get("postiiDetails") as? NSDictionary
self.company = Doc.get("company") as? NSDictionary
self.content = Doc.get("content") as? String ?? ""
}
}
I wrote in my view controller for fetch this,
var postiisCollectionDetails = [PostiisCollection]()
override func viewDidLoad() {
super.viewDidLoad()
let docRef = Firestore.firestore().collection("PostiisCollection").whereField("accessType", isEqualTo: "all_access")
docRef.getDocuments { (querysnapshot, error) in
if let doc = querysnapshot?.documents, !doc.isEmpty {
print("Document is present.")
for document in querysnapshot!.documents {
_ = document.documentID
if let compCode = document.get("company") as? NSDictionary {
do {
let jsonData = try JSONSerialization.data(withJSONObject: compCode)
let companyPost: Company = try! JSONDecoder().decode(Company.self, from: jsonData)
if companyPost.companyCode == AuthService.instance.companyId ?? ""{
print(AuthService.instance.companyId ?? "")
if (document.get("postiiDetails") as? NSDictionary) != nil {
let commentItem = PostiisCollection(Doc: document)
self.postiisCollectionDetails.append(commentItem)
}
}
} catch {
print(error.localizedDescription)
}
DispatchQueue.main.async {
self.tableView.isHidden = false
self.tableView.reloadData()
}
}
}
}
}
}
I need to check for the index path with image view is either image or gif or video with different parameters, I tried with tableview delegate and datasource method by,
extension AllAccessPostiiVC: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return postiisCollectionDetails.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "AllAccessCell", for: indexPath)
let label1 = cell.viewWithTag(1) as? UILabel
let imagePointer = cell.viewWithTag(3) as? UIImageView
let getGif = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "gif") as? NSArray
let getPhoto = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "photo") as? NSArray
let getVideo = arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "video") as? NSArray
label1?.text = "\(arrPostiisCollectionFilter[indexPath.row].leadDetails?.value(forKey: "title"))"
if getGif != nil {
let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
print(arrGif[0])
let gifURL : String = "\(arrGif[0])"
let imageURL = UIImage.gifImageWithURL(gifURL)
imagePointer?.image = imageURL
playButton?.isHidden = true
}
if getPhoto != nil {
let arrPhoto = getPhoto?.value(forKey: "photoUrl") as! [String]
print(arrPhoto[0])
let storageRef = Storage.storage().reference(forURL: arrPhoto[0])
storageRef.downloadURL(completion: { (url, error) in
do {
let data = try Data(contentsOf: url!)
let image = UIImage(data: data as Data)
DispatchQueue.main.async {
imagePointer?.image = image
playButton?.isHidden = true
}
} catch {
print(error)
}
})
}
if getVideo != nil {
let arrVideo = getVideo?.value(forKey: "videoUrl") as! [String]
let videoURL = URL(string: arrVideo[0])
let asset = AVAsset(url:videoURL!)
if let videoThumbnail = asset.videoThumbnail{
SVProgressHUD.dismiss()
imagePointer?.image = videoThumbnail
playButton?.isHidden = false
}
}
}
}
If I run, the app hangs in this page and data load time is getting more, some cases the preview image is wrongly displayed and not able to handle its completion
As others have mentioned in the comments, you are better of not performing the background loading in cellFroRowAtIndexPath.
Instead, it's better practice to add a new method fetchData(), where you perform all the server interaction.
So for example:
// Add instance variables for fast access to data
private var images = [UIImage]()
private var thumbnails = [UIImage]()
private func fetchData(completion: ()->()) {
// Load storage URLs
var storageURLs = ...
// Load data from firebase
let storageRef = Storage.storage().reference(forURL: arrPhoto[0])
storageRef.downloadURL(completion: { (url, error) in
// Parse data and store resulting image in image array
...
// Call completion handler to indicate that loading has finished
completion()
})
}
Now you can call fetchData() whenever you would like to refresh data and call tableview.reloadData() within the completion handler. That of course must be done on the main thread.
This approach simplifies your cellForRowAtIndexPath method.
There you can just say:
imagePointer?.image = ...Correct image from image array...
Without any background loading.
I suggest using below lightweight extension for image downloading from URL
using NSCache
extension UIImageView {
func downloadImage(urlString: String, success: ((_ image: UIImage?) -> Void)? = nil, failure: ((String) -> Void)? = nil) {
let imageCache = NSCache<NSString, UIImage>()
DispatchQueue.main.async {[weak self] in
self?.image = nil
}
if let image = imageCache.object(forKey: urlString as NSString) {
DispatchQueue.main.async {[weak self] in
self?.image = image
}
success?(image)
} else {
guard let url = URL(string: urlString) else {
print("failed to create url")
return
}
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
// response received, now switch back to main queue
DispatchQueue.main.async {[weak self] in
if let error = error {
failure?(error.localizedDescription)
}
else if let data = data, let image = UIImage(data: data) {
imageCache.setObject(image, forKey: url.absoluteString as NSString)
self?.image = image
success?(image)
} else {
failure?("Image not available")
}
}
}
task.resume()
}
}
}
Usage:
let path = "https://i.stack.imgur.com/o5YNI.jpg"
let imageView = UIImageView() // your imageView, which will download image
imageView.downloadImage(urlString: path)
No need to put imageView.downloadImage(urlString: path) in mainQueue, its handled in extension
In your case:
You can implement following logic in cellForRowAt method
if getGif != nil {
let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
let urlString : String = "\(arrGif[0])"
let image = UIImage.gifImageWithURL(urlString)
imagePointer?.image = image
playButton?.isHidden = true
}
else if getPhoto != nil {
let arrPhoto = getPhoto?.value(forKey: "photoUrl") as! [String]
let urlString = Storage.storage().reference(forURL: arrPhoto[0])
imagePointer?.downloadImage(urlString: urlString)
playButton?.isHidden = true
}
elseif getVideo != nil {
let arrVideo = getVideo?.value(forKey: "videoUrl") as! [String]
let urlString = arrVideo[0]
imagePointer?.downloadImage(urlString: urlString)
playButton?.isHidden = false
}
If you have one imageView to reload in tableView for photo, video and gif. then use one image array to store it prior before reloading. So that your main issue of hang or stuck will be resolved. Here the main issue is each time in table view cell collection data is being called and checked while scrolling.
Now I suggest to get all photo, gifs and video (thumbnail) as one single array prior to table view reload try this,
var cacheImages = [UIImage]()
private func fetchData(completionBlock: () -> ()) {
for (index, _) in postiisCollectionDetails.enumerated() {
let getGif = postiisCollectionDetails[index].leadDetails?.value(forKey: "gif") as? NSArray
let getPhoto = postiisCollectionDetails[index].leadDetails?.value(forKey: "photo") as? NSArray
let getVideo = postiisCollectionDetails[index].leadDetails?.value(forKey: "video") as? NSArray
if getGif != nil {
let arrGif = getGif?.value(forKey: "gifUrl") as! [String]
let gifURL : String = "\(arrGif[0])"
self.randomList.append(gifURL)
/////---------------------------
let imageURL = UIImage.gifImageWithURL(gifURL)
self.cacheImages.append(imageURL!)
//////=================
}
else if getVideo != nil {
let arrVideo = getVideo?.value(forKey: "videoUrl") as! [String]
let videoURL: String = "\(arrVideo[0])"
let videoUrl = URL(string: arrVideo[0])
let asset = AVAsset(url:videoUrl!)
if let videoThumbnail = asset.videoThumbnail{
////--------------
self.cacheImages.append(videoThumbnail)
//-----------
}
self.randomList.append(videoURL)
}else if getPhoto != nil {
let arrPhoto = getPhoto?.value(forKey: "photoUrl") as! [String]
let photoURL : String = "\(arrPhoto[0])"
/////---------------------------
let url = URL(string: photoURL)
let data = try? Data(contentsOf: url!)
if let imageData = data {
let image = UIImage(data: imageData)
if image != nil {
self.cacheImages.append(image!)
}
else {
let defaultImage: UIImage = UIImage(named:"edit-user-80")!
self.cacheImages.append(defaultImage)
}
}
//////=================
}
else {
//-----------------
let defaultImage: UIImage = UIImage(named:"edit-user-80")!
self.cacheImages.append(defaultImage)
//--------------------
}
}
completionBlock()
}
After getting all UIImage as array where loop is being called. Now you call this function inside your viewDidLoad. So after all values in images fetched then try to call tableView like this,
override func viewDidLoad() {
self.fetchData {
DispatchQueue.main.async
self.tableView.reloadData()
}
}
}
Now atlast, you may use SDWebImage or any other background image class or download method to call those in tableView cellforRow method,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// your cell idetifier & other stuffs
if getVideo != nil {
imagePointer?.image = cacheImages[indexPath.row]
playButton?.isHidden = false
}else {
imagePointer?.image = cacheImages[indexPath.row]
// or get photo with string via SdWebImage
// imagePointer?.sd_setImage(with: URL(string: photoURL), placeholderImage: UIImage(named: "edit-user-80"))
playButton?.isHidden = true
}
return cell
}
You're handling data in a totally wrong manner. Data(contentsOf: url!) - This is wrong. You should chache the images and should download it to directory. When you convert something into data it takes place into the memory(ram) and it is not good idea when playing with large files. You should use SDWebImage kind of library to set images to imageview.
Second thing if let videoThumbnail = asset.videoThumbnail - This is also wrong. Why you're creating assets and then getting thumbnail from it? You should have separate URL for the thumbnail image for your all videos in the response of the API and then again you can use SDWebImage to load that thumbnail.
You can use SDWebImage for gif as well.
Alternative of SDWebImage is Kingfisher. Just go through both libraries and use whatever suitable for you.

Cannot convert value of type string to type url in coercion

I'm receiving the error Cannot convert value of type string
to type url in coercion at this constant. Thanks for the help ahead of time :)
**let safariVC = SFSafariViewController(url: JobUrl as URL)**
import UIKit
import SafariServices
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
//Indeed API Query as JSON
final let urlString = "http://api.indeed.com/ads/apisearch?publisher=9727336427429597&as_phr=&as_any=&as_not=&as_ttl=&as_cmp=&jt=parttime&st=&salary=&radius=25&l=32304&fromage=any&limit=25&sort=&psf=advsrch=&userip=1.2.3.4&useragent=Mozilla/%2F4.0%28Firefox%29&v=2&format=json"
#IBOutlet weak var tableView: UITableView!
var jobTitleArray = [String]()
var snippetArray = [String]()
var companyArray = [String]()
var cityArray = [String]()
var jobUrlArray = [String]()
// url = URL(string: epsDictionary["link"] as! String)
override func viewDidLoad() {
super.viewDidLoad()
self.downloadJsonWithURL()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
//dispose of any resources that can be recreated
}
//fetch Json
func downloadJsonWithURL() {
let url = NSURL(string: urlString)
URLSession.shared.dataTask(with: (url as? URL)!, completionHandler: {(data, response, error) ->
Void in
if let JsonObj = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments)
as? NSDictionary {
print(JsonObj!.value(forKey: "results")!)
if let resultsArray = JsonObj!.value(forKey: "results") as? NSArray {
for result in resultsArray {
if let resultDict = result as? NSDictionary {
if let jobTitle = resultDict.value(forKey: "jobtitle") {
self.jobTitleArray.append(jobTitle as! String)
}
if let snippet = resultDict.value(forKey: "snippet") {
self.snippetArray.append(snippet as! String)
}
if let company = resultDict.value(forKey: "company") {
self.companyArray.append(company as! String)
}
if let city = resultDict.value(forKey: "formattedRelativeTime") {
self.cityArray.append(city as! String)
}
if let jobUrl1 = resultDict.value(forKey: "url") {
self.jobUrlArray.append(jobUrl1 as! String)
}
OperationQueue.main.addOperation({
self.tableView.reloadData()
})
}
}
}
}
}).resume()
}
func downloadJsonWithTask() {
let url = NSURL(string: urlString)
var downloadTask = URLRequest(url: (url as? URL)!, cachePolicy: URLRequest.CachePolicy.reloadIgnoringCacheData, timeoutInterval: 20)
downloadTask.httpMethod = "Get"
URLSession.shared.dataTask(with: (url as? URL)!, completionHandler: {(data, response, error) ->
Void in
let jsonData = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments)
print(jsonData!)
}).resume()
}
var jobToUrl = URL(string: "http://www.indeed.com/viewjob?jk=5de353a4c580dce0&qd=8FiWEXDXvmdNb_GJC9BAOpLFMiNO7rztIOPtGp_-cISTa1VWcmBigetsBoobMSCXdNyr-z6ge7UiYg2Mx15EH6m1Aj3izkOw87NHJgxznYA&indpubnum=9727336427429597&atk=1bchrhjof5hgga1k")
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
print("USER SELECTED CELL")
let JobUrl = jobUrlArray[indexPath.row]
**let safariVC = SFSafariViewController(url: JobUrl as URL)**
self.present(safariVC, animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return jobTitleArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
cell.jobTitle.text = jobTitleArray[indexPath.row]
cell.jobSummary.text = snippetArray[indexPath.row]
cell.employerName.text = companyArray[indexPath.row]
cell.cityName.text = cityArray[indexPath.row]
cell.ApplyButton.text = jobUrlArray[indexPath.row]
// let imageUrl = NSURL(string: imageUrlArray[indexPath.row])
// if imageUrl != nil {
// let data = NSData(contentsOf: (imageUrl as? URL)!)
// cell.imageView?.image = UIImage (data: data as! Data)
// }
return cell
}
}
You can't cast a string to a URL. Casting simply says "Ok, the thing in this box. It's not a string, it's an URL." If the object can't double as the other class, the cast fails.
You need to create a URL using a string as input:
let url = URL(string: myString)
String and URL are not related therefore cannot be casted to each other. You have to create the URL from a string (like in another line in the code).
let safariVC = SFSafariViewController(url: URL(string: JobUrl)!)
Don't use NSURL in Swift 3 at all.
PS: Be aware that all optional bindings in downloadJsonWithURL are meaningless if you are using multiple arrays as data source. If one value does not pass the binding the app will crash at the latest in cellForRow due to an out-of-range exception.
URL is an object which has different properties (like absoluteString,path, scheme etc. which are String objects themselves) so you cannot cast URL to String, they are different types.
With an URL you can use the URLSession class to access the contents of remote resource or represent a local path for example. Check the documentation and be sure that you know the classes you're using.
https://developer.apple.com/reference/foundation/nsurl
In Swift try to create a URL with String (through unwrapping):
if let url = URL(String: jobUrlArray[indexPath.row]) {
let safariVC = SFSafariViewController(url: jobUrl)
}
or use url? in code
but be sure you don't use explicitly unwrapped optional like:
let safariVC = SFSafariViewController(url: URL(string: JobUrl)!)
your code might crash, it's not safe.

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.

the table view is not being updated after download the data

First of all, to avoid my bad English, I uploaded a video showing my problem
http://www.mediafire.com/download/j6krsa274o80ik9/Screen_Recording.mov
Second, I have a UITableViewController, that uses a remote API to download data. the data contains many image URLs, my first problem is that the tableView is not being updated even though i am doing .reloadData() function
my second problem is that in the function:
tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
I download the images, which I had got their urls from the first call, then eventhing works good but I can't see the image unless I clicked on the row
Please see the video, it is easier to understand
Here is my code: (I gave you the full code of my UITableView because, it is simple, and because it has two functions, and they are the ones that making me problems)
class Physicst: NSObject {
let image : String
var imageData: UIImage?
let name : NSString
init(image: String, name: NSString) {
self.image = image
self.name = name
}
}
class PhysicistTableViewController: UITableViewController {
var physicsts : [Physicst]?
#IBOutlet var physicstsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
loadDataFromDBPedia()
}
func loadDataFromDBPedia() {
let session = NSURLSession.sharedSession()
var url = "http://dbpedia.org/sparql/"
let query = "http://dbpedia.org&query=select * {?o dbo:thumbnail ?p . ?o dbo:award dbr:Nobel_Prize_in_Physics} limit 10"
url = url + "?default-graph-uri=" + query.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
url = url + "&format=JSON&CXML_redir_for_subjs=121&CXML_redir_for_hrefs=&timeout=30000&debug=on"
let request = NSMutableURLRequest(URL: NSURL(string: url)!)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response ,error) in
if let error = error {
print ("\(error)")
}
if let response = response {
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
print("Status code = \(statusCode)")
}
if let data = data {
do {
let jsonResponse = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
let binding = jsonResponse["results"]!!["bindings"] as! NSArray
for oneBinding in binding {
let name = oneBinding["o"]!!["value"] as! NSString
var image = oneBinding["p"]!!["value"] as! String
image = image.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
let physicst = Physicst(image: image, name: name)
if self.physicsts == nil {
self.physicsts = [Physicst]()
}
self.physicsts!.append(physicst)
}
self.physicstsTableView.reloadData()
}catch _ {
print ("not well json-formatted response")
}
}
})
task.resume()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.physicsts == nil {
return 0
}else {
return self.physicsts!.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("physicstCell")! as UITableViewCell
let row = indexPath.row
let physicst = self.physicsts![row]
cell.textLabel?.text = physicst.name as String
if (physicst.imageData == nil) {
let session = NSURLSession.sharedSession()
let url = NSURL(string: physicst.image as String)
if let url = url {
let request = NSMutableURLRequest(URL: url)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let data = data {
let imageData = UIImage(data: data)
cell.imageView?.image = imageData
physicst.imageData = imageData
self.physicstsTableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
}
})
task.resume()
}else {
print ("nill URL \(physicst.image)")
}
}else {
cell.imageView?.image = physicst.imageData!
}
return cell
}
}
**fell free to copy/paste it, there is no custom cell, so it should work **
tableView reload should be called on main thread. session.dataTaskWithRequest completion block is called on background thread performing UI Operations on background thread might lead to serious consequences. I believe the problem you are facing is just one of those consequences. Modify the code as follow.
func loadDataFromDBPedia() {
let session = NSURLSession.sharedSession()
var url = "http://dbpedia.org/sparql/"
let query = "http://dbpedia.org&query=select * {?o dbo:thumbnail ?p . ?o dbo:award dbr:Nobel_Prize_in_Physics} limit 10"
url = url + "?default-graph-uri=" + query.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())!
url = url + "&format=JSON&CXML_redir_for_subjs=121&CXML_redir_for_hrefs=&timeout=30000&debug=on"
let request = NSMutableURLRequest(URL: NSURL(string: url)!)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response ,error) in
if let error = error {
print ("\(error)")
}
if let response = response {
let httpResponse = response as! NSHTTPURLResponse
let statusCode = httpResponse.statusCode
print("Status code = \(statusCode)")
}
if let data = data {
do {
let jsonResponse = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
let binding = jsonResponse["results"]!!["bindings"] as! NSArray
for oneBinding in binding {
let name = oneBinding["o"]!!["value"] as! NSString
var image = oneBinding["p"]!!["value"] as! String
image = image.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)!
let physicst = Physicst(image: image, name: name)
if self.physicsts == nil {
self.physicsts = [Physicst]()
}
self.physicsts!.append(physicst)
}
dispatch_async(dispatch_get_main_queue()) { () -> Void in
self.physicstsTableView.reloadData()
}
}catch _ {
print ("not well json-formatted response")
}
}
})
task.resume()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("physicstCell")! as UITableViewCell
let row = indexPath.row
let physicst = self.physicsts![row]
cell.textLabel?.text = physicst.name as String
if (physicst.imageData == nil) {
let session = NSURLSession.sharedSession()
let url = NSURL(string: physicst.image as String)
if let url = url {
let request = NSMutableURLRequest(URL: url)
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let data = data {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let imageData = UIImage(data: data)
cell.imageView?.image = imageData
physicst.imageData = imageData
self.physicstsTableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
})
}
})
task.resume()
}else {
print ("nill URL \(physicst.image)")
}
}else {
cell.imageView?.image = physicst.imageData!
}
return cell
}
TIP
Downloading the images manually for each cell and then loading it to tableViewCell and handling caching in order to improve the performance of scroll is like re inventing the wheel when you have tubless tires availabe :) Please consider using SDWebImage or AFNetworking I have personlly used SDWebImage and its caching feature works perfectly.

Swift 2 Function Call and JSON usage

I have a Problem with Swift and Xcode 7.
class ConnectVC: UITableViewController {
var username:Array< String > = Array < String >()
var TableData:Array< String > = Array < String >()
var pictures:Array< String > = Array < String >()
var profile_pictures:Array< String > = Array < String >()
override func viewDidLoad() {
super.viewDidLoad()
get_data_from_url("-url-")
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return TableData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ConnectVCCell
let picture = pictures[indexPath.row]
print(pictures.count)
print(picture)
print(profile_pictures.count)
let pic = profile_pictures[indexPath.row]
if picture != "" {
let aString = "-url-"
let url = NSURL(string: aString)
let data = NSData(contentsOfURL: url!)
print(url)
let image = UIImage(data: data!)
cell.imageURL.image = image
}else{
print("No picture")
cell.imageURL.image = nil
}
cell.mainLabel.text = TableData[indexPath.row]
return cell
}
func get_data_from_url(url:String)
{
let url = NSURL(string: url)
let urlRequest = NSMutableURLRequest(URL: url!,
cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 15.0)
let queue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(
urlRequest,
queue: queue,
completionHandler: {response, data, error in
if data!.length > 0 && error == nil{
let json = NSString(data: data!, encoding: NSASCIIStringEncoding)
self.extract_json(json!)
}else if data!.length == 0 && error == nil{
print("Nothing was downloaded")
} else if error != nil{
print("Error happened = \(error)")
}
}
)
}
func extract_json(data:NSString)
{
let jsonData:NSData = data.dataUsingEncoding(NSASCIIStringEncoding)!
do {
// Try parsing some valid JSON
let json: AnyObject? = try NSJSONSerialization.JSONObjectWithData(jsonData, options: .AllowFragments)
let data_list = json as? NSArray
for (var i = 0; i < data_list!.count ; i++ )
{
if let data_obj = data_list![i] as? NSDictionary
{
if let text = data_obj["text"] as? String
{
if let picture = data_obj["picture"] as? String
{
if let user = data_obj["user"] as? String
{
self.save_image("-url-")
TableData.append(text + " [" + user + "]")
pictures.append(picture)
}
}
}
}
}
}
catch let error as NSError {
// Catch fires here, with an NSErrro being thrown from the JSONObjectWithData method
print("A JSON parsing error occurred, here are the details:\n \(error)")
}
do_table_refresh();
}
func save_image(url:String){
let url = NSURL(string: url)
let urlRequest = NSMutableURLRequest(URL: url!,
cachePolicy: .ReloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: 15.0)
let queue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(
urlRequest,
queue: queue,
completionHandler: {response, data, error in
if data!.length > 0 && error == nil{
let json = NSString(data: data!, encoding: NSASCIIStringEncoding)
self.extract_json_picture(json!)
}else if data!.length == 0 && error == nil{
print("Nothing was downloaded")
} else if error != nil{
print("Error happened = \(error)")
}
}
)
}
func extract_json_picture(data:NSString)
{
let jsonData:NSData = data.dataUsingEncoding(NSASCIIStringEncoding)!
do {
// Try parsing some valid JSON
let json: AnyObject? = try NSJSONSerialization.JSONObjectWithData(jsonData, options: .AllowFragments)
print(json)
let user_info = json as? NSArray
if let user_list = user_info![0] as? NSDictionary
{
if let profile_picture = user_list["picture"] as? String
{
profile_pictures.append(profile_picture)
}
}
}
catch{
print("A JSON parsing error occurred, here are the details:\n \(error)")
}
With this Code I get the following Error:
fatal error: Array index out of range
in the following line:
let pic = profile_pictures[indexPath.row]
The Problem is, that the array is empty. But I don't see the Problem. I think the function where I fill the array is correctly called.
Can someone help?
`tableViewcellForRowAtIndexPath: is getting called before TableData has data.
Ensure your number of rows in section returns the number of pictures:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return profile_pictures.count
}
This prevents a cell from being created when a picture doesn't exist at that index.

Resources