UseDefault Create the issue in the tableView method - ios

I just create the Bus ticket booking app. Now i want to save the search history (City Name and Date) and Display in my tableView. So this is what i coding in my SearchButtonAction.
#IBAction func btnSearchAction(_ sender: Any) {
if txtTo.text != nil && txtFrom.text != nil {
arrTo.append(txtTo.text!)
arrFrom.append(txtTo.text!)
UserDefault.set(arrTo, forKey: "arrTo")
UserDefault.set(arrFrom,forKey: "arrFrom")...
}
}
Now on my historyViewController I just coding like below
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrFrom.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RecentCell") as! RecentCell
cell.selectionStyle = .none
cell.lblFrom.text = arrFrom[indexPath.row]
cell.lblTo.text = arrTo[indexPath.row]
tblSearch.reloadData()
return cell
}
So it's Call only numberOfRowsInSection Method again and again Even arr count is more than one. What is the correct way to save two textField text in UserDefaults.

Remove reloading datas of tblSearch from cellForRowAt TableView data source method

When you reload tableview inside cellForRowAt method, tableview will go into looping. So you tableview won't display. So please remove tblSearch.reloadData() this line from cellForRow methhod.
please check screenshot, so you will get the better idea about looping.
http://prntscr.com/lte2mo

Related

how to move a table view cell from one table view to a new table view

Currently, I am trying to move a tableview cell from one table view to another. I can't seem to get the proper mechanics down and need help with this task.
Right now I have an array that is not filled with any goals for my progress table view cells.
var goals: [String] = []
Here is the setup for the rest of this progress table view.
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return goals[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TodayGoalViewCell_1", for: indexPath) as? GoalTableViewCell
cell?.goalLabel.text = goals[indexPath.section][indexPath.row]
cell?.cellDelegate = self
cell?.index = indexPath
return cell!
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section]
}
func numberOfSections(in tableView: UITableView) -> Int {
return goals.count
}
In a separate file I have another table view that is already filled with goals. Here is the code:
var goals = ["goal 1", "goal 2", "goal 3"]
extension GoalsViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Goals.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "GoalConversationsCell_1", for: indexPath)
cell.textLabel?.text = Goals[indexPath.row]
cell.textLabel?.lineBreakMode = NSLineBreakMode.byWordWrapping
cell.textLabel?.numberOfLines = 3
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.section == 0 {
Goals.remove(at: indexPath.row)
Goals.count != 0 {
showGoalSelected()
} else {
Goals.append(contentsOf: theEmptyModel)
}
tableView.reloadData()
}
}
}
I would like to make it so that when a user selects a goal from the table view which already had goals, that these goals are moved to the progress table view. How would I do this?
If there is a button inside your cell and you want to get news when this button is clicked, you need to look at the protocol-delegate pattern.
But you can also try to remove the button inside the cell and use a text label. To use the didSelectRowAt method to catch clicking on cell, tableView in delegete.
I do not know what kind of design and structure you have, I just offer you perspective.

Action when tableview row selection has changed

I have a tableview implemented. The list shows fine, but I want to detect when the row selection has been changed. I am trying to do a certain action when the row selection is changed, for example, print("row selection has changed ")
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "exploreCell", for: indexPath)
cell.textLabel?.text = tableArray[indexPath.row]
return cell
}
I hope the question is clear.
You can observer selection state from this 2 delegate methods. So when you select any cell it will call didSelectRowAt delegate method and if you tap on selected cell again it will call didDeselectRowAt
Make sure to set tableView selection property to multiple selection incase if you want user to select multiple cell
tableView.allowsMultipleSelection = true
Swift Delegate methods.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//Your code here
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
//Your code here
}

Cells in table view not responding

I am working on a todo list app and whenever I run it on the simulator and try to print the items in my array, the other cells item get printed.
Here's my code:
import UIKit
class TodoListViewController: UITableViewController {
let itemArray = ["math","english"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
// DATASOURCE METHODS
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ToDoItemCell", for: indexPath)
cell.textLabel?.text = itemArray[indexPath.row]
return cell
}
// DELEGATE METHODS
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
print(itemArray[indexPath.row])
}
}
You need didSelectRowAt not didDeselectRowAt , the latter is triggered when you select a row so you get the print from the previous selected row
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(itemArray[indexPath.row])
}

Is it better to hide a tableviewcell or filter in data source? (performance issue)

I have a UITableViewController where I have cells that I want to hide.
What I'm currently doing is hiding the cells with heightForRowAt returning 0 and cellForRowAt returning a cell with isHidden = false. But since I am using this solution, I noticed the app was slower when I'm scrolling in my tableView.
// Currently returning a height of 0 for hidden cells
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let post = timeline?.postObjects?[indexPath.row], post.hidden ?? false {
return 0.0
}
return UITableView.automaticDimension
}
// And a cell with cell.isHidden = false (corresponding to identifier "hiddenCell")
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let post = timeline?.postObjects?[indexPath.row] {
if post.hidden ?? false {
return tableView.dequeueReusableCell(withIdentifier: "hiddenCell", for: indexPath)
} else {
return (tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell).with(post: post, timelineController: self, darkMode: isDarkMode())
}
}
}
I was thinking about why not apply a filter on the array to totally remove hidden cells of the tableView, but I don't know if filtering them each time is great for performances...
// Returning only the number of visible cells
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return timeline?.postObjects?.filter{!($0.hidden ?? false)}.count
}
// And creating cells for only visible rows
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let post = timeline?.postObjects?.filter{!($0.hidden ?? false)}[indexPath.row] {
return (tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell).with(post: post, timelineController: self, darkMode: isDarkMode())
}
}
What is the best option? Hiding cells when generating them (first) or exclude them of the list (second)?
I would recommend to let the table view data source methods to deal with a filtered version of timeline. However, do not do this in cellForRowAt method because we need to do it one time but not for each cell drawing.
So, what you could do is to declare filteredTimeline and do the filter one time in the viewDidLoad method (for instance):
class TableViewController: UIViewController {
// ...
var filteredTimeline // as the same type of `timeline`
override func viewDidLoad() {
// ...
filteredTimeline = timeline?.postObjects?.filter{!($0.hidden ?? false)}
// ...
}
// Returning only the number of visible cells
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredTimeline.count ?? 0
}
// And creating cells for only visible rows
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let post = filteredTimeline?.postObjects?.filter{!($0.hidden ?? false)}[indexPath.row] {
return (tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell).with(post: post, timelineController: self, darkMode: isDarkMode())
}
}
// ...
}
In case of there is a better place to filteredTimeline = timeline?.postObjects?.filter{!($0.hidden ?? false)} rather than viewDidLoad, you might need to call tableView.reloadData().
An alternative you could do:
if you think that you don't need the original timeline you could filter it itself:
timeline = timeline?.postObjects?.filter{!($0.hidden ?? false)}
tableView.reloadData()
and you will not need an extra filtered array.
Extra tip:
In case of returning 0.0 value in heightForRowAt method for a certain row, cellForRowAt will not even get called; For example:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return indexPath.row == 0 ?? 0.0 : 100.0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// ...
}
At this point, cellForRowAt should get called only one time because the height for the first row is 0.0.
There is no point of having cells with a size of 0. Your best bet is to filter your datasource, but my suggestion would be to keep two arrays at the same time.
But handle the filtering elsewhere then in the numberOfRowsInSection.
var filteredObjects = []
func filterObjects() {
filteredObjects = timeline?.postObjects?.filter{!($0.hidden ?? false)}
}
// Returning only the number of visible cells
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredObjects.count
}
// And creating cells for only visible rows
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let post = filteredObjects[indexPath.row] {
return (tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! PostTableViewCell).with(post: post, timelineController: self, darkMode: isDarkMode())
}
}
I don't know how you handle the filtering, but whenever you want to apply your filter you simply
filterObjects()
tableView.reloadData()

[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray

I am getting JSON data from an API and display it in a tableView. When user scrolls at the last row, the next page will be added to the current data.
But I am getting this error when next page data is added.
[__NSArray0 objectAtIndex:]: index 0 beyond bounds for empty NSArray
func loadUser(_ currentPage:Int=1){
APIService.loadUser(currentPage, size: 100, callback: { data in
if let data = data["user"].arrayValue as [JSON]?{
self.jsonData?.append(contentsOf: data
self.tableView.reloadData()
self.hideLoadingInView(view: self.view)
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.jsonData?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell") as! UserTableViewCell
cell.user = self.jsonData?[indexPath.row]
if !isLoading && (indexPath.row == self.jsonData!.count - 1){
currentPage += 1
loadUser(currentPage)
}
return cell
}
I would suggest few potential fixes:
Add Exception Breakpoint to your project, so you can better debug your particular issue. It's very useful tip: https://www.natashatherobot.com/xcode-debugging-trick/ You'll probably find out what's wrong with this.
Move the UI manipulation inside the API call response into the DispatchQueue.main.async { ... } block, to be sure the UI is manipulated on the main thread. Otherwise it can cause weird behavior and maybe also your issue.
Move the check if you need to fetch another page from API to the tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) function. Some people do recommend to do it in scrollViewDidScroll delegated method of UIScrollView.
Then it will look like this.
func loadUser(_ currentPage:Int=1){
APIService.loadUser(currentPage, size: 100, callback: { data in
if let data = data["user"].arrayValue as [JSON]?{
DispatchQueue.main.async {
self.jsonData?.append(contentsOf: data)
self.tableView.reloadData()
self.hideLoadingInView(view: self.view)
}
}
})
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.jsonData?.count ?? 0
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if !isLoading && (indexPath.row == self.jsonData!.count - 1){
currentPage += 1
loadUser(currentPage)
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UserCell") as! UserTableViewCell
cell.user = self.jsonData?[indexPath.row]
return cell
}

Resources