UICollectionViewCell - how to select all items/cells in UIButton action method - ios

I'm trying to select all UICollectionViewCells after a UIButton is tapped.
How do I do this?

Updated for Swift 3
Just Shadow's Answer updated to Swift 3
for i in 0..<assetCollectionView.numberOfSections {
for j in 0..<assetCollectionView.numberOfItems(inSection: i) {
assetCollectionView.selectItem(atIndexPath: IndexPath(row: j, section: i), animated: false, scrollPosition: .none)
}
}

You can select all cells in the first section through:
for (NSInteger row = 0; row < [self.collectionView numberOfItemsInSection:0]; row++) {
[self.collectionView selectItemAtIndexPath:[NSIndexPath indexPathForRow:row inSection:0] animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
If you have more than 1 section, just use another nested for loop to loop through all the sections.

Updated to Swift 4
Updated Indrajit Sinh Rayjada's answer and put into an extension, as this is so general that it really should be an extension.
extension UICollectionView {
func selectAll() {
for section in 0..<self.numberOfSections {
for item in 0..<self.numberOfItems(inSection: section) {
self.selectItem(at: IndexPath(item: item, section: section), animated: false, scrollPosition: [])
}
}
}
}

Here's the solution:
for (NSInteger i = 0; i < [_assetCollectionView numberOfSections]; i++)
{
for (NSInteger j = 0; j < [_assetCollectionView numberOfItemsInSection:i]; j++)
{
[_assetCollectionView selectItemAtIndexPath:[NSIndexPath indexPathForRow:j inSection:i] animated:NO scrollPosition:UICollectionViewScrollPositionNone];
}
}

Related

appending new content and refreshing UITableView freezes my app for couple seconds in Swift

In my Swift app I have a UITableView and UITextView. The idea is simple, when user adds a text - it should appear at the bottom of the table view.
So I have an array of my object SingleMessage:
var messages = [SingleMessage]()
When user adds a text to UITextView, I send message with Socket.IO and receive it:
func messageArrived(_ notification: Notification) {
if let message = (notification as NSNotification).userInfo?["message"] as? SingleMessage {
DispatchQueue.main.async(execute: { () -> Void in
messages.append(message)
self.tview.reloadData()
self.scrollToBottom()
)}
}
}
my function scrollToBottom() contains the following code:
if(self.messages.count > 0) {
let iPath = IndexPath(row: self.tview.numberOfRows(inSection: 0)-1, section: self.tview.numberOfSections-1)
self.tview.scrollToRow(at: iPath, at: UITableViewScrollPosition.bottom, animated: false)
}
and then I have cellForRow function, that does a lot of stuff, like setting fonts and texts for each label, etc.
override func tableView(_ tview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tview.dequeueReusableCell(withIdentifier: "chat") as! SingleCommentCell
if let msg:SingleMessage = self.messages[(indexPath as NSIndexPath).row] as? SingleMessage {
.
.
.
My problem is that when I type something, then presses the send button immediately and start typing again - the whole interface freezes for couple seconds and I don't even see the feedback from the keyboard. I think that the problem is that the table view has to be completely refreshed.
I'm using the interface above in the Chat component, so the problem occurs not only when user quickly types several messages in a row, but also when there're many incoming messages.
Is there any way of speeding up the whole interface, like for example add new cells at the bottom of the table view and avoid refreshing already existing ones?
The other functions related to my UITableViewController are:
override func tableView(_ tview: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Then I have:
override func viewDidLoad() {
super.viewDidLoad()
tview.separatorStyle = UITableViewCellSeparatorStyle.none
tview.backgroundColor = UIColor.clear
tview.delegate = self
tview.dataSource = self
self.tview.estimatedRowHeight = 100
NotificationCenter.default.addObserver(self, selector: #selector(ChatView.messageArrived(_:)), name: NSNotification.Name(rawValue: incomingMessage), object: nil)
}
reloadData is a very expensive operation. It rebuilds the entire table. You are better off keeping better track of your model, using insert and delete row functions when you want to perform those operations and refresh individual rows when they change.
A good strategy for this is to keep the old model, generate the new model, then compute the set of items that were created, moved, or removed and generate individual table operations for each case. Here is a bit of sample code:
- (void) setDevicesForKey: (NSString *) propertyKey
toDevices: (NSArray *) newDevices
{
NSArray *currentDevices = [self valueForKey: propertyKey];
NSUInteger tableSection = [self sectionForKey: propertyKey];
NSIndexSet *indexesOfItemsToRemove = [currentDevices indexesOfObjectsPassingTest: ^BOOL(DeviceItem * itemToCheck, NSUInteger idx, BOOL *stop) {
return ![newDevices containsObject: itemToCheck];
}];
NSIndexSet *indexesOfItemsToAdd = [newDevices indexesOfObjectsPassingTest:^BOOL(DeviceItem *itemToCheck, NSUInteger idx, BOOL *stop) {
return ![currentDevices containsObject: deviceItem];
}];
UITableView *tableView = [self tableView];
[tableView beginUpdates];
{
NSMutableArray *removeIndexPaths = [NSMutableArray array];
[indexesOfItemsToRemove enumerateIndexesUsingBlock: ^(NSUInteger idx, BOOL *stop) {
[removeIndexPaths addObject: [NSIndexPath indexPathForItem: idx inSection: tableSection]];
}];
[tableView deleteRowsAtIndexPaths: removeIndexPaths withRowAnimation: UITableViewRowAnimationAutomatic];
NSMutableArray *insertIndexPaths = [NSMutableArray array];
[indexesOfItemsToAdd enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
[insertIndexPaths addObject: [NSIndexPath indexPathForItem: idx inSection: tableSection]];
}];
[tableView insertRowsAtIndexPaths: insertIndexPaths withRowAnimation: UITableViewRowAnimationAutomatic];
[newDevices enumerateObjectsUsingBlock: ^(DeviceItem *itemToCheck, NSUInteger idx, BOOL *stop) {
if([currentDevices containsObject: itemToCheck])
{
NSUInteger oldIndex = [currentDevices indexOfObject: ticketToCheck];
NSUInteger newIndex = [newDevices indexOfObject: ticketToCheck];
if(oldIndex != newIndex)
{
NSIndexPath *fromIndexPath = [NSIndexPath indexPathForRow: oldIndex inSection: tableSection];
NSIndexPath *toIndexPath = [NSIndexPath indexPathForRow: newIndex inSection: tableSection];
[tableView moveRowAtIndexPath: fromIndexPath toIndexPath: toIndexPath];
}
}
}];
[self setValue: newDevices forKey: propertyKey];
}
[tableView endUpdates];
}
I recommend to insert the row with insertRows(at rather than calling reloadData and scroll only if the cell is not visible.
func messageArrived(_ notification: Notification) {
if let message = notification.userInfo?["message"] as? SingleMessage {
DispatchQueue.main.async {
// if the index path is created before the item is inserted the last row is self.messages.count
let newIndexPath = IndexPath(row: self.messages.count, section: 0)
self.messages.append(message)
self.tableView.insertRows(at: [newIndexPath], with: .automatic)
if let visiblePaths = self.tableView.indexPathsForVisibleRows, !visiblePaths.contains(newIndexPath) {
self.tableView.scrollToRow(at: newIndexPath, at: .bottom, animated: false)
}
}
}
}
Note:
The restriction of 8 characters for variable names is all over for more than 30 years.
Names like tview are hard to read. I'm using tableView in the code.

How to Expand single particular section?

I have used third party expandable table view named "ExpandableTableView",when i want to expand particular single section using custom button in my view.my code snippet is :
UIView *view=Gesture.view;
isFirsTime=NO;
//NSLog(#"%ld",(long)view.tag);
for (int h=0; h<dicAll.count; h++)
{
NSString *strRegisterId=[[[dictStatndardDefectsResult valueForKey:#"defectdata:"]valueForKey:#"projectdefectid"]objectAtIndex:h];
NSString *strBtnTag=[NSString stringWithFormat:#"%ld",(long)view.tag];
if ([strRegisterId isEqualToString:strBtnTag])
{
btnIndex=h;
// NSLog(#"%ld",(long)btnIndex);
isTappedMarker=YES;
}
}
NSMutableArray *indexPaths=[[NSMutableArray alloc]init];
[indexPaths addObject:[NSIndexPath indexPathForRow:0 inSection:btnIndex]];
NSLog(#"%#",arrDefectImages);
NSLog(#"numberOfRowsInSection: %ld",(long)[self tableView:tblSupplierDefect numberOfRowsInSection:btnIndex]);
[tblSupplierDefect beginUpdates];
[self.tblSupplierDefect insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic];
[tblSupplierDefect endUpdates];
where the btnIndex is section number which i want to expand.
I have achieved something like that by using this:
func didTapOnHeader(tapGesture: UITapGestureRecognizer) {
let view = tapGesture.view as! ViewHeader
// collapse if already expended.
if view.tag == self.dataBinder.selectedSection {
//collaps the section
self.dataBinder.selectedSection = nil
self.tableView.reloadSections(NSIndexSet(index:view.tag), withRowAnimation: UITableViewRowAnimation.Automatic)
}else {
// collapse last selected section, at a time one section should be selected.
self.dataBinder.selectedSection = nil
self.tableView.reloadData()
//expand plan details
self.dataBinder.selectedSection = view.tag
self.tableView.reloadSections(NSIndexSet(index:view.tag), withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = ViewHeader.instanceFromNib()
view.tag = section
let gesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.didTapOnHeader(_:)))
view.addGestureRecognizer(gesture)
//### Assignment ###
view.planDescriptionLabel.text = plan.planDescription
return view
}
This will close perviously selected sections and will expand only one section at ta time.

How to get the last indexpath of a uicollectionview?

I want to get the last indexpath of a uicollectionview.
I tried this:
NSInteger section = [self numberOfSectionsInCollectionView:self.collectionView] - 1;
NSInteger item = [self collectionView:self.collectionView numberOfItemsInSection:section] - 1;
NSIndexPath *lastIndexPath = [NSIndexPath indexPathForItem:item inSection:section];
But couldn't find the last indexpath of an uicollectionview .
Any suggestions,
Thanks in advance.
NSInteger lastSectionIndex = [tableView numberOfSections] - 1;
NSInteger lastRowIndex = [tableView numberOfRowsInSection:lastSectionIndex] - 1;
// Now just construct the index path
NSIndexPath *pathToLastRow = [NSIndexPath indexPathForRow:lastRowIndex inSection:lastSectionIndex];
Answer for Swift 3
extension UICollectionView {
func scrollToLastIndexPath(position:UICollectionViewScrollPosition, animated: Bool) {
self.layoutIfNeeded()
for sectionIndex in (0..<self.numberOfSections).reversed() {
if self.numberOfItems(inSection: sectionIndex) > 0 {
self.scrollToItem(at: IndexPath.init(item: self.numberOfItems(inSection: sectionIndex)-1, section: sectionIndex),
at: position,
animated: animated)
break
}
}
}
}
Note that my experience shows that animated property should be false for initializing the collectionView.
From the code above, as an answer to your question use this method:
func lastIndexPath() -> IndexPath? {
for sectionIndex in (0..<self.numberOfSections).reversed() {
if self.numberOfItems(inSection: sectionIndex) > 0 {
return IndexPath.init(item: self.numberOfItems(inSection: sectionIndex)-1, section: sectionIndex)
}
}
return nil
}

UICollectionView scroll to last item without Animation

I have a UICollectionView to display chat messages. At the beginning I load the existing messages to the collectionView and scroll the collectionView down to the last message with this method:
- (void)scrollToLastMessageAnimated:(BOOL)animated;
{
if (_messages.count == 0) { return; }
NSUInteger indexOfLastSection = _messagesBySections.count - 1;
NSInteger indexOfMessageInLastSection = [_messagesBySections[indexOfLastSection] count] - 1;
NSIndexPath *path = [NSIndexPath indexPathForItem:indexOfMessageInLastSection
inSection:indexOfLastSection];
[_collectionView scrollToItemAtIndexPath:path
atScrollPosition:UICollectionViewScrollPositionCenteredVertically
animated:animated];
}
This only works animated when I call it in the viewDidAppear: method and not in viewWillAppear: method. How can I scroll down without animation?
Here it is...
NSInteger section = [self.collectionView numberOfSections] - 1;
NSInteger item = [self.collectionView numberOfItemsInSection:section] - 1;
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:(UICollectionViewScrollPositionBottom) animated:YES];
I also provided my answer in another question of yours but I am also writing it here, since this answer is being related only with this problem
Solution
To scroll the view at the last index without crashing before view appears, you first need to trigger the reload of your collectionView data. After it has been reloaded call your method to scroll your view.
-(void)viewWillAppear:(BOOL)animated {
[collectionView reloadData];
[self scrollToLastMessageAnimated:YES];
}
Update
[collectionView setContentOffset:CGPointMake(0, CGFLOAT_MAX)]
Swift 3.0 Code :
let index = IndexPath(item: (self.mFetchedResultsControllerVar?.fetchedObjects?.count)! - 1, section: 0)
self.collectionView?.scrollToItem(at: index, at: UICollectionViewScrollPosition.bottom, animated: true)
A more foolproof solution that handles multiple/0 sections/items
extension UICollectionView {
func scrollToLastItem(at scrollPosition: UICollectionViewScrollPosition = .centeredHorizontally, animated: Bool = true) {
let lastSection = numberOfSections - 1
guard lastSection >= 0 else { return }
let lastItem = numberOfItems(inSection: lastSection) - 1
guard lastItem >= 0 else { return }
let lastItemIndexPath = IndexPath(item: lastItem, section: lastSection)
scrollToItem(at: lastItemIndexPath, at: scrollPosition, animated: animated)
}
}
This Extension scrolls to the last section that actually has items.
Best approach so far.
public extension UICollectionView {
func scrollToLastItem() {
scrollToLastItem(animated: true, atScrollPosition: .bottom)
}
func scrollToLastItem(animated: Bool, atScrollPosition scrollPosition: UICollectionViewScrollPosition) {
guard numberOfSections > 0 else {
return
}
var sectionWithItems: SectionInfo?
for section in Array(0...(numberOfSections - 1)) {
let itemCount = numberOfItems(inSection: section)
if itemCount > 0 {
sectionWithItems = SectionInfo(numberOfItems: itemCount, sectionIndex: section)
}
}
guard let lastSectionWithItems = sectionWithItems else {
return
}
let lastItemIndexPath = IndexPath(row: lastSectionWithItems.numberOfItems - 1, section: lastSectionWithItems.sectionIndex)
scrollToItem(at: lastItemIndexPath, at: scrollPosition, animated: animated)
}
}
The problem was actually caused by problematic Autolayout constraints,
see my other thread for the solution that helped me in the end: https://stackoverflow.com/a/24033650/2302437

How can I loop through UITableView's cells?

I have n sections (known amount) and X rows in each section (unknown amount. Each row has a UITextField. When the user taps the "Done" button I want to iterate through each cell and do some conditional tests with the UITextField. If the tests pass data from each cell is written to a database. If not, then a UIAlert is shown. What is the best way to loop through the rows and if there is a more elegant solution to this please do advise.
If you only want to iterate through the visible cells, then use
NSArray *cells = [tableView visibleCells];
If you want all cells of the table view, then use this:
NSMutableArray *cells = [[NSMutableArray alloc] init];
for (NSInteger j = 0; j < [tableView numberOfSections]; ++j)
{
for (NSInteger i = 0; i < [tableView numberOfRowsInSection:j]; ++i)
{
[cells addObject:[tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:j]]];
}
}
Now you can iterate through all cells:
(CustomTableViewCell is a class, which contains the property textField of the type UITextField)
for (CustomTableViewCell *cell in cells)
{
UITextField *textField = [cell textField];
NSLog(#"%#"; [textField text]);
}
Here is a nice swift implementation that works for me.
func animateCells() {
for cell in tableView.visibleCells() as! [UITableViewCell] {
//do someting with the cell here.
}
}
Accepted answer in swift for people who do not know ObjC (like me).
for section in 0 ..< sectionCount {
let rowCount = tableView.numberOfRowsInSection(section)
var list = [TableViewCell]()
for row in 0 ..< rowCount {
let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: row, inSection: section)) as! YourCell
list.append(cell)
}
}
for xcode 9 use this - (similar to #2ank3th but the code is changed for swift 4):
let totalSection = tableView.numberOfSections
for section in 0..<totalSection
{
print("section \(section)")
let totalRows = tableView.numberOfRows(inSection: section)
for row in 0..<totalRows
{
print("row \(row)")
let cell = tableView.cellForRow(at: IndexPath(row: row, section: section))
if let label = cell?.viewWithTag(2) as? UILabel
{
label.text = "Section = \(section), Row = \(row)"
}
}
}
for (UIView *view in TableView.subviews) {
for (tableviewCell *cell in view.subviews) {
//do
}
}
Since iOS may recycle tableView cells which are off-screen, you have to handle tableView one cell at a time:
NSIndexPath *indexPath;
CustomTableViewCell *cell;
NSInteger sectionCount = [tableView numberOfSections];
for (NSInteger section = 0; section < sectionCount; section++) {
NSInteger rowCount = [tableView numberOfRowsInSection:section];
for (NSInteger row = 0; row < rowCount; row++) {
indexPath = [NSIndexPath indexPathForRow:row inSection:section];
cell = [tableView cellForRowAtIndexPath:indexPath];
NSLog(#"Section %# row %#: %#", #(section), #(row), cell.textField.text);
}
}
You can collect an NSArray of all cells beforehands ONLY, when the whole list is visible. In such case, use [tableView visibleCells] to be safe.
quick and dirty:
for (UIView *view in self.tableView.subviews){
for (id subview in view.subviews){
if ([subview isKindOfClass:[UITableViewCell class]]){
UITableViewCell *cell = subview;
// do something with your cell
}
}
}
Here's a completely different way of thinking about looping through UITableView rows...here's an example of changing the text that might populate your UITextView by looping through your array, essentially meaning your tableView cell data.
All cells are populated with data from some kind of model. A very common model would be using an NSObject and NSMutableArray of those objects. If you were in didSelectRowAtIndexPath, you would then want to do something like this to affect the row you're selecting after modifying the array above:
for(YourObject *cellRow in yourArray)
{
if(![cellRow.someString isEqualToString:#""])
{
cellRow.someString = #"";
}
//...tons of options for conditions related to your data
}
YourObject *obj = [yourArray objectAtIndex:indexPath.row];
obj.someString = #"selected";
[yourArray insertObject:views atIndex:indexPath.row];
[yourArray removeObjectAtIndex:indexPath.row];
[yourTable reloadData];
This code would remove all the UITextField's text in every row except the one you selected, leaving the text "selected" in the tapped cell's UITextField as long as you're using obj.someString to populate the field's text in cellForRowAtIndexPath or willDisplayRowAtIndexPath using YourObject and yourArray.
This type of "looping" doesn't require any conditions of visible cells vs non visible cells. If you have multiple sections populated by an array of dictionaries, you could use the same logic by using a condition on a key value. Maybe you want to toggle a cells imageView, you could change the string representing the image name. Tons of options to loop through the data in your tableView without using any delegated UITableView properties.
swift 5:
guard let cells = self.creditCardTableView.visibleCells as? [CreditCardLoanCell] else {
return
}
cells.forEach { cell in
cell.delegate = self
}
I would like to add my two cents to the matter even though this post is old. I created an array of type UITableViewCell and appended each new cell to it before returning it in cellForRowAt. See code below:
var cellArray = [UITableViewCell]()
//UITableView code
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! Cell
//set up cell information
cellArray.append(cell)
return cell
}
Then if you need any information from each cell (i.e., UITextFields) in your Done button, you can iterate through the array like so in the desired context:
for cell in cellArray {
let myCell = cell as! Cell
//do stuff
}
Hope this helps anyone in the future

Resources