Pattern · Auth and Onboarding

New

Profile Setup SwiftUI Block

The first-run profile step: a photo or initials colour, a display name, a username checked live, an optional role, and Continue or Skip.

The profile step right after sign up, with where the person is in your first run, a large title and minimal fields. The avatar takes a photo from the system picker or shows initials on one of five theme colours, the username is cleaned as it's typed and checked for availability through your closure, and an optional role and birthday follow. Continue runs your closure through working, done and failed, and Skip for now leaves without saving.

  • SwiftUI
  • iOS 17.0+
  • profile-setup
Get SwiftPieces Pro

$199 once, every piece included. See how installing works · Sign in

01

What's included

SwiftUI source you own, built on Apple frameworks only.

  • Step 2 of 3, a large title and only the fields a first run needs
  • A photo from the system picker, or initials on one of five theme colours
  • A username cleaned as it's typed and checked live: Checking, yours, or taken
  • An optional role menu and birthday
  • Continue shows working, a check, or your error's message, with Skip for now

02

Implementation notes

How it behaves, what it handles, and the decisions behind it.

The step opens with where the person is ("Step 2 of 3", when you pass it), a large title and a line on why. One card holds the avatar, Add a photo through the system photo picker and five colours for the initials, then Display name and Username. The avatar shows a person symbol until a name is typed, then its initials on the picked colour, or the photo once one is chosen, with Change photo and Remove beside it. The username keeps itself to lowercase letters, numbers, periods and underscores; once it's three characters long and the typing pauses, the block asks your closure whether it's free, saying "Checking…" with a spinner in the field, then "@mayachen is yours" with a check, or that it's taken in the danger colour. A second card asks "What do you do?" from your roles and, when you ask for it, an optional birthday. Continue checks the name and username, runs your closure and shows working, done and failed in place; Skip for now leaves without saving.

Notes

  • ProfileSetup(step:of:title:message:name:roles:asksBirthday:checkUsername:onContinue:onSkip:). Only onContinue is required. It is a first-run section, not a profile editor: no navigation stack, background, padding or scroll view of its own, so place it in your onboarding's column, such as a ScrollView with .padding(.horizontal, 20).
  • onContinue receives ProfileSetup.Profile: the photo as JPEG Data (at most 1024 pixels on its long side, or nil), the initials colour's index (0 to 4, in the order ink, grey, muted, light, accent), the trimmed name, the username, and the role and birthday when given. Throw to fail: the block shows a thrown LocalizedError's errorDescription, or "Couldn't save your profile. Try again." A CancellationError goes back to rest quietly.
  • checkUsername returns whether a username is free. It runs 0.45 seconds after the typing stops, and a newer username cancels the check before it. If it throws, the field says the check failed and Continue still works, so your server has the last word. Nil skips the live check.
  • step and of show "Step 2 of 3" above the title; nil hides it. title and message replace the default words. name prefills the display name, from Sign in with Apple, say.
  • roles fills the "What do you do?" menu, which also offers "Not now"; an empty list hides the row. asksBirthday adds a Birthday row that stays empty until Add is tapped and can be cleared again.
  • onSkip shows "Skip for now" under Continue. Nil hides it.
  • Photos come from PhotosPicker, which runs out of process, so no photo library permission is asked for. A photo that can't be read says so under the button.
  • Fields: Display name is .name with words capitalised; Username is .username without autocorrection. Return moves from name to username and continues from there.
  • Haptics: success when the profile saves, an error when it fails, a warning when Continue is refused, and a selection tick for each colour.
  • VoiceOver reads the header as one element, each field by its label with its error or availability as a hint, each colour by name with Selected, and the role and birthday rows by their labels with their values. It announces a taken username, refusals, failures and "Profile saved". Every control is at least 44 points tall. At accessibility sizes the avatar stacks above its buttons and every line wraps.
  • Reduce Motion turns every change into a short fade.
  • ProfileSetupExample at the end of ProfileSetup.swift is Fieldnote's second first-run step from the preview. Its stand-in server says "maya", "admin" and "fieldnote" are taken and saves after a second.

Theming

  • Reads SPTheme from @Environment(\.spTheme) and requires the design-system item.
  • "Step 2 of 3" is type.subhead in textSecondary; the title is the display face at 30 points, semibold at most, in textPrimary; the line is type.body in textSecondary.
  • Cards are color.surface with radius.lg; the first has 16 point padding and a SPHairline under the photo row, and groups sit 24 points apart (space.xl).
  • The avatar is 76 points with a separator ring. Its colours are textPrimary with bg initials, neutral and secondary with statusOn, fill with textPrimary, and accent with accentOn. The picked swatch has a 2 point textPrimary ring.
  • Fields are 52 points tall on fill with radius.md and a 1 point separator hairline that turns to textPrimary when focused and to danger on an error. The username's "@" is textTertiary; its note is type.footnote in textSecondary, and the available check is success.
  • Rows lead with a 24 point symbol column in textSecondary, labels in type.body.
  • Continue is the only accent: accent with accentOn in type.headline, radius.md, 52 points. Add a photo and Skip for now are textPrimary semibold. Errors are type.footnote in danger.
  • States move with motion.snappy, the photo with motion.smooth, the buttons' press with motion.standard.