new-api-support
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.
معلومات المصدر
- المستودع
- nalexn/ViewInspector
- آخر نشاط في المصدر
- ١ فبراير ٢٠٢٦ في ١٩:٣٠
- لغة SKILL.md المكتشفة
- الإنجليزية
- النجوم
- ٢٬٦٣٥
- التفرعات
- ١٩٦
خيارات التثبيت
يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.
مراجعة ملفات المصدر
اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.
عرض SKILL.md
SKILL.md
تعليمات المصدر · معاينة للقراءة فقط- 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):
```bash
# Find SwiftUI interface files in Xcode SDK
find /Applications/Xcode.app/Contents/Developer/Platforms -name "SwiftUI.swiftmodule" -type d 2>/dev/null | head -5
# Search for the entity in SwiftUI interfaces
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
# For macOS SDK
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:
1. **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
2. **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`) |
3. **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
```
4. **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:
```bash
grep -l "similar_modifier" Sources/ViewInspector/Modifiers/*.swift
```
### Step 4: Reverse Engineering Investigation
**Create a reverse engineering test** to understand the internal structure:
```swift
import XCTest
import SwiftUI
@testable import ViewInspector
final class ReverseEngineeringTests: XCTestCase {
func testInvestigate_<EntityName>() throws {
// Create a simple view using the target API
let sut = EmptyView().<targetAPI>()
// Print the internal structure
print("\(Inspector.print(sut) as AnyObject)")
}
}
```
**Run the investigation test:**
```bash
swift test --filter "testInvestigate_"
```
**Analyze the output** to identify:
1. The internal modifier/view type name (e.g., `_AppearanceActionModifier`)
2. Property names and their paths (e.g., `appear`, `disappear`)
3. Nested structure for complex types
4. 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:
```swift
@available(iOS 13.0, macOS 10.15, tvOS 13.0, *)
public extension InspectableView {
// For simple value extraction
func <modifierName>() throws -> <ReturnType> {
return try modifierAttribute(
modifierName: "<InternalModifierName>", // From Inspector.print
path: "modifier|<propertyPath>", // Path to the value
type: <ReturnType>.self,
call: "<modifierName>")
}
// For callback invocation
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:
```swift
@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" // From Inspector.print
public static var namespacedPrefixes: [String] {
["SwiftUI.NewViewType"]
}
}
}
// MARK: - Content Extraction
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
extension ViewType.NewViewType: SingleViewContent { // or MultipleViewContent
public static func child(_ content: Content) throws -> Content {
return try Inspector.attribute(path: "content", value: content.view)
}
}
// MARK: - Extraction from View hierarchy
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
public extension InspectableView where View == ViewType.NewViewType {
// Add attribute getters based on Inspector.print analysis
func someAttribute() throws -> SomeType {
return try Inspector.attribute(
path: "attributePath",
value: content.view,
type: SomeType.self)
}
}
// MARK: - Global View hierarchy access
@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:**
```swift
import XCTest
import SwiftUI
@testable import ViewInspector
// Example: Tab is meant to be used inside TabView
@available(iOS 18.0, macOS 15.0, tvOS 18.0, watchOS 11.0, visionOS 2.0, *)
final class TabTests: XCTestCase {
// Test extraction in proper context (inside TabView)
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")
}
// Test searching for content inside Tab inside TabView
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:**
```swift
// Example: subscriptionStoreButtonLabel applies to SubscriptionStoreView
@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)
}
}
// Example: listRowBackground applies to views inside List
@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()
// Verify the background color
}
}
```
**For style modifiers, test with the view type they style:**
```swift
// Example: buttonStyle applies to Button
@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()
// Verify style properties
}
}
```
**Generic test file structure (when no specific context applies):**
```swift
import XCTest
import SwiftUI
@testable import ViewInspector
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
final class NewViewTypeTests: XCTestCase {
// Test basic extraction
func testExtractionFromSingleViewContainer() throws {
let view = AnyView(NewViewType())
XCTAssertNoThrow(try view.inspect().anyView().newViewType())
}
// Test attribute inspection
func testSomeAttributeInspection() throws {
let sut = NewViewType(someParam: .value)
let value = try sut.inspect().newViewType().someAttribute()
XCTAssertEqual(value, .value)
}
// Test in view hierarchy
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):**
```swift
@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 {
عرض على GitHubملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub