Migrate from iFrame to the iOS Checkout SDK

Replace iOS webview-based Spreedly iFrame payment collection with native SPLTextField fields or CardFormDropIn.

Replace an iOS webview-based Spreedly iFrame checkout with native SPLTextField fields or the CardFormDropIn pre-built card form while preserving your backend transaction flow.

Use this guide when your iOS app currently loads Spreedly iFrame UI or a web checkout inside a webview. Match outcomes such as tokenization, validation, field-state handling, and PCI boundaries instead of trying to recreate identical JavaScript method names.

Match outcomes (tokenize, validation, PCI boundaries), not identical JavaScript method names.

Start with Upgrading your checkout experience if you need the cross-platform migration plan or the detailed Web iFrame to Web Checkout SDK migration path.

Further reading

Who this guide is for

You used…This guide covers…
iFrame v1 hosted fields (iframe-v1.min.js, separate number + CVV iframes)Yes — full mapping below
SpreedlyExpress checkout button (SpreedlyExpress.onPaymentMethod)No — use Express Checkout for the native drop-in; this guide is for per-field iframe parity
ACH, full 3DS, offsite, APMBrief pointers only — see dedicated guides linked from getting-started.md

Official legacy references: iFrame UI, iFrame events.


Start here

Five steps to replace iframe hosted fields on iOS:

  1. App launchSpreedly.initializeSDK() once. See Basic setup.
  2. Before checkout — your server returns signed credentials; call Spreedly.setup(config:) (Swift) or setupWithConfig(_:) (Objective-C).
  3. Choose UICardFormDropIn (fastest) or headless SPLTextField (iframe-style per-field callbacks). See Choose your path.
  4. TokenizeSpreedly.shared().createCreditCard(...) and listen on subscribeToPaymentResults (Swift) or paymentDelegate (ObjC). There is no createPaymentMethod on iOS.
  5. Leave checkoutSpreedly.shared().reset() (alias resetPaymentState()). There is no destroy() — the SDK singleton stays alive. See SDK lifecycle.

Gate checkout on Spreedly.isInitialized. Setup and lifecycle details: getting-started.md.

Legacy iframeiOS equivalent
readySpreedly.isInitialized after setup(config:) succeeds
destroy / remove iframePlatform — no destroy(); use reset() + remove native fields; re-setup(config:) when you need fresh auth

Quick legacy → iOS cheat sheet

Legacy (iframe v1)iOS SDK
Spreedly.init(...)SpreedlyConfig + Spreedly.setup(config:)
Spreedly.on('ready')Spreedly.isInitialized after setup(config:) — see Basic setup
fieldEvent / inputPropertiesonFieldStateChange { HostedFieldState } (headless only)
Spreedly.validate()Spreedly.areAllFieldsValid(fieldTypes:)
Spreedly.tokenizeCreditCard()Spreedly.shared().createCreditCard(...) + subscribeToPaymentResults
paymentMethod eventPaymentResult success on payment stream
Spreedly.setNumberFormat(...)Spreedly.shared().setNumberFormat(_:) or setNumberFormat(type:)
Spreedly.toggleMask()Spreedly.shared().toggleMask()
Spreedly.setRecache / recacherecachePaymentMethod + subscribeToRecacheResults
Spreedly.reload()resetPaymentState() + new signed auth → setup(config:)
Express sheet survives rotationAutomatic in CardFormDropIn / CardFormDropInViewController — no merchant API
Clear fields but keep mask/formatresetPaymentFormPreservingDisplayConfig()

Choose your path: headless vs express

CapabilityHeadless SPLTextField / SPLTextFieldViewControllerExpress CardFormDropIn
Fastest path to tokenizeMore wiringYes — built-in form + submit
onFieldStateChange / HostedFieldStateYesNo
HostedFieldState.iin (BIN prefix while typing)YesHostedFieldState.iin on card-number eventsNo — use headless or backend BIN
Custom trailingIcon / brand artworkYes (.cardNumber only)Built-in label only
onFocusChanged / onInputLengthYesNo
setNumberFormat / toggleMask from merchant UIYesYes — call from UI outside the sheet
In-field mask eyeNo — merchant UI + toggleMask()Same

Rule of thumb: iframe merchants who relied on fieldEvent per keystroke → headless. Merchants who only need a working card form → express.

Deep dives: Custom Payment Forms (headless), Express Checkout (drop-in).


Not the same as iframe

Read this before coding — intentional differences from iframe v1 (by design, not missing card features).

Status (in the Status column of mapping tables below):

  • Parity — Supported with an iOS-first API or pattern.
  • Platform — Same outcome via iOS/UIKit idioms (not a JS-shaped API).
  • Gap — No equivalent today; read Notes for what to do instead.
  • N/A — Web-only or not meaningful on iOS.

iframe inputProperties flags → iOS (what is similar)

Legacy iframe sends one JSON blob per fieldEvent with separate booleans. iOS still validates the same rules — it exposes a different shape:

Legacy iframe propertySimilar on iOS?Use on iOS instead
inputProperties.validNumberYes (combined)HostedFieldState.isValid on the card-number field (plus numberLength, cardScheme)
inputProperties.validCvvYes (combined)HostedFieldState.isValid on the CVC field (plus cvvLength)
inputProperties.luhnValidYes (combined)HostedFieldState.isValid — Luhn and scheme-specific rules run inside validation; there is no separate luhnValid property
inputProperties.cardTypeYesHostedFieldState.cardScheme
inputProperties.numberLength / cvvLengthYesHostedFieldState.numberLength / cvvLength (digit counts only)
inputProperties.iinYesHostedFieldState.iin (headless card number only)
setValue('number' | 'cvv', …)No (intentional)User typing + Wallet/autofill only — no programmatic PAN/CVV API

You cannot mirror iframe logic that branches on luhnValid separately from validNumber (e.g. show “checksum failed” while length rules still pass). Use isValid and your own copy, or onValidationChange.

All Gap and N/A iframe APIs are listed in the 1:1 mapping tables below — not only in this summary.

Legacy iframeiOS SDK todayStatus
Single inputProperties on every eventHostedFieldState + areAllFieldsValid(fieldTypes:)Platform
fieldEvent on express onlyCardFormDropIn has no onFieldStateChange — use headlessPlatform

Use onFieldStateChange for scheme and digit counts — never log raw PAN/CVV or parse onChange ciphertext (opaque on .cardNumber and .cvc).


Legacy iFrame (JavaScript) 1:1 mapping

If you integrated Spreedly iFrame v1 (iframe-v1.min.js) in a WebView or mobile browser, use these tables to find the Checkout iOS pattern. Official legacy references: iFrame UI, iFrame events.

Step-by-step by topic

Initialization and configuration

If you previously used…Use this in iOS SDK…NotesStatus
Spreedly.init(environmentKey, { nonce, signature, certificateToken, timestamp, numberEl, cvvEl, … })SpreedlyConfig + Spreedly.setup(config:) (Swift) or setupWithConfig(_:) (ObjC)Basic setup. Add SPLTextField or CardFormDropIn — no DOM element ids.Platform
Spreedly.on('ready', fn)After setup(config:) succeedsUse Spreedly.isInitialized or your own flag — no global ready callback.Platform
Spreedly.setLabel / setPlaceholdertitle: / placeholder: on SPLTextField; DropInCoreFieldLabels on expressSee Theme and Styling.Parity
Spreedly.setStyle(field, css)SpreedlyTheme / SpreedlyThemeManagerMap CSS to theme tokens.Platform
Spreedly.setNumberFormat('prettyFormat' | 'plainFormat' | 'maskedFormat')Spreedly.shared().setNumberFormat(_:) (.pretty / .plain / .masked); ObjC: setNumberFormatWithCardNumberFormatRawValue: or setNumberFormatWithType:; Swift: setNumberFormat(type:)See iframe prettyFormat vs iOS .pretty. toggleMask = plain ↔ masked (not Pretty).Parity
Spreedly.toggleMask()Spreedly.shared().toggleMask() on main threadCoupled PAN + CVV; merchant UI outside fields.Parity
Spreedly.transferFocus(field)shouldFocus: true on SwiftUI SPLTextField or SPLTextFieldViewController (UIKit / ObjC)becomeFirstResponder() still works on the view controller.Parity
Spreedly.toggleAutoComplete()enableAutofill: false on headless fields or CardFormDropInDisplayConfig.enableAutofillLegacy autofill off.Parity
Spreedly.setParam(name, value)Spreedly.shared().setParam(parameter: .allowBlankName | .allowExpiredDate | .allowBlankDate | .allowInternationalZipCodes, value:)Typed ValidationParam.Parity

Validation and field state (headless)

If you previously used…Use this in iOS SDK…NotesStatus
Spreedly.validate() + validation event (inputProperties)Spreedly.areAllFieldsValid(fieldTypes:); onValidationChange; createCreditCardPaymentProcessingResult.validationFailedPer-field booleans + aggregate; no single inputProperties object. Use HostedFieldState while typing.Parity
fieldEvent with input + inputProperties.cardType / validNumber / validCvvonFieldStateChange { HostedFieldState } (plus onChange / onValidationChange if you split concerns)HostedFieldState bundles scheme, digit lengths, iin, isValid, focus, mask — no separate validNumber / validCvv / luhnValid bits. Brand icons are still merchant assets.Platform
inputProperties.iin (6–8 digits while typing)HostedFieldState.iin on card-number snapshotsMerchant-safe prefix only (not full PAN); nil when fewer than six digits. Omitted from HostedFieldState debug description.Parity
inputProperties.numberLength / cvvLengthHostedFieldState.numberLength / cvvLength (digit counts)Do not infer lengths from onChange ciphertext on PAN/CVC.Platform
fieldEvent focus / blurHostedFieldEventType.focus / .blur, or onFocusChangedSame semantics as iframe focus/blur.Platform
fieldEvent mouseover / mouseoutDesktop-only hover; not emitted on iOS.N/A
inputProperties.luhnValidHostedFieldState.isValidSimilar on iOS: Luhn + length + scheme rules run inside isValid. Not exposed: separate luhnValid boolean.Platform
setValue('number', …) / setValue('cvv', …)Not exposedSensitive fields accept user input and autofill only — no programmatic setValue (PCI).Gap

Tokenization, reset, and listeners

If you previously used…Use this in iOS SDK…NotesStatus
Spreedly.tokenizeCreditCard()Spreedly.shared().createCreditCard(...) / createCreditCardObjC(...) + subscribeToPaymentResults or paymentDelegateAsync PaymentResult.Parity
eligible_for_card_updatereligibleForCardUpdater on createCreditCard / createCreditCardObjCWithAdditionalFields:metadata:eligibleForCardUpdater:Typed param wins over conflicting metadata.Parity
Spreedly.reload()resetPaymentState() + new signed auth → Spreedly.setup(config:)Unlike iframe reload() (DOM remount), iOS keeps one Spreedly instance — setup rotates signing; resetPaymentState alone does not. Successful createCreditCard calls resetPaymentFormPreservingDisplayConfig() (fields only, mask/format kept). Express rotation: automatic in CardFormDropIn.Platform
(no iframe equivalent)resetPaymentFormPreservingDisplayConfig()Clears field values and validation; keeps setNumberFormat / toggleMask. When it runs.Platform
Express rotation while sheet open(automatic)CardFormDropIn keeps typed fields on rotation — no merchant API. Does not restore PAN after dismiss. Sheet lifecycle.Platform
Spreedly.removeHandlers() / offCancel Combine AnyCancellable subscriptionsNo global event bus.Platform
destroyreset() + remove views; re-setup(config:) for new authNo public destroy().Platform

Recache, errors, 3DS

If you previously used…Use this in iOS SDK…NotesStatus
recacheReadyAdd SpreedlyCVVRecachingView / CVVRecachingView (or CVVRecachingViewController) + setup before recachePaymentMethodThere is no recacheReady event name — readiness is structural (submit disabled until CVV valid). See Recaching → Web iframe parity.Platform
recache successrecachePaymentMethod + subscribeToRecacheResults / recacheDelegateNot on payment stream.Parity
errorsPaymentResult failure + APIErrorHandlerError handling.Parity
paymentMethodPaymentResult successToken + payload.Parity
3ds:statussubscribeToThreeDSChallengeResults + gateway APIs3DS Global, 3DS Gateway-Specific.Platform
fraud:tokenBraintree device data (Braintree APM); gateway fraud via 3DS/APM modulesDifferent fraud integrations; not a drop-in fraud:token. No Stripe Radar module on iOS.Gap
consoleError(no merchant hook)Use Spreedly support + your own crash reporting for app code.N/A

Mask and display (iframe toggleMask / setNumberFormat)

The iframe has no eye icon inside number or CVV iframes. Mask/reveal is merchant-owned UI calling Spreedly.shared().setNumberFormat(_:) or toggleMask() on the main thread.

iframe prettyFormat vs iOS .pretty

  • Match: grouped spaced digits with the full PAN visible on focus and blur (iframe prettyFormat).
  • Use .plain when you want all digits visible without spacing (iframe plainFormat).
  • Use .masked or toggleMask() for * on every digit (iframe maskedFormat / toggleMask).
  • setNumberFormat(.pretty) from masked unmasks the PAN only; CVV may stay masked until you set plain/masked or call toggleMask().
  • toggleMask() does not select Pretty — call setNumberFormat(.pretty) for grouped spacing.

Full table: Display formats.

SPLTextField(type: .cardNumber, title: "Card number", isRequired: true)
SPLTextField(type: .cvc, title: "CVC", isRequired: true)

Spreedly.shared().setNumberFormat(.pretty)   // iframe prettyFormat — grouped, all digits visible
Spreedly.shared().setNumberFormat(.plain)    // iframe plainFormat
Spreedly.shared().setNumberFormat(.masked)   // iframe maskedFormat — `*` on PAN + CVV

Button("toggleMask()") { Spreedly.shared().toggleMask() }  // plain ↔ masked (not Pretty)

Objective-C:

[[Spreedly shared] setNumberFormatWithCardNumberFormatRawValue:2]; // 0=pretty, 1=plain, 2=masked
[[Spreedly shared] setNumberFormatWithType:@"maskedFormat"];
[[Spreedly shared] toggleMask];

Read Spreedly.shared().hostedCardDisplayState for UI labels only — do not assign mask flags directly. Full reference: PAN/CVV display.

PAN + CVV share one display state: Spreedly.shared().setNumberFormat / toggleMask apply to both headless .cardNumber and .cvc fields. Unlike separate iframe embeds with per-field state.

SPLTextField.forceMaskOnLifecycleStop (default true) — masks revealed PAN when the app backgrounds; does not replace toggleMask(). See CHANGELOG.

Express: seed layout with CardFormDropInDisplayConfig(cardNumberFormat:); mask APIs still apply from merchant UI outside the sheet.


Field-state observability (headless)

Legacy fieldEvent / inputProperties let merchants drive BIN UX, pay-button state, and validation while typing. iOS today (headless):

  • Use SPLTextField.onFieldStateChange with HostedFieldState — preferred iframe-style channel: INPUT, FOCUS, BLUR, VALIDATION, PAN_MASK_CHANGED; includes cardScheme, numberLength / cvvLength, iin, isValid, isEmpty, isFocused, isPanMasked.
  • HostedFieldState.isValid replaces iframe validNumber, validCvv, and luhnValid (validation still runs; booleans are combined).
  • The SDK does not ship brand image assets — use trailingIcon (SwiftUI) or trailingIconViewFactory (UIKit) on .cardNumber only.
SignalMerchant-visible?Notes
onFieldStateChange(HostedFieldState)?YesPreferred — same role as iframe fieldEvent + inputProperties
onChangeYes (ciphertext on PAN/CVC)Do not log or parse for display; use onFieldStateChange for lengths
onValidationChange(Bool)?YesPer-field validity; also on HostedFieldState.isValid
Spreedly.areAllFieldsValid(fieldTypes:)YesPre-submit aggregate (main thread)
onInputLengthYesDigit count for card/CVC before secure storage (counts only)
PaymentProcessingResult.validationFailedYesUse getInvalidFieldTypes() after failed submit
Hosted-field interaction telemetryInternalNot a supported merchant API

Iframe event names → Swift cases: INPUT.input, FOCUS.focus, BLUR.blur, VALIDATION.validation, PAN_MASK_CHANGED.panMaskChanged. The tables in this guide use iframe-style names for parity; Swift code compares against the HostedFieldEventType enum cases. Objective-C reads the enum's raw value or HostedFieldEventType... constants.

Card brand while typing: HostedFieldState.cardScheme + optional trailingIcon: (SwiftUI) or trailingIconViewFactory (UIKit) on headless PAN field only. SDK does not ship brand image assets.

SwiftUI vs UIKit / Objective-C:

SwiftUIUIKit / Objective-C
onFieldStateChangeonFieldStateChange or hostedFieldStateListener
onChangefieldTextChangeListener
trailingIcontrailingIconViewFactory
shouldFocusshouldFocus on view controller (or becomeFirstResponder())

Code samples (headless)

@State private var cardBrand: CardType?

SPLTextField(
    type: .cardNumber,
    title: "Card number",
    isRequired: true,
    onFieldStateChange: { state in
        if state.fieldType == .cardNumber {
            cardBrand = state.cardScheme
        }
    },
    trailingIcon: { scheme in
        Image(systemName: iconName(for: scheme))
    }
)
// Pre-submit gate — mirrors iframe validNumber / validCvv / luhnValid → use isValid (one flag per field)
var cardValid = false
SPLTextField(type: .cardNumber, title: "Card number", isRequired: true,
    onFieldStateChange: { state in
        if state.eventType == .validation || state.eventType == .input {
            cardValid = state.isValid
        }
    })
// guard cardValid && Spreedly.areAllFieldsValid(fieldTypes: [.cardNumber, .expirationMonth, .expirationYear, .cvc])

Objective-C:

fieldVC.onFieldStateChange = ^(HostedFieldState *state) { /* ... */ };
// Or: fieldVC.hostedFieldStateListener = self;  // HostedFieldStateListener

UIKit wrapper:

let fieldVC = SPLTextFieldViewController(field: .cardNumber, title: "Card number", isRequired: true, placeholder: nil, onValidationChange: nil)
fieldVC.onFieldStateChange = { state in /* HostedFieldState */ }

See custom-payment-forms.md and express-checkout.md.


Hosted field observability (summary)

Legacy iframeiOS equivalent
fieldEvent + inputProperties.cardType / validNumber / validCvvonFieldStateChange { HostedFieldState } — use isValid, cardScheme, numberLength / cvvLength (no separate validNumber / validCvv / luhnValid flags)
inputProperties.numberLength / cvvLengthHostedFieldState.numberLength / cvvLength
fieldEvent focus / blurHostedFieldEventType.focus / .blur, or onFocusChanged
toggleMaskSpreedly.shared().toggleMask() — plain ↔ masked (PAN+CVV)
Live iinHostedFieldState.iin on headless card-number events

IIN while typing (headless)

Iframe inputProperties.iin maps to HostedFieldState.iin on SPLTextField / SPLTextFieldViewController card-number callbacks only:

DetailiOS behavior
When setonFieldStateChange while the customer types
Lengthnil until 6 digits; 8 for Visa/Mastercard when enough digits are present
Safe to use forBIN icons, routing rules, digit-count UX — not the full PAN
Express drop-inNo onFieldStateChange — use headless fields or your backend BIN API
SPLTextField(type: .cardNumber, title: "Card number", isRequired: true,
    onFieldStateChange: { state in
        guard state.fieldType == .cardNumber, let prefix = state.iin else { return }
        // Drive BIN UI from prefix only — do not log with other payment data
    })

API lookup (reference)

Detailed inventory and extended mapping tables. For onboarding, start with Start here.

Public API inventory

Single checklist of merchant-facing APIs verified against the shipped SDK surface.

SDK-level

  • Spreedly.isInitializedtrue after successful signed setup
  • Spreedly.setup(config:) / setupWithConfig(_:) — SDK configuration (Basic setup)
  • Spreedly.areAllFieldsValid(fieldTypes:) — aggregate validation before tokenize
  • SpreedlyUIManager.shared.areAllFieldsValid() — all registered fields
  • getRegisteredFieldCount() / getInvalidFieldTypes() on SpreedlyUIManager.shared — pay-button gating (ObjC: getInvalidFieldTypes returns NSNumber raw values)
  • EmailValidator.isValid(_:) — optional email gate
  • Spreedly.shared().hostedCardDisplayState, setNumberFormat(_:), setNumberFormat(type:), toggleMask()
  • SpreedlyUIManager.shared.setHostedCardDisplayState(_:) / resetHostedCardDisplayState()
  • SpreedlyUIManager.shared.applySeparatedExpirationAutofill(month:year:excluding:)
  • Spreedly.shared().setParam(parameter:value:)ValidationParam
  • Objective-C: hostedCardDisplayCardNumberFormatRawValue on [Spreedly shared]

Headless SPLTextField only

  • Callbacks: onFieldStateChange, onFocusChanged, onFocus, onInputLength, onValidationChange, onChange
  • HostedFieldState: fieldType, eventType, isFocused, isValid, isEmpty, cardScheme, numberLength, cvvLength, isPanMasked, iin
  • forceMaskOnLifecycleStop, enableAutofill, trailingIcon / trailingIconViewFactory
  • HostedFieldStateListener / hostedFieldStateListener

CardFormDropIn only

  • DropInCoreFieldLabels, CardFormDropInDisplayConfig
  • onProcessingResult — immediate validation only; token on subscribeToPaymentResults
  • Not available: onFieldStateChange, per-field focus callbacks, custom PAN trailingIcon

Safe field-state observability (extended)

SignalMerchant-visible?Notes
onFieldStateChange(HostedFieldState)?YesPreferred iframe-style channel
onChangeYes (ciphertext on PAN/CVC)Do not log or display
onValidationChange(Bool)?YesAlso in HostedFieldState.isValid
PaymentProcessingResult.validationFailedYesUse getInvalidFieldTypes() after failed submit
onFieldStateChange on CardFormDropInNoUse headless

Extended mapping tables

Initialization and configuration (full)

If you previously used…Use this in iOS SDK…NotesStatus
Spreedly.setFieldType('number'|'cvv', …)Default keyboards from FormFieldType; optional keyboardType on SPLTextFieldPlatform
Spreedly.setTitle(field, text)Native accessibility patternsNo iframe title attributeN/A
(no iframe equivalent)hostedCardDisplayState read-onlyAfter setNumberFormat / toggleMaskPlatform
Spreedly.transferFocus on expressNot on CardFormDropInInternal focus chain onlyPlatform
Spreedly.setRequiredAttribute(field)isRequired: trueParity

Validation and field state (full)

Same rows as Validation and field state (headless) above, plus:

If you previously used…Use this in iOS SDK…NotesStatus
Enter / Tab / Escape from iframesubmitLabel, onSubmit, focus chain between fieldsTab order is merchant-owned on iOS — wire card → expiry → CVC explicitly.Platform

Release notes and docs

If you previously used…Use this in iOS SDK…NotesStatus
iframe feed.xml / web changelog habitsCHANGELOG + GitHub releases on checkout-ios-packageOptional RSS/XML feed is not published from this repo today.Gap
TypeScript / @typesSwift sources + Xcode Quick HelpTypes ship with the SDK; use Xcode or doc comments.Platform

See also Headless PAN API quick reference.

Getting help

TopicDocument
Cross-platform migration planUpgrading your checkout experience
iOS SDK package distributionCheckout iOS SDK package repository
Legacy iFrame UIiFrame UI
Legacy iFrame eventsiFrame events
SDK selection and tokenizationTokenization
Backend-generated authenticationSecuring iFrame
Spreedly Supportspreedly.com/support

Next steps



Did this page help you?