agy-superpowers 5.0.8 → 5.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/agent/rules/superpowers.md +54 -0
- package/template/agent/skills/frontend-developer/SKILL.md +39 -0
- package/template/agent/skills/frontend-developer/references/react-nextjs.md +343 -0
- package/template/agent/skills/frontend-developer/references/react-rules/_sections.md +46 -0
- package/template/agent/skills/frontend-developer/references/react-rules/_template.md +28 -0
- package/template/agent/skills/frontend-developer/references/react-rules/advanced-event-handler-refs.md +55 -0
- package/template/agent/skills/frontend-developer/references/react-rules/advanced-init-once.md +42 -0
- package/template/agent/skills/frontend-developer/references/react-rules/advanced-use-latest.md +39 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-api-routes.md +38 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-defer-await.md +80 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-dependencies.md +51 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-parallel.md +28 -0
- package/template/agent/skills/frontend-developer/references/react-rules/async-suspense-boundaries.md +99 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-barrel-imports.md +59 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-conditional.md +31 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-defer-third-party.md +49 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-dynamic-imports.md +35 -0
- package/template/agent/skills/frontend-developer/references/react-rules/bundle-preload.md +50 -0
- package/template/agent/skills/frontend-developer/references/react-rules/client-event-listeners.md +74 -0
- package/template/agent/skills/frontend-developer/references/react-rules/client-localstorage-schema.md +71 -0
- package/template/agent/skills/frontend-developer/references/react-rules/client-passive-event-listeners.md +48 -0
- package/template/agent/skills/frontend-developer/references/react-rules/client-swr-dedup.md +56 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-batch-dom-css.md +107 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-cache-function-results.md +80 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-cache-property-access.md +28 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-cache-storage.md +70 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-combine-iterations.md +32 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-early-exit.md +50 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-flatmap-filter.md +60 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-hoist-regexp.md +45 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-index-maps.md +37 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-length-check-first.md +49 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-min-max-loop.md +82 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-set-map-lookups.md +24 -0
- package/template/agent/skills/frontend-developer/references/react-rules/js-tosorted-immutable.md +57 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-activity.md +26 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-animate-svg-wrapper.md +47 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-conditional-render.md +40 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-content-visibility.md +38 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-hoist-jsx.md +46 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-hydration-no-flicker.md +82 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-hydration-suppress-warning.md +30 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-resource-hints.md +85 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-script-defer-async.md +68 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-svg-precision.md +28 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rendering-usetransition-loading.md +75 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-defer-reads.md +39 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-dependencies.md +45 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-derived-state-no-effect.md +40 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-derived-state.md +29 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-functional-setstate.md +74 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-lazy-state-init.md +58 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-memo-with-default-value.md +38 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-memo.md +44 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-move-effect-to-event.md +45 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-no-inline-components.md +82 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-simple-expression-in-memo.md +35 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-split-combined-hooks.md +64 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-transitions.md +40 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-use-deferred-value.md +59 -0
- package/template/agent/skills/frontend-developer/references/react-rules/rerender-use-ref-transient-values.md +73 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-after-nonblocking.md +73 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-auth-actions.md +96 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-cache-lru.md +41 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-cache-react.md +76 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-dedup-props.md +65 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-hoist-static-io.md +142 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-parallel-fetching.md +83 -0
- package/template/agent/skills/frontend-developer/references/react-rules/server-serialization.md +38 -0
- package/template/agent/skills/frontend-developer/references/svelte-sveltekit.md +220 -0
- package/template/agent/skills/frontend-developer/references/vanilla.md +275 -0
- package/template/agent/skills/frontend-developer/references/vue-nuxt.md +289 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/_index.md +154 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/animation-class-based-technique.md +254 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/animation-state-driven-technique.md +291 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-async.md +97 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-data-flow.md +307 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-fallthrough-attrs.md +174 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-keep-alive.md +137 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-slots.md +216 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-suspense.md +228 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-teleport.md +108 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-transition-group.md +128 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/component-transition.md +125 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/composables.md +290 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/directives.md +162 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/perf-avoid-component-abstraction-in-lists.md +159 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/perf-v-once-v-memo-directives.md +182 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/perf-virtualize-large-lists.md +187 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/plugins.md +166 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/reactivity.md +344 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/render-functions.md +201 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/sfc.md +310 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/state-management.md +135 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/best-practices/updated-hook-performance.md +187 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/_index.md +23 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-beforeenter-no-param-trigger.md +167 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-beforerouteenter-no-this.md +176 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-guard-async-await-pattern.md +227 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-navigation-guard-infinite-loop.md +187 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-navigation-guard-next-deprecated.md +150 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-param-change-no-lifecycle.md +181 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-simple-routing-cleanup.md +209 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/router/router-use-vue-router-for-production.md +183 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/_index.md +29 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/async-component-testing.md +163 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/teleport-testing-complexity.md +158 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-async-await-flushpromises.md +175 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-browser-vs-node-runners.md +208 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-component-blackbox-approach.md +144 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-composables-helper-wrapper.md +238 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-e2e-playwright-recommended.md +242 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-no-snapshot-only.md +197 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-pinia-store-setup.md +228 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-suspense-async-components.md +229 -0
- package/template/agent/skills/frontend-developer/references/vue-rules/testing/testing-vitest-recommended-for-vue.md +204 -0
- package/template/agent/skills/mobile-developer/SKILL.md +52 -0
- package/template/agent/skills/mobile-developer/references/android-native.md +396 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-accessibility.md +36 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-architecture.md +52 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-coroutines.md +139 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-data-layer.md +51 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-emulator-skill.md +108 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-gradle-logic.md +126 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-retrofit.md +142 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-testing.md +102 -0
- package/template/agent/skills/mobile-developer/references/android-rules/android-viewmodel.md +43 -0
- package/template/agent/skills/mobile-developer/references/android-rules/coil-compose.md +74 -0
- package/template/agent/skills/mobile-developer/references/android-rules/compose-navigation.md +422 -0
- package/template/agent/skills/mobile-developer/references/android-rules/compose-performance-audit.md +199 -0
- package/template/agent/skills/mobile-developer/references/android-rules/compose-ui.md +49 -0
- package/template/agent/skills/mobile-developer/references/android-rules/gradle-build-performance.md +346 -0
- package/template/agent/skills/mobile-developer/references/android-rules/kotlin-concurrency-expert.md +169 -0
- package/template/agent/skills/mobile-developer/references/android-rules/rxjava-to-coroutines-migration.md +101 -0
- package/template/agent/skills/mobile-developer/references/android-rules/xml-to-compose-migration.md +338 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-best-practices.md +52 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-checks-migration.md +134 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-cli-app-best-practices.md +123 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-doc-validation.md +45 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-matcher-best-practices.md +106 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-modern-features.md +241 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-package-maintenance.md +75 -0
- package/template/agent/skills/mobile-developer/references/flutter-rules/dart-test-fundamentals.md +124 -0
- package/template/agent/skills/mobile-developer/references/flutter.md +291 -0
- package/template/agent/skills/mobile-developer/references/ios-native.md +358 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/accessibility-patterns.md +215 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/animation-advanced.md +403 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/animation-basics.md +284 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/animation-transitions.md +326 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/charts-accessibility.md +135 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/charts.md +602 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/image-optimization.md +203 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/latest-apis.md +464 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/layout-best-practices.md +266 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/liquid-glass.md +416 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/list-patterns.md +394 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/macos-scenes.md +318 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/macos-views.md +357 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/macos-window-styling.md +303 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/performance-patterns.md +403 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/scroll-patterns.md +293 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/sheet-navigation-patterns.md +363 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/state-management.md +417 -0
- package/template/agent/skills/mobile-developer/references/ios-rules/view-structure.md +389 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/_sections.md +86 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/_template.md +28 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/animation-derived-value.md +53 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/animation-gesture-detector-press.md +95 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/animation-gpu-properties.md +65 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/design-system-compound-components.md +66 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/fonts-config-plugin.md +71 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/imports-design-system-folder.md +68 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/js-hoist-intl.md +61 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-callbacks.md +44 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-function-references.md +132 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-images.md +53 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-inline-objects.md +97 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-item-expensive.md +94 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-item-memo.md +82 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-item-types.md +104 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/list-performance-virtualize.md +67 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/monorepo-native-deps-in-app.md +46 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/monorepo-single-dependency-versions.md +63 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/navigation-native-navigators.md +188 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-compiler-destructure-functions.md +50 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-compiler-reanimated-shared-values.md +48 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-state-dispatcher.md +91 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-state-fallback.md +56 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/react-state-minimize.md +65 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/rendering-no-falsy-and.md +74 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/rendering-text-in-text-component.md +36 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/scroll-position-no-state.md +82 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/state-ground-truth.md +80 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-expo-image.md +66 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-image-gallery.md +104 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-measure-views.md +78 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-menus.md +174 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-native-modals.md +77 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-pressable.md +61 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-safe-area-scroll.md +65 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-scrollview-content-inset.md +45 -0
- package/template/agent/skills/mobile-developer/references/react-native-rules/ui-styling.md +87 -0
- package/template/agent/skills/mobile-developer/references/react-native.md +345 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# SwiftUI View Structure Reference
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [View Structure Principles](#view-structure-principles)
|
|
6
|
+
- [Prefer Modifiers Over Conditional Views](#prefer-modifiers-over-conditional-views)
|
|
7
|
+
- [Extract Subviews, Not Computed Properties](#extract-subviews-not-computed-properties)
|
|
8
|
+
- [When @ViewBuilder Functions Are Acceptable](#when-viewbuilder-functions-are-acceptable)
|
|
9
|
+
- [When to Extract Subviews](#when-to-extract-subviews)
|
|
10
|
+
- [Container View Pattern](#container-view-pattern)
|
|
11
|
+
- [ZStack vs overlay/background](#zstack-vs-overlaybackground)
|
|
12
|
+
- [Compositing Group Before Clipping](#compositing-group-before-clipping)
|
|
13
|
+
- [Reusable Styling with ViewModifier](#reusable-styling-with-viewmodifier)
|
|
14
|
+
- [Skeleton Loading with Redacted Views](#skeleton-loading-with-redacted-views)
|
|
15
|
+
- [UIViewRepresentable Essentials](#uiviewrepresentable-essentials)
|
|
16
|
+
- [Summary Checklist](#summary-checklist)
|
|
17
|
+
|
|
18
|
+
## View Structure Principles
|
|
19
|
+
|
|
20
|
+
SwiftUI's diffing algorithm compares view hierarchies to determine what needs updating. Proper view composition directly impacts performance.
|
|
21
|
+
|
|
22
|
+
## Prefer Modifiers Over Conditional Views
|
|
23
|
+
|
|
24
|
+
**Prefer "no-effect" modifiers over conditionally including views.** When you introduce a branch, consider whether you're representing multiple views or two states of the same view.
|
|
25
|
+
|
|
26
|
+
### Use Opacity Instead of Conditional Inclusion
|
|
27
|
+
|
|
28
|
+
```swift
|
|
29
|
+
// Good - same view, different states
|
|
30
|
+
SomeView()
|
|
31
|
+
.opacity(isVisible ? 1 : 0)
|
|
32
|
+
|
|
33
|
+
// Avoid - creates/destroys view identity
|
|
34
|
+
if isVisible {
|
|
35
|
+
SomeView()
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Why**: Conditional view inclusion can cause loss of state, poor animation performance, and breaks view identity. Using modifiers maintains view identity across state changes.
|
|
40
|
+
|
|
41
|
+
### When Conditionals Are Appropriate
|
|
42
|
+
|
|
43
|
+
Use conditionals when you truly have **different views**, not different states:
|
|
44
|
+
|
|
45
|
+
```swift
|
|
46
|
+
// Correct - fundamentally different views
|
|
47
|
+
if isLoggedIn {
|
|
48
|
+
DashboardView()
|
|
49
|
+
} else {
|
|
50
|
+
LoginView()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Correct - optional content
|
|
54
|
+
if let user {
|
|
55
|
+
UserProfileView(user: user)
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Conditional View Modifier Extensions Break Identity
|
|
60
|
+
|
|
61
|
+
A common pattern is an `if`-based `View` extension for conditional modifiers. This changes the view's return type between branches, which destroys view identity and breaks animations:
|
|
62
|
+
|
|
63
|
+
```swift
|
|
64
|
+
// Problematic -- different return types per branch
|
|
65
|
+
extension View {
|
|
66
|
+
@ViewBuilder func `if`<T: View>(_ condition: Bool, transform: (Self) -> T) -> some View {
|
|
67
|
+
if condition {
|
|
68
|
+
transform(self) // Returns T
|
|
69
|
+
} else {
|
|
70
|
+
self // Returns Self
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Prefer applying the modifier directly with a ternary or always-present modifier:
|
|
77
|
+
|
|
78
|
+
```swift
|
|
79
|
+
// Good -- same view identity maintained
|
|
80
|
+
Text("Hello")
|
|
81
|
+
.opacity(isHighlighted ? 1 : 0.5)
|
|
82
|
+
|
|
83
|
+
// Good -- modifier always present, value changes
|
|
84
|
+
Text("Hello")
|
|
85
|
+
.foregroundStyle(isError ? .red : .primary)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Extract Subviews, Not Computed Properties
|
|
89
|
+
|
|
90
|
+
### The Problem with @ViewBuilder Functions
|
|
91
|
+
|
|
92
|
+
When you use `@ViewBuilder` functions or computed properties for complex views, the entire function re-executes on every parent state change:
|
|
93
|
+
|
|
94
|
+
```swift
|
|
95
|
+
// BAD - re-executes complexSection() on every tap
|
|
96
|
+
struct ParentView: View {
|
|
97
|
+
@State private var count = 0
|
|
98
|
+
|
|
99
|
+
var body: some View {
|
|
100
|
+
VStack {
|
|
101
|
+
Button("Tap: \(count)") { count += 1 }
|
|
102
|
+
complexSection() // Re-executes every tap!
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@ViewBuilder
|
|
107
|
+
func complexSection() -> some View {
|
|
108
|
+
// Complex views that re-execute unnecessarily
|
|
109
|
+
ForEach(0..<100) { i in
|
|
110
|
+
HStack {
|
|
111
|
+
Image(systemName: "star")
|
|
112
|
+
Text("Item \(i)")
|
|
113
|
+
Spacer()
|
|
114
|
+
Text("Detail")
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### The Solution: Separate Structs
|
|
122
|
+
|
|
123
|
+
Extract to separate `struct` views. SwiftUI can skip their `body` when inputs don't change:
|
|
124
|
+
|
|
125
|
+
```swift
|
|
126
|
+
// GOOD - ComplexSection body SKIPPED when its inputs don't change
|
|
127
|
+
struct ParentView: View {
|
|
128
|
+
@State private var count = 0
|
|
129
|
+
|
|
130
|
+
var body: some View {
|
|
131
|
+
VStack {
|
|
132
|
+
Button("Tap: \(count)") { count += 1 }
|
|
133
|
+
ComplexSection() // Body skipped during re-evaluation
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
struct ComplexSection: View {
|
|
139
|
+
var body: some View {
|
|
140
|
+
ForEach(0..<100) { i in
|
|
141
|
+
HStack {
|
|
142
|
+
Image(systemName: "star")
|
|
143
|
+
Text("Item \(i)")
|
|
144
|
+
Spacer()
|
|
145
|
+
Text("Detail")
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Why This Works
|
|
153
|
+
|
|
154
|
+
1. SwiftUI compares the `ComplexSection` struct (which has no properties)
|
|
155
|
+
2. Since nothing changed, SwiftUI skips calling `ComplexSection.body`
|
|
156
|
+
3. The complex view code never executes unnecessarily
|
|
157
|
+
|
|
158
|
+
## When @ViewBuilder Functions Are Acceptable
|
|
159
|
+
|
|
160
|
+
Use for small, simple sections (a few views, no expensive computation) that don't affect performance. `@ViewBuilder` functions work particularly well for static content that doesn't depend on any `@State` or `@Binding`, since SwiftUI won't need to diff them independently. Extract to a separate `struct` when the section is complex, depends on state, or needs to be skipped during re-evaluation.
|
|
161
|
+
|
|
162
|
+
## When to Extract Subviews
|
|
163
|
+
|
|
164
|
+
Extract complex views into separate subviews when:
|
|
165
|
+
- The view has multiple logical sections or responsibilities
|
|
166
|
+
- The view contains reusable components
|
|
167
|
+
- The view body becomes difficult to read or understand
|
|
168
|
+
- You need to isolate state changes for performance
|
|
169
|
+
- The view is becoming large (keep views small for better performance)
|
|
170
|
+
|
|
171
|
+
## Container View Pattern
|
|
172
|
+
|
|
173
|
+
### Avoid Closure-Based Content
|
|
174
|
+
|
|
175
|
+
Closures can't be compared, causing unnecessary re-renders:
|
|
176
|
+
|
|
177
|
+
```swift
|
|
178
|
+
// BAD - closure prevents SwiftUI from skipping updates
|
|
179
|
+
struct MyContainer<Content: View>: View {
|
|
180
|
+
let content: () -> Content
|
|
181
|
+
|
|
182
|
+
var body: some View {
|
|
183
|
+
VStack {
|
|
184
|
+
Text("Header")
|
|
185
|
+
content() // Always called, can't compare closures
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Usage forces re-render on every parent update
|
|
191
|
+
MyContainer {
|
|
192
|
+
ExpensiveView()
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Use @ViewBuilder Property Instead
|
|
197
|
+
|
|
198
|
+
```swift
|
|
199
|
+
// GOOD - view can be compared
|
|
200
|
+
struct MyContainer<Content: View>: View {
|
|
201
|
+
@ViewBuilder let content: Content
|
|
202
|
+
|
|
203
|
+
var body: some View {
|
|
204
|
+
VStack {
|
|
205
|
+
Text("Header")
|
|
206
|
+
content // SwiftUI can compare and skip if unchanged
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Usage - SwiftUI can diff ExpensiveView
|
|
212
|
+
MyContainer {
|
|
213
|
+
ExpensiveView()
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## ZStack vs overlay/background
|
|
218
|
+
|
|
219
|
+
Use `ZStack` to **compose multiple peer views** that should be layered together and jointly define layout.
|
|
220
|
+
|
|
221
|
+
Prefer `overlay` / `background` when you’re **decorating a primary view**.
|
|
222
|
+
Not primarily because they don’t affect layout size, but because they **express intent and improve readability**: the view being modified remains the clear layout anchor.
|
|
223
|
+
|
|
224
|
+
A key difference is **size proposal behavior**:
|
|
225
|
+
- In `overlay` / `background`, the child view implicitly adopts the size proposed to the parent when it doesn’t define its own size, making decorative attachments feel natural and predictable.
|
|
226
|
+
- In `ZStack`, each child participates independently in layout, and no implicit size inheritance exists. This makes it better suited for peer composition, but less intuitive for simple decoration.
|
|
227
|
+
|
|
228
|
+
Use `ZStack` (or another container) when the “decoration” **must explicitly participate in layout sizing**—for example, when reserving space, extending tappable/visible bounds, or preventing overlap with neighboring views.
|
|
229
|
+
|
|
230
|
+
### Examples
|
|
231
|
+
|
|
232
|
+
```swift
|
|
233
|
+
// GOOD - decoration via overlay (layout anchored to button)
|
|
234
|
+
Button("Continue") { }
|
|
235
|
+
.overlay(alignment: .trailing) {
|
|
236
|
+
Image(systemName: "lock.fill").padding(.trailing, 8)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// BAD - ZStack when overlay suffices (layout no longer anchored to button)
|
|
240
|
+
ZStack(alignment: .trailing) {
|
|
241
|
+
Button("Continue") { }
|
|
242
|
+
Image(systemName: "lock.fill").padding(.trailing, 8)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// GOOD - background shape takes parent size
|
|
246
|
+
HStack(spacing: 12) { Text("Inbox"); Text("Next") }
|
|
247
|
+
.background { Capsule().strokeBorder(.blue, lineWidth: 2) }
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Compositing Group Before Clipping
|
|
251
|
+
|
|
252
|
+
**Always add `.compositingGroup()` before `.clipShape()` when clipping layered views (`.overlay` or `.background`).** Without it, each layer is antialiased separately and then composited. Where antialiased edges overlap — typically at rounded corners — you get visible color fringes (semi-transparent pixels of different colors blending together).
|
|
253
|
+
|
|
254
|
+
```swift
|
|
255
|
+
let shape = RoundedRectangle(cornerRadius: 16)
|
|
256
|
+
|
|
257
|
+
// BAD - each layer antialiased separately, producing color fringes at corners
|
|
258
|
+
Color.red
|
|
259
|
+
.overlay(.white, in: shape)
|
|
260
|
+
.clipShape(shape)
|
|
261
|
+
.frame(width: 200, height: 150)
|
|
262
|
+
|
|
263
|
+
// GOOD - layers composited first, antialiasing applied once during clipping
|
|
264
|
+
Color.red
|
|
265
|
+
.overlay(.white, in: .rect)
|
|
266
|
+
.compositingGroup()
|
|
267
|
+
.clipShape(shape)
|
|
268
|
+
.frame(width: 200, height: 150)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`.compositingGroup()` forces all child layers to be rendered into a single offscreen buffer before the clip is applied. This means antialiasing only happens once — on the final composited result — eliminating the fringe artifacts.
|
|
272
|
+
|
|
273
|
+
## Reusable Styling with ViewModifier
|
|
274
|
+
|
|
275
|
+
Extract repeated modifier combinations into a `ViewModifier` struct. Expose via a `View` extension for autocompletion:
|
|
276
|
+
|
|
277
|
+
```swift
|
|
278
|
+
private struct CardStyle: ViewModifier {
|
|
279
|
+
func body(content: Content) -> some View {
|
|
280
|
+
content
|
|
281
|
+
.padding()
|
|
282
|
+
.background(Color(.secondarySystemBackground))
|
|
283
|
+
.clipShape(.rect(cornerRadius: 12))
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
extension View {
|
|
288
|
+
func cardStyle() -> some View {
|
|
289
|
+
modifier(CardStyle())
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Custom ButtonStyle
|
|
295
|
+
|
|
296
|
+
Use the `ButtonStyle` protocol for reusable button designs. Use `PrimitiveButtonStyle` only when you need custom interaction handling (e.g., simultaneous gestures):
|
|
297
|
+
|
|
298
|
+
```swift
|
|
299
|
+
struct PrimaryButtonStyle: ButtonStyle {
|
|
300
|
+
func makeBody(configuration: Configuration) -> some View {
|
|
301
|
+
configuration.label
|
|
302
|
+
.bold()
|
|
303
|
+
.foregroundStyle(.white)
|
|
304
|
+
.padding(.horizontal, 16)
|
|
305
|
+
.padding(.vertical, 8)
|
|
306
|
+
.background(Color.accentColor)
|
|
307
|
+
.clipShape(Capsule())
|
|
308
|
+
.scaleEffect(configuration.isPressed ? 0.95 : 1)
|
|
309
|
+
.animation(.smooth, value: configuration.isPressed)
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Discoverability with Static Member Lookup
|
|
315
|
+
|
|
316
|
+
Make custom styles and modifiers discoverable via leading-dot syntax:
|
|
317
|
+
|
|
318
|
+
```swift
|
|
319
|
+
extension ButtonStyle where Self == PrimaryButtonStyle {
|
|
320
|
+
static var primary: PrimaryButtonStyle { .init() }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Usage: .buttonStyle(.primary)
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
This pattern works for any SwiftUI style protocol (`ButtonStyle`, `ListStyle`, `ToggleStyle`, etc.).
|
|
327
|
+
|
|
328
|
+
## Skeleton Loading with Redacted Views
|
|
329
|
+
|
|
330
|
+
Use `.redacted(reason: .placeholder)` to show skeleton views while data loads. Use `.unredacted()` to opt out specific views:
|
|
331
|
+
|
|
332
|
+
```swift
|
|
333
|
+
VStack(alignment: .leading) {
|
|
334
|
+
Text(article?.title ?? String(repeating: "X", count: 20))
|
|
335
|
+
.font(.headline)
|
|
336
|
+
Text(article?.author ?? String(repeating: "X", count: 12))
|
|
337
|
+
.font(.subheadline)
|
|
338
|
+
Text("SwiftLee")
|
|
339
|
+
.font(.caption)
|
|
340
|
+
.unredacted()
|
|
341
|
+
}
|
|
342
|
+
.redacted(reason: article == nil ? .placeholder : [])
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Apply `.redacted` on a container to redact all children at once.
|
|
346
|
+
|
|
347
|
+
## UIViewRepresentable Essentials
|
|
348
|
+
|
|
349
|
+
When bridging UIKit views into SwiftUI:
|
|
350
|
+
|
|
351
|
+
- `makeUIView(context:)` is called **once** to create the UIKit view
|
|
352
|
+
- `updateUIView(_:context:)` is called on **every SwiftUI redraw** to sync state
|
|
353
|
+
- The representable struct itself is **recreated on every redraw** -- avoid heavy work in its init
|
|
354
|
+
- Use a `Coordinator` for delegate callbacks and two-way communication
|
|
355
|
+
|
|
356
|
+
```swift
|
|
357
|
+
struct MapView: UIViewRepresentable {
|
|
358
|
+
let coordinate: CLLocationCoordinate2D
|
|
359
|
+
|
|
360
|
+
func makeUIView(context: Context) -> MKMapView {
|
|
361
|
+
let map = MKMapView()
|
|
362
|
+
map.delegate = context.coordinator
|
|
363
|
+
return map
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
func updateUIView(_ map: MKMapView, context: Context) {
|
|
367
|
+
map.setCenter(coordinate, animated: true)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
func makeCoordinator() -> Coordinator { Coordinator() }
|
|
371
|
+
|
|
372
|
+
class Coordinator: NSObject, MKMapViewDelegate { }
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
## Summary Checklist
|
|
377
|
+
|
|
378
|
+
- [ ] Prefer modifiers over conditional views for state changes
|
|
379
|
+
- [ ] Avoid `if`-based conditional modifier extensions (they break view identity)
|
|
380
|
+
- [ ] Complex views extracted to separate subviews
|
|
381
|
+
- [ ] Views kept small for better performance
|
|
382
|
+
- [ ] `@ViewBuilder` functions only for simple sections
|
|
383
|
+
- [ ] Container views use `@ViewBuilder let content: Content`
|
|
384
|
+
- [ ] Extract views when they have multiple responsibilities or become hard to read
|
|
385
|
+
- [ ] Reusable styling extracted into `ViewModifier` or `ButtonStyle`
|
|
386
|
+
- [ ] Custom styles exposed via static member lookup for discoverability
|
|
387
|
+
- [ ] Use `.redacted(reason: .placeholder)` for skeleton loading states
|
|
388
|
+
- [ ] `.compositingGroup()` before `.clipShape()` on layered views (overlay/background) to avoid antialiasing fringes
|
|
389
|
+
- [ ] UIViewRepresentable: heavy work in make/update, not in struct init
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Sections
|
|
2
|
+
|
|
3
|
+
This file defines all sections, their ordering, impact levels, and descriptions.
|
|
4
|
+
The section ID (in parentheses) is the filename prefix used to group rules.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Core Rendering (rendering)
|
|
9
|
+
|
|
10
|
+
**Impact:** CRITICAL
|
|
11
|
+
**Description:** Fundamental React Native rendering rules. Violations cause
|
|
12
|
+
runtime crashes or broken UI.
|
|
13
|
+
|
|
14
|
+
## 2. List Performance (list-performance)
|
|
15
|
+
|
|
16
|
+
**Impact:** HIGH
|
|
17
|
+
**Description:** Optimizing virtualized lists (FlatList, LegendList, FlashList)
|
|
18
|
+
for smooth scrolling and fast updates.
|
|
19
|
+
|
|
20
|
+
## 3. Animation (animation)
|
|
21
|
+
|
|
22
|
+
**Impact:** HIGH
|
|
23
|
+
**Description:** GPU-accelerated animations, Reanimated patterns, and avoiding
|
|
24
|
+
render thrashing during gestures.
|
|
25
|
+
|
|
26
|
+
## 4. Scroll Performance (scroll)
|
|
27
|
+
|
|
28
|
+
**Impact:** HIGH
|
|
29
|
+
**Description:** Tracking scroll position without causing render thrashing.
|
|
30
|
+
|
|
31
|
+
## 5. Navigation (navigation)
|
|
32
|
+
|
|
33
|
+
**Impact:** HIGH
|
|
34
|
+
**Description:** Using native navigators for stack and tab navigation instead of
|
|
35
|
+
JS-based alternatives.
|
|
36
|
+
|
|
37
|
+
## 6. React State (react-state)
|
|
38
|
+
|
|
39
|
+
**Impact:** MEDIUM
|
|
40
|
+
**Description:** Patterns for managing React state to avoid stale closures and
|
|
41
|
+
unnecessary re-renders.
|
|
42
|
+
|
|
43
|
+
## 7. State Architecture (state)
|
|
44
|
+
|
|
45
|
+
**Impact:** MEDIUM
|
|
46
|
+
**Description:** Ground truth principles for state variables and derived values.
|
|
47
|
+
|
|
48
|
+
## 8. React Compiler (react-compiler)
|
|
49
|
+
|
|
50
|
+
**Impact:** MEDIUM
|
|
51
|
+
**Description:** Compatibility patterns for React Compiler with React Native and
|
|
52
|
+
Reanimated.
|
|
53
|
+
|
|
54
|
+
## 9. User Interface (ui)
|
|
55
|
+
|
|
56
|
+
**Impact:** MEDIUM
|
|
57
|
+
**Description:** Native UI patterns for images, menus, modals, styling, and
|
|
58
|
+
platform-consistent interfaces.
|
|
59
|
+
|
|
60
|
+
## 10. Design System (design-system)
|
|
61
|
+
|
|
62
|
+
**Impact:** MEDIUM
|
|
63
|
+
**Description:** Architecture patterns for building maintainable component
|
|
64
|
+
libraries.
|
|
65
|
+
|
|
66
|
+
## 11. Monorepo (monorepo)
|
|
67
|
+
|
|
68
|
+
**Impact:** LOW
|
|
69
|
+
**Description:** Dependency management and native module configuration in
|
|
70
|
+
monorepos.
|
|
71
|
+
|
|
72
|
+
## 12. Third-Party Dependencies (imports)
|
|
73
|
+
|
|
74
|
+
**Impact:** LOW
|
|
75
|
+
**Description:** Wrapping and re-exporting third-party dependencies for
|
|
76
|
+
maintainability.
|
|
77
|
+
|
|
78
|
+
## 13. JavaScript (js)
|
|
79
|
+
|
|
80
|
+
**Impact:** LOW
|
|
81
|
+
**Description:** Micro-optimizations like hoisting expensive object creation.
|
|
82
|
+
|
|
83
|
+
## 14. Fonts (fonts)
|
|
84
|
+
|
|
85
|
+
**Impact:** LOW
|
|
86
|
+
**Description:** Native font loading for improved performance.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Rule Title Here
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: Optional description of impact (e.g., "20-50% improvement")
|
|
5
|
+
tags: tag1, tag2
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Rule Title Here
|
|
9
|
+
|
|
10
|
+
**Impact: MEDIUM (optional impact description)**
|
|
11
|
+
|
|
12
|
+
Brief explanation of the rule and why it matters. This should be clear and concise, explaining the performance implications.
|
|
13
|
+
|
|
14
|
+
**Incorrect (description of what's wrong):**
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// Bad code example here
|
|
18
|
+
const bad = example()
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Correct (description of what's right):**
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
// Good code example here
|
|
25
|
+
const good = example()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Reference: [Link to documentation or resource](https://example.com)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Prefer useDerivedValue Over useAnimatedReaction
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: cleaner code, automatic dependency tracking
|
|
5
|
+
tags: animation, reanimated, derived-value
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Prefer useDerivedValue Over useAnimatedReaction
|
|
9
|
+
|
|
10
|
+
When deriving a shared value from another, use `useDerivedValue` instead of
|
|
11
|
+
`useAnimatedReaction`. Derived values are declarative, automatically track
|
|
12
|
+
dependencies, and return a value you can use directly. Animated reactions are
|
|
13
|
+
for side effects, not derivations.
|
|
14
|
+
|
|
15
|
+
**Incorrect (useAnimatedReaction for derivation):**
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { useSharedValue, useAnimatedReaction } from 'react-native-reanimated'
|
|
19
|
+
|
|
20
|
+
function MyComponent() {
|
|
21
|
+
const progress = useSharedValue(0)
|
|
22
|
+
const opacity = useSharedValue(1)
|
|
23
|
+
|
|
24
|
+
useAnimatedReaction(
|
|
25
|
+
() => progress.value,
|
|
26
|
+
(current) => {
|
|
27
|
+
opacity.value = 1 - current
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// ...
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Correct (useDerivedValue):**
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { useSharedValue, useDerivedValue } from 'react-native-reanimated'
|
|
39
|
+
|
|
40
|
+
function MyComponent() {
|
|
41
|
+
const progress = useSharedValue(0)
|
|
42
|
+
|
|
43
|
+
const opacity = useDerivedValue(() => 1 - progress.get())
|
|
44
|
+
|
|
45
|
+
// ...
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use `useAnimatedReaction` only for side effects that don't produce a value
|
|
50
|
+
(e.g., triggering haptics, logging, calling `runOnJS`).
|
|
51
|
+
|
|
52
|
+
Reference:
|
|
53
|
+
[Reanimated useDerivedValue](https://docs.swmansion.com/react-native-reanimated/docs/core/useDerivedValue)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Use GestureDetector for Animated Press States
|
|
3
|
+
impact: MEDIUM
|
|
4
|
+
impactDescription: UI thread animations, smoother press feedback
|
|
5
|
+
tags: animation, gestures, press, reanimated
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use GestureDetector for Animated Press States
|
|
9
|
+
|
|
10
|
+
For animated press states (scale, opacity on press), use `GestureDetector` with
|
|
11
|
+
`Gesture.Tap()` and shared values instead of Pressable's
|
|
12
|
+
`onPressIn`/`onPressOut`. Gesture callbacks run on the UI thread as worklets—no
|
|
13
|
+
JS thread round-trip for press animations.
|
|
14
|
+
|
|
15
|
+
**Incorrect (Pressable with JS thread callbacks):**
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { Pressable } from 'react-native'
|
|
19
|
+
import Animated, {
|
|
20
|
+
useSharedValue,
|
|
21
|
+
useAnimatedStyle,
|
|
22
|
+
withTiming,
|
|
23
|
+
} from 'react-native-reanimated'
|
|
24
|
+
|
|
25
|
+
function AnimatedButton({ onPress }: { onPress: () => void }) {
|
|
26
|
+
const scale = useSharedValue(1)
|
|
27
|
+
|
|
28
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
29
|
+
transform: [{ scale: scale.value }],
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<Pressable
|
|
34
|
+
onPress={onPress}
|
|
35
|
+
onPressIn={() => (scale.value = withTiming(0.95))}
|
|
36
|
+
onPressOut={() => (scale.value = withTiming(1))}
|
|
37
|
+
>
|
|
38
|
+
<Animated.View style={animatedStyle}>
|
|
39
|
+
<Text>Press me</Text>
|
|
40
|
+
</Animated.View>
|
|
41
|
+
</Pressable>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Correct (GestureDetector with UI thread worklets):**
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
|
|
50
|
+
import Animated, {
|
|
51
|
+
useSharedValue,
|
|
52
|
+
useAnimatedStyle,
|
|
53
|
+
withTiming,
|
|
54
|
+
interpolate,
|
|
55
|
+
runOnJS,
|
|
56
|
+
} from 'react-native-reanimated'
|
|
57
|
+
|
|
58
|
+
function AnimatedButton({ onPress }: { onPress: () => void }) {
|
|
59
|
+
// Store the press STATE (0 = not pressed, 1 = pressed)
|
|
60
|
+
const pressed = useSharedValue(0)
|
|
61
|
+
|
|
62
|
+
const tap = Gesture.Tap()
|
|
63
|
+
.onBegin(() => {
|
|
64
|
+
pressed.set(withTiming(1))
|
|
65
|
+
})
|
|
66
|
+
.onFinalize(() => {
|
|
67
|
+
pressed.set(withTiming(0))
|
|
68
|
+
})
|
|
69
|
+
.onEnd(() => {
|
|
70
|
+
runOnJS(onPress)()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Derive visual values from the state
|
|
74
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
75
|
+
transform: [
|
|
76
|
+
{ scale: interpolate(withTiming(pressed.get()), [0, 1], [1, 0.95]) },
|
|
77
|
+
],
|
|
78
|
+
}))
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<GestureDetector gesture={tap}>
|
|
82
|
+
<Animated.View style={animatedStyle}>
|
|
83
|
+
<Text>Press me</Text>
|
|
84
|
+
</Animated.View>
|
|
85
|
+
</GestureDetector>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Store the press **state** (0 or 1), then derive the scale via `interpolate`.
|
|
91
|
+
This keeps the shared value as ground truth. Use `runOnJS` to call JS functions
|
|
92
|
+
from worklets. Use `.set()` and `.get()` for React Compiler compatibility.
|
|
93
|
+
|
|
94
|
+
Reference:
|
|
95
|
+
[Gesture Handler Tap Gesture](https://docs.swmansion.com/react-native-gesture-handler/docs/gestures/tap-gesture)
|