Doesnt save userdefaults once the app closes - ios

I have created an app that is a uitableview that has 5 arrays of cell [1, 2, 3, 4, 5] that can edit when the cell is tapped. I have accomplish that when you tapped a cell it shows an alertview and a textfield. You can edit the cell in short. And i have save the edit when it goes to another view controller. But the problem is when i try to close the app and open it, It goes back to the default number of messages like [1, 2, 3, 4, 5]. Any tips how can i save the edited cell but at the same time when the uitableview is opened it shows the array. Sorry for my english. Thanks btw i am new to programming sorry for this code
*Messages.Swift = tableview
class Messages: UITableViewController {
#IBOutlet var uitableView: UITableView!
var tField: UITextField!
var index: Int?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
//Get the array
if let array = UserDefaults.standard.object(forKey:"messages") as? Array<String> {
self.app.helper.messages = array
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return app.helper.messages.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let messagesCell = app.helper.messages[indexPath.row]
cell.textLabel?.text = messagesCell
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let path = tableView.indexPathForSelectedRow
index = path?.row
changeMessage()
}
func changeMessage() {
let alert = UIAlertController(title: "Message", message: "Enter new preset message", preferredStyle: UIAlertControllerStyle.alert)
alert.addTextField(configurationHandler: configurationTextField)
let doneAction = UIAlertAction(title: "Save", style: UIAlertActionStyle.default, handler:{ (UIAlertAction) in
let modelString = self.tField.text
self.app.helper.messages[self.index!] = modelString!
self.app.helper.saveToDefaults()
self.tableView.reloadData()
})
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alert.addAction(doneAction)
alert.addAction(cancelAction)
self.present(alert, animated: true, completion: nil)
}
func configurationTextField(textField: UITextField!){
if (textField) != nil {
tField = textField
textField.text = app.helper.messages[index!]
}
}
}
*Helper.swift
class Helper {
var messages: [String] = ["1", "2", "3", "4", "5"]
func saveToDefaults() {
let defaults = UserDefaults.standard
defaults.set(messages, forKey: "messages")
defaults.synchronize()
}
}

try
override func viewWillAppear(_ animated: Bool) {
//Get the array
if let array = UserDefaults.standard.object(forKey:"messages") as? Array<String> {
self.app.helper.messages = array
}
//make this array global(declare it below var index: Int?)
tableView.reloadData()
}
and
let messagesCell = array[indexPath.row]

Try to load user default before setting the table view delegate. Because the table view is loading with the dataset you assigned in helper, not with the dataset from user default.
override func viewDidLoad() {
super.viewDidLoad()
//Get the array
if let array = UserDefaults.standard.object(forKey:"messages") as? Array<String> {
self.app.helper.messages = array
}
tableView.delegate = self
tableView.dataSource = self
}
Or you can reload the table view in view will appear after loading the dataset from user defaut
override func viewWillAppear(_ animated: Bool) {
//Get the array
if let array = UserDefaults.standard.object(forKey:"messages") as? Array<String> {
self.app.helper.messages = array
}
tableView.reloadData()
}

Related

How can I fix the error that says no value inputs to core data?

I would like to save the values "title: String" and "done: Boolean" by following sentences. Title is written in textfield in Alert, and done is set as false as default. but there are errors saying no value is input to core data.
How can I fix this?
import UIKit
import CoreData
class TodoListViewController: UITableViewController {
var itemArray = [Item]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - TableViewDataSource Methods
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 = Item(context: context)
cell.textLabel?.text = item.title
cell.accessoryType = item.done ? .checkmark : .none
return cell
}
// MARK: - TableView Delegate Methods
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
itemArray[indexPath.row].done = !itemArray[indexPath.row].done
tableView.deselectRow(at: indexPath, animated: true)
tableView.reloadData()
self.saveItems()
}
// MARK: - Add New Items
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "Add New Todoey Item", message: "", preferredStyle: .alert)
var textField = UITextField()
let newItem = Item(context: self.context)
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Create new item"
textField = alertTextField
}
let action = UIAlertAction(title: "Add Item", style: .default) { (action) in
print(textField.text ?? "no data")
newItem.title = alert.textFields?.first?.text ?? ""
newItem.done = false
self.itemArray.append(newItem)
print(self.itemArray)
print(newItem)
self.saveItems()
self.tableView.reloadData()
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
func saveItems(){
do {
print("\(context)")
try context.save()
}catch {
print("error, \(error)")
}
}
Error message is as follows:
error, Error Domain=NSCocoaErrorDomain Code=1570 "title is a required value." UserInfo={NSValidationErrorObject=<Todoey.Item: 0x600000424a00> (entity: Item; id: 0x60000277c4c0 x-coredata:///Item/tA47CD9C0-9813-49C7-9594-91AFFCFE511F3; data: {
done = 0;
title = nil;
}), NSLocalizedDescription=title is a required value., NSValidationErrorKey=title, NSValidationErrorValue=null}
It is the first time to ask a question here. If there is more detail required, please let me know.
I write in the textfield, but there is no text in tableView. but when I print , there is a right input.
how app works
code of appdelegate
delegate properties in viewcontroller

Edit operation is not working in Core Data

I'm working on core data crud operation.I've Person object and when user click on add (+) button user can add the person name and person name will show in tableview. When user click on any tableView cell it shows the alert and current tap cell person show in alert text field and user can edit the name of person but my logic is not working fine and user is not editing.I've one more problem like when i run the app the pervious users record not showing but it will showing after I will enter the new user and fetch all the pervious users see the code and guide me thanks in advance.
ViewController Class:
#IBOutlet weak var tableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var items : [Person] = [] {
didSet{
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
tableView.register(UINib(nibName: String(describing: StundentCell.self), bundle: .main), forCellReuseIdentifier: String(describing: StundentCell.self))
}
#IBAction func addPerson(_ sender: UIBarButtonItem) {
showAlert()
}
private func showAlert(_ title:String = "Add new Item", message: String = "what is your name"){
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addTextField()
let subbmitBtn = UIAlertAction(title: "Add Person", style: .default) { (action) in
let textField = alertController.textFields![0]
let newPerson = Person(context:self.context)
newPerson.name = textField.text
newPerson.age = 20
newPerson.gender = "Male"
do {
try self.context.save()
}
catch{
}
self.fetchPerople()
}
alertController.addAction(subbmitBtn)
self.present(alertController, animated: true, completion: nil)
}
func fetchPerople(){
do{
self.items = try context.fetch(Person.fetchRequest())
}
catch{
}
}
}
extension ViewController: UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
self.configurePersonCell(tableView, cellForRowAt: indexPath)
}
func configurePersonCell(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StundentCell.self)) as? StundentCell else {return UITableViewCell()}
cell.studentData = items[indexPath.row]
cell.delete.tag = indexPath.row
cell.delete.addTarget(self, action: #selector(deleteRecord(sender:)), for: .touchUpInside)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var person = items[indexPath.row].name
let alertController = UIAlertController(title: "Edit Person", message: "Edit name", preferredStyle: .alert)
alertController.addTextField()
let alertField = alertController.textFields![0]
alertField.text = person
let saveBtn = UIAlertAction(title: "Save", style: .default) { (action) in
let getName = alertController.textFields![0]
person = getName.text
do{
try self.context.save()
}
catch{
}
self.fetchPerople()
}
alertController.addAction(saveBtn)
self.present(alertController, animated: true, completion: nil)
}
func gotoPersonDetails(personData:Person){
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
#objc func deleteRecord(sender: UIButton){
if items.count > 0{
let personRemove = self.items[sender.tag]
self.context.delete(personRemove)
do {
try self.context.save()
}
catch{
}
self.fetchPerople()
}
}
#IBOutlet weak var tableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var items : [Person] = [] {
didSet{
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
tableView.register(UINib(nibName: String(describing: StundentCell.self), bundle: .main), forCellReuseIdentifier: String(describing: StundentCell.self))
}
#IBAction func addPerson(_ sender: UIBarButtonItem) {
showAlert()
}
private func showAlert(_ title:String = "Add new Item", message: String = "what is your name"){
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertController.addTextField()
let subbmitBtn = UIAlertAction(title: "Add Person", style: .default) { (action) in
let textField = alertController.textFields![0]
let newPerson = Person(context:self.context)
newPerson.name = textField.text
newPerson.age = 20
newPerson.gender = "Male"
do {
try self.context.save()
}
catch{
}
self.fetchPerople()
}
alertController.addAction(subbmitBtn)
self.present(alertController, animated: true, completion: nil)
}
func fetchPerople(){
do{
self.items = try context.fetch(Person.fetchRequest())
}
catch{
}
}
}
extension ViewController: UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
self.configurePersonCell(tableView, cellForRowAt: indexPath)
}
func configurePersonCell(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
guard let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StundentCell.self)) as? StundentCell else {return UITableViewCell()}
cell.studentData = items[indexPath.row]
cell.delete.tag = indexPath.row
cell.delete.addTarget(self, action: #selector(deleteRecord(sender:)), for: .touchUpInside)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var person = items[indexPath.row].name
let alertController = UIAlertController(title: "Edit Person", message: "Edit name", preferredStyle: .alert)
alertController.addTextField()
let alertField = alertController.textFields![0]
alertField.text = person
let saveBtn = UIAlertAction(title: "Save", style: .default) { (action) in
//get the person name in alert text field
let getName = alertController.textFields![0]
//edit the name of the person object
person = getName.text
do{
try self.context.save()
}
catch{
}
self.fetchPerople()
}
alertController.addAction(saveBtn)
self.present(alertController, animated: true, completion: nil)
}
func gotoPersonDetails(personData:Person){
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
#objc func deleteRecord(sender: UIButton){
/* fro api calling or firebase for deleting the rows in tableview
if items.count > 0 {
items.remove(at:sender.tag)
}
*/
if items.count > 0{
let personRemove = self.items[sender.tag]
//remove the person
self.context.delete(personRemove)
//save the data
do {
try self.context.save()
}
catch{
}
//refetch the data
self.fetchPerople()
}
}
You are modifying the name string but you don't update the record.
Get the whole person
let person = items[indexPath.row] // can be a constant because it's reference type
assign the name to the text field
alertField.text = person.name
and assign the edited name back
let nameField = alertController.textFields![0]
person.name = nameField.text
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let person = items[indexPath.row]
let alertController = UIAlertController(title: "Edit Person", message: "Edit name", preferredStyle: .alert)
alertController.addTextField()
let alertField = alertController.textFields![0]
alertField.text = person.name
let saveBtn = UIAlertAction(title: "Save", style: .default) { (action) in
let nameField = alertController.textFields![0]
person.name = nameField.text
do{
try self.context.save()
}
catch{
}
self.fetchPerople()
}
alertController.addAction(saveBtn)
self.present(alertController, animated: true, completion: nil)
}

My TableView only lists the last item in my itemArray?

I have four items in my itemArray, but my TableView only lists my last appended item, and it lists it first. However, I can check and uncheck the three rows below the first row that actually has a text.
import UIKit
class ToDoListViewController: UITableViewController {
var itemArray = [Item]()
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
let newItem = Item()
newItem.title = "Find Mike"
itemArray.append(newItem)
let newItem2 = Item()
newItem.title = "Buy Eggos"
itemArray.append(newItem2)
let newItem3 = Item()
newItem.title = "Destroy Demongorgon"
itemArray.append(newItem3)
let newItem4 = Item()
newItem.title = "RockOn!"
itemArray.append(newItem4)
// if let items = defaults.array(forKey: "ToDoListArray") as? [String] {
// itemArray = items
// }
}
//MARK: - TableView Datasource Methods
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
}
//MARK: - TableView Delegate Methods
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//print(itemArray[indexPath.row])
itemArray[indexPath.row].done = !itemArray[indexPath.row].done
tableView.reloadData()
tableView.deselectRow(at: indexPath, animated: true)
}
//MARK: - Add New Items
#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) { (action) in
//what will happen once the user clicks the add item
let newItem = Item()
newItem.title = textField.text!
self.itemArray.append(newItem)
self.defaults.set(self.itemArray, forKey: "ToDoListArray")
self.tableView.reloadData()
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Create new item"
textField = alertTextField
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
}
StackOverflow say I need more of a description... I think this says it all... This is for a Udemy iOS series...
You have used same instance for set item name. Change your viewDidLoad method.
override func viewDidLoad() {
super.viewDidLoad()
let newItem = Item()
newItem.title = "Find Mike"
itemArray.append(newItem)
let newItem2 = Item()
newItem2.title = "Buy Eggos"
itemArray.append(newItem2)
let newItem3 = Item()
newItem3.title = "Destroy Demongorgon"
itemArray.append(newItem3)
let newItem4 = Item()
newItem4.title = "RockOn!"
itemArray.append(newItem4)
// if let items = defaults.array(forKey: "ToDoListArray") as? [String] {
// itemArray = items
// }
}
check this and it's working, Your code after some slight edits:
import UIKit
class Item {
var title: String
var done: Bool
init(itemTitle: String, itemStatus: Bool) {
title = itemTitle
done = itemStatus
}
}
class ToDoListViewController: UITableViewController {
var itemArray = [Item]()
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
addItemsToArray()
// if let items = defaults.array(forKey: "ToDoListArray") as? [String] {
// itemArray = items
// }
}
//MARK: - add Items
func addItemsToArray(){
let newItem = Item(itemTitle: "Find Mike", itemStatus: false)
itemArray.append(newItem)
let newItem2 = Item(itemTitle: "Buy Eggos", itemStatus: false)
itemArray.append(newItem2)
let newItem3 = Item(itemTitle: "Destroy Demongorgon", itemStatus: false)
itemArray.append(newItem3)
let newItem4 = Item(itemTitle: "RockOn!", itemStatus: false)
itemArray.append(newItem4)
}
//MARK: - TableView Datasource Methods
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)
cell.textLabel?.text = itemArray[indexPath.row].title
cell.accessoryType = itemArray[indexPath.row].done ? .checkmark : .none
return cell
}
//MARK: - TableView Delegate Methods
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//print(itemArray[indexPath.row])
itemArray[indexPath.row].done = !itemArray[indexPath.row].done
tableView.reloadData()
tableView.deselectRow(at: indexPath, animated: true)
}
//MARK: - Add New Items
#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) { (action) in
//what will happen once the user clicks the add item
let newItem = Item(itemTitle: textField.text!, itemStatus: false)
self.itemArray.append(newItem)
self.defaults.set(self.itemArray, forKey: "ToDoListArray")
self.tableView.reloadData()
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Create new item"
textField = alertTextField
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
}
The problem in your implementation is that you are using the same instance you are appending newItem instead of newItem2 and newItem3
I would strongly advise using the below approach I will provide, it is safe and be customize easily
struct Item {
var titleName: String?
var description: String?
init(titleName: String, description: String) {
self.titleName = titleName
self.description = description
}
}
enum ItemOptions: Int, CaseIterable {
case Find
case Local
case Search
var title: String {
switch self {
case .Find: return "Find"
case .Local: return "Local"
case .Search: return "Search"
}
}
var desciprion: String {
switch self {
case .Find: return "Find Desc"
case .Local: return "Local Desc"
case .Search: return "Search Desc"
}
}
}
class ToDoListViewController: UITableViewController {
var itemArray = [Item]()
let defaults = UserDefaults.standard
override func viewDidLoad() {
super.viewDidLoad()
for item in ItemOptions.allCases {
itemArray.append(Item(titleName: item.title, description: item.desciprion))
}
}
}
You can update the item Struct and add multiple values and only you change the enum either add maybe a new image, price.

How to save UILabel from custom cell so that every time I open the app it displays the latest change?

I am a beginner in Swift. I've exhausted all my trial and errors and need help!!
I am creating a scoreboard project using a UITableView with a Custom Cell that holds a UILabel and a UIButton. After a button press the UILabel increments by one to simulate a point for the player. I am having trouble saving the point in UILabel so that every time I open the app the point for that player remains. I've tried using UserDefaults, structs, and delegates but have't had any luck...I could be doing it wrong. I just need to know what the proper approach is for this.
Note: I am able to save the player name successfully from the UIAlertController so that when I open the app the names are still there unless I delete them, but haven't had any luck saving the points for each name itself, they still remain "0".
It should look like this when I close then open the app, but it only does this when the app is opened:
Scoreboard UITableView - Screenshot
Here's the ViewController code:
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var items = [String]()
#IBOutlet weak var listTableView: UITableView!
#IBAction func addItem(_ sender: AnyObject) {
alert()
}
override func viewDidLoad() {
super.viewDidLoad()
listTableView.dataSource = self
self.items = UserDefaults.standard.stringArray(forKey:"items") ?? [String]()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! PointsCell
cell.textLabel?.text = items[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func saveData() {
UserDefaults.standard.set(items, forKey: "items")
}
func alert(){
let alert = UIAlertController(title: "Add Player", message: "", preferredStyle: .alert)
alert.addTextField{
(textfield) in
textfield.placeholder = " Enter Player Name "
}
let add = UIAlertAction(title: "Add", style: .default)
{
(action) in guard let textfield = alert.textFields?.first else {return}
if let newText = textfield.text
{
self.items.append(newText)
self.saveData()
let indexPath = IndexPath(row: self.items.count - 1, section: 0)
self.listTableView.insertRows(at: [indexPath], with: .automatic)
}
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel) {
(alert) in
}
alert.addAction(add)
alert.addAction(cancel)
present(alert, animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
saveData()
}
}
Here is my custom cell code called PointsCell:
import UIKit
class PointsCell: UITableViewCell {
var winScore = 0
#IBOutlet weak var scoreUILabel: UILabel!
#IBAction func pointButtonPressed(_ sender: Any) {
winScore += 1
scoreUILabel.text = "\(winScore)"
}
}
The points still remains 0 because you are note saving them, firstly you need points for each player so you need to combine player name and score into object.
class Player:NSObject, Codable{
let name: String
var score : Int
init(name: String, score: Int) {
self.name = name
self.score = score
}
override var description: String{
return "Name :" + self.name + " Score :" + String(self.score )
}
}
Swift 4 introduced the Codable protocol, by adopting Codable on your own types enables you to serialize them to and from any of the built-in data formats.
Now you can easily access a player with name and score.
var players = [Player]()
To get stored value from UserDefaults
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let items: Data = UserDefaults.standard.object(forKey: "items") as? Data{
self.players = try! PropertyListDecoder().decode([Player].self, from: items)
self.listTableView.reloadData()
}
}
When you adding new player to your list create new instance of Player and add it to list.
func alert(){
let alert = UIAlertController(title: "Add Player", message: "", preferredStyle: .alert)
alert.addTextField{
(textfield) in
textfield.placeholder = " Enter Player Name "
}
let add = UIAlertAction(title: "Add", style: .default)
{
(action) in guard let textfield = alert.textFields?.first else {return}
if let newText = textfield.text
{
let player = Player(name: newText, score: 0)
self.players.append(player)
let indexPath = IndexPath(row: self.players.count - 1, section: 0)
self.listTableView.insertRows(at: [indexPath], with: .automatic)
}
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel) {
(alert) in
}
alert.addAction(add)
alert.addAction(cancel)
present(alert, animated: true, completion: nil)
}
Now update your UITableViewDataSource method as new list item.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! PointsCell
cell.player = players[indexPath.row]
return cell
}
now need to update saveData method to save new list into UserDefaults, you'll call this method whenever you want to save your list.
func saveData() {
UserDefaults.standard.set(try! PropertyListEncoder().encode(self.players), forKey: "items")
}
PointsCell class also need to be updated as new object type:
class PointsCell: UITableViewCell {
#IBOutlet weak var scoreUILabel: UILabel!
#IBOutlet weak var nameUILabel: UILabel!
var player: Player? {
didSet{
if let name = player?.name {
self.nameUILabel?.text = name
}
if let score = player?.score {
self.scoreUILabel?.text = String(score)
}
}
}
#IBAction func pointbuttonPressed(_ sender: Any) {
if self.player != nil{
let score = self.player?.score ?? 0
self.player?.score = score + 1
scoreUILabel.text = String(self.player?.score ?? 0)
}
}
}

how to get data to save to specific category in tableview instead on all categories?

I have a groceryList app
when you add an item to the category list it adds to the entire list of categories when is should not!
https://github.com/mrbryankmiller/Grocery-TableView-.git
class GroceryItemsTableViewController: UITableViewController {
//var groceryItem = ["Item1", "Item2", "Item3"]
//var groceryList = ["Breakfast","Lunch", "Dinner"]
#IBOutlet var groceryItemTableView: UITableView!
#IBAction func addGroceryItemButtonPressed(sender: UIBarButtonItem) {
///new way///
let alertController: UIAlertController = UIAlertController(title: "Add Grocery Item", message: "", preferredStyle: .Alert)
//Cancel Button
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//cancel code
}
alertController.addAction(cancelAction)
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in
let textField = alertController.textFields![0]
groceryItem.items.append(textField.text!)
self.tableView.reloadData()
}
alertController.addAction(saveAction)
//Add text field
// alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in
// textField.textColor = UIColor.blackColor()
alertController.addTextFieldWithConfigurationHandler { (textField : UITextField!) -> Void in
textField.placeholder = "Enter an Item"
//alertController.textFields
}
//Present the AlertController
self.presentViewController(alertController, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
//self.navigationItem.leftBarButtonItem = self.editButtonItem()
}
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 groceryItem.items.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("groceryItem1", forIndexPath: indexPath)
cell.textLabel!.text = groceryItem.items [indexPath.row]
return cell
}
}
If you see carefully the declaration of your class groceryItem you have a static array of elements for every item in your grocery list so every time you add a new element it's shared among all the grocery items.
Instead you should have for each grocery item a list associated with each of its items.
You could define a new struct to save for each grocery item its list of item associated like in the following way:
struct GroceryItem {
var name: String
var items: [String]
}
The we are going to change a little the code in your GroceryListTableViewController to refactor the code according your new model, so it should be like the following:
GroceryListTableViewController:
class GroceryListTableViewController: UITableViewController, GroceryItemsTableViewControllerProtocol {
var groceryList = [GroceryItem]()
#IBAction func addButton(sender: UIBarButtonItem) {
let alertController: UIAlertController = UIAlertController(title: "Add Grocery Category", message: "", preferredStyle: .Alert)
//Cancel Button
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//cancel code
}
alertController.addAction(cancelAction)
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in
let textField = alertController.textFields![0]
self.groceryList.append(GroceryItem(name: textField.text!, items: [String]()))
self.tableView.reloadData()
}
alertController.addAction(saveAction)
alertController.addTextFieldWithConfigurationHandler { (textField : UITextField!) -> Void in
textField.placeholder = "Enter an Item"
//alertController.textFields
}
//Present the AlertController
self.presentViewController(alertController, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
//edit button
self.navigationItem.leftBarButtonItem = self.editButtonItem()
groceryList.append(GroceryItem(name: "Breakfast", items: ["Item1", "Item2", "Item3"]))
groceryList.append(GroceryItem(name: "Lunch", items: ["Item1", "Item2", "Item3"]))
groceryList.append(GroceryItem(name: "Dinner", items: ["Item1", "Item2", "Item3"]))
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return groceryList.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("prototype1", forIndexPath: indexPath) as UITableViewCell
cell.textLabel!.text = groceryList [indexPath.row].name
return cell
}
// pass a tableview cell value to navigationBar title in swift//
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destinationVC = segue.destinationViewController as! GroceryItemsTableViewController
let cell = sender as! UITableViewCell
let idx = self.tableView.indexPathForSelectedRow?.row
destinationVC.delegate = self
destinationVC.itemList = groceryList[idx!].items
destinationVC.navigationItem.title = cell.textLabel?.text
}
func didAddGroceryItem(itemName: String) {
let idx = self.tableView.indexPathForSelectedRow?.row
groceryList[idx!].items.append(itemName)
}
func didRemoveGroceryItem(index: Int) {
let idx = self.tableView.indexPathForSelectedRow?.row
groceryList[idx!].items.removeAtIndex(index)
}
}
In the above I have refactored all the code regarding the new model, I put only the places where the code change the rest keep the same.
The thing you need to pass the item associated with the cell selected to the another UIViewController and you can do it very easily in your prepareForSegue. For that we need to get the index for the selected cell and pass the elements to the another UIViewController where we have a new array of [String] created as data source to show the items.
The another important point in the code is that the GroceryListTableViewController now implements a new protocol called GroceryItemsTableViewControllerProtocol. This protocol it's the way to notify to GroceryListTableViewController from the GroceryItemsTableViewController every time a new item is added to the list it's called the delegate pattern.
GroceryItemsTableViewController:
protocol GroceryItemsTableViewControllerProtocol: class {
func didAddGroceryItem(itemName: String)
func didRemoveGroceryItem(index: Int)
}
class GroceryItemsTableViewController: UITableViewController {
weak var delegate: GroceryItemsTableViewControllerProtocol?
var itemList: [String]!
#IBAction func addGroceryItemButtonPressed(sender: UIBarButtonItem) {
///new way///
let alertController: UIAlertController = UIAlertController(title: "Add Grocery Item", message: "", preferredStyle: .Alert)
//Cancel Button
let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .Cancel) { action -> Void in
//cancel code
}
alertController.addAction(cancelAction)
let saveAction: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { [weak self] action -> Void in
guard let s = self else { return }
let textField = alertController.textFields![0]
s.itemList.append(textField.text!)
s.delegate?.didAddGroceryItem(textField.text!)
s.tableView.reloadData()
}
alertController.addAction(saveAction)
alertController.addTextFieldWithConfigurationHandler { (textField : UITextField!) -> Void in
textField.placeholder = "Enter an Item"
//alertController.textFields
}
//Present the AlertController
self.presentViewController(alertController, animated: true, completion: nil)
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemList.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("groceryItem1", forIndexPath: indexPath)
cell.textLabel!.text = itemList[indexPath.row]
return cell
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data source
itemList.removeAtIndex(indexPath.row)
delegate?.didRemoveGroceryItem(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
}
EDIT:
To handle properly the deletion you should create a new delegate method no notify the GroceryListTableViewController that a item has been deleted and then delete it properly and you can see in the updated code above.
I hope this help you.

Resources