UIImage returns nil on segue push - swift

I have an image URL that needs to be parsed and displayed. The URL exists, but returns nil.
It successfully parses in the cellForRowAt function by calling cell.recipeImage.downloadImage(from: (self.tableViewDataSource[indexPath.item].image))
With this line the image displays. However, it doesn't exist when calling it in didSelectRowAt
RecipeTableViewController.swift
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let resultsVC = Storyboard.instantiateViewController(withIdentifier: "ResultsViewController") as! ResultsViewController
// Information to be passed to ResultsViewController
if (tableViewDataSource[indexPath.item] as? Recipe) != nil {
if isSearching {
resultsVC.getTitle = filteredData[indexPath.row].title
//resultsVC.imageDisplay.downloadImage(from: (self.filteredData[indexPath.row].image))
} else {
resultsVC.getTitle = tableViewDataSource[indexPath.row].title
// Parse images
resultsVC.imageDisplay.downloadImage(from: (self.tableViewDataSource[indexPath.row].image))
}
}
// Push to next view
self.navigationController?.pushViewController(resultsVC, animated: true)
}
extension UIImageView {
func downloadImage(from url: String) {
let urlRequest = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil {
print(error!)
return
}
DispatchQueue.main.sync {
self.image = UIImage(data: data!)
}
}
task.resume()
}
}
ResultsViewController.swift
class ResultsViewController: UIViewController {
var getTitle = String()
var getImage = String()
#IBOutlet weak var recipeDisplay: UILabel!
#IBOutlet weak var imageDisplay: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
recipeDisplay.text! = getTitle
}
...
}
Returns the error
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

From my understanding, the app is getting crashed at this line:
recipeDisplay.text! = getTitle
If it is, obviously this is not the proper way to do it. Just remove the force unwrapping because the text on the label here is nil by default. Force referencing a nil value will crash the app.
recipeDisplay.text = getTitle
UPDATED:
- Let's make sure that you wired the label and the outlets properly. Connect ti to the VC, not the File Owner.

You're calling view-related code on views that haven't been initialized yet. Remember, IBOutlets are implicitly unwrapped properties, so if you try to access them before they're initialized they'll force-unwrap and crash. So it's not that the UIImage is coming up nil, it's that recipeDisplay is nil and is getting force unwrapped.
The idiomatic iOS thing to do is to hand a view model of some sort (an object or a struct) to the view controller, and then let it do the work with that item once it has finished loading.
So, in you didSelect method, you could create your view model (which you'd need to define) and hand it off like this:
let title = filteredData[indexPath.row].title
let imageURL = self.tableViewDataSource[indexPath.row].image
let viewModel = ViewModel(title: title, imageURL: imageURL)
resultsVC.viewModel = viewModel
And then in your resultsVC, you'd do something like this:
override func viewDidLoad() {
super.viewDidLoad()
if let vm = viewModel {
recipeDisplay.text = vm.title
downloadImage(from: vm.imageURL)
}
}
So in your case all you'd need to do is hand those strings to your VC (you can wrap them up in a view model or hand them off individually) and then in that VC's viewDidLoad() that's where you'd call downloadImage(from:). That way there's no danger of calling a subview before that subview has been loaded.
One last note: Your download method should be a little safer with its use of the data and error variables, and its references to self. Remember, avoid using ! whenever you don't absolutely have to use it (use optional chaining instead), and unless you have a really good reason to do otherwise, always use [weak self] in closures.
I'd recommend doing it like this:
func downloadImage(from url: String) {
let urlRequest = URLRequest(url: URL(string: url)!)
let task = URLSession.shared.dataTask(with: urlRequest) { [weak self] (data,response,error) in
if let error = error {
print(error)
return
}
if let data = data {
DispatchQueue.main.sync {
self?.image = UIImage(data: data)
}
}
}
task.resume()
}
Update: Because the 'view model' concept was a little too much at once, let me explain.
A view model is just an object or struct that represents the presentation data a screen needs to be in a displayable state. It's not the name of a type defined by Apple and isn't defined anywhere in the iOS SDK. It's something you'd need to define yourself. So, in this case, I'd recommend defining it in the same fine where you're going to use it, namely in the same file as ResultsViewController.
You'd do something like this:
struct ResultsViewModel {
let title: String
let imageURL: String
}
and then on the ResultsViewController, you'd create a property like:
var viewModel: ResultsViewModel?
or if you don't like dealing with optionals, you can do:
var viewModel = ResultsViewModel(title: "", imageURL: "")
OR, you can do what you're already doing, but I'd highly recommend renaming those properties. getTitle sounds like it's doing something more besides just holding onto a value. title would be a better name. Same criticism goes for getImage, with the additional criticism that it's also misleading because it sounds like it's storing an image, but it's not. It's storing an image url. imageURL is a better name.

Related

Why isn't my method getting called?

I have a viewController communicating with DownloaderHandler using DownloaderDelegate protocol.
My protocol is defined as:
protocol DownloaderDelegate : class {
func didReceive(data:Data)
}
I have a viewController
class ViewController: UIViewController {
weak var downloadHandler : DownloaderHandler?
override func viewDidLoad() {
super.viewDidLoad()
downloadHandler = DownloaderHandler()
downloadHandler?.delegate = self
changeBackground()
}
func changeBackground (){
let googleURL = URL(fileURLWithPath: "https://www.google.com/doodle4google/images/splashes/featured.png")
print(googleURL)
downloadHandler?.downloadData(url:googleURL) // Line BB
}
}
extension ViewController : DownloaderDelegate{
func didReceive(data: Data) {
let image = UIImage(data: data)
let imageView = UIImageView(image: image!)
view.insertSubview(imageView, at: 0)
}
}
And I have a Delegating class as :
class DownloaderHandler : NSObject, URLSessionDelegate{
weak var delegate :DownloaderDelegate?
var downloadsSession: URLSession = {
let configuration = URLSessionConfiguration.default
let session = URLSession(configuration: configuration)
return session // Line AA
}()
func downloadData(url: URL){ // Line CC
downloadsSession.dataTask(with: url){ data, response, error in
print("error is \(error), data is \(data) and response is \(response)") // Line DD
if let badError = error {
print(" the url didn't succeeed error is \(badError.localizedDescription)")
}
else if let someResponse = response as? HTTPURLResponse {
if someResponse.statusCode == 200{
self.delegate?.didReceive(data: data!)
}
}
}
}
}
Using breakpoints: Line AA, gets loaded. Line BB calls. Line CC never gets called. Why? What am I doing wrong?!
You have declared:
weak var downloadHandler : DownloaderHandler?
Then you say:
downloadHandler = DownloaderHandler()
downloadHandler is a weak reference, and nothing else retains this DownloaderHandler instance, so it vanishes in a puff of smoke after it is created. Your logging shows it being created, but if you were to log on its deinit you would also see it vanish immediately afterward. By the time you say downloadHandler?.downloadData(url:googleURL), your downloadHandler reference is nil and so nothing happens; you are talking to nobody at that point.
[You are probably slavishly following a mental rule that delegate references should be weak. But that rule is predicated on the assumption that the delegate has an independent existence, and thus should not be "owned" by the referrer. This object, however, has no independent existence; it is more a decorator object (what I would call a Helper). Thus, the reference needs to be strong. The back-reference is still weak, so you won't get a circular retain cycle.]
Remove the "weak" qualifier from the downloadHandler property on your view controller.
As it is the only reference to the downloadHandler object, it will be removed from memory as soon as the viewDidLoad method finishes executing.
You can make a small test; add a breakpoint to line BB and check if downloadHandler has a value. I suspect it will be "nil", because it is a weak property.

I'm getting nil error with image?

i'm getting fatal error: unexpectedly found nil while unwrapping an Optional value
imagedata is not nil it has a value of 2604750 bytes
I don't know why it show this error as I can see img1 is nil why ?
any comments !!!
#IBOutlet var img1: UIImageView!
#IBOutlet var img2: UIImageView!
// in viewWillAppear I gave it a default image
self.img1.image = UIImage(named: "dummy.png" )
self.img1.image = UIImage(named: "dummy.png" )
// i changed to send the nsmanagedobject but it's still same error
func setimage(person: NSManagedObject){
let data: NSData = NSData()
if person.valueForKey("picture") as! NSData == data{
if person.valueForKey("tag") as! Int == 1 {
img1.image = UIImage(named: "dummy" )
}else if person.valueForKey("tag") as! Int == 2 {
img2.image = UIImage(named: "dummy")
}}
else{
if person.valueForKey("tag") as! Int == 1 {
img1!.image = UIImage(data: person.valueForKey("picture") as! NSData )
}else if person.valueForKey("tag") as! Int == 2 {
img2.image = UIImage(data: person.valueForKey("picture") as! NSData )
}
}
}
So, you have a simple mistake. In fact your outlets were nil. However, not because you did not assign them in the storyboard, but because the setimage was called on a different instance of ViewController.
You have a property view1 in your second view controller which is declared as:
let view1: ViewController = ViewController()
This creates a NEW instance of ViewController. When you then call view1.setimage you get a crash because outlets for THIS instance are not connected.
The property in your second view controller should be
var view1: ViewController!
and in your imageTapped method of the ViewController you should modify code so it has this line:
view.view1 = self
Forced unwrapping might not be ideal, but it should work as long as you ensure that whenever you instantiate your second view controller you set the view1 property.
I think is the same thing as Andriy is suggesting. Probably your Outlet for the image is not connected with the view.
Your outlets are nil, there are a couple of reasons why this could happen.
They have not been connected in the interface builder.
You are accessing them before they have been instantiated, e.g. before viewDidLoad() has been called.

Cannot load view using NSNib

Anyone knows the correct way to instantiate a view using NSNib in Swift ?
The code below crashes.
import Cocoa
class ViewController: NSViewController {
var myview:NSView!
override func viewDidLoad() {
super.viewDidLoad()
let obj = AutoreleasingUnsafeMutablePointer<NSArray?>()
if let success = NSNib(nibNamed: "View", bundle: NSBundle.mainBundle())?.instantiateWithOwner(self, topLevelObjects: obj) where success {
if let m = obj.memory { // fatal error:
myview = m[0] as! NSView
}
}
}
}
You are seeing the fatal error there because the value you are trying to access is nil and you are forcefully trying to unwrap the optional. You should safely unwrap optionals this is what they are meant for. So, always use if statement when you try to access optionals, then you won't get this error.

SwiftyJSON how to correctly access variables?

I try to figure out SwiftyJSON but I'm facing a problem
The code shown below works fine
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://api.whitehouse.gov/v1/petitions.json")
var request = NSURLRequest(URL: url!)
var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)
if data != nil {
let hoge = JSON(data: data!)
let count = hoge["results"][0]["body"]
println(count)
}
}}
but when i try to add a method which accesses the hoge it returns nothing
code looks like this
func res() {
dump(hoge)
}
I tried to declare let hoge and let count in the header of ViewController, but it always gives errors.
How to do it correctly, so i can access array thorough all the code ?
Thanks in advance
If you declare a variable inside a function, like you do here in viewDidLoad, this variable is only available in the same scope, meaning that variable doesn't exist outside viewDidLoad. Actually it is even deallocated (destroyed) when the function execution finishes.
The solution is to create var hoge: JSON? at the root of your class, outside any function, then only assign the JSON value to this variable when it is available:
class ViewController: UIViewController {
var hoge: JSON?
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://api.whitehouse.gov/v1/petitions.json")
var request = NSURLRequest(URL: url!)
var data = NSURLConnection.sendSynchronousRequest(request, returningResponse: nil, error: nil)
if data != nil {
hoge = JSON(data: data!)
let count = hoge!["results"][0]["body"]
println(count)
}
}}
That way you can also create other methods that can access hoge outside of viewDidLoad.

UIImageView is NIL

I have a default image in viewItem to make sure that it is working, it shows on the detail view of the splitview.
#IBOutlet weak var ImageView: UIImageView!
var imageCache = [String: UIImage]()
override func viewDidLoad() {
super.viewDidLoad()
self.configureView()
}
func configureView() {
if let detail: AnyObject = self.detailItem {
if let label = self.detailDescriptionLabel {
let dict = detail as [String: String]
label.text = ""
let s = dict["result"]
let vr = NString(string: s!)
let vrd = vr.doubleValue
let value = ceil(vrd*20)
let valueString = String(format: "%.0f", value)
vresult.text = "\(valueString)%"
getPic(dict) // <---- trouble maker
fitem.hidden = false
ritem.hidden = false
}
} else {
navigationController?.popViewControllerAnimated(true)
}
}
func getPic(item: [String: String]) {
var chachedImage = self.imageCache[item["image"]!]
println(item["image"]) // <-- prints out the url
if cachedImage == nil {
var imgUrl = NSURL(string: item["image"]!)
let request: NSURLRequest = NSURLRequest(URL: imgUrl!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {( reponse: NSURLResponse!, data: NSData!, error; NSError!) -> Void in
if error == nil {
cachedImage = UIImage(data: data)
println("got here no problem") // <-- prints out
self.imageCache[item["image"]!] = cachedImage
println(self.imageCache) // <-- prints reference OK
dispatch_async(dispatch_get_main_queue(), {
self.ImageView.image = cachedImage // <---- offender
})
} else {
println("Error: \(error.localizedDescription)")
}
})
} else {
dispatch_async(dispatch_get_main_queue(), {
self.ImageView.image = cachedImage
})
}
}
ImageView is coming up nil every time.
fatal error: unexpectedly found nil while unwrapping an Optional value
but the default image shows. I've moved this out of the dispatch and even tried setting it straight from the viewDidLoad() always errors. It used to be a UIWebView and worked perfectly except that it would not cache anything. Since loading these images is a lot of work, I thought caching would be good, I've got caching working for thumbnails in the MASTER view.
It may be because of how your instaciating your viewcontroller.
let vc = MyViewController()
Something like this wont work. You're creating the VC without actually giving the storyboard a chance to link the IBOutlets. Instead use
storyboard.instantiateViewControllerWithIdentifier(identifier: String)
You may need to get reference to the storyboard using
let storyboard = UIStoryboard(name: name, bundle: NSBundle.mainBundle())
Hope this helps :)
Changing your variable name shouldn't make any difference except for readibility/maintainability unless there's a namespace conflict (good to understand why/where that might be happening). Also I was wondering - you made the IBOutlet'ed varable weak. When the last remaining strong ref to the object goes away, the weak references to the object are set nil by the runtime/garbage collector automatically. (Look up that section of the Swift documentation if you're not solid about it).
Maybe you should check your classes and controllers by adding deinit { println(,"function name deallocated' }. Between your use of weak and improved behavior seen when you change the variable name, it seems like there might be some weird (buggy) interactions going on in your app itself.
Well silly me. I've been working on this for a few days, I got the great idea to try and change the name, and it worked. I tried changing it back and it broke, apparently you can't use ImageView as a variable!
In my case was because I was using a nib and didn't register it.
Once I did registered it, it worked
My case Was Different I used
awakeFromNib()
instead of
viewDidLoad()
.