Back to Articles
Swift 6 & iOS 18 Engineering
•15 min read•Swift 6.0 • SwiftUI • Vapor 4 & CryptoKit

Swift 6 & iOS E-Signature Integration: URLSession, CryptoKit & SwiftUI (2026)

How to architect data-race-free electronic signature workflows in iOS 18, macOS, and Swift Vapor 4 services using Swift 6 actor isolation, Sendable value types, SFSafariViewController Universal Link callbacks, and Apple CryptoKit HMAC-SHA256 verification.

Quick Answer (TL;DR)

Swift 6 Strict Concurrency & iOS 18 E-Signature Blueprint

To add legally binding e-signatures to an iOS 18 / Swift 6 application without shipping bloated legacy Objective-C XCFrameworks, encapsulate Signbee's POST https://signb.ee/api/v1/send call inside a backend Swift actor SignbeeClient using URLSession.shared.data(for:) and Codable & Sendable structs. Return the single-use signing_url to your SwiftUI view, present the ceremony via SFSafariViewController or ASWebAuthenticationSession with a Universal Link redirect_url, and verify X-Signbee-Signature webhooks on your server using Apple CryptoKit (HMAC<SHA256>.isValidAuthenticationCode).

0 MB binary bloat (No CocoaPods)
Swift 6 Sendable & Actor safe
Hardware CryptoKit HMAC verification

1. Why Legacy Objective-C E-Signature SDKs Fail in the Swift 6 Era

With the release of Swift 6 and Xcode 16+, Apple introduced compile-time data-race safety (SWIFT_STRICT_CONCURRENCY = complete) as a first-class language guarantee. Every value crossing an isolation domain—between a @MainActor SwiftUI view model and a background network task—must conform to Sendable.

Mobile engineering teams building wealth-management apps, field-service iPad platforms, private aviation charter portals, or telehealth onboarding flows often discover that legacy enterprise e-signature iOS SDKs are frozen in an Objective-C past. Those vendor SDKs typically ship as closed-source binary .xcframework bundles that:

  • Violate Swift 6 Concurrency Isolation: They rely on shared mutable singletons, untyped [AnyHashable: Any] notification payloads, and completion handlers dispatched on unspecified Grand Central Dispatch (DispatchQueue) threads without @Sendable annotations.
  • Inflate App Store Download Size: Legacy iOS e-signature frameworks bundle redundant PDF rendering engines and UIKit nibs that add 22 MB to 38 MB to your uncompressed app binary—pushing users closer to cellular download warnings and degrading cold-start dyld linking time by 180 ms+.
  • Create App Store Privacy Manifest Friction: Third-party binary SDKs that touch file-timestamp or user-defaults APIs without an updated, cryptographically signed PrivacyInfo.xcprivacy trigger automated ITMS-91053 rejections when you upload builds to App Store Connect.

By integrating directly with Signbee's OpenAPI 3.1 REST endpoints using Apple's native URLSession, Codable, and SafariServices frameworks, your iOS and macOS codebase adds zero external package dependencies. For a broader look at how developer-first APIs outperform legacy enterprise suites, read our analysis of the best e-signature APIs for developers in 2026 and DocuSign API pricing explained.

iOS / Swift Engineering CriterionSignbee REST + Native Swift 6Self-Hosted Open-Source WebViewLegacy Objective-C Binary SDK
Swift 6 Strict Concurrency100% Native Actor & Sendable structsRequires custom Swift wrapperRequires @preconcurrency workarounds
IPA Binary Size Impact0 KB (Uses Foundation & SafariServices)0 KB on client (Heavy Docker server)+22 MB to +38 MB XCFramework
App Store PrivacyInfo.xcprivacyZero 3rd-party SDK manifest riskZero 3rd-party SDK manifest riskHigh ITMS-91053 rejection risk
Apple Silicon / VisionOS SupportiOS, iPadOS, macOS, visionOS nativeWebView onlyOften missing visionOS / simulator slices
Envelope Cost at ScaleFree tier (5/mo) • $20/mo Pro Unlimited$90–$240/mo cloud hosting$4.00–$8.50 per signed envelope

2. Security Architecture: Why Your Signbee API Key Belongs on Your Backend, Not in the IPA

Before writing a single line of Swift networking code, every mobile architect must enforce a fundamental security boundary: never embed your secret signbee_live_... API key inside an iOS, iPadOS, or macOS client binary. Even if you obfuscate strings or store keys in Xcode build settings, any attacker with a jailbroken device or Proxyyman/Charles TLS interception can extract client-side bearer tokens and dispatch unauthorized contracts under your organization's identity.

Instead, production Apple ecosystem apps use a clean three-step capability token flow:

  1. iOS App Requests Contract Initiation: Your SwiftUI view calls your authenticated backend endpoint (e.g., POST https://api.yourcompany.com/v1/agreements/initiate) passing the user's session JWT and agreement parameters.
  2. Backend actor SignbeeClient Dispatches to Signbee: Your server (running Swift Vapor 4, Hummingbird 2, or Node/Go/Kotlin) invokes POST https://signb.ee/api/v1/send using your secret signbee_live_... key and receives the single-use signing_url.
  3. iOS Presents the Tokenized signing_url: Your backend returns only the signing_url and document_id to the iPhone or iPad. Because the signing_url is a cryptographically random, single-document capability URL that expires automatically, it is completely safe to load on the mobile device.

3. Building a Thread-Safe `actor SignbeeClient` in Swift 6

Whether you run Swift on the server with Vapor 4 / Hummingbird 2 or build an internal macOS administrative tool where Keychain-backed credentials are permitted, modeling your API client as a Swift 6 actor with Sendable value structs guarantees compile-time thread safety.

Below is the complete Swift 6 implementation of SignbeeClient. It uses JSONEncoder.KeyEncodingStrategy.convertToSnakeCase and JSONDecoder.KeyDecodingStrategy.convertFromSnakeCase so your Swift properties remain idiomatic camelCase (senderName, signingUrl) while serializing cleanly to Signbee's snake_case wire format (sender_name, signing_url).

Sources/SignbeeKit/SignbeeClient.swiftSwift 6.0 (Strict Concurrency)
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

public struct SendDocumentRequest: Codable, Sendable, Equatable {
    public let title: String
    public let markdown: String?
    public let pdfBase64: String?
    public let pdfUrl: String?
    public let senderName: String
    public let senderEmail: String
    public let recipientName: String
    public let recipientEmail: String
    public let expiresInDays: Int
    public let webhookUrl: String?
    public let redirectUrl: String?
    public let metadata: [String: String]?

    public init(
        title: String,
        markdown: String? = nil,
        pdfBase64: String? = nil,
        pdfUrl: String? = nil,
        senderName: String,
        senderEmail: String,
        recipientName: String,
        recipientEmail: String,
        expiresInDays: Int = 14,
        webhookUrl: String? = nil,
        redirectUrl: String? = nil,
        metadata: [String: String]? = nil
    ) {
        self.title = title
        self.markdown = markdown
        self.pdfBase64 = pdfBase64
        self.pdfUrl = pdfUrl
        self.senderName = senderName
        self.senderEmail = senderEmail
        self.recipientName = recipientName
        self.recipientEmail = recipientEmail
        self.expiresInDays = expiresInDays
        self.webhookUrl = webhookUrl
        self.redirectUrl = redirectUrl
        self.metadata = metadata
    }
}

public struct SendDocumentResponse: Codable, Sendable, Equatable, Identifiable {
    public let id: String
    public let status: String
    public let title: String
    public let signingUrl: URL
    public let verifyUrl: URL
    public let expiresAt: String
    public let createdAt: String
}

public enum SignbeeClientError: Error, Sendable, LocalizedError {
    case invalidResponse
    case httpError(statusCode: Int, message: String)

    public var errorDescription: String? {
        switch self {
        case .invalidResponse:
            return "Received a non-HTTP response from Signbee API."
        case let .httpError(statusCode, message):
            return "Signbee API error (HTTP \(statusCode)): \(message)"
        }
    }
}

public actor SignbeeClient {
    private let apiKey: String
    private let baseURL: URL
    private let session: URLSession
    private let encoder: JSONEncoder
    private let decoder: JSONDecoder

    public init(
        apiKey: String,
        baseURL: URL = URL(string: "https://signb.ee")!,
        session: URLSession = .shared
    ) {
        self.apiKey = apiKey
        self.baseURL = baseURL
        self.session = session

        let enc = JSONEncoder()
        enc.keyEncodingStrategy = .convertToSnakeCase
        self.encoder = enc

        let dec = JSONDecoder()
        dec.keyDecodingStrategy = .convertFromSnakeCase
        self.decoder = dec
    }

    public func sendDocument(
        _ payload: SendDocumentRequest,
        idempotencyKey: String = UUID().uuidString
    ) async throws -> SendDocumentResponse {
        let endpoint = baseURL.appendingPathComponent("/api/v1/send")
        var request = URLRequest(url: endpoint)
        request.httpMethod = "POST"
        request.timeoutInterval = 15
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")
        request.setValue(idempotencyKey, forHTTPHeaderField: "Idempotency-Key")
        request.httpBody = try encoder.encode(payload)

        let (data, response) = try await session.data(for: request)
        guard let httpResponse = response as? HTTPURLResponse else {
            throw SignbeeClientError.invalidResponse
        }

        guard (200...299).contains(httpResponse.statusCode) else {
            let rawBody = String(data: data, encoding: .utf8) ?? "Unknown error"
            throw SignbeeClientError.httpError(statusCode: httpResponse.statusCode, message: rawBody)
        }

        return try decoder.decode(SendDocumentResponse.self, from: data)
    }

    public func fetchDocumentStatus(documentId: String) async throws -> SendDocumentResponse {
        let endpoint = baseURL.appendingPathComponent("/api/v1/documents/\(documentId)")
        var request = URLRequest(url: endpoint)
        request.httpMethod = "GET"
        request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
        request.setValue("application/json", forHTTPHeaderField: "Accept")

        let (data, response) = try await session.data(for: request)
        guard let httpResponse = response as? HTTPURLResponse else {
            throw SignbeeClientError.invalidResponse
        }
        guard (200...299).contains(httpResponse.statusCode) else {
            let rawBody = String(data: data, encoding: .utf8) ?? "Unknown error"
            throw SignbeeClientError.httpError(statusCode: httpResponse.statusCode, message: rawBody)
        }

        return try decoder.decode(SendDocumentResponse.self, from: data)
    }
}

4. Presenting the Signing Ceremony in SwiftUI with `SFSafariViewController` & Universal Links

Once your backend returns the signingUrl to your iOS app, you have three native presentation choices on iOS 18: SFSafariViewController, ASWebAuthenticationSession, or WKWebView.

For consumer and enterprise iOS apps, wrapping SFSafariViewController in a UIViewControllerRepresentable sheet and pairing it with an Apple Universal Link (https://app.yourcompany.com/contracts/complete) in Signbee's redirect_url parameter delivers the highest conversion rate. The user sees Safari's verified HTTPS security bar, draws their signature with smooth 120Hz ProMotion touch responsiveness or Apple Pencil on iPad, taps Complete Signature, and Signbee redirects to your Universal Link—which SwiftUI intercepts via .onOpenURL to automatically dismiss the modal sheet and transition to your success screen.

If you are designing a fully customized, brand-matched signing experience, check out our guide on building a white-label e-signature API flow, or compare self-hosted alternatives in our open-source e-signature API comparison.

Sources/App/Views/ContractSigningFlowView.swiftSwiftUI + SafariServices (iOS 18)
import SwiftUI
import SafariServices
import Observation

struct SafariSigningView: UIViewControllerRepresentable {
    let signingURL: URL

    func makeUIViewController(context: Context) -> SFSafariViewController {
        let config = SFSafariViewController.Configuration()
        config.entersReaderIfAvailable = false
        config.barCollapsingEnabled = true

        let safariVC = SFSafariViewController(url: signingURL, configuration: config)
        safariVC.preferredBarTintColor = UIColor(red: 0.04, green: 0.04, blue: 0.05, alpha: 1.0)
        safariVC.preferredControlTintColor = UIColor(red: 0.96, green: 0.62, blue: 0.04, alpha: 1.0)
        safariVC.dismissButtonStyle = .close
        return safariVC
    }

    func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context) {}
}

@Observable
@MainActor
final class ContractSigningViewModel {
    var activeSigningURL: URL?
    var isSigned: Bool = false
    var verificationURL: URL?
    var errorMessage: String?

    func presentSigningCeremony(signingURL: URL, verifyURL: URL) {
        self.verificationURL = verifyURL
        self.activeSigningURL = signingURL
    }

    func handleIncomingUniversalLink(_ url: URL) {
        // Matches redirect_url: https://app.yourcompany.com/contracts/complete?doc_id=...
        guard url.path.contains("/contracts/complete") else { return }
        self.activeSigningURL = nil
        self.isSigned = true
    }
}

public struct ContractSigningFlowView: View {
    @State private var viewModel = ContractSigningViewModel()
    let initialSigningURL: URL
    let initialVerifyURL: URL

    public var body: some View {
        VStack(spacing: 20) {
            if viewModel.isSigned {
                Label("Agreement Cryptographically Signed", systemImage: "checkmark.seal.fill")
                    .font(.headline)
                    .foregroundStyle(.green)

                if let verifyURL = viewModel.verificationURL {
                    Link("View SHA-256 Audit Certificate", destination: verifyURL)
                        .buttonStyle(.borderedProminent)
                }
            } else {
                Button("Review & Sign Agreement") {
                    viewModel.presentSigningCeremony(
                        signingURL: initialSigningURL,
                        verifyURL: initialVerifyURL
                    )
                }
                .buttonStyle(.borderedProminent)
            }
        }
        .sheet(item: Binding(
            get: { viewModel.activeSigningURL.map { IdentifiableURL(url: $0) } },
            set: { if $0 == nil { viewModel.activeSigningURL = nil } }
        )) { item in
            SafariSigningView(signingURL: item.url)
                .ignoresSafeArea()
        }
        .onOpenURL { incomingURL in
            viewModel.handleIncomingUniversalLink(incomingURL)
        }
    }
}

private struct IdentifiableURL: Identifiable {
    let id = UUID()
    let url: URL
}

iOS Signing Container Matrix: SFSafariViewController vs. ASWebAuthenticationSession vs. WKWebView

Choosing the right iOS web container depends on whether your users sign on personal iPhones or managed multi-user iPad kiosks:

CapabilitySFSafariViewControllerASWebAuthenticationSessionWKWebView (Custom UI)
Best Use CaseConsumer & B2B iPhone/iPad appsCustom URL scheme callback flowsIn-store iPad kiosks & point-of-sale
Process Isolation & TLS BarOut-of-process + Verified HTTPS barOut-of-process + Verified HTTPS barApp-controlled navigation bar
Callback InterceptionUniversal Links (.onOpenURL)Custom scheme or Universal LinkWKNavigationDelegate policy hook
Ephemeral Session OptionStandard Safari SandboxprefersEphemeralWebBrowserSession = trueWKWebsiteDataStore.nonPersistent()

5. Server-Side Swift: Verifying `X-Signbee-Signature` Webhooks in Vapor 4 with Apple `CryptoKit`

Even when a user completes the signing ceremony on iOS and triggers your Universal Link redirect, your backend database must treat the signed webhook from Signbee as the authoritative source of truth. A user might close Safari half a second after submitting their signature before the redirect completes, or sign the agreement from the backup email link on their MacBook instead of their iPhone.

If your backend runs on Swift Vapor 4 or Hummingbird 2, Apple's CryptoKit library (available on both macOS and Linux via swift-crypto) provides HMAC<SHA256>.isValidAuthenticationCode. Unlike naive string equality checks that are vulnerable to byte-by-byte timing side channels, isValidAuthenticationCode performs a constant-time comparison directly over the decoded hexadecimal signature bytes and the raw HTTP body buffer.

Sources/Server/Controllers/SignbeeWebhookController.swiftVapor 4 + Apple CryptoKit
import Vapor
import Crypto

struct SignbeeWebhookPayload: Content, Sendable {
    let eventId: String
    let event: String
    let documentId: String
    let status: String
    let sha256Hash: String?
    let signedPdfUrl: String?
    let verifyUrl: String?
    let signerIp: String?
    let signedAt: String?

    enum CodingKeys: String, CodingKey {
        case eventId = "event_id"
        case event
        case documentId = "document_id"
        case status
        case sha256Hash = "sha256_hash"
        case signedPdfUrl = "signed_pdf_url"
        case verifyUrl = "verify_url"
        case signerIp = "signer_ip"
        case signedAt = "signed_at"
    }
}

enum SignbeeSignatureVerifier {
    static func verify(
        rawBody: ByteBuffer,
        signatureHeader: String?,
        webhookSecret: String
    ) -> Bool {
        guard let signatureHeader = signatureHeader else { return false }
        let hexString = signatureHeader.replacingOccurrences(of: "sha256=", with: "")
            .trimmingCharacters(in: .whitespacesAndNewlines)

        guard let signatureBytes = Data(hexEncoded: hexString) else {
            return false
        }

        let payloadData = Data(buffer: rawBody)
        let symmetricKey = SymmetricKey(data: Data(webhookSecret.utf8))

        return HMAC<SHA256>.isValidAuthenticationCode(
            signatureBytes,
            authenticating: payloadData,
            using: symmetricKey
        )
    }
}

extension Data {
    init?(hexEncoded hex: String) {
        guard hex.count.isMultiple(of: 2) else { return nil }
        var bytes = Data(capacity: hex.count / 2)
        var index = hex.startIndex
        while index < hex.endIndex {
            let nextIndex = hex.index(index, offsetBy: 2)
            guard let b = UInt8(hex[index..<nextIndex], radix: 16) else { return nil }
            bytes.append(b)
            index = nextIndex
        }
        self = bytes
    }
}

func registerSignbeeWebhookRoutes(_ app: Application, webhookSecret: String) {
    app.on(.POST, "webhooks", "signbee", body: .collect(maxSize: "2mb")) { req async throws -> HTTPStatus in
        guard let rawBuffer = req.body.data else {
            throw Abort(.badRequest, reason: "Missing webhook request body")
        }

        let signatureHeader = req.headers.first(name: "X-Signbee-Signature")
        guard SignbeeSignatureVerifier.verify(
            rawBody: rawBuffer,
            signatureHeader: signatureHeader,
            webhookSecret: webhookSecret
        ) else {
            req.logger.warning("Rejected Signbee webhook with invalid HMAC-SHA256 signature")
            throw Abort(.unauthorized, reason: "Invalid X-Signbee-Signature")
        }

        let payload = try req.content.decode(SignbeeWebhookPayload.self)
        req.logger.info("Verified Signbee event=\(payload.event) doc=\(payload.documentId) sha256=\(payload.sha256Hash ?? "pending")")

        // Persist status & trigger APNs push notification to iOS device
        return .ok
    }
}

6. Generating On-Device PDFs with `PDFKit` / SwiftUI `ImageRenderer` & Base64 Dispatch

In many iOS and iPadOS field applications—such as solar installation site surveys, automotive lease walkarounds, or clinical consent forms—the document to be signed includes annotated photos or vector diagrams rendered right on the iPad screen.

With SwiftUI's ImageRenderer (or UIGraphicsPDFRenderer), your iOS app can render any SwiftUI view hierarchy into an in-memory Data buffer, encode it via pdfData.base64EncodedString(), and pass it to your backend to submit inside Signbee's pdf_base64 field. Signbee preserves every vector path and embedded photograph, presents the exact PDF for Party B's signature, appends the A4 Audit Certificate page, and seals the combined binary with an immutable SHA-256 digest.

To see how flat-rate API pricing transforms unit economics for mobile-first startups and high-volume apps, read our guide to affordable e-signature API integration, compare every tier in our e-signature API pricing comparison (2026), and test your Xcode Simulator builds today using our free e-signature API.

7. App Store Review Guidelines (3.1.1) & Privacy Nutrition Label Compliance

iOS product managers frequently ask whether embedding contract execution inside an iPhone or iPad app triggers Apple App Store Review friction. Two compliance advantages make Signbee ideal for iOS teams:

  • Exempt from Guideline 3.1.1 (In-App Purchase): Executing binding commercial contracts, equipment leases, employment agreements, or physical service orders governs real-world obligations outside digital app feature unlocking, meaning Apple never requires In-App Purchase commissions on agreements signed in your app.
  • Zero Third-Party Tracking or Fingerprinting SDKs: Because your app talks to Signbee via your backend using standard URLSession and presents the signing link in SFSafariViewController, you ship zero third-party binary frameworks and never invoke restricted device-fingerprinting APIs.

8. Frequently Asked Questions: Swift 6 & iOS E-Signature Integration

Why do legacy Objective-C and CocoaPods e-signature SDKs fail under Swift 6 strict concurrency?

Legacy iOS e-signature SDKs built with Objective-C and distributed as binary XCFrameworks rely on mutable singleton managers, delegate callbacks on arbitrary GCD queues, and non-Sendable dictionary payloads. When compiling with Swift 6 strict concurrency checking enabled (SWIFT_STRICT_CONCURRENCY = complete), these legacy frameworks trigger dozens of data-race warnings and Sendable conformance errors. Worse, they bloat your App Store binary by 18 MB to 35 MB and frequently lack privacy nutrition manifests (PrivacyInfo.xcprivacy). Using a native Swift 6 actor and Sendable structs with Signbee's REST API requires zero third-party frameworks and passes strict concurrency verification cleanly.

Should I store my Signbee API key inside an iOS app bundle or Info.plist?

Never store your production Signbee Bearer API key (signbee_live_...) in your iOS application binary, Info.plist, or Xcode build configurations. Any string compiled into an IPA bundle can be extracted in seconds using strings, otool, or a TLS MITM proxy. Always hold your Signbee API key on your backend server (such as a Swift Vapor 4 microservice, AWS Lambda, or Node.js API). Your iOS client authenticates with your backend using the user's session token, and your backend calls Signbee's POST /api/v1/send endpoint and returns only the single-use, capability-scoped signing_url to the iOS device.

Should I use SFSafariViewController, ASWebAuthenticationSession, or WKWebView for in-app contract signing on iOS?

For most native iOS 18 and SwiftUI applications, SFSafariViewController or ASWebAuthenticationSession is strongly preferred over a raw WKWebView. SFSafariViewController runs out-of-process, provides native AutoFill and passkey support, displays a trusted TLS lock indicator that reassures users signing legal agreements, and automatically intercepts Universal Links configured in your redirect_url parameter to dismiss the sheet the moment the user finishes signing. If you require a completely chrome-free embedded layout inside an iPad kiosk app, WKWebView works seamlessly with Signbee's responsive mobile signing ceremony.

How do I verify X-Signbee-Signature webhooks in a Swift Vapor 4 backend using Apple CryptoKit?

In a Swift Vapor 4 route handler, extract the raw ByteBuffer from req.body.data and convert it to Foundation Data before decoding JSON. Parse the hex string from the X-Signbee-Signature header (stripping any sha256= prefix) into a Data array of bytes. Then call CryptoKit's HMAC<SHA256>.isValidAuthenticationCode(signatureBytes, authenticating: rawPayloadData, using: SymmetricKey(data: secretData)). Apple's CryptoKit implementation performs a constant-time byte comparison in hardware-accelerated native code, protecting your webhook endpoint from timing attacks.

Does integrating Signbee e-signatures trigger Apple App Store Guideline 3.1.1 In-App Purchase requirements?

No. Executing real-world legal contracts, B2B service agreements, residential leases, waivers, or employment onboarding documents does not unlock digital in-app content or app features, so it falls outside Apple App Store Review Guideline 3.1.1 for In-App Purchases. Furthermore, because Signbee requires zero external dependencies and collects only the statutory audit metadata required for ESIGN and eIDAS compliance (IP address, user-agent, email, and timestamp), your App Privacy nutrition label remains straightforward and passes App Review without friction.

Can I generate a PDF on-device using SwiftUI ImageRenderer or PDFKit and send it via Signbee?

Yes. While sending CommonMark markdown via Signbee's markdown field is the fastest and lowest-bandwidth path, iOS and macOS apps that render bespoke visual inspection reports, annotated architectural floorplans, or medical intake forms using PDFKit or SwiftUI's ImageRenderer can encode the generated PDF Data as a base64 string (pdfData.base64EncodedString()) and pass it to your backend to forward in the pdf_base64 field of POST /api/v1/send. Signbee seals the exact PDF bytes with a cryptographic SHA-256 certificate upon signature completion.

Add Native E-Signatures to Your iOS & Swift Stack Today

Zero CocoaPods. Zero binary bloat. Start with 5 free documents per month or unlock unlimited envelopes for $20/mo.