I get
Referencing instance method 'encode' on 'Array' requires that '(item: String?, price: String?, salesPrice: String?)' conform to 'Encodable' "
this error in the storeData() function. Did I even save the tuple correctly in user defaults? If anyone can help that would be great!Any help is appreciated!
import UIKit
let defaults = UserDefaults(suiteName: "com.Saving.Data")
struct Product: Codable {
var title: String
var price: String
var salePrice: String
}
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var items: [(item: String?, price: String?, salesPrice: String?)] = []
override func viewDidLoad() {
super.viewDidLoad()
getData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
getData()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
storeData()
}
override var prefersStatusBarHidden: Bool {
return true
}
#IBAction func addButtonTapped(_ sender: Any) {
let alert = UIAlertController(title: "Product Information", message: nil, preferredStyle: .alert)
alert.addTextField { (itemTF) in
itemTF.placeholder = "Item"
}
alert.addTextField { (textField) in
textField.placeholder = "Price"
}
alert.addTextField { (textField) in
textField.placeholder = "Sale Price"
}
let action = UIAlertAction(title: "Add", style: .default) { (_) in
var product : (item: String, price: String, salesPrice: String) = ("","","")
if let textField1 = alert.textFields?[0], let text = textField1.text {
print(text)
product.item = text
}
if let textField2 = alert.textFields?[1], let text = textField2.text {
print(text)
product.price = text
}
if let textField3 = alert.textFields?[2], let text = textField3.text {
print(text)
product.salesPrice = text
}
self.add(product)
}
alert.addAction(action)
present(alert, animated: true)
storeData()
}
func add(_ product: (item: String, price: String, salesPrice: String)) {
let index = 0
items.insert(product, at: index)
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
storeData()
}
func storeData() {
if let data = try? PropertyListEncoder().encode(items) {
UserDefaults.standard.set(data, forKey: "savedData")
}
}
func getData() {
if let data = UserDefaults.standard.data(forKey: "savedData") {
let items = try! PropertyListDecoder().decode([Product].self, from: data)
print(items)
}
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let product = items[indexPath.row]
cell.textLabel?.text = product.item
print(product.price ?? "")
print(product.salesPrice ?? "")
cell.contentView.backgroundColor = UIColor(red:0.92, green:0.92, blue:0.92, alpha:1.0)
cell.textLabel?.textColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
tableView.separatorColor = UIColor(red:0.92, green:0.92, blue:0.92, alpha:1.0)
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == .delete else { return }
items.remove(at: indexPath.row)
tableView.reloadData()
storeData()
}
}
As already mentioned in the comments it's incomprehensible why you use an extra tuple with the same design as your struct Product. This causes your problems.
So get rid of the tuple.
Declare items
var items = [Product]()
Replace the alert action with
let action = UIAlertAction(title: "Add", style: .default) { _ in
let item = alert.textFields?[0].text ?? ""
let price = alert.textFields?[1].text ?? ""
let salesPrice = alert.textFields?[2].text ?? ""
let product = Product(item: item, price: price, salesPrice: salesPrice)
self.addProduct(product)
}
Replace add with
func addProduct(_ product: Product) {
let index = 0
items.insert(product, at: index)
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
storeData()
}
Replace getData with
func getData() {
if let data = UserDefaults.standard.data(forKey: "savedData") {
do {
items = try PropertyListDecoder().decode([Product].self, from: data)
print(items)
tableView.reloadData()
} catch { print(error) }
}
}
Side note: Be aware that the code saves the data in standard UserDefaults, not in your custom suite.
And finally – not related to the issue – to get a nice animation in tableView:commit editingStyle: replace
tableView.reloadData()
with
tableView.deleteRows(at: [indexPath], with: .fade)
Related
well I'm trying to add date to my header sections every time the user is done with his task. the problem is im kinda new with headers section and for some reason every time I add more than two tasks into the "Done" section, it seems like it duplicates itself.
have looked around stackOverFlow and couldn't find what I needed, and I hope u guys can help me up :)
Here's a pic of the app with the problem: https://imgur.com/wjVy9Uy
Here's my code:
import Foundation
import CoreData
import UIKit
import SwipeCellKit
protocol TaskDelegate {
func updateTaskName(name:String)
}
class TasksManViewController: UITableViewController, SwipeTableViewCellDelegate {
#IBOutlet weak var Sege: UISegmentedControl!
var tasksArray = [Task](){
didSet {
// because we perform this operation on the main thread, it is safe
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
let isSwipeRightEnabled = true
var delegate: TaskDelegate?
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
}
override func viewWillAppear(_ animated: Bool) {
loadTasks()
}
// MARK: - DataSource + Delegate Methods:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasksArray.count
}
override func numberOfSections(in tableView: UITableView) -> Int {
return tasksArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCellRow") as! SwipeTableViewCell
cell.delegate = self
cell.textLabel?.text = tasksArray[indexPath.row].title
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
if orientation == .left {
guard isSwipeRightEnabled else { return nil }
let doneAction = SwipeAction(style: .destructive, title: "Done") { (action, indexPath) in
//STEP1: Append the task to the doneTasksArr:
self.tasksArray[indexPath.row].isDone = true
//STEP3: Remove the Row:
self.tasksArray.remove(at: indexPath.row)
//STEP4: Update the Model:
self.saveTasks()
self.delegate?.updateTaskName(name: "")
self.tableView.reloadData()
}
let unDoneAction = SwipeAction(style: .destructive, title: "Undone") { (unDoneAction, indexPath) in
self.tasksArray[indexPath.row].isDone = false
self.tasksArray.remove(at: indexPath.row)
self.saveTasks()
}
//configure btn:
doneAction.backgroundColor = .cyan
unDoneAction.backgroundColor = .blue
if Sege.selectedSegmentIndex == 0 { //Doing this to allow the user to unDone tasks.
return [doneAction]
} else { return [unDoneAction] }
} else {
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
self.context.delete(self.tasksArray[indexPath.row])
self.tasksArray.remove(at: indexPath.row)
self.saveTasks()
self.delegate?.updateTaskName(name: "")
self.tableView.reloadData()
}
return [deleteAction]
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
// MARK: - Class Methods:
#IBAction func addBtnTapped(_ sender: UIBarButtonItem) {
Sege.selectedSegmentIndex = 0 // Doing this to avoid the user to insert a task into the DoneTasks by mistake! and avoiad a bug :)
insertNewTask()
}
func insertNewTask() {
var textField = UITextField()
let alert = UIAlertController(title: "New Task", message: "Please Add Your Task", preferredStyle: .alert)
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Create New Task"
textField = alertTextField
}
let action = UIAlertAction(title: "Add", style: .default) { (action) in
let newItem = Task(context: self.context)
newItem.title = textField.text!
newItem.isDone = false
newItem.date = Date()
self.tasksArray.append(newItem)
self.delegate?.updateTaskName(name: newItem.title!)
self.saveTasks()
}
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
}
// MARK: - Sege Section:
#IBAction func segeControlTapped(_ sender: UISegmentedControl) {
switch Sege.selectedSegmentIndex
{
case 0:
//Loading normal tasks whose are not done
loadTasks()
case 1:
//Loading the doneTasks:
loadDoneTasksFrom()
default:
print("There's something wrong with Sege!")
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let completedTasksToDisply = 0
if Sege.selectedSegmentIndex == 1 {
if let firstTask = tasksArray[section].date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy"
let dateString = dateFormatter.string(from: firstTask)
return " " + dateString + " " + " " + "Completed Tasks: \(completedTasksToDisply)"
}
}
return ""
}
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
header.textLabel?.font = UIFont(name: "Verdana", size: 13.0)
header.textLabel?.textAlignment = NSTextAlignment.center
}
//MARK: - Model Manipulation Methods:
func saveTasks() {
do {
try! context.save()
} catch let err {
print("Error Saving context \(err)")
}
}
func loadTasks() {
let request: NSFetchRequest<Task> = Task.fetchRequest()
request.predicate = NSPredicate(format: "isDone == %#", NSNumber(value: false))
request.sortDescriptors = [NSSortDescriptor(key: "isDone", ascending: false)]
do{
tasksArray = try! context.fetch(request)
} catch {
print("There was an error with loading items \(error)")
}
tableView.reloadData()
}
func loadDoneTasksFrom() {
let request:NSFetchRequest<Task> = Task.fetchRequest()
request.predicate = NSPredicate(format: "isDone == %#", NSNumber(value: true))
request.sortDescriptors = [NSSortDescriptor(key: "isDone", ascending: false)]
do{
tasksArray = try context.fetch(request)
} catch {
print("Error fetching data from context\(error)")
}
tableView.reloadData()
}
}
EDIT: Okay I see what you are trying to do. Give this a shot - we will need a double array - each array inside will have to be split by date - our query would look something like this:
var tasksArray = [[Task]]()
var sectionDates = [String]()
func loadTasks() {
tasksArray.removeAll(keepingCapacity: false)
sectionDates.removeAll(keepingCapacity: false)
let request: NSFetchRequest<Task> = Task.fetchRequest()
request.predicate = NSPredicate(format: "isDone == %#", NSNumber(value: false))
request.sortDescriptors = [NSSortDescriptor(key: "isDone", ascending: false)]
do{
let tasks = try! context.fetch(request)
for task in tasks {
if let taskDate = task.date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy"
let dateString = dateFormatter.string(from: taskDate)
if self.sectionDates.contains(dateString) {
if let section = self.sectionDates.firstIndex(of: dateString) {
self.tasksArray[section].append(task)
}
} else {
self.sectionDates.append(dateString)
// EDIT - ADD THIS LINE (and self. where necessary)
self.tasksArray.append([])
if let section = self.sectionDates.firstIndex(of: dateString) {
self.tasksArray[section].append(task)
}
}
}
}
} catch {
print("There was an error with loading items \(error)")
}
tableView.reloadData()
}
Could also be helpful to load your objects in order but with this set up it should still group everything correctly regardless of the order - unless you want your sections in a specific order then you can specify in query
Now changes to your tableView delegate methods
override func numberOfSections(in tableView: UITableView) -> Int {
return sectionDates.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasksArray[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCellRow") as! SwipeTableViewCell
cell.delegate = self
cell.textLabel?.text = tasksArray[indexPath.section][indexPath.row].title
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let completedTasksToDisply = 0
if Sege.selectedSegmentIndex == 1 {
let dateString = sectionDates[section]
return " " + dateString + " " + " " + "Completed Tasks: \(completedTasksToDisply)"
}
return ""
}
I figured I'd leave the rest of the tableView delegate methods to you but for the most part we just need to identify the section before the indexPath row when working with the double array - as we did in cellForRow
I ran this code and this was the result:
I am trying to get three items in a single tableview cell just like the picture.
I want the app to look exactly like the picture.
Image(CLICK ME) What I want it to look like
I have tried to do this a million different way but cant seem to figure out the correct way to do it, the app completely crash right now.
Need some help as i am a beginner to swift, i am still learning but for some reason this has been a challenge.
import UIKit
class ViewController: UIViewController, UIAdaptivePresentationControllerDelegate {
#IBOutlet weak var tableView: UITableView!
var items:[Product]? = []
// VIEW LOAD
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 13.0, *) {
self.isModalInPresentation = true
}
getData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
getData()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
storeData()
}
override var prefersStatusBarHidden: Bool {
return true
}
// ADD ITEMS
#IBAction func addButtonTapped(_ sender: Any) {
let alert = UIAlertController(title: "Product Information", message: nil, preferredStyle: .alert)
alert.addTextField { (itemTextField) in
itemTextField.placeholder = "Item"
}
alert.addTextField { (priceTextField) in
priceTextField.placeholder = "Price"
}
alert.addTextField { (salePriceTextField) in
salePriceTextField.placeholder = "Sale Price"
}
let action = UIAlertAction(title: "Add", style: .default) { (_) in
let item = alert.textFields?[0].text ?? ""
let price = alert.textFields?[1].text ?? ""
let salesPrice = alert.textFields?[2].text ?? ""
let product = Product(item: item, price: price, salesPrice: salesPrice)
self.addProduct(product)
}
alert.addAction(action)
present(alert, animated: true)
storeData()
}
func addProduct(_ product: Product) {
let index = 0
items?.insert(product, at: index)
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
storeData()
}
//STORE DATA
func storeData() {
UserDefaultUtil.saveData(products: items)
}
func getData() {
items = UserDefaultUtil.loadProducts()
}
}
//EXTENSION
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let product = items![indexPath.row]
cell.textLabel?.text = "\(product.item!) \(product.price) \(product.salesPrice)"
cell.contentView.backgroundColor = UIColor(red:0.92, green:0.92, blue:0.92, alpha:1.0)
cell.textLabel?.textColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
tableView.separatorColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == .delete else { return }
items?.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
storeData()
}
}
class UserDefaultUtil {
private static let Key = "savedData"
private static func archivePeople(people : [Product]) -> NSData {
return NSKeyedArchiver.archivedData(withRootObject: people as NSArray) as NSData
}
static func loadProducts() -> [Product]? {
if let unarchivedObject = UserDefaults.standard.object(forKey: Key) as? Data {
return NSKeyedUnarchiver.unarchiveObject(with: unarchivedObject as Data) as? [Product]
}
return nil
}
static func saveData(products : [Product]?) {
let archivedObject = archivePeople(people: products!)
UserDefaults.standard.set(archivedObject, forKey: Key)
UserDefaults.standard.synchronize()
}
}
class Product: NSObject, NSCoding {
var item: String?
var price: String?
var salesPrice: String?
required init(item:String, price:String, salesPrice: String) {
self.item = item
self.price = price
self.salesPrice = salesPrice
}
required init(coder aDecoder: NSCoder) {
self.item = aDecoder.decodeObject(forKey: "item") as? String
self.price = aDecoder.decodeObject(forKey: "price") as? String
self.salesPrice = aDecoder.decodeObject(forKey: "salesPrice") as? String
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(item, forKey: "item")
aCoder.encode(price, forKey: "price")
aCoder.encode(salesPrice, forKey: "salesPrice")
}
}
import UIKit
class ViewController: UIViewController, UIAdaptivePresentationControllerDelegate {
#IBOutlet weak var tableView: UITableView!
var items = [Product]()
// VIEW LOAD
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 13.0, *) {
self.isModalInPresentation = true
}
getData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
getData()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
storeData()
}
override var prefersStatusBarHidden: Bool {
return true
}
// ADD ITEMS
#IBAction func addButtonTapped(_ sender: Any) {
let alert = UIAlertController(title: "Product Information", message: nil, preferredStyle: .alert)
alert.addTextField(configurationHandler: { (itemTextField) -> Void in
itemTextField.placeholder = "Item"
})
alert.addTextField(configurationHandler: { (priceTextField) -> Void in
priceTextField.placeholder = "Price"
})
alert.addTextField(configurationHandler: { (salePriceTextField) -> Void in
salePriceTextField.placeholder = "Sale Price"
})
alert.addAction(UIAlertAction(title: "Add", style: .default) { (action) in
let item = alert.textFields?[0].text ?? ""
let price = alert.textFields?[1].text ?? ""
let salesPrice = alert.textFields?[2].text ?? ""
let product = Product(item: item, price: price, salesPrice: salesPrice)
self.addProduct(product)
})
self.present(alert, animated: true, completion: nil)
storeData()
}
func addProduct(_ product: Product) {
items.append(product)
tableView.reloadData()
storeData()
}
//STORE DATA
func storeData() {
UserDefaultUtil.saveData(products: items)
}
func getData() {
if let data = UserDefaults.standard.data(forKey: "savedData"), let unarchivedObject = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Product] {
items = unarchivedObject
}
}
}
//EXTENSION
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let product = items[indexPath.row]
cell.textLabel?.text = "\(product.item!) \(product.price) \(product.salesPrice)"
cell.contentView.backgroundColor = UIColor(red:0.92, green:0.92, blue:0.92, alpha:1.0)
cell.textLabel?.textColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
tableView.separatorColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == .delete else { return }
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
storeData()
}
}
class UserDefaultUtil {
private static let Key = "savedData"
private static func archivePeople(people : [Product]) -> NSData {
return NSKeyedArchiver.archivedData(withRootObject: people as NSArray) as NSData
}
static func saveData(products : [Product]?) {
let archivedObject = archivePeople(people: products!)
UserDefaults.standard.set(archivedObject, forKey: Key)
UserDefaults.standard.synchronize()
}
}
class Product: NSObject, NSCoding {
var item: String?
var price: String?
var salesPrice: String?
required init(item:String, price:String, salesPrice: String) {
self.item = item
self.price = price
self.salesPrice = salesPrice
}
required init(coder aDecoder: NSCoder) {
self.item = aDecoder.decodeObject(forKey: "item") as? String
self.price = aDecoder.decodeObject(forKey: "price") as? String
self.salesPrice = aDecoder.decodeObject(forKey: "salesPrice") as? String
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(item, forKey: "item")
aCoder.encode(price, forKey: "price")
aCoder.encode(salesPrice, forKey: "salesPrice")
}
}
Total Profit Calculate
import UIKit
class ViewController: UIViewController, UIAdaptivePresentationControllerDelegate {
#IBOutlet weak var tableView: UITableView!
var items = [Product]()
// VIEW LOAD
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 13.0, *) {
self.isModalInPresentation = true
}
getData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
getData()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
storeData()
}
override var prefersStatusBarHidden: Bool {
return true
}
// ADD ITEMS
#IBAction func addButtonTapped(_ sender: Any) {
let alert = UIAlertController(title: "Product Information", message: nil, preferredStyle: .alert)
alert.addTextField(configurationHandler: { (itemTextField) -> Void in
itemTextField.placeholder = "Item"
})
alert.addTextField(configurationHandler: { (priceTextField) -> Void in
priceTextField.placeholder = "Price"
})
alert.addTextField(configurationHandler: { (salePriceTextField) -> Void in
salePriceTextField.placeholder = "Sale Price"
})
alert.addAction(UIAlertAction(title: "Add", style: .default) { (action) in
let item = alert.textFields?[0].text ?? ""
let price = alert.textFields?[1].text ?? ""
let salesPrice = alert.textFields?[2].text ?? ""
let profit = Double(salesPrice)! - Double(price)!
let TotalProfit = profit
let product = Product(item: item, price: price, salesPrice: salesPrice, TotalProfit: TotalProfit)
self.addProduct(product)
})
self.present(alert, animated: true, completion: nil)
storeData()
}
func addProduct(_ product: Product) {
items.append(product)
tableView.reloadData()
storeData()
}
//STORE DATA
func storeData() {
UserDefaultUtil.saveData(products: items)
}
func getData() {
if let data = UserDefaults.standard.data(forKey: "savedData"), let unarchivedObject = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Product] {
items = unarchivedObject
}
}
}
//EXTENSION
extension ViewController: UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let product = items[indexPath.row]
cell.textLabel?.text = "\(product.item!) \(String(describing: product.price)) \(String(describing: product.salesPrice))"
cell.detailTextLabel?.text = "Total Profit \(product.TotalProfit)"
cell.contentView.backgroundColor = UIColor(red:0.92, green:0.92, blue:0.92, alpha:1.0)
cell.textLabel?.textColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
tableView.separatorColor = UIColor(red:0.13, green:0.13, blue:0.13, alpha:1.0)
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == .delete else { return }
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
storeData()
}
}
class UserDefaultUtil {
private static let Key = "savedData"
private static func archivePeople(people : [Product]) -> NSData {
return NSKeyedArchiver.archivedData(withRootObject: people as NSArray) as NSData
}
static func saveData(products : [Product]?) {
let archivedObject = archivePeople(people: products!)
UserDefaults.standard.set(archivedObject, forKey: Key)
UserDefaults.standard.synchronize()
}
}
class Product: NSObject, NSCoding {
var item: String?
var price: String?
var salesPrice: String?
var TotalProfit: Double?
init(item:String, price:String, salesPrice: String ,TotalProfit: Double ) {
self.item = item
self.price = price
self.salesPrice = salesPrice
self.TotalProfit = TotalProfit
}
required init(coder aDecoder: NSCoder) {
self.item = aDecoder.decodeObject(forKey: "item") as? String
self.price = aDecoder.decodeObject(forKey: "price") as? String
self.salesPrice = aDecoder.decodeObject(forKey: "salesPrice") as? String
self.TotalProfit = aDecoder.decodeObject(forKey: "TotalProfit") as? Double
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(item, forKey: "item")
aCoder.encode(price, forKey: "price")
aCoder.encode(salesPrice, forKey: "salesPrice")
aCoder.encode(TotalProfit, forKey: "TotalProfit")
}
}
I'm trying to delete a reminder from my tableview but when its been sourced from filtering through the searchbar, the regular delete works perfect when a filter isnt taking place, but when a filter is in the search bar it crashes the app.
Heres the code;
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
var updatedArray = [Reminder]()
if editingStyle == .delete {
if filtered == false {
reminders.remove(at: indexPath.row)
tableReminders.deleteRows(at: [indexPath], with: .fade)
convertAndSaveInDDPath(array: reminders)
}
if filtered == true {
updatedArray = reminders.filter{ $0.reminderName != filterData[indexPath.row].reminderName}
print(updatedArray)
reminders = updatedArray
tableReminders.deleteRows(at: [indexPath], with: .fade)
tableReminders.reloadData()
//convertAndSaveInDDPath(array: reminders)
}
}
}
Any help would be appreciated, thank you.
EDIT (NEW CODE):
public struct Reminder {
var reminderName : String
var reminderPriority : String
var reminderDate : Date
var reminderStatus : String
var reminderSavedTime : Date
}
var reminders : [Reminder] = []
var filtered : Bool = false
public func getFilePath(fileName:String) -> String {
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
let filePath = url.appendingPathComponent(fileName)?.path
return filePath!
}
public func convertAndSaveInDDPath (array:[Reminder]) {
let objCArray = NSMutableArray()
for obj in array {
// we have to do something like this as we can't store struct objects directly in NSMutableArray
let dict = NSDictionary(objects: [obj.reminderName ,obj.reminderPriority, obj.reminderDate, obj.reminderStatus, obj.reminderSavedTime ], forKeys: ["reminderName" as NSCopying,"reminderPriority" as NSCopying, "reminderDate" as NSCopying, "reminderStatus" as NSCopying, "reminderSavedTime" as NSCopying])
objCArray.add(dict)
}
// this line will save the array in document directory path.
objCArray.write(toFile: getFilePath(fileName: "remindersArray"), atomically: true)
}
public func getArray() -> [Reminder]? {
var remindersArray = [Reminder]()
if let _ = FileManager.default.contents(atPath: getFilePath(fileName: "remindersArray")) {
let array = NSArray(contentsOfFile: getFilePath(fileName: "remindersArray"))
for (_,userObj) in array!.enumerated() {
let reminderDict = userObj as! NSDictionary
let reminder = Reminder(reminderName: (reminderDict.value(forKey: "reminderName") as? String)!, reminderPriority: (reminderDict.value(forKey: "reminderPriority") as? String)!, reminderDate: (reminderDict.value(forKey: "reminderDate") as? Date)!, reminderStatus: (reminderDict.value(forKey: "reminderStatus") as? String)!, reminderSavedTime: (reminderDict.value(forKey: "reminderSavedTime") as? Date)!)
remindersArray.append(reminder)
}
return remindersArray
}
return nil
}
class ViewController: UIViewController, UITableViewDataSource, UITextFieldDelegate, UITableViewDelegate, UISearchBarDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Reminder Section
return filteredReminder.count
}
#IBOutlet var searchBar: UISearchBar!
var filterData = [Reminder]()
var originalReminder = [Reminder]() // original data array.
var filteredReminder = [Reminder]() // data that used to show in tableview.
var sortedAZState : Bool = false
var sortedTimeState : Bool = false
var sortedPrioState : Bool = false
#IBOutlet weak var tableReminders: UITableView!
#IBOutlet weak var timeSortBtn: UIButton!
#IBOutlet weak var sortBtn: UIButton!
#IBOutlet weak var prioritySortBtn: UIButton!
#IBAction func BtnSort(_ sender: Any) {
sortList(sender: sortBtn) // sorts by a-z through the sort function
}
#IBAction func btnSortTime(_ sender: Any) {
sortList(sender: timeSortBtn)
}
#IBAction func btnSortPriority(_ sender: Any) {
sortList(sender: prioritySortBtn)
}
func sortList(sender: UIButton) { // should probably be called sort and not filter
if sender.tag == 1 && sortedAZState == false {
reminders.sort() { $0.reminderName < $1.reminderName } // sort the reminder by name
tableReminders.reloadData(); // notify the table view the data has changed
print("sender.tag 1")
sortedAZState = true
}
else if sender.tag == 1 && sortedAZState == true {
reminders.sort() { $0.reminderName > $1.reminderName } // sort the reminder by name
tableReminders.reloadData(); // notify the table view the data has changed
print("sender.tag 1")
sortedAZState = false
}
else if sender.tag == 2 && sortedTimeState == false {
reminders.sort { $0.reminderSavedTime.compare($1.reminderSavedTime) == .orderedAscending }
tableReminders.reloadData();
print("sender.tag 2")
sortedTimeState = true
}
else if sender.tag == 2 && sortedTimeState == true {
reminders.sort { $0.reminderSavedTime.compare($1.reminderSavedTime) == .orderedDescending }
tableReminders.reloadData();
print("sender.tag 2")
sortedTimeState = false
}
else if sender.tag == 3 && sortedPrioState == false {
reminders.sort() { $0.reminderPriority.count < $1.reminderPriority.count } // sort the reminder by priority
tableReminders.reloadData(); // notify the table view the data has changed
print("sender.tag 3")
sortedPrioState = true
}
else if sender.tag == 3 && sortedPrioState == true {
reminders.sort() { $0.reminderPriority.count > $1.reminderPriority.count } // sort the reminder by priority
tableReminders.reloadData(); // notify the table view the data has changed
print("sender.tag 3")
sortedPrioState = false
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create an object of the dynamic cell "plainCell"
let cell = tableView.dequeueReusableCell(withIdentifier: "ReminderTableViewCell", for: indexPath) as! ReminderTableViewCell
// Depending on the section, fill the textLabel with the relevant text
// Reminder Section
cell.reminderLabel.text = filteredReminder[indexPath.row].reminderName
if filteredReminder[indexPath.row].reminderPriority == "!" {
let yourImage: UIImage = UIImage(named: "lowpriority")!
cell.priorityImage.image = yourImage
}
else if filteredReminder[indexPath.row].reminderPriority == "!!" {
let yourImage: UIImage = UIImage(named: "mediumpriority")!
cell.priorityImage.image = yourImage
}
else if filteredReminder[indexPath.row].reminderPriority == "!!!" {
let yourImage: UIImage = UIImage(named: "highpriority")!
cell.priorityImage.image = yourImage
}
/* I DON'T KNOW WHAT THIS COMPLETION FOR, HOPE IT IS WORKING A/C TO YOUR NEED. */
// cell.completeButtonAction = { [unowned self] in
// let reminderCall = reminders[indexPath.row].reminderName
// let alert = UIAlertController(title: "Complete!", message: "You have completed \(reminderCall).", preferredStyle: .alert)
// let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
// alert.addAction(okAction)
//
// reminders.remove(at: indexPath.row)
// self.tableReminders.deleteRows(at: [indexPath], with: .fade)
// convertAndSaveInDDPath(array: reminders)
//
// print("reminder deleted")
//
// self.present(alert, animated: true, completion: nil)
// }
//
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
print(reminders[indexPath.row].reminderName)
let selectedReminder = reminders[indexPath.row].reminderName
let destinationVC = EditReminderViewController()
destinationVC.reminderPassed = selectedReminder
performSegue(withIdentifier: "editSegue", sender: indexPath)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
filteredReminder.remove(at: indexPath.row)
tableReminders.deleteRows(at: [indexPath], with: .fade)
print(filteredReminder)
convertAndSaveInDDPath(array: filteredReminder) // UNCOMMENT THIS
}
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
// var decapValue = searchBar.text?.lowercased()
// print(decapValue!)
// print(LinearSearch(searchText: decapValue!, array: reminders))
if searchBar.text != "" {
self.filteredReminder = originalReminder.filter({ reminder -> Bool in
return reminder.reminderName.lowercased().contains(searchText.lowercased())
})
}
else {
self.filteredReminder = self.originalReminder
}
tableReminders.reloadData()
// print(filterData)
}
/*
func LinearSearch(searchText: String, array: [Reminder]) -> Bool { // search function to return a true or a false bool (contains two parameneters, search value and array
for i in reminders { // cycles through each element in the array
if i.reminderName.lowercased().contains(searchText) { // if element = search (return true)
filterData.append(i)
print(filterData)
return true
}
}
return false // returns false if no element comes back to equal the searchValue
}
*/
#IBAction func btnAdd(_ sender: Any) {
performSegue(withIdentifier: "addSegue", sender: (Any).self)
}
override func viewDidLoad() {
super.viewDidLoad() // example cell
// reminders.append(Reminder(reminderName: "HOMEWORK", reminderPriority: "LOW", reminderDate: "4324", reminderStatus: "INCOMPLETE"))
tableReminders.dataSource = self
tableReminders.delegate = self
searchBar.delegate = self
tableReminders.reloadData()
// print file path of array saved
// print(getFilePath(fileName: "remindersArray"))
let reminderRetrievedArray = getArray()
reminders = reminderRetrievedArray!
originalReminder = reminders
gfilteredReminder = reminders
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I didn't know which code you were after so there is all of it. But yes you are correct, it deletes one at a time, but it is saving an empty array to be loaded for next open.
Here are the changes to make your code work perfect, clean, understandable and working. If you have any doubt then write comment and if there is any wrong in answer then update the answer.
You don not need to maintain flag filtered. I am assuming you have two array
var originalReminder = [Reminder]() // original data array.
var filteredReminder = [Reminder]() // data that used to show in tableview.
In ViewDidLoad , set right data in originalReminder and if you wanted to show all the original data in table then assign same data in filteredReminder also.
Now we will manage the tableview with 1 array i.e. filteredReminder and if searchbar text is empty the then we will assign orignal array to filtered array. So your searchbar textDidChange look like this.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text != "" {
self.filteredReminder = originalReminder.filter({ reminder -> Bool in
return reminder.reminderName.lowercased().contains(searchText.lowercased())
})
}
else {
self.filteredReminder = self.originalReminder
}
tableReminders.reloadData()
}
You can remove redundant code from cellForRow like below
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredReminder.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Create an object of the dynamic cell "plainCell"
let cell = tableView.dequeueReusableCell(withIdentifier: "ReminderTableViewCell", for: indexPath) as! ReminderTableViewCell
// Depending on the section, fill the textLabel with the relevant text
// Reminder Section
cell.reminderLabel.text = filteredReminder[indexPath.row].reminderName
if filteredReminder[indexPath.row].reminderPriority == "!" {
let yourImage: UIImage = UIImage(named: "lowpriority")!
cell.priorityImage.image = yourImage
}
else if filteredReminder[indexPath.row].reminderPriority == "!!" {
let yourImage: UIImage = UIImage(named: "mediumpriority")!
cell.priorityImage.image = yourImage
}
else if filteredReminder[indexPath.row].reminderPriority == "!!!" {
let yourImage: UIImage = UIImage(named: "highpriority")!
cell.priorityImage.image = yourImage
}
/* I DON'T KNOW WHAT THIS COMPLETION FOR, HOPE IT IS WORKING A/C TO YOUR NEED. */
// cell.completeButtonAction = { [unowned self] in
// let reminderCall = reminders[indexPath.row].reminderName
// let alert = UIAlertController(title: "Complete!", message: "You have completed \(reminderCall).", preferredStyle: .alert)
// let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
// alert.addAction(okAction)
//
// reminders.remove(at: indexPath.row)
// self.tableReminders.deleteRows(at: [indexPath], with: .fade)
// convertAndSaveInDDPath(array: reminders)
//
// print("reminder deleted")
//
// self.present(alert, animated: true, completion: nil)
// }
//
return cell
}
To delete the cell, now need to do this only.
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
filteredReminder.remove(at: indexPath.row)
tableReminders.deleteRows(at: [indexPath], with: .fade)
// convertAndSaveInDDPath(array: filteredReminder) // UNCOMMENT THIS
}
}
How can I update or insert a row in a tableview without reloading all the data?
In the ClientsViewController a list of clients is shown alphabetically and separated by sections (first letter of the client`s name).
When I update a client, the tableview shows 2 entries (old and new one) of the same client. When I try to add a client, it crashes.
I think the problem is with indexing.
class ClientsViewController: UITableViewController {
var sortedFirstLetters: [String] = []
var sections: [[Client]] = [[]]
var tableArray = [Client]()
var client: Client?
var refresher: UIRefreshControl!
#IBOutlet var noClientsView: UIView!
#IBAction func unwindToClients(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? ClientViewController, let client = sourceViewController.client {
if let selectedIndexPath = tableView.indexPathForSelectedRow {
// Update an existing client.
tableArray[selectedIndexPath.row] = client
tableView.reloadRows(at: [selectedIndexPath], with: .automatic)
}
else {
// Add a client.
let newIndexPath = IndexPath(row: tableArray.count, section: 0)
tableArray.append(client)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
let firstLetters = self.tableArray.map { $0.nameFirstLetter }
let uniqueFirstLetters = Array(Set(firstLetters))
self.sortedFirstLetters = uniqueFirstLetters.sorted()
self.sections = self.sortedFirstLetters.map { firstLetter in
return self.tableArray
.filter { $0.nameFirstLetter == firstLetter }
.sorted { $0.name < $1.name }
}
// DispatchQueue.main.async {
// self.tableView.reloadData()
// }
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondScene = segue.destination as! ClientViewController
if segue.identifier == "ShowDetail", let indexPath = self.tableView.indexPathForSelectedRow {
let currentPhoto = sections[indexPath.section][indexPath.row]
secondScene.client = currentPhoto
}
else if segue.identifier == "AddItem" {
print("add")
}
else {
fatalError("The selected cell is not being displayed by the table")
}
}
#objc func handleRefresh(_ refreshControl: UIRefreshControl) {
getClients()
refreshControl.endRefreshing()
}
}
extension ClientsViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.refreshControl?.addTarget(self, action: #selector(ClientsViewController.handleRefresh(_:)), for: UIControlEvents.valueChanged)
tableView.backgroundView = noClientsView
getClients() //for only the 1st time ==> when view is created ==> ok ish
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
getClients() // not a good idea to make a request to the server everytime the view appears on the screen.
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if(self.tableArray.count > 0) {
return sortedFirstLetters[section]
}
else {
return ""
}
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
print(sortedFirstLetters)
return sortedFirstLetters
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let name = sections[indexPath.section][indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: "ClientCell", for: indexPath)
cell.textLabel?.text = name.name
cell.detailTextLabel?.text = name.city + " - " + name.province
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(sections[section].count > 0) {
tableView.backgroundView = nil
}
return sections[section].count
}
func getClients() {
makeRequest(endpoint: "api/clients/all",
parameters: [:],
completionHandler: { (container : ApiContainer<Client>?, error : Error?) in
if let error = error {
print("error calling POST on /getClients")
print(error)
return
}
self.tableArray = (container?.result)!
self.prepareData()
DispatchQueue.main.async {
self.tableView.reloadData()
}
} )
}
//sorts and makes the index
func prepareData() {
let firstLetters = self.tableArray.map { $0.nameFirstLetter }
let uniqueFirstLetters = Array(Set(firstLetters))
self.sortedFirstLetters = uniqueFirstLetters.sorted()
self.sections = self.sortedFirstLetters.map { firstLetter in
return self.tableArray
.filter { $0.nameFirstLetter == firstLetter }
.sorted { $0.name < $1.name }
}
}
}
Bellow is ClientViewController and Client struct.
class ClientViewController: UITableViewController, UIPickerViewDelegate, UIPickerViewDataSource {
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var addressTextField: UITextField!
#IBOutlet weak var cityTextField: UITextField!
#IBOutlet weak var provinceTextField: UITextField!
#IBOutlet weak var postalCodeTextField: UITextField!
#IBOutlet weak var contactsLabel: UILabel!
let numberOfRowsAtSection: [Int] = [4, 2]
var client: Client?
var selectedProvince: String?
override func viewWillAppear(_ animated: Bool) {
self.title = "New"
if (client?.client_id) != nil {
self.title = "Edit"
nameTextField.text = client?.name
provinceTextField.text = client?.province
cityTextField.text = client?.city
addressTextField.text = client?.address
postalCodeTextField.text = client?.postal_code
selectedProvince = client?.province
}
}
#objc func save(sender: UIButton!) {
let name = nameTextField.text ?? ""
let address = addressTextField.text ?? ""
let city = cityTextField.text ?? ""
let province = selectedProvince ?? ""
let postal_code = postalCodeTextField.text ?? ""
var endPoint: String
if (client?.client_id) != nil {
endPoint = "api/clients/update"
} else {
endPoint = "api/clients/add"
}
client = Client(name:name, client_id: client?.client_id, postal_code: postal_code, province: province, city: city, address: address)
let requestBody = makeJSONData(client)
makeRequestPost(endpoint: endPoint,
requestType: "POST",
requestBody: requestBody,
view: view,
completionHandler: { (response : ApiContainer<Client>?, error : Error?) in
if let error = error {
print("error calling POST on /todos")
print(error)
return
}
let b = (response?.meta)!
let a = (response?.result[0])
let client_id = a?.client_id
self.client?.client_id = client_id
if(b.sucess == "yes") {
//change message and use the custom func like on error.
let alert = UIAlertController(title: "Success!", message: "All good", preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {
(_)in
self.performSegue(withIdentifier: "unwindToClients", sender: self)
})
alert.addAction(OKAction)
DispatchQueue.main.async(execute: {
self.present(alert, animated: true, completion: nil)
})
}
else
{
self.showAlert(title: "Error", message: "Error Creating Client")
//return
}
} )
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.sectionHeaderHeight = 50.0;
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Save", style: .plain, target: self, action: #selector(save))
let thePicker = UIPickerView()
provinceTextField.inputView = thePicker
thePicker.delegate = self
// ToolBar
let toolBar = UIToolbar()
toolBar.barStyle = .default
toolBar.isTranslucent = true
toolBar.tintColor = UIColor(red: 92/255, green: 216/255, blue: 255/255, alpha: 1)
toolBar.sizeToFit()
// Adding Button ToolBar
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(ClientDetailViewController.doneClick))
let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(ClientDetailViewController.cancelClick))
toolBar.setItems([cancelButton, spaceButton, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
provinceTextField.inputAccessoryView = toolBar
}
#objc func doneClick() {
provinceTextField.resignFirstResponder()
}
#objc func cancelClick() {
provinceTextField.resignFirstResponder()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rows: Int = 0
if section < numberOfRowsAtSection.count {
rows = numberOfRowsAtSection[section]
}
return rows
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let backItem = UIBarButtonItem()
backItem.title = "Client"
navigationItem.backBarButtonItem = backItem
if segue.identifier == "unwindToClients",
let destination = segue.destination as? ClientsViewController
{
destination.client = client
}
if segue.identifier == "showContacts",
let destination = segue.destination as? ContactsViewController
{
destination.client = client
}
}
// MARK: - Picker view
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView( _ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return provinces.count
}
func pickerView( _ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return provinces[row].name
}
func pickerView( _ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
provinceTextField.text = provinces[row].name
}
}
struct Client: Codable {
var client_id: Int!
let name: String!
let postal_code: String!
let province: String!
let city: String!
let address: String!
init(name: String, client_id: Int! = nil, postal_code: String, province: String, city: String, address: String) {
self.client_id = client_id
self.name = name
self.postal_code = postal_code
self.province = province
self.city = city
self.address = address
}
var nameFirstLetter: String {
return String(self.name[self.name.startIndex]).uppercased()
}
}
Append the items in datasource at index where you want to insert row in table. than follow steps 2,3,4.
Call method tbl.beginupdate
call insertRowsAtIndexPaths method of table view
Call tbl.endupdate
you can update tableView rows using this..
self.tableView.reloadRows(at: [indexPath!], with: .top)
I've googled and read a lot of questions and answers about this topic, however I still can't solve my problem. I have a project connected to firebase, with a tableView connected to that information. The following error pops up:
unexpectedly found nil while unwrapping an Optional value.
I've checked all my connections between UIButtons, UILabels and such, and most of all i have tried using breakpoints to locate the error. However I can't seem to get my head around a solution. Here comes code and messages from the project and the error:
#IBOutlet weak var addButton: UIBarButtonItem!
let listToUsers = "ListToUsers"
var backgroundNr = 0
var items: [GroceryItem] = []
let ref = FIRDatabase.database().reference(withPath: "grocery-items")
let usersRef = FIRDatabase.database().reference(withPath: "online")
var user: User!
var userCountBarButtonItem: UIBarButtonItem!
var counter = 1
override func viewDidLoad() {
super.viewDidLoad()
if user?.email == "tor#gmail.com" {
addButton.isEnabled = false
}
tableView.allowsMultipleSelectionDuringEditing = false
userCountBarButtonItem = UIBarButtonItem(title: "1",
style: .plain,
target: self,
action: #selector(userCountButtonDidTouch))
userCountBarButtonItem.tintColor = UIColor.white
navigationItem.leftBarButtonItem = userCountBarButtonItem
usersRef.observe(.value, with: { snapshot in
if snapshot.exists() {
self.userCountBarButtonItem?.title = snapshot.childrenCount.description
} else {
self.userCountBarButtonItem?.title = "0"
}
})
ref.queryOrdered(byChild: "completed").observe(.value, with: { snapshot in
var newItems: [GroceryItem] = []
for item in snapshot.children {
let groceryItem = GroceryItem(snapshot: item as! FIRDataSnapshot)
newItems.append(groceryItem)
}
self.items = newItems
self.tableView.reloadData()
})
FIRAuth.auth()!.addStateDidChangeListener { auth, user in
guard let user = user else { return }
self.user = User(authData: user)
let currentUserRef = self.usersRef.child(self.user!.uid)
currentUserRef.setValue(self.user!.email)
currentUserRef.onDisconnectRemoveValue()
}
}
// MARK: UITableView Delegate methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! SubjectTableViewCell
let groceryItem = items[indexPath.row]
if backgroundNr == 0 {
cell.backgroundColor = UIColor(red: 222/255, green: 164/255, blue: 50/255, alpha: 0.6)
backgroundNr += 1
} else {
cell.backgroundColor = UIColor.white
backgroundNr -= 1
}
cell.textLabel?.text = groceryItem.name
cell.detailTextLabel?.text = groceryItem.addedByUser
toggleCellCheckbox(cell, isCompleted: groceryItem.completed)
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let groceryItem = items[indexPath.row]
groceryItem.ref?.removeValue()
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRow(at: indexPath) else { return }
let groceryItem = items[indexPath.row]
let toggledCompletion = !groceryItem.completed
toggleCellCheckbox(cell, isCompleted: toggledCompletion)
groceryItem.ref?.updateChildValues([
"completed": toggledCompletion
])
}
func toggleCellCheckbox(_ cell: UITableViewCell, isCompleted: Bool) {
if !isCompleted {
cell.accessoryType = .none
cell.textLabel?.textColor = UIColor.black
cell.detailTextLabel?.textColor = UIColor.black
} else {
cell.accessoryType = .checkmark
cell.textLabel?.textColor = UIColor.gray
cell.detailTextLabel?.textColor = UIColor.gray
}
}
// MARK: Add Item
#IBAction func addButtonTapped(_ sender: Any) {
let alert = UIAlertController(title: "Kunskaps Område",
message: "Lägg till objekt",
preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Spara",
style: .default) { _ in
// 1
guard let textField = alert.textFields?.first,
let text = textField.text else { return }
// 2
let groceryItem = GroceryItem(name: text,
addedByUser: self.user!.email,
completed: false)
// 3
let groceryItemRef = self.ref.child(text.lowercased())
// 4
groceryItemRef.setValue(groceryItem.toAnyObject())
}
let cancelAction = UIAlertAction(title: "Cancel",
style: .default)
alert.addTextField()
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert, animated: true, completion: nil)
}
func userCountButtonDidTouch() {
performSegue(withIdentifier: listToUsers, sender: nil)
}
#IBAction func startClockTapp(_ sender: Any) {
Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(GroceryListTableViewController.updateCounter), userInfo: nil, repeats: true)
}
func updateCounter() {
counter += 1
let clock = String(counter)
print(clock)
let clockCurrent = Clock(name: clock, addedByUser: self.user!.email)
let clockCurrentRef = self.ref.child(clock.lowercased())
clockCurrentRef.setValue(clockCurrent.toAnyObject())
}
When using breakpoints to locate where the error occurs, it seems that it is located in the following function: (the first line of code in "UITable view delegate methods)
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
I don't now if I'm using the breakpoints wrong, however that's the result I get, if it helps.
The "grocery" objects struct file´s code is the following:
import Foundation
import Firebase
struct GroceryItem {
let key: String
let name: String
let addedByUser: String
let ref: FIRDatabaseReference?
var completed: Bool
init(name: String, addedByUser: String, completed: Bool, key: String = "") {
self.key = key
self.name = name
self.addedByUser = addedByUser
self.completed = completed
self.ref = nil
}
init(snapshot: FIRDataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
name = snapshotValue["name"] as! String
addedByUser = snapshotValue["addedByUser"] as! String
completed = snapshotValue["completed"] as! Bool
ref = snapshot.ref
}
func toAnyObject() -> Any {
return [
"name": name,
"addedByUser": addedByUser,
"completed": completed
]
}
}
The database content on Firebase gets structured as following:
educationlevel-e230e
grocery-items
Fysik:, addedByUser:, completed:
The full string of the basic error message:
fatal error: unexpectedly found nil while unwrapping an Optional value
2016-12-24 01:22:54.799117 EducationLevel[1077:263810]
fatal error:
unexpectedly found nil while unwrapping an Optional value
I really can't find the answer to this problem by my self or anywhere on the web, REALLY appreciates it if I could get some help (sorry for the probably bad english, I'm from Sweden;) )
(If you need more strings from the error message or anything like that I'll immediately update this question)
Thanks!! Tor from Sweden.