Unable to display array values on drop down menu - arrays

I am trying to code a drop down menu where I try to get an array to be displayed on a UITableView when a button is pressed. However, the entire mechanism works without having the values displayed. I am able to see the cell borders and scroll but no values can be seen :( Appreciate any help!
(My code)
import UIKit
class MainViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var dropDownButton: UIButton!
var fruityList = ["Apple", "Orange", "Pear", "Banana", "Peach"]
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.isHidden = true
}
#IBAction func dropDownButtonPressed(_ sender: Any) {
if tableView.isHidden {
animate(toggle: true)
} else {
animate(toggle: false)
}
}
func animate(toggle:Bool) {
if toggle {
UIView.animate(withDuration: 0.3) {
self.tableView.isHidden = false
}
} else {
UIView.animate(withDuration: 0.3) {
self.tableView.isHidden = true
}
}
}
}
extension MainViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return fruityList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = fruityList[indexPath.row]
return cell
}
}

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.

Remove item from array in UITableview

What I'm trying to accomplish is when user selects an element for UITableView this element gets append to servicioSeleccionadoarray. But I'm stuck in this because if user decides to deselect the cell I want to remove that item from the array. I've try anyarray.remove(at:)but I can figure the way to tapp into that index.
This is my code so far.
class ServicioHogarViewController: UIViewController{
let serviciosHogar = [String](arrayLiteral: "Alfombras", "Muebles Madera", "Sillones", "Marmol", "Aplicación Teflón","Vestiduras", "Salas", "Colchones", "Sillas Oficinas")
#IBOutlet weak var servicioHogarTB1: UITableView!
var selectedIndex : Int? = nil
var servicioSeleccionado : [String] = []
#IBAction func doneButton(_ sender: UIButton) {
performSegue(withIdentifier: "datePick2", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
servicioHogarTB1.delegate = self
servicioHogarTB1.dataSource = self
servicioHogarTB1.register(UINib(nibName: "ServicioHogarCell", bundle: nil), forCellReuseIdentifier: "servicioCell1")
servicioHogarTB1.separatorStyle = .none
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
print(servicioSeleccionado)
}
}
// MARK : UITabeView Delegation
extension ServicioHogarViewController : UITableViewDelegate, UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return serviciosHogar.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath)?.accessoryType == .checkmark {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
} else {
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
}
if tableView.cellForRow(at: indexPath)?.accessoryType == .checkmark {
servicioSeleccionado.append(serviciosHogar[indexPath.row])
print(servicioSeleccionado)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "servicioCell1", for: indexPath) as! ServicioHogarCell
let servicio = serviciosHogar[indexPath.row]
cell.servicioLabel.text = servicio
return cell
}
}
If you have an array :
var cast = ["Vivien", "Marlon", "Kim", "Karl"]
and you want to remove "Marlon" from it, you can find the index of Marlon using the func firstIndex(of: Element) -> Int? method for an array and then remove it like so :
if let index = cast.firstIndex(of: "Marlon"){
cast.remove(at: index)
print(cast)
}
That said, on didSelectRowAt run this function for your array and you'll accomplish what you're looking to.

Sorting a TableView alphabetically in Swift 3

I have searched Stack Overflow well and truly with a variety of different options offered but none I can seem to get working.
I have an app which has a glossary of terms for NFL (e.g. offence/defence) etc. It comes from a JSON API and is stored locally. I would like to sort the data programmatically, so the terms are all listed in their respective sections ("A", "B", "C" etc.) alphabetically.
View Controller:
import UIKit
class GlossaryVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
private let glossaryCellId = "GlossaryCell"
var glossarys = [Offence]()
var filteredGlossarys = [Offence]()
private var subCategorySet: Set<String> = []
private var glossarysDictionary: [String: [Offence]] = [:]
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var noResultsView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
filteredGlossarys = glossarys
tableView.delegate = self
tableView.dataSource = self
tableView.backgroundColor = UIColor.clear
searchBar.delegate = self
noResultsView.isHidden = true
hideKeyboardWhenTappedAround()
}
override func viewDidAppear(_ animated: Bool) {
DataProvider.getData(with: .GLOSSARY_DATA_URL) { (letters) in
self.glossarys = letters.flatMap { $0.offences }
self.filteredGlossarys = self.glossarys
self.groupGlossarys()
}
}
func groupGlossarys() {
glossarysDictionary.removeAll()
subCategorySet.removeAll()
for glossary in filteredGlossarys {
subCategorySet.insert(glossary.glossaryLetter!)
}
for subCategory in subCategorySet {
glossarysDictionary[subCategory] = []
}
for glossary in filteredGlossarys {
var list = glossarysDictionary[glossary.glossaryLetter!]
list?.append(glossary)
glossarysDictionary[glossary.glossaryLetter!] = list
}
checkAndShowTableView()
}
func checkAndShowTableView() {
if subCategorySet.count > 0 {
tableView.isHidden = false
noResultsView.isHidden = true
tableView.reloadData()
} else {
tableView.isHidden = true
noResultsView.isHidden = false
}
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let header = view as! UITableViewHeaderFooterView
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
func numberOfSections(in tableView: UITableView) -> Int {
return subCategorySet.count
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let subCategoryList = Array(subCategorySet)
return subCategoryList[section]
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let subCategoryList = Array(subCategorySet)
let glossaryList = glossarysDictionary[subCategoryList[section]]
return glossaryList!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let glossaryCell = tableView.dequeueReusableCell(withIdentifier: glossaryCellId, for: indexPath) as? GlossaryCell else {
fatalError("Unable to dequeue contact cell")
}
let subCategoryList = Array(subCategorySet)
let glossaryList = glossarysDictionary[subCategoryList[indexPath.section]]
glossaryCell.configure(glossary:(glossaryList?[indexPath.row])!)
return glossaryCell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.backgroundColor = UIColor.clear
cell.contentView.backgroundColor = UIColor.clear
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let viewGlossaryDetailsVC = segue.destination as? GlossaryDetailsVC,
let section = tableView.indexPathForSelectedRow?.section, let row = tableView.indexPathForSelectedRow?.row {
let subCategoryList = Array(subCategorySet)
let glossaryList = glossarysDictionary[subCategoryList[section]]
let glossarys = glossaryList?[row]
viewGlossaryDetailsVC.glossarys = glossarys
}
}
}
extension GlossaryVC: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if !searchText.isEmpty {
filteredGlossarys = glossarys.filter({ (glossary) -> Bool in
let glossaryTitleRange = glossary.glossaryTitle?.range(of: searchText, options: .caseInsensitive)
let glossaryDescriptionRange = glossary.glossaryDescription?.range(of: searchText, options: .caseInsensitive)
let glossaryKeywordsRange = glossary.glossaryKeywords?.range(of: searchText, options: .caseInsensitive)
return glossaryTitleRange != nil || glossaryDescriptionRange != nil || glossaryKeywordsRange != nil
})
} else {
filteredGlossarys = glossarys
}
groupGlossarys()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
}
I have so far tried the sorted(by: {}) method on the let subCategoryList = Array(subCategorySet) in the titleForHeaderInSection which does arrange the section headers alphabetically but the data doesn't change. So you end up having words beginning with "H" in the "A" subcategory.
Data is grouped based on a "letter" key in the database, e.g., so all "A" are grouped together, etc.
Any help would be appreciated, all I want to do is sort it alphabetically and I feel a little out of my depth.

Populating an UITableview from a struct

I have two viewControllers one called programlist that displays the list of tiles and populates a a suitable view.
the second viewController inputs the data. Issues implementing the callback due to an error in the prepareForsegue function. Getting the error "Instance member 'callback' cannot be used on type 'addWorkout'"
viewController 1 aka Programlist:
import UIKit
struct Item: Codable {
var title: String
var others: [String]
}
class ProgramList: UIViewController, UITableViewDataSource, UITableViewDelegate{
var Programs = [Item]()
#IBOutlet weak var programTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
load()
}
//saving current state of programs array
func save() {
guard let data = try? JSONEncoder().encode(Programs) else { return }
UserDefaults.standard.set(data, forKey: "notes")
}
//loading saved program array
func load() {
guard let loadedData = UserDefaults.standard.data(forKey: "notes") else { return }
do {
Programs = try JSONDecoder().decode([Item].self, from: loadedData)
programTableView.reloadData()
} catch { print(error) }
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Programs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.programTitle.text = Programs[indexPath.row].title
return cell
}
//Removing Item by swipping left & saving this newly established array
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
Programs.remove(at: indexPath.row)
programTableView.reloadData()
save()
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAddPage"{
workoutController.callback = { [weak self] string in
let entered = Item(title: string, others: ["hi"])
self?.programs.append(entered)
let indexPath = IndexPath(row: self?.programs.count - 1, section: 0)
self?.tableView.insertRows(at: [indexPath], with: .automatic)
self?.save()
}
}
}
}
}
}
viewController 2 aka addWorkout:
import UIKit
class addWorkout: UIViewController {
#IBOutlet weak var workoutTitle: UITextField!
var callback : ((String) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func saveWorkoutTitle(_ sender: Any) {
if !workoutTitle.text!.isEmpty {
callback?(workoutTitle.text!)
}
}
}
The main mistake is you are trying to save an array of Item – which is not supported anyway – to UserDefaults and read an array of String. That's a clear type mismatch.
To be able to save an array of a custom struct to UserDefaults adopt Codable to save the struct as JSON.
struct Item : Codable {
var title: String
var others: [String]
}
Further it's a very bad practice to declare a data source array outside of any class.
This is the ProgramList class with adjusted load and save methods and the data source array inside the class. The method viewDidAppear is not needed.
class ProgramList: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var programTableView: UITableView!
var programs = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
load()
}
//saving current state of programs array
func save() {
guard let data = try? JSONEncoder().encode(programs) else { return }
UserDefaults.standard.set(data, forKey: "notes")
}
//loading saved program array
func load() {
guard let loadedData = UserDefaults.standard.data(forKey: "notes") else { return }
do {
programs = try JSONDecoder().decode([Item].self, from: loadedData)
programTableView.reloadData()
} catch { print(error) }
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return programs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.programTitle.text = programs[indexPath.row].title
return cell
}
//Removing Item by swipping left & saving this newly established array
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
programs.remove(at: indexPath.row)
programTableView.deleteRows(at: [indexPath], with: .automatic)
save()
}
}
}
To share data between controllers use a closure as callback and pass the string
class AddWorkout: UIViewController {
#IBOutlet weak var workoutTitle: UITextField!
var callback : ((String) -> Void)?
#IBAction func saveWorkoutTitle(_ sender: Any) {
if !workoutTitle.text!.isEmpty {
callback?(workoutTitle.text!)
}
}
}
Back in ProgramList controller assign a closure to the callback property in prepareForSegue (or right before presenting the controller)
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAddPage" {
let workoutController = segue.destination as! AddWorkout
workoutController.callback = { string in
let entered = Item(title: string, others: ["hi"])
self.programs.append(entered)
let indexPath = IndexPath(row: self.programs.count - 1, section: 0)
self.tableView.insertRows(at: [indexPath], with: .automatic)
self.save()
}
}
}

How to make a button, which adds new row with array data in TableView inside ViewController(they are not separate view controllers!)?

I am making a game using swift, and I want to make a history of answers. But the problem is Xcode tells thread1 exc_bad_instruction when I click button "addResult". Here is the code. Before launching the simulator and even after launching there is no error shown, however, when I click the "addResult" button the app crashes.
class FirstViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var choice1Text: UITextField!
#IBOutlet weak var resultLabel: UILabel!
#IBOutlet weak var choice2Text: UITextField!
#IBOutlet var mytableView: UITableView! {
didSet {
mytableView.dataSource = self
}
}
var items: [ResultlistItem]
required init?(coder aDecoder: NSCoder) {
items = [ResultlistItem]()
let row0item = ResultlistItem()
row0item.text = "Yes"
items.append(row0item)
let row1item = ResultlistItem()
row1item.text = "No"
items.append(row1item)
super.init(coder: aDecoder)
}
}
#IBAction func addResult(sender: AnyObject) {
let item = ResultlistItem()
item.text = "new one"
items.append(item)
mytableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Resultitem", for: indexPath)
let item = items[indexPath.row]
configureText(for: cell, with: item)
return cell
}
func configureText(for cell: UITableViewCell, with item: ResultlistItem) {
let label = cell.viewWithTag(1000) as! UILabel
label.text = item.text
}

Resources