Token导航 LogoToken导航

Agent Skill · 待分类

axiom-swiftui-gestures(公理 Swiftui 手势)

axiom-swiftui-gestures 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

axiom-swiftui-gestures 是待分类类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为只读;许可证为 MIT,第三方扫描已通过,按安装量在待分类类的 11,209 个 Skill 里排第 1,130。

总安装 4,920周安装 205GitHub Stars 873下载 1,640核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:axiom-swiftui-gestures(公理 Swiftui 手势)
来源仓库:https://github.com/charleswiltgen/axiom
仓库路径:skills/axiom-swiftui-gestures
安装命令:
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-gestures
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-gestures

这个 Skill 是做什么的

这个 Skill 的分类还没最终确定,用法以来源仓库的说明为准。

axiom-swiftui-gestures 提供 SwiftUI 手势识别的综合指导,涵盖点击、拖拽、缩放等交互实现。适用于实现多手势组合、自定义识别器或处理跨平台(iOS/macOS/visionOS)手势兼容性问题。支持 GestureState 状态管理和 VoiceOver 可访问性集成,提升交互体验一致性。

安装前应核实是否会调用系统 API 或修改用户输入处理逻辑。需参考原始文档确认示例提示是否完整覆盖实际项目需求边界。

来源仓库内路径:skills/axiom-swiftui-gestures

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-ges…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它标注为只读,先用只读任务试一次,确认行为符合预期再扩大使用范围。

常见问题

6 条

axiom-swiftui-gestures 怎么安装?

在终端执行 npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-gestures。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

axiom-swiftui-gestures 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

axiom-swiftui-gestures 需要什么权限?

权限级别是只读。该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

axiom-swiftui-gestures 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

axiom-swiftui-gestures 用的人多吗?

公开数据是累计安装 4,920 次,近一周安装 205 次,下载 1,640 次,GitHub 873 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

axiom-swiftui-gestures 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理待分类类任务、又不想每次都手写提示词的人。
  • 需要许可证清楚、并且有第三方扫描结果的团队项目。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

待分类类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
infsh-cli

infsh CLI · Codex、Claude

4,374,576382权限未标注未明确未收录
ai-video-generation

AI 视频生成 · Codex、Claude

4,313,376382调用外部服务未明确有提醒
lark-openapi-explorer

云雀 OpenAPI 浏览器 · Codex、Claude

2,064,1209,011需要联网未明确有提醒
lark-workflow-standup-report

Lark 工作流程站立报告 · Codex、Claude

2,041,6328,978需要联网未明确通过
use-my-browser

使用我的浏览器 · Codex、Claude

1,848,49140操作浏览器未明确未通过
axiom-swiftui-gestures

公理 Swiftui 手势 · 本页

4,920873只读MIT通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

SwiftUI Gestures

Comprehensive guide to SwiftUI gesture recognition with composition patterns, state management, and accessibility integration.

When to Use This Skill

  • Implementing tap, drag, long press, magnification, or rotation gestures
  • Composing multiple gestures (simultaneously, sequenced, exclusively)
  • Managing gesture state with GestureState
  • Creating custom gesture recognizers
  • Debugging gesture conflicts or unresponsive gestures
  • Making gestures accessible with VoiceOver
  • Cross-platform gesture handling (iOS, macOS, axiom-visionOS)

Example Prompts

These are real questions developers ask that this skill is designed to answer:

1. "My drag gesture isn't working - the view doesn't move when I drag it. How do I debug this?"

→ The skill covers DragGesture state management patterns and shows how to properly update view offset with @GestureState

2. "I have both a tap gesture and a drag gesture on the same view. The tap works but the drag doesn't. How do I fix this?"

→ The skill demonstrates gesture composition with.simultaneously,.sequenced, and.exclusively to resolve gesture conflicts

3. "I want users to long press before they can drag an item. How do I chain gestures together?"

→ The skill shows the.sequenced pattern for combining LongPressGesture with DragGesture in the correct order

4. "My gesture state isn't resetting when the gesture ends. The view stays in the wrong position."

→ The skill covers @GestureState automatic reset behavior and the updating parameter for proper state management

5. "VoiceOver users can't access features that require gestures. How do I make gestures accessible?"

→ The skill demonstrates.accessibilityAction patterns and providing alternative interactions for VoiceOver users


Choosing the Right Gesture (Decision Tree)

What interaction do you need?

├─ Single tap/click?
│  └─ Use Button (preferred) or TapGesture
│
├─ Drag/pan movement?
│  └─ Use DragGesture
│
├─ Hold before action?
│  └─ Use LongPressGesture
│
├─ Pinch to zoom?
│  └─ Use MagnificationGesture
│
├─ Two-finger rotation?
│  └─ Use RotationGesture
│
├─ Multiple gestures together?
│  ├─ Both at same time? → .simultaneously
│  ├─ One after another? → .sequenced
│  └─ One OR the other? → .exclusively
│
└─ Complex custom behavior?
   └─ Create custom Gesture conforming to Gesture protocol

Pattern 1: Basic Gesture Recognition

TapGesture

❌ WRONG (Custom tap on non-semantic view)
Text("Submit")
  .onTapGesture {
    submitForm()
  }

Problems:

  • Not announced as button to VoiceOver
  • No visual press feedback
  • Doesn't respect accessibility settings
✅ CORRECT (Use Button for tap actions)
Button("Submit") {
  submitForm()
}
.buttonStyle(.bordered)

When to use TapGesture: Only when you need tap *data* (location, count) or non-standard tap behavior:

Image("map")
  .onTapGesture(count: 2) { // Double-tap for details
    showDetails()
  }
  .onTapGesture { location in // Single tap to pin
    addPin(at: location)
  }

DragGesture

❌ WRONG (Direct state mutation in gesture)
@State private var offset = CGSize.zero

var body: some View {
  Circle()
    .offset(offset)
    .gesture(
      DragGesture()
        .onChanged { value in
          offset = value.translation // ❌ Updates every frame, causes jank
        }
    )
}

Problems:

  • View updates on every drag event (60-120 times per second)
  • No way to reset to original position
  • Loses intermediate state if drag cancelled
✅ CORRECT (Use GestureState for temporary state)
@GestureState private var dragOffset = CGSize.zero
@State private var position = CGSize.zero

var body: some View {
  Circle()
    .offset(x: position.width + dragOffset.width,
            y: position.height + dragOffset.height)
    .gesture(
      DragGesture()
        .updating($dragOffset) { value, state, _ in
          state = value.translation // Temporary during drag
        }
        .onEnded { value in
          position.width += value.translation.width // Commit final
          position.height += value.translation.height
        }
    )
}

Why: GestureState automatically resets to initial value when gesture ends, preventing state corruption.


LongPressGesture

@GestureState private var isDetectingLongPress = false
@State private var completedLongPress = false

var body: some View {
  Text("Press and hold")
    .foregroundStyle(isDetectingLongPress ? .red : .blue)
    .gesture(
      LongPressGesture(minimumDuration: 1.0)
        .updating($isDetectingLongPress) { currentState, gestureState, _ in
          gestureState = currentState // Visual feedback during press
        }
        .onEnded { _ in
          completedLongPress = true // Action after hold
        }
    )
}

Key parameters:

  • minimumDuration: How long to hold (default 0.5 seconds)
  • maximumDistance: How far finger can move before cancelling (default 10 points)

MagnificationGesture

@GestureState private var magnificationAmount = 1.0
@State private var currentZoom = 1.0

var body: some View {
  Image("photo")
    .scaleEffect(currentZoom * magnificationAmount)
    .gesture(
      MagnificationGesture()
        .updating($magnificationAmount) { value, state, _ in
          state = value.magnification
        }
        .onEnded { value in
          currentZoom *= value.magnification
        }
    )
}

Platform notes:

  • iOS: Pinch gesture with two fingers
  • macOS: Trackpad pinch
  • visionOS: Pinch gesture in 3D space

RotationGesture

@GestureState private var rotationAngle = Angle.zero
@State private var currentRotation = Angle.zero

var body: some View {
  Rectangle()
    .fill(.blue)
    .frame(width: 200, height: 200)
    .rotationEffect(currentRotation + rotationAngle)
    .gesture(
      RotationGesture()
        .updating($rotationAngle) { value, state, _ in
          state = value.rotation
        }
        .onEnded { value in
          currentRotation += value.rotation
        }
    )
}

Pattern 2: Gesture Composition

Simultaneous Gestures

Use when: Two gestures should work *at the same time*
@GestureState private var dragOffset = CGSize.zero
@GestureState private var magnificationAmount = 1.0

var body: some View {
  Image("photo")
    .offset(dragOffset)
    .scaleEffect(magnificationAmount)
    .gesture(
      DragGesture()
        .updating($dragOffset) { value, state, _ in
          state = value.translation
        }
        .simultaneously(with:
          MagnificationGesture()
            .updating($magnificationAmount) { value, state, _ in
              state = value.magnification
            }
        )
    )
}

Use case: Photo viewer where you can drag AND pinch-zoom at the same time.


Sequenced Gestures

Use when: One gesture must *complete* before the next starts
@State private var isLongPressing = false
@GestureState private var dragOffset = CGSize.zero

var body: some View {
  Circle()
    .offset(dragOffset)
    .gesture(
      LongPressGesture(minimumDuration: 0.5)
        .onEnded { _ in
          isLongPressing = true
        }
        .sequenced(before:
          DragGesture()
            .updating($dragOffset) { value, state, _ in
              state = value.translation
            }
            .onEnded { _ in
              isLongPressing = false
            }
        )
    )
}

Use case: iOS Home Screen — long press to enter edit mode, *then* drag to reorder.


Exclusive Gestures

Use when: Only *one* gesture should win, not both
var body: some View {
  Rectangle()
    .gesture(
      TapGesture(count: 2) // Double-tap
        .onEnded { _ in
          zoom()
        }
        .exclusively(before:
          TapGesture(count: 1) // Single tap
            .onEnded { _ in
              select()
            }
        )
    )
}

Why: Without .exclusively, double-tap triggers *both* single and double tap handlers.

How it works: SwiftUI waits to see if second tap comes. If yes → double tap wins. If no → single tap wins.


Pattern 3: GestureState vs State

When to Use Each

Use CaseState TypeWhy
Temporary feedback during gesture@GestureStateAuto-resets when gesture ends
Final committed value@StatePersists after gesture
Animation during gesture@GestureStateSmooth transitions
Data persistence@StateSurvives view updates

Full Example: Draggable Card

struct DraggableCard: View {
  @GestureState private var dragOffset = CGSize.zero // Temporary
  @State private var position = CGSize.zero          // Permanent

  var body: some View {
    RoundedRectangle(cornerRadius: 12)
      .fill(.blue)
      .frame(width: 300, height: 200)
      .offset(
        x: position.width + dragOffset.width,
        y: position.height + dragOffset.height
      )
      .gesture(
        DragGesture()
          .updating($dragOffset) { value, state, transaction in
            state = value.translation

            // Enable animation for smooth feedback
            transaction.animation = .interactiveSpring()
          }
          .onEnded { value in
            // Commit final position with animation
            withAnimation(.spring()) {
              position.width += value.translation.width
              position.height += value.translation.height
            }
          }
      )
  }
}

Key insight: GestureState's third parameter transaction lets you customize animation during the gesture.


Pattern 4: Custom Gestures

When to Create Custom Gestures

  • Need gesture behavior not provided by built-in gestures
  • Want to encapsulate complex gesture logic
  • Reusing gesture across multiple views

Example: Swipe Gesture with Direction

struct SwipeGesture: Gesture {
  enum Direction {
    case left, right, up, down
  }

  let minimumDistance: CGFloat
  let coordinateSpace: CoordinateSpace

  init(minimumDistance: CGFloat = 50, coordinateSpace: CoordinateSpace = .local) {
    self.minimumDistance = minimumDistance
    self.coordinateSpace = coordinateSpace
  }

  // Value is the direction
  typealias Value = Direction

  // Body builds on DragGesture
  var body: AnyGesture<Direction> {
    DragGesture(minimumDistance: minimumDistance, coordinateSpace: coordinateSpace)
      .map { value in
        let horizontal = value.translation.width
        let vertical = value.translation.height

        if abs(horizontal) > abs(vertical) {
          return horizontal < 0 ? .left : .right
        } else {
          return vertical < 0 ? .up : .down
        }
      }
      .eraseToAnyGesture()
  }
}

// Usage
Text("Swipe me")
  .gesture(
    SwipeGesture()
      .onEnded { direction in
        switch direction {
        case .left: deleteItem()
        case .right: archiveItem()
        default: break
        }
      }
  )

Pattern 5: Gesture Velocity and Prediction

Accessing Velocity

@State private var velocity: CGSize = .zero

var body: some View {
  Circle()
    .gesture(
      DragGesture()
        .onEnded { value in
          // value.velocity is deprecated in iOS 18+
          // Use value.predictedEndLocation and time

          let timeDelta = value.time.timeIntervalSince(value.startLocation.time)
          let distance = value.translation

          velocity = CGSize(
            width: distance.width / timeDelta,
            height: distance.height / timeDelta
          )

          // Animate with momentum
          withAnimation(.interpolatingSpring(stiffness: 100, damping: 15)) {
            applyMomentum(velocity: velocity)
          }
        }
    )
}

Predicted End Location (iOS 16+)

DragGesture()
  .onChanged { value in
    // Where gesture will likely end based on velocity
    let predicted = value.predictedEndLocation

    // Show preview of where item will land
    showPreview(at: predicted)
  }

Use case: Springy physics, momentum scrolling, throw animations.


Pattern 6: Accessibility Integration

Making Custom Gestures Accessible

❌ WRONG (Gesture-only, no VoiceOver support)
Image("slider")
  .gesture(
    DragGesture()
      .onChanged { value in
        updateVolume(value.translation.width)
      }
  )

Problem: VoiceOver users can't adjust the slider.

✅ CORRECT (Add accessibility actions)
@State private var volume: Double = 50

var body: some View {
  Image("slider")
    .gesture(
      DragGesture()
        .onChanged { value in
          volume = calculateVolume(from: value.translation.width)
        }
    )
    .accessibilityElement()
    .accessibilityLabel("Volume")
    .accessibilityValue("\(Int(volume))%")
    .accessibilityAdjustableAction { direction in
      switch direction {
      case .increment:
        volume = min(100, volume + 5)
      case .decrement:
        volume = max(0, volume - 5)
      @unknown default:
        break
      }
    }
}

Why: VoiceOver users can now swipe up/down to adjust volume without seeing or using the gesture.

Keyboard Alternatives (macOS)

Rectangle()
  .gesture(
    DragGesture()
      .onChanged { value in
        move(by: value.translation)
      }
  )
  .onKeyPress(.upArrow) {
    move(by: CGSize(width: 0, height: -10))
    return .handled
  }
  .onKeyPress(.downArrow) {
    move(by: CGSize(width: 0, height: 10))
    return .handled
  }
  .onKeyPress(.leftArrow) {
    move(by: CGSize(width: -10, height: 0))
    return .handled
  }
  .onKeyPress(.rightArrow) {
    move(by: CGSize(width: 10, height: 0))
    return .handled
  }

Pattern 7: Cross-Platform Gestures

iOS vs macOS vs visionOS

GestureiOSmacOSvisionOS
TapGestureTap with fingerClick with mouse/trackpadLook + pinch
DragGestureDrag with fingerClick and dragPinch and move
LongPressGestureLong pressClick and holdLong pinch
MagnificationGestureTwo-finger pinchTrackpad pinchPinch with both hands
RotationGestureTwo-finger rotateTrackpad rotateRotate with both hands

Platform-Specific Gestures

var body: some View {
  Image("photo")
    .gesture(
      #if os(iOS)
      DragGesture(minimumDistance: 10) // Smaller threshold for touch
      #elseif os(macOS)
      DragGesture(minimumDistance: 1) // Precise mouse control
      #else
      DragGesture(minimumDistance: 20) // Larger for spatial gestures
      #endif
        .onChanged { value in
          updatePosition(value.translation)
        }
    )
}

Common Pitfalls

Pitfall 1: Forgetting to Reset GestureState

❌ WRONG
@State private var offset = CGSize.zero // Should be GestureState

var body: some View {
  Circle()
    .offset(offset)
    .gesture(
      DragGesture()
        .onChanged { value in
          offset = value.translation
        }
    )
}

Problem: When drag ends, offset stays at last value instead of resetting.

Fix: Use @GestureState for temporary state, or manually reset in .onEnded.


Pitfall 2: Gesture Conflicts with ScrollView

❌ WRONG (Drag gesture blocks scrolling)
ScrollView {
  ForEach(items) { item in
    ItemView(item)
      .gesture(
        DragGesture()
          .onChanged { _ in
            // Prevents scroll!
          }
      )
  }
}

Fix: Use .highPriorityGesture() or .simultaneousGesture() appropriately:

ScrollView {
  ForEach(items) { item in
    ItemView(item)
      .simultaneousGesture( // Allows both scroll and drag
        DragGesture()
          .onChanged { value in
            // Only trigger if horizontal swipe
            if abs(value.translation.width) > abs(value.translation.height) {
              handleSwipe(value)
            }
          }
      )
  }
}

Pitfall 3: Using.gesture() Instead of Button

❌ WRONG (Reimplementing button)
Text("Submit")
  .padding()
  .background(.blue)
  .foregroundStyle(.white)
  .clipShape(RoundedRectangle(cornerRadius: 8))
  .onTapGesture {
    submit()
  }

Problems:

  • No press animation
  • No accessibility traits
  • Doesn't respect system button styling
  • More code
✅ CORRECT
Button("Submit") {
  submit()
}
.buttonStyle(.borderedProminent)

When TapGesture is OK: When you need tap *location* or multiple tap counts:

Canvas { context, size in
  // Draw canvas
}
.onTapGesture { location in
  addShape(at: location) // Need location data
}

Pitfall 4: Not Handling Gesture Cancellation

❌ WRONG (Assumes gesture always completes)
DragGesture()
  .onChanged { value in
    showPreview(at: value.location)
  }
  .onEnded { value in
    hidePreview()
    commitChange(at: value.location)
  }

Problem: If user drags outside bounds and gesture cancels, preview stays visible.

✅ CORRECT (GestureState auto-resets)
@GestureState private var isDragging = false

var body: some View {
  content
    .gesture(
      DragGesture()
        .updating($isDragging) { _, state, _ in
          state = true
        }
        .onChanged { value in
          if isDragging {
            showPreview(at: value.location)
          }
        }
        .onEnded { value in
          commitChange(at: value.location)
        }
    )
    .onChange(of: isDragging) { _, newValue in
      if !newValue {
        hidePreview() // Cleanup when cancelled
      }
    }
}

Pitfall 5: Forgetting coordinateSpace

❌ WRONG (Location relative to view, not screen)
DragGesture()
  .onChanged { value in
    // value.location is relative to the gesture's view
    addAnnotation(at: value.location)
  }

Problem: If view is offset/scrolled, coordinates are wrong.

✅ CORRECT (Specify coordinate space)
DragGesture(coordinateSpace: .named("container"))
  .onChanged { value in
    addAnnotation(at: value.location) // Relative to "container"
  }

// In parent:
ScrollView {
  content
}
.coordinateSpace(name: "container")

Options:

  • .local — Relative to gesture's view (default)
  • .global — Relative to screen
  • .named("name") — Relative to named coordinate space

Performance Considerations

Minimize Work in.onChanged

❌ SLOW
DragGesture()
  .onChanged { value in
    // Called 60-120 times per second!
    let position = complexCalculation(value.translation)
    updateDatabase(position) // ❌ I/O in gesture
    reloadAllViews() // ❌ Heavy work
  }
✅ FAST
@GestureState private var dragOffset = CGSize.zero

var body: some View {
  content
    .offset(dragOffset) // Cheap - just layout
    .gesture(
      DragGesture()
        .updating($dragOffset) { value, state, _ in
          state = value.translation // Minimal work
        }
        .onEnded { value in
          // Heavy work once, not 120 times/second
          let finalPosition = complexCalculation(value.translation)
          updateDatabase(finalPosition)
        }
    )
}

Use Transaction for Smooth Animations

DragGesture()
  .updating($dragOffset) { value, state, transaction in
    state = value.translation

    // Disable implicit animations during drag
    transaction.animation = nil
  }
  .onEnded { value in
    // Enable spring animation for final position
    withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
      commitPosition(value.translation)
    }
  }

Why: Animations during gesture can feel sluggish. Disable during drag, enable for final snap.


Troubleshooting

Gesture Not Recognizing

Check:

  1. Is view interactive? (Some views like Text ignore gestures unless wrapped)
  2. Is another gesture taking priority? (Use .highPriorityGesture() or .simultaneousGesture())
  3. Is view clipped? (Use .contentShape() to define tap area)
  4. Is gesture too restrictive? (Check minimumDistance, minimumDuration)
// Fix unresponsive gesture
Text("Tap me")
  .frame(width: 100, height: 100)
  .contentShape(Rectangle()) // Define full tap area
  .onTapGesture {
    handleTap()
  }

Gesture Conflicts with Navigation

NavigationLink(destination: DetailView()) {
  ItemRow(item)
    .simultaneousGesture( // Don't block navigation
      LongPressGesture()
        .onEnded { _ in
          showContextMenu()
        }
    )
}

Gesture Breaking ScrollView

Use horizontal-only gesture detection:

ScrollView {
  ForEach(items) { item in
    ItemView(item)
      .simultaneousGesture(
        DragGesture()
          .onEnded { value in
            // Only trigger on horizontal swipe
            if abs(value.translation.width) > abs(value.translation.height) * 2 {
              if value.translation.width < 0 {
                deleteItem(item)
              }
            }
          }
      )
  }
}

Testing Gestures

UI Testing with Gestures

func testDragGesture() throws {
  let app = XCUIApplication()
  app.launch()

  let element = app.otherElements["draggable"]

  // Get start and end coordinates
  let start = element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5))
  let finish = element.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.5))

  // Perform drag
  start.press(forDuration: 0.1, thenDragTo: finish)

  // Verify result
  XCTAssertTrue(app.staticTexts["Dragged"].exists)
}

Manual Testing Checklist

  • Gesture works on first interaction (no "warmup" needed)
  • Gesture can be cancelled (drag outside bounds)
  • Multiple rapid gestures work correctly
  • Gesture works with VoiceOver enabled
  • Gesture works on all target platforms (iOS/macOS/visionOS)
  • Gesture doesn't block scrolling or navigation
  • Gesture provides visual feedback during interaction
  • Gesture respects accessibility settings (Reduce Motion)

Resources

WWDC: 2019-237, 2020-10043, 2021-10018

Docs: /swiftui/composing-swiftui-gestures, /swiftui/gesturestate, /swiftui/gesture

Skills: axiom-accessibility-diag, axiom-swiftui-performance, axiom-ui-testing


Remember: Prefer built-in controls (Button, Slider) over custom gestures whenever possible. Gestures should enhance interaction, not replace standard controls.

平台分布

Claude Code

27.47%
按下载量换算451

OpenCode

24%
按下载量换算394

Codex

18.31%
按下载量换算300

Cursor

13.64%
按下载量换算224

Antigravity

7.6%
按下载量换算125

Gemini CLI

3.28%
按下载量换算54

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

只读

该 Skill 主要提供规则、说明或参考内容,本身偏只读;真正读写文件、联网或执行命令仍取决于宿主 Agent 的任务。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

继续浏览同类 Skills