Pattern · Auth and Onboarding
NewProfile 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
$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:). OnlyonContinueis 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 aScrollViewwith.padding(.horizontal, 20).onContinuereceivesProfileSetup.Profile: the photo as JPEGData(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 thrownLocalizedError'serrorDescription, or "Couldn't save your profile. Try again." ACancellationErrorgoes back to rest quietly.checkUsernamereturns 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.stepandofshow "Step 2 of 3" above the title; nil hides it.titleandmessagereplace the default words.nameprefills the display name, from Sign in with Apple, say.rolesfills the "What do you do?" menu, which also offers "Not now"; an empty list hides the row.asksBirthdayadds a Birthday row that stays empty until Add is tapped and can be cleared again.onSkipshows "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
.namewith words capitalised; Username is.usernamewithout 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.
ProfileSetupExampleat the end ofProfileSetup.swiftis 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
SPThemefrom@Environment(\.spTheme)and requires thedesign-systemitem. - "Step 2 of 3" is
type.subheadintextSecondary; the title is the display face at 30 points, semibold at most, intextPrimary; the line istype.bodyintextSecondary. - Cards are
color.surfacewithradius.lg; the first has 16 point padding and aSPHairlineunder the photo row, and groups sit 24 points apart (space.xl). - The avatar is 76 points with a
separatorring. Its colours aretextPrimarywithbginitials,neutralandsecondarywithstatusOn,fillwithtextPrimary, andaccentwithaccentOn. The picked swatch has a 2 pointtextPrimaryring. - Fields are 52 points tall on
fillwithradius.mdand a 1 pointseparatorhairline that turns totextPrimarywhen focused and todangeron an error. The username's "@" istextTertiary; its note istype.footnoteintextSecondary, and the available check issuccess. - Rows lead with a 24 point symbol column in
textSecondary, labels intype.body. - Continue is the only accent:
accentwithaccentOnintype.headline,radius.md, 52 points. Add a photo and Skip for now aretextPrimarysemibold. Errors aretype.footnoteindanger. - States move with
motion.snappy, the photo withmotion.smooth, the buttons' press withmotion.standard.
03
Keep building
Pieces that work well next to this one.