Repository navigation
Reuse solid border images across iOS Views with the same border - #59206
Open
hryhoriiK97 wants to merge 2 commits into
Open
hryhoriiK97 wants to merge 2 commits into
hryhoriiK97 wants to merge 2 commits into
Conversation
A stretchable solid border image depends only on the corner radii, border widths and colors, not on the View's size, yet every invalidateLayer drew a new one. Keep stretchable images in an NSCache keyed by those inputs so Views with the same border share one image. Images drawn with dynamic colors are not cached, since they depend on the trait collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An NSArray key hashes by its element count and NSValue gives every struct of a custom type the same hash, so lookups compared against every cached key. Use a small key class that hashes the radii and widths itself and compares all inputs exactly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Updated: the cache key is now a small class with a real hash (an |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary:
RCTViewComponentView(React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm) draws a View's border into a bitmap whenever Core Animation can't draw it: in particular a visible border on a View that doesn't clip its content, since Core Animation draws a layer's border above its sublayers while CSS draws it behind the content. That is the common card, button and input style withborderRadiusandborderWidth. The bitmap comes fromRCTGetSolidBorderImage(React/Views/RCTBorderDrawing.m), which runs on everyinvalidateLayer, so on every mount and on every size or border change. Views whose borders Core Animation draws, or that have no border, never reach this function.Current behavior. Each call renders a new image with
UIGraphicsImageRenderer. In the common case the image is stretchable: its size and pixels depend only on the corner radii, border widths and colors, not on the View's size. A list of identical cards still draws the same image once per card.With this change. Stretchable images are kept in an
NSCacheof at most 64 entries, keyed by everything the drawing reads: the corner radii, the border widths as passed in (the corner insets are computed from them before pixel rounding), the four border colors, the background color anddrawToEdge. Views with the same border reuse one image instead of each drawing its own. The key is a small class that hashes the radii and widths itself, because anNSArrayhashes by its element count andNSValuegives every struct of a custom type the same hash; it compares all inputs exactly. Images drawn with a dynamic color are not cached, because they depend on the trait collection; a color counts as static when resolving it returns the same object, which is how UIKit treats non-dynamic colors. Non-stretchable images and dashed or dotted borders are drawn as before. The drawing code is unchanged, so the pixels are too.The 64 entries count distinct border styles, not Views, since the key doesn't include the View's size. When an app uses more styles than that,
NSCacheevicts older entries and a miss draws the image as before.Changelog:
[IOS] [CHANGED] - Views with the same solid border share one border image instead of each drawing its own
Test Plan:
clang-format --dry-run --Werroron the changed file passes.xcodebuildin Release and run on a physical iPhone 15 Pro (iOS 26.6.1).systemRedColorandlabelColor, in both light and dark appearance.ScrollView, each aViewwrapping aText, with four contents: plain (no border or shadow), card (borderRadius: 8,borderWidth: 1, solidbackgroundColor,shadowColor/Opacity/Radius/Offset), the same card withboxShadowinstead, and the card with a differentborderRadiuson every item, so no two items share a border image and every lookup misses. Timed: the main-thread mount phase of the transaction (TransactionTelemetry), a timer aroundinvalidateLayer, and press-to-mount from the Event Timingtouchendentry. The two arms are the same binary with the cache switched by a launch argument, in the order before, after, after, before. The presses were driven by Argent on the physical iPhone. iPhone 15 Pro, Release, medians:invalidateLayerboxShadowcards: mount phaseinvalidateLayerboxShadowcards: mount phaseboxShadowcards: press-to-mountAt 20 items, press-to-mount differs by a few milliseconds in both directions between the arms, within its noise.
The same idea was proposed in #15281 in 2017 and closed as stale.
🤖 Generated with Claude Code