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.
Related
I have a UIViewController with a collectionView inside it. The layout may seem confusing but I am making use of horizontal scrolling UICollectionViewCells, presented in 3 sections.
The main collection view is made up of 3 sections. I access 3 different UICollectionViewCell classes for each section becuase it is fetching different data for each one. Once the data is fetched, it dequeues another Cell class, which is the same one accessed for all the sections.
There are 2 buttons in each cell. When I press the button, a value changes within the database. When I re-run the program, the cell in which the button was pressed has moved to its corresponding section that it should be in as per the change. However, I want to achieve this as soon as I press the button. How can I move the cell from one section to another, baring in mind that they the cells presented are inside a collectionview cell making up the sections, which is then inside a greater UICollectionView.?
I've tried researching how to do this but no luck, and everyone seems to be using a drag and drop method which seems too complicated for what I am trying to achieve. Would appreciate any help.
Thanks
The approach I followed to achieve the above situation is using a custom Delegation when cell is removing from collectionviews. A container CollectionView has two sections with 2 different cells containing two different Horizontal UICollectionViews. These two collectionview's cells have UIButtons. When tap on one collectionView's button, that particular cell is removed, custom Delegate method calls and another CollectionView's cell is populated. This is the link of the project I have created for this particular scenario. For better understanding, here is a GIF for demonstration.
I have a UICollectionView, and I override setSelected in the UICollectionViewCell subclass to perform a little bounce animation when the user selects the cell.
The problem is that on selection of the cell, the very selection of the cell alters the data for the other cells (for instance, imagine the other cells have a label in each one that has the amount of cells selected displayed). So I call collectionView.reloadData() so that all the other cells update as well to show the new data (for instance, the amount of cells selected in a label).
However, this reloadData() call seems to reset the UICollectionView completely. Any animations taking place are stopped and the cells are simply updated without animation.
How do I have the cell selection animation, and update the other cells at the same time?
I've tried calling reloadData() only after the animation has completed, but this makes it look like there's lag/delay in updating the other cells (as it doesn't update them until a moment after when the animation finishes), which doesn't look very good.
Similarly, I've tried calling reloadItemsAtIndexPaths: (and exclude the selected cell that will animate), but for whatever reason with collection views this method seems really slow. Like there's a noticeable amount of lag after pressing it.
What should I be doing here? What's the standard for reloading data without the collection view destroying in-progress animations?
Reloading throws away the existing cells and creates (or reuses) new ones. So any animation is inherently destroyed. So, don't reload.
Instead, update your data model as usual and then get the currently visible index paths and associated cells and directly update those cells.
I am using a UICollectionView in combination with a NSFetchedResultsController. When I fetch new data, I throw them in a SQLite database and my frc gets a notification that it will update. I implement the appropriate delegate methods and insert the new data in a batch update and I don't call reloadData afterwards. But as soon as the data is inserted, the collectionview scrolls to the top (to make the new items visible?). I would like to interfere this process and imitate the Facebook/9Gag implementation. When there is new data, a button is faded in which lets the user scroll to the top. At the time the button is shown, the new items are already inserted, so the user can also just scroll up himself. I tried the following:
hooking into the UIScrollViewDelegate methods to see if I could stop the collectionView from scrolling (none of the methods is called during this update process)
show the "scroll to show more"-button when the frc willUpdate and just insert the items when the button is pressed. The problem is that the user cannot scroll up himself
Before inserting the items, I calculate the indexPath of currently visible items AFTER the insertion (adding the number of inserted items) and scroll to that index without animation in the completion block of the batch update. This only works if I delay the scrolling, but then it flickers, because it's first scrolling up a little
I experimented the same problem, by calling "insertItemAtIndexPaths" to increment data into my collectionView.
I noticed two things :
- If you add items AFTER the current visible index path, there's no automatic scroll
- If you add items BEFORE the current visible index path, there's no automatic scroll, like you said, but the contentOffset is kept at the same position, but with different contentSize, because you've just added new items.
To deal with this "side effect", I used the solution mentioned here : UICollectionView insert cells above maintaining position (like Messages.app)
As it's explained in the following post, the "CATransaction.setDisableAction(false)" is the key thing to update smoothly the contentOffset position.
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 am using a UITableView to display the results of a series of calculations. When the user hits 'calculate', I add the latest result to the screen. When I add a new cell, the UITableViewCell object is added to an array (which is indexed by tableView:cellForRowAtIndexPath:), and then I use the following code to add this new row to what is displayed on the screen:
[thisView beginUpdates];
[thisView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation: UITableViewRowAnimationFade];
[thisView endUpdates];
This results in the new cell being displayed. However, I then want to immediately scroll the screen down so that the new cell is the lowermost cell on-screen. I use the following code:
[thisView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
This almost works great. However, the first time a cell is added and scrolled to, it appears onscreen only briefly before vanishing. The view scrolls down to the correct place, but the cell is not there. Scrolling the view by hand until this invisible new cell's position is offscreen, then back again, causes the cell to appear - after which it behaves normally. This only happens the first time a cell is added; subsequent cells don't have this problem. It also happens regardless of the combination of scrollToRowAtIndexPath and insertRowsAtIndexPath animation settings.
EDIT:
I've now started inserting cells at the second-to-last position of the table, rather than the end, and the problem still occurs - when first inserted, a cell is 'invisible' until it goes offscreen and comes back on again. What could be causing this, and how can I force the cell to be drawn as soon as it is added to the table?
You're having problems because your updating the table without updating the data model backing it. Tables don't actually know how many rows they have nor what cells to display. They depend on the datasource and the delegate to tell them these things. Your design expects the table itself to track them.
insertRowsAtIndexPaths: is intended to be used for moving existing rows around a table, not for adding entirely new logical rows. When you insert an entirely new cell, the tableview looses track of how many rows it actually has.
Before you display a new row, the first thing you should do is update the values returned by:
– numberOfSectionsInTableView:
– tableView:numberOfRowsInSection:
... to reflect the addition of the new rows. This will allow the table to understand how big it is.
Then you need to update cellForRowAtIndexPath: to return the correct cell for the added row. Then you need to reload the table.
After you've done that, you should be able to scroll the tableview to the end and have the cell display properly.
The important thing to remember about tables is that they are dumb. The table itself holds no data, doesn't know how many sections and rows it has or what order the rows and sections come in. All the logic about data, sections, rows, cells and cell contents comes from the datasource and/or the delegate. When you want to change a table, you actually change the datasource and/or the delegate and then the table will reflect those changes automatically.
Edit:
Upon rereading the parent, I see that your putting the actual UITableViewCell objects in your data array and that you have one cell for each row.
This is not how tableviews are supposed to work and this will not scale beyond a few dozen rows at most.
Tableviews are intended to be an illusion that allows you display a lOGICAL table which has an arbitrary high number or rows. To that end, it only keeps enough UITableViewCell objects alive to cover the visually displayed area in the UI. With a default cell height of 44 pixels this means a tableview will never have more than 9 cell objects at a time.
Instead of eating memory holding cells that are not displayed, the tableview lets the delegate dequeue a cell that has scrolled off screen, repopulate it with the data of another LOGICAL row and then display it in a new position. This is done in cellForRowAtIndexPath:
You really need to start over here with your design. Your data needs to be kept separate from the user interface objects. You don't want to have more cells alive at anyone time than absolutely necessary because your memory use will balloon and your response time will degrade. Your current problem is the result of this unusual design.
When you've done that, you can add the result row as outlined above.
Try to scroll with some time shift after cell update via NSTimer or performSelector:withDelay:. It can help but to fix all problems I think there need to do more work.
The glitches may be caused because a UITableView considers itself the owner of any UITableViewCell instances it is displaying, and reuses them as needed. Part of that process is calling prepareForReuse on the cell. Since you are keeping the cells in an array, you do not want them reused. Try implementing an empty prepareForReuse in your UITableViewCell class. Or just create cells dynamically in tableView:cellForRowAtIndexPath: as apple recommends.
I used what Skie suggested to avoid the problem in the following way:
Immediately after adding the row:
[self performSelector:#selector(scrollToDesiredArea:) withObject:newIndexPath afterDelay:0.4f];
This called the following:
-(void)scrollToDesiredArea:(NSIndexPath *)indexPath{
UITableView *thisView = (UITableView*)self.view;
[thisView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
The delay of 0.4s seems to be sufficient to avoid the glitching; any less and it still happens. It may have to be different on varying models of iPhone hardware, though - I only tested on emulator.