Why a change in my firebase database makes my app crash - swift

I am building an application with WebView and Firebase as database. The url used to load the vewView comes from the database.
I have implemented both WebView and Firebase correctly but the problem is that when I change an url in the database, my app crashes. It basically saying that the url is nil...which is not in reality since the new url is completely valid.
Even when I rebuild my app, the error still continues....
class ViewController: UIViewController {
var dbRef: DatabaseReference!
var nextUrl = "https://www.google.co.uk/"
func retrieveUrl () {
dbRef = Database.database().reference().child("EXAMPLE")
dbRef.observe(.value) {
(snapshot) in
let value = snapshot.value as! NSDictionary
let url = value["url"]!
self.nextUrl = (url as! String)
print (nextUrl)
print ("webview is about to load")
let request = URLRequest (url: URL(string: nextUrl)!)
self.webView.load(request)
self.webView.addObserver(self, forKeyPath: #keyPath(WKWebView.isLoading), options: .new, context: nil)
}
print ("function observe is called")
}
override func viewDidLoad () {
super.viewDidLoad()
retrieveUrl()
}
Here is the fatal error:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

Related

UIImage returns nil on segue push

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.

How to prevent fatal error: unexpectedly found nil wile unwrapping an Optional value

So I'm new to core data, starting doing some tutorials and got a very basic setup where a 10 scores are saved from the AppDelegate and that works fine (am able to fetch and print from the console).
Then when I want to use a fetchrequest in another VC to retrieve the data which has been saved successfully, I get the following error:
fatal error: unexpectedly found nil while unwrapping an Optional value.
Here's the code to retrieve the data:
import Foundation
import CoreData
import UIKit
class testVC: UIViewController {
var managedObjectContext: NSManagedObjectContext!
var scores = [Scores]()
#IBOutlet weak var retrieveDataLabel: UIButton!
#IBOutlet weak var saveLabel: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func retrieveData(sender: AnyObject) {
let fetchRequest = NSFetchRequest(entityName: "Scores")
do {
if let results = try managedObjectContext.executeFetchRequest(fetchRequest) as? [Scores]{
scores = results
for result in results {
if let gameScore = result.valueForKey("gameScore") as? Int{
print("Your score is \(gameScore)")
}
}
}
}catch{
print("Error fetching data")
}}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Since I'm using if let .. shouldn't I be getting "Error fetching data"? instead of getting the fatal error? How come I can retrieve the data when loading the app but not when I try to retrieve the data in a different VC using the same code?
I've looked at other questions relating to fatal error found nil while unwrapping an optional value:
What does "fatal error: unexpectedly found nil while unwrapping an Optional value" mean?
fatal error: unexpectedly found nil while unwrapping an Optional value
and it seems that I'm basically trying to retrieve something which isn't there, but the data is saved indeed and I can retrieve it in the console when starting the app. Using the same fetch request I'd expect to get identical results when I load the app and retrieve the data but that's not the case.
What am I missing here? It feels like it should be very basic but for some reason just cannot get it to work after trying for 2 days.
app delegate code to save and retrieve data:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
//addTestData()
let fetchRequest = NSFetchRequest(entityName: "Scores")
do {
if let results = try managedObjectContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]{
for result in results {
if let gameScore = result.valueForKey("gameScore") as? Int{
print("Your score is \(gameScore)")
}
}
}
}catch{
print("Error fetching data")
}
return true
}
func addTestData(){
guard let entity = NSEntityDescription.entityForName("Scores", inManagedObjectContext: managedObjectContext) else{
fatalError("Could not find entity description")
}
for i in 1...10{
let score = Scores(entity: entity, insertIntoManagedObjectContext: managedObjectContext)
score.gameScore = i
}
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
abort()
}
}
Any help would be greatly appreciated!
When you use if let, you are checking to see if it is possible to make the result as an Int. You want to see if that result exists first. Try this:
for result in results {
if let gameScore = result.valueForKey("gameScore") {
print("Your score is \(gameScore as! Int)")
}
}
The issue is the nil NSManagedObjectContext (declared but no value)
By definition executeFetchRequest returns a non-optional array on success and since you are supposed to know from the Core Data model that the entity Scores returns always Scores objects you can safely write
do {
scores = try managedObjectContext.executeFetchRequest(fetchRequest) as! [Scores]
And obviously using a NSManagedObject subclass use the property directly to avoid the type casting. You need the optional binding only if the property gameScore is declared as optional.
for result in results {
if let gameScore = result.gameScore {
print("Your score is \(gameScore)")
}
However for a Int value like a score a non-optional is more reasonable, than you can reduce the code to
for result in results {
print("Your score is \(result.gameScore)")
}
So I did manage to get it working, using the following for managedObjectContext:
let managedContext = AppDelegate().managedObjectContext
I'm still puzzled why I didn't get the error which was specified in the catch but at least it's working now.
Thanks for your answers!

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()
.

Swift Core Data NSFetchRequest no results

I've encountered a problem when working with Core Data. I have this Core Data Model named 'Test.xcdatamodeld' and it contains only one entity named 'Data'. This Data entity contains one attribute named 'currCount' of type Int32.
What I'm trying to do is to read the value of 'currCount' but when I try to fetch something out of my entity 'Data' there are no results.
This is my fetching code:
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Data")
request.returnsObjectsAsFaults = false
var results: NSArray = context.executeFetchRequest(request, error: nil)!
var currCount:Int32 = 0
if results.count > 0 {
var res = results[0] as NSManagedObject
currCount = Int32(res.valueForKey("currCount") as Int)
println("Local Count: \(currCount)")
return currCount
}
else {
println("firstTime no Questions")
return 0
}
I always end up in the else-statement. I don't understand what I'm doing wrong. I've been going this way in another project and it works fine.
My AppDelegate.swift is as follows:
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
return true
}
func applicationWillResignActive(application: UIApplication!) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication!) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication!) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication!) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication!) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
// MARK: - Core Data stack
lazy var applicationDocumentsDirectory: NSURL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named "com.jqsoftware.MyLog" in the application's documents Application Support directory.
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1] as NSURL
}()
lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = NSBundle.mainBundle().URLForResource("Test", withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()
lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("Test.sqlite")
var error: NSError? = nil
var failureReason = "There was an error creating or loading the application's saved data."
if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil {
coordinator = nil
// Report any error we got.
var dict = [String: AnyObject]()
dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
dict[NSLocalizedFailureReasonErrorKey] = failureReason
dict[NSUnderlyingErrorKey] = error
error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
// Replace this with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(error), \(error!.userInfo)")
abort()
}
return coordinator
}()
lazy var managedObjectContext: NSManagedObjectContext? = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
if coordinator == nil {
return nil
}
var managedObjectContext = NSManagedObjectContext()
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
// MARK: - Core Data Saving support
func saveContext () {
if let moc = self.managedObjectContext {
var error: NSError? = nil
if moc.hasChanges && !moc.save(&error) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
NSLog("Unresolved error \(error), \(error!.userInfo)")
abort()
}
}
}
}
EDIT
Even if I save something to currCount before fetching it again, I get 0 results. This is the code I use for saving (which comes before the code above):
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var request = NSFetchRequest(entityName: "Data")
request.returnsObjectsAsFaults = false
var results: NSArray = context.executeFetchRequest(request, error: nil)!
var currentCount:Int32 = 5
var newCount = NSEntityDescription.insertNewObjectForEntityForName("Data", inManagedObjectContext: context) as NSManagedObject
newHp.setValue(currentCount, forKey: "currCount")
Once you have inserted something on your context, you should save it to make changes permanent (I suppose you are using a SQLite store).
In other words, you need to run a context.save(&error) method.
Update 1
var error: NSError?
if !context.save(&error) {
println("Error saving context: \(error?.localizedDescription)\n\(error?.userInfo)")
}