On iOS, when you append something to the beginning of UITableView's data source, the cell will "animate in" and push the rest of the cells down only if the user is already scrolled to the top. When the user is scrolled down farther down the list and a new item gets appended to the top, iOS does not automatically move the cells down.
When a new cell comes in from the top, how do I determine between the two?
My use case is that when the user has scrolled down past the iOS point, I can display a "new item above" button for the user to click . (similar to Twitter)
I'm using dynamic height for cells, so hard coding the scrollView by pixel isn't the most ideal way. I'd like to utilize iOS's way to determine it.
There is an array available of all visible cells' index paths called indexPathsForVisibleRows. You can access that property and check to see whether or not your first row is one of them, and go from there. You can also access visibleCells property to get the actual UITableViewCells themselves.
How to determine whether the user has scrolled past the first cell: a UITableView is a subclass of UIScrollView, so you can just look at the contentOffset property.
I'm not exactly sure that that's enough to accomplish what you want though. You could also maybe take advantage of the fact that tableView's cellForRowAtIndexPath: method only returns a cell if it's visible, and request the first cell. I've never tried implementing something like this before though so there might be a better approach.
Related
This is a very rare occurring bug from hell,
I have an infinite scroll controller that displays products, 2 in each row. Rarely, something affects the controller and causes items to vanish, when I tap the empty area where the item should be, it works as expected and directs the user to the item details controller. When I back out back to the list, sometimes the cell shows its content, and others get hidden.
Sometimes it just a couple of items missing, sometimes there are so many missing items that makes the list appear empty, like only 1 or 2 cells are visible per screen height.
An even stranger situation is, when I scroll really fast to the end and stretch the screen really fast out of the visible area, and there are no more items to load, the visible items can jump from left to right.
Please see these two videos.
https://www.dropbox.com/s/jibflcouz1ena8n/missingProductImages.mov?dl=0
https://www.dropbox.com/s/uz13fzorypnp38t/again.mov?dl=0
I could send code but I didn't want to clutter this place with full length code, let me know if you want to see a specific section of the code please. Maybe someone could have an idea of what might be going on by looking at the vids.
What's probably going on here is a state problem with your collection view cells. The code assigning model values to the cell's views would be of use here. But absent any actual information, the first thing to review would be the cell's prepareForReuse implementation:
Does it call super?
Does it clear out all current values?
Does it cancel any pending asynchronous operations?
Are fetches and cancellations correctly matched?
Next, check if there's any essential configuration in the cell's init/initWithCoder methods -- those are only called on first creation, not on reuse.
Those are the normal pitfalls of UICollectionView cell handling. If they don't suggest the problem, please post the cell code.
It looks like your cells are not being reused correctly.
Can you check that you have set the same reuseIdentifier for your cell in Interface Builder that you are assigning in your code?
Update: Attached image to show where to set the identifier in the storyboard/xib
Update: added layout solution
Another problem could be due to the layout bounds of your collectionViewCell. When you load your cells they bounds are not calculated until they have been added to your collectionView and rendered. This can cause the elements in your cell to layout with the wrong values. This happens commonly with async image frameworks as they cache a resized version of the image for performance. When you cell loads, the cached image is loaded at the wrong the wrong size.
Add the following code to your collectionViewCell:
- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
self.contentView.frame = bounds;
[self setHighlighted:NO];
}
I'm relatively new to Swift and iOS and I have one issue - I have a custom SwipeCell class that extends UITableViewCell. It's a cell that can be swiped left and right, and has buttons on each side. For some reason if I made the buttons out of the frame of the cell when user swipes the cell they would appear but could not call an action, so my solution was to make cell wider than it is (so buttons can fit in it). Because it was done this way my cell has to have an offset by default for the total width of the buttons of the left (let's say 100), so it's position is X:-100.
And that's fine, and everything works fines with the cells, however there is one huge issue - if I call the deletion of any cell from the tableView like this
tableView.deleteRowsAtIndexPaths([tableView.indexPathForCell(activeCell)!], withRowAnimation: .None)
tableView then deletes the cell, and all of the other cells that are currently visible (doesn't happen with cells above or below the screen bounds) get moved to X:0 instead of staying at X:-100, so I assume that deleteRowsAtIndexPaths calls some function that resets the visible cells positions to 0,0. I'm currently setting swipe cells positions with layoutSubviews() since the number of buttons is dynamic and couldn't be determined upfront, but layoutSubviews is not the function where the bug happens.
So to sum up question - what function does deleteRowsAtIndexPaths call after deleting a cell that resets/redraws the visible cells?
deleteRowsAtIndexPaths deletes a row(s) from the tableView. This is handled internally by iOS. From Apple documentation:
Deletes the rows specified by an array of index paths, with an option
to animate the deletion.
Another interesting thing to note here is that this method does not modify your model (object that holds data used by your table view cells to render on themselves). You have to do that yourself. The cells are deleted, but if you call reloadData without deleting the row from your Model, cell will reappear.
Expect that cells get deleted and created all the time. Cells are very, very temporary objects. Write the code to create one when needed, and don't make any assumptions. Don't assume the cell is there later, don't assume it's in the same row, don't assume it displays the same data (because cells are recycled).
Since I could find out what happens inside of deleteRowAtIndexPaths, and why it changes frames of each Cell, I decided to just do my own function that deletes a row, by obtaining cells with tableView.visibleCells, and then just moving cells bellow the deleted cell up by a height of deleted cell. Thank you all for trying to help me, and especially thanks to Abhinav who told me that it was handled internally, which help me decide to write a custom code for the deletion.
I am getting really frustrated trying to solve this problem i tried implementing it in many many ways but no solution. I have a UIStepper in a custom cell and i want to change a value on the cell. Everything works fine expect when i scroll around the tableView the values from the UIStepper changes from one cell to another. Please help here are my screen shoots.
Link to tableview implementation and link to cell implementation
You are trying to store the stepper value inside each individual cell. That's not going to work because cells are reused; the cell that you now see in row 2 may reappear in row 20 when the user scrolls.
That is why you must store the value for the stepper in the model (your data) on a row-by-row basis, so that you can set it freshly and correctly for that row every single time cellForRowAtIndexPath: is called.
This, in turn, means that as the user steps the stepper, its valueChanged is going to need to talk to the table view data source so that the model can be updated ("the stepper value for row 5 has just been changed to 3") and maintained in the model.
I'm new to iOS and MonoTouch, and I have a pretty basic need - I have two vastly different views to display in the same table cell depending on whether it is selected or not. When you load the app I'll present a list of products in a custom UITableView, with all the rows deselected and each row basically just showing the product names in a label. As soon as a user taps one of the rows and selects it, I show a very different view with more of a shopping cart layout. Since the two views are so different they have vastly different sizes. What i need is for the cell itself (or row?) to grow and shrink according to the natural height of whichever view is currently being displayed.
I'm using descendants of UITableViewController, UITableViewSource, and UITableViewCell for the solution. What I do is add both versions to the cell's ContentView, then set the Hidden property as needed when the cell/row is selected/deselected to show the right layout. That works well. I'm in demo mode right now, so I'm not overly worried about efficiency or responsiveness, that will have to come later.
I know that GetHeightForRow is responsible for sizing the rows but it only gets called once, when the cell is first shown. It seems to me that I need to alert the TableView to re-poll the source for a new size as the views are changing, but I can't figure out how to do it.
I tried (rather hopefully) to cause GetHeightForRow to be invoked again manually using Cell.SetNeedsLayout and Cell.SetNeedsDisplay, hoping that would cause the table to re-query the source for new dimensions, but no joy.
I tinkered with the direct approach, trying to size the contentview itself but it didn't seem as if it was leading anywhere. I feel as if the table needs to be told to query for a new row size, but I'm open to any suggestions.
Surely I'm not the first to attempt this? What am I missing?
Try forcing GetHeightForRow to be called again by calling ReloadRows instead.
You may or may not have to specify begin/end updates (this might just be for animation, though?)
tableView.BeginUpdates();
tableView.EndUpdates();
I am using a standard grouped style UITableView that returns a custom header view for one of it's sections (using the tableView's delegate method viewForHeaderInSection:). The view that is returned for the section header contains a control. It displays an image and is a control so it can tapped to let the user change the image. My desired UI is similar to the photo for a contact in the Apple Contacts app (a floating imageView-like control in a grouped style table).
The problem I'd like to solve is that touches on the tableView section header go straight to the control. I'd like the table to get a chance to determine if the touch is actually the beginning of a scroll gesture. If not, then the table can pass the event to the section header view for processing.
This is how all the rows in the table behave since I have "delaysContentTouches" for the table on. I suspect the problem is because the section header view is not in the table's view hierarchy. So everything is probably working per spec. just not the way I want.
I want the section header view to behave regarding touches just like rows do (table gets first chance to process).
BTW I am not placing this control in a table row (which would solve the problem) because I want the rounded rect grouped style for all table rows, but not for this one UI element. This control is in the center of my table (header for section 1) which is why I want drags on it to scroll the table.
OK, so apparently this is simulator issue only. On a device my tableView gets the first chance at the event. So I guess I need to listen to the Apple mantra of "always test on an actual device" before posting to StackOverflow. Sorry friends... may my error be helpful to others who, like me, probably spend too much time in the simulator.