i wanted to load ASYNC an Image to a Cell.
my Code is:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as xmlParserDatenCell
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
var test = UIImage(data: NSData(contentsOfURL: NSURL(string: daten[imagePath.row]["bildlink"]!)!)!)
cell.bild1.image = test
})
my answer from this is nil.
No idea to to it. :(
i searched the WEB for hrs.
EDIT:
var daten = [String:String] looks my daten.
and my parser returns this into:
daten.append(String:String)
There are a lot of things going wrong here. When you leave the main queue for an async, you have to return to main before setting the image. Additionally, you need to debug your code to make sure at each step you're getting what you expect (i.e. Is daten[0]["bildlink"] actually returning an image address?)
Anyways, here's some code that should work if you're using swift 1.2 and the xml is actually returning some data.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { () -> Void in // background
var loadedImage: UIImage? //optional image, will be set if the rest succeeds
if let link = daten[indexPath.row]["bildlink"], // address for image
url = NSURL(string: link), // .. to URL
data = NSData(contentsOfURL: url), // .. to data
image = UIImage(data: data) { // .. image from data
loadedImage = image
}
dispatch_async(dispatch_get_main_queue()) { () -> Void in // return to main thread
cell.bild1.image = loadedImage ?? nil // if above successful, actually set image
}
}
You should also check to make sure the cell hasn't been reused before setting the image. You can do this a whole bunch of ways, but I would add a property like imageAddress to save the string from daten[0]["bildlink"] and reference it like if cell.imageAddress == link { cell.bild1.image = loadedImage }
To make an async web request you can use:
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue(), completionHandler:{
(response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
// do stuff with response, data & error here
})
If you're using it inside of cellForRowAtIndexPath:, you may not end up with the same cell as you expect since it's async and cells get reused, so make sure you have the correct cell before you set the image:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = self.tableView!.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell
var urlRequest = NSMutableURLRequest(URL: NSURL(string: daten[indexPath.row]["bildlink"]!)!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue(), completionHandler:{
(response:NSURLResponse!, data: NSData!, error: NSError!) -> Void in
if let cellToUpdate = self.tableView?.cellForRowAtIndexPath(indexPath) {
cellToUpdate.imageView?.image = UIImage(data: data)
// need to reload the view, which won't happen otherwise since this is in an async call
cellToUpdate.setNeedsLayout()
}
})
}
Related
I after asking another question today and fixed my Swift 4.2 code, I realized that I can't debug the app because of some profiles.
Errors shown when Testing application
The app also crashes on launch so there is not much I can do for now. I believe that the issue is how I am getting the information from the webserver (currently my computer). I am quite new to this, so i might have some mistakes in my code, so bear with me.
import UIKit
class InfoTableViewController: UITableViewController {
//MARK: Properties
class Produs {
var nume: String!
var codBare: String!
init(nume: String, codBare: String) {
self.nume = nume
self.codBare = codBare
}
}
var produse = [Produs]()
override func viewDidLoad() {
super.viewDidLoad()
//Load elements from server, theoretically
loadProducts()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return produse.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "InfoTableViewCell"
guard let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? InfoTableViewCell else {
fatalError("The dequeued cell is not an instance of InfoTableViewCell.")
}
// Fetches the appropriate meal for the data source layout.
let produs = produse[indexPath.row]
cell.nameMain.text = produs.nume
cell.nameInfo.text = produs.codBare
return cell
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
//MARK: Private Methods
private func loadProducts() { //This function retrieves information in a JSON format from the server
var request = URLRequest(url: URL(string: "192.168.0.145")!)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request, completionHandler: { data, response, error -> Void in
do {
let decoder = JSONDecoder()
let json = try decoder.decode([[String]].self, from: data!)
print(json)
for produs in json {
print(produs)
var nume_prod: String = produs[0] // Numele produsului
var cod_ext: String = produs[1] // Codul de bare al produsului
var prod_obj = Produs(nume: nume_prod, codBare: cod_ext)
self.produse.append(prod_obj)
}
} catch {
print("JSON Serialization error")
}
}).resume()
}
}
I am getting the information from the server in a JSON file that is an array of arrays and looks like this:
[
[
"product1",
"code1"
],
[
"product2",
"code2"
],
[
"product3",
"code3"
]
]
Thank you for your help!
Don't send the JSON as array of arrays, send it as array of dictionaries. It simplifies the decoding considerably.
[
{"name":"product1", "code":"code1"},
{"name":"product2", "code":"code2"},
{"name":"product3", "code":"code3"}
]
Then declare the model as struct and never declare properties as implicit unwrapped optional which are initialized with non-optional values. If you need optionals declare them as regular optional (?) otherwise non-optional
struct Produs {
let name: String
let code: String
}
Replace loadProducts with
private func loadProducts() { //This function retrieves information in a JSON format from the server
let url = URL(string: "192.168.0.145")!
URLSession.shared.dataTask(with: url, completionHandler: {[unowned self] data, response, error in
if let error = error { print(error); return }
do {
self.produse = try JSONDecoder().decode([Produs].self, from: data!)
print(self.produse)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}).resume()
}
And replace cellForRowAt with
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InfoTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! InfoTableViewCell
// Fetches the appropriate meal for the data source layout.
let produs = produse[indexPath.row]
cell.nameMain.text = produs.name
cell.nameInfo.text = produs.code
return cell
}
I want to create A Ui TableView with a list of image link with swift 2:
for example : var images = ["link1","link2",...,linkN"]
I create a custom cell to display the image :
let cell = tableView.dequeueReusableCellWithIdentifier(CurrentFormTableView.CellIdentifiers.ImageCell, forIndexPath: indexPath) as! ImageCell
cell.urlImageView.tag = indexPath.row
cell.displayImage(images[index.row])
cell.selectionStyle = UITableViewCellSelectionStyle.None
return cell
And here I have my custom cell to load my image :
import UIKit
class ImageCell: UITableViewCell {
#IBOutlet weak var urlImageView: UIImageView!
#IBOutlet weak var loadingStatus: UIActivityIndicatorView!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func loadImageFromUrl(url: String, view: UIImageView){
if view.image == nil {
self.startLoading()
// Create Url from string
let url = NSURL(string: url)!
// Download task:
// - sharedSession = global NSURLCache, NSHTTPCookieStorage and NSURLCredentialStorage objects.
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (responseData, responseUrl, error) -> Void in
// if responseData is not null...
if let data = responseData{
// execute in UI thread
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.stopLoading()
view.image = UIImage(data: data)
})
}
}
// Run task
task.resume()
}
}
func displayImage(imageUrl: String){
imageView?.image = nil
if imageUrl != nil && imageUrl != "" {
print(imageUrl)
loadImageFromUrl(imageUrl,view: urlImageView)
} else {
loadingStatus.hidden = true
}
}
func startLoading(){
loadingStatus.hidden = false
loadingStatus.startAnimating()
}
func stopLoading(){
loadingStatus.hidden = true
loadingStatus.stopAnimating()
}
}
The problem is that, she times the images are loading correctly, and sometimes, one image or more "override the other" so I have multiple identical images instead of see all my different images. How it is possible ? Where is my mistake ?
You're not implementing the reuse of the cells, meaning that the imageView's image of one cell will be the same as another that it was reused from, until the new image has loaded.
To prevent this, implement the -prepareForReuse: method:
override func prepareForReuse() {
super.prepareForReuse()
urlImageView.image = nil
// cancel loading
}
Furthermore, you shouldn't be doing any network-related code in the view layer, it should be done in the view controller. This will allow you to implement caching mechanisms if the image has already been downloaded for a specific cell, as well as alter the state of other views.
i.e. In your view controller:
var cachedImages = [String: UIImage]()
func cellForRow...() {
let imageURL = imageURLs[indexPath.row]
if let image = cachedImages[imageURL] {
cell.urlImageView.image = cachedImages[imageURL]
}
else {
downloadImage(indexPath, { image in
if let image = image {
cachedImages[imageURL] = image
cell.urlImageView.image = image
}
})
}
}
func downloadImage(indexPath: NSIndexPath, callback: () -> (UIImage?)) {
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (responseData, responseUrl, error) -> Void in
// if responseData is not null...
if let data = responseData {
// execute in UI thread
dispatch_async(dispatch_get_main_queue(), {
callback(UIImage(data: data))
})
}
else {
dispatch_async(dispatch_get_main_queue(), {
callback(nil)
})
}
}
// Run task
task.resume()
}
I have researched this and nothing seems to be working. I am trying to build a recipe app and the the image of the dish & names of the dish (appetizer) are not downloading in order. How can I do this?
Code:
class Appetizers: UITableViewController {
var valueToPass: String!
var valuePassed: String!
var appetizer = [String]()
var images = [UIImage]()
func refresh() {
dispatch_async(dispatch_get_main_queue(), {
self.tableView.reloadData()
})
}
override func viewDidLoad() {
super.viewDidLoad()
// Parse - class - column
let query = PFQuery(className: "Appetizers")
query.orderByAscending("appetizer")
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if error == nil {
if let objects = objects {
for object in objects {
let load = object.objectForKey("appetizer") as! String
self.appetizer.append(load)
let imageFile = object["imageFiles"] as! PFFile
imageFile.getDataInBackgroundWithBlock({ (imageData: NSData?, error: NSError?) -> Void in
if error != nil {
print(error)
} else {
if let data = imageData {
self.images.append(UIImage(data: data)!)
self.tableView.reloadData()
}
}
})
self.tableView.reloadData()
}
}
} else {
print("Error: \(error!) \(error!.userInfo)")
}
}
sleep(1)
refresh()
}
override func viewWillAppear(animated: Bool) {
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return appetizer.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.textLabel?.text = appetizer[indexPath.row]
// add image to table
if images.count > indexPath.row {
cell.imageView?.image = images[indexPath.row]
}
return cell
}
// when user taps on cell ...
func getCellLabel () {
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell!
valueToPass = currentCell.textLabel!.text
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
getCellLabel()
self.performSegueWithIdentifier("0", sender: self)
}
}
When performing asynchronous queries, you have no assurances regarding the order they complete. So, the concept of two separate arrays, one an array of strings and another an array of images will always be problematic.
You could, for example, replace images with a dictionary indexed by the appetizer name, and thus it wouldn't matter what order they complete.
var appetizer = [String]()
var images = [String: UIImage]()
Thus, it might look like:
override func viewDidLoad() {
super.viewDidLoad()
let query = PFQuery(className: "Appetizers")
query.orderByAscending("appetizer")
query.findObjectsInBackgroundWithBlock { objects, error in
guard error == nil, let objects = objects else {
print(error)
return
}
for (index, object) in objects.enumerate() {
let appetizerName = object.objectForKey("appetizer") as! String
self.appetizer.append(appetizerName)
let imageFile = object["imageFiles"] as! PFFile
imageFile.getDataInBackgroundWithBlock { imageData, error in
guard error == nil, let data = imageData else {
print(error)
return
}
// when the image comes in, asynchronously update only that one row
self.images[appetizerName] = UIImage(data: data)
self.tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .Fade)
}
}
// reload the table only once, after all of the `appetizer` entries are created (but likely before the images come in)
self.tableView.reloadData()
}
}
And
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
let name = appetizer[indexPath.row]
cell.textLabel?.text = name
cell.imageView?.image = images[name]
return cell
}
Or you could just replace these two separate properties with one that is an array of custom objects (e.g. an Appetizer object that has both a name property and an image property).
But any way you do that, you want to name sure you're not dealing with two separate array.
By the way, but this process of loading all of the images can be problematic if you have a lot of rows. This code is employing "eager" loading of images (loading them whether they're currently required or not). The problem is that images are relatively large assets (in comparison to the string values) and you can run into memory issues, network bandwidth issues, etc.
One generally likes to employ "lazy" loading (e.g. let cellForRowAtIndexPath request the image only when it's needed. For example, let's say you have 200 rows, of which only 12 are visible at one point. You shouldn't be requesting 200 images, but rather only that for the 12 visible ones. If you take the image retrieval out of viewDidLoad and, instead, have cellForRowAtIndexPath request them one at a time, you'll have much better network performance and less demanding memory characteristics.
If you're going to save the images in some structure like the code currently does, at the very least make sure you purge those images upon receiving notification of a memory warning (and, obviously, gracefully handle the re-requesting them in a JIT manner as needed).
I figured out the problem with my table not loading without sleep() ...
I had 'self.tableView.reloadData()' outside of the block.
Rob was very helpful :)
I have two functions in a view controller. The first function parses JSON and makes an array; another generates a table with the array data. The problem is that it seems that the first function cannot send its array data to the second function.
Here is the code:-
class secondViewController: UIViewController, UITableViewDataSource {
let chartTitle:[String] = ["Name",......]
func parseJSON(){
let url = NSURL(string: "http://00000.us-west-2.elasticbeanstalk.com/index.php?000000")
let request = NSURLRequest(URL: url!)
do {
let data = try NSURLConnection.sendSynchronousRequest(request, returningResponse: nil)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers)
var name = json["Name"]
var chartContent:[String] = ["\(name)",.....] //Contents of current chart contents
} catch{
//Handle Exception
}
} catch{
//Handle Exception
}
}
override func viewDidLoad() {
parseJSON()
...
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { //currnet table information.
let cell = UITableViewCell()
cell.textLabel?.text = chartTitle[indexPath.row] + "\t\t\t\t\t here comes info" + chartContent[indexPath.row]
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return chartTitle.count
}
}
This code has an error at the tableView function:
Use of unresolved identifier 'chartContent'
I tried to declare the variables outside the first function which is right under the class secondViewController but there was another error on UITableViewDataSource.
Any solution for these?
Charttitle is defined outside any procedure, so it's available everywhere. Chartcontent is defined in a block, so it's usable just in it's block
Its because chartContent is a local variable just available to parseJson func only and its scope is till that func block. You have to create this variable the same way you dis chartTitle to be available throughout the class.
Hi I'm new with swift and parse.com, I'm trying do populate my array with already saved images in parse, try to use the dispatch_async but don't know how this works, heres the code:
//imageArray declaration in table:
var imageArray: Array<UIImage> = []
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomCell
var query = PFQuery(className: "ParseClass")
query.findObjectsInBackgroundWithBlock { ( objects: [AnyObject]?, error: NSError?) -> Void in
if(error == nil) {
let imageObjects = objects as! [PFObject]
for object in objects! {
let thumbNail = object["columnInParse"] as! PFFile
thumbNail.getDataInBackgroundWithBlock ({ (imageData: NSData?, error: NSError?) -> Void in
if (error == nil) {
if let image = UIImage(data:imageData!) {
//here where the error appears: Cannot invoke 'dispatch_async' with an argument list of type '(dispatch_queue_t!, () -> _)'
dispatch_async(dispatch_get_main_queue()) {
cell.image.image = self.imageArray.append( image )
}
}
}
})
}
}
else{
println("Error in retrieving \(error)")
}
}
return cell
}
Hope you people understand that code.
The proper way to use that dispatch block is like so:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//perform your work here
self.imageArray.append(image)
cell.image.image = imageArray[indexPath.row]
})
Another reason that the compiler is giving this warning is because you're trying to append images to an array and set that as the cells image. What you should be doing is shown above.
Finally, I would recommend moving your queries out of cellForRowAtIndexPath: and into seperate methods as this is bad code design.
Edit: Rewrote method to provide examples of good code design...
var imageArray = [UIImage]()
func performLookupQuery() {
var query = PFQuery(className: "ParseClass")
query.findObjectsInBackgroundWithBlock {(objects: [AnyObject]?, error: NSError?)
if error == nil {
let imageObjects = objects as! [PFObject]
for object in imageObjects {
let thumbnail = object["columnInParse"] as! PFFile
thumbnail.getDataInBackgroundWithBlock{(imageData: NSData?, error: NSError?)
if error == nil {
if let image = UIImage(data: imageData!) {
imageArray.append(image)
//now your imageArray has all the images in it that you're trying to display
//you may need to reload the TableView after this to get it to display the images
}
}
}
}
}
self.tableView.reloadData()
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
cell.image.image = nil
cell.image.image = imageArray[indexPath.row]
return cell
}
I didn't add all the error checking in the query method like you should, I was just writing it to show what you would do. Now that you have this query method, you can call it in viewDidLoad and then the results will be available when you try to load your table.
override func viewDidLoad(animated: Bool) {
super.viewDidLoad(animated)
self.performLookupQuery()
}