UICollectionView and cell re-ordering

I have a UIcollectionView to display data I receive from a bluetooth peripheral

I get new data every second.



for re-ordering I found this function


override func collectionView(_ collectionView: UICollectionView,

moveItemAt sourceIndexPath: IndexPath,

to destinationIndexPath: IndexPath)


it work but only if I move for one position

because I reload data every second, if the reload occur during a move, the move is reset

If I stop the reload of data, it work well like a normal re-ordering


But to stop data updating, I need to know when the Drag Begin to stop new data from coming in

and when the Drag ended to restart the data.


Is there a way to know it without implementing the whole UICollectionViewDragDelegate, UICollectionViewDropDelegate

Because I am having problem doing that :-)

Answered by PBK in 355039022

Perhaps this will help. I use a UITableViewController class (so "self" is the table) and it is also a UITextFieldDelegate and I use ObjectiveC.


When the user indicates they want to move a cell I do this:

        self.title=@"Reorder Stocks";

(note that this is a gross violation of MVC - I am storing a C variable in a V element. "I can easier teach twenty what were good to be done then be one of the twenty to follow mine own teaching." (Wm. Shakespeare, The Merchant of Venice)



I have the following functions:

-(void)tableView:(UITableView *)tableView  moveRowAtIndexPath:(NSIndexPath *)indexPathFrom toIndexPath:(NSIndexPath *)indexPathTo{
    NSMutableDictionary *temp= [[NSMutableDictionary alloc] initWithDictionary:[theStocks objectAtIndex:[indexPathFrom row]]] ;                                                                                                   
    [theStocks removeObjectAtIndex:[indexPathFrom row]];
    [theStocks insertObject:temp atIndex:[indexPathTo row]];
    [tableView reloadData];
       // and here you would again allow reloading of the incoming data.

}

- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath{
    if([self.title isEqualToString:@"Reorder Stocks"]){    
     //  here you would set some variable to stop reloading
          return YES;
    }else{
        return NO;
     }
}

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
     return UITableViewCellEditingStyleNone;
}
Accepted Answer

Perhaps this will help. I use a UITableViewController class (so "self" is the table) and it is also a UITextFieldDelegate and I use ObjectiveC.


When the user indicates they want to move a cell I do this:

        self.title=@"Reorder Stocks";

(note that this is a gross violation of MVC - I am storing a C variable in a V element. "I can easier teach twenty what were good to be done then be one of the twenty to follow mine own teaching." (Wm. Shakespeare, The Merchant of Venice)



I have the following functions:

-(void)tableView:(UITableView *)tableView  moveRowAtIndexPath:(NSIndexPath *)indexPathFrom toIndexPath:(NSIndexPath *)indexPathTo{
    NSMutableDictionary *temp= [[NSMutableDictionary alloc] initWithDictionary:[theStocks objectAtIndex:[indexPathFrom row]]] ;                                                                                                   
    [theStocks removeObjectAtIndex:[indexPathFrom row]];
    [theStocks insertObject:temp atIndex:[indexPathTo row]];
    [tableView reloadData];
       // and here you would again allow reloading of the incoming data.

}

- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath{
    if([self.title isEqualToString:@"Reorder Stocks"]){    
     //  here you would set some variable to stop reloading
          return YES;
    }else{
        return NO;
     }
}

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
     return UITableViewCellEditingStyleNone;
}

Yyyeeess


It work !!!


dont laugh, but in the other thread you wrote that you did not use storyboard.

i misunderstood it and i though you did not use graphical element

so i was surprise to see you answering about collectionView


Now, i know why you did not needed the course i was talking about :-)



p.s. i need to read more about MVC.



another problem i have is with the documentation,

i search in UITableView and did not find the canMoveRowAtIndexPath


found it with duckduckgo in this page

https://developer.apple.com/documentation/uikit/uitableviewdatasource/1614927-tableview?preferredLanguage=occ


At the top it said "Asks the data source..." but i had no luck will that either


I saw that you were using moveRowAtIndexPath

Which was the same as the was using in collection view


So In Xcode I started to type

func collectionView


And I saw the same as the one you use in table view

    override func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
        pauseDisplayNow = true
        return  true
    }


i must add that first time i try a search the documentation with canMove, I found nothing

as i was writing this answer, i retried it and now it work. So, definitly, again, the problem is me and my big brain :-)


but still, i have problem reading the documention.


I was lucky because i use coreBluetooth Guide and without it i would not have been able to do it.

sadly the Guide are going away.



ohh,

i need to handle the case where the move is canceled, because i dont restart the data in this case.

The TableView has a source for its data called the UITableViewDataSource. I set the data source and the delegate for my table in the .xib file. You might be able to do that in Storyboard or you might need to add, in the .h file, the following:


@interface myViewControllerNameHere : UIViewController<UITableViewDelegate,UITableViewDataSource>

Try the following and your answer will become clear.



-(void)tableView:(UITableView *)tableView  moveRowAtIndexPath:(NSIndexPath *)indexPathFrom toIndexPath:(NSIndexPath *)indexPathTo{  
    NSMutableDictionary *temp= [[NSMutableDictionary alloc] initWithDictionary:[theStocks objectAtIndex:[indexPathFrom row]]] ;                                                                                                     
    [theStocks removeObjectAtIndex:[indexPathFrom row]];  
    [theStocks insertObject:temp atIndex:[indexPathTo row]];  
    [tableView reloadData];  
       // and here you would again allow reloading of the incoming data.  
     NSLog(@"How do I detect when I cancel a move???? %@   %@",indexPathTo,indexPathFrom);
}

when i do a complete move, if work,


but if i drag the cell just a bit and drop it at the same place
this is not called


     override func collectionViewOld(_ collectionView: UICollectionView,
                                 moveItemAt sourceIndexPath: IndexPath,
                                 to destinationIndexPath: IndexPath) {
        
        let laSource = caDeviceModel.position[sourceIndexPath.row]
        caDeviceModel.position.remove(at:sourceIndexPath.row)
        caDeviceModel.position.insert(laSource, at: destinationIndexPath.row)
        
        print ("move Canceled ")
        
        pauseDisplayNow = false
        self.collectionView.reloadData()
        
    }


but this was called at the begining , so there is not more update

    override func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
        pauseDisplayNow = true
        return  true
    }


then i re-do a complete move, and it work

One difference between the UICollectionView Method:

collectionView:moveItemAtIndexPath:toIndexPath

and the UITableView method:

moveRowAtIndexPath:toIndexPath


is that the collection view only "calls this method if the position of the item changed".


I am not familiar with moving things in UICollectionViews - there is, for example, a dropDelegtae that might work.

If your object is a table you could consider using a UITableView rather than a UICollectionView.

I tried to do the

UICollectionViewDragDelegate and UICollectionViewDropDelegate


i have trouble implementing it but i was able to create empty item :-) and now i can see when they are called

i have many

dropSessionDidUpdate

being called but if the cell does not change position like you mentionned,

the performDropWith is not beeing called ( so i cant restart the data to come in)


i could cheat and put a timer but that is not proper programming.

for now, that is what i might do because i need the app for my solar eBike soon and i just need to work for me now


after that, i will have time to look more into that.

if i may, i would like to ask you another question. Tell me if i should post it, so you could get point.


i receive my data from bluetooth in the same order every time. exemple caDeviceModel.dataReceived [A,B,C,D]

i then add new data that i calculate from those.

but i might not want to see them display in that order. so i let the user re-order the cell

but i only keep an array with the index of where the cell is located, exemple it start with: caDeviceModel.position [0,1,2,3]

if a user swap the last 2 cells, my array change to caDeviceModel.position [0,1,3,2]

when the colectionview (or tableview) ask for cellForItemAt indexPath 2,

i get position 3, so i get the data from at index 3 in my array of data and get D


         let monIndex = caDeviceModel.position[indexPath.row]
        cell.valueLabel.text = caDeviceModel.dataReceived[monIndex]


is that an acceptable method of doing it.

could i improve that

What you propose is certainly acceptable. I would leave the original data array as it is (M). I would then create a second array whose object at index N was the index of the original data array that I wanted to display at location N (V). I think this approach is cleaner.

UICollectionView and cell re-ordering
 
 
Q