Finding nil with optional managedObject, using CoreData and NSFetchedResultsController - swift

I am trying to populate a collectionView with some CoreData. I'm programming in Swift and am using iOS 9. I have most everything hooked up correctly (to my knowledge), but I keep having a crash due to one line of code.
Here is the code from my viewController. The line that errors out with nil is "managedObjectContext: coreDataStack.context":
override func viewDidLoad() {
super.viewDidLoad()
//1
let fetchRequest = NSFetchRequest(entityName: "Family")
let firstNameSort =
NSSortDescriptor(key: "firstName", ascending: true)
fetchRequest.sortDescriptors = [firstNameSort]
//2
fetchedResultsController =
NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: coreDataStack.context,
sectionNameKeyPath: nil,
cacheName: nil)
fetchedResultsController.delegate = CollectionViewFetchedResultsControllerDelegate(collectionView: familyCollectionView)
//3
do {
try fetchedResultsController.performFetch()
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
}
Below is the "CoreDataStack" object that I reference:
import Foundation
import CoreData
class CoreDataStack {
let modelName = "CoreDataModel"
lazy var context: NSManagedObjectContext = {
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = self.psc
return managedObjectContext
}()
private lazy var psc: NSPersistentStoreCoordinator = {
let coordinator = NSPersistentStoreCoordinator(
managedObjectModel: self.managedObjectModel)
let url = self.applicationDocumentsDirectory
.URLByAppendingPathComponent(self.modelName)
do {
let options =
[NSMigratePersistentStoresAutomaticallyOption : true]
try coordinator.addPersistentStoreWithType(
NSSQLiteStoreType, configuration: nil, URL: url,
options: options)
} catch {
print("Error adding persistent store.")
}
return coordinator
}()
private lazy var managedObjectModel: NSManagedObjectModel = {
let modelURL = NSBundle.mainBundle()
.URLForResource(self.modelName,
withExtension: "momd")!
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()
private lazy var applicationDocumentsDirectory: NSURL = {
let urls = NSFileManager.defaultManager().URLsForDirectory(
.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1]
}()
func saveContext () {
if context.hasChanges {
do {
try context.save()
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
abort()
}
}
}
}
Any ideas on why I'm picking up nil when I call that line?

The problem exists because self.coreDataStack was never assigned however it was accessed when the fetchedResultsController was defined.
To simply solve the problem, create and assign as instance of CoreDataStuck to your view controller's coreDataStack property before it is accessed:
self.coreDataStack = CoreDataStack()
I would like to stress the importance of managing the NSManagedObjectContext however. I believe it is a more common pattern to create an instance of CoreDataStack and pass a reference to it as you need it. For instance, you can create an instance of it within your Application Delegate and assign the reference to your first view controller with something like this:
lazy var cds = CoreDataStack()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSOBject: AnyObject?]?) -> Bool {
// Obtain a reference to your root view controller, this may have to be different if you use different types of controllers. This would be the View controller where you use the core data stack in your question though.
let vc = window!.rootViewController as! ViewController
vc.coreDataStack = self.cds // This will now ensure the property on your view controller is assigned and can be accessed.
return true
}
Later on in your app, I would then suggest passing this same reference around. You could do it by accessing the app delegate, personally I think it's cleaner to just pass along a reference through the prepare for segue method. I'll just imagine there's a SecondViewController class that exists in your app and that you're transitioning to it from the ViewController in your question:
class SecondViewController: UIViewController {
var cds: CoreDataStack!
}
I'll assume there's a segue between ViewController and SecondViewController with the name "next". In the ViewController file, define the prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "next" {
let secondVc = segue.destinationViewController as! SecondViewController
secondVc.cds = self.coreDataStack
}
}
I tried to stress the idea of keeping a reference to the instance of the core data stack that's first created, I think as you go along you'll find it more beneficial.

Related

How do I reload UIScrollView containing ViewControllers to add/remove view controllers

I have a scrollView that contains a dynamic amount of WeatherViewControllers each displaying the weather data of a different city the user has saved. The user can segue from the WeatherViewControllers to a CityListViewController. Where they can add and remove cities from their list which in turn should add and remove WeatherViewControllers from the scrollView upon dismissing the CityListViewController, this is where I am running into a problem.
Currently I am trying to use a protocol to call viewDidLoad in the scrollViewController upon dismissing the CityListViewController but am getting an error:
Fatal error: Unexpectedly found nil while unwrapping an Optional
value: file)
when it gets to:
let weatherScreen = storyboard!.instantiateViewController(identifier: "View Controller") as! ViewController
Side Note: Upon initially opening the app the scrollView loads properly with all the correct WeatherViewControllers in the UIScrollView and the correct cities in the list.
class ScrollViewController: UIViewController, ScrollReloadProtocol {
func reloadScrollView() {
print("SCROLL RELOADED!!!!!*******")
self.viewDidLoad()
}
#IBOutlet weak var totalScrollView: UIScrollView!
var pages = [ViewController]()
var x = 0
var weatherScreensArray = [SavedCityEntity]()
var weatherScreenStringArray = [String]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var horizString = "H:|[page1(==view)]"
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
//userDefaults used to keep track of which screen is which to put different cities on different viewControllers
defaults.set(0, forKey: "screenNumber")
//load cities to get number of cities saved
loadCities()
var views : [String: UIView] = ["view": view]
//create all weatherWeatherControllers
while x <= weatherScreensArray.count {
pages.append(createAndAddWeatherScreen(number: x))
weatherScreenStringArray.append("page\(x+1)")
views["\(weatherScreenStringArray[x])"] = pages[x].view
let addToHoriz = "[\(weatherScreenStringArray[x])(==view)]"
horizString.append(addToHoriz)
x+=1
}
horizString.append("|")
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[page1(==view)]|", options: [], metrics: nil, views: views)
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: horizString, options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views)
NSLayoutConstraint.activate(verticalConstraints + horizontalConstraints)
}
//Function to create and add weatherViewController
func createAndAddWeatherScreen(number: Int) -> ViewController {
defaults.set(number, forKey: "screenNumber")
let weatherScreen = storyboard!.instantiateViewController(identifier: "View Controller") as! ViewController
weatherScreen.view.translatesAutoresizingMaskIntoConstraints = false
totalScrollView.addSubview(weatherScreen.view)
addChild(weatherScreen)
weatherScreen.didMove(toParent: self)
return weatherScreen
}
}
You could try something like this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let weatherScreen = storyboard.instantiateViewController(withIdentifier: "View Controller") as! ViewController
It's also probably better to use one word for the identifier: "ViewController", and give it the same name as the class. Make sure to set these values also in your actual storyboard.

Why delegate event is not received swift?

I would like to pass data from EditPostViewController to NewsfeedTableViewController using delegates, but func remove(mediaItem:_) is never called in the adopting class NewsfeedTableViewController. What am I doing wrong?
NewsfeedTableViewController: UITableViewController, EditPostViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
//set ourselves as the delegate
let editPostVC = storyboard?.instantiateViewController(withIdentifier: "EditPostViewController") as! EditPostViewController
editPostVC.delegate = self
}
//remove the row so that we can load a new one with the updated data
func remove(mediaItem: Media) {
print("media is received heeeee")
// it does't print anything
}
}
extension NewsfeedTableViewController {
//when edit button is touched, send the corresponding Media to EditPostViewController
func editPost(cell: MediaTableViewCell) {
let editPostVC = storyboard?.instantiateViewController(withIdentifier: "EditPostViewController") as? EditPostViewController
guard let indexPath = tableView.indexPath(for: cell) else {
print("indexpath was not received")
return}
editPostVC?.currentUser = currentUser
editPostVC?.mediaReceived = cell.mediaObject
self.navigationController?.pushViewController(editPostVC!, animated: true)
}
protocol EditPostViewControllerDelegate: class {
func remove(mediaItem: Media)
}
class EditPostViewController: UITableViewController {
weak var delegate: EditPostViewControllerDelegate?
#IBAction func uploadDidTap(_ sender: Any) {
let mediaReceived = Media()
delegate?.remove(mediaItem: mediaReceived)
}
}
The objects instantiating in viewDidLoad(:) and on edit button click event are not the same objects. Make a variable
var editPostVC: EditPostViewController?
instantiate in in viewDidLoad(:) with delegate
editPostVC = storyboard?.instantiateViewController(withIdentifier: "EditPostViewController") as! EditPostViewController
editPostVC.delegate = self
and then present it on click event
navigationController?.pushViewController(editPostVC, animated: true)
or
present(editPostVC, animated: true, completion: nil)
you can pass data from presenter to presented VC before or after presenting the VC.
editPostVC.data = self.data
I suggest having a property in NewsfeedTableViewController
var editPostViewController: EditPostViewController?
and then assigning to that when you instantiate the EditPostViewController.
The idea is that it stops the class being autoreleased when NewsfeedTableViewController.viewDidLoad returns.

Swift: Passing managedObjectContext from AppDelegate to UINavigationController

I have some iOS project (Project) with an #escaping method in the AppDelegate that is used to create the NSPersistentContainer for this project. This method is then called within the didFinishLaunchingWithOptions method as follows:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var persistentContainer: NSPersistentContainer!
func createProjectContainer(completion: #escaping (NSPersistentContainer) -> ()) {
let container = NSPersistentContainer(name: "Project")
container.loadPersistentStores { (_, error) in
guard error == nil else { fatalError("Failed to load store: \(error!)") }
DispatchQueue.main.async { completion(container) }
}
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
createProjectContainer { container in
self.persistentContainer = container
let storyboard = self.window?.rootViewController?.storyboard
guard let vc = storyboard?.instantiateViewController(withIdentifier: "NavigationController") as? NavigationController else {
fatalError("Cannot instantiate root view controller")
}
vc.managedObjectContext = container.viewContext
self.window?.rootViewController = vc
}
return true
}
}
The NavigationController class is simply a subclass of UINavigationController, which embeds a UIViewController (ChildViewController) and has an NSManagedObjectContext variable (managedObjectContext), which I want to pass to the first ChildViewController:
class NavigationController: UINavigationController {
var managedObjectContext: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
guard let vc = self.childViewControllers.first as? ChildViewController else { fatalError("") }
vc.managedObjectContext = managedObjectContext
print("NavigationController: viewDidLoad")
print("managedObjectContext ---> \(managedObjectContext)")
}
}
Now the print("NavigationController: viewDidLoad") appears to be called twice. On the first call, managedObjectContext = nil, on the second call it has been assigned. Does this matter that the app is loading this twice?
It seems to be happening during the storyboard?.instantiateViewController(withIdentifier: "NavigationController") which is called after the first time that NavigationController has been loaded due to the #escaping property of the closure. However, if I exclude that line, as just get the reference to the NavigationController the managedObjectContext appears never to be assigned.

Prepare for segue data not being loaded

I have a carousel and each carousel item contains a unique tableview. When you tap the add button it takes you to a new view controller so that you can give the tableview a name and associate items to display in each cell. Now when I hit the save button it performs the segue back to the carousel's viewcontroller. My prepareforsegue looks like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! BillSplittersViewController
let passedBill: NSManagedObject = bill as NSManagedObject
destinationVC.allBillSplitters = getUpdatedSplitters()
print(2)
print(destinationVC.allBillSplitters)
destinationVC.billName = billName
destinationVC.bill = passedBill
}
When the carousel view controller is displayed it doesn't show the added item in the carousel which loads from the allBillSplitters array. If I try reloading the carousel in viewdidload, viewwillappear or view willlayoutsubviews nothing changes. It does update if I do it in viewdidlayoutsubviews but you can see this happening which isn't great.
To debug I tried printing the array in prepareforsegue and then in viewdidload and it prints the array in prepareforsegue without the added item and the does the same thing in viewdidload -- but then it prints them again with the added item -- but I can't see it in the loaded view.
I'm using iCarousel if that helps. I am new to this and unsure what's going on so I don't know what code to use. I am using storyboards for both viewcontrollers and the segue is attached to the view and not the button itself. Any help would be great. Thanks!
More information:
#IBAction func saveButtonWasPressed() {
let managedContext = bill.managedObjectContext
if let splitter = splitter {
splitter.mutableSetValue(forKey: "items").removeAllObjects()
setBillSplitterValues(splitter)
setSelectedItemsToBillSplitter(splitter)
} else {
let entity = NSEntityDescription.entity(forEntityName: "BillSplitter", in: managedContext!)
let newBillSplitter = NSManagedObject(entity: entity!, insertInto: managedContext)
setBillSplitterValues(newBillSplitter)
setSelectedItemsToBillSplitter(newBillSplitter)
}
do {
try managedContext!.save()
}
catch let error as NSError {
print("Core Data save failed: \(error)")
}
self.performSegue(withIdentifier: "segueToBillSplitters", sender: self)
}
retrieving the new billSplitters:
func getUpdatedSplitters() -> [BillSplitter] {
var allBillSplitters = [BillSplitter]()
let managedContext = bill.managedObjectContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "BillSplitter")
let predicate = NSPredicate(format: "ANY bills == %#", bill)
fetchRequest.predicate = predicate
do {
let results =
try managedContext!.fetch(fetchRequest)
allBillSplitters = results as! [BillSplitter]
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
}
return allBillSplitters
}
So when pressing save, it either updates an existing 'billSplitter' to coredata or adds a new one, then calls performSegue. In prepareForSegue it then fetches all the 'billSplitters' from CoreData and passes them to the destination view controller.
I thought a NSManagedObjectContextDidSave observer might help if it called performSegue once the notification was received but nothing changed.
Update:
I have tried the following in my saveButtonWasPressed method. When the viewcontroller loads, it quickly shows the old carousel then flickers and shows the updated carousel. I'm not sure where to go from here.
#IBAction func saveButtonWasPressed() {
DispatchQueue.global(qos: .background).async { [weak weakSelf = self] in
let managedContext = weakSelf?.bill.managedObjectContext
if let splitter = weakSelf?.splitter {
splitter.mutableSetValue(forKey: "items").removeAllObjects()
weakSelf?.setBillSplitterValues(splitter)
weakSelf?.setSelectedItemsToBillSplitter(splitter)
} else {
let entity = NSEntityDescription.entity(forEntityName: "BillSplitter", in: managedContext!)
let newBillSplitter = NSManagedObject(entity: entity!, insertInto: managedContext)
weakSelf?.setBillSplitterValues(newBillSplitter)
weakSelf?.setSelectedItemsToBillSplitter(newBillSplitter)
}
do {
try managedContext!.save()
}
catch let error as NSError {
print("Core Data save failed: \(error)")
}
DispatchQueue.main.async { [weak weakSelf = self] in
guard let weakSelf = weakSelf else { return }
weakSelf.performSegue(withIdentifier: "segueToBillSplitters", sender: self)
}
}
}
I may not understand what you are asking exactly but my only input is that when I use prepare for segue I always use a segue identifier
var valueToPass
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if (segue.identifier == "yourSegue") {
let viewController = segue.destination as! ViewController
viewController.passedValue = valueToPass as String
}
Your declare your identifier in the storyboard, also make sure your segue is an action segue

Passing same object context 2 two separate view controllers

I have a managedObject that is being passed from 1 view controller to another the first pass works fine but when I try to pass the next object after the relationship has been set it doesn't send anything and comes back as either nil or if I try to use other methods comes back with a syntax error. The code I am using for the view controllers is as follows
View Controller 1, The first object set:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "popOver":
if let VC = segue.destinationViewController as? ClassDeckNameViewController
{
if let ppc = VC.popoverPresentationController {
VC.modalPresentationStyle = UIModalPresentationStyle.Popover
ppc.permittedArrowDirections = UIPopoverArrowDirection.Any
ppc.delegate = self
}
VC.classSave = (sender as! ClassSelection)
}
default: break
}
}
}
#IBAction func buttonPriest(sender: AnyObject) {
let entity = NSEntityDescription.entityForName("ClassSelection", inManagedObjectContext: classMOC!)
let newObject = ClassSelection(entity: entity!,insertIntoManagedObjectContext: classMOC)
newObject.classname = "Priest"
var error: NSError?
if let err = error {
println(err)
} else {
classMOC?.save(&error)
self.performSegueWithIdentifier("popOver", sender: newObject)
}
}
This passes the object without problem to the second view controller but this is the one that won't pass any further to the final presenting controller offering the user the final selections for their "Deck":
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showCardSelection" {
let detailVC: CardSelectionViewController = segue.destinationViewController as! CardSelectionViewController
detailVC.passedDeckObject = (sender as! Deck)
}
}
#IBAction func enterButton(sender: AnyObject) {
let entityDescription = NSEntityDescription.entityForName("Deck",inManagedObjectContext: managedObjectContext!)
let storeDeck = Deck(entity: entityDescription!,insertIntoManagedObjectContext: managedObjectContext)
storeDeck.deckname = usersDeckName.text
storeDeck.classSelected = classSave!
var error: NSError?
managedObjectContext?.save(&error)
if let err = error {
status.text = err.localizedFailureReason
} else {
usersDeckName.text = ""
status.text = "Deck Saved"
self.performSegueWithIdentifier("showCardSelection", sender: storeDeck)
}
}
I made passedDeckObject a variable of type Deck? in the final view controller to set the final relationship methods I know I am doing something wrong but I am unsure what! Any help with this would be amazing!
This looks to be a misconfiguration issue where the segue is being triggered directly in the storyboard rather than calling your code. As such the sender is a button rather than the new entity instance you're expecting.
To fix, disconnect the segue in the storyboard and connect (if it isn't already) the button to your action method in the view controller.