I’m integrating BraintreePayPalMessaging into a UITableView/UICollectionView product listing.
Each row represents a product and may show a PayPal messaging banner.
The key behaviour of BTPayPalMessagingView in this context is:
- loads content asynchronously.
- While loading, we don’t want to show anything at all (no placeholder box), so the view stays hidden.
- Once it has content, we need to reveal it and the row’s height must grow to fit the banner.
On iOS, making a row change height means we must update the table/collection (reload a row, apply a diffable snapshot, etc.). But those updates are allowed to swap out the cell instance at that index. Since BTPayPalMessagingView lives inside a particular cell instance, there is no guarantee that the cell which ends up on screen after the update is the same one that owns the view that just became ready.
That’s the core conflict I’m running into.
What I’ve tried
I see two natural approaches, and both have serious problems.
1. Cell owns a hidden BTPayPalMessagingView
Each cell has a BTPayPalMessagingView subview. When configured for product X, we set the config - start(config:), keep the view hidden, and the row is laid out as if there is no banner.
Later, the view finishes loading and fires a delegate (“did appear”). To reveal it we must grow the row, which means updating the table/collection. But UITableView is free to reuse or replace the cell instance during that update, so the ready view stays attached to the “old” cell, and the “new” cell now representing that row may not contain the view that just became ready. There is no safe, UIKit-supported way to say:
“Whatever cell is now used for product X after this update, show this exact BTPayPalMessagingView that just became ready.”
Even if the user didn’t scroll at all, the act of updating the table so the row height changes can break the link between the ready view and the displayed cell.
2. Service owns one BTPayPalMessagingView per config
To decouple from cell instances, I tried a service that creates and caches one BTPayPalMessagingView per product/config. Cells ask the service for “the view for config X” and embed it:
- If it’s still loading → embed and keep hidden.
- If it’s ready → embed and show.
When the view for config X finishes loading, the service knows “config X is ready”. To reflect that in the UI, we still have to update the table/collection so the row height grows. After that update we can re-embed the cached view into whatever cell ends up representing config X. That keeps content logically correct (the right view for the right product), but now every async “ready” event means:
- changing some shared state,
- updating the data source snapshot or reloading rows,
- embedding during cell configurations,
- triggering new layouts and cell configurations.
With many products having banners that become ready at different times, these constant updates cause visible scroll jank / lag in practice.
So:
- If the view is tied to the cell → we can’t reliably reveal it when it becomes ready, because table updates can replace the cell instance.
- If the view is tied to the config → we can keep it correct, but the only way to reveal it (row height change) is to constantly update the list as async events come in, which hurts performance.
Ask
Right now it feels like BTPayPalMessagingView isn’t really designed with reusable UITableView/UICollectionView scenarios in mind, where async content appears later and changes row height — or maybe it is, but only if we keep an empty box while content is loading, guessing its future height and hoping it fits once the message is loaded. But that workaround isn’t acceptable UX in our case.
Could you please clarify:
- What is the intended way to integrate
BTPayPalMessagingView into a reusable list, where the banner must be completely hidden while loading and only shown (with a row height change) once it’s ready?
- Is there a recommended ownership model (view tied to cell vs. tied to config), or any best practices/sample code for this kind of integration?
- If the current API isn’t designed for this use-case, would you consider providing a more list-friendly abstraction (e.g. a model-based renderer, or a controller/host that plays nicely with cell reuse and async height changes)?
At the moment every approach I try either risks showing the wrong banner in the wrong cell, or causes noticeable jank from frequent table updates when banners become ready. Any guidance or API adjustments to make this scenario robust would be very appreciated.
I’m integrating BraintreePayPalMessaging into a
UITableView/UICollectionViewproduct listing.Each row represents a product and may show a PayPal messaging banner.
The key behaviour of
BTPayPalMessagingViewin this context is:On iOS, making a row change height means we must update the table/collection (reload a row, apply a diffable snapshot, etc.). But those updates are allowed to swap out the cell instance at that index. Since
BTPayPalMessagingViewlives inside a particular cell instance, there is no guarantee that the cell which ends up on screen after the update is the same one that owns the view that just became ready.That’s the core conflict I’m running into.
What I’ve tried
I see two natural approaches, and both have serious problems.
1. Cell owns a hidden
BTPayPalMessagingViewEach cell has a
BTPayPalMessagingViewsubview. When configured for product X, we set the config -start(config:), keep the view hidden, and the row is laid out as if there is no banner.Later, the view finishes loading and fires a delegate (“did appear”). To reveal it we must grow the row, which means updating the table/collection. But
UITableViewis free to reuse or replace the cell instance during that update, so the ready view stays attached to the “old” cell, and the “new” cell now representing that row may not contain the view that just became ready. There is no safe, UIKit-supported way to say:Even if the user didn’t scroll at all, the act of updating the table so the row height changes can break the link between the ready view and the displayed cell.
2. Service owns one
BTPayPalMessagingViewper configTo decouple from cell instances, I tried a service that creates and caches one
BTPayPalMessagingViewper product/config. Cells ask the service for “the view for config X” and embed it:When the view for config X finishes loading, the service knows “config X is ready”. To reflect that in the UI, we still have to update the table/collection so the row height grows. After that update we can re-embed the cached view into whatever cell ends up representing config X. That keeps content logically correct (the right view for the right product), but now every async “ready” event means:
With many products having banners that become ready at different times, these constant updates cause visible scroll jank / lag in practice.
So:
Ask
Right now it feels like
BTPayPalMessagingViewisn’t really designed with reusableUITableView/UICollectionViewscenarios in mind, where async content appears later and changes row height — or maybe it is, but only if we keep an empty box while content is loading, guessing its future height and hoping it fits once the message is loaded. But that workaround isn’t acceptable UX in our case.Could you please clarify:
BTPayPalMessagingViewinto a reusable list, where the banner must be completely hidden while loading and only shown (with a row height change) once it’s ready?At the moment every approach I try either risks showing the wrong banner in the wrong cell, or causes noticeable jank from frequent table updates when banners become ready. Any guidance or API adjustments to make this scenario robust would be very appreciated.