How to delete a cell in a section using custom action? - ios

ive been struggling too delete my cells in the tableview using trailingSwipeActionsConfigurationForRowAt I have it set up to where you swipe the cell the delete image shows up and an alertView shows when the delete image is pressed and asks if you want to delete the row. But once yes is pressed the cell doesn't get deleted/removed
import UIKit
class CartViewController: UIViewController {
var selectedProduct: ItemList! // allows data to be passed into the CartVC
// allows data to be sepearted into sections
var cartItems: [CartItem] = []
var groupedItems: [String: [CartItem]] = [:]
var brandTitle: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
groupedItems = Dictionary(grouping: cartItems, by: {$0.itemList.brandName})
brandTitle = groupedItems.map{$0.key}.sorted()
}
}
extension CartViewController: UITableViewDelegate, UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return brandTitle.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let brand = brandTitle[section]
return groupedItems[brand]!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cartCell = tableView.dequeueReusableCell(withIdentifier: "CartCell") as! CartCell
let brand = brandTitle[indexPath.section]
let itemsToDisplay = groupedItems[brand]![indexPath.row]
cartCell.configure(withCartItems: itemsToDisplay.productList)
return cartCell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cartHeader = tableView.dequeueReusableCell(withIdentifier: "CartHeader") as! CartHeader
let headerTitle = brandTitle[section]
cartHeader.brandName.text = "Brand: \(headerTitle)"
return cartHeader
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 45
}
func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
let cartFooter = tableView.dequeueReusableCell(withIdentifier: "CartFooter") as! CartFooter
let brand = brandTitle[section]
let subtotal = groupedItems[brand]?.map { $0.getCartTotal() }.reduce(0, +) ?? 0
cartFooter.cartTotal.text = String(subtotal)
return cartFooter
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 150
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let edit = UIContextualAction(style: .normal, title: "") { (action, view, nil) in
let refreshAlert = UIAlertController(title: "Deletion", message: "Are you sure you want to remove this item from cart? ", preferredStyle: .alert)
refreshAlert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (action: UIAlertAction!) in
}))
refreshAlert.addAction(UIAlertAction(title: "No", style: .default, handler: { (action: UIAlertAction!) in
refreshAlert .dismiss(animated: true, completion: nil)
}))
self.present(refreshAlert, animated: true, completion: nil)
}
edit.backgroundColor = .red
edit.image = #imageLiteral(resourceName: "best_sellers")
let config = UISwipeActionsConfiguration(actions: [edit])
config.performsFirstActionWithFullSwipe = false
return config
}
}

iOS checks the number of rows before and after a delete operation, and expects them to add up correctly following the change.In your alert action you need to delete your current object.
refreshAlert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { (action: UIAlertAction!) in
tableView.beginUpdates()
// brandTitle[indexPath.section].remove(at: indexPath.row) // or you can directly use this
let brand = brandTitle[indexPath.section]
groupedItems[brand]!.remove(at:indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()
}))

Related

How to enable Leading and Trailing Swipe with Drag and Drop UITableview Cell Swift

I am trying to enable Leading and Trailing swipe with Long press tableview cell to drag and drop option using Swift. Here, I am using below code I can able to drag and drop it but can’t able to do long press also can't able to enable leading and trailing swipe at a time. Three things need to enable by default when app launched.
Tableview Delegate
override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .normal, title: "Delete", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
print("OK, marked as Delete")
success(true)
})
deleteAction.backgroundColor = .orange
return UISwipeActionsConfiguration(actions: [deleteAction])
}
override func tableView(_ tableView: UITableView,trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let modifyAction = UIContextualAction(style: .normal, title: "Edit", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
print("Update action ...")
self.showaddMilestone()
success(true)
})
modifyAction.image = UIImage(named: "edit")
modifyAction.backgroundColor = .red
return UISwipeActionsConfiguration(actions: [modifyAction])
}
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedObject = self.milestoneTitles[sourceIndexPath.row]
milestoneTitles.remove(at: sourceIndexPath.row)
milestoneTitles.insert(movedObject, at: destinationIndexPath.row)
debugPrint("\(sourceIndexPath.row) => \(destinationIndexPath.row)")
// To check for correctness enable: self.tableView.reloadData()
}
You can do this in following way...
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
#available(iOS 11.0, *)
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
//EDIT
let actionEDIT = UIContextualAction(style: .normal, title: "", handler: { (action,view,completionHandler ) in
//do stuff
completionHandler(true)
})
actionEDIT.image = UIImage(named: "icn_edit")
actionEDIT.backgroundColor = UIColor.UIColorFromHex(hex: "F7F7F7")
//PDF
let actionPDF = UIContextualAction(style: .normal, title: "", handler: { (action,view,completionHandler ) in
//do stuff
completionHandler(true)
})
actionPDF.image = UIImage(named: "icn_pdf")
actionPDF.backgroundColor = UIColor.UIColorFromHex(hex: "F7F7F7")
//SHARE
let actionSHARE = UIContextualAction(style: .normal, title: "", handler: { (action,view,completionHandler ) in
//do stuff
completionHandler(true)
})
actionSHARE.image = UIImage(named: "icn_shareGreen")
actionSHARE.backgroundColor = UIColor.UIColorFromHex(hex: "F7F7F7")
let configuration = UISwipeActionsConfiguration(actions: [actionSHARE,actionPDF,actionEDIT])
return configuration
}
// leftAction Leading
func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let leftAction = UIContextualAction(style: .normal, title: "Edit", handler: { (ac:UIContextualAction, view:UIView, success:(Bool) -> Void) in
print("leftAction tapped")
success(true)
})
leftAction.image = UIImage(named: "")
leftAction.backgroundColor = UIColor.red
return UISwipeActionsConfiguration(actions: [leftAction])
}
Here's something that works for me but I'm not sure if it's correct and will always work in future
final class ViewController: UITableViewController {
private let data: [String] = [
"1", "2", "3", "4", "5"
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.dragInteractionEnabled = true
tableView.dragDelegate = self
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .value1, reuseIdentifier: "cell")
cell.detailTextLabel?.text = data[indexPath.row]
return cell
}
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
}
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let delete = UIContextualAction(style: .destructive, title: "Delete") { (action, view, completion ) in
completion(true)
}
return UISwipeActionsConfiguration(actions: [delete])
}
}
extension ViewController: UITableViewDragDelegate {
func tableView(_ tableView: UITableView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
return []
}
}

How to reload Visible cells of TableView in ios swift

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.

didSelectRowAt is not being called for my table view

I want to make the transition from the UITableView cell to the view controller. My didSelectRowAt isn't working.
Here is code:
import UIKit
class ViewController: UIViewController, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var goals = [String]()
#IBAction func onAddTapped() {
let alert = UIAlertController(title: "Add Goal", message: nil, preferredStyle: .alert)
alert.addTextField { (dessertTF) in
dessertTF.placeholder = "Enter Goal"
}
let action = UIAlertAction(title: "Add", style: .default) { (_) in
guard let goal = alert.textFields?.first?.text else { return }
print(goal)
self.add(goal)
}
alert.addAction(action)
present(alert, animated: true)
}
func add(_ goal: String) {
let index = 0
goals.insert(goal, at: index)
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .left)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "segue", sender: self)
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return goals.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let dessert = goals[indexPath.row]
cell.textLabel?.text = goals
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
guard editingStyle == .delete else { return }
desserts.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
The problem is that you aren't creating your cells correctly.
Inside of your cellForRow function change
let cell = UITableViewCell()
to this:
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
Make sure that in your storyboard file, that you set the cell identifier to "Cell"

call by touching cell in Swift

i used Core Date to save names and phone numbers
i would like to make a call by touching cell
here is my code:
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var people = [Person]()
override func viewDidLoad() {
super.viewDidLoad()
let fetchRequest: NSFetchRequest<Person> = Person.fetchRequest()
do {
let people = try PersistenceService.context.fetch(fetchRequest)
self.people = people
self.tableView.reloadData()
} catch {}
}
#IBAction func onPlusTapped() {
let alert = UIAlertController(title: "Add Person", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Name"
}
alert.addTextField { (textField) in
textField.placeholder = "Phone number"
textField.keyboardType = .numberPad
}
let action = UIAlertAction(title: "Post", style: .default) { (_) in
let name = alert.textFields!.first!.text!
let phoneNumber = alert.textFields!.last!.text!
let person = Person(context: PersistenceService.context)
person.name = name
person.phoneNumber = phoneNumber
PersistenceService.saveContext()
self.people.append(person)
self.tableView.reloadData()
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
override var prefersStatusBarHidden: Bool {
return true
}
}
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: nil)
cell.textLabel?.text = people[indexPath.row].name
cell.detailTextLabel?.text = people[indexPath.row].phoneNumber
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
people.remove(at: indexPath.item)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
UIApplication.shared.openURL(NSURL(string: "tel://" + (people[indexPath.row].phoneNumber?.description)!)! as URL)
print(people[indexPath.row].phoneNumber?.description)
}
}
No need to add an #IBAction, you can use didSelectRow from UITableViewDelegate
You are already implementing the didSelectRowAt use IBActions only inside UITableView if you have a UIButton inside the UITableViewCell

UserDefaults in a UITableViewCell not saving (swift3)

My code right now just lists things you manually enter. However when the user switches view controllers the code disappears. I tried to use userdefualts to save my current code in the number of rows in selection function but it does not save the items in the tableview cells. I just want to save whatever is in the tableview cells.
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var items: [String] = [""]
#IBOutlet weak var listTableView: UITableView!
#IBAction func addItem(_ sender: AnyObject) {
alert()
}
#IBOutlet weak var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
listTableView.dataSource = self
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "listitem") as! ItemTableViewCell
cell.itemLabel.text = items[indexPath.row]
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let userDefaults = UserDefaults.standard
userDefaults.setValue(items, forKey: "items")
userDefaults.synchronize()
return items.count
}
func alert(){
let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
alert.addTextField{
(textfield) in
textfield.placeholder = " Enter "
}
let add = UIAlertAction(title: "Add", style: .default){
(action) in
let textfield = alert.textFields![0]
self.items.append(textfield.text!)
self.listTableView.reloadData()
}
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: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
}}
You have a number of issues in your code:
You never load your data back from UserDefaults. - This is the big one
There is no need to call synchronise.
You should save your data when data is added/deleted, not in numberOfRowsInSection.
It will look nicer if you insert a new row rather than reloading the whole table
I would suggest something like:
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var items = [String]()
#IBOutlet weak var listTableView: UITableView!
#IBAction func addItem(_ sender: AnyObject) {
alert()
}
#IBOutlet weak var webView: UIWebView!
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: "listitem") as! ItemTableViewCell
cell.itemLabel.text = items[indexPath.row]
cell.preservesSuperviewLayoutMargins = false
cell.separatorInset = UIEdgeInsets.zero
cell.layoutMargins = UIEdgeInsets.zero
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: "", message: "", preferredStyle: .alert)
alert.addTextField{
(textfield) in
textfield.placeholder = " Enter "
}
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)
saveData()
let indexPath = IndexPath(row: 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: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
saveData()
}
}
Also, you shouldn't really use UserDefaults for data storage this way, but I presume this is just a simple learning exercise.

Resources