I tried to put a breakpoint and I noticed that error occured after the tableView.endUpdates
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 (333) must be equal to the number of
rows contained in that section before the update (333), plus or minus
the number of rows inserted or deleted from that section (0 inserted,
235 deleted) and plus or minus the number of rows moved into or out of
that section (0 moved in, 0 moved out).'
import UIKit
import RealmSwift
import RxCocoa
import RxSwift
var myIndex = 0
class TCB_View: UIViewController,UITableViewDelegate,UITableViewDataSource,UISearchBarDelegate{
var notificationToken: NotificationToken? = nil
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
var filteredArray:[String] = []
var branchesArray:[String] = []
var compositeDisposable = CompositeDisposable()
var isSearchin = false
deinit {
compositeDisposable.dispose()
notificationToken?.invalidate()
}
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
tableView?.delegate = self
tableView?.dataSource = self
searchBar?.delegate = self
searchBar?.returnKeyType = UIReturnKeyType.done
searchBar?.placeholder = "Search Trial Courts"
let realm = try! Realm()
let tcb = realm.objects(TrialCourtBranches.self).filter("tc.tc_filter == true")
count = tcb.count
print(count)
for branch in tcb{
branchesArray.append(branch.branch_name)
}
notificationToken = tcb.observe { [weak self] (changes: RealmCollectionChange) in
guard let tableView = self?.tableView else { return }
switch changes {
case .initial:
// Results are now populated and can be accessed without blocking the UI
tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
let fromRow = {(row: Int) in return IndexPath(row: row,section:0) }
// Query results have changed, so apply them to the UITableView
tableView.beginUpdates()
tableView.insertRows(at: insertions.map(fromRow),with: .automatic)
tableView.deleteRows(at: deletions.map(fromRow),with: .automatic)
tableView.reloadRows(at: modifications.map(fromRow),with: .automatic)
tableView.endUpdates()
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
}
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == ""{
isSearchin = false
view.endEditing(true)
tableView.reloadData()
}else{
isSearchin = true
compositeDisposable.insert(UISearchBar.rx.init(searchBar).value.subscribe(onNext: { (stringResult) in
self.filteredArray = self.branchesArray.filter({(branchesArray:String) -> Bool in
if branchesArray.contains(self.searchBar.text!){
return true
}else{
return false
}
})
}, onError: { (errorResult) in
print(errorResult)
}, onCompleted: {
print("onCompleted")
}, onDisposed: {
print("onDisposed")
}))
tableView.reloadData()
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TrialCourtCell") as! CustomTableViewCell
cell.branchName.text = branchesArray[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
myIndex = indexPath.row
}
}
When I tapped the tableCell in my other viewcontroller , error execute
Related
Essentially I am attempting to change a variable when a specific row is selected however, the code is still printing -1. Here is all my code relating. I am trying to be able to click a certain tableview cell and then be able to print out that text. Would the searchBar effect my values? I first code the tableview and then the searchbar and then I implement a submit button which prints the values of my variables.
class ViewController: UIViewController, UITableViewDataSource, UISearchBarDelegate, UITableViewDelegate {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet weak var tableView: UITableView!
let Data = ["dog","cat","goat"]
var filteredData: [String]!
var num = -1
var animal: String = ""
override func viewDidLoad() {
super.viewDidLoad()
if tableView != nil {
self.tableView.dataSource = self
}
filteredData = Data
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as UITableViewCell
cell.textLabel?.text = filteredData[indexPath.row]
print(indexPath.row)
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredData.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
num = 0
}
if indexPath.row == 1 {
num = 1
}
if indexPath.row == 2 {
num = 2
}
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredData = searchText.isEmpty ? Data : Data.filter { (item: String) -> Bool in
// If dataItem matches the searchText, return true to include it
return item.range(of: searchText, options: .caseInsensitive, range: nil, locale: nil) != nil
}
tableView.reloadData()
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
self.searchBar.showsCancelButton = true
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.showsCancelButton = false
searchBar.text = ""
searchBar.resignFirstResponder()
}
#IBAction func Submit(_ sender: Any) {
print(num)
print(filteredData.count)
if num == 0 {
animal = "dog"
}
if num == 1 {
animal = "cat"
}
if num == 2 {
animal = "goat"
}
print(animal)
}
}
There are a few issues that are not allowing you to achieve what you want:
The == operator is checking if two variables are equal to each other, not assigning one variable to another, and it will return a boolean value, either true or false. In the body of your if statements, change == to = to assign a value to the variable num.
Change your code to:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
num = 0
}
if indexPath.row == 1 {
num = 1
}
if indexPath.row == 2 {
num = 2
}
}
After seeing your updated code, it looks like you only set the dataSource of the tableView and not the delegate. You need to add the line to viewDidLoad:
tableView.delegate = self
Also, instead of having multiple if statements to check the indexPath, you could replace that entire body of code with one line:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
num = indexPath.row
}
I want to expand and collpase the multilevel array in uitableview like the following
Cat1
SubCat1
Info 1
Info 2
SubCat2
Info 1
Info 2
SubCat3
Info 1
Info 2
Cat2
SubCat1
Info 1
Info 2
For that purpose I have done the following code.
struct CellData {
var opened = Bool()
var subCatTitle = String()
var subCatList = [String]()
}
struct MainModel {
var opened = Bool()
var categoryTitle = String()
var categoryList = [CellData]()
}
I have made the list
#IBOutlet var expandableThreeStageTableView: UITableView!
var arrayList = [CellData]()
var expandableList = [MainModel]()
func loadData(){
arrayList.append(CellData(opened: false, subCatTitle: "SubCat1", subCatList: ["Info1","Info2","Info3"]))
arrayList.append(CellData(opened: false, subCatTitle: "SubCat2", subCatList: ["Info1","Info2","Info3"]))
arrayList.append(CellData(opened: false, subCatTitle: "SubCat3", subCatList: ["Info1","Info2"]))
arrayList.append(CellData(opened: false, subCatTitle: "SubCat4", subCatList: ["Info1"]))
expandableList.append(MainModel(opened: true, categoryTitle: "Cat1", categoryList: arrayList))
expandableList.append(MainModel(opened: false, categoryTitle: "Cat2", categoryList: arrayList))
expandableList.append(MainModel(opened: false, categoryTitle: "Cat3", categoryList: arrayList))
}
And delegate, datasource methods are given below
extension TextFieldAsSearchVC : UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return expandableList.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section:
Int) -> Int {
if expandableList[section].opened{
if expandableList[section].categoryList[section].opened{
return
expandableList[section].categoryList[section].subCatList.count////which extra count should return here
}else{
print("COUNT ",expandableList[section].categoryList.count)
return expandableList[section].categoryList.count +
1///here +1 is for catname + subcatname
}
}else{
return 1
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
if indexPath.row == 0{
let cell =
expandableThreeStageTableView.dequeueReusableCell(withIdentifier:
"TextFieldAsSearchVCCell", for: indexPath) as! TextFieldAsSearchVCCell
cell.lblValue.text =
expandableList[indexPath.section].categoryTitle
return cell
}else if indexPath.row <=
expandableList[indexPath.section].categoryList.count{
let cell =
expandableThreeStageTableView.dequeueReusableCell(withIdentifier:
"SectionDataCell", for: indexPath) as! SectionDataCell
cell.rowLabel.text =
expandableList[indexPath.section].categoryList[indexPath.row -
1].subCatTitle
return cell
}
else{
let cell =
expandableThreeStageTableView.dequeueReusableCell(withIdentifier:
"SectionDataCell", for: indexPath) as! SectionDataCell
cell.rowLabel.text =
expandableList[indexPath.section].categoryList[indexPath.row].
subCatList[indexPath.row]//how to access rows in subcategories
return cell
}
}
}
extension TextFieldAsSearchVC : UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath:
IndexPath) {
if indexPath.row == 0{
if expandableList[indexPath.section].opened{
expandableList[indexPath.section].opened = false
//now reload the section
let sections = IndexSet(integer: indexPath.section)
expandableThreeStageTableView.reloadSections(sections,
with: .automatic)
}else{
expandableList[indexPath.section].opened = true
//now reload sections
let sections = IndexSet(integer: indexPath.section)
expandableThreeStageTableView.reloadSections(sections,
with: .automatic)
}
}else {
if
expandableList[indexPath.section].categoryList[indexPath.row].opened{
expandableList[indexPath.section].categoryList[indexPath.row].opened =
false
expandableThreeStageTableView.reloadRows(at:
[IndexPath(index: indexPath.row)], with: .automatic)
}else{
expandableList[indexPath.section].categoryList[indexPath.row].opened =
true
expandableThreeStageTableView.reloadRows(at:
[IndexPath(index: indexPath.row)], with: .automatic)
}
}
}
}
From above code I can expand and collapse the Categories but not Subcategories.. When I tried to click on Subcategories it gives me an error
*** Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Invalid index path for use
with UITableView. Index paths passed to table view must contain exactly
two indices specifying the section and row. Please use the category on
NSIndexPath in NSIndexPath+UIKitAdditions.h if possible.'
How to deal with such type of logic?
The specific error you are getting occurs in this line:
expandableThreeStageTableView.reloadRows(at:
[IndexPath(index: indexPath.row)], with: .automatic)
An IndexPath needs both, a row and a section; you're only providing a row. So it should be something like this:
expandableThreeStageTableView.reloadRows(at:
[IndexPath(row: indexPath.row, section: indexPath.section)], with: .automatic)
If you really only need to reload the current indexPath, simply call it like this:
expandableThreeStageTableView.reloadRows(at:
[indexPath], with: .automatic)
This would fix the error you are getting, but I don't know if that solves your problem or not.
I have the following code which is working fine, it gets a list of items from a list in Realm called groceryList and displays them on a UITableView in descending order based on the productName. What I would like to be able to do is scroll to the latest inserted row/item in the table, right now when a new item is inserted the user may not see it since the items are alphabetically reordered and the latest item may not be visible on the tableView.
How can I scroll to the latest inserted row/item in a UITableView?
Realm Objects:
class Item:Object{
#objc dynamic var productName:String = ""
#objc dynamic var isItemActive = true
#objc dynamic var createdAt = NSDate()
}
class ItemList: Object {
#objc dynamic var listName = ""
#objc dynamic var createdAt = NSDate()
let items = List<Item>()
}
UITableView:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
var allItems : Results<Item>!
var groceryList : ItemList!
override func viewDidLoad() {
super.viewDidLoad()
groceryList = realm.objects(ItemList.self).filter("listName = %#", "groceryList").first
updateResultsList()
}
func updateResultsList(){
if let list = groceryList{
allItems = activeList.items.sorted(byKeyPath: "productName", ascending: false)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! CustomCell
let data = allItems[indexPath.row]
cell.displayProductName.text = data.productName
return cell
}
}
You can use Realm notifications to know when the data source Results has been modified, then update your table view from there and do the scrolling as well.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var allItems: Results<Item>!
var groceryList: ItemList!
var notificationToken: NotificationToken? = nil
deinit {
notificationToken?.invalidate()
}
override func viewDidLoad() {
super.viewDidLoad()
groceryList = realm.objects(ItemList.self).filter("listName = %#", "groceryList").first
updateResultsList()
observeGroceryList
}
func updateResultsList(){
if let list = groceryList {
allItems = activeList.items.sorted(byKeyPath: "productName", ascending: false)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! CustomCell
let data = allItems[indexPath.row]
cell.displayProductName.text = data.productName
return cell
}
func observeGroceryList() {
notificationToken = allItems.observe { [weak self] (changes: RealmCollectionChange) in
switch changes {
case .initial:
self?.tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
self?.tableView.beginUpdates()
self?.tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
self?.tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
with: .automatic)
self?.tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
self?.tableView.endUpdates()
if let lastInsertedRow = insertions.last {
self?.tableView.scrollToRow(at: insertions.last, at: .none, animated: true)
}
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
print("\(error)")
}
}
}
}
Add below code as extension of tableview.
extension UITableView {
func scrollToBottom() {
let sections = numberOfSections-1
if sections >= 0 {
let rows = numberOfRows(inSection: sections)-1
if rows >= 0 {
let indexPath = IndexPath(row: rows, section: sections)
DispatchQueue.main.async { [weak self] in
self?.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
}
}
}
Now simply use it in your method:
func updateResultsList(){
if let list = groceryList{
allItems = activeList.items.sorted(byKeyPath: "productName", ascending: false
yourTableView.scrollToBottom()
}
}
Just use this method where you want, it should be scroll down.
yourTableView.scrollToBottom()
I have UITableView inside UIScrollView and implemented paging with which I get 10 records in each page. I am facing a problem when after the IndexPath row is 9 then again UITableView reloads cells starting from row 2 due to which all the Pages are loaded once. Here is my code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if listData != nil{
print("list count in numberOfRowsInSection\(listData?.count)")
return (listData?.count)!
}
return 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("CellForRowIndexPath:\(indexPath.row)")
let cell: NewPostCell = tableView.dequeueReusableCell(withIdentifier: "NewPostCell") as? NewPostCell ??
NewPostCell(style: .default, reuseIdentifier: "NewPostCell")
cell.delegate = self
cell.updateWithModel(self.listData![indexPath.row] as AnyObject)
cell.tbleUpdateDelegate = self
cell.selectionStyle = .none
cell.accessoryType = .none
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
print("in will display row - \(indexPath.row)")
if pageNumber >= totalPages {
return
}
else
{
if (listData?.count)! == 10*pageNumber{
if (listData?.count)! - 3 == indexPath.row{
if !boolHitApi{
boolHitApi = true
return
}
pageNumber += 1
self.callService()
}
}
}
}
override func viewWillAppear(_ animated: Bool) {
callService()
}
func callService(){
SVProgressHUD.show()
setPageToOne()
ProfileApiStore.shared.requestToGetProfile(loggedInUserId: UserStore.shared.userId, userId: UserStore.shared.userIdToViewProfile, limit: "10", page: String(self.pageNumber), completion: {(result) in
SVProgressHUD.dismiss()
self.totalPages = result.totalPages!
if self.listData?.count == 0 || (self.pageNumber as AnyObject) as! Int == (1 as AnyObject) as! Int{
self.listData = result.userdata?.newPostData
} else {
self.listData = self.listData! + (result.userdata?.newPostData)!
}
self.tableView.reloadData()
})
}
The code you mentioned in the comments adds 10 rows to the datasource (self.listData) but you are only calling insertRows with one row.
You could loop through them, adding an item to the array and adding a row each time:
func callService() {
ProfileApiStore.shared.requestToGetProfile(loggedInUserId: UserStore.shared.userId, userId: UserStore.shared.userIdToViewProfile, limit: "10", page: String(self.pageNumber), completion: {(result) in
SVProgressHUD.dismiss()
self.totalPages = result.totalPages!
let newItems = result.userdata?.newPostData
tableView.beingUpdates()
for item in newItems {
self.listData.append(item)
let indexPath = IndexPath(row:(self.listData!.count-1), section:0) // shouldn't this be just self.listData!.count as your adding a new row?
tableView.insertRows(at: [indexPath], with: .left)
}
tableView.endUpdates()
})
}
I'm currently working with Xcode 10 and Swift 4.2 and I was trying to search to my list of contacts using a SearchBar, all done except that the SearchBar is returning index out of bounds. Here is my code:
#IBOutlet weak var searchBar: UISearchBar!
var contactList: [CNContact]!
var inSearchMode = false
var filteredData = [CNContact]()
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
searchBar.returnKeyType = UIReturnKeyType.done
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let store = CNContactStore()
store.requestAccess(for: .contacts, completionHandler: { (success, error) in
if success {
let keys = CNContactViewController.descriptorForRequiredKeys()
let request = CNContactFetchRequest(keysToFetch: [keys])
do {
self.contactList = []
try store.enumerateContacts(with: request, usingBlock: { (contact, status) in
self.contactList.append(contact)
})
} catch {
print("Error")
}
OperationQueue.main.addOperation({
self.tableView.reloadData()
})
}
})
}
Last night I made search func here on stack and all is fine except for that error that throws when running the app. How can I solve it?
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if contactList != nil {
return contactList.count
}
if inSearchMode {
return filteredData.count
}
return 0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath)
let contact: CNContact!
contact = contactList[indexPath.row]
cell.textLabel?.text = "\(contact.givenName) \(contact.familyName)"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let contact = contactList[indexPath.row]
let controller = CNContactViewController(for: contact)
navigationController?.pushViewController(controller, animated: true)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
inSearchMode = false
view.endEditing(true)
tableView.reloadData()
} else{
inSearchMode = true
filteredData = contactList.filter {
$0.givenName.range(of: searchBar.text!, options: [.caseInsensitive, .diacriticInsensitive ]) != nil ||
$0.familyName.range(of: searchBar.text!, options: [.caseInsensitive, .diacriticInsensitive ]) != nil
}
tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath)
let contact: CNContact!
if inSearchMode {
contact = filteredData[indexPath.row]
} else {
contact = contactList[indexPath.row]
}
cell.textLabel?.text = "\(contact.givenName) \(contact.familyName)"
return cell
}
you should check if you are in search mode or not in cellForRow to fetch correctly
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if inSearchMode {
return filteredData.count
}
if contactList != nil {
return contactList.count
}
return 0
}
when you call reloadData() -numberOfRows -CellForRow are recalling also