Agent skills for iOS, iPadOS, Swift, SwiftUI, and modern Apple framework development.
90
90%
Does it follow best practices?
Impact
—
Average score across 248 eval scenarios
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.
Use existing transferable values as proxies or fallbacks when they describe your data accurately:
| Value | Typical use |
|---|---|
String | Plain-text previews, titles, or body fallbacks |
Data | Binary payloads when you control serialization |
URL | Links and file references |
Color (SwiftUI) | Color transfer; semantic colors resolve against a default environment |
Codable models | Custom JSON representations via CodableRepresentation |
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)On macOS 13+, prefer SwiftUI's .copyable, .cuttable, and command-based .pasteDestination(for:action:validator:) modifiers over direct pasteboard usage when you are wiring Edit menu commands and keyboard shortcuts. Current Apple docs list those command modifiers as iOS/iPadOS/Mac Catalyst 27 beta, not iOS 26 defaults. On iOS 26 targets, use UIPasteboard directly for custom clipboard commands, or use SwiftUI drag/drop and ShareLink for Transferable-driven sharing flows.
Docs: copyable(_:) · cuttable(for:action:) · pasteDestination(for:action:validator:)
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.UTType declarations and transfer code in modules that are visible to every app target or extension that imports, exports, drags, or shares the type.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(edge: .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. It insets the modified view, adjusts the safe area, and extends affected scroll edge effects.
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 */ }
}
}@MainActor @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..tessl-plugin
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
references
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