Moving items from one tableView to another tableView with consolidation [closed] - objective-c

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
Let's say I have 2 UITableViews next to eachother on an ipad in landscape-mode.
Now I want to move multiple items from one tableView to the other. They are allowed to be inserted on the bottom of the other tableView. Both have multiSelection activated.
Now the movement itself is no problem with normal cells. But in my program each cell has an object which contains the consolidationState of the cell. There are 4 states a cell can have: Basic, Holding, Parent, Child.
Basic = an ordinary cell.
Holding = a cell which contains multiple childs but which wont be shown in this state.
Parent = a cell which contains multiple childs and are shown directly below this cell.
Child = a cell created by the Parent cell.
The object in each cell also has some array which contains its children. The object also holds a quantityValue, which is displayed on the cell itself.
Now the movement gets tricky. Holding and Parent cells can't move at all. Basic cells can move freely. Child cells can move freely but based on how many Child cells are left in the Parent. The parent will change or be deleted all together.
If a Parent cell has more then 1 Child cell left it will stay a Parent cell.
Else the Parent has no or 1 Child cell left and is useless. It will then be deleted.
The items that are moved will always be of the same state. They will all be Basic cells.
This is how I programmed the movement:
*First I determine which of the tableViews is the sender and which is the receiver.
*Second I ask all indexPathsForSelectedRows and sort them from highest row to lowest.
*Then I build the data to be transferred. This I do by looping through the selectedRows and ask their object from the sender's listOfItems.
*When I saved all the data I need I delete all the items from the sender TableView. This is why I sorted the selectedRows so I can start at the highest indexPath.row and delete without screwing up the other indexPaths.
*When I loop through the selectedRows I check whether I found a cell with state Basic or Child.
*If its a Basic cell I do nothing and just delete the cell. (this works fine with all Basic Cells)
*If its a Child cell I go and check it's Parent cell immidiately. Since all Child cells are directly below the Parent cell and no other the the Parent's Childs are below that Parent I can safely get the path of the selected Childcell and move upwards and find it's Parent cell. When this Parent cell is found (this will always happen, no exceptions) it has to change accordingly.
*The Parent cell will either be deleted or the object inside will have its quantity and children reduced.
*After the Parent cell has changed accordingly the Child cell is deleted similarly like the Basic cells
*After the deletion of the cells the receiver tableView will build new indexPaths so the movedObjects will have a place to go.
*I then insert the objects into the listOfItems of the receiver TableView.
The code works in the following ways:
Only Basic cells are moved.
Basic cells and just 1 child for each parent is moved.
A single Basic/Child cell is moved.
The code doesn't work when:
I select more then 1 or all childs of some parent cell.
The problem happens somewhere into updating the parent cells. I'm staring blindly at the code now so maybe a fresh look will help fix things. Any help will be appreciated.
Here is the method that should do the movement:
-(void)moveSelectedItems
{
UITableView *senderTableView = //retrieves the table with the data here.
UITableView *receiverTableView = //retrieves the table which gets the data here.
NSArray *selectedRows = senderTableView.indexPathsForSelectedRows;
//sort selected rows from lowest indexPath.row to highest
selectedRows = [selectedRows sortedArrayUsingSelector:#selector(compare:)];
//build up target rows (all objects to be moved)
NSMutableArray *targetRows = [[NSMutableArray alloc] init];
for (int i = 0; i<selectedRows.count; i++)
{
NSIndexPath *path = [selectedRows objectAtIndex:i];
[targetRows addObject:[senderTableView.listOfItems objectAtIndex:path.row]];
}
//delete rows at active
for (int i = selectedRows.count-1; i >= 0; i--)
{
NSIndexPath *path = [selectedRows objectAtIndex:i];
//check what item you are deleting. act upon the status. Parent- and HoldingCells cant be selected so only check for basic and childs
MyCellObject *item = [senderTableView.listOfItems objectAtIndex:path.row];
if (item.consolidatedState == ConsolidationTypeChild)
{
for (int j = path.row; j >= 0; j--)
{
MyCellObject *consolidatedItem = [senderTableView.listOfItems objectAtIndex:j];
if (consolidatedItem.consolidatedState == ConsolidationTypeParent)
{
//copy the consolidated item but with 1 less quantity
MyCellObject *newItem = [consolidatedItem copyWithOneLessQuantity]; //creates a copy of the object with 1 less quantity.
if (newItem.quantity > 1)
{
newItem.consolidatedState = ConsolidationTypeParent;
[senderTableView.listOfItems replaceObjectAtIndex:j withObject:newItem];
}
else if (newItem.quantity == 1)
{
newItem.consolidatedState = ConsolidationTypeBasic;
[senderTableView.listOfItems removeObjectAtIndex:j];
MyCellObject *child = [senderTableView.listOfItems objectAtIndex:j+1];
child.consolidatedState = ConsolidationTypeBasic;
[senderTableView.listOfItems replaceObjectAtIndex:j+1 withObject:child];
}
else
{
[senderTableView.listOfItems removeObject:consolidatedItem];
}
[senderTableView reloadData];
}
}
}
[senderTableView.listOfItems removeObjectAtIndex:path.row];
}
[senderTableView deleteRowsAtIndexPaths:selectedRows withRowAnimation:UITableViewRowAnimationTop];
//make new indexpaths for row animation
NSMutableArray *newRows = [[NSMutableArray alloc] init];
for (int i = 0; i < targetRows.count; i++)
{
NSIndexPath *newPath = [NSIndexPath indexPathForRow:i+receiverTableView.listOfItems.count inSection:0];
[newRows addObject:newPath];
DLog(#"%i", i);
//scroll to newest items
[receiverTableView setContentOffset:CGPointMake(0, fmaxf(receiverTableView.contentSize.height - recieverTableView.frame.size.height, 0.0)) animated:YES];
}
//add rows at target
for (int i = 0; i < targetRows.count; i++)
{
MyCellObject *insertedItem = [targetRows objectAtIndex:i];
//all moved items will be brought into the standard (basic) consolidationType
insertedItem.consolidatedState = ConsolidationTypeBasic;
[receiverTableView.ListOfItems insertObject:insertedItem atIndex:receiverTableView.ListOfItems.count];
}
[receiverTableView insertRowsAtIndexPaths:newRows withRowAnimation:UITableViewRowAnimationNone];
}
If anyone has some fresh ideas of why the movement is bugging out let me know. If you feel like you need some extra information I'll be happy to add it.
Again the problem is in the movement of ChildCells and updating the ParentCells properly.
I could use some fresh looks and outsider ideas on this.
Thanks in advance.
*updated based on comments
With this code there were 2 Bugs left unsolved:
Bug1: selecting them all, then moving them leaves a Child cell
Bug2: when Parent-Child-Child are the last 3 items in the list and then you try to select them all and move them it crashes.
Again single item movement worked perfect at this time.
Just when you thought it was over a single bug remains:
When you move all but a single ChildCell from a ParentCell the ChildCell wont get changed back into the BasicCell. This is an important aspect that needs to be added since it might screw up a second movement from the table.
When you have a situation as follows:
P-C-C-C-C-P-C-C
And you select(marked with x) like this:
P-C-C-C-C-P-C-Cx
It acts like this after the movement:
P-C-C-C-C-C
While expecting:
P-C-C-C-C-B
Which will screw up the movement the next time you make it.
The Parent here has a quantity of 4. But there are 5 childcells behind it.
When you are selecting the cells like this now:
P-Cx-C-Cx-C-Cx
while expected:
P-Cx-C-Cx-C-Bx
The parent shouldn't be deleted since only 2 of its 4 children are selected. But thanks to the previous movement and the code as it is now he will think 3 of its children are removed and will return this:
C-C
While expected:
P-C-C
Replacing the item in the datasource proved to be a horrendous task. It just didn't seem to stick.
This last bug was solved now as well.
The bug was caused by miscalculating the index of the child. Since the datasource hadn't updated itself yet I needed to check which index of the child in the Parent-Child structure was still left and add it to the j instead of just the +1
This piece I added after the newItem.quantity == 1 check
//replace the one child that is left and make it basic
int x;
for (x = 1; x<consolidatedItem.quantity; x++)
{
MyCell *cell = (MyCell *)[sender cellForRowAtIndexPath:[NSIndexPath indexPathForRow:x+j inSection:0]];
if (![cell isSelected])
{
break;
}
}
MyCellItem *lastChild = [sender objectAtIndex:j+x];
MyCellItem *newChild = [lastChild copyToConsolidatedStateBasic];
[sender.listOfItems replaceObjectAtIndex:j+x withObject:newChild];

Update: There were in fact lots of problems with this code. Principally the values stored in the NSIndexPaths of the selectedRows array were becoming out of date once a parent row had been deleted from the main array.
Here's the tested and working code (after working with OP)
for (int i = selectedRows.count-1; i >= 0; i--)
{
NSIndexPath *path = [selectedRows objectAtIndex:i];
//check what item you are deleting. act upon the status. Parent- and HoldingCells cant be selected, so the only check that will be made are on child and basic cells
MyCellObject *item = [sender.listOfItems objectAtIndex:path.row];
if (item.consolidatedState == ConsolidationTypeChild)
{
for (int j = path.row; j >= 0; j--)
{
MyCellObject *consolidatedItem = [sender.listOfItems objectAtIndex:j];
if (consolidatedItem.consolidatedState == ConsolidationTypeParent)
{
//copy the consolidated item but with 1 less quantity
MyCellObject *newItem = [consolidatedItem copyWithOneLessQuantity];
if (newItem.quantity > 1)
{
newItem.consolidatedState = ConsolidationTypeParent;
[sender.listOfItems replaceObjectAtIndex:j withObject:newItem];
}
else if (newItem.quantity == 1)
{
MyCellObject *child = [sender.listOfItems objectAtIndex:j+1];
child.consolidatedState = ConsolidationTypeBasic;
[sender.listOfItems replaceObjectAtIndex:j+1 withObject:child];
}
if (newItem.quantity <= 1) {
[sender.listOfItems removeObjectAtIndex:j];
// Update indexPath row values for selected items that are above the removed row
for (int k = i; k >= 0; k--)
{
NSIndexPath *aPath = [selectedRows objectAtIndex:k];
if (aPath.row >= j)
{
[selectedRows replaceObjectAtIndex:k withObject:[NSIndexPath indexPathForRow:(aPath.row -1) inSection:0]];
}
else
{
break;
}
}
path = [NSIndexPath indexPathForRow:(path.row -1) inSection:0]
}
}
}
}
[sender.listOfItems removeObjectAtIndex:path.row];

Related

UICollectionView Very Slow Custom Layout

I am using a UICollectionView with a custom layout that lays out cells in a grid format. There can be well over 50 rows and 50 columns. Scrolling occurs both vertically and horizontally. Currently, I am doing all of the layout setup in prepareLayout and storing it in arrays:
- (void)prepareLayout {
NSMutableArray *newLayoutInfo = [[NSMutableArray alloc] init];
NSMutableArray *newLinearLayoutInfor = [[NSMutableArray alloc] init];
NSInteger sectionCount = [self.collectionView numberOfSections];
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0];
self.heightForRows = [delegate collectionViewHeightForAllRows];
self.totalWidthsForRows = [[NSMutableArray alloc] init];
for (int i = 0; i < sectionCount; i++) {
[self.totalWidthsForRows addObject:[NSNumber numberWithInt:0]];
}
for (NSInteger section = 0; section < sectionCount; section++) {
NSMutableArray *cellLayoutInfo = [[NSMutableArray alloc] init];
NSInteger itemCount = [self.collectionView numberOfItemsInSection:section];
for (NSInteger item = 0; item < itemCount; item++) {
indexPath = [NSIndexPath indexPathForItem:item inSection:section];
UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
itemAttributes.frame = [self frameForCellAtIndexPath:indexPath];
[cellLayoutInfo addObject:itemAttributes];
[newLinearLayoutInfor addObject:itemAttributes];
}
[newLayoutInfo addObject:cellLayoutInfo];
}
self.layoutInfo = newLayoutInfo;
self.linearLayoutInfo = newLinearLayoutInfor;
}
Then in layoutAttributesForElementsInRect I have:
- (NSArray*)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray *rows = [self.linearLayoutInfo filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(UICollectionViewLayoutAttributes *evaluatedObject, NSDictionary *bindings) {
return CGRectIntersectsRect(rect, [evaluatedObject frame]);
}]];
This works okay, but it is laggy and jumpy when I have over 50 columns and 50 rows. The problem I now have is that I must set
-(BOOL)shouldInvalidateLayoutForBoundsChange {
return YES;
}
This makes it prepare the entire layout every time the bounds change, which, needless to say, has a huge impact on performance and you can barely scroll. The cells consist of just text with an opaque background, so there is no issue there.
I am sure I am not doing this right and that there must be a better way. Thanks for the help in advance.
In custom flow layout I do this and it seems to help:
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
return !(CGSizeEqualToSize(newBounds.size, self.collectionView.frame.size));
}
-(BOOL)shouldInvalidateLayoutForBoundsChange {
return YES;
}
Causes the layout to do prepareLayout() every time it scrolls, which means anything of heavy computing in prepare will lead to a laggy practice, so one possible direction to solve this is to check what's really taking much time. One possibility is what's inside
for (NSInteger section = 0; section < sectionCount; section++)
{
// generate attributes ...
}
in order to generate attributes for the layout. Every time it scrolls, every time this generalization reruns, so that it impacts on the scroll appear to be jumpy and clumsy. So in order to solve this issue, or at least sort out that this is not the really trouble, I suggest setting a flag in this layout algorithm, say, isScrolling, standing for the situation where the layout needs to prepare. Every time in prepareLayout() check the flag, if it is YES, then we'll know there's no need to do for loop to regenerate all the attributes, which alreay exsit ever since the first time the layout is initialised.
ok--I understand now. Here's what I recommend: create 3 collection views... one for the column headers (where each cell is column header), one for the row leaders (each cell = 1 row leader) and one collection view for your cells. Then when the scroll position of any collection view is changed by the user, update the scroll positions for the other 2 collection views as appropriate.

IBAction to display next object of array

I'm trying to do something I thought was going to be very simple but I can't figure it out. I'm filling an array with objects like this in TableViewController:
if (indexPath.row == 0) {
detailController.textArray = [[NSMutableArray alloc] initWithObjects:#"Text1", #"Text2", #"Text3", #"Text4", #"Text5", nil];
}
Then in the DetailController I added this action to make the label display the next object of the textArray:
- (IBAction)nexTextButtonPressed:(id)sender {
int i = 0;
if (i<[textArray count])
i++;
textLabel.text = [textArray objectAtIndex:i];
}
I connected the button in ib to the action with option Touch Down (tried some of the others too). Something isn't working, the button jumps to the second object of the array but then it doesn't work any more. What could be causing this and how to fix it?
int i = 0; is initialized inside the method, it will always be reinitialized to zero on every call which will not let you move forward from second object
Well every time you hit the button i is getting reset to 0 so when it hits the if statement yes it goes into the statement as the condition is correct, but then you are incrementing i by 1 so i becomes 1 then you retrieve the array index 1 every time. You aren't actually doing anything else. No loop to print through the array or anything.

insertItemsAtIndexPaths Update Error

In UICollectionView, I am trying to use performBatchUpdates:completion to perform updates to my grid view. My data source array is self.results.
Here's my code:
dispatch_sync(dispatch_get_main_queue(), ^{
[self.collectionView performBatchUpdates:^{
int resultsSize = [self.results count];
[self.results addObjectsFromArray:newData];
NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
if (resultsSize == 0) {
[arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:0 inSection:0]];
}
else {
for (int i = 0; i < resultsSize; i++) {
[arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:resultsSize + i inSection:0]];
}
}
for (id obj in self.results)
[self.collectionView insertItemsAtIndexPaths:arrayWithIndexPaths];
} completion:nil];
Explanation of what I have / what I'm doing:
This code runs fine when the initial insertion into the collection view is done. However, when I add/insert more data into my collection view (by updating self.results and calling this), this gives the following error:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of items in section 0. The number of items contained in an
existing section after the update (8) must be equal to the number of
items contained in that section before the update (4), plus or minus
the number of items inserted or deleted from that section (32
inserted, 0 deleted) and plus or minus the number of items moved into
or out of that section (0 moved in, 0 moved out).'
I understand this means that the data source wasn't updated properly. However, when querying my self.results array, I see the new count of the data. I am doing that on the first line using addObjectsFromArray. I also stored the old results size in resultsSize. I use that variable to add the newly added index paths into arrayWithIndexPaths.
Now when adding/inserting items, I tried the following for-loops:
for (id obj in self.results) which is what I am using now. It works initially but further inserts crashes.
for (UIImage *image in newData) works as well initially but further inserts crash.
From the name of the function, I believe that insertItemsAtIndexPaths will insert all items at those index paths without a loop. However, without a loop, the app crashes when it initially tries to populate the data.
I also tried looping from resultsSize + 1 until the new self.results count (which contains the new data) and that also crashes at the initial update.
Any suggestions about what I'm doing wrong?
Thank you,
I see several things wrong here. First, I'm not sure why you're using dispatch_sync, I don't have much experience with GCD, and I couldn't make it work with that in there (it seemed to hang, and the UI was unresponsive). Perhaps someone else can help with that. Second, in the loop where you add index paths, you're looping over resultsSize, which as I understand it, is the size of the array before the update, which is not what you want -- you want to start the new indexes at resultsSize and loop up to resultsSize + newData.count. Lastly, when you call insertItemsAtIndexPaths, you want to do that once, not in a loop. I tried this, and it worked to update the collection view (I didn't try it from scratch with an empty collection view):
-(void)addNewCells {
[self.collectionView performBatchUpdates:^{
int resultsSize = [self.results count];
[self.results addObjectsFromArray:newData];
NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
for (int i = resultsSize; i < resultsSize + newData.count; i++) {
[arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
[self.collectionView insertItemsAtIndexPaths:arrayWithIndexPaths];
}
completion:nil];
}

How to identify an row object in a NSTableView with a specific index?

I use an NSArrayController in InterfaceBuilder to manage objects displayed in am NSTableView. When I selected one or multiple rows the following code gets called to delete the object(s) and update the selection.
NSIndexSet* selectedRowIndices = [m_tableView selectedRowIndexes];
if (!selectedRowIndices || selectedRowIndices.count < 1) {
return;
}
[self removeObjectsAtIndices:selectedRowIndices];
// -------------------------------------------------------
// SELECT ROW BELOW OR EQUAL TO THE LAST DELETED ROW.
// -------------------------------------------------------
// Retrieve the highest selection index (lowest row).
NSInteger highestSelectedRowIndex = NSNotFound;
for (NSUInteger index = selectedRowIndices.firstIndex; index < selectedRowIndices.count; ++index) {
if (index > highestSelectedRowIndex) {
highestSelectedRowIndex = index;
}
}
if (highestSelectedRowIndex != NSNotFound && highestSelectedRowIndex < [m_tableView numberOfRows]) {
// 1) Get the selected object for the highest selected index.
// TODO: Retrieve the object from m_tableView or m_arrayController somehow!?!
// 2) Update the table view selection.
//[self updateTableViewWithSelectedObjects:...];
}
However, I can not find out how I can identify the object that match with the highest index of the former selection.
Why? I would like to move the new selection to the row below the last selection.
ATTENTION: The above code contains several errors!
This is what I ended up with - thanks to Thomas for clarifications.
NSUInteger nextSelectedRowIndex = selectedRowIndices.firstIndex;
if (nextSelectedRowIndex != NSNotFound) {
if (nextSelectedRowIndex >= m_tableView.numberOfRows) {
nextSelectedRowIndex = m_tableView.numberOfRows - 1;
}
id nextSelection = [[m_arrayController arrangedObjects] objectAtIndex:nextSelectedRowIndex];
[self updateTableViewWithSelectedObjects:nextSelection]];
}
The indexes in an NSIndexSet are in order. There's no need for a loop to find the highest.
If you want to select a given row, just invoke -selectRowIndexes:byExtendingSelection: with the new selection you want to establish. For example, [m_tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:highestSelectedRowIndex] byExtendingSelection:NO]. You don't need to know which object that is.
If you still want to know the object, you have to get the array controller's arrangedObjects and apply -objectAtIndex: to that.

Total number of rows in UITableView with multiple sections

I need to calculate the total number of rows in my UITableView, however I have multiple sections. Is there a property on the table view I can access? If not, how can I find this information?
Create an "Objective-c Category" of UITableView from the new file dialogue of xCode and add this method to it:
-(NSInteger)numberOfRowsInTotal{
NSInteger sections = self.numberOfSections;
NSInteger cellCount = 0;
for (NSInteger i = 0; i < sections; i++) {
cellCount += [self numberOfRowsInSection:i];
}
return cellCount;
}
Then import your category header into any class that you need to access this method in et voila, you have what you're looking for.
You can easily calculate this value by iterating through each section.
NSUInteger allRows = 0;
for(NSInteger i = 0; i < [tableview numberOfSections]; i++)
{
allRows += [tableview numberOfRowsInSection:i];
}
If you would like a property you can easily add the above code to a category for UITableView.
A UITableView doesn't know how many sections or rows it has, but your table view data source does. Iterate over the sections and add up the number of rows in each, if that's what you need to do. There may be an easier way, depending on how you're storing the data.