Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
71
89%
Does it follow best practices?
Impact
—
No eval scenarios have been run
Advisory
Suggest reviewing before use
Adopt the Transferable protocol to enable sharing, drag and drop, copy/paste, and ShareLink with a unified API. Available iOS 16+.
Transferable describes how a type converts to and from transfer representations (clipboard, drag, share sheet). Conform by implementing a static transferRepresentation property.
struct Note: Codable, Identifiable {
let id: UUID
var title: String
var body: String
}
extension Note: Transferable {
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .note)
ProxyRepresentation(exporting: \.body) // fallback: plain text
}
}
extension UTType {
static let note = UTType(exportedAs: "com.example.note")
}Representation order matters — place the most specific first, with broader fallbacks after.
These types already conform to Transferable out of the box:
| Type | Content type |
|---|---|
String | .plainText, .utf8PlainText |
Data | .data |
URL | .url |
AttributedString | .rtf |
Image (SwiftUI) | .image |
Color (SwiftUI) | .color |
For types conforming to Codable. Serializes to JSON by default:
static var transferRepresentation: some TransferRepresentation {
CodableRepresentation(contentType: .myType)
}Delegate to another Transferable type. Ideal for quick text or URL fallbacks:
ProxyRepresentation(exporting: \.title) // export only
ProxyRepresentation(\.url) // import + export via URLFull control over binary serialization:
DataRepresentation(contentType: .png) { image in
try image.pngData()
} importing: { data in
try MyImage(data: data)
}Use DataRepresentation(exportedContentType:) for export-only representations.
For large content best transferred as files:
FileRepresentation(contentType: .movie) { video in
SentTransferredFile(video.fileURL)
} importing: { receivedFile in
let dest = FileManager.default.temporaryDirectory.appendingPathComponent(receivedFile.file.lastPathComponent)
try FileManager.default.copyItem(at: receivedFile.file, to: dest)
return Video(url: dest)
}Present the system share sheet with a Transferable item:
ShareLink(item: note, preview: SharePreview(note.title)) {
Label("Share", systemImage: "square.and.arrow.up")
}
// Multiple items
ShareLink(items: selectedNotes) { note in
SharePreview(note.title)
}
// Simple string sharing
ShareLink(item: "Check out this app!", subject: Text("Cool App"))ShareLink requires the item to conform to Transferable. The preview provides a title, optional image, and optional icon for the share sheet.
struct NoteCard: View {
let note: Note
var body: some View {
Text(note.title)
.draggable(note) // Note must be Transferable
}
}Use .draggable(note) { DragPreview(note) } to provide a custom drag preview.
struct NoteBoard: View {
@State private var notes: [Note] = []
var body: some View {
VStack {
ForEach(notes) { NoteCard(note: $0) }
}
.dropDestination(for: Note.self) { droppedNotes, location in
notes.append(contentsOf: droppedNotes)
return true
} isTargeted: { isOver in
// Highlight drop zone
}
}
}For reordering within a list, combine .draggable with .dropDestination or use onMove on ForEach inside List.
Accept multiple content types with separate .dropDestination modifiers or use DropDelegate for advanced logic:
.dropDestination(for: String.self) { strings, _ in
notes.append(contentsOf: strings.map { Note(id: UUID(), title: $0, body: "") })
return true
}For direct clipboard access outside SwiftUI's drag/drop system, use UIPasteboard:
// Copy
UIPasteboard.general.string = note.title
// Paste
if let text = UIPasteboard.general.string {
// use text
}For Transferable types with custom content types, export to Data first:
let data = try await note.exported(as: .note)
UIPasteboard.general.setData(data, forPasteboardType: UTType.note.identifier)Prefer SwiftUI's .copyable, .cuttable, and .pasteDestination modifiers (iOS 16+) over direct UIPasteboard usage when possible — they integrate with the Edit menu and keyboard shortcuts automatically.
enum SharedContent: Transferable {
case text(String)
case url(URL)
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation { content in
switch content {
case .text(let s): return s
case .url(let u): return u.absoluteString
}
}
}
}When your type should be sharable but not importable:
extension Report: Transferable {
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(exportedContentType: .pdf) { report in
try report.renderPDF()
}
}
}UTType identifiers in Info.plist under Exported/Imported Type Identifiers.FileRepresentation files are temporary; copy them if you need to persist.Transferable conformance must be on the main type, not an extension in a different module, to avoid linker issues.Use consistent patterns for loading images, previewing media, and presenting a full-screen viewer.
AsyncImage for simple remote images. LazyImage is from the third-party Nuke library if you need advanced caching and prefetching.QuickLook) to present a full-screen media viewer.openWindow for desktop/visionOS and a sheet for iOS.struct MediaPreviewRow: View {
@Environment(QuickLook.self) private var quickLook
let attachments: [MediaAttachment]
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(attachments) { attachment in
Button {
quickLook.prepareFor(
selectedMediaAttachment: attachment,
mediaAttachments: attachments
)
} label: {
LazyImage(url: attachment.previewURL) { state in
if let image = state.image {
image.resizable().aspectRatio(contentMode: .fill)
} else {
ProgressView()
}
}
.frame(width: 120, height: 120)
.clipped()
}
.buttonStyle(.plain)
}
}
}
}
}struct AppRoot: View {
@State private var quickLook = QuickLook.shared
var body: some View {
content
.environment(quickLook)
.sheet(item: $quickLook.selectedMediaAttachment) { selected in
MediaUIView(selectedAttachment: selected, attachments: quickLook.mediaAttachments)
}
}
}Provide a custom top selector or pill row that sits above scroll content, using safeAreaBar(.top) on iOS 26 and a compatible fallback on earlier OS versions.
Use safeAreaBar(edge: .top) to attach the view to the safe area bar.
if #available(iOS 26.0, *) {
content
.safeAreaBar(edge: .top) {
TopSelectorView()
.padding(.horizontal, .layoutPadding)
}
}Use .safeAreaInset(edge: .top) and hide the toolbar background to avoid double layers.
content
.toolbarBackground(.hidden, for: .navigationBar)
.safeAreaInset(edge: .top, spacing: 0) {
VStack(spacing: 0) {
TopSelectorView()
.padding(.vertical)
.padding(.horizontal, .layoutPadding)
.background(Color.primary.opacity(0.06))
.background(Material.ultraThin)
Divider()
}
}safeAreaBar when available; it integrates better with the navigation bar.Use a title menu in the navigation bar to provide context‑specific filtering or quick actions without adding extra chrome.
ToolbarTitleMenu to attach a menu to the navigation title.@ToolbarContentBuilder
private var toolbarView: some ToolbarContent {
ToolbarTitleMenu {
Button("Latest") { timeline = .latest }
Button("Resume") { timeline = .resume }
Divider()
Button("Local") { timeline = .local }
Button("Federated") { timeline = .federated }
}
}NavigationStack {
TimelineView()
.toolbar {
toolbarView
}
}struct TimelineScreen: View {
@State private var timeline: TimelineFilter = .home
var body: some View {
NavigationStack {
TimelineView()
.toolbar {
ToolbarItem(placement: .principal) {
VStack(spacing: 2) {
Text(timeline.title)
.font(.headline)
Text(timeline.subtitle)
.font(.caption)
.foregroundStyle(.secondary)
}
}
ToolbarTitleMenu {
Button("Home") { timeline = .home }
Button("Local") { timeline = .local }
Button("Federated") { timeline = .federated }
}
}
.navigationBarTitleDisplayMode(.inline)
}
}
}ToolbarItem(placement: .principal) {
VStack(spacing: 2) {
Text(title)
.font(.headline)
Text(subtitle)
.font(.caption)
.foregroundStyle(.secondary)
}
}Use a bottom-anchored input bar for chat, composer, or quick actions without fighting the keyboard.
.safeAreaInset(edge: .bottom) to anchor the toolbar above the keyboard.ScrollView or List.@FocusState and set initial focus when needed.@MainActor
struct ConversationView: View {
@FocusState private var isInputFocused: Bool
@State private var scrollPosition = ScrollPosition(edge: .bottom)
@State private var draft = ""
var body: some View {
ScrollView {
LazyVStack {
ForEach(messages) { message in
MessageRow(message: message)
}
}
.scrollTargetLayout()
.padding(.horizontal, .layoutPadding)
}
.scrollPosition($scrollPosition)
.safeAreaInset(edge: .bottom) {
InputBar(text: $draft)
.focused($isInputFocused)
}
.scrollDismissesKeyboard(.interactively)
.onAppear { isInputFocused = true }
}
}.scrollDismissesKeyboard(.interactively) for chat-like screens.Use this when adding or customizing the macOS/iPadOS menu bar with SwiftUI commands.
Scene level with .commands { ... }.SidebarCommands() when your UI includes a navigation sidebar.CommandMenu for app-specific menus and group related actions.CommandGroup to insert items before/after system groups or replace them.FocusedValue for context-sensitive menu items that depend on the active scene.@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
CommandMenu("Actions") {
Button("Run", action: run)
.keyboardShortcut("R")
Button("Stop", action: stop)
.keyboardShortcut(".")
}
}
}
private func run() {}
private func stop() {}
}WindowGroup {
ContentView()
}
.commands {
CommandGroup(before: .systemServices) {
Button("Check for Updates") { /* open updater */ }
}
CommandGroup(after: .newItem) {
Button("New from Clipboard") { /* create item */ }
}
CommandGroup(replacing: .help) {
Button("User Manual") { /* open docs */ }
}
}@Observable
final class DataModel {
var items: [String] = []
}
struct ContentView: View {
@State private var model = DataModel()
var body: some View {
List(model.items, id: \.self) { item in
Text(item)
}
.focusedSceneValue(model)
}
}
struct ItemCommands: Commands {
@FocusedValue(DataModel.self) private var model: DataModel?
var body: some Commands {
CommandGroup(after: .newItem) {
Button("New Item") {
model?.items.append("Untitled")
}
.disabled(model == nil)
}
}
}Settings scene adds the Settings menu item on macOS automatically.OpenSettingsAction or SettingsLink.Use this when building a macOS Settings window backed by SwiftUI's Settings scene.
App and compile it only for macOS.SettingsView) and drive values with @AppStorage.TabView to group settings sections when you have more than one category.Form inside each tab to keep controls aligned and accessible.OpenSettingsAction or SettingsLink for in-app entry points to the Settings window.@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
#if os(macOS)
Settings {
SettingsView()
}
#endif
}
}@MainActor
struct SettingsView: View {
@AppStorage("showPreviews") private var showPreviews = true
@AppStorage("fontSize") private var fontSize = 12.0
var body: some View {
TabView {
Tab("General", systemImage: "gear") {
Form {
Toggle("Show Previews", isOn: $showPreviews)
Slider(value: $fontSize, in: 9...96) {
Text("Font Size (\(fontSize, specifier: "%.0f") pts)")
}
}
}
Tab("Advanced", systemImage: "star") {
Form {
Toggle("Enable Advanced Mode", isOn: .constant(false))
}
}
}
.scenePadding()
.frame(maxWidth: 420, minHeight: 240)
}
}SettingsView in a NavigationStack unless you truly need deep push navigation.NavigationSplitView with a sidebar list of categories.Form; keep rows focused and accessible.skills
accessorysetupkit
references
activitykit
references
adattributionkit
references
alarmkit
references
app-clips
app-intents
references
app-store-optimization
app-store-review
apple-on-device-ai
appmigrationkit
references
audioaccessorykit
references
authentication
references
avkit
references
background-processing
references
browserenginekit
references
callkit
references
carplay
references
cloudkit
references
contacts-framework
references
core-bluetooth
references
core-data
core-motion
references
core-nfc
references
coreml
references
cryptokit
references
cryptotokenkit
references
debugging-instruments
device-integrity
references
dockkit
references
energykit
references
eventkit
references
financekit
references
focus-engine
gamekit
references
healthkit
references
homekit
references
ios-accessibility
ios-localization
ios-networking
ios-simulator
references
mapkit
metrickit
references
musickit
references
natural-language
references
paperkit
references
passkit
references
pdfkit
references
pencilkit
references
permissionkit
references
photokit
push-notifications
realitykit
references
relevancekit
references
scenekit
references
sensorkit
references
speech-recognition
spritekit
references
storekit
swift-api-design-guidelines
swift-architecture
swift-charts
references
swift-codable
swift-concurrency
swift-formatstyle
swift-language
swift-security
references
swift-testing
swiftdata
swiftlint
swiftui-animation
swiftui-gestures
references
swiftui-layout-components
swiftui-liquid-glass
references
swiftui-patterns
swiftui-performance
swiftui-uikit-interop
swiftui-webkit
tabletopkit
references
tipkit
references
vision-framework
weatherkit
references
widgetkit
references