If sectionNameKeyPath is not nil I cannot start my app - ios

Whenever I try to run my application when my sectionNameKeyPath is not nil, it crashes with the error
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray'
but whenever I set that to nil it works and I add some data, quit the app, set the sectionNameKeyPath back to my Core Data attribute, it now works just fine! This is the 7th hour today I'm fighting with this bug and I just can't solve it. What is wrong in my code?
//
// ViewController.swift
// Expense Manager
//
// Created by Andrei Vataselu on 10/3/17.
// Copyright © 2017 Andrei Vataselu. All rights reserved.
//
import UIKit
import SideMenu
import CoreData
import SwipeCellKit
let green = UIColor(red:0.00, green:0.62, blue:0.45, alpha:1.0)
let red = UIColor(red:0.95, green:0.34, blue:0.34, alpha:1.0)
let appDelegate = UIApplication.shared.delegate as? AppDelegate
var userMoney : [UserMoney] = []
var managedObjectContext: NSManagedObjectContext? = appDelegate?.persistentContainer.viewContext
class ViewController: UIViewController, NSFetchedResultsControllerDelegate {
#IBOutlet weak var sumTextField: UITextField!
#IBOutlet weak var userBudgetLabel: UILabel!
#IBOutlet var tap: UITapGestureRecognizer!
#IBOutlet weak var topView: UIView!
#IBOutlet weak var plusButton: UIButton!
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var moreBtn: UIButton!
func userBudgetCount(_ section: Int) -> Int{
return fetchedResultsController.sections![section].numberOfObjects
}
func getUserBudgetAtIndexPath(indexPath : IndexPath) -> Budget {
return fetchedResultsController.object(at: indexPath) as Budget
}
override func viewDidLoad() {
super.viewDidLoad()
self.hideKeyboard()
tableView.delegate = self
tableView.dataSource = self
self.tableView.tableFooterView = UIView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
fetchCoreDataObject()
}
func fetchCoreDataObject() {
self.fetch { (complete) in
if complete {
if userBudgetCount(0) >= 1 {
userBudgetLabel.text = replaceLabel(number: userMoney[userMoney.count - 1].userMoney)
tableView.isHidden = false
plusButton.isHidden = false
moreBtn.isHidden = false
} else {
tableView.isHidden = true
userBudgetLabel.text = "Bugetul tau"
plusButton.isHidden = true
moreBtn.isHidden = true
}
}
}
}
var fetchedResultsController: NSFetchedResultsController<Budget> {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest<Budget>(entityName: "Budget")
// Set the batch size to a suitable number.
fetchRequest.fetchBatchSize = 20
// Edit the sort key as appropriate.
let sortDescriptor = NSSortDescriptor(key: "dateSubmitted" , ascending: false)
fetchRequest.sortDescriptors = [sortDescriptor]
// Edit the section name key path and cache name if appropriate.
// nil for section name key path means "no sections".
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext!, sectionNameKeyPath: "dateSection", cacheName: nil)
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
do {
try _fetchedResultsController!.performFetch()
} 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)")
}
return _fetchedResultsController!
}
var _fetchedResultsController: NSFetchedResultsController<Budget>? = nil
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func initialAddButtonPressed(_ sender: Any) {
if sumTextField.text != "" {
self.saveMoney(userMoney: (sumTextField.text! as NSString).doubleValue, completion: { (complete) in
})
self.save(sumText: sumTextField.text! , dataDescription: "Buget initial", dataColor: green) {
complete in
if complete {
tableView.isHidden = false
}
}
userBudgetLabel.text = "\(sumTextField.text!) RON"
self.fetchCoreDataObject()
tableView.reloadData()
} else {
sumInvalidAlert()
}
self.dismissKeyboard()
sumTextField.text = ""
}
#IBAction func plusButtonPressed(_ sender: Any) {
let plusController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let addBudgetAction = UIAlertAction(title: "Adauga buget", style: .default) {
(action) -> Void in
guard let createAddBudgetVC = self.storyboard?.instantiateViewController(withIdentifier: "AddBudgetVC") else { return }
self.presentViewController(createAddBudgetVC)
}
let addExpenseAction = UIAlertAction(title: "Adauga plata", style: .default) {
(action) -> Void in
guard let createAddExpenseVC = self.storyboard?.instantiateViewController(withIdentifier: "AddExpenseVC") else { return }
self.presentViewController(createAddExpenseVC)
}
let cancelAction = UIAlertAction(title: "Anuleaza", style: .cancel, handler: nil)
plusController.addAction(addBudgetAction)
plusController.addAction(addExpenseAction)
plusController.addAction(cancelAction)
present(plusController, animated: true, completion: nil)
}
#IBAction func moreButtonPressed(_ sender: Any) {
}
}
extension ViewController {
func fetch(completion: (_ complete: Bool) -> ()){
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
let fetchMoneyRequest = NSFetchRequest<UserMoney>(entityName: "UserMoney")
do{
userMoney = try managedContext.fetch(fetchMoneyRequest)
print("success")
completion(true)
} catch {
debugPrint("Could not fetch \(error.localizedDescription)")
completion(false)
}
}
func removeCell(atIndexPath indexPath: IndexPath){
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
do {
try managedContext.save()
debugPrint("removeCell CONTEXT SAVED")
} catch {
debugPrint("removeCell CONTEXT NOT SAVED \(error.localizedDescription)")
}
}
func cancelCell(color: UIColor, atIndexPath indexPath: IndexPath){
guard let managedContext = appDelegate?.persistentContainer.viewContext else { return }
if color.description == green.description {
// scade buget
userMoney[userMoney.count - 1].userMoney -= (getUserBudgetAtIndexPath(indexPath: indexPath).dataSum! as NSString).doubleValue
} else {
userMoney[userMoney.count - 1].userMoney += (getUserBudgetAtIndexPath(indexPath: indexPath).dataSum! as NSString).doubleValue
}
do {
try managedContext.save()
} catch {
print("cancelCell Managed Context Saving ERROR: \(error.localizedDescription)")
}
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource,SwipeTableViewCellDelegate {
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
guard orientation == .right else {
let cancelAction = SwipeAction(style: .default, title: "Anuleaza"){
(action, indexPath)
in
self.cancelCell(color: self.getUserBudgetAtIndexPath(indexPath: indexPath).dataColor as! UIColor, atIndexPath: indexPath)
self.removeCell(atIndexPath: indexPath)
self.fetchCoreDataObject()
tableView.deleteRows(at: [indexPath], with: .automatic)
}
cancelAction.backgroundColor = UIColor(red:0.16, green:0.63, blue:0.74, alpha:1.0)
return [cancelAction]
}
let deleteAction = SwipeAction(style: .destructive, title: "Sterge") { (action, indexPath) in
self.removeCell(atIndexPath: indexPath)
self.fetchCoreDataObject()
tableView.deleteRows(at: [indexPath], with: .automatic)
}
deleteAction.backgroundColor = red
return [deleteAction]
}
func numberOfSections(in tableView: UITableView) -> Int {
if let sections = fetchedResultsController.sections {
return sections.count
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "expenseCell") as? ExpenseCell else { return UITableViewCell() }
print("indexPathRow: \(indexPath.row) | indexPathSection: \(indexPath.section)")
let budget = fetchedResultsController.object(at: indexPath) as Budget
cell.delegate = self
cell.configureCell(budget: budget)
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return UITableViewCellEditingStyle.none
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = fetchedResultsController.sections {
let currentSection = sections[section]
return currentSection.numberOfObjects
}
return 0
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if let sections = fetchedResultsController.sections {
let currentSections = sections[section]
return currentSections.name
}
return nil
}
}

Related

Add Animation on TableView Cells

I have made a tableView with cells that take the data from an API. I have imported ViewAnimator Package because I want to add some animation when the cells appear but the animation starts while the tableview had already be presented with data.
Maybe I have made a mistake at the logic but I can't find the solution.
The OpeningViewController is this :
import UIKit
import ViewAnimator
class OpeningViewController: UIViewController {
//MARK: - IBProperties
#IBOutlet var openingImg: UIImageView!
#IBOutlet var startButton: UIButton!
//MARK: - Properties
var nft : Nft?
//MARK: - Life Cyrcle
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let animation = AnimationType.from(direction: .top, offset: 50)
openingImg.animate(animations: [animation] , delay: 0.3, duration: 2)
openingImg.layer.shadowColor = UIColor.black.cgColor
openingImg.layer.shadowOffset = CGSize(width: 0, height: 0)
openingImg.layer.shadowOpacity = 0.65
openingImg.layer.shadowRadius = 10
}
//MARK: - Methods
#IBAction func startApp(_ sender: Any) {
HapticsManager.shared.selectionVibrate()
let storyBoard = UIStoryboard(name: "Lobby", bundle: nil)
let controller = storyBoard.instantiateViewController(withIdentifier: "LobbyViewController") as! LobbyViewController
controller.modalTransitionStyle = .flipHorizontal
self.navigationController?.pushViewController(controller, animated: true)
}
}
The presentation happens in LobbyViewController :
import UIKit
import ViewAnimator
class LobbyViewController: UIViewController {
// MARK: - IBProperties
#IBOutlet weak var tableView: UITableView!
// MARK: - Properties
var data: [DataEnum] = []
var likes:[Int] = []
var numlikes: Int = 0
var nfts: [Nft] = []
let creators : [Creator] = []
var icons: [Icon] = []
var loadData = APICaller()
// MARK: - Life Cyrcle
override func viewDidLoad() {
super.viewDidLoad()
let nib = UINib(nibName: "AssetTableViewCell", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "AssetTableViewCell")
let nib2 = UINib(nibName: "CreatorsTableViewCell", bundle: nil)
tableView.register(nib2, forCellReuseIdentifier: "CreatorsTableViewCell")
tableView.dataSource = self //method to generate cells,header and footer before they are displaying
tableView.delegate = self //method to provide information about these cells, header and footer ....
downloadJSON {
self.tableView.reloadData()
print("success")
}
loadData.downloadData { (result) in
print(result)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let animation = AnimationType.from(direction: .top, offset: 300)
UIView.animate(views: tableView.visibleCells,
animations: [animation], delay: 1, duration: 2)
}
//stelnei ta dedomena apo to kathe row ston PresentViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? PresentViewController {
if tableView.cellForRow(at: tableView.indexPathForSelectedRow!) is AssetTableViewCell {
destination.nft = nfts[tableView.indexPathForSelectedRow!.row-1]
destination.delegate = self
} else {
//add alert action
let alert = UIAlertController(title: "Invalid Touch", message: "You press wrong row. Choose one of the following list.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alert, animated: true, completion: {
return
})
}
}
}
// MARK: - Methods
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "https://public.arx.net/~chris2/nfts.json")
URLSession.shared.dataTask(with: url!) { [self] data, response, error in
if error == nil {
do {
self.nfts = try JSONDecoder().decode([Nft].self, from: data!)
let creators = nfts.map { nft in
nft.creator
}
self.data.append(.type1(creators: creators))
self.nfts.forEach { nft in
self.data.append(.type2(nft: nft))
}
DispatchQueue.main.async {
completed()
}
}
catch {
print("error fetching data from api")
}
}
}.resume()
}
}
// MARK: - Extensions
extension LobbyViewController : UITableViewDelegate , UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
indexPath.row == 0 ? 100 : UITableView.automaticDimension
}
//gemizo ta rows tou table
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch self.data[indexPath.item] {
case .type1(let creators):
print("--->", creators)
let cell = tableView.dequeueReusableCell(withIdentifier: "CreatorsTableViewCell",
for: indexPath) as! CreatorsTableViewCell
cell.layer.cornerRadius = 15
cell.layer.shadowColor = UIColor.black.cgColor
cell.layer.shadowOffset = CGSize(width: 0, height: 0)
cell.layer.shadowOpacity = 0.8
cell.layer.shadowRadius = 15
cell.layer.cornerRadius = cell.frame.height/2
cell.updateCreators(creators)
return cell
case .type2(let nft):
let cell = tableView.dequeueReusableCell(withIdentifier: "AssetTableViewCell",
for: indexPath) as! AssetTableViewCell
cell.nameLabel?.text = nft.name
cell.nameLabel.layer.cornerRadius = cell.nameLabel.frame.height/2
cell.likesLabel?.text = "\((numlikes))"
let imgUrl = (nft.image_url)
print(imgUrl)
cell.iconView.downloaded(from: imgUrl)
cell.iconView.layer.cornerRadius = cell.iconView.frame.height/2
return cell
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetails", sender: self)
}
}
extension LobbyViewController : TestDelegate{
func sendBackTheLikess(int: Int) {
numlikes = int
tableView.reloadData()
}
}
// MARK: - Enums
enum DataEnum {
case type1(creators: [Creator])
case type2(nft: Nft)
}
// MARK: - Struct
struct Constants {
static let url = "https://public.arx.net/~chris2/nfts.json"
}
The APICaller :
import Foundation
final class APICaller {
static let shared = APICaller()
public struct Constants {
static let url = "https://public.arx.net/~chris2/nfts.json"
}
public func downloadData(completion:#escaping (Result<[Nft], Error>) -> Void )
{
guard let url = URL(string:Constants.url)else{
return
}
let task = URLSession.shared.dataTask(with: url) { data, response, error in
//print(response)
print("here")
guard let data = data , error == nil else{
print("something went wrong with data")
return
}
print("here4")
//mexri edo exoume parei ta data kai tora me to do-catch tha ta kanoume convert se object
do{
//Decode the response
let nfts = try JSONDecoder().decode([Nft].self, from: data)
completion(.success(nfts))
print(nfts)
}catch{
completion(.failure(error))
}
}
task.resume()
}
}
and here is a video as a gif of what happen fro better understanding
https://gifyu.com/image/SEGkZ

Swift Xcode 13 Programmatic UITableViewController nil delegate

The Delegated function fires but crashes as its nil, the objects in the items array is populated by CoreData, this var model: CoreDataModel = CoreDataModel(CoreDataController.shared) has to be instantiated rather than as expected in the viewDidLoad to prevent a nil error for the table view row count (model.items.count)
On startup the items array is the complete Sqlite DB, on search its the subset of the table and printing to the console proves the array is changed and only has the subset of Albums.
BaseViewController
import UIKit
import CoreData
protocol UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
}
protocol MasterModel {
var client: LastFMClient { get }
func searchFeed(with userSearchTerm: String?, completion: #escaping (Bool) -> Void)
}
protocol DataReloadTableViewDelegate: class {
func reloadAlbumsTable()
}
class BaseViewController: UITableViewController, MasterModel {
let cellId = "sdlfjowieurewfn3489844224947824dslaksjfs;ad"
let logoContainer = UIView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
let image = UIImage(named: "lastFMRedBlack")
let searchBar = UISearchBar()
let client = LastFMClient()
var model: CoreDataModel = CoreDataModel(CoreDataController.shared)
private var searchResults: Root?
override func viewDidLoad() {
super.viewDidLoad()
setupSearchController()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
tableView.register(SubtitleTableViewCell.self, forCellReuseIdentifier: cellId)
tableView.tableFooterView = UIView(frame: CGRect.zero)
tableView.separatorColor = UIColor(red: 72.5/255, green: 0/255, blue: 0/255, alpha: 1)
imageView.contentMode = .scaleAspectFit
imageView.image = image
logoContainer.addSubview(imageView)
navigationItem.titleView = logoContainer
print(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask))
model.delegate = self
model.fetchAllAlbums()
}
// MARK - SearchBar
private func setupSearchController() {
searchBar.sizeToFit()
searchBar.placeholder = "Search for Album"
searchBar.delegate = self
showSearchBarButton(shouldShow: true)
}
func showSearchBarButton (shouldShow: Bool) {
if shouldShow {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(handleShowSearchBar))
} else {
searchBar.showsCancelButton = true
navigationItem.rightBarButtonItem = nil
}
}
func search(shouldShow: Bool) {
showSearchBarButton(shouldShow: !shouldShow)
navigationItem.titleView = shouldShow ? searchBar : logoContainer
}
#objc func handleShowSearchBar(){
search(shouldShow: true)
searchBar.becomeFirstResponder()
}
// MARK - API Request
func searchFeed(with userSearchTerm: String?, completion: #escaping (Bool) -> Void) {
// Use the API to get data
client.getFeed(from: LastFMRequest.albumSearch(userSearchTerm: userSearchTerm) ) { result in
switch result {
case .success(let data):
do {
let data = try DataParser.parse(data, type: RootNode.self)
self.searchResults = data.results
completion(true)
} catch {
print(error.localizedDescription)
completion(false)
}
case .failure(let error):
print(error.localizedDescription)
completion(false)
}
}
}
}
extension BaseViewController: UISearchBarDelegate {
func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
searchBar.text = nil
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
search(shouldShow: false)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let searchTextString = searchBar.text else { return }
searchFeed(with: searchTextString.replacingOccurrences(of: " ", with: "+").lowercased(), completion: {_ in
if self.searchResults!.albumMatches.album.count == 0 {
DispatchQueue.main.async {
let alertController = UIAlertController(title: "No Albums Found", message: "Try Another Keyword(s)", preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .default) { action in
print("Pressed OK")
}
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
} else {
let dataManager = DataManager(data: self.searchResults!)
do {
try dataManager.saveData()
} catch {
print(error)
}
}
})
search(shouldShow: false)
searchBar.resignFirstResponder()
}
}
class SubtitleTableViewCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
extension BaseViewController: UITableViewDataSource {
var numberOrSections: Int { return 1 }
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard section >= 0 && section < numberOrSections else { return 0 }
return model.items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let albumItem = model.items[indexPath.row]
cell.textLabel?.text = albumItem.value(forKeyPath: "name") as? String
cell.detailTextLabel?.text = albumItem.value(forKeyPath: "artist") as? String
cell.accessoryType = .disclosureIndicator
// Populate the cell from the object
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = DetailViewController()
let albumItem = model.items[indexPath.row]
vc.iamgeURL = albumItem.value(forKeyPath: "imageUrl") as? String
vc.albumName = albumItem.value(forKeyPath: "name") as? String
navigationController?.pushViewController(vc, animated: true)
}
}
extension BaseViewController: DataReloadTableViewDelegate {
func reloadAlbumsTable(){
DispatchQueue.main.async {
print(self.model.items.count)
self.tableView.reloadData()
}
}
}
CoreDataModel
import Foundation
import CoreData
class CoreDataModel {
weak var delegate: DataReloadTableViewDelegate?
let coreDataController: CoreDataController
var items:[Albums] = []
init(_ coreDataController: CoreDataController) {
self.coreDataController = coreDataController
self.coreDataController.mainContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
}
internal func saveSearchAlbums(responseData: Root) throws {
let newSearch = Searches(context: coreDataController.mainContext)
newSearch.searchQuery = responseData.attr.forField
for (_, element) in responseData.albumMatches.album.enumerated() {
let newAlbum = Albums(context: coreDataController.mainContext)
let artistName = element.artist
let albumName = element.name
let imageUrlTwo = element.image[2].text
let imageUrlZero = element.image[0].text
let imageUrlOne = element.image[1].text
var imageUrl: String = ""
if !JustLetters.blank(text: imageUrlTwo) {
imageUrl = imageUrlTwo
}
if !JustLetters.blank(text: imageUrlZero) {
imageUrl = imageUrlZero
}
if !JustLetters.blank(text: imageUrlOne) {
imageUrl = imageUrlOne
}
if !JustLetters.blank(text: artistName) && !JustLetters.blank(text: albumName) && !JustLetters.blank(text: imageUrl) {
newAlbum.searches = newSearch
newAlbum.artist = artistName
newAlbum.name = albumName
newAlbum.imageUrl = imageUrl
newSearch.addToAlbums(newAlbum)
}
}
// Save context
coreDataController.saveContext()
fetchAlbumsByKeyword(searchTerm: responseData.attr.forField)
}
internal func fetchAlbumsByKeyword(searchTerm: String) {
// Create Fetch Request
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Albums")
// Add Sort Descriptor
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
// Add Predicate
let predicate = NSPredicate(format: "name CONTAINS[c] %#", searchTerm)
fetchRequest.predicate = predicate
do {
items = try coreDataController.mainContext.fetch(fetchRequest) as! [Albums]
} catch {
print(error)
}
delegate!.reloadAlbumsTable()
}
internal func fetchAllAlbums() {
// Create the FetchRequest for all searches
let allAlbums: NSFetchRequest = Albums.fetchRequest()
do {
items = try coreDataController.mainContext.fetch(allAlbums)
} catch {
print(error)
}
}
}
Delegate is assigned/set on the class name and not on any instance identifier so a delegate can only be set on a class with one instance
I am unable to show specific proof, I rely on cause and effect of a single change to make the above statement.
I had more than one instance of CoreDataModel, I set the delegate on the first instance in the viewDidLoad, the second instance is set on the search click. I refactored out the DataManager Class which itself creates and instance of CoreDataModel.
The final result is the delegate is not nil and performs as expected. Repo 'show_album_search_results' branch

Value of type 'Item' has no member 'parentCategory'

I have 2 entities
I'm using XCode 10 right now, I am not sure if I did something wrong or XCode bug.
I added 1 line into these lines
let newItem = Item(context: self.context)
newItem.title = textField.text!
newItem.done = false
newItem.parentCategory = self.selectedCategory <--------- ADD HERE
self.itemArray.append(newItem)
self.saveItems()
Any hints for me on why this is happening ?
TodoListVC
//
// TodoListVC
// ListHue
// Copyright © 2018 LR Web Design. All rights reserved.
//
import UIKit
import CoreData
class TodoListVC: UITableViewController {
var itemArray = [Item]()
var selectedCategory : Category? {
didSet {
loadItems()
}
}
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
// ---------------------------------------------------------------------------------------------------------
//MARK - viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
print(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask))
}
// ---------------------------------------------------------------------------------------------------------
//MARK - Datasource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "listItemCell", for: indexPath)
let item = itemArray[indexPath.row]
cell.textLabel?.text = item.title
cell.accessoryType = item.done == true ? .checkmark : .none
return cell
}
// ---------------------------------------------------------------------------------------------------------
//MARK - Delegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
context.delete(itemArray[indexPath.row])
itemArray.remove(at: indexPath.row)
itemArray[indexPath.row].done = !itemArray[indexPath.row].done
self.saveItems()
tableView.deselectRow(at: indexPath, animated: true)
}
// ---------------------------------------------------------------------------------------------------------
//MARK - Add new item
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
var textField = UITextField()
let alert = UIAlertController(title: "Add New Item", message: "", preferredStyle: .alert)
//action
let action = UIAlertAction(title: "Add Item", style: .default) { (action) in
let newItem = Item(context: self.context)
newItem.title = textField.text!
newItem.done = false
newItem.parentCategory = self.selectedCategory
self.itemArray.append(newItem)
self.saveItems()
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Create new item"
textField = alertTextField
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
// ---------------------------------------------------------------------------------------------------------
//MARK - Model Manipulation Methods
func saveItems() {
do {
try context.save()
} catch {
print("Error saving context, \(error)")
}
self.tableView.reloadData()
}
func loadItems(with request: NSFetchRequest<Item> = Item.fetchRequest()) {
let predicate = NSPredicate(format: "parentCategory.name MATCHES %#", selectedCategory?.name!)
request.predicate = predicate
do {
itemArray = try context.fetch(request)
} catch {
print("Error fetching data from the context, \(error)")
}
self.tableView.reloadData()
}
}
//MARK: - Search bar methods
extension TodoListVC : UISearchBarDelegate {
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
let request : NSFetchRequest<Item> = Item.fetchRequest()
request.predicate = NSPredicate(format: "title CONTAINS[cd] %#", searchBar.text!)
request.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)]
loadItems(with: request)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text?.count == 0 {
loadItems()
DispatchQueue.main.async {
searchBar.resignFirstResponder()
}
}
}
}
CategoryVC
//
// CategoryVC.swift
// ListHue
// Copyright © 2018 LR Web Design. All rights reserved.
//
import UIKit
import CoreData
class CategoryVC: UITableViewController {
var categories = [Category]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
loadCategories()
}
// ---------------------------------------------------------------------------------------------------------
//MARK - Table View Datasource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CategoryCell", for: indexPath)
let category = categories[indexPath.row]
cell.textLabel?.text = category.name
return cell
}
// ---------------------------------------------------------------------------------------------------------
//MARK - Table View Delegate Methods
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "goToItems", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! TodoListVC
//get the category of the selected cell
if let indexPath = tableView.indexPathForSelectedRow {
//set the property
destinationVC.selectedCategory = categories[indexPath.row]
}
}
// ---------------------------------------------------------------------------------------------------------
//MARK - Data Manipulation Methods
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
print("click")
var textField = UITextField()
let alert = UIAlertController(title: "Add New Category", message: "", preferredStyle: .alert)
//action
let action = UIAlertAction(title: "Add Category", style: .default) { (action) in
let newCategory = Category(context: self.context)
newCategory.name = textField.text!
self.categories.append(newCategory)
self.saveCategories()
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Create new item"
textField = alertTextField
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
// ---------------------------------------------------------------------------------------------------------
//MARK - Add New Categories
func saveCategories() {
do {
try context.save()
} catch {
print("Error saving context, \(error)")
}
self.tableView.reloadData()
}
func loadCategories(with request: NSFetchRequest<Category> = Category.fetchRequest()) {
do {
categories = try context.fetch(request)
} catch {
print("Error fetching data from the context, \(error)")
}
self.tableView.reloadData()
}
}
I got the diagram backward, thanks to #Larme, and #Ladislav helped me to know that.

Doesn't save editing text in UITextView

I have to ViewControllers and two classes MasterViewController.swift and LoginViewController.swift. In first class I have tableView in which you can add record and it open new ViewController with class DetailViewController.swift in with is textView in which I can add text and when I go back to tableView it must save my text, but when I do it it doesn't save. This is cod from MasterViewController:
import UIKit
import CoreData
class MasterViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, NSFetchedResultsControllerDelegate {
#IBOutlet var tableView: UITableView!
var isAuthenticated = false
var managedObjectContext: NSManagedObjectContext? = nil
var _fetchedResultsController: NSFetchedResultsController? = nil
var didReturnFromBackground = false
override func awakeFromNib() {
super.awakeFromNib()
}
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = self.editButtonItem()
view.alpha = 0
let addButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: "insertNewObject:")
self.navigationItem.rightBarButtonItem = addButton
NSNotificationCenter.defaultCenter().addObserver(self, selector: "appWillResignActive:", name: UIApplicationWillResignActiveNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "appDidBecomeActive:", name: UIApplicationDidBecomeActiveNotification, object: nil)
}
#IBAction func unwindSegue(segue: UIStoryboardSegue) {
isAuthenticated = true
view.alpha = 1.0
}
func appWillResignActive(notification : NSNotification) {
view.alpha = 0
isAuthenticated = false
didReturnFromBackground = true
}
func appDidBecomeActive(notification : NSNotification) {
if didReturnFromBackground {
self.showLoginView()
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(false)
self.showLoginView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func showLoginView() {
if !isAuthenticated {
self.performSegueWithIdentifier("loginView", sender: self)
}
}
func insertNewObject(sender: AnyObject) {
let context = self.fetchedResultsController.managedObjectContext
let entity = self.fetchedResultsController.fetchRequest.entity!
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName(entity.name!, inManagedObjectContext: context) as! NSManagedObject
newManagedObject.setValue(NSDate(), forKey: "date")
newManagedObject.setValue("New Note", forKey: "noteText")
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow() {
let object = self.fetchedResultsController.objectAtIndexPath(indexPath)as! NSManagedObject
(segue.destinationViewController as! DetailViewController).detailItem = object
}
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.fetchedResultsController.sections?.count ?? 0
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
self.configureCell(cell, atIndexPath: indexPath)
return cell
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let context = self.fetchedResultsController.managedObjectContext
context.deleteObject(self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject)
var error: NSError? = nil
if !context.save(&error) {
abort()
}
}
}
func configureCell(cell: UITableViewCell, atIndexPath indexPath: NSIndexPath) {
let object = self.fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
cell.textLabel?.text = object.valueForKey("noteText")!.description
}
#IBAction func logoutAction(sender: AnyObject) {
isAuthenticated = false
self.performSegueWithIdentifier("loginView", sender: self)
}
var fetchedResultsController: NSFetchedResultsController {
if _fetchedResultsController != nil {
return _fetchedResultsController!
}
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Note", inManagedObjectContext: self.managedObjectContext!)
fetchRequest.entity = entity
fetchRequest.fetchBatchSize = 20
let sortDescriptor = NSSortDescriptor(key: "date", ascending: false)
let sortDescriptors = [sortDescriptor]
fetchRequest.sortDescriptors = [sortDescriptor]
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: self.managedObjectContext!, sectionNameKeyPath: nil, cacheName: "Master")
aFetchedResultsController.delegate = self
_fetchedResultsController = aFetchedResultsController
var error: NSError? = nil
if !_fetchedResultsController!.performFetch(&error) {
abort()
}
return _fetchedResultsController!
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case .Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
default:
return
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
default:
return
}
}
}
My DetailViewControoler.swift:
import UIKit
import CoreData
class DetailViewController: UIViewController, UITextViewDelegate {
let ManagedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate)
#IBOutlet weak var detailTextView: UITextView!
var note: Note? = nil
var detailItem: AnyObject? {
didSet {
self.configureView()
}
}
func configureView() {
if let detail: Note = self.detailItem as? Note {
if let detailTextView = self.detailTextView {
detailTextView.text = detail.noteText
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.configureView()
}
func textViewDidEndEditing( textView: UITextView) {
if let detail: Note = self.detailItem as? Note {
if let detailTextView = self.detailTextView {
detail.noteText = detailTextView.text
}
}
ManagedObjectContext.managedObjectContext!.save(nil)
}
}

Trouble with write to coredata iOS

Please help me! when I press the record button in СoreData the following error:
This my code (AddViewController):
class AddPersonViewController: UIViewController {
var textFieldFirstName: UITextField!
var textFieldLastName: UITextField!
var textFieldAge: UITextField!
var barButtonAdd: UIBarButtonItem!
func createNewPerson(sender: AnyObject){
let appDelegate = UIApplication.sharedApplication().delegate
as AppDelegate
let managedObjectContext = appDelegate.managedObjectContext
let newPerson =
NSEntityDescription.insertNewObjectForEntityForName("Person",
inManagedObjectContext: managedObjectContext!) as? Person
if let person = newPerson{
person.firstName = textFieldFirstName.text
person.lastName = textFieldLastName.text
if let age = textFieldAge.text.toInt(){
person.age = age
} else {
person.age = 18
}
var savingError: NSError?
if managedObjectContext!.save(&savingError){
navigationController!.popViewControllerAnimated(true)
} else {
println("Failed to save the managed object context")
}
} else {
println("Failed to create the new person object")
}
}
override func viewDidLoad() {
super.viewDidLoad()
title = "New Person"
var textFieldRect = CGRect(x: 20,
y: 80,
width: view.bounds.size.width - 40,
height: 31)
textFieldFirstName = UITextField(frame: textFieldRect)
textFieldFirstName.placeholder = "First Name"
textFieldFirstName.borderStyle = .RoundedRect
textFieldFirstName.autoresizingMask = .FlexibleWidth
textFieldFirstName.contentVerticalAlignment = .Center
view.addSubview(textFieldFirstName)
textFieldRect.origin.y += 37
textFieldLastName = UITextField(frame: textFieldRect)
textFieldLastName.placeholder = "Last Name"
textFieldLastName.borderStyle = .RoundedRect
textFieldLastName.autoresizingMask = .FlexibleWidth
textFieldLastName.contentVerticalAlignment = .Center
view.addSubview(textFieldLastName)
textFieldRect.origin.y += 37
textFieldAge = UITextField(frame: textFieldRect)
textFieldAge.placeholder = "Age"
textFieldAge.borderStyle = .RoundedRect
textFieldAge.autoresizingMask = .FlexibleWidth
textFieldAge.keyboardType = .NumberPad
textFieldAge.contentVerticalAlignment = .Center
view.addSubview(textFieldAge)
barButtonAdd = UIBarButtonItem(title: "Add",
style: .Plain,
target: self,
action: "createNewPerson:")
navigationItem.rightBarButtonItem = barButtonAdd
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
textFieldFirstName.becomeFirstResponder()
}
}
TablePerson:
class PersonsListTableViewController: UITableViewController,
NSFetchedResultsControllerDelegate {
struct TableViewConstants{
static let cellIdentifier = "Cell"
}
var barButtonAddPerson: UIBarButtonItem!
var frc: NSFetchedResultsController!
var managedObjectContext: NSManagedObjectContext?{
return (UIApplication.sharedApplication().delegate
as AppDelegate).managedObjectContext
}
func addNewPerson(sender: AnyObject){
/* This is a custom segue identifier that we have defined in our
storyboard that simply does a "Show" segue from our view controller
to the "Add New Person" view controller */
performSegueWithIdentifier("addPerson", sender: nil)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
barButtonAddPerson = UIBarButtonItem(barButtonSystemItem: .Add,
target: self,
action: "addNewPerson:")
}
func controllerWillChangeContent(controller: NSFetchedResultsController!) {
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController!,
didChangeObject anObject: AnyObject!,
atIndexPath indexPath: NSIndexPath!,
forChangeType type: NSFetchedResultsChangeType,
newIndexPath: NSIndexPath!) {
if type == .Delete{
tableView.deleteRowsAtIndexPaths([indexPath],
withRowAnimation: .Automatic)
}
else if type == .Insert{
tableView.insertRowsAtIndexPaths([newIndexPath],
withRowAnimation: .Automatic)
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController!) {
tableView.endUpdates()
}
override func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
let sectionInfo = frc.sections![section] as NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
}
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCellWithIdentifier(
TableViewConstants.cellIdentifier,
forIndexPath: indexPath) as UITableViewCell
let person = frc.objectAtIndexPath(indexPath) as Person
cell.textLabel.text = person.firstName + " " + person.lastName
cell.detailTextLabel!.text = "Age: \(person.age)"
return cell
}
override func setEditing(editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
if editing{
navigationItem.setRightBarButtonItem(nil, animated: true)
} else {
navigationItem.setRightBarButtonItem(barButtonAddPerson, animated: true)
}
}
override func tableView(tableView: UITableView,
commitEditingStyle editingStyle: UITableViewCellEditingStyle,
forRowAtIndexPath indexPath: NSIndexPath){
let personToDelete = self.frc.objectAtIndexPath(indexPath) as Person
managedObjectContext!.deleteObject(personToDelete)
if personToDelete.deleted{
var savingError: NSError?
if managedObjectContext!.save(&savingError){
println("Successfully deleted the object")
} else {
if let error = savingError{
println("Failed to save the context with error = \(error)")
}
}
}
}
override func tableView(tableView: UITableView,
editingStyleForRowAtIndexPath indexPath: NSIndexPath)
-> UITableViewCellEditingStyle {
return .Delete
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Persons"
navigationItem.leftBarButtonItem = editButtonItem()
navigationItem.rightBarButtonItem = barButtonAddPerson
/* Create the fetch request first */
let fetchRequest = NSFetchRequest(entityName: "Person")
let ageSort = NSSortDescriptor(key: "age", ascending: true)
let firstNameSort = NSSortDescriptor(key: "firstName", ascending: true)
fetchRequest.sortDescriptors = [ageSort, firstNameSort]
frc = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: managedObjectContext!,
sectionNameKeyPath: nil,
cacheName: nil)
frc.delegate = self
var fetchingError: NSError?
if frc.performFetch(&fetchingError){
println("Successfully fetched")
} else {
println("Failed to fetch")
}
}
}
and Person.swift:
#objc(Person) class Person: NSManagedObject {
#NSManaged var age: NSNumber
#NSManaged var firstName: String
#NSManaged var lastName: String
}
Everything seems to be fine, but I keep getting an error so that it was not possible to write data: "Failed to create the new person object"
this is link to my Xcode project:
download
I don't understand why I keep getting ab error. Please help!
"Failed to create the new person object" being printed means newPerson is nil. A good reason for that could be that your managedObjectContext is nil. Could you check and report back?
I find solution. I xcdatamodeld does not have a class in which the variables were described by Person. Thank you for your answers. And sorry for my stupidity.

Resources