IndexPath for variable row - arrays

I am using a UITableView with prototype cells. The main TableView is DiveDetails2VC and the prototype cell is based on DiveItemViewController via segue.
I need to save to parse the results, and have been able to save only row 0 to parse. I do not know how to set this up so that any row selected in the tableview is sent to parse. I am aware this is caused by the line in saveEntry:
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
but I do not know how to set the forRow statement to allow any row within the displayed array. There is only one section.
DiveItemViewController
class DiveItemViewController: UITableViewController, ItemDataProtocol
{
private let NumberOfSections: Int = 1
// MARK: - Public Properties
//
// This property is an object that conforms to the ItemDataSelectedProtocol. We use it to call the
// selectedItem method.
//
var itemDataSelectedDelegate: AnyObject?
private var itemData: Array<String> = Array<String>()
override func viewDidLoad()
{
super.viewDidLoad()
// Adds "+" to dive item selection so divers can add another item to the selected list
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: #selector(DiveItemViewController.AddItemButton(_:)))
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
// ---------------------------------------------------------------------------------------------
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell: UITableViewCell! = tableView.dequeueReusableCellWithIdentifier(Resource.DiveItemCell)
if self.itemData.isEmpty == false
{
cell.textLabel?.text = itemData[indexPath.row]
}
parseItem = cell.textLabel!.text!
return cell
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return self.NumberOfSections
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return itemData.count
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = UITableViewCellAccessoryType.Checkmark
if self.itemDataSelectedDelegate?.respondsToSelector(#selector(DiveDetails2VC.itemDataSelectedItem(_:))) != nil
{
(self.itemDataSelectedDelegate as! ItemDataSelectedProtocol).itemDataSelectedItem(indexPath.row)
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
override func tableView(tableView: UITableView, willDeselectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath?
{
tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = UITableViewCellAccessoryType.None
return indexPath
}
func appendData(data: Array<String>) {
}
#IBAction func saveEntry (sender: AnyObject) {
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(indexPath)
let updateDivelog2Query = PFQuery(className: "divelog")
updateDivelog2Query.whereKey("uuid", equalTo: diveUUID)
updateDivelog2Query.getFirstObjectInBackgroundWithBlock {(objects: PFObject?, error: NSError?) -> Void in
if let updateDivelog2Object = objects {
updateDivelog2Object.setValue (self.itemData[indexPath.row], forKey: cell!.textLabel!.text!)
updateDivelog2Object.pinInBackground()
updateDivelog2Object.saveInBackgroundWithBlock {(done:Bool, error:NSError?) in
if done {
print ("ParseData UPDATED data saved")
} else {
updateDivelog2Object.saveEventually()
}
}}}
}
func itemTitle(title: String)
{
self.navigationItem.title = title
}
#IBAction func AddItemButton (sender: AnyObject) {
let alert = UIAlertController(title: "Add item",
message: "Add a new Item to your list",
preferredStyle: .Alert)
let saveAction = UIAlertAction(title: "Save",
style: .Default,
handler: { (action:UIAlertAction) -> Void in
let textField = alert.textFields!.first
self.itemData.append(textField!.text!)
self.tableView.reloadData()
self.appendData(self.itemData)
})
let cancelAction = UIAlertAction(title: "Cancel",
style: .Default) { (action: UIAlertAction) -> Void in
}
alert.addTextFieldWithConfigurationHandler {
(textField: UITextField) -> Void in
}
alert.addAction(saveAction)
alert.addAction(cancelAction)
presentViewController(alert,
animated: true,
completion: nil)
tableView.reloadData()
}
func itemData(data: Array<String>)
{
self.itemData = data
}
}

Declare a var in your viewController:
var selectedIndexPath: NSIndexPath?
And then in your didSelectRowAtIndexPath, set this var with the selected indexPath like:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
if let _ = self.selectedIndexPath {
selectedIndexPath = indexPath
}else{
selectedIndexPath = NSIndexPath(forRow: indexPath.row, inSection: indexPath.section)
}
tableView.cellForRowAtIndexPath(indexPath)?.accessoryType = UITableViewCellAccessoryType.Checkmark
if self.itemDataSelectedDelegate?.respondsToSelector(#selector(DiveDetails2VC.itemDataSelectedItem(_:))) != nil
{
(self.itemDataSelectedDelegate as! ItemDataSelectedProtocol).itemDataSelectedItem(indexPath.row)
}
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
And then use this selectedIndexPath like this in your saveEntry method:
#IBAction func saveEntry (sender: AnyObject) {
if let indexPath = selectedIndexPath {
let cell = tableView.cellForRowAtIndexPath(indexPath)
...
}
}
EDIT: Added ")" behind "inSection: indexPath.section)"

Related

How do you save new index location in persistentcontainer?

I have a tableview where I have added the ability to move the location of rows. I am able to use the "swapAt" function to update my array and it works fine. The problem is that when I close and re-open the app, the rows continue to show in their old IndexPath. How can I also update the index location in the PersistentContainer so that when I open the app, it is updated with the new IndexPath?
I tried calling the save method of the context but it does not work.
import UIKit
import CoreData
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var toDoList = [Item] ()
var count: Int {
toDoList.count
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addItem))
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(editItem))
title = "Todo"
loadItem()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return toDoList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath)
cell.textLabel?.text = toDoList [indexPath.row].title
if toDoList[indexPath.row].checkmark == true {
cell.accessoryType = .checkmark }
else {
cell.accessoryType = .none
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if toDoList[indexPath.row].checkmark == false {
toDoList[indexPath.row].checkmark = true } else {
toDoList[indexPath.row].checkmark = false
}
tableView.reloadData()
saveItem()
}
func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
toDoList.swapAt(sourceIndexPath.row, destinationIndexPath.row)
saveItem()
}
#objc func addItem () {
let ac = UIAlertController(title: "Add new category", message: nil, preferredStyle: .alert)
var textField = UITextField()
let submitAction = UIAlertAction(title: "Add", style: .default) { (action) in
let newItem = Item(context: self.context)
newItem.title = textField.text
newItem.checkmark = false
newItem.order = Int64(self.count)
self.toDoList.append(newItem)
self.saveItem()
}
ac.addTextField { (alertTextField) in
alertTextField.placeholder = "Create new item"
textField = alertTextField
}
ac.addAction(submitAction)
present(ac, animated: true, completion: nil)
}
func saveItem () {
do {
try context.save()
} catch {
print("This is the \(error)")
}
self.tableView.reloadData()
}
func loadItem () {
let request: NSFetchRequest<Item> = Item.fetchRequest()
let sortRequest = NSSortDescriptor(key: "order", ascending: true)
request.sortDescriptors = [sortRequest]
do {
toDoList = try context.fetch(request)
} catch {
print("the error is \(error)")
}
tableView.reloadData()
}
#objc func editItem () {
if tableView.isEditing {
tableView.isEditing = false
} else {
tableView.isEditing = true
}}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
context.delete(toDoList[indexPath.row])
saveItem()
self.toDoList.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
}
I would tackle this problem by having an index property in the Coredata entity; this index reflects the position of the element in the table view. In the swapAtFunction add the logic to switch the index properties of the two elements.
Then, when you fetch the entities, you add a predicate to your request, to sort the elements by the index property.
let fetchRequest = NSFetchRequest<EntityName>(entityName: "EntityName")
let sortDescripor = NSSortDescriptor(key: "index", ascending: true)
fetchRequest.sortDescriptors = [sortDescripor]
Then execute fetchRequest as you normally would, and assign the resulting array to the UICollectionView's Datasource.

Reloading TableViewCell inside CollectionViewCell

I tried viewWillAppear, tableView.reloadData(), and collectionView.reloadData() but the data array never seems to show up
import UIKit
class yearOne: UICollectionViewController, coursesDelegate {
let customCellIdentifier = "cellID"
let customCellIdentifier2 = "cellID2"
let customCellIdentifier3 = "cellID3"
let customCellIdentifier4 = "cellID4"
let quarters = [
customLabel (title: "Fall Quarter"),
customLabel (title: "Winter Quarter"),
customLabel (title: "Spring Quarter"),
customLabel (title: "Summer Quarter")
]
func sendDataBackFall(data: String) {
fallQuarterCell.data.append(data)
//UserDefaults.standard.set(fallQuarterCell.data, forKey: "SavedArray")
}
func sendDataBackWinter(data2: String) {
winterQuarterCell.data.append(data2)
//UserDefaults.standard.set(winterQuarterCell.data, forKey: "SavedArray")
}
func sendDataBackSpring(data3: String) {
springQuarterCell.data.append(data3)
//UserDefaults.standard.set(springQuarterCell.data, forKey: "SavedArray")
}
func sendDataBackSummer(data4: String) {
summerQuarterCell.data.append(data4)
//UserDefaults.standard.set(summerQuarterCell.data, forKey: "SavedArray")
}
let vc = fallQuarterCell()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("1")
print(fallQuarterCell.data)
vc.tableView.reloadData()
collectionView.reloadData()
print("2")
print(fallQuarterCell.data)
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.dataSource = self
collectionView.delegate = self
self.collectionView!.register(fallQuarterCell.self, forCellWithReuseIdentifier: customCellIdentifier)
self.collectionView!.register(winterQuarterCell.self, forCellWithReuseIdentifier: customCellIdentifier2)
self.collectionView!.register(springQuarterCell.self, forCellWithReuseIdentifier: customCellIdentifier3)
self.collectionView!.register(summerQuarterCell.self, forCellWithReuseIdentifier: customCellIdentifier4)
navigationItem.title = "Year One"
navigationController?.navigationBar.prefersLargeTitles = true
collectionView?.backgroundColor = .lightGray
//navigationItem.prompt = "Click the + button to add courses, Swipe left on a course to delete."
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return quarters.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if (indexPath.row == 0){
let cell1 = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier, for: indexPath) as! fallQuarterCell
cell1.layer.borderColor = UIColor.orange.cgColor
cell1.layer.borderWidth = 2
cell1.layer.cornerRadius = 5
cell1.quarters = self.quarters[0]
return cell1
}
else if (indexPath.row == 1){
let cell2 = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier2, for: indexPath) as! winterQuarterCell
cell2.layer.borderColor = UIColor.blue.cgColor
cell2.layer.borderWidth = 2
cell2.layer.cornerRadius = 5
cell2.quarters = self.quarters[1]
return cell2
}
else if (indexPath.row == 2){
let cell3 = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier3, for: indexPath) as! springQuarterCell
cell3.layer.borderColor = UIColor.green.cgColor
cell3.layer.borderWidth = 2
cell3.layer.cornerRadius = 5
cell3.quarters = self.quarters[2]
return cell3
}
else if (indexPath.row == 3){
let cell4 = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier4, for: indexPath) as! summerQuarterCell
cell4.layer.borderColor = UIColor.red.cgColor
cell4.layer.borderWidth = 2
cell4.layer.cornerRadius = 5
cell4.quarters = self.quarters[3]
return cell4
}
else{
return UICollectionViewCell()
}
}
#objc func buttonAction(sender: UIButton!) {
switch sender.tag {
case 0:
let destination = SearchPage()
destination.delegate = self
destination.tag = sender.tag
navigationController?.pushViewController(destination, animated: true)
case 1:
let destination = SearchPage()
destination.delegate = self
destination.tag = sender.tag
navigationController?.pushViewController(destination, animated: true)
case 2:
let destination = SearchPage()
destination.delegate = self
destination.tag = sender.tag
navigationController?.pushViewController(destination, animated: true)
case 3:
let destination = SearchPage()
destination.delegate = self
destination.tag = sender.tag
navigationController?.pushViewController(destination, animated: true)
default:
let destination = SearchPage()
destination.delegate = self
destination.tag = sender.tag
navigationController?.pushViewController(destination, animated: true)
}
}
}
extension yearOne : UICollectionViewDelegateFlowLayout{
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let width = (view.frame.width - 30)
return CGSize(width: width, height: 200)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 8
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 1
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
UIEdgeInsets(top: 30, left: 10, bottom: 30, right: 10)
}
}
class fallQuarterCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource {
// var data = UserDefaults.standard.object(forKey: "SavedArray") as? [String] ?? [String](){
// didSet{
// self.tableView.reloadData()
// }
// }
static var data: [String] = []
// func load(){
// if let loadeddata: [String] = UserDefaults.standard.object(forKey: "SavedArray") as? [String] {
// data = loadeddata
// tableView.reloadData()
// }
// }
let cellId = "coursesName"
let tableView:UITableView = {
let tableView = UITableView()
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.backgroundColor = UIColor.white
return tableView
}()
override init(frame: CGRect){
super.init(frame: frame)
addSubview(tableView)
setupView()
}
func setupView(){
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
tableView.delegate = self
tableView.dataSource = self
self.backgroundColor = UIColor.white
contentView.addSubview(quarterLabel)
contentView.addSubview(addButton)
quarterLabel.translatesAutoresizingMaskIntoConstraints = false
quarterLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10).isActive = true
quarterLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10).isActive = true
addButton.translatesAutoresizingMaskIntoConstraints = false
addButton.topAnchor.constraint(equalTo: quarterLabel.topAnchor, constant: -5).isActive = true
addButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10).isActive = true
addButton.heightAnchor.constraint(equalToConstant: 25).isActive = true
addButton.widthAnchor.constraint(equalToConstant: 25).isActive = true
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 35).isActive = true
tableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 5).isActive = true
tableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10).isActive = true
tableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5).isActive = true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fallQuarterCell.data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let stringLabel = fallQuarterCell.data[indexPath.row]
cell.textLabel?.text = stringLabel
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
if (indexPath.row == 0){
fallQuarterCell.data.remove(at: 0)
self.tableView.reloadData()
//UserDefaults.standard.set(fallQuarterCell.data, forKey: "SavedArray")
}
else if (indexPath.row == 1){
fallQuarterCell.data.remove(at: 1)
self.tableView.reloadData()
//UserDefaults.standard.set(fallQuarterCell.data, forKey: "SavedArray")
}
else if (indexPath.row == 2){
fallQuarterCell.data.remove(at: 2)
self.tableView.reloadData()
//UserDefaults.standard.set(fallQuarterCell.data, forKey: "SavedArray")
}
else if (indexPath.row == 3){
fallQuarterCell.data.remove(at: 3)
self.tableView.reloadData()
//UserDefaults.standard.set(fallQuarterCell.data, forKey: "SavedArray")
}
else if (indexPath.row == 4){
fallQuarterCell.data.remove(at: 4)
self.tableView.reloadData()
//UserDefaults.standard.set(fallQuarterCell.data, forKey: "SavedArray")
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
//add class information???
}
var quarters: customLabel? {
didSet {
guard let quarters = quarters else {return}
quarterLabel.text = quarters.title
}
}
let quarterLabel : UILabel = {
let label = UILabel()//frame: CGRect(x: 15, y: -75, width: 300, height: 50))
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = UIColor.black
label.font = UIFont.boldSystemFont(ofSize: 16)
//label.textAlignment = .center
return label
}()
let addButton : UIButton = {
let button = UIButton()//frame: CGRect(x: 345, y: 10, width: 30, height: 30))
button.setImage(UIImage(named: "addicon"), for: .normal)
button.imageEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
button.tag = 0
button.addTarget(self, action: #selector(yearOne.buttonAction), for: .touchUpInside)
return button
}()
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I also have the
class winterQuarterCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource
class springQuarterCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource
class summerQuarterCell: UICollectionViewCell, UITableViewDelegate, UITableViewDataSource
similar to that of fallQuarterCell(). I used the print(fallQuarterCell.data) to see whether the delegation works and data string appends, and it does seem to append to the array, just that it won't appear on the fallQuarterCell tableView when backed out from the SearchPage()
Here is the 'SearchPage()where the user clicks on an element from the tableView to append to thefallQuarterCell.datathrough delegation and automatically pop backs toyearOne()`:
import UIKit
protocol coursesDelegate {
func sendDataBackFall(data: String)
func sendDataBackWinter(data2: String)
func sendDataBackSpring(data3: String)
func sendDataBackSummer(data4: String)
}
class SearchPage: UITableViewController {
var delegate: coursesDelegate?
let cellId = "course"
var allCourses : NSArray = NSArray()
var filteredCourses = [String]()
var resultSearchController = UISearchController()
var tag: Int?
#objc func addTapped(sender: UIBarButtonItem!) {
let addAlert = UIAlertController(title: "Create New Course", message: "Enter a course name", preferredStyle: .alert)
addAlert.addTextField {(textfield:UITextField) in textfield.placeholder = "Course"}
addAlert.addAction(UIAlertAction(title: "Create", style: .default, handler: { (action:UIAlertAction) in
var _customCourse = String()
if let courseTextField = addAlert.textFields?.first, let customCourse = courseTextField.text{
_customCourse = customCourse
}
}))
addAlert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(addAlert, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let courses : CourseList = CourseList()
allCourses = courses.coursesList
navigationItem.title = "Select Courses"
navigationController?.navigationBar.prefersLargeTitles = true
let button1 = UIBarButtonItem(title: "Add Course", style: .plain, target: self, action: #selector(addTapped))
self.navigationItem.rightBarButtonItem = button1
self.view.backgroundColor = .systemBackground
tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
tableView.delegate = self
tableView.dataSource = self
self.tableView.tableFooterView = UIView()
resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.obscuresBackgroundDuringPresentation = false
controller.searchBar.placeholder = "Search Courses"
controller.searchBar.sizeToFit()
tableView.tableHeaderView = controller.searchBar
return controller
})()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (resultSearchController.isActive) {
return filteredCourses.count
}
else{
return allCourses.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
if (resultSearchController.isActive) {
cell.textLabel?.text = filteredCourses[indexPath.row]
return cell
}
else {
let courses = self.allCourses[indexPath.row]
cell.textLabel?.text = courses as? String
return cell
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
tableView.deselectRow(at: indexPath, animated: true)
if (tag == 0){
if (resultSearchController.isActive){
// fallQuarterCelll.data.append(filteredCourses[indexPath.row])
// UserDefaults.standard.set(fallQuarterCelll.data, forKey: "SavedArray")
// self.navigationController?.popViewController(animated: true)
let data = filteredCourses[indexPath.row]
delegate?.sendDataBackFall(data: data)
self.navigationController?.popViewController(animated: true)
}
else{
// fallQuarterCelll.data.append(allCourses[indexPath.row] as! String)
// UserDefaults.standard.set(fallQuarterCelll.data, forKey: "SavedArray")
// self.navigationController?.popViewController(animated: true)
let data = allCourses[indexPath.row] as! String
delegate?.sendDataBackFall(data: data)
self.navigationController?.popViewController(animated: true)
}
}
}
var isSearchBarEmpty: Bool {
return resultSearchController.searchBar.text?.isEmpty ?? true
}
}
extension SearchPage: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
filteredCourses.removeAll(keepingCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (allCourses as NSArray).filtered(using: searchPredicate)
filteredCourses = array as! [String]
self.tableView.reloadData()
}
}
Ignore the UsersDefault... I'm trying to make the data appear on the fallQuarterCell tableView first.
You don't reload table view in fallQuarterCell.
let vc = fallQuarterCell() creates a new cell. Your collection view doesn't contain it, so vc.tableView.reloadData() does nothing.
When you use collectionView.dequeueReusableCell, you may get the old cell, so you should reload the table view in this cell. You should read documentation:
Call this method from your data source object when asked to provide a
new cell for the collection view. This method dequeues an existing
cell if one is available or creates a new one
For example, you can add func updateViews() in fallQuarterCell:
func updateViews() {
tableView.reloadData()
}
And call it here:
if (indexPath.row == 0){
let cell1 = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier, for: indexPath) as! fallQuarterCell
cell1.layer.borderColor = UIColor.orange.cgColor
cell1.layer.borderWidth = 2
cell1.layer.cornerRadius = 5
cell1.quarters = self.quarters[0]
cell1.updateViews()
return cell1
}
All you have to do is create a separate cell class for collection view cell, and create an outlet of your table view into that cell class.
In your cellForItemAt method of the collection view do below code:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "TabelCell", for: indexPath) as! MyTabelCell
cell.MyTableView.reloadData()
}
Now you have to reload collection view and it will automatically reload the table view. That's the best and easiest way to approach TableView in CollectionView

Cannot assign to property: 'itemArray' is a get-only property

I am currently trying to implement a simple search function using UISearchBar and Core Data I am getting an error within this function "Cannot assign to property: "ItemArray" is a get-only property" If anyone can help with the error and also suggest another simple way to implement the search that would be really helpful.
func loadItems(with request: NSFetchRequest<Item>) {
let request : NSFetchRequest<Item> = Item.fetchRequest()
do {
itemArray = try context.fetch(request)
} catch {
print("Error fetching data from context")
}
tableView.reloadData()
}
}
Here is the whole file:
import UIKit
import CoreData
class TodoListViewController: UITableViewController {
var itemArray: [Item] { return Array(self.category.items!) as! [Item] }
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var category: Category!
override func viewDidLoad() {
super.viewDidLoad()
print(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask))
}
#IBOutlet weak var todoTableView: UITableView!
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: "ToDoItemCell", for: indexPath)
let item = itemArray[indexPath.row]
cell.textLabel?.text = item.title
cell.accessoryType = item.done ? .checkmark : .none
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//print(itemArray[indexPath.row])
itemArray[indexPath.row].done = !itemArray[indexPath.row].done
saveItems()
tableView.deselectRow(at: indexPath, animated: true)
}
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
var textField = UITextField()
let alert = UIAlertController(title: "Add New Todoey Item", message: "", preferredStyle: .alert)
let action = UIAlertAction(title: "Add Item", style: .default) { [weak self] (action) in
guard let self = self else { return }
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let newItem = Item(context: self.context)
newItem.title = textField.text!
self.category.addToItems(newItem)
self.saveItems()
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Create new item"
textField = alertTextField
print(alertTextField.text)
}
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
func saveItems() {
do {
try context.save()
} catch {
print("Error saving context")
}
self.tableView.reloadData()
}
func loadItems(with request: NSFetchRequest<Item>) {
let request : NSFetchRequest<Item> = Item.fetchRequest()
do {
itemArray = try context.fetch(request)
} catch {
print("Error fetching data from context")
}
tableView.reloadData()
}
}
extension TodoListViewController: 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)
tableView.reloadData()
}
itemArray is declared as read-only property, that's exactly what the error says.
A solution is to declare the property as empty array
var itemArray = [Item]()
and call loadItems in viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
loadItems()
print(FileManager.default.urls(for: .documentDirectory, in: .userDomainMask))
}
The parameter in loadItems is pointless, omit it.
func loadItems() { ... }
If you want to fetch the items for a specific Category add an appropriate NSPredicate in loadItems

How to save an array. So its the same after App restarts

I am wondering how to let my to-do-list be the same after the app is closed. In my code I am declaring an array right in the beginning and I want to know how I can save it updated for the user, after he restarted the App. I searched the web for some answers but only found old ideas about creating entities in storyboard. And I am pretty sure, that by now there has to be something more beautifully than adding it manually.
How can I save the array when it is getting updated, so it won't get lost after an app restart?
My Code:
import UIKit
var checklist = ["Item 1", "Item 2"]
class ChecklistViewController: BaseViewController, UITableViewDelegate, UITableViewDataSource{
var newChecklistItemString: String?
var alertInputTextField: UITextField?
#IBOutlet weak var myTableView: UITableView!
let mainStoryboard:UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var selectedChecklist: [String] = []
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (checklist.count)
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
cell.textLabel?.font = UIFont.boldSystemFont(ofSize: 18.0)
cell.textLabel?.text = checklist[indexPath.row]
if selectedChecklist.contains(checklist[indexPath.row]) {
cell.accessoryType = .checkmark
}
else{
cell.accessoryType = .none
}
return cell
}
// checkmarks when tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
selectedChecklist.append(checklist[indexPath.row])
tableView.deselectRow(at: indexPath, animated: true)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let value = checklist.remove(at: indexPath.row)
myTableView.reloadData()
}
}
override func viewDidAppear(_ animated: Bool) {
myTableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
addSlideMenuButton()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func addNewObject(_ sender: Any) {
let alert = UIAlertController(title: "New Item", message: nil, preferredStyle: .alert)
alert.addTextField { (alertInputTextField) in
alertInputTextField.autocapitalizationType = .sentences
}
alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
self.dismiss(animated: true, completion: nil)
}))
alert.addAction(UIAlertAction(title: "Add", style: .default, handler: { (action) in
let textf = alert.textFields![0] as UITextField
if(textf.text != "")
{
checklist.append(textf.text!)
}
self.myTableView.reloadData()
}))
self.present(alert, animated: true, completion: nil)
}
}
For a simple string array use UserDefaults
Declare two methods to load and save data
func loadDefaults()
{
self.checklist = UserDefaults.standard.array(forKey: "checklist") as? [String] ?? []
}
func saveDefaults()
{
UserDefaults.standard.set(self.checklist, forKey: "checklist")
}
In viewDidLoad add
loadDefaults()
And right after adding and removing an item add
saveDefaults()
For more complex data other solutions like Core Data are preferable
Note:
Rather than always reloading the entire table view use the API to insert and delete single rows. The benefit is a nice animation.
In tableView:commit replace myTableView.reloadData() with
myTableView.deleteRows(at: [indexPath], with: .automatic)
and in the alert action replace the entire if expression with
if !textf.text!.isEmpty {
let indexPath = IndexPath(row: checklist.count, section: 0)
checklist.append(textf.text!)
myTableView.insertRows(at: [indexPath], with: .automatic)
}
And move the variable checklist into the class!
Find this solution:
// Use this code to fetch saved item list.
if let items = UserDefaults.standard.value(forKey: "ItemListArray") as? [String] {
print(items)
}
// Use this code to save your item list.
let itemList = ["Item 1", "Item 2", "Item 3", "Item 4"]
UserDefaults.standard.setValue(itemList, forKey: "ItemListArray")
// Use this code to remove item list
UserDefaults.standard.setValue(nil, forKey: "ItemListArray")
Check the code below:-
class ClassUserDefaults {
static let shared = ClassUserDefaults()
// MARK: - SETTER GETTER
var selectedChecklist: [String]? {
get {
guard let savedItem = (UserDefaults.standard.object(forKey: "selectedChecklist")) as? Data else { return nil }
guard let data = NSKeyedUnarchiver.unarchiveObject(with: savedItem) as? [String]? else { return nil}
return data
}
set {
guard let value = newValue else {
UserDefaults.standard.removeObject(forKey: "selectedChecklist")
return
}
let item = NSKeyedArchiver.archivedData(withRootObject: value)
UserDefaults.standard.set(item, forKey: "selectedChecklist")
UserDefaults.standard.synchronize()
}
}
}
usage in your ViewController:-
ClassUserDefaults.shared.selectedChecklist = selectedChecklist
also you want to set wherever you want like:-
cell.textLabel?.text = ClassUserDefaults.shared.selectedChecklist[indexPath.row]
Hope it helps :)

Cannot assign value of type '[[String : AnyObject]]' to type '[[String : AnyObject?]]'

I'm getting the following error:
Cannot assign value of type '[[String : AnyObject]]' to type '[[String : AnyObject?]]'
It's strange this assignment was working before then when I restarted my Xcode, I started to get this error. From what I have read online, this should not give the error.
Here is my code:
import UIKit
import Alamofire
import SwiftyJSON
class Signal Condo TableViewController: UITableViewController {
#IBOutlet var tableview: UITableView!
var singleCondoData = [[String:AnyObject]]()
var CondoIndivi = [[String:AnyObject]]()
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
self.tableView.delegate = self
self.tableView.dataSource = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return singleCondoData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! SignleTableTableViewCell
if singleCondoData.count != 0 {
let dict = singleCondoData[indexPath.row] as NSDictionary
//cell.label1.text? = (dict["name"] as? String)!
if let nullcheck = (dict["address"] as? String) {
cell.label2.text? = nullcheck
}
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let dict = singleCondoData[indexPath.row] as NSDictionary
if let unitNullCheck = (dict["mls_number"] as? String) {
let item_id = unitNullCheck
getCondoUnits(item_id)
print(item_id)
}
}
//get the individual condo id
func getCondoUnits(condo_id : String){
Alamofire.request(.GET, "http://android.goidx.com/search/?mls_number=" + String(condo_id)).validate().responseJSON { response in
if let value = response.result.value {
let json = JSON(value)
if let resData = json.arrayObject {
self.CondoIndivi = (resData as? [[String:AnyObject]])!
print(self.CondoIndivi)
}
if self.CondoIndivi.count > 0 {
self.tableview.reloadData()
}
}
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let identifier = segue.identifier {
switch identifier {
case "details" :
let buildingdDetailVC = segue.destinationViewController as! DetailsViewController
buildingdDetailVC.CondoIndivi2 = self.CondoIndivi // line of the error
default:
break
}
}
}
}
The type of CondoIndivi2 variable is [[String: AnyObject?]] but you are passing an array of type [[String: AnyObject]] where the dictionary values are non-optional.
Since any non-optional value with same type can be safely converted to its optional corresponding, you can do the following:
buildingdDetailVC.CondoIndivi2 = self.CondoIndivi.map { $0 as [String: AnyObject?] }

Resources