Override UIAccessibilityLayoutChangedNotification when UICollectionView changes layout - ios

I'm trying to enable VoiceOver accessibility on my collection view which behaves as follows:
Upon selection of an item, it will remove the rest of the items in the section, leaving only the item that was selected.
When I select the item again, it will re-populate the section with the items that was previously removed.
For step 1, voiceover reads out the item just fine, and it'll be the focus after the layout change.
However, for step 2, things get a bit weird. It'll start reading out the item, and just before it finishes, the focus jumps to another item, and begins reading that one instead.
From Apple's documentation about collection views, it automatically posts a layout change notification when a layout occurs. But, from the looks of it, it's passing on some random element to the first focus element after the layout change.
When a collection view changes its onscreen layout, it posts the UIAccessibilityLayoutChangedNotification notification.
Is there anyway to override this automatic layout changed notification, so that I can pass in the correct focus element?

I found the reason for what was happening.
The reason an element was randomly focusing was because I was calling reloadData on the collectionview in the performBatchUpdate's completion block.
Removing the reloadData solved this problem. However, because I'm not calling reloadData, I had to call reloadItemsAtIndexPaths on specific cells, which is fine in my case.

Related

Crash with UICollectionView and custom flow layout

I've implemented sticky section headers for a collection view, using http://blog.radi.ws/post/32905838158/sticky-headers-for-uicollectionview-using as a jumping off point, and they work.
But I'm seeing a very weird crash.
When I push an editor detail view, change the item's name such that it moves from one section to another ( think, changing the last name of a person in a contacts list which is grouped by first letter of last name), and then pop back to the collection view it crashes with a complaint
UICollectionView received layout attributes for a cell with an index path that does not exist
Here's the flow of execution:
In the editor detail view, I change the item's name such that it will move from section X to section Y.
The item's model emits a "name changed" notification.
The root view controller which owns the collection view catches that "name changed" notification, rebuilds its internal indexes, and then calls -reloadData on the collection view. This is all good.
I hit the Back button in the UI, and the following flow happens ( confirmed via debugger and caveman NSlog calls )
numberOfSectionsInCollectionView: is called, and my code returns correct number of sections
collectionView:numberOfItemsInSection: is called for each section, and correct number of items is returned
my custom flow layout's -layoutAttributesForElementsInRect: is called. The first thing I do in there is call [super layoutAttributesForElementsInRect] to get a baseline layout.
Logging the the inherited baseline layout I see attributes for the previous arrangement of cells, not the current one. E.g., the layout is for the arrangement before the edits I just made. So, incorrect sections and or cells in incorrect sections.
Now here's what blows my mind.
If I comment out the entire implementation of -layoutAttributesForElementsInRect, it still crashes. But, if I comment out:
- (BOOL) shouldInvalidateLayoutForBoundsChange:(CGRect)newBound {
return YES;
}
Then, it works correctly.
This tells me that the collection view, or something in flow layout is caching results, but only if the flow layout shouldInvalidateLayoutForBoundsChange
Note, if I just use a vanilla UICollectionViewFlowLayout everything works fine.
TLDR
Custom UICollectionViewFlowLayout, if -shouldInvalidateLayoutForBoundsChange returns YES, gets out-of-date layout attributes from [super layoutAttributesForElementsInRect]
Any ideas?
I solved this by removing the shouldInvalidateLayoutForBoundsChange: override, and instead implementing scrollViewDidScroll: on the collection view's delegate to invalidate layout:
override func scrollViewDidScroll(scrollView: UIScrollView) {
collectionView?.collectionViewLayout.invalidateLayout()
}
This keeps the sticky headers but stops the crashing.

How to completely reset a UITableViewController while still on screen

In my app, I have a very custom UITableView. The cells are all statically defined in Interface Builder, but based on the data structure the table morphs in many various ways. For example, if some data doesn't exist, some cells (or entire sections) are not displayed, custom separator lines are added to account for missing cells, extra views are loaded into the cells, VoiceOver labels change, etc. Because all the cells are static, I set up the table layout in viewDidLoad because I always have the data available at that time. I have always presented this view controller modally, which has worked great. If the user wants to display different data in this table they have to dismiss the view controller and pick a different item to present it again, and it gets rendered appropriately in all cases.
But now I am converting this into a split view controller for iPad, so this UITableViewController never disappears off screen, but I need to set up the table again when the user taps an item. The problem is, because the table is never deallocated, its previous layout still exists when I load more data into it. It would be a lot of work (and an excellent opportunity for many difficult to reproduce bugs to pop up) to test all possible scenarios and try to reset it back to its "pre viewDidLoad state" or undo those previous layout changes if not relevant anymore, if not impossible because I don't have references to the many different custom separator lines generated.
My question is, is it possible to completely reset the table view controller every time a row is selected in the master view controller, therefore allowing it to properly set up the layout because it is not stuck with the previous layout?
I essentially need some way to completely wipe it clean as if it never did any setup, then instantiate it again to cause viewDidLoad get called (or I can move that code to its own method or viewWillAppear). I'm basically looking for a way to reset the tableView back to how it is defined in Interface Builder.
I believe this would result in a flash because the table would completely disappear then reappear in a different format, but that would be acceptable. If that can be animated that'd be nice. If this is really not recommended at all, how do you suggest I proceed to ensure the layout is always appropriate for the data it is presenting?
I was over-thinking this. There's really no need to completely throw away the table and generate a new one. It turned out to be simpler than I had thought to reset the table back to its default state. Just had to be sure to catch every possible thing that could change, including VoiceOver labels, and reset to nil or the default value. Then it can run through the reset code then the layout code every time the data changes and render an appropriate layout. The most difficult part was to remove the custom separator lines, which I solved by adding each one to an array when it's created, then index through it and remove each one from its superview then remove the Autolayout constraints associated with it. One can wrap all of this into a UIView animation block to get a nice fading effect. It's working quite well.

Allowing selection while reloading UITableView section

I have a basic tableview with section headers that contains cells. A cell either has a checkmark visible or not. When the user selects the cell, it toggles the checkmark, and if all items in that section has the checkmark visible, additionally the section header is greyed out.
I trigger the UI update in tableView:didSelectRowAtIndexPath: by first updating the model and then call reloadSections:withRowAnimation:UITableViewRowAnimationAutomatic on the relevant section.
The problem is that when animating the change (duration about 0.3 s), the user cannot select another row until the animation is done. It is a common usage scenario, wanting to check a number of items in batch. I also absolutely want to animate the change.
The documentation states that one can configure a UIAnimation using UIViewAnimationOptionAllowUserInteraction to remedy this, but since I'm not using a manual UIAnimation but only calling UITableViewRowAnimationAutomatic, I don't know how to accomplish this.
Is there a way to allow the user to select (and initiate animations) on other cells while such an animation/reload is ongoing?
Note
It's tricky to get a custom section header to redraw/animate, the only way I managed to do it is by first reloading the affected section, and then immediately reloading the table – the latter to hide a flicker that appears otherwise.
If I only reload the single affected row, there is not animation on the section header – it just changes.
I ended up disregarding that the section header does not animate, the user experience is more severely affected by not being able to quickly toggle several items in the list.

Is there a way to watch for a selection to change in UITableView?

I have a UITableViewController that comes on to the screen in a popup. Basically it slides in from the left and shows the hierarchy available.
When it comes on screen, I select an item within it to represent the current location. Before today, this all worked.
Today I added a UITextView as a header to the table. Since then, my selection is acting strangely. When the UITableViewController appears, I can see my selected row get selected briefly, but it immediately becomes unselected. Calls to indexPathForSelectedRow return nil.
Is there a way to be alerted whenever the selection changes in UITableView? I know to use tableView:didSelectRowAtIndexPath: to keep track of when the user selects an item, but this problem is happening without user input. I'm assuming I've done something incorrect within the code, but I'm trying to track it down without posting all my code here.
Edit
Apropos of nothing, I did find that the problem goes away if I remove the Navigation Controller that I had added to the UITableViewController. This was because I had not set the property clearsSelectionOnViewWillAppear to NO. By adding the Navigation Controller, the UITableViewController was getting loaded differently, causing the property to actually be used.
However, I still would like to know how to be alerted to table view selection changes.

UICollection view delete item rearrangement weird animation

I have a basic UICollectionView and a basic list of items. I've implemented a delete button on the items so that the user can remove some of them. Everything works great apart from the item rearrangement while the item is being deleted.
If you delete the item N-1, it visually deletes the first one in the list, moves the last one to the first position and "reuses all the cells" to set the correct content.
Basically, I would have expected it to move the last one to the N-1 item.
NOTE: This expected behavior happens when I've got one line and 2 items which makes me believe I did things right somehow.
Questions are :
Is this the standard behaviour of a collection view when deleting object ?
How can I fix it quickly without rewriting the whole thing ?
I figured out the problem. It happened that I was posting a notification after the deleteItemAtIndexPath which was actually calling a setter which was calling collectionView reloadData. This reloadData wasn't supposed to be called at this stage so I fixed it and it works like a charm.
So, if you have funny animations make sure you're not calling a reloadData in a way or another.

Resources