How to update CoreData data using Swift - ios

In myscenario, I am storing popupVC textfield title and descript values into CoreData. Once, stored Data into CoreData then fetching data into UITableView. Each and every Tableview cell I am having edit button within editActionsForRowAt. Once, user clicked the edit button I am passing the EditpopupVC.task = self.userData[indexPath.row]. In EditpopupVC I am receiving the values like var task: NSManagedObject? = nil. Now, After modification of data if user click update button then I am calling func update(title: String, descript: String) { } for passed particular value update.
Edit Button Click to Passing data from ListViewController to EditpopupVC
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .default, title: "Edit", handler: { (action, indexPath) in
print("Edit tapped")
let task = self.userData[indexPath.row]
let EditpopupVC = self.storyboard?.instantiateViewController(withIdentifier: "editviewcontroller") as! EditViewController
EditpopupVC.titlename = "Edit"
EditpopupVC.task = self.userData[indexPath.row]
EditpopupVC.modalTransitionStyle = .crossDissolve
EditpopupVC.modalPresentationStyle = .overFullScreen
self.present(EditpopupVC, animated: true, completion: nil)
})
return [editAction]
}
Once User Modified Text Data then Update button click to Calling Update Function
update(title: Titletextfield.text ?? "Empty", descript: Descriptiontextview.text)
CoreData Update Function I am using Below
func update(title: String, descript: String) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedObjectContext = appDelegate.persistentContainer.viewContext
task.title = title
task.descript = descript
do {
try managedObjectContext.save()
} catch {
print("Could not save. \(error)")
}
}
EditpopupVC.task = self.userData[indexPath.row] This line helping to pass the array of Data to EditpopupVC.

Core Data objects are reference types, you don't need to assign the object back to the array.
The crash occurs because you don't set tasks in the popupVC so it remains empty and raises a out-of-range exception.
In the main controller declare updateTitles with a more specific type
var updateTitles = [Myrecord]()
In popupVC delete tasks, declare task as
var task : Myrecord!
and replace update with
func update(title: String, descript: String) {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let managedObjectContext = appDelegate.persistentContainer.viewContext
task.title = title
task.descript = descript
do {
try managedObjectContext.save()
self.dismiss(animated: true, completion: nil)
} catch {
print("Could not save. \(error)")
}
}
the index path is not needed.
When the controller disappears reload the table view.
Side note:
Force unwrapping AppDelegate is fine. Without AppDelegate the application won't even launch.

Related

Swift - Unexpected rows added to CoreData

I have a CoreData base with 6 rows in it.
I na ViewController, the data is displayed in a UITable, when I select a row in the table, the didSelectRow
lists 6 rows. That are all the rows.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
caches = CoreData.getCaches()
print ("Amount \(caches.count)") // gives 6
performSegue(withIdentifier: "Select", sender: nil)
}
When the Segue is executed the prepareForSegue is executed. Now the same command results with the value 7.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
caches = CoreData.getCaches()
print ("Amount \(caches.count)") // gives 7
}
I suspect that something in the background is happening, but i can't find out what.
Below is the static method for reference:
static func getCaches() -> [Caches] {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var resultArray: [Caches] = []
let request = NSFetchRequest<Caches>(entityName: "Caches")
request.returnsObjectsAsFaults = false
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
let sortDescriptors = [sortDescriptor]
request.sortDescriptors = sortDescriptors
do {
resultArray = try context.fetch(request)
} catch {
print("Error - \(error)")
}
return resultArray
}
After a lot of searching I found it.
I execute a performSegueWithIdentifier. Which calls the prepareForSegue in the calling ViewController. But apparently before that, the variables/properties from the called VC are created. (Which is logical if you give it some thought)
In the called VC, a variable was initialised with the following code
(copied from somewhere on the net)
var cache = Caches((context: (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext))
This line of ode was causing the trouble. Cause it creates an entity in the persistentContainer (not written to the actual CoreData). I replaced it with a plain old:
var cache = Caches()
And everything is working okay now. Thanks for the support.

Save user input data of UITableView

There is an option in my app to add new excercises (via user input), i've managed to set the alert and add feature, but I can't save the data added and let it be persistent after closure and that.
//
// ExcerciseListViewController.swift
// OneRepMax
//
// Created by Mauro Garcia on 17/01/2019.
// Copyright © 2019 Mauro Garcia. All rights reserved.
//
import Foundation
import UIKit
import CoreData
class ExcerciseListViewController: UIViewController, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
title = "Excercises"
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
// This view controller itself will provide the delegate methods and row data for the table view.
tableView.delegate = self
tableView.dataSource = self
}
#IBOutlet weak var tableView: UITableView!
// Add a new excercise
#IBAction func addExcercise(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "New Excercise", message: "Add a new excercise", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save", style: .default) { [unowned self] action in
guard let textField = alert.textFields?.first,
let nameToSave = textField.text
else {
return
}
print(nameToSave)
self.excercises.append(nameToSave)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
alert.addTextField()
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
var excercises: [String] = []
}
// MARK: - UITableViewDataSource
extension ExcerciseListViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
print(excercises.count)
return excercises.count
}
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let cell =
tableView.dequeueReusableCell(withIdentifier: "Cell",
for: indexPath)
cell.textLabel?.text = excercises[indexPath.row]
return cell
}
// What happens when user touch an excercise
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let excerciseChosen = "\(excercises[indexPath.row])"
goBackToOneButtonTapped((Any).self)
excUserChose = excerciseChosen
print(excUserChose)
print("You tapped cell number \(indexPath.row).")
print(excerciseChosen)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "changeText"), object: nil)
}
func goBackToOneButtonTapped(_ sender: Any) {
performSegue(withIdentifier: "unwindToViewController", sender: self)
}
}
// TODO: save user input
In my AppDelegate.swift
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded 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.
*/
let container = NSPersistentContainer(name: "OneRepMax")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
I want to -when the user press "Save"- to save the data to be accesible after.
I've tried to implement CoreData but I have failed trying. Don't know what I'm doing wrong.
EDIT:
Code for adding and saving input
var excercisess: [NSManagedObject] = []
#IBAction func addExcercise(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "New Excercise", message: "Add a new excercise", preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save", style: .default) { [unowned self] action in
guard let textField = alert.textFields?.first,
let nameToSave = textField.text
else {
return
}
self.save(name: nameToSave)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
alert.addTextField()
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
func save(name: String) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
/*1.
Before you can save or retrieve anything from your Core Data store, you first need to get your hands on an NSManagedObjectContext. You can consider a managed object context as an in-memory “scratchpad” for working with managed objects.
Think of saving a new managed object to Core Data as a two-step process: first, you insert a new managed object into a managed object context; then, after you’re happy with your shiny new managed object, you “commit” the changes in your managed object context to save it to disk.
Xcode has already generated a managed object context as part of the new project’s template. Remember, this only happens if you check the Use Core Data checkbox at the beginning. This default managed object context lives as a property of the NSPersistentContainer in the application delegate. To access it, you first get a reference to the app delegate.
*/
let managedContext = appDelegate.persistentContainer.viewContext
/*
An NSEntityDescription object is associated with a specific class instance
Class
NSEntityDescription
A description of an entity in Core Data.
Retrieving an Entity with a Given Name here person
*/
let entity = NSEntityDescription.entity(forEntityName: "Excercise", in: managedContext)!
/*
Initializes a managed object and inserts it into the specified managed object context.
init(entity: NSEntityDescription,
insertInto context: NSManagedObjectContext?)
*/
let excercises = NSManagedObject(entity: entity, insertInto: managedContext)
//we can simply create person object this way also.
// let excercise = Excercise(context: managedContext)
/*
With an NSManagedObject in hand, you set the name attribute using key-value coding. You must spell the KVC key (name in this case) exactly as it appears in your Data Model
*/
excercises.setValue(name, forKeyPath: "name")
/*
You commit your changes to person and save to disk by calling save on the managed object context. Note save can throw an error, which is why you call it using the try keyword within a do-catch block. Finally, insert the new managed object into the people array so it shows up when the table view reloads.
*/
do {
try managedContext.save()
excercisess.append(excercises)
tableView.reloadData()
print("SAVED")
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
And the code for fetching:
func fetchAllExcercises(){
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
/*Before you can do anything with Core Data, you need a managed object context. */
let managedContext = appDelegate.persistentContainer.viewContext
/*As the name suggests, NSFetchRequest is the class responsible for fetching from Core Data.
Initializing a fetch request with init(entityName:), fetches all objects of a particular entity. This is what you do here to fetch all Person entities.
*/
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Excercise")
/*You hand the fetch request over to the managed object context to do the heavy lifting. fetch(_:) returns an array of managed objects meeting the criteria specified by the fetch request.*/
do {
excercisess = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
There are several things that you still need once you have the xcdatamodeld file set up.
Your array should be an array of your CoreData entity.
var exercises = [Exercise]()
You need to create an instance of the managed object context.
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
Then you will need to use that instance to create new objects in the array, save the new objects and fetch them. Have a go at finding out how to create those functions (Youtube, web articles) and then see if you still have questions.

IOS Swift how can I reload a tableView from a different controller

I have 2 controllers A and Controller B . Controller A has a TableView and Controller B is a subview that when clicked opens a form and on Submit it enters data into the database. My problem is that I attempt to reload my TableView from Controller B from the user hits submit and I get the following error
fatal error: unexpectedly found nil while unwrapping an Optional value from this line
self.TableSource.reloadData()
Now the data from Controller B is successfully inserted so after I restart my app the data I submit is there . This is my code (TableSource is the TableView outlet)
Controller A
func reloadTable(latmin: Float,latmax: Float,lonmin: Float, lonmax: Float) {
let url:URL = URL(string:ConnectionString+"MY-URL")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let parameter = "parameters"
request.httpBody = parameter.data(using: String.Encoding.utf8)
session.dataTask(with:request, completionHandler: {(data, response, error) in
if error != nil {
} else {
do {
let parsed = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String:Any]
if let S = parsedData["myData"] as? [AnyObject] {
for A in Data {
// gets Json Data
}
DispatchQueue.main.async {
// This is what I named my TableView
self.TableSource.reloadData()
}
}
} catch let error as NSError {
print(error)
}
}
}).resume()
}
That is my HTTP-Request that gets data from the database, now in that same Controller A I have a button that when clicked opens the SubView to Controller B and this is the code
#IBAction func Post_Action(_ sender: Any) {
let Popup = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ControllerB") as! Controller B
self.addChildViewController(Popup)
Popup.view.frame = self.view.frame
self.view.addSubview(Popup.view)
Popup.didMove(toParentViewController: self)
}
This is the code in Controller B and this is how I try to reload the TableView in Controller A
#IBAction func Submit_Form(_ sender: Any) {
// Code that submits the form no issues here
latmin = 32.18
latmax = 32.50
lonmin = -81.12
lonmax = -81.90
let Homepage = ControllerA()
Homepage.reloadTable(latmin: latmin!,latmax: latmax!,lonmin: lonmin!,lonmax: lonmax!)
}
So as stated before Controller A loads the data from the Database, Controller B has a form and when submitted it enters new data into the database . That whole process works I just now want to update the TableView in Controller A from the form is submitted in Controller B
I would suggest using protocol:
protocol SomeActionDelegate {
func didSomeAction()
}
In ViewController B
var delegate: SomeActionDelegate?
In ViewController A when segue
viewControllerB.delegate = self
You should add this
extension ViewControllerA: SomeActionDelegate {
func didSomeAction() {
self.tableView.reloadData()
}
}
And in ViewController B
func didChangeSomething() {
self.delegate?.didSomeAction()
}
It works like when ViewController B didChangeSomething() it sends message to ViewController A that it should didSomeAction()
You can do it with NSNotification
in swift 3.0
Think you have two viwe controllers called viewcontrollerA and viewControllerB
viewcontrollerA has the tableview.
you need to reload it from viewcontrolerB
implementaion of viewcontrollerA
create a function to relod your tableview in viewcontrollerA and call it in viewDidLoad
override func viewDidLoad() {
let notificationNme = NSNotification.Name("NotificationIdf")
NotificationCenter.default.addObserver(self, selector: #selector(YourControllername.reloadTableview), name: notificationNme, object: nil)
}
func relodaTableview() {
self.TableSource.reloadData()
}
implementation in viewcontrollerB (where you want to reload tableview)
post the notification in button click or anywhere you want like below
let notificationNme = NSNotification.Name("NotificationIdf")
NotificationCenter.default.post(name: notificationNme, object: nil)
hope this will help to you.

UITableView reloadData not working when viewcontroller is loaded a 2nd time

I have a UITableView with custom cell displaying a list of files that can be downloaded. The cell displays the filename and download status. Everything working fine except one scenario :
The user downloads a file and navigates back to the home screen while file download in progress...
He comes back to the previous screen. File download still in progress.
File download complete. I am using tableview.reloadData() at this point to refresh the download status to "Download Complete" but reloadData() not working in this scenario. The cell label still shows "Download in progress".
Scrolling the tableview to get the cell out of screen and back refreshes the cell correctly. Anyway to do this programmatically?"
Otherwise, in normal case where user doesn't change screen, reloadData() is working fine.
Any idea how to fix this?
Thanks
I have used alamofire download with progress in the function below which is inside my UIViewController.
func DownloadFile(fileurl: String, indexPath: NSIndexPath, itemPath: String, itemName: String, itemPos: String, ItemSelected:Bool) {
let cell = myTableView.cellForRowAtIndexPath(indexPath) as! myCustomCell
let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
Alamofire.download(.GET, fileurl, destination: destination)
.progress {bytesRead, totalBytesRead, totalBytesExpectedToRead in
// This closure is NOT called on the main queue for performance
// reasons. To update your ui, dispatch to the main queue.
dispatch_async(dispatch_get_main_queue()) {
print("Total bytes read on main queue: \(totalBytesRead) / \(totalBytesExpectedToRead)")
let progress = Int((Double(totalBytesRead)/Double(totalBytesExpectedToRead)) * 100)
cell.lblMoreRuqyaFileInfo.text = "Downloading file...(\(progress)%)"
}
}
.response { _, _, _, error in
if let error = error {
print("\(error)")
cell.lblMoreRuqyaFileInfo.text = "Failed to download file. Please try again."
} else {
cell.lblMoreRuqyaFileInfo.text = "File Downloaded sucessfully"
//reloadData() not working from here
self.myTableView.reloadData()
}
}
}
The above func is being called in the tableview's editActionsForRowAtIndexPath below.
func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
if myTableView.cellForRowAtIndexPath(indexPath) == nil {
let action = UITableViewRowAction()
return [action]
}
let cell = myTableView.cellForRowAtIndexPath(indexPath) as! myCustomCell
let fileurl = cell.textLabel!.text
let ruqyainfo = cell.lblMoreRuqyaFileInfo.text
let sItemPath = cell.lblCompiledRuqya.text! + "->" + cell.textLabel!.text! + "->\(indexPath.section)->\(indexPath.row)"
let sItemName = cell.lblCompiledRuqya.text!
let sItemPos = "->\(indexPath.section)->\(indexPath.row)"
var bItemSelected:Bool = false
if myTableView.cellForRowAtIndexPath(indexPath)?.accessoryType == UITableViewCellAccessoryType.Checkmark {
bItemSelected = true
} else {
bItemSelected = false
}
//check if file already downloaded,return empty action, else show Download button
if ruqyainfo?.containsString("Download") == false {
let action = UITableViewRowAction()
return [action]
}
let line = AppDelegate.dictCompiledRuqya.mutableArrayValueForKey(AppDelegate.dictCompiledRuqya.allKeys[indexPath.section] as! String)
let name = line[indexPath.row].componentsSeparatedByString("#")
let DownloadAction = UITableViewRowAction(style: .Normal, title: "Download\n(\(name[3]))") { (action: UITableViewRowAction!, indexPath) -> Void in
self.myTableView.editing = false
AppDelegate.arrDownloadInProgressItem.append(name[0])
self.DownloadFile(fileurl!, indexPath: indexPath, itemPath: sItemPath, itemName: sItemName,itemPos: sItemPos, ItemSelected: bItemSelected)
}
DownloadAction.backgroundColor = UIColor.purpleColor()
return [DownloadAction]
}
//Objective C
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.yourTableView reloadData];
}
//Swift
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.yourTableView.reloadData()
}
you can use delegates so that the download controller can tell the first controller that the download is complete, and that it should redraw the tableView - or at least the indexPath that has just completed.
set up the download controller like this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
let destinationController : DownloadController = segue.destinationViewController as! DownloadController
destinationController.delegate = self
destinationController.indexRowToRefresh = currentlySelectedIndexRow
}
and then in the download completion closure, add something like this
delegate.refreshTableRow(indexRowToRefresh)
and in your first controller, implement the delegate method to refresh this row (or the whole table)
func refreshTableRow(indexRowToRefresh : Int)
{
var indexPath = NSIndexPath(forRow: indexRowToRefresh, inSection: 0)
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Top)
}
Hey #John Make sure your TableView datasource is reflected with file upload status permanently.I think, when you complete file uploading you change status in tableview datasource
1. First scenario as you are switching to second view controller, and coming back to previous view, it might be reinitializing your datasource. Data source might wasn't permanently reflected.2. In normal scenario, as you are on same view controller(not switching to other). Tableview datasource might be not reinitialized holding updated file upload status.
I suggest to save user file download status saved at some persistant storage or on web server. That doesn't leads to inconsistency in data.
It could be a thread related issue (aka you're coming back from the download thread, not on the UI thread, hence the data is refreshed but not displayed).
Try using this on selfand pass it a selector that refreshes your tableview.
performSelectorOnMainThread:
You used :
let cell = myTableView.cellForRowAtIndexPath(indexPath) as! myCustomCell
and set text for lblMoreRuqyaFileInfo :
cell.lblMoreRuqyaFileInfo.text = "File Downloaded sucessfully"
so, you don't have to call self.myTableView.reloadData()
Try:
dispatch_async(dispatch_get_main_queue()) {
cell.lblMoreRuqyaFileInfo.text = "File Downloaded sucessfully"
}
p/s and where did you call functionDownloadFile , show it, plz!

Getting " unexpectedly found nil" error when setting "self imageView! image" in Swift

I am getting the above error in this collection view-detail view code.
Detail VC:
class DetailViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView?
var photo: NSDictionary?
var image1:UIImage = UIImage()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
//downloadFromUrl(url)
if let photo = photo {
let url1 = (photo["images"]!["thumbnail"]!!["url"])! as! String
let url:NSURL = NSURL(string: url1 as String)!
//print(url)
downloadFromUrl(url)
}
}
func downloadFromUrl(url: NSURL) {
//self.imageView.hnk_setImageFromURL(url)
let config:NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session:NSURLSession = NSURLSession(configuration: config)
let request:NSURLRequest = NSURLRequest(URL: url)
let task = session.downloadTaskWithRequest(request) { (location, response, error) -> Void in
let data:NSData = NSData(contentsOfURL: location!)!
let image:UIImage = UIImage(data: data)!
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.image1 = image
print(self.image1)
self.imageView.image = self.image1
})
}
task.resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
And here is some methods in collection VC:
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! PhotosViewCell
cell.backgroundColor = UIColor.brownColor()
cell.photo = self.photos[indexPath.row] as? NSDictionary
//self.p = self.photos[indexPath.row] as! NSDictionary
//print("Link is \(cell.photo!["link"]!)")
return cell
}
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let p = self.photos[indexPath.row] as! NSDictionary
let VC:DetailViewController = DetailViewController()
VC.photo = p
self.presentViewController(VC, animated: true, completion: nil)
}
I know I am probably getting this error because viewDidLoad() is not yet ready but what is the solution and I do need this dictionary inside viewDidLoad() because I need to call that downloadFromUrl() method.
Oh, I am not using prepareforsegue() method. tried that too, maybe I was doing wrong.
In the debug area, I am getting the following:
2016-01-06 13:30:55.075 AuthCheck[3623:74910] Warning: Attempt to present <AuthCheck.DetailViewController: 0x7ff103dfb6c0> on <AuthCheck.PhotosViewController: 0x7ff103c2fd90> whose view is not in the window hierarchy!
<UIImage: 0x7ff103cd1820>, {150, 150}
fatal error: unexpectedly found nil while unwrapping an Optional value
Which means correct image is being downloaded but it is not being able to set to imageView's image property. No idea why?
Will someone tell me the solution please?
You are getting a runtime error because you are force-unwrapping an optional that happens to contain no value (i.e., it is nil). Please check your optionals before unwrapping (it is good practice):
print(photo!) // <--Dangerous
if let photo = photo {
print(photo) // <--Safe
}
...unless your code path makes it 100% sure that the optional does contain a value.
You are downloading data from the internet; that should typically be an asynchronous operation, because it takes significant time and you don't want to freeze your main thread (user interface) while it completes.
So you need to perform it on the background, and use the obtained data inside a completion handler (i.e., block or closure).
ViewDidLoad() is called when the view controller is loaded, meaning that it happens when you create the instance, before you present the view controller
// viewDidLoad() is called right here
let VC:DetailViewController = DetailViewController()
// this happens after you try to print photo
VC.photo = p
This means that your optional variable photo is still null when you print it. To avoid the error, you could unwrap the variable with
if let photo = photo {
print(photo)
}
Although this wouldn't help you accomplish your goal. So, do your tasks in viewDidAppear rather than viewDidLoad, which is called after the view becomes visible.
override func viewDidAppear() {
super.viewDidAppear()
print(photo!)
downloadFromUrl(url)
}
Or, if you need to do the task before the view appears, create a separate function in DetailViewController and call it after assigning VC.photo.
func task() {
print(photo!)
downloadFromUrl(url)
}
then in CollectionView, do
let p = self.photos[indexPath.row] as! NSDictionary
let VC:DetailViewController = DetailViewController()
VC.photo = p
VC.task()
Actually a safer way to do it is to call downloadFromUrl(url) from the didSet method like this :
var photo: NSDictionary = NSDictionary() {
didSet {
// your code to get the url here you can test if photo is nil
downloadFromUrl(url)
}
}
But this is not the problem here. Since you don't use the storyboard to initialise your viewController (let VC = DetailViewController()) you never instantiate the content of your viewController (for exemple the imageView)

Resources