UITableView multiselect - objective-c

I have a uitableview with 50 rows populated from a predefined nsarray.
How can I select multiple the rows with say maximum 3 allowed at a time and show check when selected and remove check when deselected/
I am really new to xcode and any help is much appreciated. Thank you.

Your data needs to keep track of whether it is selected or not.
Two common ways are: each object in your predefined array has a BOOL that indicates whether or not it is selected, or you keep a second array that holds only references to selected objects. Since you're limited to three selected, the second option might be better.
When someone selects a cell in your table, you change the selection status of the related object, either switching its BOOL or adding/removing it in the extra array. This is also the place to check whether you already have as many selections as you allow. If selections have changed, you then tell your table to reload data.
In cellForRowAtIndexPath: you check whether or not the object is selected and mark it accordingly.

int counter = 0; //keep track of how many rows are selected
int maxNum = 3; //Most cells allowed to be selected
//Called when the user selects a row
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//If the cell isn't checked and there aren't the maximum allowed selected yet
if (cell.accessoryType != UITableViewCellAccessoryCheckmark && counter < maxNum)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
counter++;
}
else if (counter >= maxNum) return; //Don't do anything if the cell isn't checked and the maximum has been reached
else //If cell is checked and gets selected again, deselect it
{
cell.accessoryType = UITableViewCellAccessoryNone;
counter--;
}
}
You might also want to keep an array of indices of the cells that are selected, in case you want to do something with the data that's in them. If you don't know how to do this, let me know and I'll add the code.
Notes:
You need to be implementing the table view delegate protocol in order to have this method called correctly.
This isn't the "best" way to do it (using cell content to keep track of selection is generally frowned up) but it is very easy.
You might run into problems with cell reuse. If you want to fix that, store the cell's index and set the accessory type in cellForRowAtIndexPath

Related

delete particular tableview item in SWRevealViewController in objective C

Hi how to remove particular table view cells in SWRevealViewController .my table View cells are hard coded in story board.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
if(indexPath.row==2)
{
return 0.0;
}
if(indexPath.row==3)
{
return 0.0;
}
return 75;
}
While running first time its messed up.once click that particular cell its working properly. 
Here the Screen Shot: https://i.stack.imgur.com/YUNoi.jpg
Do as follows
1.Create Number of dynamic cells as per your needs(how much items you want to show).
2.Give different identifier to all of your cells.
3.Make a array of identifiers of all of yours cell in the sequence in which you want to show the cells. identifierArray[] in my case.
4.Then in numberOfItems inSection, return identifierArry.count.
5.Simply in cellForRow atIndexpath, dequeue your cell with identifier: identifierArray[indexpat.row].
6.if You want to delete any row then simply remove the identifier from IdentifierArray like so identifierArray.removeObject[position]. where position is the position of your item in array.
7.And after removing the identifier from array call tableView.reloadData method.
Hope it will help you.
Note: If it will solve your problem then not forget to mark this as accepted by checking the tick mark.

Populate NSTableView with unknown number of columns

I have a NSTableview and a button. NSTableview has a unknown number of columns.
The first column has a image well and 2 text boxes, the others (again, unknown number) are normal textbox columns.
The button opens up a file open dialogue. Once I choose the files (.jpg) I would like to process them.
So far everything is made (chose files, columns, etc..) what is missing is the populating of the table:
I have the loop that goes through all the selected files. What is the best way to do this:
display the image in the image well of the first cell,
type the filename in the first textbox of the first cell,
type the filepath in the second cell of the textbox,
type "YES" in all other columns.
My difficulty is that I have no idea how many columns will be there since it depends from the user. The number of columns will not change during Runtime. they are set up at startup based on the configuration. if the configuration is changed then the app should be reloaded.
I am a beginner in Objective-C/Cocoa programming.
EDIT:
additional info as requested:
It is a view based NSTableView
each column represents an action that has to be taken in a later moment on an image. the program user can decide what and how many actions to take, thats the reason for a unknown amount of columns in the table view.
You can add columns programmatically using addTableColumn:. This method takes an NSTableColumn instance that you can create in your code.
The rest of your architecture (displaying images, etc.) does not particularly change from "normal" code just because the columns have been added dynamically.
Here is a snippet that should get you started:
NSTableColumn* tc = [[NSTableColumn alloc] init];
NSString *columnIdentifier = #"NewCol"; // Make a distinct one for each column
NSString *columnHeader = #"New Column"; // Or whatever you want to show the user
[[tc headerCell ] setStringValue: columnHeader];
tc.identifier = columnIdentifier;
// You may need this one, too, to get it to show.
self.dataTableview.headerView.needsDisplay = YES;
When populating the table, and assuming that the model is an array (in self.model) of NSDictionary objects, it could go something like this;
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
NSString *columnIdentifier = tableColumn.identifier;
NSDictionary *rowDict = [self.model objectAtIndex: row];
NSString *value = [rowDict valueForKey: columnIdentifier]; // Presuming the value is stored as a string
// Show the value in the view
}
More in the docs.
When user adds a column or row, you should reflect it in your model (by binding or by code), so you know the size of your table, when you need to populating it.
set tableView.delegate (in code or in Interface Builder), reference here
implement:
- (NSView*) tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn*)tableColumn row:(NSInteger)row
{
Item* itemView = [tableView makeViewWithIdentifier:#"rowItem" owner:self];
/*Here you populate your cell view*/
id entryObject = [self.entries objectAtIndex:row];
[itemView setEntry:entryObject];
return itemView;
}
and then invoke [tableView reloadData];
maybe for you better to use this method
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
Just see the NSTableViewDataSource and NSTableViewDelegate

Assertion Failure in UICollectionViewData indexPathForItemAtGlobalIndex

I am using performBatchUpdates() to update my collection view, where I am doing a complete refresh, i.e. delete whatever was in it and re-insert everything. The batch updates are done as part of an Observer which is attached to a NSMutableArray (bingDataItems).
cellItems is the array containing items that are or will be inserted into the collection view.
Here is the code:
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
cultARunner *_cultARunner = [cultARunner getInstance];
if ( [[_cultARunner bingDataItems] count] ) {
[self.collectionView reloadData];
[[self collectionView] performBatchUpdates: ^{
int itemSize = [cellItems count];
NSMutableArray *arrayWithIndexPaths = [NSMutableArray array];
// first delete the old stuff
if (itemSize == 0) {
[arrayWithIndexPaths addObject: [NSIndexPath indexPathForRow: 0 inSection: 0]];
}
else {
for( int i = 0; i < cellItems.count; i++ ) {
[arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
}
[cellItems removeAllObjects];
if(itemSize) {
[self.collectionView deleteItemsAtIndexPaths:arrayWithIndexPaths];
}
// insert the new stuff
arrayWithIndexPaths = [NSMutableArray array];
cellItems = [_cultARunner bingDataItems];
if ([cellItems count] == 0) {
[arrayWithIndexPaths addObject: [NSIndexPath indexPathForRow: 0 inSection: 0]];
}
else {
for( int i = 0; i < [cellItems count]; i++ ) {
[arrayWithIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
}
[self.collectionView insertItemsAtIndexPaths:arrayWithIndexPaths];
}
completion:nil];
}
}
I get this error, but not all of the times (why ?)
2012-12-16 13:17:59.789 [16807:19703] *** Assertion failure in -[UICollectionViewData indexPathForItemAtGlobalIndex:], /SourceCache/UIKit_Sim/UIKit-2372/UICollectionViewData.m:442
2012-12-16 13:17:59.790 [16807:19703] DEBUG: request for index path for global index 1342177227 when there are only 53 items in the collection view
I checked the only thread that mentioned the same problem here: UICollectionView Assertion failure, but it is not very clear i.e. doing [collectionview reloadData] is not advisable in the performBatchUpdates() block.
Any suggestions on what might be going wrong here ?
Finally! Ok, here's what was causing this crash for me.
As previously noted, I was creating supplementary views in order to provide custom-styled section headers for my collection view.
The problem is this: it appears that the indexPath of a supplementary view MUST correspond to the indexPath of an extant cell in the collection. If the supplementary view's index path has no corresponding ordinary cell, the application will crash. I believe that the collection view attempts to retrieve information for a supplementary view's cell for some reason during the update procedure. It crashes when it cannot find one.
Hopefully this will solve your problem too!
This is the proper workaround to this crash:
Each of your supplementary views are associated with a certain index path. If you don't have a cell at that index path (initial load, you've deleted the row, etc), return a height of 0 for your supplementary view via your layout's delegate.
So, for a flow layout, implement UICollectionViewDelegateFlowLayout's
(CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout referenceSizeForHeaderInSection:(NSInteger)section
method (and the corresponding footer method, if you're using footers) with the following logic
if ( you-have-a-cell-at-the-row-for-this-section )
return myNormalHeaderSize;
else return CGSizeMake( 0,0 );
Hope this helps!
reloadData doesn't work for me, because the whole purpose of using performBatchUpdates is to get the changes animated. If you use reloadData you only refresh the data, but without animations.
So suggestions of "replace performBatchUpdates with reloadData" is pretty much saying "give up on what you're trying to do."
I'm sorry, I'm just frustrated because this error keeps coming up for me while I'm trying to do some great animated updates and my model is 100 % correct, it's some iOS magic inside getting broken and forcing me to change my solutions completely.
My opinion is that Collection Views are still buggy and can't do complicated animated refreshes, even though they should be able to. Because this used to be the same thing for Table Views but those are now pretty stable (it took time, though).
//Edit (Sep 1, 2013)
The reported bug is closed now so this issues seems to have been resolved by Apple already.
I have been having the same problem.
I have tried a number of variations, but the final one that seems to work is [self.collectionView reloadData], where "self.collectionView"is the name of your collection view.
I have tried the following methods, straight from the "UICollectionView Class Reference": inserting, moving, and deleting items.
These were used at first, to "move" the item from one section to another.
deleteItemsAtIndexPaths:
insertItemsAtIndexPaths:
Next, I tried moveItemAtIndexPath:toIndexPath:.
They all produced the following error:
Assertion failure in -[UICollectionViewData indexPathForItemAtGlobalIndex:], /SourceCache/UIKit_Sim/UIKit-2372/UICollectionViewData.m:442
So, try the "reloadData" method.
If you remove the last cell from a section containing header/footer the bug appears.
I tried to return nil for header/footer size/element at that time and this sometimes fixes the issue.
Options:
Reload the whole table view instead of animating the removal of the last item.
Add an additional invisible, basic cell with a size less than 1.
A cheeseball mistake that can lead to this error is reusing the same UICollectionViewFlowLayout on multiple collectionViews on the same viewcontroller! Just init different flowLayouts for each collectionview and you'll be good to go!
I ran into this problem when I delete one of the cells from my collection view.
The problem was that I use a custom layout, and the call layoutAttributesForElementsInRect was returning more than the number of cells in the collection view after the delete.
Apparently UICollectionView just iterates through the array returned by the method without checking the number of cells.
Modifying the method to return the same number of layout attributes solved the crash.
I still couldn't figure out how the global index was incremented so much, but I solved my problem by inserting a temporary item in the underlying datasource array i.e. cellItems and calling [self.collectionview reloadData] in viewDidLoad().
This inserts a placeholder cell temporarily in the collection view until I trigger the actual process using performBatchUpdates().

Objective-C UITableView index

How do I read always the most upper line (cell.textLabel.text) in a UITableView?
I have two slightly different approaches;
1:
UITableViewCell *cell2 = [tableView cellForRowAtIndexPath:indexPath];
NSString *cellText2 = cell2.textLabel.text;
NSLog (#" cellText2 = %#", cellText2);
2:
NSMutableString *newidea = [self.array objectAtIndex:indexPath.row];
NSLog (#"newidea = %#", newidea);
Both codes are inside the method - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
self.array is same array that fills up the tableView.
The former approach always shows text from the sixth cell. The latter approach always shows text from the fifth cell. In my tableview there is four cells at a time.#
What I want to get is the text from the most upper cell.
I think you have the wrong approach. You don't read values from cells, instead you let the cells read values from your data array. A cell can always have an arbitrary value since they are reused. Even if you have 30 "cells" in your table view there may only be 5 existing actual cells. When a cell goes outside the table view when you scroll, it is moved to the bottom and reused as the next cell. That's why you always have to set the values for each cell on the index path.
Instead you should get the value in the first cell from your data array if you have one. When the table view asks what title the cell att indexPath.row == 0 should have, you give it to it in cellForRowAtIndexPath, for example from an array called "_cellTitles" containing 30 strings for 30 different cells.
If you want to get the text from the "most upper" visible cell, then you can call indexPathsForVisibleRows on the table view. The first object in the returned array is the index path for the most upper visible cell. You can check the string in your array at index indexPath.row.
Example:
NSArray *visibleRows = [self.tableView indexPathsForVisibleRows];
NSIndexPath *firstVisibleCell = [visibleRows objectAtIndex:0];
NSString *firstVisibleCellTitle = [_myDataArray objectAtIndex:indexPath.row];
If you always want to read from first row, instead of indexPath just say 1 there. That way it will always read from the first row.

reloadData in NSTableView but keep current selection

I have anNSTableView showing the contents of a directory. I watch for FSEvents, and each time I get an event I reload my table view.
Unfortunately, the current selection then disappears. Is there a way to avoid that?
Well, you can save selection before calling reloadData and restore it after that.
NSInteger row = [self.tableView selectedRow];
[self.tableView reloadData];
[self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
This worked for me in some cases. But if you insert some items BEFORE the selected row, you should andjust your row variable by adding count of added items to it.
Swift 4.2
Create an extension and add a method which preserves selection.
extension NSTableView {
func reloadDataKeepingSelection() {
let selectedRowIndexes = self.selectedRowIndexes
self.reloadData()
self.selectRowIndexes(selectedRowIndexes, byExtendingSelection: false)
}
}
Do this in case you use the traditional way of populating table views (not NSArrayController).
It depends on how you populate your NSTableView.
If you have the table view bound to an NSArrayController, which in turn contain the items that your table view is displaying, then the NSArrayController has an option to preserve the selection. You can select it (or not) from within Interface Builder as a property on the NSArrayController. Or you can use the setPreservesSelection method in code.
However, if you completely replace the array of items that the NSArrayController manages each time you get your FSEvents, then maybe the preservation of selection cannot work. Unfortunately the Apple docs on this property of NSArrayController are a bit vague as to when it can and cannot preserve the selection.
If you are not using an NSArrayController, but maybe using a dataSource to populate the table view, then I think you'll have to manage the selection yourself.
In the case of using Data Source, Apple Documentation in the header file on reloadData() is that
The selected rows are not maintained.
To get around, you can use reloadDataForRowIndexes(rowIndexes: NSIndexSet, columnIndexes: NSIndexSet). As mentioned in the same header file
For cells that are visible, appropriate dataSource and delegate methods will be called and the cells will be redrawn.
Thus the data will be reloaded, and the selection is kept as well.
A variant on #silvansky's answer.
This one has no need to keep track of count of items inserted/deleted. And it maintains multiple selection.
The idea is to...
1. create an array of selected objects/nodes from the current selection.
2. refresh the table using reloadData
3. for each object obtained in step 1, find/record it's new index
4. tell the table view/outline view to select the updated index set
- (void)refresh {
// initialize some variables
NSIndexSet *selectedIndexes = [self.outlineView selectedRowIndexes];
NSMutableArray *selectedNodes = [NSMutableArray array];
NSMutableIndexSet *updatedSelectedIndex = [NSMutableIndexSet indexSet];
// 1. enumerate all selected indexes and record the nodes/objects
[selectedIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
[selectedNodes addObject:[self.outlineView itemAtRow:idx]];
}];
// 2. refresh the table which may add new objects/nodes
[self.outlineView reloadData];
// 3. for each node in step 1, find the new indexes
for (id selectedNode in selectedNodes) {
[updatedSelectedIndex addIndex:[self.outlineView rowForItem:selectedNode]];
}
// 4. tell the outline view to select the updated index set
[self.outlineView selectRowIndexes:updatedSelectedIndex byExtendingSelection:NO];
}