Swift expandable table view header for sections problem [duplicate] - ios

This question already has answers here:
How to deal with dynamic Sections and Rows in iOS UITableView
(3 answers)
Closed 3 years ago.
I have question about expandable table view by header. I made it for section zero and its working correctly but when I try to make it same thing for section one too it gives error which is
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (0) must be equal to the number of rows contained in that section before the update (2), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
If I didn't trigger second header function, first one still working. When I clicked second header ( which is section one) it gives that error.
Here my header view code:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if section == 0 {
let button = UIButton(type: .system)
button.setTitle("Kapat", for: .normal)
button.setTitleColor(.black, for: .normal)
button.backgroundColor = .lightGray
button.titleLabel!.font = UIFont.boldSystemFont(ofSize: 14)
button.addTarget(self, action: #selector(handleExpandCloseForAlim(sender:)), for: .touchUpInside)
return button
}
if section == 1 {
let button2 = UIButton(type: .system)
button2.setTitle("Kapat", for: .normal)
button2.setTitleColor(.black, for: .normal)
button2.backgroundColor = .lightGray
button2.titleLabel!.font = UIFont.boldSystemFont(ofSize: 14)
button2.addTarget(self, action: #selector(handleExpandCloseForTeslim(sender:)), for: .touchUpInside)
return button2
}
else{
return nil
}
}
Here my action functions:
#objc func handleExpandCloseForAlim(sender: UIButton) {
var indexPaths = [IndexPath]()
for row in self.userAdressDict.indices{
let indexPath = IndexPath(row: row, section: 0)
indexPaths.append(indexPath)
}
let indexPath = IndexPath(row: (sender.tag), section: 0)
let adresso = self.userAdressDict[indexPath.row]
let isExp = adresso.isExpanded
adresso.isExpanded = !isExp!
if isExp! {
tableView.deleteRows(at: indexPaths, with: .fade)
}else {
tableView.insertRows(at: indexPaths, with: .fade)
}
print(adresso.isExpanded!, adresso.userMahalle!)
}
#objc func handleExpandCloseForTeslim(sender: UIButton) {
var indexPathsForTeslim = [IndexPath]()
for row in self.userAdressDictForTeslim.indices{
let indexPath = IndexPath(row: row, section: 1)
indexPathsForTeslim.append(indexPath)
}
let indexPath = IndexPath(row: (sender.tag), section: 1)
let adresso = self.userAdressDictForTeslim[indexPath.row]
let isExp = adresso.isExpanded
adresso.isExpanded = !isExp!
if isExp! {
tableView.deleteRows(at: indexPathsForTeslim, with: .fade)
}else {
tableView.insertRows(at: indexPathsForTeslim, with: .fade)
}
print(adresso.isExpanded!, adresso.userMahalle!)
}
And here my numberOfRowsInSection part:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
guard let element = self.userAdressDict.first as? adresStruct else { return 0 }
if element.isExpanded {
return self.userAdressDict.count
}
else {
return 0
}
}
if section == 1 {
guard let teslim = self.userAdressDictForTeslim.last as? adresStruct else { return 0 }
if teslim.isExpanded {
return self.userAdressDictForTeslim.count
}
else {
return 0
}
}
return 1
}
I can't find any solution. I assume sender thing working wrongly but it may be another problem. I hope I asked clearly.Please help me.

I am not deep dive in your code but I copy-paste into an Xcode project to look into. So there is basic way to solve your problem, I hope it helps to you.
I assume your class is like that.
class Adres{
var title: String = ""
var isExpanded: Bool = true
init(title: String, isExpanded: Bool){
self.title = title
self.isExpanded = isExpanded
}
}
And in there mainly 2 different variable for adresDict & adresDictForTeslim.
So I keep an lock array to doing logic stuff of expandable.
var keys: [Int] = [1,1]
<1> Show
<0> Hide
Now, the arrays elements are showing into UITableView, because its expanded.
There's mock data.
var userAdressDict = [Adres(title: "First Adres", isExpanded: true) ,Adres(title: "Second Adres", isExpanded: true)]
var userAdressDictForTeslim = [Adres(title: "First Teslim", isExpanded: true),Adres(title: "Second Teslim", isExpanded: true)]
viewForHeaderInSection is same as well.
And for numberOfRowsInSection check array for key that if its expanded or not.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
if self.keys[0] == 1 {
return userAdressDict.count
}else{
return 0
}
}else{
if self.keys[1] == 1 {
return userAdressDictForTeslim.count
}else{
return 0
}
}
}
Then check these keys in handler functions.
#objc func handleExpandCloseForAlim(sender: UIButton) {
if keys[0] == 0 {
keys[0] = 1
}else{
keys[0] = 0
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
If first array elements in table view is expanded close it, or not expand it.
Then in another function.
#objc func handleExpandCloseForTeslim(sender: UIButton) {
if keys[1] == 0 {
keys[1] = 1
}else{
keys[1] = 0
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
So far so good. It's working on my simulator.
Then there's more efficient way to handle keys of expanded.
Make your UIButton's of the sectionHeader as public variable in your ViewController and control it if it's title is "Kapat" and clicked then it must be "Aç" or if its "Aç" and touched it must be "Kapat".

Related

UITableView reloadData() causing reload to UISearchBar inside every sections

I have a tableview which has 2 sections. Both of the sections have UISearchBar in the indexPath.row 0 and the rest of the rows in each section populate the list of array.
Whenever I type some text in search bar every time the searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) delegate method gets called and inside the delegate method I call tableView.reloadData() to reload the search results in tableview.
Now the problem is each time the tableView reloads the UISearchBar reloads too (as UISearchbar is in row number 1) and every time the SearchBar keypad Resigns.
Instead of doing tableView.reloadData() I even tried to reload every row except the first one using bellow code
let allButFirst = (self.tableView.indexPathsForVisibleRows ?? []).filter { $0.section != selectedSection || $0.row != 0 }
self.tableView.reloadRows(at: allButFirst, with: .automatic)
But no luck. App gets crashed saying
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to insert row 2 into section 0, but there are only 2 rows in section 0 after the update'
You are probably changing the data source and then you are reloading rows at index paths what doesn't exist yet.
It is not so easy, but let's have an example: Before you start typing, the search result will contain something like this:
["aa", "ab", "ba", "bb"]
Then you will type "a" to the search bar and data source changes into:
["aa", "ab"]
tableView.deleteRows(at: [IndexPath(row:3, section: 0), IndexPath(row:4, section: 0)], with: .automatic)
then you delete everything in this searchbar and your data source will change to the default: ["aa", "ab", "ba", "bb"]
so in this case you need to call:
tableView.insertRows(at: [IndexPath(row:3, section: 0), IndexPath(row:4, section: 0)], with: .automatic)
I created some working example - without storyboard source, I believe it is pretty simple to recreated it according this class.
class SearchCell: UITableViewCell {
#IBOutlet weak var textField:UITextField?
}
class TextCell: UITableViewCell {
#IBOutlet weak var label:UILabel?
}
class ViewController: UIViewController, UITableViewDataSource, UITextFieldDelegate {
#IBOutlet weak var tableView: UITableView?
weak var firstSectionTextField: UITextField?
var originalDataSource:[[String]] = [["aa","ab","ba","bb"], ["aa","ab","ba","bb"]]
var dataSource:[[String]] = []
let skipRowWithSearchInput = 1
override func viewDidLoad() {
super.viewDidLoad()
dataSource = originalDataSource
tableView?.tableFooterView = UIView()
tableView?.tableHeaderView = UIView()
}
func numberOfSections(in tableView: UITableView) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource[section].count + skipRowWithSearchInput
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0, let cell = tableView.dequeueReusableCell(withIdentifier: "search", for: indexPath) as? SearchCell {
cell.textField?.removeTarget(self, action: #selector(textFieldDidChangeText(sender:)), for: .editingChanged)
cell.textField?.addTarget(self, action: #selector(textFieldDidChangeText(sender:)), for: .editingChanged)
if indexPath.section == 0 {
firstSectionTextField = cell.textField
}
return cell
} else if let cell = tableView.dequeueReusableCell(withIdentifier: "text", for: indexPath) as? TextCell {
cell.label?.text = dataSource[indexPath.section][indexPath.row - skipRowWithSearchInput]
return cell
} else {
return UITableViewCell()
}
}
#objc func textFieldDidChangeText(sender: UITextField) {
let section = sender == firstSectionTextField ? 0 : 1
let text = sender.text ?? ""
let oldDataSource:[String] = dataSource[section]
//if the search bar is empty then use the original data source to display all results, or initial one
let newDataSource:[String] = text.count == 0 ? originalDataSource[section] : originalDataSource[section].filter({$0.contains(text)})
var insertedRows:[IndexPath] = []
var deletedRows:[IndexPath] = []
var movedRows:[(from:IndexPath,to:IndexPath)] = []
//resolve inserted rows
newDataSource.enumerated().forEach { (tuple) in let (toIndex, element) = tuple
if oldDataSource.contains(element) == false {
insertedRows.append(IndexPath(row: toIndex + skipRowWithSearchInput, section: section))
}
}
//resolve deleted rows
oldDataSource.enumerated().forEach { (tuple) in let (fromIndex, element) = tuple
if newDataSource.contains(element) == false {
deletedRows.append(IndexPath(row: fromIndex + skipRowWithSearchInput, section: section))
}
}
//resolve moved rows
oldDataSource.enumerated().forEach { (tuple) in let (index, element) = tuple
if newDataSource.count > index, let offset = newDataSource.firstIndex(where: {element == $0}), index != offset {
movedRows.append((from: IndexPath(row: index + skipRowWithSearchInput, section: section), to: IndexPath(row: offset + skipRowWithSearchInput, section: section)))
}
}
//now set dataSource for uitableview, right before you are doing the changes
dataSource[section] = newDataSource
tableView?.beginUpdates()
if insertedRows.count > 0 {
tableView?.insertRows(at: insertedRows, with: .automatic)
}
if deletedRows.count > 0 {
tableView?.deleteRows(at: deletedRows, with: .automatic)
}
movedRows.forEach({
tableView?.moveRow(at: $0.from, to: $0.to)
})
tableView?.endUpdates()
}
}
the result:
If do you need to clarify something, feel free to ask in comment.
Try this-
tableView.beginUpdates()
//Do the update thing
tableView.endUpdates()
It worked.
I took two sections one for search field and another for reloading data (rows populating data).
I took separate custom cell for search and took outlet in that class itself.
and in viewForHeaderInSection I used tableView.dequeueReusableCell(withIdentifier:) and returned customCell.contentView
Then I called tableview.ReloadData() in searchBar(_ searchBar: UISearchBar, textDidChange searchText: String)
It worked without problem.

iOS: Invalid update: invalid number of rows in section 0

I create a table view that every time I scroll to the end of table it will show shimmer in table section 1 for waiting the api load a new data. After api finish load data, new data will append to the array that use for contain all data that be show to table view on section 0, then reload the table view to update the section 1 numberOfRowsInSection to 0 to hide the shimmer and update the section 0 numberOfRowsInSection
So this is the example of my code
fileprivate var data = [dataArray]() {
didSet {
guard !data.isEmpty else {
return
}
tableView.reloadData()
tableView.layoutIfNeeded()
view.layoutIfNeeded()
}
}
fileprivate var isLoadingMore: Bool = false {
didSet {
tableView.reloadSections(IndexSet(integer: 1), with: .automatic)
tableView.layoutIfNeeded()
tableViewHeightConstraint.constant = tableView.contentSize.height + 60.0
view.layoutIfNeeded()
}
}
fileprivate func loadData() {
if let size = self.paging.totalSize,
data.count >= size {
return
}
let limit = paging.limit
let offset = paging.offset
guard !isLoadingMore else { return }
isLoadingMore = true
controller.requestContent(
completion: { [weak self] (success, offset, size, data) in
DispatchQueue.main.async {
self?.isLoadingMore = false
guard let list = data,
let data = list as? [dataArray],
let size = size else {
return
}
if success {
if self?.paging.currentPage == 0 {
self?.data = data
if self?.scrollView.frame.size.height >= self?.scrollView.contentSize.height {
self?.paging.totalSize = size
self?.paging.currentPage += 1
self?.loadData()
return
}
} else {
self?.data.append(contentsOf: songs)
}
self?.paging.totalSize = size
self?.paging.currentPage += 1
} else {
self?.alert("failed")
}
}
})
}
fileprivate func loadDataFromCoreData() {
isLoadingMore = false
let context = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
let dataFromCoreData = Datas.fetchData(context: context).filter({$0.isSaved})
songs = dataFromCoreData.map({ dataArray(song: $0) })
}
func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0:
return data.count
case 1:
return isLoadingMore ? 1 : 0
default:
return 0
}
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if ((scrollView.contentOffset.y + scrollView.frame.size.height) >= scrollView.contentSize.height){
loadData()
}
}
func setupForCeckingAvailableData() {
utility.checkInternetAccess = { [weak self] result in
if result {
self?.paging = Paging()
self?.loadData()
} else {
self?.loadDataFromCoreData()
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
setupForCeckingAvailableData()
}
so some time when I first time load or try fetch new data by scrolling to the end of table I got a crash with the message is
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (0) must be equal to the number of rows contained in that section before the update (10), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
So what the cause of it? Why got that crash even though I already use reloadData() every time I append a new data to variable data. Should I change reloadData() to
tableView.beginUpdates()
tableView.insertRows(at: indexPaths, with: .automatic)
tableView.endUpdates()
? if that so, is that the crash cause insertRows only refresh specific row and section so no take a loot of time to refresh table with a loot of datas??
That's all of my question, Thank you
I just want to point out something else.
Generally speaking, Singletons are Evil and I avoid them like the plague.
This is a real big code smell and would advise you to stop using this type of code:
TableView.beginUpdates()
TableView.insertRows(at: indexPaths, with: .automatic)
TableView.endUpdates()
See What is so bad about singletons? there are many more blog posts about it.
after I do some research I found a clue.
I add tableView.reloadData() before tableView.reloadSections(IndexSet(integer: 1), with: .automatic) because when I see the crashlytic crash happend at tableView.reloadSections(IndexSet(integer: 1), with: .automatic)

UICollection load more cell manually Swift

I have made a UICollection who contain only 2 cells and with a footer who contain a button "Show me more". And when we click on the button "Show me more" it should append 2 more cells in the UICollection.
My problem is when I click on the button the UICollection doesn't reload and append the 2 new cells. I checked different post about infinite scrolling, but I can't resolve my problem. Here is my code:
My button and his function
lazy var showMoreButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Show me more", for: .normal)
button.addTarget(self, action: #selector(handleShowMore), for: .touchUpInside)
return button
}()
func handleShowMore(){
homeDatasource?.incrementNumberOfUser()
HomeDatasourceController().collectionView?.reloadData()
}
My collection
`
var numberOfUser: Int = 2
class HomeDatasource: Datasource, JSONDecodable {
override func footerClasses() -> [DatasourceCell.Type]? {
return [UserFooter.self]
}
override func cellClasses() -> [DatasourceCell.Type] {
return [UserCell.self, TweetCell.self] //UserCell -> section 0 and TweetCell -> section 1
}
override func item(_ indexPath: IndexPath) -> Any? {
if indexPath.section == 1 {
return tweets[indexPath.item]
}
return users[indexPath.item]
}
override func numberOfItems(_ section: Int) -> Int {
if section == 1 {
return tweets.count
}
return numberOfUser //users.count
}
override func numberOfSections() -> Int {
return 2
}
func incrementNumberOfUser() {
if numberOfUser <= users.count {
numberOfUser = numberOfUser + 2
}
}
}
`
this line
HomeDatasourceController().collectionView?.reloadData()
seems to be the cause. Because you are creating a new 'HomeDatasourceController' although at the time of reloading it should already exist. So you should reuse the already existing instance of 'HomeDatasourceController'.
Somewhere you created an instance e.g.
let datasourceController = HomeDatasourceController()
now reuse that instance in
func handleShowMore() {
homeDatasource?.incrementNumberOfUser()
datasourceController.collectionView?.reloadData()
}

iOS Swift, Update UITableView custom cell label outside of tableview CellForRow using tag

Setup (Swift 1.2 / iOS 8.4):
I have UITableView custom cell (identifier = Cell) inside UIViewController. Have two buttons (increment/decrement count) and a label (display count) inside the custom TableView cell.
Goal:
Update the label as we press the increase count or decrease count button.
At present I am able to get the button Tag and call a function outside of the CellForRowAtIndexPath. The button press increases and decreases the count. But I am not able to display the count update in the label.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:FoodTypeTableViewCell = self.tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! FoodTypeTableViewCell
cell.addBtn.tag = indexPath.row // Button 1
cell.addBtn.addTarget(self, action: "addBtn:", forControlEvents: .TouchUpInside)
cell.subBtn.tag = indexPath.row // Button 2
cell.subBtn.addTarget(self, action: "subBtn:", forControlEvents: .TouchUpInside)
cell.countLabel.text = // How can I update this label
return cell
}
func addBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
count = 1 + count
println(count)
return count
}
func subBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
if count == 0 {
println("Count zero")
} else {
count = count - 1
}
println(count)
return count
}
I have seen this question here and there but was not able to find a clear answer in Swift. I would really appreciate if you could help answer it clearly so that other people can not just copy, but clearly understand what is going on.
Thank you.
Here is a solution that doesn't require tags. I'm not going to recreate the cell exactly as you want, but this covers the part you are asking about.
Using Swift 2 as I don't have Xcode 6.x anymore.
Let's start with the UITableViewCell subclass. This is just a dumb container for a label that has two buttons on it. The cell doesn't actually perform any specific button actions, it just passes on the call to closures that are provided in the configuration method. This is part of MVC. The view doesn't interact with the model, just the controller. And the controller provides the closures.
import UIKit
typealias ButtonHandler = (Cell) -> Void
class Cell: UITableViewCell {
#IBOutlet private var label: UILabel!
#IBOutlet private var addButton: UIButton!
#IBOutlet private var subtractButton: UIButton!
var incrementHandler: ButtonHandler?
var decrementHandler: ButtonHandler?
func configureWithValue(value: UInt, incrementHandler: ButtonHandler?, decrementHandler: ButtonHandler?) {
label.text = String(value)
self.incrementHandler = incrementHandler
self.decrementHandler = decrementHandler
}
#IBAction func increment(sender: UIButton) {
incrementHandler?(self)
}
#IBAction func decrement(sender: UIButton) {
decrementHandler?(self)
}
}
Now the controller is just as simple
import UIKit
class ViewController: UITableViewController {
var data: [UInt] = Array(count: 20, repeatedValue: 0)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! Cell
cell.configureWithValue(data[indexPath.row], incrementHandler: incrementHandler(), decrementHandler: decrementHandler())
return cell
}
private func incrementHandler() -> ButtonHandler {
return { [unowned self] cell in
guard let row = self.tableView.indexPathForCell(cell)?.row else { return }
self.data[row] = self.data[row] + UInt(1)
self.reloadCellAtRow(row)
}
}
private func decrementHandler() -> ButtonHandler {
return { [unowned self] cell in
guard
let row = self.tableView.indexPathForCell(cell)?.row
where self.data[row] > 0
else { return }
self.data[row] = self.data[row] - UInt(1)
self.reloadCellAtRow(row)
}
}
private func reloadCellAtRow(row: Int) {
let indexPath = NSIndexPath(forRow: row, inSection: 0)
tableView.beginUpdates()
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
tableView.endUpdates()
}
}
When the cell is dequeued, it configures the cell with the value to show in the label and provides the closures that handle the button actions. These controllers are what interact with the model to increment and decrement the values that are being displayed. After changing the model, it reloads the changed cell in the tableview.
The closure methods take a single parameter, a reference to the cell, and from this it can find the row of the cell. This is a lot more de-coupled than using tags, which are a very brittle solution to knowing the index of a cell in a tableview.
You can download a full working example (Requires Xcode7) from https://bitbucket.org/abizern/so-32931731/get/ce31699d92a5.zip
I have never seen anything like this before so I am not sure if this will be the correct way to do. But I got the intended functionality using the bellow code:
For people who find it difficult to understand:
The only problem we have in this is to refer to the TableView Cell. Once you figure out a way to refer the cell, you can interact with the cell components.
func addBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0) // This defines what indexPath is which is used later to define a cell
let cell = tableView.cellForRowAtIndexPath(indexPath) as! FoodTypeTableViewCell! // This is where the magic happens - reference to the cell
count = 1 + count
println(count)
cell.countLabel.text = "\(count)" // Once you have the reference to the cell, just use the traditional way of setting up the objects inside the cell.
return count
}
func subBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(indexPath) as! FoodTypeTableViewCell!
if count == 0 {
println("Count zero")
} else {
count = count - 1
}
cell.countLabel.text = "\(count)"
println(count)
return count
}
I hope someone will benefit from this.
PLEASE CORRECT ME IF THERE IS SOME PROBLEM IN THIS SOLUTION OR THERE IS A BETTER/PROPER WAY TO DO THIS.
Use tableView.reloadData() to reload your tableView content each time you click a button.
let text = "something"
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:FoodTypeTableViewCell = self.tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! FoodTypeTableViewCell
cell.addBtn.tag = indexPath.row // Button 1
cell.addBtn.addTarget(self, action: "addBtn:", forControlEvents: .TouchUpInside)
cell.subBtn.tag = indexPath.row // Button 2
cell.subBtn.addTarget(self, action: "subBtn:", forControlEvents: .TouchUpInside)
cell.countLabel.text = something
return cell
}
func addBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
count = 1 + count
println(count)
something = "\(count)"
self.tableView.reloadData()
return count
}
func subBtn(sender: AnyObject) -> Int {
let button: UIButton = sender as! UIButton
if count == 0 {
println("Count zero")
} else {
count = count - 1
}
println(count)
something = "\(count)"
self.tableView.reloadData()
return count
}
Update1
After your comments ...
you have an array (one value for each food) like this, and whenever you click on a button, you take the index of the row the contains that button, then use that index to retrive the value of count from your array, then reload the table view content.

Get indexPath of "next" UITableViewCell

I have some code when tapping on a cell of a table view. Under certain circumstances I want to call the function tableView(_, didSelectRowAtIndexPath) recursively for the next cell. That means that when I selected row 5, I want to select row 6, etc.
How can I get the indexPath of the next cell based on another row?
Here's an answer in Swift:
private func nextIndexPath(for currentIndexPath: IndexPath, in tableView: UITableView) -> IndexPath? {
var nextRow = 0
var nextSection = 0
var iteration = 0
var startRow = currentIndexPath.row
for section in currentIndexPath.section ..< tableView.numberOfSections {
nextSection = section
for row in startRow ..< tableView.numberOfRows(inSection: section) {
nextRow = row
iteration += 1
if iteration == 2 {
let nextIndexPath = IndexPath(row: nextRow, section: nextSection)
return nextIndexPath
}
}
startRow = 0
}
return nil
}
I use this code because I have a tableview with custom cells that contain a UITextField. It's configured with a Next button, and when that button is tapped, the focus is moved to the next UITextField.
To go to the previous indexPath, see this answer: https://stackoverflow.com/a/56867271/
For an example project that includes a previous/next button as a toolbar above a keyboard, check out the example project:
https://github.com/bvankuik/TableViewWithTextFieldNextButton
For previous indexPath I have made the following extension on UITableView
( Swift 5.0 )
extension UITableView {
func previousIndexPath(currentIndexPath: IndexPath) -> IndexPath? {
let startRow = currentIndexPath.row
let startSection = currentIndexPath.section
var previousRow = startRow
var previousSection = startSection
if startRow == 0 && startSection == 0 {
return nil
} else if startRow == 0 {
previousSection -= 1
previousRow = numberOfRows(inSection: previousSection) - 1
} else {
previousRow -= 1
}
return IndexPath(row: previousRow, section: previousSection)
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let nextIndexPath=NSIndexPath(forRow: indexPath.row + 1, inSection: indexPath.section);
// You should be sure than this NSIndexPath exist, and ...make what you want
}
this will work in swift 4
for previous and next
let nextIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section)
let previousIndexPath = IndexPath(row: indexPath.row - 1, section: indexPath.section)
I wrote an IndexPath extension method, I found its logic is a bit easier to understand than #Bart van Kuik's solution.
Written in Swift 5, Xcode 11, works for multi-section UITableView.
import UIKit
extension IndexPath {
// Helper Methods
func incrementRow(plus: Int=1) -> IndexPath {
return IndexPath(row: row + plus, section: section)
}
func incrementSection(plus: Int=1) -> IndexPath {
return IndexPath(row: 0, section: section + plus)
}
func next(in table: UITableView) -> IndexPath? {
// if can find cell for next row, return next row's IndexPath
if let _ = table.cellForRow(at: incrementRow()) {
return incrementRow()
}
// cannot find next row, try to find row 0 in next section
else if let _ = table.cellForRow(at: incrementSection()) {
return incrementSection()
}
// can find neither next row nor next section, the current indexPath is already the very last IndexPath in the given table
return nil
}
}
As for the previous IndexPath, #Bishal Ghimire's answer is valid, but here's the IndexPath version extension.
func previous(in table: UITableView) -> IndexPath? {
// if the current indexPath is the very first IndexPath, then there's no previous
if row == 0 && section == 0 { return nil }
// if the current indexPath is the first row in a section, return table's previous section's last row's IndexPath
if row == 0 {
let lastRowInPrevSection = table.numberOfRows(inSection: section - 1) - 1
return IndexPath(row: lastRowInPrevSection, section: section - 1)
}
// else just return previous row's IndexPath in the same section
else {
return IndexPath(row: row - 1, section: section)
}
}
You can drag & drop these method into any of your project and use them directly, in my case, I'm trying to highlight next cell's textField when the user hit return key, so the usage is like this:
...
if let nextIndexPath = currentIndexPath.next(in: myTableView),
let nextCell = myTableView.cellForRow(at: nextIndexPath) as? MyCell {
nextCell.textField.becomeFirstResponder()
} else {
// there's no next IndexPath in the given table, simply resign first responder for the current cell's textField
currentCell.textField.resignFirstResponder()
}
...
Currently, it seems to me that only(?) Bart van Kuiks answer currently considers the possibility, that a section could consists of none rows.
The other posters might correct their answers. Meanwhile I post my code for next and previous cells as UITableView-Extensions. Feel free to edit the code, if you find any mistakes.
extension UITableView {
func indexPathOfCell(after indexPath: IndexPath) -> IndexPath? {
var row = indexPath.row + 1
for section in indexPath.section..<numberOfSections {
if row < numberOfRows(inSection: section) {
return IndexPath(row: row, section: section)
}
row = 0
}
return nil
}
func indexPathOfCell(before indexPath: IndexPath) -> IndexPath? {
var row = indexPath.row - 1
for section in (0...indexPath.section).reversed() {
if row >= 0 {
return IndexPath(row: row, section: section)
}
if section > 0 {
row = numberOfRows(inSection: section - 1) - 1
}
}
return nil
}
}
For those who liked #Bishal Ghimire's previousIndexPath() method, here is what the nextIndexPath() method would be.
import UIKit
extension UITableView {
func nextIndexPath(currentIndexPath: IndexPath) -> IndexPath? {
let startRow = currentIndexPath.row
let startSection = currentIndexPath.section
var nextRow = startRow
var nextSection = startSection
if startSection == numberOfSections-1 && startRow == numberOfRows(inSection: startSection)-1 {
return nil
} else if startRow == numberOfRows(inSection: startSection)-1 {
nextSection += 1
nextRow = 0
} else {
nextRow += 1
}
return IndexPath(row: nextRow, section: nextSection)
}
}
You can get the IndexOFObeject
NSUInteger indexOfTheObject = [Array indexOfObject:indexPath];
and for Cell tap:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *temp = [Array objectAtIndex:indexPath.row+1];
temp...
}

Resources