Block · Commerce
NewCheckout SwiftUI Block
A SwiftUI checkout screen. A one-column checkout: bag with steppers, address and delivery speed, promo code, summary, then Pay or Apple Pay to the placed order.
The checkout section of a cart or checkout screen. Each bag line has a quantity stepper, and the line price, summary and Pay label roll as quantities change; delivery shows the address with Change and each speed with its day and price, and a promo code applies or explains why not. Pay shows its working, done and failed states in place, Apple Pay uses the system button, and the block settles into the placed order with its number and arrival day.
- SwiftUI
- iOS 17.0+
- checkout
$199 once, every piece included. See how installing works · Sign in
01
What's included
SwiftUI source you own, built on Apple frameworks only.
- Bag lines with a picture, quantity stepper and confirmed Remove
- The address with Change, and delivery speeds with their days and prices
- A promo code that applies, or shows why it was refused
- Subtotal, delivery, discount, tax and a total that rolls as you change the bag
- Pay and Apple Pay through to a calm placed-order confirmation
Requires
In-App Purchase capability
02
Implementation notes
How it behaves, what it handles, and the decisions behind it.
The Bag card lists each line with its picture (your image, a remote URL, or an SF Symbol on a neutral square), the name over its variant, and a compact stepper beside the line's price; the price, the item count, the summary and the Pay label roll to their new figures as quantities change, and at one the minus becomes Remove, which asks to confirm. Delivery shows the address with Change, which opens your address picker, then each speed as a selectable row with its day ("Arrives Thu, Oct 23") and price. Payment holds the saved method with Change, a promo field whose Apply checks the code and either adds the discount line or shows why it was refused under the field, and the summary: subtotal, delivery, discount, tax and the total, the block's one large figure. Pay carries the total in the accent and shows a spinner while your closure runs, then a checkmark; a thrown error ("Your card was declined") shows under it and the button is ready to try again. The system Apple Pay button sits below it. Once the order goes through, the block settles into a calm confirmation with the order number, the arrival day, the total paid and View order. Pass nil while the cart loads and the block shows grey shapes in its own layout; an empty bag says so and hides the rest.
Notes
CheckoutBlock(cart:onQuantityChange:onPlaceOrder:onApplyPromo:onApplePay:onEditAddress:onChangePayment:onViewOrder:)takes oneCheckoutBlock.Cart. It is a section: no navigation stack, background, padding or scroll view of its own, so place it in your own column: aScrollViewwith.padding(.horizontal, 20), or a sheet.- Your app owns the cart and every price. The block only adds them up for display: the subtotal from each line's
unitPricetimes its quantity, the chosen speed's price, the discount, tax, and the total. Money isDecimaland formats in the person's locale fromcurrencyCode. Cartholds theitems, an optionaladdress, thedeliveryOptionswith thedeliveryIDchosen at first, an optionalpaymentmethod, an optionaltaxand thecurrencyCode.setQuantity(_:for:)is there for apps that keep the cart as state.Cart.sampleis Field Coffee's bag from the preview.- An
Itemhas anid,name, optionalvariant,unitPrice,quantity,maxQuantity(the stepper's upper bound, 10 by default) and aThumbnail:.image(Image("…")),.url(URL)loaded withAsyncImage, or.symbol("mug"). Lines at quantity zero are left out. DeliveryOptionhas anid,title, the day itarrives(shown as "Arrives Thu, Oct 23") or your ownestimatewording, and aprice; zero reads Free. The block keeps the speed chosen in it and hands it to you in theOrder.Addressis aname,street, optionalline2,city,region,postalCodeand optionalcountry.PaymentMethodis atitle("Visa •••• 4242"), an optionaldetail("Expires 08/28") and an SF Symbol; the block never draws a network logo.Taxis.rate(0.08625), a share of the subtotal after any discount rounded to the currency's minor unit, or.amount(…)that you work out.onQuantityChangegets a line's id and its new quantity. Update your cart and pass it back. Zero arrives after the person confirmed Remove.onPlaceOrderisasync throwsand gets anOrder: the lines, address, speed, payment method, discount and every amount exactly as shown. Return aConfirmation(number, optionalarrivesand amessageline such as where the receipt went) and the block shows the placed order. Throwing shows the error'serrorDescription, or "Your payment didn't go through. Try again.", under Pay; throwCancellationError()to go back to rest quietly.onApplyPromoisasync throws: check the code and return aDiscountwith itscode, alabel("15% off") and aValue(.amount,.percent(0.15)or.freeDelivery). A thrown error's description shows under the field. Nil hides the field. An applied code shows on the field's row with Remove.onApplePayshows the systemPayWithApplePayButtonand isasync throwslikeonPlaceOrder. Present the Apple Pay sheet withorder.paymentRequest(merchantIdentifier:merchantName:countryCode:), which fills the summary items for you, then place the order and return its confirmation. Nil hides the button.onEditAddressopens your address picker; pass the chosen address back incart. With no address yet the row reads "No delivery address" with Add.onChangePaymentdoes the same for the payment method. Nil hides Change and Add.onViewOrdergets theConfirmation; nil hides View order.- Pay stays disabled with a line under it until there is an address and a payment method. While a payment runs, the bag, delivery and promo code hold still.
- Haptics: a light tap on Pay, a selection tick for quantities and speeds, success when the order is placed or a code applies, and an error when a payment or code fails.
- VoiceOver reads each line as its name and variant with "2 for $48.00", the stepper as an adjustable Quantity with a Remove action, each speed as a button with its day and price that is marked selected, the summary lines as label and amount, and the total as one element. Codes applied, failures and "Order placed" are announced.
- Dynamic Type: at accessibility sizes the pictures step aside so names keep the width, Change moves under the address and the payment method, prices move under each speed's name, the summary amounts and the total stack under their labels, and the stepper grows with the text. Every row keeps its shape down to 320 points.
- Reduce Motion turns every change into a short fade, and the placeholder holds still instead of breathing.
CheckoutBlockExampleat the end ofCheckoutBlock.swiftis the Checkout screen in the preview: Field Coffee's bag, a saved address with Standard and Express, SPRING15 for 15% off (any other code is refused), Change swapping to a second saved address or card, and Pay settling on the placed order. 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.lg, 16 point padding andSPHairlinedividers inseparator, inset to the text; groups sit 24 points apart (space.xl) undertype.headlinetitles. - The total is the one display moment:
type.numeral(34)intextPrimary, withtype.tracking(for:). Rows usetype.body, secondary lines and the summarytype.subheadwith monospaced digits, notestype.footnote; "Order placed" uses the display face at 28 points, capped at semibold, falling back to the text design rather than a monospaced one. - Text is
textPrimaryandtextSecondary; row symbols and picture placeholders aretextSecondaryonfill; empty radio rings and field promptstextTertiary. - The stepper and the promo field sit on
fillwithradius.md; the field adds a 1 pointseparatorstroke that turnstextPrimarywhen focused anddangerwith a line under it on an error. - Pay is the
accentwithaccentOntext intype.headlineandradius.md. The chosen speed's check is the only other use of the accent. The Apple Pay button is the system's own, black on light grounds and white on dark ones as Apple asks, at the same 52 point height and corner. - The placed check is
success; failures aredanger. - Changes move with
motion.smooth, selections and presses withmotion.snappyandmotion.standard. Every animation becomes a short fade under Reduce Motion.
Requirements
- For Apple Pay: the Apple Pay capability with a merchant ID. Not needed when you leave
onApplePaynil.
03
Keep building
Pieces that work well next to this one.