Pull to refresh jumps while dragging - ios

I've used this tutorial to implement pull to refresh in tableview
https://cocoacasts.com/how-to-add-pull-to-refresh-to-a-table-view-or-collection-view
Cells have dynamic height in my table.
When I pull to refresh and drag down (before I can release), table jumps. I've been reading various solutions and tried few but I couldn't fix this issue. Is there any definite solution for this problem?
I've tried following solutions:
1) This one doesn't work at all
var cellHeights: [IndexPath : CGFloat] = [:]
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights[indexPath] = cell.frame.size.height
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return cellHeights[indexPath] ?? 70.0
}
2) https://stackoverflow.com/a/31417756/12114641
This solution seems to work however table jumps when dragging is released, it should be smooth instead of jump.
3) Following also doesn't work
UIView.animate(withDuration: 0.5) {
self.refreshControl.endRefreshing()
}

Related

How To Update All The Visible UITableViewCell Every Time A New Row Appears

I want the UITableView's refreshing to be triggered by a new cell's appearing.
Simple setup. UITableView, dataSource is [Double]. Each row in the table will present one number in the array.
The special thing I wanna do is that I want the table to label the cell with max number of all visible cells. Then each of the other cells will calculate the difference from the max number onscreen. And this should update every time a new cell appears.
import UIKit
class ViewController: UIViewController {
var max = 0.0
var data:[Double] = [13,32,43,56,91,42,26,17,63,41,73,54,26,87,64,33,26,51,99,85,57,43,30,33,20]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .red
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.reloadData()
}
private func calculate() {
// calculate the max of visible cells
let indexes = tableView.indexPathsForVisibleRows!
max = indexes
.map { data[$0.row] }
.reduce(0) { Swift.max($0,$1) }
// trying to update all the visible cells.
// tableView.reloadRows(at: indexes, with: .none) ****
// tableView.reloadData() ****
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "default")!
cell.textLabel?.text = "\(data[indexPath.row]) : \(max)"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 64
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
calculate()
}
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
calculate()
}
}
What I Have Done
There are 2 lines of code with marks of **** at the end.
If I uncomment tableView.reloadRows(at: indexes, with: .none), Xcode produces error: 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndexedSubscript:]: index 15 beyond bounds [0 .. 13]'. I don't really understand why, but I know onscreen visible cells are at most 14 in order to fit in the screen, but in table loading phase, the simulator thinks there were 19 visible cells at some point
If instead, I uncomment tableView.reloadData(), this lines of code will trigger willDisplay func which will again call calculate func which has reloadData() in it. It was a infinite recursive cycle, and no row successfully displayed onscreen.
If instead, I don't uncomment anything, the table will not update cells that are already onscreen. Only newly appearing cells will correctly display the effect that I want.
Thank you for reading all this and trying to offer help.
You don't want to call reloadCells since that will trigger willDisplay and then you get an infinite loop.
Rather, you can access the visible cell and update it directly by calling cellForRow(at:) for the visible cells.
private func updateVisibleCells() {
for indexPath in self.tableView.indexPathsForVisibleRows ?? [] {
if let cell = self.tableView.cellForRow(at: indexPath) {
cell.textLabel?.text = "\(data[indexPath.row]) : \(max)"
}
}
}
Now, if you call updateVisibleCells straight after you call calculate() in willDisplayCell you will get a message in the console:
Attempted to call -cellForRowAtIndexPath: on the table view while it was in the process of updating its visible cells, which is not allowed.
This will cause a crash in a release build.
To work around this we can defer the call to updateVisibleCells by dispatching it asynchronously so that it is called after the table view update is complete:
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.calculate()
DispatchQueue.main.async {
self.updateVisibleCells()
}
}
You want to use didEndDisplaying rather than willDisplay so that the rows are updated correctly when rows scroll out of view. You don't need anything in willDisplay

Customizing the new table after using UISearchbar and filtering

I am using a UISearchBar to search through my table. My problem is, as soon as I search for something, the UISearchBar creates a new tableview as overlay with the search results and this new table is basically without any properties (no background, separator insets, everything white etc.). How can I customize this new table view (make it look like the previous one before). I don't find any function where I can access it to give it properties like e.g. background color or so.
Is there a function I can use for customizing the view? I am using this function that at least the height for the cells are the same:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
The following screenshots describe my problem best.
Update the tableView's background view as follow,
tableView.backgroundColor = UIColor.red()
or
tableView.backgroundView = UIView()
tableView.backgroundView.backgroundColor = UIColor.red()
Alright found a solution after some hours of trying. The following two functions helped me to also customize the cells of the new table view which is put over my old one. I didn't know about the willDisplay function, but it works like it should:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if(searchActive){
cell.backgroundColor = UIColor.clear
tableView.backgroundView = UIImageView(image: UIImage(named: "darkBackground"))
tableView.separatorStyle = .none
}
}

UITableView reload animation doesn't work properly

I have a table view which reloads it data with animation on button tap. It was working great until there was one label with text.
I use this code to reload data with animation for one button (I have only one section):
tableView.reloadSections(IndexSet(integersIn: 0..<tableView.numberOfSections), with: UITableViewRowAnimation.right)
and this for another button:
tableView.reloadSections(IndexSet(integersIn: 0..<tableView.numberOfSections), with: UITableViewRowAnimation.left)
And on the top of table view it works okay, but in the middle or in the end it scrolling.
Link for gif - https://giphy.com/gifs/l0DAHUyzm7BMGnKrm/html5
Then I added this code for reloading with animation:
let offset = tableView.contentOffset
tableView.beginUpdates()
tableView.reloadSections(IndexSet(integersIn: 0..<tableView.numberOfSections), with: UITableViewRowAnimation.left)
tableView.endUpdates()
tableView.layer.removeAllAnimations()
tableView.setContentOffset(offset, animated: false)
Same code for .right animation. It fixed scrolling, but added another issue. Again on top of table view it works okay, but then... Watch gif please.
Link for gif - https://giphy.com/gifs/xT1R9Gfaa2po6dMf2U/html5
I'm using test data to fill table view, not fetching or something else.
Hope for help, thanks
EDIT:
I found that if I set standard cell height from code animation works nice, only in this case it works:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 44.0
}
I found the problem. The problem was in cell height, animation was starting for estimated cell height in 44.0 and my cell height is 117.0. So this code fixed my problem:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 117.0
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 117.0
}

TableViewCell Auto height issue

I am writing an iOS app.
User types in movie search keywords and it fetches data from omdbapi.com and displays movies in tableview.
Issue is that that table view cell isn't getting displayed correctly.
Project URL:
project on onedrive
I tried using every autolayout method but, its not working.
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension;
}
You need to set estimate method for table height too. so place these method for height calculation and also check your cell constraint.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Hope this will help you.

remove the delete button ⛔️ on table rows in edit mode

I am trying to learn Swift, but there is a problem in my project that drives me nuts.
I have a working list of data in a ViewController fed by parse.com. I managed to implement a swipe-feature that reveals buttons for both deleting and editing. That is working fine. Now I want the user to be able to reorder the cells. So I successfully implemented a button to put the table into editing-mode. My 2 problems with that are:
When I enter edit-mode I just want to be able to reorder the cells since editing and deleting is done via swipe (via "tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath)". How can I achive that the user doesn't see the 2 buttons for deleting and editing when in editing-mode and touching the delete-circle that is provided automatically?
Is it possible to remove the delete-circle altogether? Using "UITableViewCellEditingStyle.None" also disables the swipe-functionality.
Thanks in advance!
To avoid the round red delete button that appears when you put set UITableView isEditing to true at the left and does nothing when you click it, the minimum that worked for me was this (Swift 4, iOS 11)
// Avoid the round red delete button on the left of the cell:
func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
I also have these functions, which probably interact:
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return savedTempTable.isEditing
}
// Including this function in the delegate enable left-swipe deleting
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath)
{
if editingStyle == .delete {
savedConversions.remove(at: indexPath.row)
}
}
// Including this function enables reordering
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath,to destinationIndexPath: IndexPath)
{
let elem = savedConversions.remove(at: sourceIndexPath.row)
savedConversions.insert(elem, at: destinationIndexPath.row)
}
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
Although people can delete a row through swipe the delete-button in editing mode should not be removed. People may not know about the swipe gesture and by removing the delete button (which they already expect in editing mode) the app becomes more difficult to use.
If you really want to remove the delete button then you have to implement the delegate method tableView(_:editingStyleForRowAtIndexPath:). There you can return .None while the screen is in editing mode and .Delete while the screen is not.
To enable reordering you have to implement the data source methods tableView(_:canMoveRowAtIndexPath:) and tableView(_:moveRowAtIndexPath:toIndexPath:).
You follow this way to remove the delete Icon while editing:
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewCellAccessoryNone;
}

Resources