Pattern · Forms
NewProfile Form SwiftUI Block
An edit-profile section: photo, name, a username that checks itself as it's typed, bio with a count, and Save.
The edit-profile section of an account screen or sheet. The photo changes through the system picker, the username checks availability through your closure and offers suggestions when it's taken, and the bio counts toward its limit. Save wakes only when something changed and everything is valid, shows working, done and failed, and `hasChanges` lets your sheet confirm before discarding edits.
- SwiftUI
- iOS 17.0+
- profile-form
$199 once, every piece included. See how installing works · Sign in
01
What's included
SwiftUI source you own, built on Apple frameworks only.
- Photo with the system picker, Change and Remove
- A username that checks itself, with suggestions when it's taken
- Bio with a count that warns near the limit
- Optional pronouns and website, checked as you leave them
- Save only when changed and valid, plus
hasChangesfor a discard prompt
02
Implementation notes
How it behaves, what it handles, and the decisions behind it.
The photo sits at the top in a circle, with Add photo or Change photo opening the system photo picker and Remove taking it out; a picked photo is scaled to 1024 points and shown at once. One card holds Name, Username and Bio. The username drops its @, keeps to lowercase letters, digits, periods and underscores, and checks itself through your closure a moment after typing stops: a spinner in the field and "Checking availability", then a check in the success colour with "@maya.chen is available", or a cross with "@maya is taken. Try one of these:" and your suggestions as chips that fill the field when tapped. The bio counts toward its limit, turning to the warning colour near it and to the danger colour past it. Pronouns and Website sit in an Optional card; the website checks its shape once you leave it. Save stays dimmed until something has changed and everything is valid, then runs your closure with a spinner, a check, or the thrown error's message under it. hasChanges tells your screen there are unsaved edits so it can ask before it closes. Pass nil while the profile loads and the block draws grey shapes in its own layout.
Notes
ProfileForm(profile:hasChanges:bioLimit:checkUsername:onSave:onSuccess:). OnlyprofileandonSaveare required. It is a section: no navigation stack, background, padding or scroll view of its own, so place it in your own column or sheet.ProfileForm.Profileholdsname,username(without the @),bio,pronouns,websiteand an optionalphotoURL, loaded withAsyncImage; without one the block shows the initials.Profile.sampleis Maya Chen from the preview. A newprofilereplaces the fields only while nothing is edited, so a refresh never wipes what was typed.onSavereceivesProfileForm.Changes: the editedProfile, trimmed, and aPhotoChange(.unchanged,.replaced(Data)with a JPEG at most 1024 points on its longest side, or.removed). Return to succeed, throw to fail; a thrownLocalizedError'serrorDescriptionshows under Save, otherwise "Couldn't save your profile. Try again." ACancellationErrorgoes back to rest quietly. After the check, the saved values become the new baseline andonSuccessis called; close your sheet there.checkUsernamereceives the cleaned name about half a second after typing stops and returns aUsernameStatus:.available,.taken(suggestions:)or.invalid(message)in your words. A thrown error reads "Couldn't check this name right now. You can still save." and lets Save go ahead. Leave it out to check the format only (at least 3 characters, no leading, trailing or doubled periods). A name the person already has is never checked.hasChangesis aBinding<Bool>the block writes. Use it for.interactiveDismissDisabled(hasChanges)and a "Discard your changes?"confirmationDialogon Cancel, as the Example does.bioLimitdefaults to 160 characters.- Fields: Name is
.name, Username.usernamewith the ASCII keyboard, Website.URLwith the URL keyboard. Return moves from Name to Username to Bio and from Pronouns to Website, and Done on the website saves when it can. - Haptics: success when a save lands, an error when it fails or a photo can't be read.
- VoiceOver reads each field by its label, the username's state as its value ("Checking", "Available", "Taken"), the bio count as "89 of 160 characters", announces available, taken, removed and saved, and hints at Save while nothing has changed. Each suggestion reads "Use maya.chen". Every control is at least 44 points tall, and text wraps at accessibility sizes.
- Reduce Motion turns every change into a short fade.
ProfileFormExampleat the end ofProfileForm.swiftis the Edit profile sheet from the preview: Cancel confirms before discarding edits and the sheet can't be swiped away while there are some. Its stand-in server says "maya" and a few other short names are taken and offers three others. It reads the theme from the environment; passwrapsInNavigationStack: falseto push it in your own stack.
Theming
- Reads
SPThemefrom@Environment(\.spTheme)and requires thedesign-systemitem. - Cards are
color.surfacewithradius.lgand 16 point padding; groups sit 24 points apart. The Optional title istype.headline. - Fields are 52 points tall on
fillwithradius.mdand a 1 pointseparatorhairline that turns totextPrimarywhen focused and todangeron an error. Labels aretype.subheadintextSecondary, inputtype.bodyintextPrimary, prompts and the @textTertiary, the counttype.captionwith monospaced digits. - The photo is an 88 point circle on
fillwith aseparatorring; initials use the display face (type.face) at 32 points, capped at semibold, intextSecondary. - Availability uses
successanddangerfor its icon and line; suggestion chips are capsules onfillwith aseparatorring. - Save is the only accent:
accentwithaccentOnintype.headline,radius.md, 52 points, at 40% opacity while there's nothing to save. Change photo istype.subheadsemibold intextPrimary, RemovetextSecondary. - States move with
motion.snappy, loading withmotion.smooth, presses withmotion.standard.
03
Keep building
Pieces that work well next to this one.