
Swiftui Pro
- 24.2k installs
- 4.4k repo stars
- Updated April 20, 2026
- twostraws/swiftui-agent-skill
SwiftUI Pro is an agent skill that guides developers through modern SwiftUI best practices for iOS development.
About
An agent skill for AI coding assistants helping developers write modern, idiomatic SwiftUI code. Covers iOS 26+ and Swift 6.2+ with guidance on navigation, layout, animations, state management, VoiceOver accessibility, and avoiding deprecated APIs.
- Modern SwiftUI best practices for iOS 26+ and Swift 6.2+
- Covers navigation, layout, animations, state management, VoiceOver, deprecated APIs
- Targets common LLM mistakes in SwiftUI code
Swiftui Pro by the numbers
- 24,205 all-time installs (skills.sh)
- +765 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #8 of 1,048 Mobile Development skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
swiftui-pro capabilities & compatibility
- Capabilities
- swiftui layout · state management · animations
- Platforms
- macOS
npx skills add https://github.com/twostraws/swiftui-agent-skill --skill swiftui-proAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 24.2k |
|---|---|
| repo stars | ★ 4.4k |
| Security audit | 3 / 3 scanners passed |
| Last updated | April 20, 2026 |
| Repository | twostraws/swiftui-agent-skill ↗ |
How do you review SwiftUI code for best practices?
An agent skill for AI coding assistants helping developers write modern, idiomatic SwiftUI code. Covers iOS 26+ and Swift 6.2+ with guidance on navigation, layout, animations, state management, Voice
Who is it for?
iOS developers reviewing SwiftUI projects who want Paul Hudson-backed reference checks that skip nitpicks and false positives.
Skip if: UIKit-only projects, greenfield SwiftUI scaffolding from scratch, or teams needing automated XCTest generation rather than code review.
When should I use this skill?
User is reading, writing, or reviewing SwiftUI code and wants best-practice feedback on APIs, views, data flow, or navigation.
What you get
Structured review report flagging deprecated APIs, data flow issues, suboptimal modifiers, and navigation problems with reference-backed citations.
- code review report
- deprecated api findings
- data flow recommendations
By the numbers
- Skill version 1.1 authored by Paul Hudson
- Uses 4 bundled reference files: api.md, views.md, data.md, and navigation references
Files
Review Swift and SwiftUI code for correctness, modern API usage, and adherence to project conventions. Report only genuine problems - do not nitpick or invent issues.
Review process:
1. Check for deprecated API using references/api.md. 1. Check that views, modifiers, and animations have been written optimally using references/views.md. 1. Validate that data flow is configured correctly using references/data.md. 1. Ensure navigation is updated and performant using references/navigation.md. 1. Ensure the code uses designs that are accessible and compliant with Apple’s Human Interface Guidelines using references/design.md. 1. Validate accessibility compliance including Dynamic Type, VoiceOver, and Reduce Motion using references/accessibility.md. 1. Ensure the code is able to run efficiently using references/performance.md. 1. Quick validation of Swift code using references/swift.md. 1. Final code hygiene check using references/hygiene.md.
If doing a partial review, load only the relevant reference files.
Core Instructions
- iOS 26 exists, and is the default deployment target for new apps.
- Target Swift 6.2 or later, using modern Swift concurrency.
- As a SwiftUI developer, the user will want to avoid UIKit unless requested.
- Do not introduce third-party frameworks without asking first.
- Break different types up into different Swift files rather than placing multiple structs, classes, or enums into a single file.
- Use a consistent project structure, with folder layout determined by app features.
Output Format
Organize findings by file. For each issue:
1. State the file and relevant line(s). 2. Name the rule being violated (e.g., "Use foregroundStyle() instead of foregroundColor()"). 3. Show a brief before/after code fix.
Skip files with no issues. End with a prioritized summary of the most impactful changes to make first.
Example output:
ContentView.swift
Line 12: Use `foregroundStyle()` instead of `foregroundColor()`.
// Before
Text("Hello").foregroundColor(.red)
// After
Text("Hello").foregroundStyle(.red)Line 24: Icon-only button is bad for VoiceOver - add a text label.
// Before
Button(action: addUser) {
Image(systemName: "plus")
}
// After
Button("Add User", systemImage: "plus", action: addUser)Line 31: Avoid `Binding(get:set:)` in view body - use `@State` with `onChange()` instead.
// Before
TextField("Username", text: Binding(
get: { model.username },
set: { model.username = $0; model.save() }
))
// After
TextField("Username", text: $model.username)
.onChange(of: model.username) {
model.save()
}Summary
1. Accessibility (high): The add button on line 24 is invisible to VoiceOver. 2. Deprecated API (medium): foregroundColor() on line 12 should be foregroundStyle(). 3. Data flow (medium): The manual binding on line 31 is fragile and harder to maintain.
End of example.
References
references/accessibility.md- Dynamic Type, VoiceOver, Reduce Motion, and other accessibility requirements.references/api.md- updating code for modern API, and the deprecated code it replaces.references/design.md- guidance for building accessible apps that meet Apple’s Human Interface Guidelines.references/hygiene.md- making code compile cleanly and be maintainable in the long term.references/navigation.md- navigation usingNavigationStack/NavigationSplitView, plus alerts, confirmation dialogs, and sheets.references/performance.md- optimizing SwiftUI code for maximum performance.references/data.md- data flow, shared state, and property wrappers.references/swift.md- tips on writing modern Swift code, including using Swift Concurrency effectively.references/views.md- view structure, composition, and animation.
{
"name": "swiftui-pro",
"version": "1.0.0",
"description": "Reviews SwiftUI code for best practices on modern APIs, maintainability, and performance.",
"author": { "name": "Paul Hudson" },
"homepage": "https://github.com/twostraws/SwiftUI-Agent-Skill",
"repository": "https://github.com/twostraws/SwiftUI-Agent-Skill",
"license": "MIT",
"skills": "./skills/"
}
interface:
display_name: "SwiftUI Pro"
short_description: "Reviews SwiftUI code for modern best practices."
icon_small: "./assets/swiftui-pro-icon.svg"
icon_large: "./assets/swiftui-pro-icon.png"
brand_color: "#006AFD"
default_prompt: "Use $swiftui-pro to review my project."
policy:
allow_implicit_invocation: true<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="227.72" height="227.72" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 227.72 227.72">
<defs>
<style>
.st0 {
fill: none;
stroke: url(#linear-gradient1);
stroke-miterlimit: 10;
stroke-width: 6px;
}
.st1 {
fill: url(#linear-gradient);
fill-rule: evenodd;
}
</style>
<linearGradient id="linear-gradient" x1="58.405" y1="206.955" x2="178.785" y2="86.575" gradientTransform="translate(0 260) scale(1 -1)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#00d9ff"/>
<stop offset="1" stop-color="#006efd"/>
</linearGradient>
<linearGradient id="linear-gradient1" x1="33.349" y1="218.651" x2="194.371" y2="57.629" gradientTransform="translate(0 252) scale(1 -1)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#00eeaf"/>
<stop offset="0" stop-color="#00d8ff"/>
<stop offset="1" stop-color="#006ffd"/>
</linearGradient>
</defs>
<path class="st1" d="M165.43,131.08c3.87-8.17,16.03-46.62-38.6-85.38,7.61,7.03,40.19,39.2,26.05,75.5-34.2-25.27-87.91-66.75-87.91-66.75,0,0,63.69,60.57,83.58,79.15,19.9,18.59,26.97,27.71,26.47,43.59,0,.01,14.58-23.23-9.59-46.11ZM130.89,145.79c-39.35,18.56-78.42-20.82-78.42-20.82,0,0,21.12,26.7,45.19,35.06,33.72,11.71,54.61-9.64,54.61-9.64L52.42,59.03s56.43,62.62,78.47,86.76Z"/>
<circle class="st0" cx="113.86" cy="113.86" r="110.86"/>
</svg>Accessibility
- Respect the user’s accessibility settings for fonts, colors, animations, and more.
- Do not force specific font sizes. Prefer Dynamic Type (
.font(.body),.font(.headline), etc.). - If you need a custom font size, use
@ScaledMetricwhen targeting iOS 18 and earlier. When targeting iOS 26 or later,.font(.body.scaled(by:))is also available to get font size adjustment. - Flag instances where images have unclear or unhelpful VoiceOver readings, e.g.
Image(.newBanner2026). If they are decorative, suggest usingImage(decorative:)oraccessibilityHidden(), otherwise attach anaccessibilityLabel(). - If the user has “Reduce Motion” enabled, replace large, motion-based animations with opacity instead.
- If buttons have complex or frequently changing labels, recommend using
accessibilityInputLabels()to provide better Voice Control commands. For example, if a button had a live-updating share price for Apple such as “AAPL $271.68”, adding an input label for “Apple” would be a big improvement. - Buttons with image labels must always include text, even if the text is invisible:
Button("Label", systemImage: "plus", action: myAction). Flag icon-only buttons that lack a text label as being bad for VoiceOver. Usually SwiftUI will make labels use the correct label style based on their context – e.g. buttons in iOS toolbars will automatically be icon-only by default – but if there's a specific reason for a button to remain visually icon-only, apply.labelStyle(.iconOnly)to preserve the visual while keeping the text available for VoiceOver. - If color is an important differentiator in the user interface, make sure to respect the environment’s
.accessibilityDifferentiateWithoutColorsetting by showing some kind of variation beyond just color – icons, patterns, strokes, etc. - The same is true of
Menu: usingMenu("Options", systemImage: "ellipsis.circle") { }is much better than just using an image. In the rare case where the menu trigger should really display only the icon,.labelStyle(.iconOnly)can be used. - Never use
onTapGesture()unless you specifically need tap location or tap count. All other tappable elements should be aButton. - If
onTapGesture()must be used, make sure to add.accessibilityAddTraits(.isButton)or similar so it can be read by VoiceOver correctly.
Using modern SwiftUI API
- Always use
foregroundStyle()instead offoregroundColor(). - Always use
clipShape(.rect(cornerRadius:))instead ofcornerRadius(). - Always use the
TabAPI instead oftabItem(). - Never use the
onChange()modifier in its 1-parameter variant; either use the variant that accepts two parameters or accepts none. - Do not use
GeometryReaderif a newer alternative works:containerRelativeFrame(),visualEffect(), or theLayoutprotocol. FlagGeometryReaderusage and suggest the modern alternative. - When designing haptic effects, prefer using
sensoryFeedback()over older UIKit APIs such asUIImpactFeedbackGenerator. - Use the
@Entrymacro to define customEnvironmentValues,FocusValues,Transaction, andContainerValueskeys. This replaces the legacy pattern of manually creating a type conforming to (for example)EnvironmentKeywith adefaultValue, then extendingEnvironmentValueswith a computed property. - Strongly prefer
overlay(alignment:content:)over the deprecatedoverlay(_:alignment:). For example, use.overlay { Text("Hello, world!") }rather than.overlay(Text("Hello, world!")). - Never use
.navigationBarLeadingand.navigationBarTrailingfor toolbar item placement; they are deprecated. The correct, modern placements are.topBarLeadingand.topBarTrailing. - Prefer to rely on automatic grammar agreement when dealing with English, French, German, Portuguese, Spanish, and Italian. For example, use
Text("^[\(people) person](inflect: true)")to show a number of people. - You can fill and stroke a shape with two chained modifiers; you do not need an overlay for the stroke. The overlay was required previously, but this is fixed in iOS 17 and later.
- When referencing images from an asset catalog, prefer the generated symbol asset API when the project is configured to use them:
Image(.avatar)rather thanImage("avatar"). - When targeting iOS 26 and later, SwiftUI has a native
WebViewview type that replaces almost all uses of hand-wrappedWKWebViewinsideUIViewRepresentable. To use it, make sure to includeimport WebKit. ForEachover anenumerated()sequence should not convert to an array first. UseForEach(items.enumerated(), id: \.element.id)directly.- When hiding scroll indicators, use
.scrollIndicators(.hidden)rather thanshowsIndicators: falsein the initializer. - Never use
Textconcatenation with+.
For example, the usage of + here is bad and deprecated:
Text("Hello").foregroundStyle(.red)
+
Text("World").foregroundStyle(.blue)Instead, use text interpolation like this:
let red = Text("Hello").foregroundStyle(.red)
let blue = Text("World").foregroundStyle(.blue)
Text("\(red)\(blue)")Using ObservableObject
If using ObservableObject is absolutely required – for example if you are trying to create a debouncer using a Combine publisher – you should always make sure import Combine is added. This was previously provided through SwiftUI, but that is no longer the case.
Data flow, shared state, and property wrappers
It is important that SwiftUI body code and logic code be kept separate in order to make code easier to read, write, and maintain. That usually means placing code into methods rather than inline in the body property, but often also means carving functionality out into separate @Observable classes.
These rules help ensure code is efficient and works well in the long term.
Shared state
@Observableclasses must be marked@MainActorunless the project has Main Actor default actor isolation. Flag any@Observableclass missing this annotation.- All shared data should use
@Observableclasses with@State(for ownership) and@Bindable/@Environment(for passing). - Strongly prefer not to use
ObservableObject,@Published,@StateObject,@ObservedObject, or@EnvironmentObjectunless they are unavoidable, or if they exist in legacy/integration contexts when changing architecture would be complicated.
Local state
@Stateshould be markedprivateand only owned by the view that created it.- If a view stores a class instance that contains expensive-to-recompute data, e.g.
CIContext, it can be stored using@Stateeven though it is not an observable object. This effectively uses@Stateas a cache – storing something persistently, but not doing any change tracking on it since it's not an observable object.
Bindings
- Strongly prefer to avoid creating bindings using
Binding(get:set:)in view body code. It is much cleaner and simpler to use a binding provided by@State,@Bindingor similar, then useonChange()to trigger any effects. - If the user needs to enter a number into a
TextField, bind theTextFieldto a numeric value such asIntorDouble, then use itsformatinitializer like this:TextField("Enter your score", value: $score, format: .number). Apply either.keyboardType(.numberPad)(for integers) or.keyboardType(.decimalPad)(for floating-point numbers) as appropriate. Using the modifier alone is not sufficient.
Working with data
- Prefer to make structs conform to
Identifiablerather than usingid: \.somePropertyin SwiftUI code. - Never attempt to use
@AppStorageinside an@Observableclass, even if marked@ObservationIgnored– it will not trigger view updates when a change happens.
SwiftData
- If you only need the number of items matching a query, consider
ModelContext.fetchCount()with a fetch descriptor. This will not live update if the data changes unless something else triggers the update, such as@Query, so it should be used carefully.
For more help with SwiftData, suggest the SwiftData Pro agent skill.
If the project uses SwiftData with CloudKit
- Never use
@Attribute(.unique). - Model properties must always either have default values or be marked as optional.
- All relationships must be marked optional.
Design
Creating a uniform design in this app
Prefer to place standard fonts, sizes, colors, stack spacing, padding, rounding, animation timings, and more into a shared enum of constants, so they can be used by all views. This allows the app’s design to feel uniform and consistent, and be adjusted easily.
Requirements for flexible, accessible design
- Never use
UIScreen.main.boundsto read available space; prefer alternatives such ascontainerRelativeFrame(), orvisualEffect()as appropriate, or (if there is no alternative)GeometryReader. - Prefer to avoid fixed frames for views unless content can fit neatly inside; this can cause problems across different device sizes, different Dynamic Type settings, and more. Giving frames some flexibility is usually preferred.
- Apple’s minimum acceptable tap area for interactions on iOS is 44x44. Ensure this is strictly enforced.
Standard system styling
- Strongly prefer to use
ContentUnavailableViewwhen data is missing or empty, rather than designing something custom. - When using
searchable(), you can show empty results usingContentUnavailableView.searchand it will include the search term they used automatically – there’s no need to useContentUnavailableView.search(text: searchText)or similar. - If you need an icon and some text placed horizontally side by side, prefer
LabeloverHStack. - Prefer system hierarchical styles (e.g. secondary/tertiary) over manual opacity when possible, so the system can adapt to the correct context automatically.
- When using
Form, wrap controls such asSliderinLabeledContentso the title and control are laid out correctly. LabeledContentalso works outsideFormfor any title-value display; it might be necessary to define a customLabeledContentStylefor consistent layout across views.- When using
RoundedRectangle, the default rounding style is.continuous– there is no need to specify it explicitly.
Ensuring designs work for everyone
- Use
bold()instead offontWeight(.bold), because usingbold()allows the system to choose the correct weight for the current context. - Only use
fontWeight()for weights other than bold when there's an important reason - scattering aroundfontWeight(.medium)orfontWeight(.semibold)is counterproductive. - Avoid hard-coded values for padding and stack spacing unless specifically requested.
- Avoid UIKit colors (
UIColor) in SwiftUI code; use SwiftUIColoror asset catalog colors. - The font size
.caption2is extremely small, and is generally best avoided. Even the font size.captionis on the small side, and should be used carefully.
Hygiene
- If the project requires secrets such as API keys, never include them in the repository.
- Code comments and documentation comments should be present where the logic isn't self-evident.
- Unit tests should exist for core application logic. UI tests only where unit tests are not possible.
@AppStoragemust never be used to store usernames, passwords, or other sensitive data. Use the keychain for that.- If SwiftLint is configured, it should return no warnings or errors.
- If the project uses Localizable.xcstrings, prefer to add user-facing strings using symbol keys (e.g. “helloWorld”) in the string catalog with
extractionStateset to "manual", accessing them via generated symbols such asText(.helloWorld). Offer to translate new keys into all languages supported by the project. - If the Xcode MCP is configured, prefer its tools over generic alternatives. For example,
RenderPreviewis able to capture images of rendered SwiftUI previews for examination, andDocumentationSearchcan search Apple’s documentation for latest usage instructions.
Navigation and presentation
- Use
NavigationStackorNavigationSplitViewas appropriate; flag all use of the deprecatedNavigationView. - Strongly prefer to use
navigationDestination(for:)to specify destinations; flag all use of the oldNavigationLink(destination:)pattern where it should be replaced. - Never mix
navigationDestination(for:)andNavigationLink(destination:)in the same navigation hierarchy; it causes significant problems. navigationDestination(for:)must be registered once per data type; flag duplicates.
Alerts, confirmation dialogs, and sheets
- Always attach
confirmationDialog()to the user interface that triggers the dialog. This allows Liquid Glass animations to move from the correct source. - If an alert has only a single “OK” button that does nothing but dismiss the alert, it can be omitted entirely:
.alert("Dismiss Me", isPresented: $isShowingAlert) { }. - If a sheet is designed to present an optional piece of data, prefer
sheet(item:)oversheet(isPresented:)so the optional is safely unwrapped. - When using
sheet(item:)with a view that accepts the item as its only initializer parameter, prefersheet(item: $someItem, content: SomeView.init)oversheet(item: $someItem) { someItem in SomeView(item: someItem) }.
Performance
- When toggling modifier values, prefer ternary expressions over if/else view branching to avoid
_ConditionalContent, preserve structural identity, and avoid repeatedly recreating underlying platform views. - Avoid
AnyViewunless absolutely required. Use@ViewBuilder,Group, or generics instead. - If a
ScrollViewhas an opaque, static, and solid background, prefer to usescrollContentBackground(.visible)to improve scroll-edge rendering efficiency. - It is more efficient to break views up by making dedicated SwiftUI views rather than place them into computed properties or methods. Using
@ViewBuilderon a property or method does not solve this; breaking views up is strongly preferred. - Always ensure view initializers are kept as small and simple as possible, avoiding any non-trivial work. Flag any work that can be moved into a
task()modifier to be run when the view is shown. - Similarly, assume each view’s
bodyproperty is called frequently – if logic such as sorting or filtering can be moved out of there easily, it should be. - Avoid creating properties to store formatters such as
DateFormatterunless they are required. A more natural approach is to useTextwith a format, like this:Text(Date.now, format: .dateTime.day().month().year())orText(100, format: .currency(code: "USD")). - Avoid expensive inline transforms in
List/ForEachinitializers (e.g.items.filter { ... }) when they are repeated often. - Prefer deriving transformed data from the source-of-truth using
let, or caching in@State. However, do not cache derived collections in@Stateunless you also own explicit invalidation logic to avoid stale UI. - For large data sets in
ScrollView, useLazyVStack/LazyHStack; flag eager stacks with many children. - Prefer using
task()overonAppear()when doing async work, because it will be cancelled automatically when the view disappears. - Avoid storing escaping
@ViewBuilderclosures on views when possible; store built view results instead.
Example:
// Anti-pattern: stores an escaping closure on the view.
struct CardView<Content: View>: View {
let content: () -> Content
var body: some View {
VStack(alignment: .leading) {
content()
}
.padding()
.background(.ultraThinMaterial)
.clipShape(.rect(cornerRadius: 8))
}
}
// Preferred: store the built view value; the synthesized init handles calling the builder.
struct CardView<Content: View>: View {
@ViewBuilder let content: Content
var body: some View {
VStack(alignment: .leading) {
content
}
.padding()
.background(.ultraThinMaterial)
.clipShape(.rect(cornerRadius: 8))
}
}Swift
- Prefer Swift-native string methods over Foundation equivalents: use
replacing("a", with: "b")notreplacingOccurrences(of: "a", with: "b"). - Prefer modern Foundation API:
URL.documentsDirectoryinstead ofFileManagerdirectory lookups,appending(path:)to append strings to a URL. - Never use C-style number formatting like
String(format: "%.2f", value). UseText(value, format: .number.precision(.fractionLength(2)))or similarFormatStyleAPIs. - Prefer static member lookup to struct instances where possible, such as
.circlerather thanCircle(), and.borderedProminentrather thanBorderedProminentButtonStyle(). - Avoid force unwraps (
!) and forcetryunless the failure is truly unrecoverable, and even then prefer usingfatalError()with a clear description. If possible, useif let,guard let, nil-coalescing, ortry?/do-catch. - Filtering text based on user-input must be done using
localizedStandardContains()as opposed tocontains()orlocalizedCaseInsensitiveContains(). - Strongly prefer
DoubleoverCGFloat, except when using optionals orinout; Swift is able to bridge the two freely except in those two cases. - If you want to count array objects that match a predicate, always use
count(where:)rather thanfilter()followed bycount. - Prefer
Date.nowoverDate()for clarity. - When
import SwiftUIis already in a file, you do not need to addimport UIKitorimport AppKitto access things likeUIImageorNSImage– they are imported automatically on the appropriate platform. - When dealing with the names of people, strongly prefer to use
PersonNameComponentswith modern formatting over simple string interpolation such asText("\(firstName) \(lastName)"). - If a given type of data is repeatedly sorted using an identical closure, e.g.
books.sorted { $0.author < $1.author }, prefer to make the type in question conform toComparableso the sort order is centralized. - Prefer to avoid manual date formatting strings if possible. If manual date formatting is used for user display, at least make sure to use “y” rather than “yyyy” for years, so the year value is correct in all localizations. If the purpose is data exchange with an API, this rule does not apply.
- When trying to convert a string to a date, prefer the modern
Dateinitializer API such asDate(myString, strategy: .iso8601). - Flag instances where errors triggered by a user action are swallowed silently, e.g. using
print(error.localizedDescription)rather than showing an alert or similar. - Prefer
if let value {shorthand overif let value = value {. - Omit return for single expression functions.
ifandswitchcan be used as expressions when returning values and assigning to variables.
For example, this kind of code:
var tileColor: Color {
if isCorrect {
return .green
} else {
return .red
}
}Should be written like this:
var tileColor: Color {
if isCorrect {
.green
} else {
.red
}
}Swift Concurrency
- If an API offers both modern
async/awaitequivalents and older closure-based variants, always prefer theasync/awaitversions. - Never use Grand Central Dispatch (
DispatchQueue.main.async(),DispatchQueue.global(), etc.). Always use modern Swift concurrency (async/await, actors,Task). - Never use
Task.sleep(nanoseconds:); useTask.sleep(for:)instead. - Flag any mutable shared state that isn't protected by an actor or
@MainActor, unless the project is configured to use MainActor default actor isolation. - Assume strict concurrency rules are being applied; flag
@Sendableviolations and data races. - When evaluating
MainActor.run(), check whether the project has its default actor isolation set to Main Actor first, becauseMainActor.run()might not be needed. Task.detached()is often a bad idea. Check any usage extremely carefully.
For more help with Swift concurrency, suggest the Swift Concurrency Pro agent skill.
SwiftUI Views
- Strongly prefer to avoid breaking up view bodies using computed properties or methods that return
some View, even if@ViewBuilderis used. Extract them into separateViewstructs instead, placing each into its own file. - Flag
bodyproperties that are excessively long; they should be broken into extracted subviews. - If the user has created a handful of small, private helper
some Viewproperties for structural readability, and they both belong to the same concern asbodyand would fit inbodyat an acceptable length if inlined, these can be left alone. Otherwise, they should be extracted to newViewstructs. - Button actions should be extracted from view bodies into separate methods, to avoid mixing layout and logic.
- Similarly, general business logic should not live inline in
task(),onAppear()or elsewhere inbody. - Prefer to place view logic into view models or similar, so it can be tested. For more help with testing, suggest the Swift Testing Pro agent skill.
- Each type (struct, class, enum) should be in its own Swift file. Flag files containing multiple type definitions.
- Unless a full-screen editing experience is required, prefer using
TextFieldwithaxis: .verticalto usingTextEditor, because it allows placeholder text. If a specific minimum height is required forTextField, use something likelineLimit(5...). - If a button action can be provided directly as an
actionparameter, do so. For example:Button("Label", systemImage: "plus", action: myAction)is preferred overButton("Label", systemImage: "plus") { action() }. - When rendering SwiftUI views to images, strongly prefer
ImageRendereroverUIGraphicsImageRenderer. #Previewshould be used for previews, not the legacyPreviewProviderprotocol.- When using
TabView(selection:), use a binding to a property that stores an enum rather than an integer or string. For example,Tab("Home", systemImage: "house", value: .home)is better thanTab("Home", systemImage: "house", value: 0). - Strongly prefer to avoid breaking up view bodies using computed properties or methods that return
some View, even if@ViewBuilderis used. Extract them into separateViewstructs instead, placing each into its own file. (Yes, this is repeated, but it’s so important it needs to be mentioned twice.)
Animating views
- Strongly prefer to use the
@Animatablemacro over creatinganimatableDatamanually – the macro automatically adds conformance to theAnimatableprotocol and creates the correctanimatableDataproperty. If some properties should not or cannot be animated (e.g. Booleans, integers, etc), mark them@AnimatableIgnored. - Never use
animation(_ animation: Animation?); always provide a value to watch, such as.animation(.bouncy, value: score). - Chaining animations must be done using a
completionclosure passed towithAnimation(), rather than trying to execute multiplewithAnimation()calls using delays.
For example:
Button("Animate Me") {
withAnimation {
scale = 2
} completion: {
withAnimation {
scale = 1
}
}
}Related skills
How it compares
Pick swiftui-pro over generic Swift linters when the goal is expert SwiftUI-specific review with reference-backed guidance rather than syntax-only static analysis.
FAQ
What does swiftui-pro check during a review?
swiftui-pro checks deprecated APIs via references/api.md, view and modifier optimization via references/views.md, data flow via references/data.md, and navigation patterns via bundled references. The skill reports only genuine problems, not nitpicks.
What version is swiftui-pro?
swiftui-pro is version 1.1, authored by Paul Hudson under the MIT license. The skill includes bundled reference files for API, views, data, and navigation review checks in SwiftUI projects.
Is Swiftui Pro safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.