How do you edit or delete text in a tableView? - xcode

I am trying have the user to modify/delete default text in a tableview, I have tried the following code witch works but as soon as the user goes to the other view controller and back (or leaves the app and comes back), the text goes back to being the default text.
How could I have it work?
I have posted the entire code for the tableView so you can have an overview of what I could have done wrong.
thank you !
var places = [Dictionary<String,String>()]
var activePlace = -1
class TableViewController: UITableViewController {
func companyNameUpdatedAlert(title: String, error: String, indexPath: Int) {
let alert = UIAlertController(title: title, message: error, preferredStyle: UIAlertControllerStyle.Alert)
alert.addTextFieldWithConfigurationHandler { (textField) -> Void in
textField.placeholder = "Enter new text"
}
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in
let lat = places[indexPath]["lat"]!
let lon = places[indexPath]["lon"]!
places.removeAtIndex(indexPath)
places.insert(["name" : alert.textFields![0].text!, "lat" : lat, "lon" : lon], atIndex: indexPath)
self.tableView.reloadData()
}))
self.presentViewController(alert, animated: true, completion: nil)
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let changeText = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Change text" , handler: { (action:UITableViewRowAction, indexPath:NSIndexPath) -> Void in
self.companyNameUpdatedAlert("Update text", error: "enter text below", indexPath: indexPath.row)
})
let deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete" , handler: { (action:UITableViewRowAction, indexPath:NSIndexPath) -> Void in
places.removeAtIndex(indexPath.row)
tableView.reloadData()
})
return [changeText, deleteAction]
}
override func viewDidLoad() {
//save start
if NSUserDefaults.standardUserDefaults().objectForKey("places") != nil {
places = NSUserDefaults.standardUserDefaults().objectForKey("places") as! [Dictionary]
//save stop
super.viewDidLoad()
if places.count == 1 {
places.removeAtIndex(0)
places.append(["name":"Long press on map to add location","lat":"90","lon":"90"])
}
if NSUserDefaults.standardUserDefaults().objectForKey("places") != nil {
places = NSUserDefaults.standardUserDefaults().objectForKey("places") as! [Dictionary]
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return places.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = places[indexPath.row]["name"]
return cell
}
override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
activePlace = indexPath.row
return indexPath
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "newPlace" {
activePlace = -1
}
}
override func viewWillAppear(animated: Bool) {
tableView.reloadData()
}
}

When you edit the table you should persist your data in the NSUserDefaults. Otherwise you are just changing the class property which is reloaded from the original NSUserDefaults each time the class loads.
Try this inside your UIAlertAction closure:
NSUserDefaults.standardUserDefaults().setObject(places, forKey: "places")
NSUserDefaults.standardUserDefaults().synchronize()

Related

tableView.indexPathForSelectedRow returns nil - Xcode 13.4.1 UIKit

please help me to understand why the tableView.indexPathForSelectedRow method returns nil.
I want to make a transfer from Table View Controller to View Controller. I have a segue by a StoryBoard and leadingSwipeActions.
import UIKit
class TableViewController: UITableViewController {
let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return months.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = months[indexPath.row]
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "editItem" {
let path = tableView.indexPathForSelectedRow
print("\(path)") ##// Prints nil.##
}
}
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let editAction = UIContextualAction(style: .normal, title: "Edit Item") { [self] (action, view, completion) in
performSegue(withIdentifier: "editItem", sender: self)
}
editAction.backgroundColor = .systemOrange
return UISwipeActionsConfiguration(actions: [editAction])
}
}
Leading swipe - and subsequent tap of the action button - does not select the row.
Since the sender parameter of the "prepare for segue" method is defined as Any?, one approach would be to pass the indexPath when you call the segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "editItem" {
// make sure an Index Path was passed as the sender
if let path = sender as? IndexPath {
print("Index Path: \(path)")
}
}
}
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
// note: use [weak self] to avoid possible retain cycle
let editAction = UIContextualAction(style: .normal, title: "Edit Item") { [weak self] (action, view, completion) in
guard let self = self else { return }
// pass the path as the sender
self.performSegue(withIdentifier: "editItem", sender: indexPath)
}
editAction.backgroundColor = .systemOrange
return UISwipeActionsConfiguration(actions: [editAction])
}
Edit - in response to comment
The sender: Any? parameter means that sender can be any object. That allows you to differentiate what action or piece of code initiated the segue.
Examples:
self.performSegue(withIdentifier: "editItem", sender: indexPath)
self.performSegue(withIdentifier: "editItem", sender: 1)
self.performSegue(withIdentifier: "editItem", sender: 2)
self.performSegue(withIdentifier: "editItem", sender: "Hello")
self.performSegue(withIdentifier: "editItem", sender: self)
self.performSegue(withIdentifier: "editItem", sender: myButton)
Then, in prepare(...), you can evaluate the sender to decide what to do next.
Quite often, when using table views to "push" to another controller that relates to the tapped cell (a "details" controller, for example), the developer will connect a segue from the cell prototype to Details view controller. Then (if you gave that segue an identifier of "fromCell"), you can do something like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "fromCell" {
if let cell = sender as? UITableViewCell {
if let path = tableView.indexPath(for: cell) {
print("Index Path: \(path)")
// here we might get data for that row/section,
// and pass it to the next controller
}
}
}
}
In your case, we send the indexPath of the cell we just acted on as the sender parameter.

Swift - search table view with multiple selection

I have followed a tutorial of Vea software (https://www.veasoftware.com/tutorials/2015/6/27/uisearchcontroller-in-swift-xcode-7-ios-9-tutorial) to create a table view with search bar on xcode 7, ios 9. Now I need to select multiple rows from the table view by adding a checkmark on each row when selected, the problem is that when I use the search bar the checkmarks don't match the rows anymore..
Here's my code:
class SportSearchTableViewController: UITableViewController, UISearchResultsUpdating {
let appleProducts = ["Mac","iPhone","Apple Watch","iPad"]
var filteredAppleProducts = [String]()
var resultSearchController = UISearchController()
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath,animated: true)
let selectedRow = tableView.cellForRowAtIndexPath(indexPath)!
if selectedRow.accessoryType == UITableViewCellAccessoryType.None {
selectedRow.accessoryType = UITableViewCellAccessoryType.Checkmark
selectedRow.tintColor = UIColor.blueColor()
} else {
selectedRow.accessoryType = UITableViewCellAccessoryType.None
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = UISearchController(searchResultsController: nil)
self.resultSearchController.searchResultsUpdater = self
self.resultSearchController.dimsBackgroundDuringPresentation = false
self.resultSearchController.searchBar.sizeToFit()
self.tableView.tableHeaderView = self.resultSearchController.searchBar
self.tableView.reloadData()
}
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 {
if (self.resultSearchController.active)
{
return self.filteredAppleProducts.count
}
else
{
return self.appleProducts.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell?
if (self.resultSearchController.active)
{
cell!.textLabel?.text = self.filteredAppleProducts[indexPath.row]
return cell!
}
else
{
cell!.textLabel?.text = self.appleProducts[indexPath.row]
return cell!
}
}
func updateSearchResultsForSearchController(searchController: UISearchController)
{
self.filteredAppleProducts.removeAll(keepCapacity: false)
let searchPredicate = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
let array = (self.sportsList as NSArray).filteredArrayUsingPredicate(searchPredicate)
self.filteredAppleProducts = array as! [String]
self.tableView.reloadData()
}
Does anyone know how to fix that?

Not able to show Delete button

I am trying to implement an alarm clock app just like apple's Clock app.On the click of Edit button on the left hand side I want to make table enter into Editing mode with red circles on left side of every cell(custom UITableViewCell) And on the click of that red circle want to show "Delete" button/action on the right side.
I have been trying a lot and went through many sites but still could not figure out. Can someone please see what mistake I am making?
I have referred below and many others links:
How to enable swipe to delete cell in a TableView?
UITableViewCell, show delete button on swipe
class SavedAlarmListViewController: UIViewController,UITableViewDataSource,UITableViewDelegate{
#IBOutlet weak var tableView: UITableView!
var alarms = [AlarmDataObject]()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
self.navigationItem.leftBarButtonItem = self.editButtonItem()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
refreshList()
}
func refreshList() {
alarms = AlarmsList.sharedInstance.allSavedAlarms()
tableView.reloadData()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return alarms.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "cell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! AlarmTableViewCell
// custom code to set data ....
return cell
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true // all cells are editable
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let alarm = alarms.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
AlarmsList.sharedInstance.removeAnAlarm(alarm)
}
}
}
class AlarmTableViewCell:UITableViewCell {
// IBOutlets
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
You should implement
tableView(_:editingStyleForRowAtIndexPath:) and return .Delete

Load data to UITableView

I have data saved like this:
var mineSpillere = ["erik", "tom", "phil"]
How can i add that data to a UITableView by pressing a UIButton like this?:
#IBAction func backButtonAction(sender: AnyObject) {
// Press this button here to add the "mineSpillere" data to myTableView
}
Set the data source of the table view inside the IBAction. so like this:
#IBAction func backButtonAction(sender: AnyObject) {
myTableView.dataSource = self
// Loading from NSUserDefaults:
// Please name it something better than array I couldn't come up with any names.
if let array = NSUserDefaults.standardUserDefaults().arrayForKey("key") as? [String]
{
// The key exists in user defaults
}
else
{
// The key doesn't exist in the user defaults, do some error handling here.
}
}
And then implement the data source:
extension MyVC : UITableViewDataSource
{
func numberOfSectionsInTableView(tableView: UITableView) -> Int
{
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return mineSpillere.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = UITableViewCell(style: .Default, reuseIdentifier: "cell")
cell.textLabel!.text = mineSpillere[indexPath.row]
return cell
}
}

Cannot invoke 'getDataInBackgroundWithBlock' with an argument list of type '((NSData!, NSError!) -> Void)'

I'm using swift 6.3 and having 2 similar errors
Cannot invoke 'findObjectsInBackgroundWithBlock' with an argument list of type '(([AnyObject]!, NSError!) -> Void)'
Cannot invoke 'getDataInBackgroundWithBlock' with an argument list of type '((NSData!, NSError!) -> Void)'
Any ideas please?
import Parse
import UIKit
class UserVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var resultTable: UITableView!
var resultNameArray = [String]()
var resultUserNameArray = [String]()
var resultUserImageFiles = [PFFile]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidAppear(animated: Bool) {
resultNameArray.removeAll(keepCapacity: false)
resultUserNameArray.removeAll(keepCapacity: false)
resultUserImageFiles.removeAll(keepCapacity: false)
var query = PFUser.query()
query!.whereKey("username", notEqualTo: PFUser.currentUser()!.username!)
//// here is the error////
query.findObjectsInBackgroundWithBlock {(objects:[AnyObject]!, error:NSError!) -> Void in
if error == nil {
for object in objects {
self.resultNameArray.append(object.objectForKey("profileName") as String)
self.resultUserImageFiles.append(object.objectForKey("photo") as PFFile)
self.resultUserNameArray.append(object.objectForKey("username") as String)
self.resultsTable.reloadData()
}
}
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return resultNameArray.count
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 64
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:User_Cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! User_Cell
cell.profileLbl.text = self.resultNameArray[indexPath.row]
cell.userLbl.text = self.resultUserNameArray[indexPath.row]
//// here is the error////
self.resultUserImageFiles[indexPath.row].getDataInBackgroundWithBlock {
(imageData:NSData!, error:NSError!) -> Void in
if error == nil {
let image = UIImage(data: imageData)
cell.imgView.image = image
}
}
return cell
}
}
The first error is due to block parameters are wrong. objects and error both should be optional.
Like below:
query?.findObjectsInBackgroundWithBlock({ (objects: [AnyObject]?, error: NSError?) -> Void in
Second is same reason. imageData and error also both optional.
Like below:
self.resultUserImageFiles[indexPath.row].getDataInBackgroundWithBlock { (imageData: NSData?, error: NSError?) -> Void in
All fixed code:
class UserVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var resultTable: UITableView!
var resultNameArray = [String]()
var resultUserNameArray = [String]()
var resultUserImageFiles = [PFFile]()
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func viewDidAppear(animated: Bool) {
resultNameArray.removeAll(keepCapacity: false)
resultUserNameArray.removeAll(keepCapacity: false)
resultUserImageFiles.removeAll(keepCapacity: false)
var query = PFUser.query()
query!.whereKey("username", notEqualTo: PFUser.currentUser()!.username!)
//// here is the error////
query?.findObjectsInBackgroundWithBlock({ (objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = objects {
for object in objects {
self.resultNameArray.append(object.objectForKey("profileName") as! String)
self.resultUserImageFiles.append(object.objectForKey("photo") as! PFFile)
self.resultUserNameArray.append(object.objectForKey("username") as! String)
self.resultsTable.reloadData()
}
}
}
})
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return resultNameArray.count
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 64
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell:User_Cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! User_Cell
cell.profileLbl.text = self.resultNameArray[indexPath.row]
cell.userLbl.text = self.resultUserNameArray[indexPath.row]
//// here is the error////
self.resultUserImageFiles[indexPath.row].getDataInBackgroundWithBlock { (imageData: NSData?, error:NSError?) -> Void in
if error == nil {
let image = UIImage(data: imageData!)
cell.imgView.image = image
}
}
return cell
}
}
In swift 1.2 (Xcode update to 6.3) optionals were changed, so now, the NSError is optional.
Which makes your function the following:
query?.findObjectsInBackgroundWithBlock({ (objects: [AnyObject]?, error: NSError?) -> Void in
// Do whatever you want with these objects
})
it works great now and other error shows up here
self.resultsTable.reloadData()
the error UsersVC does not have a member named resultsTable.

Resources