title basically says it; surprised I couldn't find anything on stack overview but none helped me or were in objective C
I have a table view with a list of items and an edit button that allows user to delete rows (can also 'swipe to delete'). basically, I want to have a popup alert that says "are you sure you want to delete (rowname)" where row name is the name of the row about to be deleted. from what I have found/tried, I can get the popup BUT it shows up every time you press the edit button or swipe right. I only want the popup to appear when the user presses "delete".
and, obviously, from the popup if they press Cancel it should cancel, if they press delete it should delete
how do you do this in general?
sorry I am kind of a noob
All you have to do is present the alert when the button is pressed and set each action.
Replace your commit editingStyle delegate method with this and replace the data variable with your data array:
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
presentDeletionFailsafe(indexPath: indexPath)
}
}
func presentDeletionFailsafe(indexPath: IndexPath) {
let alert = UIAlertController(title: nil, message: "Are you sure you'd like to delete this cell", preferredStyle: .alert)
// yes action
let yesAction = UIAlertAction(title: "Yes", style: .default) { _ in
// replace data variable with your own data array
self.data.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .fade)
}
alert.addAction(yesAction)
// cancel action
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
EDIT
Example:
private let reuseId = "cellReuseId"
class SlideToDeleteViewController : UIViewController {
lazy var tableView = createTableView()
func createTableView() -> UITableView {
let tableView = UITableView()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: reuseId)
tableView.dataSource = self
tableView.delegate = self
return tableView
}
var data = ["one", "two", "three", "four"]
override func loadView() {
self.view = tableView
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension SlideToDeleteViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: reuseId)
cell?.textLabel?.text = data[indexPath.row]
return cell!
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
presentDeletionFailsafe(indexPath: indexPath)
}
}
func presentDeletionFailsafe(indexPath: IndexPath) {
let alert = UIAlertController(title: nil, message: "Are you sure you'd like to delete this cell", preferredStyle: .alert)
// yes action
let yesAction = UIAlertAction(title: "Yes", style: .default) { _ in
// put code to remove tableView cell here
self.data.remove(at: indexPath.row)
self.tableView.deleteRows(at: [indexPath], with: .fade)
}
alert.addAction(yesAction)
// cancel action
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
}
Related
I implemented to save data in local using user dafaults with table view. when insert data every data display in my tableview. but stop and run again last value is not dispayed. and when swipe and remove not working when app run next time.
import UIKit
let defaults = UserDefaults(suiteName: "com.saving.data")
class HomeWorkViewController: UITableViewController {
var rows = [String]()
call getData() method in viewDidload
override func viewDidLoad() {
super.viewDidLoad()
getData()
// Do any additional setup after loading the view.
self.navigationItem.rightBarButtonItem = self.editButtonItem
}
calling getData() method
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
getData()
}
calling storeData method
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(true)
storeData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func addButton(_ sender: Any) {
addCell()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rows.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "homeWork", for: indexPath)
cell.textLabel?.text = rows[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
rows.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.reloadData()
}else if editingStyle == .insert {
}
}
func addCell(){
let alert = UIAlertController(title: "Add Home Work", message: "Input text", preferredStyle: .alert)
alert.addTextField{(textField) in
textField.placeholder = "text...."
}
alert.addAction(UIAlertAction(title: "Confirm", style: .default, handler: {[weak alert](_) in
let row = alert?.textFields![0]
self.rows.append((row?.text)!)
self.tableView.reloadData()
}))
self.present(alert,animated: true, completion: nil)
storeData()
}
func storeData(){
defaults?.set(rows, forKey: "savedData")
defaults?.synchronize()
}
func getData(){
let data = defaults?.value(forKey: "savedData")
if data != nil {
rows = data as! [String]
}else{}
}
}
You call storeData() at the wrong place. The addAction closure is executed later in time.
func addCell() {
let alert = UIAlertController(title: "Add Home Work", message: "Input text", preferredStyle: .alert)
alert.addTextField{(textField) in
textField.placeholder = "text...."
}
alert.addAction(UIAlertAction(title: "Confirm", style: .default, handler: {[weak alert](_) in
let row = alert?.textFields![0]
let insertionIndex = self.rows.count
self.rows.append(row.text!)
self.tableView.insertRows(at: IndexPath(row: insertionIndex, section: 0), with: .automatic)
self.storeData()
}))
self.present(alert,animated: true, completion: nil)
}
And never call reloadData after calling deleteRows
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
rows.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
self.storeData()
}
}
And use the dedicated API of UserDefaults (don't call synchronize)
func storeData(){
defaults!.set(rows, forKey: "savedData")
}
func getData(){
rows = defaults!.array(forKey: "savedData") as? [String] ?? []
}
I am trying to change font size after the cell is loaded with data. On click of the icon of the tabbar i show an alertcontroller action sheet.
On click of one of the actions i want to change the font size of the labels in the cells.
The code i am using is below:
//Two global variables,retaining size of each labels
var fontSizeWord = 20.0
var fontSizeMeaning = 12.0
func changeFont(){
let optionMenu = UIAlertController(title: nil, message: "Change Font", preferredStyle: .actionSheet)
optionMenu.view.tintColor = UIColor(red: 179/256, green: 180/256, blue: 255/256, alpha: 1.0)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
let smallfont = UIAlertAction(title: "Small", style: .default) { (UIAlertAction) in
self.fontSizeMeaning = 12
self.fontSizeWord = 20
self.reloadData()
}
let mediumfont = UIAlertAction(title: "Medium", style: .default) { (UIAlertAction) in
self.fontSizeMeaning = 15
self.fontSizeWord = 23
self.reloadData()
}
let largefont = UIAlertAction(title: "Large", style: .default) { (UIAlertAction) in
self.fontSizeMeaning = 18
self.fontSizeWord = 26
self.reloadData()
}
let extraLarge = UIAlertAction(title: "Extra Large", style: .default) { (UIAlertAction) in
self.fontSizeMeaning = 21
self.fontSizeWord = 29
self.reloadData()
}
optionMenu.addAction(cancelAction)
optionMenu.addAction(smallfont)
optionMenu.addAction(mediumfont)
optionMenu.addAction(largefont)
optionMenu.addAction(extraLarge)
self.present(optionMenu, animated: true, completion: nil)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:SearchTableViewCell = self.searchTableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! SearchTableViewCell
cell.wordLbl.font = cell.wordLbl.font.withSize(CGFloat(fontSizeWord))
cell.meaningLbl.font = cell.meaningLbl.font.withSize(CGFloat(fontSizeMeaning))
return cell
}
My problem is the font of visible cells only changes once i scroll a little bit. How can i fix it to change as the action of uiactionsheet is triggered.
Try this Snippet
self.searchTableView.reloadRows(at: self.searchTableView.indexPathsForVisibleRows!, with: .automatic)
Sounds like you have to call reloadData from the main thread:
DispatchQueue.main.async {
self.tableView.reloadData()
}
If this does not work your issue must be somewhere else. Compare your project with my solution:
class ViewController: UITableViewController {
enum FontSize: String, CaseIterable {
case small = "Small"
case medium = "Medium"
case large = "Large"
var pointSize: CGFloat {
switch self {
case .small: return 10
case .medium: return 20
case .large: return 30
}
}
}
var currentFontSize: FontSize = .medium {
didSet {
if currentFontSize != oldValue, let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows {
tableView.reloadRows(at: indexPathsForVisibleRows, with: .automatic)
}
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.font = UIFont.systemFont(ofSize: currentFontSize.pointSize)
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let alert = UIAlertController(title: "Font Size", message: "Choose your preferred font size.", preferredStyle: .actionSheet)
let handler: (UIAlertAction) -> Void = { alert in
guard let title = alert.title, let fontSize = FontSize(rawValue: title) else { return }
self.currentFontSize = fontSize
}
for fontSize in FontSize.allCases {
alert.addAction(UIAlertAction(title: fontSize.rawValue, style: .default, handler: handler))
}
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true)
}
}
To get the indexPath for the visible rows you can use this:
let indexPathsForVisibleRows = yourTableView.indexPathsForVisibleRows
Then you can reload those specific cells using:
yourTableView.reloadRowsAtIndexPaths([indexPathsForVisibleRows], withRowAnimation: UITableViewRowAnimation.automatic)
You have to create some methods,this is how I did.
func configureVisibleCells(for tableView: UITableView?, animated: Bool) {
self.tableView(tableView, configureRowsAtIndexPaths: tableView?.indexPathsForVisibleRows, animated: animated)
}
func tableView(_ tableView: UITableView?, configureRowsAtIndexPaths indexPaths: [Any]?, animated: Bool) {
for indexPath in indexPaths as? [IndexPath] ?? [] {
let cell: UITableViewCell? = tableView?.cellForRow(at: indexPath)
if cell != nil {
self.tableView(tableView, configureCell: cell, forRowAt: indexPath, animated: animated)
}
}
}
func tableView(_ tableView: UITableView?, configureCell cell: UITableViewCell?, forRowAt indexPath: IndexPath?, animated: Bool) {
// Cell configuration
}
Configure the Cell in the tableView(_ tableView: UITableView?, configureCell cell: UITableViewCell?, forRowAt indexPath: IndexPath?, animated: Bool) method and call this method in your tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell .
and when you need to reload visible cells call the method tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
Hope this may help you.
I know "if indexPath.section == x or if indexPath.row == x { " to activate the button in particular row.
If my cell is at section 5 row 0, what is the proper way to write to activate the action in that cell?
Edit (more info more code)
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let name = twoDimensionalArray[indexPath.section][indexPath.row]
cell.textLabel?.text = name
cell.textLabel?.text = "\(name) Section:\(indexPath.section) Row:\(indexPath.row)"
cell.accessoryType = .disclosureIndicator
return cell
}
cause I used disclosureIndicator I could see section # & row # of all my TableCells.
My section 5 has only one row which is "Sign Out" so I had:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
if indexPath.section == 5 {
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
alertController.addAction(UIAlertAction(title: "Log Out", style: .destructive, handler: { (_) in
do {
//... ...//
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alertController, animated: true, completion: nil)
}
}
However, my other sections have multiple rows. Thus, what should I write if I want to perform an action in Section 4 Row 2?
I am a beginner at Swift 3. I have a Table View, and the user can delete a table view cell. Now I want the user to be able to change the content of the cell. I have an array that contains four names ["Stremmel", "Emma", "Sam", "Daisy"] and I want the user to be able to say edit Stremmel to George.
I searched for documentation or a similar question that could help me to figure a way to do so, but I got more confused. Can someone please provide me with some help!! Thank you. Here is my table view:
import UIKit
var list = ["Stremmel" , "Emma" , "Sam" , "Daisy"]
class ViewController: UITableViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return list.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.text = list[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete
{
list.remove(at: indexPath.row)
tableView.reloadData()
}
}
If you want to show Edit button also with Delete button then you need to implement editActionsForRowAt method with canEditRowAt method instead of commit editingStyle.
After that with editActionsForRowAt show AlertController with textField and update its value and reload the row. So remove or comment the commit editingStyle method from your code and add below two methods.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let editAction = UITableViewRowAction(style: .default, title: "Edit", handler: { (action, indexPath) in
let alert = UIAlertController(title: "", message: "Edit list item", preferredStyle: .alert)
alert.addTextField(configurationHandler: { (textField) in
textField.text = self.list[indexPath.row]
})
alert.addAction(UIAlertAction(title: "Update", style: .default, handler: { (updateAction) in
self.list[indexPath.row] = alert.textFields!.first!.text!
self.tableView.reloadRows(at: [indexPath], with: .fade)
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(alert, animated: false)
})
let deleteAction = UITableViewRowAction(style: .default, title: "Delete", handler: { (action, indexPath) in
self.list.remove(at: indexPath.row)
tableView.reloadData()
})
return [deleteAction, editAction]
}
Normally when creating a UITableViewController() class, you should have some template code that provides a edit-button and a delete-function (should be included in the edit button)! Just uncomment it, it should be accessible then!
or you could just call self.editButtonItem() in the viewDidLoad()-function.
I'm sorry for my bad English, I hope that answered you question!
I'm working on an app, and would like the swipe was equal to swipe of Trash of Mail iOS:
My ViewController has a TableView:
And my Swift code:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView: UITableView!
var arr = [NSMutableDictionary]()
var count:Int = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arr.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! TableViewCell
let row = self.arr[indexPath.row]
cell.label.text = row["name"] as? String
return cell
}
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
let alert = UIAlertController(title: "Remove?", message: "Touch in Remove", preferredStyle: .Alert)
let remove = UIAlertAction(title: "Remove", style: UIAlertActionStyle.Destructive) { (UIAlertAction) -> Void in
self.arr.removeAtIndex(indexPath.row)
self.tableView.reloadData()
}
let cancel = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
alert.addAction(cancel)
alert.addAction(remove)
self.presentViewController(alert, animated: true,completion: nil)
}
}
#IBAction func addAction(sender: AnyObject) {
arr.append(["name":"row \(count)","age":"23"])
++count
self.tableView.reloadData()
}
}
When I preview the app, I see the swipe that way:
My question is: What do I need to make my app swipe equal to swipe Mail Trash?
You should use the delegate method
'titleForDeleteConfirmationButtonForRowAtIndexPath'
and return the string value (in your case "Trash") that you wish to display