Sample Navigation

Code demonstrating how to navigate to the SDK view with SwiftUI or UIKit

Standard Experience

SwiftUI - Navigation Destination

Not currently supported


SwiftUI - Full Screeen Cover

import SwiftUI

struct ContentView: View {
    @State private var viewModel: ViewModel = .init()
    
    var body: some View {
        NavigationStack {
            Button("ReadyRemit SDK v10.0.0") {
                viewModel.showReadyRemitSDK()
            }
            .fullScreenCover(item: $viewModel.readyRemitItem) {
                AnyView($0.view)
            }
            .navigationTitle("SwiftUI - Full Screen Cover")
        }
    }
}

#Preview {
    ContentView()
}
import ReadyRemitSDK
import SwiftUI

@Observable
class ViewModel {
    struct ReadyRemitItem: Identifiable {
        let id = UUID()
        let view: any View
    }

    private let themeConfiguration: String? = nil

    var readyRemitItem: ReadyRemitItem?

    func showReadyRemitSDK() {
        ReadyRemit.shared.startSDK(
            themeConfiguration: themeConfiguration,
            fetchAccessTokenDetails: fetchAccessTokenDetails,
            verifyFundsAndCreateTransfer: verifyFundsAndCreateTransfer,
            onDismiss: { [weak self] in
                guard let self else { return }
                self.readyRemitItem = nil
            }
        ) { [weak self] readyRemitSDKView in
            guard let self else { return }
            self.readyRemitItem = .init(view: readyRemitSDKView)
        }
    }

    private func fetchAccessTokenDetails() async throws -> AccessTokenDetails {
        guard let url = URL(string: "https://example.com/v1/oauth/token") else {
            throw URLError(.badURL)
        }
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"
        let (data, urlResponse) = try await URLSession.shared.data(for: urlRequest)
        guard let httpURLResponse = urlResponse as? HTTPURLResponse else {
            throw URLError(.badServerResponse)
        }
        switch httpURLResponse.statusCode {
        case 200...299:
            // CreateAccessTokenDetailsResponse must conform to AccessTokenDetails
            return try JSONDecoder().decode(CreateAccessTokenDetailsResponse.self, from: data)
        default:
            throw CustomError.unsuccessfulResponse
        }
    }

    private func verifyFundsAndCreateTransfer(
        transferRequest: ReadyRemit.TransferRequest
    ) async throws(ReadyRemitError) -> TransferDetails {
        do {
            guard let url = URL(string: "https://example.com/v1/verifyFundsAndCreateTransfer") else {
                throw URLError(.badURL)
            }
            var urlRequest = URLRequest(url: url)
            urlRequest.httpMethod = "POST"
            urlRequest.httpBody = try JSONEncoder().encode(transferRequest)
            let (data, urlResponse) = try await URLSession.shared.data(for: urlRequest)
            guard let httpURLResponse = urlResponse as? HTTPURLResponse else {
                throw URLError(.badServerResponse)
            }
            switch httpURLResponse.statusCode {
            case 200...299:
                // CreateTransferDetailsResponse must conform to TransferDetails
                return try JSONDecoder().decode(CreateTransferDetailsResponse.self, from: data)
            default:
                throw ReadyRemitError(code: .none, message: "Insufficient Funds")
            }
        } catch let error as ReadyRemitError {
            throw error
        } catch {
            throw ReadyRemitError(code: .none, message: "Error: \(error.localizedDescription)")
        }
    }
}

UIKit - Push View Controller

This assumes ContentViewController is the root view controller of a UINativationController.

import SwiftUI

class ContentViewController: UIViewController {
    private let viewModel: ViewModel = .init()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = "UIKit - Push"
        setupButton()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        setupObservation()
    }

    private func setupButton() {
        let action = UIAction { _ in
            self.viewModel.showReadyRemitSDK()
        }
        
        let button = UIButton(primaryAction: action)
        button.setTitle("ReadyRemit SDK v10.0.0", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(button)
        
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
    
    private func setupObservation() {
        withObservationTracking {
            _ = viewModel.readyRemitItem
        } onChange: { [weak self] in
            guard let self else { return }
            Task {
                await MainActor.run {
                    if let readyRemitItem = self.viewModel.readyRemitItem {
                        let hostingController = UIHostingController(rootView: AnyView(readyRemitItem.view))
                        hostingController.modalPresentationStyle = .fullScreen
                        self.navigationController?.pushViewController(hostingController, animated: true)
                    }
                }
            }
        }
    }
}
import ReadyRemitSDK
import SwiftUI

@Observable
class ViewModel {
    struct ReadyRemitItem: Identifiable {
        let id = UUID()
        let view: any View
    }

    private let themeConfiguration: String? = nil

    var readyRemitItem: ReadyRemitItem?

    func showReadyRemitSDK() {
        ReadyRemit.shared.startSDK(
            themeConfiguration: themeConfiguration,
            fetchAccessTokenDetails: fetchAccessTokenDetails,
            verifyFundsAndCreateTransfer: verifyFundsAndCreateTransfer,
            onDismiss: { [weak self] in
                guard let self else { return }
                self.readyRemitItem = nil
            }
        ) { [weak self] readyRemitSDKView in
            guard let self else { return }
            self.readyRemitItem = .init(view: readyRemitSDKView)
        }
    }

    private func fetchAccessTokenDetails() async throws -> AccessTokenDetails {
        guard let url = URL(string: "https://example.com/v1/oauth/token") else {
            throw URLError(.badURL)
        }
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"
        let (data, urlResponse) = try await URLSession.shared.data(for: urlRequest)
        guard let httpURLResponse = urlResponse as? HTTPURLResponse else {
            throw URLError(.badServerResponse)
        }
        switch httpURLResponse.statusCode {
        case 200...299:
            // CreateAccessTokenDetailsResponse must conform to AccessTokenDetails
            return try JSONDecoder().decode(CreateAccessTokenDetailsResponse.self, from: data)
        default:
            throw CustomError.unsuccessfulResponse
        }
    }

    private func verifyFundsAndCreateTransfer(
        transferRequest: ReadyRemit.TransferRequest
    ) async throws(ReadyRemitError) -> TransferDetails {
        do {
            guard let url = URL(string: "https://example.com/v1/verifyFundsAndCreateTransfer") else {
                throw URLError(.badURL)
            }
            var urlRequest = URLRequest(url: url)
            urlRequest.httpMethod = "POST"
            urlRequest.httpBody = try JSONEncoder().encode(transferRequest)
            let (data, urlResponse) = try await URLSession.shared.data(for: urlRequest)
            guard let httpURLResponse = urlResponse as? HTTPURLResponse else {
                throw URLError(.badServerResponse)
            }
            switch httpURLResponse.statusCode {
            case 200...299:
                // CreateTransferDetailsResponse must conform to TransferDetails
                return try JSONDecoder().decode(CreateTransferDetailsResponse.self, from: data)
            default:
                throw ReadyRemitError(code: .none, message: "Insufficient Funds")
            }
        } catch let error as ReadyRemitError {
            throw error
        } catch {
            throw ReadyRemitError(code: .none, message: "Error: \(error.localizedDescription)")
        }
    }
}

UIKit - Present View Controller

import SwiftUI

class ContentViewController: UIViewController {
    private let viewModel: ViewModel = .init()

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "UIKit - Present"
        setupButton()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        setupObservation()
    }

  private func setupButton() {
        let action = UIAction { _ in
            self.viewModel.showReadyRemitSDK()
        }
        
        let button = UIButton(primaryAction: action)
        button.setTitle("ReadyRemit SDK v10.0.0", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(button)

        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
    
    private func setupObservation() {
        withObservationTracking {
            _ = viewModel.readyRemitItem
        } onChange: { [weak self] in
            guard let self else { return }
            Task {
                await MainActor.run {
                    if let readyRemitItem = self.viewModel.readyRemitItem {
                        let hostingController = UIHostingController(rootView: AnyView(readyRemitItem.view))
                        hostingController.modalPresentationStyle = .fullScreen
                        self.present(hostingController, animated: true)
                    }
                }
            }
        }
    }
}
import ReadyRemitSDK
import SwiftUI

@Observable
class ViewModel {
    struct ReadyRemitItem: Identifiable {
        let id = UUID()
        let view: any View
    }

    private let themeConfiguration: String? = nil

    var readyRemitItem: ReadyRemitItem?

    func showReadyRemitSDK() {
        ReadyRemit.shared.startSDK(
            themeConfiguration: themeConfiguration,
            fetchAccessTokenDetails: fetchAccessTokenDetails,
            verifyFundsAndCreateTransfer: verifyFundsAndCreateTransfer,
            onDismiss: { [weak self] in
                guard let self else { return }
                self.readyRemitItem = nil
            }
        ) { [weak self] readyRemitSDKView in
            guard let self else { return }
            self.readyRemitItem = .init(view: readyRemitSDKView)
        }
    }

    private func fetchAccessTokenDetails() async throws -> AccessTokenDetails {
        guard let url = URL(string: "https://example.com/v1/oauth/token") else {
            throw URLError(.badURL)
        }
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"
        let (data, urlResponse) = try await URLSession.shared.data(for: urlRequest)
        guard let httpURLResponse = urlResponse as? HTTPURLResponse else {
            throw URLError(.badServerResponse)
        }
        switch httpURLResponse.statusCode {
        case 200...299:
            // CreateAccessTokenDetailsResponse must conform to AccessTokenDetails
            return try JSONDecoder().decode(CreateAccessTokenDetailsResponse.self, from: data)
        default:
            throw CustomError.unsuccessfulResponse
        }
    }

    private func verifyFundsAndCreateTransfer(
        transferRequest: ReadyRemit.TransferRequest
    ) async throws(ReadyRemitError) -> TransferDetails {
        do {
            guard let url = URL(string: "https://example.com/v1/verifyFundsAndCreateTransfer") else {
                throw URLError(.badURL)
            }
            var urlRequest = URLRequest(url: url)
            urlRequest.httpMethod = "POST"
            urlRequest.httpBody = try JSONEncoder().encode(transferRequest)
            let (data, urlResponse) = try await URLSession.shared.data(for: urlRequest)
            guard let httpURLResponse = urlResponse as? HTTPURLResponse else {
                throw URLError(.badServerResponse)
            }
            switch httpURLResponse.statusCode {
            case 200...299:
                // CreateTransferDetailsResponse must conform to TransferDetails
                return try JSONDecoder().decode(CreateTransferDetailsResponse.self, from: data)
            default:
                throw ReadyRemitError(code: .none, message: "Insufficient Funds")
            }
        } catch let error as ReadyRemitError {
            throw error
        } catch {
            throw ReadyRemitError(code: .none, message: "Error: \(error.localizedDescription)")
        }
    }
}

Transfer Experience

SwiftUI - Navigation Destination

Not currently supported


SwiftUI - Full Screen Cover

import SwiftUI

struct ContentView: View {
    @State private var viewModel: ViewModel

    init(transferId: String) {
        _viewModel = State(initialValue: ViewModel(transferId: transferId))
    }
    
    var body: some View {
        NavigationStack {
            Button("ReadyRemit SDK v10.0.0") {
                viewModel.showReadyRemitSDK(transferId: viewModel.transferId)
            }
            .fullScreenCover(item: $viewModel.readyRemitItem) {
                AnyView($0.view)
            }
            .navigationTitle("SwiftUI - Full Screen Cover")
        }
    }
}

#Preview {
    ContentView()
}
import ReadyRemitSDK
import SwiftUI

@Observable
class ViewModel {
    struct ReadyRemitItem: Identifiable {
        let id = UUID()
        let view: any View
    }

    let transferId: String
    private let themeConfiguration: String? = nil

    var readyRemitItem: ReadyRemitItem?

    init(transferId: String) {
        self.transferId = transferId
    }

    func showReadyRemitSDK(transferId: String) {
        ReadyRemit.shared.showTransfer(
            transferId: transferId,
            themeConfiguration: themeConfiguration,
            fetchAccessTokenDetails: fetchAccessTokenDetails,
            onDismiss: { [weak self] in
                guard let self else { return }
                self.readyRemitItem = nil
            }
        ) { [weak self] readyRemitSDKView in
            guard let self else { return }
            self.readyRemitItem = .init(view: readyRemitSDKView)
        }
    }

    private func fetchAccessTokenDetails() async throws -> AccessTokenDetails {
        guard let url = URL(string: "https://example.com/v1/oauth/token") else {
            throw URLError(.badURL)
        }
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"
        let (data, urlResponse) = try await URLSession.shared.data(for: urlRequest)
        guard let httpURLResponse = urlResponse as? HTTPURLResponse else {
            throw URLError(.badServerResponse)
        }
        switch httpURLResponse.statusCode {
        case 200...299:
            // CreateAccessTokenDetailsResponse must conform to AccessTokenDetails
            return try JSONDecoder().decode(CreateAccessTokenDetailsResponse.self, from: data)
        default:
            throw CustomError.unsuccessfulResponse
        }
    }
}

UIKit - Push View Controller

This assumes ContentViewController is the root view controller of a UINativationController.

import SwiftUI

class ContentViewController: UIViewController {
  private let viewModel: ViewModel

    init(transferId: String) {
        self.viewModel = ViewModel(transferId: transferId)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = "UIKit - Push"
        setupButton()
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        setupObservation()
    }

    private func setupButton() {
        let action = UIAction { _ in
            self.viewModel.showReadyRemitSDK(transferId: viewModel.transferId)
        }
        
        let button = UIButton(primaryAction: action)
        button.setTitle("ReadyRemit SDK v10.0.0", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false
        
        view.addSubview(button)
        
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
    
    private func setupObservation() {
        withObservationTracking {
            _ = viewModel.readyRemitItem
        } onChange: { [weak self] in
            guard let self else { return }
            Task {
                await MainActor.run {
                    if let readyRemitItem = self.viewModel.readyRemitItem {
                        let hostingController = UIHostingController(rootView: AnyView(readyRemitItem.view))
                        hostingController.modalPresentationStyle = .fullScreen
                        self.navigationController?.pushViewController(hostingController, animated: true)
                    }
                }
            }
        }
    }
}
import ReadyRemitSDK
import SwiftUI

@Observable
class ViewModel {
    struct ReadyRemitItem: Identifiable {
        let id = UUID()
        let view: any View
    }

    let transferId: String
    private let themeConfiguration: String? = nil

    var readyRemitItem: ReadyRemitItem?

    init(transferId: String) {
        self.transferId = transferId
    }

    func showReadyRemitSDK(transferId: String) {
        ReadyRemit.shared.showTransfer(
            transferId: transferId,
            themeConfiguration: themeConfiguration,
            fetchAccessTokenDetails: fetchAccessTokenDetails,
            onDismiss: { [weak self] in
                guard let self else { return }
                self.readyRemitItem = nil
            }
        ) { [weak self] readyRemitSDKView in
            guard let self else { return }
            self.readyRemitItem = .init(view: readyRemitSDKView)
        }
    }

    private func fetchAccessTokenDetails() async throws -> AccessTokenDetails {
        guard let url = URL(string: "https://example.com/v1/oauth/token") else {
            throw URLError(.badURL)
        }
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"
        let (data, urlResponse) = try await URLSession.shared.data(for: urlRequest)
        guard let httpURLResponse = urlResponse as? HTTPURLResponse else {
            throw URLError(.badServerResponse)
        }
        switch httpURLResponse.statusCode {
        case 200...299:
            // CreateAccessTokenDetailsResponse must conform to AccessTokenDetails
            return try JSONDecoder().decode(CreateAccessTokenDetailsResponse.self, from: data)
        default:
            throw CustomError.unsuccessfulResponse
        }
    }
}

UIKit - Present View Controller

import SwiftUI

class ContentViewController: UIViewController {
    private let viewModel: ViewModel

    init(transferId: String) {
        self.viewModel = ViewModel(transferId: transferId)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        title = "UIKit - Present"
        setupButton()
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        setupObservation()
    }

  private func setupButton() {
        let action = UIAction { _ in
            self.viewModel.showReadyRemitSDK(transferId: viewModel.transferId)
        }
        
        let button = UIButton(primaryAction: action)
        button.setTitle("ReadyRemit SDK v10.0.0", for: .normal)
        button.translatesAutoresizingMaskIntoConstraints = false

        view.addSubview(button)

        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
        ])
    }
    
    private func setupObservation() {
        withObservationTracking {
            _ = viewModel.readyRemitItem
        } onChange: { [weak self] in
            guard let self else { return }
            Task {
                await MainActor.run {
                    if let readyRemitItem = self.viewModel.readyRemitItem {
                        let hostingController = UIHostingController(rootView: AnyView(readyRemitItem.view))
                        hostingController.modalPresentationStyle = .fullScreen
                        self.present(hostingController, animated: true)
                    }
                }
            }
        }
    }
}
import ReadyRemitSDK
import SwiftUI

@Observable
class ViewModel {
    struct ReadyRemitItem: Identifiable {
        let id = UUID()
        let view: any View
    }

    let transferId: String
    private let themeConfiguration: String? = nil

    var readyRemitItem: ReadyRemitItem?

    init(transferId: String) {
        self.transferId = transferId
    }

    func showReadyRemitSDK(transferId: String) {
        ReadyRemit.shared.showTransfer(
            transferId: transferId,
            themeConfiguration: themeConfiguration,
            fetchAccessTokenDetails: fetchAccessTokenDetails,
            onDismiss: { [weak self] in
                guard let self else { return }
                self.readyRemitItem = nil
            }
        ) { [weak self] readyRemitSDKView in
            guard let self else { return }
            self.readyRemitItem = .init(view: readyRemitSDKView)
        }
    }

    private func fetchAccessTokenDetails() async throws -> AccessTokenDetails {
        guard let url = URL(string: "https://example.com/v1/oauth/token") else {
            throw URLError(.badURL)
        }
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = "POST"
        let (data, urlResponse) = try await URLSession.shared.data(for: urlRequest)
        guard let httpURLResponse = urlResponse as? HTTPURLResponse else {
            throw URLError(.badServerResponse)
        }
        switch httpURLResponse.statusCode {
        case 200...299:
            // CreateAccessTokenDetailsResponse must conform to AccessTokenDetails
            return try JSONDecoder().decode(CreateAccessTokenDetailsResponse.self, from: data)
        default:
            throw CustomError.unsuccessfulResponse
        }
    }
}