Commit 2950e88
Fix iOS CollectionView stale layout invalidations (#35245)
### Description of Change
There are two related races in the iOS CollectionView handler when the
item source changes while cells are still participating in UIKit layout.
The first race happens after a visible templated cell invalidates its
measure. MAUI records that state on the cell and the next
ViewWillLayoutSubviews pass asks UICollectionViewFlowLayout to
invalidate exactly those changed items. Previously the handler collected
the cells first and converted them back to index paths later, at the
point where the invalidation context was created. If the bound
ItemsSource had changed in the meantime, for example an observable
source inserted items and the view then cleared or replaced ItemsSource
before the next layout pass, UIKit could still report a visible cell
whose current index path no longer existed in MAUI's new ItemsSource.
Passing that stale index path to InvalidateItems leaves UICollectionView
and the data source with inconsistent item counts and can crash during
layout.
Fix that path by resolving each invalidated visible cell to an
NSIndexPath immediately and keeping only paths that are still valid for
the current ItemsSource. The invalidation context is then built from the
validated paths. This preserves targeted invalidation for normal measure
changes, while dropping cells that belong to the previous source state
and cannot be safely invalidated by item path anymore.
The second race involves measurement cells. ItemsViewController keeps
prototype templated cells in _measurementCells so their realized content
can be transferred to real UICollectionView cells. When an ItemsSource
update, empty-source transition, or source disposal clears that
dictionary, the old code only removed the references. Those measurement
cells could still be bound to item view models and still subscribed to
LayoutAttributesChanged. Later binding or property changes from that
stale content could propagate measure/layout invalidations through cells
that are no longer owned by the active source state. In the worst case
this combines with UIKit's pending layout work after ReloadData or
source clearing and contributes to the same stale layout invalidation
problem; it can also keep disconnected measurement content behaving as
if it were still live.
Fix that by centralizing measurement-cell clearing. Before the cache is
cleared, each cached cell is detached from the layout-attribute event
and unbound so its BindingContext is removed and future measure
invalidations from that stale measured content do not flow back into the
CollectionView layout.
The regression test reproduces the important ordering: a templated
CollectionView is displayed, a visible label changes text so the cell
measure is invalidated, the observable source mutates, and ItemsSource
is immediately cleared before UIKit finishes its next layout pass. The
test forces layout afterward and verifies this no longer crashes.
### Issues Fixed
Fixes #35244
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>1 parent 6e8d8e4 commit 2950e88
3 files changed
Lines changed: 113 additions & 14 deletions
File tree
- src/Controls
- src/Core/Handlers
- Items2/iOS
- Items/iOS
- tests/DeviceTests/Elements/CollectionView
Lines changed: 23 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
139 | 139 | | |
140 | 140 | | |
141 | 141 | | |
142 | | - | |
| 142 | + | |
143 | 143 | | |
144 | 144 | | |
145 | 145 | | |
| |||
257 | 257 | | |
258 | 258 | | |
259 | 259 | | |
260 | | - | |
| 260 | + | |
261 | 261 | | |
262 | 262 | | |
263 | 263 | | |
264 | 264 | | |
265 | 265 | | |
266 | 266 | | |
267 | | - | |
268 | | - | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
269 | 273 | | |
270 | 274 | | |
271 | 275 | | |
272 | | - | |
| 276 | + | |
273 | 277 | | |
274 | 278 | | |
275 | 279 | | |
| |||
280 | 284 | | |
281 | 285 | | |
282 | 286 | | |
283 | | - | |
| 287 | + | |
284 | 288 | | |
285 | 289 | | |
286 | 290 | | |
| |||
419 | 423 | | |
420 | 424 | | |
421 | 425 | | |
422 | | - | |
| 426 | + | |
423 | 427 | | |
424 | 428 | | |
425 | 429 | | |
| |||
441 | 445 | | |
442 | 446 | | |
443 | 447 | | |
444 | | - | |
| 448 | + | |
445 | 449 | | |
446 | 450 | | |
447 | 451 | | |
| |||
530 | 534 | | |
531 | 535 | | |
532 | 536 | | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
533 | 548 | | |
534 | 549 | | |
535 | 550 | | |
| |||
Lines changed: 9 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
214 | 214 | | |
215 | 215 | | |
216 | 216 | | |
217 | | - | |
| 217 | + | |
218 | 218 | | |
219 | 219 | | |
220 | 220 | | |
221 | 221 | | |
222 | 222 | | |
223 | 223 | | |
224 | | - | |
225 | | - | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
226 | 230 | | |
227 | 231 | | |
228 | 232 | | |
229 | | - | |
| 233 | + | |
230 | 234 | | |
231 | 235 | | |
232 | | - | |
| 236 | + | |
233 | 237 | | |
234 | 238 | | |
235 | 239 | | |
| |||
Lines changed: 81 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
251 | 251 | | |
252 | 252 | | |
253 | 253 | | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
254 | 334 | | |
255 | 335 | | |
256 | 336 | | |
| |||
487 | 567 | | |
488 | 568 | | |
489 | 569 | | |
490 | | - | |
| 570 | + | |
0 commit comments