iOS 27: Scroll edge effect region sizes on UIRefreshControl presence, not its height

Summary

On iOS 27 the top scroll edge effect of a UITableView is sized around the UIRefreshControl as an edge element, but appears to key on the control's presence in the scroll view rather than its current height. After endRefreshing(), the control collapses to zero height but stays attached, and the effect region keeps reserving a refresh-control's worth of space. The first row stays blurred and dimmed even though it is scrolled fully clear of the navigation bar.

Environment

  • iOS 27 (beta), iPhone
  • Xcode 27.0.0 Beta 4
  • UIKit, UITableView inside a UINavigationController
  • Reproduces with the default edge effect style and with an explicit .soft

Steps to reproduce

  1. Push a UITableViewController with a UIRefreshControl onto a navigation stack.
  2. Ensure content extends under the navigation bar (default for UITableViewController).
  3. Pull to refresh and let the refresh end.
  4. Observe the first row after the refresh control has retracted.

Expected

Once the control retracts, the edge effect returns to its pre-pull height and the first row renders crisply.

Actual

The region stays roughly one refresh-control height too tall and the first row stays blurred. Row positions are unaffected — content offset and adjustedContentInset are correct. Only the extent of the effect is wrong. It corrects itself on the next push/pop.

Two probes that isolate it

These narrow the cause to the control's presence rather than a general staleness:

  1. Re-assigning tableView.topEdgeEffect.style after endRefreshing(), including on a later runloop turn, does NOT correct the region. The style is not what is stale.
  2. Detaching the control DOES correct it immediately: tableView.refreshControl = nil So the region is measured from the control being in the hierarchy, and is never re-measured when the control merely collapses.

Secondary issue

A UIRefreshControl that has been detached is inert if the same instance is re-assigned: tableView.refreshControl = nil tableView.refreshControl = sameInstance // never triggers again A freshly constructed UIRefreshControl must be assigned instead. This is not documented and looks like a second bug.

Minimal sample

Attached. A UITableViewController with a refresh control and a "Push" bar button that pushes and pops an empty view controller, so the incorrect region and the post-navigation correction can be compared in one run.

Related

FB20756572 reports the same "edge-effect extent is stale until the next navigation" behaviour for UIScrollEdgeElementContainerInteraction sizing, and notes a change in iOS 27 seed 1. That report received no reply.

Hello Javier,

Thank you very much for your detailed description of this issue. The root cause is likely the same as in FB20756572: this is an expected design change made in the beta. While the previously shifts might have been confusing, the design should behave more predictably in iOS 27.

Hoping this helps,

Richard Yeh  Developer Technical Support

If the iOS 27 extent follows the scroll view's edge elements, then a refresh control that has collapsed to zero height after endRefreshing() should contribute zero height. Right now it still reserves its expanded height until it's detached or until the next navigation. That doesn't match the new design either. Is the refresh control's height meant to be re-measured when it collapses? Separately, a UIRefreshControl that has been detached and re-assigned never fires again. That's unrelated to FB20756572

iOS 27: Scroll edge effect region sizes on UIRefreshControl presence, not its height
 
 
Q