Show Image from URL in detailView after selection in tableView - swift

I have a tableView which display a list of traffic-cameras parsed from a XML file. When I select a cell, it sends the ImageURL, Roadname, Coordinates etc. via the prepareForSegue method.
In my detailviewcontroller I declare the incoming values as the following:
var selectedFeedURL = String()
var selectedFeedRoadname = String()
var selectedFeedLongitude = String()
var selectedFeedLatitude = String()
I have no problem printing all the values into the log or set it as labels. The problem occurs when I try to load the selectedFeedURL (which is the URL to the image, i.e: http://webkamera.vegvesen.no/kamera?id=559847) and set it to my imageView..
In my viewDidLoad, I have the following code, which should download the image and set it to my imageView named cameraImageView.
if let url = NSURL(string:"\(selectedFeedURL)") {
if let data = NSData(contentsOfURL: url){
print("Suksess")
cameraImageView.image = UIImage(data: data)
}
}
My imageView stays empty and I doesn't get any errors or complains in the debug area. If I print out the selectedFeedURL, the link is there.
The weird part is that if I change
NSURL(string:"\(selectedFeedURL)")
to
NSURL(string:"http://webkamera.vegvesen.no/kamera?id=559847")
Basically changing the variable to a camera URL, it works perfectly.
Soo... any suggestions on what the problem might be?
Much appreciated :)

If there are white spaces and newline characters at the start and end of your URL string, it won't work.
Try:
selectedFeedURL = selectedFeedURL.stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet())
if let url = NSURL(string: selectedFeedURL) {
if let data = NSData(contentsOfURL: url){
print("Suksess")
cameraImageView.image = UIImage(data: data)
}
}

Related

Swift UITableViewCell - Cell Image changes if I scroll down too fast, but only on the first attempt

I am parsing a JSON within my viewDidLoad method. One of the keys within this JSON is the image URL, which goes into a a string array called "allCImages"
This is just a string. Therefore to populate the image into the cell, in my cellForRowAt method, I have the following:
cell.vcCellImage.downloadImage(from: allCImages[indexPath.section])
Note: vcCellImage is the IBOutlet of my cell image view.
The "downloadImage" method is part of the following extension:
extension UIImageView {
func downloadImage(from imgURL: String!) {
let theUrl = URLRequest(url: URL(string: imgURL)!)
// set initial image to nil so it doesn't use the image from a reused cell
image = nil
// check if the image is already in the cache
if let imageToCache = vc1ImageCache.object(forKey: imgURL! as NSString) {
self.image = imageToCache
print("Image is in Cache")
return
}
// download the image asynchronously
let task = URLSession.shared.dataTask(with: theUrl) { (data, response, error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
// create UIImage
let imageToCache = UIImage(data: data!)
// add image to cache
vc1ImageCache.setObject(imageToCache!, forKey: imgURL! as NSString)
self.image = imageToCache
}
}
task.resume()
}
This is working almost perfectly. For example:
1) If I scroll down my tableview slowly, all the cells contain the correct image
2) If I scroll up my tableview, slowly or quickly, all the cells contain the correct image. This is proven by the fact that my console is printing the following:
Image is in Cache
Image is in Cache
Image is in Cache
I.e, the tableview is getting my image from the cache (since to scroll up, I must have scrolled down before)
3) The issue is if I scroll down my tableview really quickly, on the first attempt. Since the image has not cached yet, the cell will display the wrong image, before changing to the correct image. Classic problem
Therefore I am missing this small piece of logic. How to resolve this?
EDIT: I tried this but the issue remains:
class VCTableViewCell: UITableViewCell {
override func prepareForReuse() {
super.prepareForReuse()
vcCellImage.image = nil
}
This occurs because of
1- cell dequeueing : cells are re-used inside the tableView
2- when you scroll before a request happens it may cause a new 1 with same url
Best option is using SDWebImage
I have faced the similar issue.
I have fixed this issue by cancelling the image request in the prepareForReuse method.
Can you try the same?
first off all if you are appending your api or any data like this just remove this
var arr = [string]()
viewDidLoad()
{
arr.append("s","sd","sd)
}
accept this
var arr = [string]()
viewWillAppear()
{
arr.removeAll()
//call api
//append Data
arr.append("s","sd","sd)
}
I have similar problem then I solve it like this, may be it helpful for you also.

JSQMessages send media

Im building a small chat app using JSQMessages, and trying to send images from photo library or camera.
I got to the point where I saved the image to parse backend but I cannot display it on screen. I found few solutions but they were in obj-C, I tried to swift-fy but it failed.
I have extra column in parse "images" next to "message".
Here is what I tried:
var message = messages[indexPath.row]
if message != "" {
return messages[indexPath.row]
} else if message?.isMediaMessage == true {
let mediaItem:JSQMessageMediaData = message!.media
if (mediaItem.isKindOfClass(JSQPhotoMediaItem)) {
var photoItem: JSQPhotoMediaItem = mediaItem as! JSQPhotoMediaItem
var image: UIImage = photoItem.image
print("yay")
}
}
return messages[indexPath.row]
You are not actually setting the message content to the photo that you have saved on the backend. You have a var image: UIImage = photoItem.image that just saves it to a variable not to the message content that is being displayed. Also this does evaluate if (mediaItem.isKindOfClass(JSQPhotoMediaItem)) {
var photoItem: JSQPhotoMediaItem = mediaItem as! JSQPhotoMediaItem
so if set your message.media = (data from parse) it should show up.

Getting Image from URL for UIView [Swift]

Alright so I've made some handlers and classes to grab data from a URL, its all returning fine, I've checked the URLs are valid and everything.
Anyways, I'm trying to do an NSData(contentsOfURL) on a stored URL in my class for a UIViewController. I'm successfully printing out String Variables like name, type, description, but I'm having difficulty displaying an UIImage into a Image View on the ViewController.
Here is my Code, it's run when the View loads:
func configureView() {
// Update the user interface for the detail item.
if let card = detailCard {
title = card.cardTitle
//Assign Elements
detailDescriptionLabel?.text = card.description
typeLabel?.text = card.cardType
cardTitleLabel?.text = card.cardTitle
costLabel?.text = card.cardCost
print(card.cardImageURL)
if let url = NSURL(string: card.cardImageURL){
let data = NSData(contentsOfURL: url)
imageView.image = UIImage(data: data!)// <--- ERROR HERE: EXC_BAD_INSTRUCTION
}
//Print Log
//print(card.logDescription)
}
}
Like the comment says above, I get the error on the imageView.image = UIImage(data: data!) line:
Thread 1: EXC_BAD_INSTRUCTION
Here is the code for cardImageURL:
var cardImageURL: String {
return String(format: "http://testyard.example.net/display/images/cards/main/%#", image)
}
//This is a computed variable when a "Card" class object is created.
It returns the correct url in a string:
http://testyard.example.net/display/images/cards/main/armor-of-testing.jpg
I've used this code elsewhere, and it worked fine, why is it throwing errors here?
Thanks in advance.
Are you sure the data being downloaded is an image? You should probably use this line:
if let image = UIImage(data: data)`
This should stop the error, though you might need to test that your data actually contains an image.

Appending Data To URL In Swift

This may be a rudimentary question, but I am trying to figure out how to "append" strings to a URL, and have them maintain, through View Controllers. More specifically;
I am building a basic File Browser app that is getting its data from a web service (in XML) form. Each time the user taps on a "folder" (which is being displayed as a list of folders in a Table View), another request is made via a NSURL session to get the contents of that folder.
My issue is that the URL string is only appending the name of the row the user tapped on, but I am unsure how to have it populate all rows the user tapped.
let urlString = "http://myurl/"
After the user taps the desired folder...
let urlString = "http://myurl/\(tappedRow)"
// This prints as http://myurl/firstfoldername/
After the user taps the next desired folder...
let urlString = "http://myurl/\(tappedRow)"
// This prints as http://myurl/secondfoldername/
// But I want http://myurl/firstfoldername/secondfoldername/
Since I am just segueing from the TableViewController to itself, and reloading the table, I assume this is working as its supposed to, but I seek to have the url string keep appending the tapped rows to the end, rather than forgetting each time. I was thinking of using NSUserDefaults to keep the last URL, but I realized this must be a common occurrence and perhaps there's a better way. Thanks!
override func viewDidLoad() {
super.viewDidLoad()
let baseUrlString = "http://myUrl/"
var currentUrl = baseUrlString
}
func cellTapped() {
currentUrl += tappedRow
}
You could use a Stack of Strings. I prepared this singleton class.
class StackFolder {
private var path = [String]()
static let sharedInstance = StackFolder()
private init() {}
static private let basePath = "http://myurl"
var baseURL = NSURL(string: basePath + "/")!
func push(folder: String) {
path.append(folder)
}
func pop() {
path.removeLast()
}
func toURL() -> NSURL {
let folders = path.reduce("/") { $0 + $1 + "/" }
let adddress = Stack.basePath + folders
return NSURL(string: adddress)!
}
}
You just need to retrieve the shared instance of Stack and:
invoke push when you want to add a folder to the path
invoke pop when you want to remove the last added folder.
Here it is an example:
StackFolder.sharedInstance.push("folder1")
StackFolder.sharedInstance.toURL().absoluteString // "http://myurl/folder1/"
StackFolder.sharedInstance.push("folder2")
StackFolder.sharedInstance.toURL().absoluteString // "http://myurl/folder1/folder2/"
StackFolder.sharedInstance.pop()
StackFolder.sharedInstance.toURL().absoluteString // "http://myurl/folder1/"
Since Stack is a Singleton you don't need to pass a reference to the same Stack object from one view controller to the other. Each time you write Stack.sharedInstance in your app you automatically get the reference to the only instance of Stack.
Hope this helps.
Declare urlString as instance variable outside any method
var urlString = "http://myurl"
Define a function addPathComponent() and call it every time a row is tapped passing the folder name as the parameter. Using the method stringByAppendingPathComponent avoids confusion with the slash path separators.
func addPathComponent(component : String)
{
urlString = urlString.stringByAppendingPathComponent(component)
// do something with the new urlString value
}

Show indicator when save core data Swift

I have a button to save picture data in core data but when I push it, it is freezing because size of the data is big. I did try to use dispatch_async but it didn’t work. How do I create the icon/indicator showing that it is loading/bookmarking rather than just freezing?
#IBAction func save() {
let content = self.foodMenu?["content"].string
let urlString = self.foodMenu?["thumbnail_images"]["full"]["url"]
let urlshare = NSURL(string: urlString!.stringValue)
let imageData = NSData(contentsOfURL: urlshare!)
let images = UIImage(data: imageData!)
dispatch_async(dispatch_get_main_queue(), {
if let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext {
self.foodClass = NSEntityDescription.insertNewObjectForEntityForName("Foods",
inManagedObjectContext: managedObjectContext) as! Foods
self.foodClass.content = content
self.foodClass.image = UIImageJPEGRepresentation(images, 1)
var e: NSError?
if managedObjectContext.save(&e) != true {
println("insert error: \(e!.localizedDescription)")
return
}
}
First, it is unlikely it is the save that is slow. I would suspect that your creation of the JPEG representation is the slow part.
Second, you are wanting to hide a problem by putting up a spinner. That really is bad for the user experience. Far better to do the following (yes it is more code);
Move your image creation and saving to a background queue.
Restructure your Core Data stack so that your saves to disk are on a private queue.
This involves using a background queue and multiple contexts in Core Data but getting this data processing off the User Interface thread is the right answer.