How to Perform CRUD operation in TableView Swift - ios

I have written code for performing Delete,Insert,Update . when I execute my code I'm getting an Error like "Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid index path for use with UITableView. Index paths passed to table view must contain exactly two indices specifying the section and row. Please use the category on NSIndexPath in NSIndexPath+UIKitAdditions.h if possible."
I'm adding the code here
import UIKit
class ViewController: UIViewController {
#IBOutlet var tableView: UITableView!
#IBOutlet var Insert: UIButton!
#IBOutlet var txtfield: UITextField!
var index = IndexPath()
var models = ["1.Audi","2.Hyundai","3.Bentley","4.Chevrolet","5.Dodge","6.Electric","7.Ford","8.Genesis","9.Honda","10.Ferrari","11.Nissan","12.Porche","13.Range Rover","14.Lamborgini","15.McLaren","16.koneisegg","17.Volvo","18.Mazda","19.Bmw"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
#IBAction func textFieldEdit(_ sender: UITextField) {
//self.tableView.reloadData()
if let cell = tableView.cellForRow(at: index) as? UITableViewCell{
cell.textLabel?.text = self.txtfield.text
}
}
#IBAction func insertBtn(_ sender: UIButton) {
if let txt = txtfield.text, !txt.isEmpty{
//self.models.append(txt)
self.models.insert(txt,at: 0)
tableView.beginUpdates()
tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .fade)
tableView.endUpdates()
print (models.count)
}
}
}
extension ViewController: UITableViewDataSource,UITableViewDelegate{
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return models.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = models[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete{
tableView.beginUpdates()
models.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
index = indexPath
self.txtfield.text = models[indexPath.row]
}
}
ScreenShot of My storyBoard

You didn't explain when you're getting the error.
Running your code as-is, no apparent errors.
However, my guess is that you're hitting an issue when you haven't yet selected a row, or when you've done something to change the selection while editing in the text field.
Try changing your textFieldEdit func to this:
#IBAction func textFieldEdit(_ sender: UITextField) {
// make sure
// a row is selected
// and
// we can get a reference to that cell
guard let pth = tableView.indexPathForSelectedRow,
let cell = tableView.cellForRow(at: pth)
else {
return
}
// properly unwrap optional .text property
let str = sender.text ?? ""
// update the data
self.models[pth.row] = str
// update the cell
cell.textLabel?.text = str
}

You've instantiated an IndexPath without properties
var index = IndexPath()
If you refer to it before setting a valid property you'll get that error.
Possible Solution
var index: IndexPath?
Use an optional for your index rather than setting an invalid default property. It'll be nil until you assign an instance.
You'll then have to use index?. or if let index = index when using the property, but it'll be safe.

Related

pass text from selected tableView cell.row to fill textbox xcode

I have a tableView that allows users to make multiple selections from an array of data,
When the user clicks done, I would like the selected text to be then transferred over to another tableViews textView
Is there a way to transfer over the selected text and have the text separated by a , ?
I am coding programmatically.
var checked = [Int]()
var items = [String]()
var selectedItems = [String]()
#objc func done() {
let hud = JGProgressHUD(style: .dark)
hud.textLabel.text = "Saving!"
hud.show(in: view)
dismiss(animated: true, completion: nil)
hud.dismiss()
let aCell = aboutCell(style: .default, reuseIdentifier: nil)
aCell.textField3.text = selectedItems.joined(separator: ",")
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath)?.accessoryType == UITableViewCell.AccessoryType.checkmark {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.none
} else {
tableView.cellForRow(at: indexPath)?.accessoryType = UITableViewCell.AccessoryType.checkmark
if selectedItems.contains(items[indexPath.row]) {
selectedItems.remove(at: selectedItems.firstIndex(of: items[indexPath.row])!)
} else {
selectedItems.append(items[indexPath.row])
}
checked.append(indexPath.row)
}
}
According to my understanding to the question, these are my thoughts:
1. First setup necessary variables
var items = [String]() // data to display in tableview
var selectedItems = [String]() // here all the selected datas are stored
2. Store the selected items data from the didSelectRowAt delegate method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedItems.contains(items[indexPath.row]) { //check if the selected already contains the items and if contains remove it
selectedItems.remove(at: selectedItems.firstIndex(of: items[indexPath.row])!)
} else { // append the required items
selectedItems.append(items[indexPath.row])
}
// ..... other codes here
}
3. on done button
let requiredText = selectedItems.joined(separator: ",")
// pass this data through delegate method
There is no need to create an array for the selected items. You can simply call tableview method selectRow(at:animated:scrollPosition:) when selecting a row and when you need to get the selected rows just call tableview instance property indexPathsForSelectedRows. Then you just need to join the selected rows with a comma and use the resulting string in your textview or textfield. Don't forget to implement didDeselectRowAt item method as well to deselectRow.
import UIKit
class TableViewController: UITableViewController {
var items: [String] = ["1st", "2nd", "3rd", "4th", "5th"]
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelection = true
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .none
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
if let cell = tableView.cellForRow(at: indexPath) {
cell.accessoryType = .checkmark
}
}
func done() {
if let indexPaths = tableView.indexPathsForSelectedRows {
// note that this will preserve the order that the rows where selected. Just sort the indexPaths if you need it sorted.
let string = indexPaths.map { items[$0.row] }.joined(separator: ",")
print(string)
// your code
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
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: "TableViewCellID", for: indexPath) as! TableViewCell
cell.textLabel?.text = items[indexPath.row]
cell.accessoryType = cell.isSelected ? .checkmark : .none
return cell
}
}

Stop tableView.reloadData() throwing an error?

I am following a tutorial for a simple list app, where you add items to a list via UITextField. However, it crashes at tableView.reloadData() and I don't know why. Making tableView optional causes it to not crash, but it also causes the app to not add the item to the list. Here is the class code:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
struct todo {
var text: String
var isDone: Bool
}
var todos = [todo]()
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
todos.append(todo(text: "test", isDone: false))
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return todos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "todo-cell", for: indexPath)
let todo = todos[indexPath.row]
cell.textLabel?.text = todo.text
if todo.isDone {
cell.accessoryType = .checkmark
} else {
cell.accessoryType = .none
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
var todo = todos[indexPath.row]
todo.isDone = !todo.isDone
todos[indexPath.row] = todo
tableView.reloadRows(at: [indexPath], with: UITableView.RowAnimation.automatic)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
todos.append(todo(text: textField.text!, isDone: false))
tableView.reloadData() // Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value... crashes here!!
textField.text = ""
textField.resignFirstResponder()
return true
}
}
My first guess is that you have not linked the tableview in your storyboard to your view controller.
Maybe you should check this first. Put a break point in viewDidLoad to see if your tableview has been set. If its nil, then there is your problem.
tableView object is nil.
#IBOutlet var tableView: UITableView! // is not link with viewController

I want to add a new row to UITableView with the click of a Button

when I click the add button it return the error 'attempt to insert row 0 into section 0, but there are only 0 rows in section 0 after the update'
import UIKit
class PlacesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var addPointTableArray = [""]
#IBOutlet weak var addPointableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
addPointableview.delegate = self
}
#objc #IBAction func addPointButton(_ sender: Any) {
addPointableview.beginUpdates()
addPointableview.insertRows(at: [IndexPath(row: addPointTableArray.count - 1, section: 0)], with: .automatic)
addPointableview.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = addPointableview.dequeueReusableCell(withIdentifier: "addCell", for: indexPath as IndexPath) as! AddPointCellCell
cell.addPointTextfiedl.tag = indexPath.row
cell.addPointTextfiedl.addTarget(self, action: #selector(addPointButton(_:)), for: UIControl.Event.touchUpInside)
addPointableview.beginUpdates()
addPointableview.insertRows(at: [IndexPath(row: addPointTableArray.count-1, section: 0)], with: .automatic)
addPointableview.endUpdates()
return cell
}
}
I have checked my table and add button I don't know where the error is coming from
You should not be inserting rows in your cellForRow(at:) method.
You also need to update your data model so that there is data for these new indexPaths. The table view will ask for data for the cells at the new indexPaths in order to refresh, and if the data model is not updated to contain data for the new cells you may crash.
A table view displays the contents of the data source array. If you are using insertRows you have to append an item to the data source before updating the UI.
Never call insert/deleteRows in cellForRowAt. And beginUpdates/endUpdates is pointless for a single insert operation.
class PlacesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var addPointTableArray = ["0"]
#IBOutlet weak var addPointableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
addPointableview.delegate = self
addPointableview.datasource = self
}
#objc #IBAction func addPointButton(_ sender: Any) {
let insertionIndex = addPointTableArray.count
let indexPath = IndexPath(row: insertionIndex, section: 0)
addPointTableArray.append(String(insertionIndex))
addPointableview.insertRows(at: [indexPath], with: .automatic)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return addPointTableArray.count // never hard-code the return value, return the number of item in the data source
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = addPointableview.dequeueReusableCell(withIdentifier: "addCell", for: indexPath) as! AddPointCellCell
cell.addPointTextfiedl.tag = Int(addPointTableArray[indexPath.row])
// assign values of the data source array to UI elements
cell.addPointTextfiedl.addTarget(self, action: #selector(addPointButton), for: UIControl.Event.touchUpInside)
return cell
}
}
This is not the proper way to add rows in a table view.
First of all you don't have to add rows in tableView(_:cellForRowAt:).
The table view data source needs to be changed in order to achieve what you want.
Since you wrote this
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
The table view always expect to have two rows.
Solution
So, first of all you have to return the number of the array because it tells the table view how many rows will be shown.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return addPointTableArray.count
}
Then, if you want to add a new row, you need to update the array before calling insertRows(at:with:):
#IBAction func addPointButton(_ sender: Any) {
addPointTableArray.append("New element")
addPointableview.insertRows(at: [IndexPath(row: addPointTableArray.count - 1, section: 0)], with: .automatic)
}
You don't need to call beginUpdates() and endUpdates() because you're only inserting, those methods are needed for multiple manipulation.
Finally, as I said before, change the tableView(_:cellForRowAt:) to this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = addPointableview.dequeueReusableCell(withIdentifier: "addCell", for: indexPath) as! AddPointCellCell
cell.addPointTextfiedl.tag = indexPath.row
cell.addPointTextfiedl.addTarget(self, action: #selector(addPointButton(_:)), for: .touchUpInside)
return cell
}
So at the end your class would look like
import UIKit
class PlacesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var addPointTableArray = [""]
#IBOutlet weak var addPointableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
addPointableview.dataSource = self
addPointableview.delegate = self
}
#IBAction func addPointButton(_ sender: Any) {
addPointTableArray.append("New element")
addPointableview.insertRows(at: [IndexPath(row: addPointTableArray.count - 1, section: 0)], with: .automatic)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return addPointTableArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = addPointableview.dequeueReusableCell(withIdentifier: "addCell", for: indexPath) as! AddPointCellCell
cell.addPointTextfiedl.tag = indexPath.row
cell.addPointTextfiedl.addTarget(self, action: #selector(addPointButton(_:)), for: UIControl.Event.touchUpInside)
return cell
}
}
I suggest you to re-study how to implement a basic table view before dealing with insertion/removing.

Can't retrieve data from TableViewCell because didSelectRowAt isn't being called

I have a custom UITableView that contains data in each cell that I want to retrieve and save it using UserDefaults.
I would like for didSelectRowAt to be called when the user taps on a cell so that I can retrieve the data within that particular cell.
The problem is that didSelectRowAt is not being called and I have tried the following methods:
Ensuring there are no gesture recognizers 'eating' the tap on the cell (I never added a gesture recognizer).
Setting the 'Selection' portion of Identity Inspector to 'None' and 'Single Selection'.
Here is a screenshot of how the ViewController with the TableView is set up:
Here is my code:
class blueSide: UIViewController, UITableViewDelegate, UITableViewDataSource {
var items : [SosItem] = []
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
ref.observe(.value, with: {
snapshot in
var newItems : [SosItem] = []
for child in snapshot.children {
if let snapshot = child as? DataSnapshot,
let sosItem = SosItem(snapshot: snapshot) {
newItems.append(sosItem)
}
}
self.items = newItems
print(self.items)
self.tableView.reloadData()
})
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let removedItem = items.remove(at: indexPath.row)
let itemsRef = ref.child(removedItem.key.lowercased())
itemsRef.removeValue()
tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let sosItem = items[indexPath.row]
print(sosItem)
UserDefaults.standard.set(sosItem.clothingDescription, forKey: "clothingDescription")
UserDefaults.standard.set(sosItem.placeName, forKey: "placeName")
UserDefaults.standard.set(sosItem.longitude, forKey: "longitude")
print("Longitude saved!")
UserDefaults.standard.set(sosItem.latitude, forKey: "latitude")
print("Latitude saved!")
print(UserDefaults.standard.value(forKey: "latitude"))
// tableView.deleteRows(at: [indexPath], with: .fade)
// tableView.reloadData()
self.performSegue(withIdentifier: "uberSegue", sender: self)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! CustomTableViewCell
//get cell data from Firebase
let sosItem = items[indexPath.row]
cell.descriptionLabel.text = sosItem.clothingDescription
cell.latitudeLabel.text = String(sosItem.latitude)
cell.longitudeLabel.text = String(sosItem.longitude)
cell.locationNameLabel.text = sosItem.placeName
cell.destinationLabel.text = sosItem.dropoffLocation
return cell
}
The didSelectedRowAt method isn't called when the tableView is in editing mode, isEditing property is set to true, or you invokes canEditRowAt
Try to selecting a row when the editing mode ends, as test!

TableView CheckMark and Uncheck With Scroll Up Still Checked Cell Value In Ios Swift 4

TableView CheckMark Cell Value Removed After Scrolling Up It will Fix
TableView in You have face a problem many times to Checkmark after scroll Up then Scroll Down To show a Your Checkmark cell is will Removed Because cell is dequeueReusableCell So This Problem Fix , you Have just put Your code and Solved Your Problem.
Any More Help So Send Massage.
Thank you So much. :)
class ViewController: UIViewController , UITableViewDataSource , UITableViewDelegate{
var temp = [Int]()
var numarr = [Int]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numarr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "id")
cell = UITableViewCell.init(style: .default, reuseIdentifier: "id")
cell?.textLabel?.text = String(numarr[indexPath.row])
if temp.contains(numarr[indexPath.row] as Int)
{
cell?.accessoryType = .checkmark
}
else
{
cell?.accessoryType = .none
}
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
if temp.contains(numarr[indexPath.row] as Int)
{
cell?.accessoryType = .none
temp.remove(at: temp.index(of: numarr[indexPath.row])!)
}
else
{
cell?.accessoryType = .checkmark
temp.append(self.numarr[indexPath.row] as Int)
}
}
override func viewDidLoad() {
super.viewDidLoad()
for i in 1...100
{
numarr.append(i)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
I think if someone were to run your code it would not show any error. But with real data it probably will. The reason is the way you store your checkmarks. You store the data of a row into the temp array when you should be storing the actualy indexPath of the array so that only that row gets the checkmark. In your case, if a row has 1 inside it's label and you click on it, that cell will be highlighted. Now if you start scrolling and another cell contains 1 then that row will also be highlighted.
I have modified your example for the case of a single section. If there is more than one section, you need to store the indexPath instead of indexPath.row.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "id")
cell = UITableViewCell.init(style: .default, reuseIdentifier: "id")
cell?.textLabel?.text = String(numarr[indexPath.row])
if temp.contains(indexPath.row) {
cell?.accessoryType = .checkmark
} else {
cell?.accessoryType = .none
}
return cell!
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
if temp.contains(indexPath.row) {
cell?.accessoryType = .none
temp.remove(at: indexPath.row)
} else {
cell?.accessoryType = .checkmark
temp.append(indexPath.row)
}
}
You are strongly discouraged from using a second array to keep the selected state.
This is Swift, an object oriented language. Use a custom struct for both num and the selected state.
In didSelectRowAt and didDeselectRowAt change the value of isSelected and reload the row.
And use always the dequeueReusableCell API which returns a non-optional cell.
struct Item {
let num : Int
var isSelected : Bool
}
var numarr = [Item]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return numarr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "id", for: indexPath)
let item = numarr[indexPath.row]
cell.textLabel?.text = String(item)
cell.accessoryType = item.isSelected ? .checkmark : .none
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
updateSelection(at: indexPath, value : true)
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
updateSelection(at: indexPath, value : false)
}
func updateSelection(at indexPath: IndexPath, value : Bool) {
let item = numarr[indexPath.row]
item.isSelected = value
tableView.reloadRows(at: [indexPath], with: .none)
}
override func viewDidLoad() {
super.viewDidLoad()
(0...100).map{Item(num: $0, isSelected: false)}
}

Resources