Block · Account

New

Invite Friends SwiftUI Block

A referral section: what both people get, a track that fills as friends join, the invite code to copy or share, and who you invited.

The referral section of an account or rewards screen. One card states the reward, fills a seat on a quiet track for each friend who joins, copies the invite code with one tap and opens the system share sheet. Invited friends list below with a status chip, and pending invites can be reminded.

  • SwiftUI
  • iOS 17.0+
  • invite-friends
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.

  • What both people get, with a track that fills a seat as each friend joins
  • The invite code copies with one tap and answers Copied
  • Share invite opens the system share sheet with your link, message and preview
  • Invited friends with Invited, Joined and Rewarded chips, and Remind on pending invites
  • Loading, empty and all-rewards-earned states, driven by one Referral value

02

Implementation notes

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

One surface card states what both people get ("Give a month, get a month") with a line on how it works, then a quiet track with a seat for each friend the reward needs under "2 of 5 joined" and "3 to go". When a friend joins, the count rolls and the next seat fills from its leading edge; when every seat is full, the count line drops "to go" and a calm line with a success checkmark says the rewards are earned. Under it, the invite code sits in a field-styled row in a monospaced face: tap anywhere on it and the code goes to the pasteboard while Copy answers "Copied" with a checkmark and a success haptic, then settles back. Share invite, the one accent action, opens the system share sheet with your link, your message and a preview. Below the card, everyone you invited lists newest first: initials on a quiet fill (an envelope or phone for an invite that went to an address), the name over a tinted status chip (Invited, Joined, Rewarded) and when, and Remind at the trailing edge of a pending invite. Remind shows a spinner while your closure runs, then reads "Reminded"; a thrown error puts its message under the row in the danger colour and Remind comes back to try again. With no invites yet the list says "No invites yet. Share your link to get started." Past five friends, a Show all row opens the rest. Pass nil while the referral loads and the block shows grey shapes in its own layout.

Notes

  • InviteFriendsBlock(referral:sharePreviewIcon:onRemind:onCopy:onShare:) takes one InviteFriendsBlock.Referral. It is a section: no navigation stack, background, padding or scroll view of its own, so place it in your own column: a ScrollView with .padding(.horizontal, 20), or a sheet.
  • Referral holds the headline, a detail line, the goal (one seat per friend the reward needs), an optional completeNote for the full track (defaults to "You've earned every reward. Thanks for spreading the word."), the invite code, the shareURL, the shareMessage and friends. joinedCount and isComplete are there for your own logic. Referral.sample is Maya's Stillwater referral from the preview.
  • A Referral.Friend has an id, a name (their name, or the email or masked phone number the invite went to), a status, the date they reached it and reminded. Status is .invited (pending, offers Remind), .joined (counts toward the goal, reward not yet paid) or .rewarded (counts, reward paid). joined and rewarded fill seats; friends past the goal read as "7 joined" on a full track.
  • Pass a new Referral whenever your backend reports a change and the block animates the difference: the chip moving on, the count rolling, a seat filling. VoiceOver announces a friend joining while the block is on screen, never on the first load.
  • onRemind is async throws and runs for one Friend. Nil hides Remind. Throwing shows the error's errorDescription (or "Couldn't send a reminder. Try again.") under that row. Set reminded on the friend from your backend so the row keeps reading "Reminded" after a relaunch.
  • Copying uses UIPasteboard and sharing uses ShareLink with SharePreview (the headline, and sharePreviewIcon such as your app icon when you pass one), so neither needs anything from you. onCopy and onShare are optional and exist for analytics.
  • Haptics mark a copied code and a sent reminder with success, and a failed reminder with an error.
  • VoiceOver reads the headline as a header, the progress as one element ("Friends joined, 2 of 5. 3 to go."), the code row as a button that spells the code out with a Copies the code hint, each friend as their name, status and when, and Remind as its own button. Copying and sent reminders are announced.
  • Dynamic Type: the count and "to go", and the code and Copy, stack when they no longer fit side by side, and at accessibility sizes each friend's chip, date and Remind drop under the name. Every row keeps one shape at any width, down to 320 points; long names wrap rather than truncate. Initials stay inside their circle.
  • Reduce Motion fades seats in instead of sweeping them, drops the press scale and turns every change into a short fade. The ghost layout holds still instead of breathing.
  • InviteFriendsBlockExample at the end of InviteFriendsBlock.swift is the Rewards screen in the preview: five invites, two joined, one already reminded, and Priya joining a moment after the screen opens. It reads the theme from the environment; pass wrapsInNavigationStack: false to push it in your own stack.

Theming

  • Reads SPTheme from @Environment(\.spTheme) and requires the design-system item.
  • The card and the list are color.surface with radius.lg and 16 point padding; rows are at least 64 points tall with SPHairline dividers in separator, inset to the text; the two groups sit 24 points apart (space.xl).
  • The headline is the one display moment: the display face (type.face) at 28 points in textPrimary, capped at semibold. When the face isn't bundled it falls back to SF Pro in the family's figure design, except that a monospaced figure design (Press, Neon) falls back to the text design, so a sentence never sets in mono. The detail, "to go" and Remind's quiet states use type.subhead; the count type.subhead semibold with monospaced digits; names type.body; dates and the complete line type.footnote; chips type.caption semibold; the list title type.headline.
  • The track's seats are textPrimary on fill. The code row is a field: fill, radius.md, a 1 point separator stroke, the code in type.body monospaced.
  • Status chips are success (Joined) and info (Rewarded) on a 14% tint of themselves, and textSecondary on fill (Invited). Avatars are fill with textSecondary initials. Failures are danger; the complete checkmark is success.
  • Share invite is the accent with accentOn text in type.headline and radius.md, the only accent on the block.
  • Changes move with motion.smooth, presses and the Copied swap with motion.snappy. Every animation becomes a short fade under Reduce Motion.