| name | new-api-support |
| description | Add introspection support for a SwiftUI API (view type, modifier, or View extension function). Use when the user wants to add support for a new SwiftUI entity to ViewInspector. |
| argument-hint | <entity_name> |
new-api-support
Add introspection support for a SwiftUI API (view type, modifier, or View extension function).
Usage
/new-api-support <entity_name>
Where <entity_name> is:
- A SwiftUI struct name (e.g.,
ContentUnavailableView, ProgressView)
- A View extension function name (e.g.,
onAppear, disabled, opacity)
- A modifier struct name (e.g.,
ScaledMetric)
Workflow
Step 1: Locate and Catalog the API
Find the API in the local iOS SDK (prefer local over network):
find /Applications/Xcode.app/Contents/Developer/Platforms -name "SwiftUI.swiftmodule" -type d 2>/dev/null | head -5
grep -r "<entity_name>" /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/*.swiftinterface 2>/dev/null | head -50
grep -r "<entity_name>" /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/*.swiftinterface 2>/dev/null | head -50
Catalog ALL related APIs:
- For functions: Find all overloads (same name, different parameters)
- For structs: Find the struct definition AND any View extension functions that return this type
- Note ALL
@available attributes for each API variant
Example catalog format:
Entity: .buttonStyle(_:)
Type: View extension function
Related APIs:
1. func buttonStyle<S>(_ style: S) -> some View where S : ButtonStyle
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
2. func buttonStyle<S>(_ style: S) -> some View where S : PrimitiveButtonStyle
@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *)
Step 2: Research Usage Context
Understand HOW the API is meant to be used by researching its typical context:
-
Search for documentation and usage patterns:
- Use web search to find Apple documentation and WWDC sessions
- Look for common usage patterns in tutorials and Stack Overflow
- Identify which parent views/containers the API is typically used with
-
Identify related parent/child relationships:
| Entity Type | Find Related Context |
|---|
| View inside container | Which container views typically hold this view? (e.g., Tab goes inside TabView) |
| View modifier | Which views is this modifier typically applied to? (e.g., subscriptionStoreButtonLabel applies to SubscriptionStoreView) |
| Container-specific modifier | Which container makes this modifier meaningful? (e.g., listRowBackground on views inside List) |
| Style modifier | Which view type does this style affect? (e.g., buttonStyle on Button) |
-
Document the context for test design:
Example context analysis:
Entity: Tab
Type: View
Typical Context: Used as direct child of TabView
Related APIs: TabView, tabItem (deprecated predecessor)
Test Structure: Tab should be tested INSIDE TabView hierarchy
Entity: subscriptionStoreButtonLabel
Type: View modifier
Typical Context: Applied to SubscriptionStoreView
Related APIs: SubscriptionStoreView, SubscriptionStoreButton
Test Structure: Apply modifier to SubscriptionStoreView, not EmptyView
-
Check for container-dependent behavior:
- Some modifiers only work in specific contexts (e.g.,
listRowInsets only meaningful in List)
- Some views only function inside specific parents (e.g.,
Section in List or Form)
- Test in the correct context to ensure real-world usability
Step 3: Determine File Placement
For new View types (structs like ProgressView, ContentUnavailableView):
- Create new file:
Sources/ViewInspector/SwiftUI/<ViewName>.swift
- Create test file:
Tests/ViewInspectorTests/SwiftUI/<ViewName>Tests.swift
For View modifiers/functions, find the appropriate existing file by category:
| Category | Source File | Test File |
|---|
| Animation (.animation, .transition) | Modifiers/AnimationModifiers.swift | ViewModifiers/AnimationModifiersTests.swift |
| Configuration (.disabled, .labelsHidden) | Modifiers/ConfigurationModifiers.swift | ViewModifiers/ConfigurationModifiersTests.swift |
| Environment (.environment, .environmentObject) | Modifiers/EnvironmentModifiers.swift | ViewModifiers/EnvironmentModifiersTests.swift |
| Interaction (.onTapGesture, .onAppear) | Modifiers/InteractionModifiers.swift | ViewModifiers/InteractionModifiersTests.swift |
| Positioning (.offset, .position) | Modifiers/PositioningModifiers.swift | ViewModifiers/PositioningModifiersTests.swift |
| Sizing (.frame, .fixedSize) | Modifiers/SizingModifiers.swift | ViewModifiers/SizingModifiersTests.swift |
| Text input (.keyboardType, .textContentType) | Modifiers/TextInputModifiers.swift | ViewModifiers/TextInputModifiersTests.swift |
| Transform (.rotationEffect, .scaleEffect) | Modifiers/TransformingModifiers.swift | ViewModifiers/TransformingModifiersTests.swift |
| Navigation bar (.navigationTitle) | Modifiers/NavigationBarModifiers.swift | - |
| Custom styles (.buttonStyle, .pickerStyle) | Modifiers/CustomStyleModifiers.swift | - |
Check existing files to confirm the pattern:
grep -l "similar_modifier" Sources/ViewInspector/Modifiers/*.swift
Step 4: Reverse Engineering Investigation
Create a reverse engineering test to understand the internal structure:
import XCTest
import SwiftUI
@testable import ViewInspector
final class ReverseEngineeringTests: XCTestCase {
func testInvestigate_<EntityName>() throws {
let sut = EmptyView().<targetAPI>()
print("\(Inspector.print(sut) as AnyObject)")
}
}
Run the investigation test:
swift test --filter "testInvestigate_"
Analyze the output to identify:
- The internal modifier/view type name (e.g.,
_AppearanceActionModifier)
- Property names and their paths (e.g.,
appear, disappear)
- Nested structure for complex types
- Whether it uses
ModifiedContent wrapper
Example Inspector.print output:
EmptyView
→ _AppearanceActionModifier
modifier: _AppearanceActionModifier
appear: Optional<() -> ()>
some: (Function)
disappear: Optional<() -> ()>
none
Iterate investigation for each API variant and parameter combination to understand all internal structures.
Step 5: Implement Introspection Support
For View modifiers, add to the appropriate Modifiers file:
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
public extension InspectableView {
func <modifierName>() throws -> <ReturnType> {
return try modifierAttribute(
modifierName: "<InternalModifierName>",
path: "modifier|<propertyPath>",
type: <ReturnType>.self,
call: "<modifierName>")
}
func call<CallbackName>() throws {
let callback = try modifierAttribute(
modifierName: "<InternalModifierName>",
path: "modifier|<callbackPath>",
type: (() -> Void).self,
call: "call<CallbackName>")
callback()
}
}
For new View types, create the full ViewType structure:
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
public extension ViewType {
struct NewViewType: KnownViewType {
public static let typePrefix: String = "NewViewType"
public static var namespacedPrefixes: [String] {
["SwiftUI.NewViewType"]
}
}
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
extension ViewType.NewViewType: SingleViewContent {
public static func child(_ content: Content) throws -> Content {
return try Inspector.attribute(path: "content", value: content.view)
}
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
public extension InspectableView where View == ViewType.NewViewType {
func someAttribute() throws -> SomeType {
return try Inspector.attribute(
path: "attributePath",
value: content.view,
type: SomeType.self)
}
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
public extension InspectableView {
func newViewType(_ index: Int? = nil) throws -> InspectableView<ViewType.NewViewType> {
return try contentForModifierLookup.newViewType(parent: self, index: index)
}
}
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
internal extension Content {
func newViewType(parent: UnwrappedView, index: Int?) throws
-> InspectableView<ViewType.NewViewType> {
let call = "newViewType(\(index == nil ? "" : "\(index!)"))"
return try .init(try Inspector.attribute(path: "content", value: view),
parent: parent, call: call, index: index)
}
}
Step 6: Add Tests
IMPORTANT: Use contextual test structure based on Step 2 research.
Tests should reflect real-world usage patterns, not just technical functionality.
For views that belong inside specific containers:
import XCTest
import SwiftUI
@testable import ViewInspector
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
final class TabTests: XCTestCase {
func testTabInsideTabView() throws {
let sut = TabView {
Tab("Home", systemImage: "house") {
Text("Home Content")
}
Tab("Settings", systemImage: "gear") {
Text("Settings Content")
}
}
let tab = try sut.inspect().tabView().tab(0)
XCTAssertEqual(try tab.labelView().text().string(), "Home")
}
func testSearchForContentInsideTab() throws {
let sut = TabView {
Tab("Home", systemImage: "house") {
Text("Home Content")
}
}
XCTAssertEqual(
try sut.inspect().find(text: "Home Content").pathToRoot,
"tabView().tab(0).text()")
}
}
For modifiers that apply to specific view types:
@available(iOS 17.0, macOS 14.0, *)
final class SubscriptionModifiersTests: XCTestCase {
func testSubscriptionStoreButtonLabel() throws {
let sut = SubscriptionStoreView(productIDs: ["com.app.subscription"])
.subscriptionStoreButtonLabel(.multiline)
let label = try sut.inspect().subscriptionStoreView().subscriptionStoreButtonLabel()
XCTAssertEqual(label, .multiline)
}
}
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
final class ListModifiersTests: XCTestCase {
func testListRowBackgroundInsideList() throws {
let sut = List {
Text("Row")
.listRowBackground(Color.red)
}
let background = try sut.inspect().list().text(0).listRowBackground()
}
}
For style modifiers, test with the view type they style:
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
final class ButtonStyleTests: XCTestCase {
func testButtonStyleOnButton() throws {
let sut = Button("Tap") { }
.buttonStyle(.borderedProminent)
let style = try sut.inspect().button().buttonStyle()
}
}
Generic test file structure (when no specific context applies):
import XCTest
import SwiftUI
@testable import ViewInspector
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
final class NewViewTypeTests: XCTestCase {
func testExtractionFromSingleViewContainer() throws {
let view = AnyView(NewViewType())
XCTAssertNoThrow(try view.inspect().anyView().newViewType())
}
func testSomeAttributeInspection() throws {
let sut = NewViewType(someParam: .value)
let value = try sut.inspect().newViewType().someAttribute()
XCTAssertEqual(value, .value)
}
func testSearch() throws {
let view = HStack { NewViewType() }
XCTAssertEqual(try view.inspect().find(ViewType.NewViewType.self).pathToRoot,
"hStack().newViewType(0)")
}
}
For generic modifiers (not container-specific):
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
final class SomeModifierTests: XCTestCase {
func testModifierApplication() throws {
let sut = EmptyView().someModifier(value: 42)
XCTAssertNoThrow(try sut.inspect().emptyView())
}
func testModifierValueInspection() throws {
let sut = EmptyView().someModifier(value: 42)
let value = try sut.inspect().emptyView().someModifier()
XCTAssertEqual(value, 42)
}
}
Run tests incrementally (headless - fast iteration):
swift test --filter "NewViewTypeTests/testExtractionFromSingleViewContainer"
swift test --filter "NewViewTypeTests"
Final verification on platform simulators:
Headless swift test is fast and suitable for development iteration, but final verification must run on actual platform simulators for all platforms the API supports.
Based on the API's @available attributes, verify tests pass on each supported platform:
xcodebuild test -scheme ViewInspector -destination 'platform=iOS Simulator,name=iPhone 16'
xcodebuild test -scheme ViewInspector -destination 'platform=tvOS Simulator,name=Apple TV'
xcodebuild test -scheme ViewInspector -destination 'platform=macOS'
xcodebuild test -scheme ViewInspector -destination 'platform=visionOS Simulator,name=Apple Vision Pro'
xcodebuild test -project .watchOS/watchOS.xcodeproj -scheme watchOS -destination 'platform=watchOS Simulator,name=Apple Watch Series 10 (46mm)'
Platform testing requirements:
- If API is
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) → test on all four platforms
- If API is
@available(iOS 15.0, *) with @available(macOS, unavailable) → only test iOS
- watchOS is peculiar: always use
.watchOS/watchOS.xcodeproj instead of the main package
Quick platform matrix check:
xcrun simctl list devices available
Step 7: Update readiness.md
Add the new API to readiness.md in the appropriate section:
For View types, add to the "View Types" table:
|:white_check_mark:| NewViewType | `attribute1`, `attribute2`, `containedView` |
For Modifiers, add to the "View Modifiers" table:
|:white_check_mark:| `.someModifier(value:)` | `someModifier() -> Type` |
Maintain alphabetical order within each section.
Step 8: Update unsupported_swiftui_apis.md
After adding support for a new API, check if unsupported_swiftui_apis.md exists in the repository root and remove the entry for the newly supported API:
ls unsupported_swiftui_apis.md
grep -n "<entity_name>" unsupported_swiftui_apis.md
Remove the entry from the appropriate section:
- For view types: Remove from "View Types - Not Supported" or "View Types - Partial Support"
- For modifiers: Remove from "View Modifiers - Not Supported"
- For property wrappers: Remove from "Property Wrappers - Not Supported"
- For partial support completions: Remove from "View Types - Partial Support" if now fully supported
Also remove from Quick Reference if the API was listed there:
# Remove lines like:
/new-api-support Gauge
/new-api-support "Group(subviews:)"
If the file doesn't exist, skip this step.
Important Notes
-
Always check @available attributes - Copy them exactly from the SDK and apply to all introspection code and tests
-
Use XCTAssertThrows instead of XCTAssertThrowsError (enforced by SwiftLint)
-
Handle platform differences - Some APIs have different availability on iOS/macOS/tvOS/watchOS. Use #if os() when needed
-
Test all overloads - Each function variant may have different internal structure
-
Modifier path format - Use | as path separator: "modifier|property|nestedProperty"
-
Common internal type prefixes:
_ prefix: Internal SwiftUI types (e.g., _AppearanceActionModifier)
Modified suffix: Wrapped content (e.g., ModifiedContent)
-
For callbacks/closures - Store them via try Inspector.attribute() then invoke
-
Registration in ViewSearchIndex - For new view types, check if registration in ViewSearchIndex.swift is needed for find() to work
Public API Design Principles
-
Never return Any from public APIs - Consumers need typed values they can work with
-
Prefer returning SwiftUI types over String, Any, or custom types:
- If a public SwiftUI type exists (like
GlassEffectTransition), return it
- When internal types (e.g.,
_GlassEffectTransition) can't be cast to public types, investigate if you can map string descriptions to public type values
-
Create wrapper types for complex modifier parameters:
- Instead of multiple methods like
glassEffectTintColor() and glassEffectShape(), create a wrapper ViewType.GlassEffect with methods tintColor(), shape(), etc.
- The wrapper holds the internal config and provides typed accessors
- Example pattern:
public extension ViewType {
struct GlassEffect {
private let config: Any
public func tintColor() throws -> SwiftUI.Color? { ... }
public func shape<S>(_ type: S.Type) throws -> S where S: SwiftUI.Shape { ... }
}
}
- Note: When inside ViewType namespace, use fully qualified names like
SwiftUI.Color and SwiftUI.Shape to avoid conflicts with other ViewType members
-
Add BinaryEquatable conformance to the public library API (not just tests) for SwiftUI types that consumers may want to compare in their tests
Investigation Techniques
-
Inspector.print() returns a String - Must wrap with print() to see output:
print(Inspector.print(someValue))
Inspector.print(someValue)
-
Investigate chained method calls to understand behavior:
- Chaining same method (e.g.,
.tint(.red).tint(.blue)) typically overwrites - last value wins
- Chaining different methods (e.g.,
.tint(.red).interactive(true)) preserves all values
- There's usually no internal "combined" structure - just single values per property
-
Check type casting between internal and public types:
- Internal types like
_Glass often cannot be cast to public types like Glass
- When casting fails, extract individual properties and map them to public type values
Test Best Practices
-
Test in proper context - Always test views and modifiers in their intended usage context:
- Child views should be tested inside their parent containers (e.g.,
Tab inside TabView)
- Container-specific modifiers should be tested on views inside that container (e.g.,
listRowBackground on views inside List)
- Style modifiers should be tested on the view type they style (e.g.,
buttonStyle on Button)
- This ensures tests validate real-world usability, not just technical extraction
-
Platform-unavailable APIs - Wrap entire test file in #if !os(visionOS) (or appropriate platform), not individual tests
-
Add @MainActor to test class to avoid main actor isolation warnings
-
Don't test platform-specific default values - They may vary across platforms. Only test explicit parameter values
-
Compare to exact values instead of XCTAssertNotNil when possible:
XCTAssertEqual(result.id, AnyHashable("testID"))
XCTAssertNotNil(result.id)
-
Don't duplicate tests that test the same functionality with different values (e.g., don't need separate tests for Circle, Capsule, RoundedRectangle shapes - one is sufficient)
-
Test missing modifier errors - Verify error messages are correct:
func testGlassEffectMissingModifierError() throws {
let sut = EmptyView().padding()
XCTAssertThrows(
try sut.inspect().emptyView().glassEffect(),
"EmptyView does not have 'glassEffect' modifier")
}
-
Search tests should search for child views inside the container, not just the container itself:
func testSearchForChildInsideContainer() throws {
let view = VStack {
NewContainer {
Text("Child1")
Text("Child2")
}
}
XCTAssertEqual(
try view.inspect().find(text: "Child2").pathToRoot,
"vStack().newContainer(0).text(1)")
}
-
Some values can't be compared directly - Namespace.ID is recreated during inspection; use XCTAssertNotNil with a comment explaining why
Advanced Introspection Techniques
ViewInspector uses several advanced techniques to access SwiftUI's internal structures. Use these when standard Inspector.attribute() approaches don't work.
1. BinaryEquatable Protocol
Problem: SwiftUI types like Font.Weight, ToolbarItemPlacement, GlassEffectTransition don't conform to Equatable, making test assertions impossible.
Solution: BinaryEquatable compares raw memory bytes instead:
public protocol BinaryEquatable: Equatable { }
extension BinaryEquatable {
public static func == (lhs: Self, rhs: Self) -> Bool {
withUnsafeBytes(of: lhs) { lhsBytes -> Bool in
withUnsafeBytes(of: rhs) { rhsBytes -> Bool in
lhsBytes.elementsEqual(rhsBytes)
}
}
}
}
@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension ToolbarItemPlacement: BinaryEquatable { }
@available(iOS 26.0, macOS 26.0, tvOS 26.0, watchOS 26.0, *)
extension GlassEffectTransition: BinaryEquatable { }
When to use: For any SwiftUI type that needs equality comparison in tests but doesn't provide Equatable.
2. unsafeBitCast with Allocator Pattern
Problem: Many SwiftUI types (gesture values, style configurations, proxies) don't have public initializers, but tests need to create instances.
Solution: Create an Allocator struct matching the memory layout, then bitcast:
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
private extension GeometryProxy {
struct Allocator48 {
let data: (Int64, Int64, Int64, Int64, Int64, Int64) = (0, 0, 0, 0, 0, 0)
}
struct Allocator52 {
let data: (Allocator48, Int32) = (.init(), 0)
}
init() {
switch MemoryLayout<Self>.size {
case 52:
self = unsafeBitCast(Allocator52(), to: GeometryProxy.self)
case 48:
self = unsafeBitCast(Allocator48(), to: GeometryProxy.self)
default:
fatalError(MemoryLayout<Self>.actualSize())
}
}
}
Key patterns:
- Create multiple Allocator variants for different OS versions (struct sizes change!)
- Use
MemoryLayout<Self>.size switch to pick the right allocator
- Use
MemoryLayout<Self>.actualSize() in default case to discover new sizes
- Name allocators by size (e.g.,
Allocator48, Allocator52)
When to use: Creating gesture values, style configurations, proxies, _ViewModifier_Content, _PreferenceValue, etc.
3. Surrogate Pattern for Private Struct Access
Problem: Need to access internal properties of a private SwiftUI type like SignInWithAppleButton.
Solution: Create a Surrogate struct matching the memory layout, then rebind:
extension SignInWithAppleButton {
struct Surrogate {
let type: ASAuthorizationAppleIDButton.ButtonType
let onRequest: (ASAuthorizationAppleIDRequest) -> Void
let onCompletion: (Result<ASAuthorization, Error>) -> Void
}
}
private func buttonSurrogate() throws -> Surrogate {
let button = try Inspector.cast(value: content.view, type: SignInWithAppleButton.self)
return try Inspector.unsafeMemoryRebind(value: button, type: Surrogate.self)
}
When to use: When you need to access multiple internal properties or invoke internal callbacks.
4. unsafeMemoryRebind for Type Conversion
Problem: Need to convert between types with identical memory layouts (e.g., Swift 6 Sendable closures).
Solution: Use Inspector.unsafeMemoryRebind:
static func unsafeMemoryRebind<V, T>(value: V, type: T.Type) throws -> T {
guard MemoryLayout<V>.size == MemoryLayout<T>.size else {
throw InspectionError.notSupported("Unable to rebind...")
}
return withUnsafeBytes(of: value) { bytes in
return bytes.baseAddress!
.assumingMemoryBound(to: T.self).pointee
}
}
When to use: Converting closure types with different Sendable annotations, or accessing surrogate types.
5. withUnsafePointer + withMemoryRebound for Binding Conversion
Problem: Internal enum type is wrapped in Binding but you need a different Binding type (e.g., Toggle's internal ToggleState enum).
Solution: Rebind the pointer:
private enum ToggleState { case on, off, mixed }
let toggleStateBinding = try Inspector.attribute(label: "_toggleState", value: content.view)
let toggleState = withUnsafePointer(to: toggleStateBinding) {
$0.withMemoryRebound(to: Binding<ToggleState>.self, capacity: 1) {
$0.pointee
}
}
return Binding(
get: { toggleState.wrappedValue == .on },
set: { toggleState.wrappedValue = $0 ? .on : .off }
)
When to use: When the internal binding type differs from the public API type.
6. withUnsafeBytes + bindMemory for Closure Invocation
Problem: Need to invoke a closure stored in a generic container with specific type parameters.
Solution: Bind the closure's memory to the expected type:
typealias Closure = (EnvironmentValues) -> ModifiedContent<V, SomeModifier>
guard let typedClosure = withUnsafeBytes(of: closure, {
$0.bindMemory(to: Closure.self).first
}) else { throw InspectionError.typeMismatch(closure, Closure.self) }
let view = typedClosure(EnvironmentValues())
When to use: Invoking closure-based content builders like GeometryReader, ScrollViewReader, or navigationBarItems.
7. Memory Patching for Environment Injection
Problem: Need to inject environment objects into views that require them for inspection.
Solution: Scan memory for matching patterns and patch:
static func inject<T>(environmentObject: AnyObject, into entity: T) -> T {
let envObjSize = EnvObject.structSize
var offset = MemoryLayout<T>.stride - envObjSize
return withUnsafeBytes(of: EnvObject.Forgery(object: nil)) { reference in
while offset >= 0 {
var copy = entity
withUnsafeMutableBytes(of: ©) { bytes in
guard bytes[offset..<offset + envObjSize].elementsEqual(reference)
else { return }
let rawPointer = bytes.baseAddress! + offset + EnvObject.seedOffset
let pointerToValue = rawPointer.assumingMemoryBound(to: Int.self)
pointerToValue.pointee = -1
}
if let seed = try? Inspector.attribute(path: label + "|_seed", value: copy, type: Int.self),
seed == -1 {
withUnsafeMutableBytes(of: ©) { bytes in
let rawPointer = bytes.baseAddress! + offset
let pointerToValue = rawPointer.assumingMemoryBound(to: EnvObject.Forgery.self)
pointerToValue.pointee = .init(object: environmentObject)
}
return copy
}
offset -= step
}
return entity
}
}
When to use: This technique is already implemented in ViewInspector for environment injection. Reference it when debugging environment-related issues.
8. NSKeyedUnarchiver + setValue for Objective-C Types
Problem: Need to create instances of Objective-C types without public initializers (e.g., ASAuthorizationAppleIDCredential).
Solution: Decode from archived data and mutate with setValue:
public extension ASAuthorizationAppleIDCredential {
convenience init(user: String, email: String?, fullName: PersonNameComponents?, ...) {
let data = Data(base64Encoded: "YnBsaXN0MDD...")
let decoder = try! NSKeyedUnarchiver(forReadingFrom: data!)
self.init(coder: decoder)!
setValue(user, forKey: "user")
setValue(email, forKey: "email")
setValue(fullName, forKey: "fullName")
}
}
When to use: Creating Objective-C class instances that need custom initialization for testing.
9. MemoryLayout.actualSize() Discovery Helper
Problem: SwiftUI struct sizes change between OS versions, breaking Allocator patterns.
Solution: Use a helper to discover new sizes:
internal extension MemoryLayout {
static func actualSize() -> String {
fatalError("New size of \(String(describing: type(of: T.self))) is \(Self.size)")
}
}
init(fractionCompleted: Double?) {
switch MemoryLayout<Self>.size {
case 12:
self = unsafeBitCast(Allocator12(fractionCompleted: fractionCompleted), to: Self.self)
case 36:
self = unsafeBitCast(Allocator36(fractionCompleted: fractionCompleted), to: Self.self)
default:
fatalError(MemoryLayout<Self>.actualSize())
}
}
When to use: Always include in Allocator pattern switch statements to detect OS changes.
10. String Description Mapping for Internal Enums
Problem: Internal enums can't be cast to public types, but their string description matches public values.
Solution: Map string descriptions to public enum values:
func glassEffectTransition() throws -> GlassEffectTransition {
let kind = try modifierAttribute(
modifierName: "GlassEffectTransitionModifier", path: "modifier|transition|kind",
type: Any.self, call: "glassEffectTransition")
let description = String(describing: kind)
if description == "materialize" {
return .materialize
} else if description.hasPrefix("matchedGeometry") {
return .matchedGeometry
} else {
return .identity
}
}
When to use: When internal enums have public equivalents but casting fails.
Quick Reference: Choosing the Right Technique
| Problem | Technique |
|---|
| Compare non-Equatable types | BinaryEquatable |
| Create type without public init | Allocator + unsafeBitCast |
| Access private struct properties | Surrogate + unsafeMemoryRebind |
| Convert closure types | withUnsafeBytes + bindMemory |
| Convert binding types | withUnsafePointer + withMemoryRebound |
| Create Obj-C types | NSKeyedUnarchiver + setValue |
| Handle OS version changes | MemoryLayout switch + actualSize() |
| Map internal to public enum | String(describing:) mapping |