Commit daf359e
[maps-ios] Fix PointAnnotation silent-fail for SF Symbol images (MAPSIOS-2182) (#12415)
## Summary
`PointAnnotation.image` and
`StyleManager.addImage(_:id:sdf:contentInsets:)` silently failed for any
`UIImage` whose `size * scale` is fractional. The common trigger is an
SF Symbol passed through `.withTintColor(_:renderingMode:
.alwaysOriginal)` — the usual way to colorize a symbol before using it
as a marker icon.
Two related floating-point-to-integer mismatches combined to produce the
failure — the second was hidden behind the first until the first was
fixed.
### 1. `Data(uiImage:)` fallback redrew at cgImage dimensions
`CoreMapsImage.init?(uiImage:)` declares the image to the native style
API as `uiImage.size * uiImage.scale` pixels. For SF Symbols and
`.withTintColor(...)` results, the backing `cgImage` pixel dimensions
diverge from `size * scale`. The `default:` fallback in
`Data.init(uiImage:)` produced a buffer sized to cgImage dims, so the
byte count did not match the declared dimensions. Core:
```
StyleError(rawValue: "Raster image reference has invalid data size")
```
### 2. Stretches overshoot declared bounds by sub-pixel amounts
With (1) addressed, a second validation surfaced: `ImageProperties`
computed stretch and content-box values as `Float(size) * scale` — a
non-integer when `size * scale` is fractional — while the declared image
width on native is `UInt32(size * scale)`, which truncates. The stretch
right edge then lay 0.x pixels past the declared edge. Core:
```
StyleError(rawValue: "expected stretchX area lies within an image (width 108), got { left: 0, right: 109 }")
```
### Reproduction, from `maps-agent-readiness-test/ios/t3-stretch`
```swift
let config = UIImage.SymbolConfiguration(pointSize: 30, weight: .bold)
let pin = UIImage(systemName: "mappin.circle.fill", withConfiguration: config)!
.withTintColor(.systemRed, renderingMode: .alwaysOriginal)
annotation.image = .init(image: pin, name: "pin") // no marker, two warnings in console
```
## Fix
- `Data.init(drawing:)` — now takes a `UIImage` (was `CGImage`),
allocates the buffer at `size * scale`, and uses `UIImage.draw(at:)`
into a `CGContext` scaled by `uiImage.scale` with a flipped Y axis. Fast
paths for PNG/JPEG/padded-row images are untouched.
- `ImageProperties` — floors stretch and content-box values to the same
`Int(size * scale)` bounds the image is declared at, so they cannot
overshoot the declared dimensions for any UIImage.
## Wrong-track note
This supersedes the closed #12390, which misidentified the root cause as
a nil `cgImage` on SF Symbols. `UIImage(systemName:).cgImage` is non-nil
on iOS 14+ (empirically verified against iOS 15.5, 16.4, 18.6
simulators).
## Jira
MAPSIOS-2182
## Test plan
- [x] `xcodebuild test -scheme MapboxTestHost
-only-testing:MapboxMapsTests/ImageTests` — all 7 tests pass, including
two new regressions that exercise the exact t3-stretch repro
(`testSymbolImageRoundTripsWithMatchingDataSize`,
`testImagePropertiesStretchWithinDeclaredBoundsForSymbolImage`).
- [ ] Visually verify in `maps-agent-readiness-test/ios/t3-stretch` by
pointing the app at this local SDK build and confirming the coffee-shop
pins render.
- [ ] Full `make build-source-tests-sim` run.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
cc @mapbox/maps-ios
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Changelog autocreator bot <github-actions[bot]@users.noreply.github.com>
GitOrigin-RevId: 892defc6624da88ae1d521f1e7076ba4e5f3f2b51 parent 31e2461 commit daf359e
3 files changed
Lines changed: 83 additions & 9 deletions
File tree
- Sources/MapboxMaps/Style/Types
- Tests/MapboxMapsTests/Style
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
72 | 72 | | |
73 | 73 | | |
74 | 74 | | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
79 | | - | |
80 | | - | |
81 | | - | |
82 | | - | |
83 | | - | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
84 | 96 | | |
85 | 97 | | |
86 | 98 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
139 | 139 | | |
140 | 140 | | |
141 | 141 | | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
142 | 203 | | |
0 commit comments