How to reload Visible cells of TableView in ios swift - ios

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.

Related

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

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()
}))

Popup alert user before delete TableViewCell in swift?

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)
}
}

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.

Xcode can't find my cell identifier

I'm getting an error with my code that says...
"Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'unable to dequeue a cell
with identifier ItemCell - must register a nib or a class for the
identifier or connect a prototype cell in a storyboard'?"
I've set the cell to ItemCell and I've run Product > Clean. It still can't find it. Can anyone see what I've done wrong? I've included a screen shot of my code and my storyboard.
import UIKit
class ItemsViewController: UITableViewController {
var itemStore: ItemStore!
#IBAction func addNewItem(_ sender: UIButton) {
let newItem = itemStore.createItem()
if let index = itemStore.allItems.index(of: newItem) {
let indexPath = IndexPath(row: index, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
}
#IBAction func toggleEditingMode(_ sender: UIButton) {
if isEditing {
sender.setTitle("Edit", for: .normal)
setEditing(false, animated: true)
} else {
sender.setTitle("Done", for: .normal)
setEditing(true, animated: true)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let statusBarHeight = UIApplication.shared.statusBarFrame.height
let insets = UIEdgeInsetsMake(statusBarHeight, 0, 0, 0)
tableView.contentInset = insets
tableView.scrollIndicatorInsets = insets
tableView.rowHeight = 65
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemStore.allItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
let item = itemStore.allItems[indexPath.row]
cell.nameLabel.text = item.name
cell.serialNumberLabel.text = item.serialNumber
cell.valueLabel.text = "$\(item.valueInDollars)"
return cell
}
override func tableView(_ tableView: UITableView,
commit editingStyle: UITableViewCellEditingStyle,
forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let item = itemStore.allItems[indexPath.row]
let title = "Delete \(item.name)"
let message = "You sure ya wanna delete this?"
let ac = UIAlertController(title: title,
message: message,
preferredStyle: .actionSheet)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
ac.addAction(cancelAction)
let deleteAction = UIAlertAction(title: "Delete", style: .destructive, handler: { (action) -> Void in
self.itemStore.removeItem(item)
self.tableView.deleteRows(at: [indexPath], with: .automatic)
})
ac.addAction(deleteAction)
present(ac, animated: true, completion: nil)
}
}
override func tableView(_ tableView: UITableView,
moveRowAt sourceIndexPath: IndexPath,
to destinationIndexPath: IndexPath) {
itemStore.moveItem(from: sourceIndexPath.row, to: destinationIndexPath.row)
}
This is my ItemCell
import UIKit
class ItemCell: UITableViewCell {
#IBOutlet var nameLabel: UILabel!
#IBOutlet var serialNumberLabel: UILabel!
#IBOutlet var valueLabel: UILabel!
}
And my ItemStore
import UIKit
class ItemStore {
var allItems = [Item] ()
#discardableResult func createItem() -> Item {
let newItem = Item(random: true)
allItems.append(newItem)
return newItem
}
func removeItem(_ item: Item) {
if let index = allItems.index(of: item) {
allItems.remove(at: index)
}
}
func moveItem(from fromIndex: Int, to toIndex: Int) {
if fromIndex == toIndex {
return
}
let movedItem = allItems[fromIndex]
allItems.remove(at: fromIndex)
allItems.insert(movedItem, at: toIndex)
}
}
You have to check your custom class name in storyboard as ItemCell.swift as your class name.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// create a new cell if needed or reuse an old one
let cell:ItemCell = tableView.dequeueReusableCell(withIdentifier: "ItemCell") as! ItemCell
let item = itemStore.allItems[indexPath.row]
cell.nameLabel.text = item.name
cell.serialNumberLabel.text = item.serialNumber
cell.valueLabel.text = "$\(item.valueInDollars)"
return cell
}
Check if you have set the correct custom class for the cell (ItemCell in this case) in Identity Inspector.

Swipe like Trash button Of Mail on iOS - Swift 2

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

Resources