Getting Started

Install the SDK, configure it, and make your first authenticated call.

Requirements

   
Deployment target iOS 15.5 or later
Swift 5.9 or later
Xcode A version shipping Swift 5.9+

You will also need a partner id, issued by InstaVision. The SDK will not talk to the backend without one.


Installation

Add the dependency to your Podfile, along with the post_install hook below:

platform :ios, '15.5'

target 'YourAppTarget' do
  use_frameworks!
  use_modular_headers!

  # Omit :tag to track the latest version.
  pod 'IVSDK', :git => 'https://github.com/InstaViewAI/IVSDK-iOS', :tag => '3.0.1'
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.5'
      config.build_settings['BUILD_LIBRARY_FOR_DISTRIBUTION'] = 'YES' # Ensure libraries are built for distribution
    end
  end
end

The post_install hook is required. It pins every pod to the same deployment target and builds them with library evolution enabled, so the compiled IVSDK framework is compatible with your app’s Swift version.

Then install and open the workspace:

pod install
open YourApp.xcworkspace

Always open the generated .xcworkspace, not the .xcodeproj.

Import

import IVSDK

Configure the SDK

Call configure once, as early as possible. Backend calls made before it fail with IVError.sdkNotConfigured.

SwiftUI

import SwiftUI
import IVSDK

@main
struct YourApp: App {
    init() {
        InstaSDK.shared.configure(
            partnerId: "your-partner-id",
            region: .us,
            environment: .production
        )

        InstaSDK.shared.onSSEMessage = { message in
            // Real-time updates from the backend.
        }
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

UIKit

import UIKit
import IVSDK

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        InstaSDK.shared.configure(
            partnerId: "your-partner-id",
            region: .us,
            environment: .production
        )

        InstaSDK.shared.onSSEMessage = { message in
            // Real-time updates from the backend.
        }
        return true
    }
}

Environments and regions

public enum Environment { case production, staging, dev }
public enum ServerRegion: String { case us = "US", apac = "APAC" }

The case is .production, not .prod. region selects data residency and only affects .production; staging and dev each have a single host. Leave region at its .us default unless InstaVision has told you otherwise.

Point a debug build at .staging with a compile-time flag rather than shipping a runtime switch:

#if DEBUG
let environment: Environment = .staging
#else
let environment: Environment = .production
#endif

Full details in the InstaSDK reference.


Using your own authentication

If your app authenticates users outside the SDK — your own backend, or an identity provider you already integrate — you do not need the SDK’s sign-in calls. Hand it the access token instead:

InstaSDK.shared.setAuthToken(token)

The token is stored and attached to every subsequent service call, exactly as if the user had signed in through the SDK. This is the only public way to hand the SDK a token.

Pair it with refreshAuthToken. The SDK calls it once before the first authenticated request after launch, and again whenever a request is rejected because the token has expired. Return a valid token and the request is retried with it:

InstaSDK.shared.refreshAuthToken = {
    return YourAuthProvider.currentAccessToken()
}

Set both before making any authenticated call. The closure must return a valid token: an empty string fails the request, and a token that is still expired is retried up to three times and then fails with the server’s error. On the first authenticated request after launch, a nil or empty result fails with IVError.refreshTokenExpired and posts the sessionTokenExpired notification. If the closure is not set at all, the SDK refreshes through its own sign-in provider instead.


Handling session expiry

When a session cannot be refreshed the SDK posts a notification. Observe it once, at the top of your app, and route back to sign-in:

NotificationCenter.default.addObserver(
    forName: .sessionTokenExpired,
    object: nil,
    queue: .main
) { _ in
    Factory.userService.localLogout()
    LiveViewObjectStore.logout()
    // present your sign-in screen
}

Your first calls

Every service lives on Factory and returns an IVPublisher — a Combine publisher that never fails, delivering .loading, then .success or .error.

import Combine
import IVSDK

final class SessionViewModel: ObservableObject {
    @Published var spaces: [SpaceModel] = []
    @Published var isLoading = false
    @Published var errorMessage: String?

    private var cancellables = Set<AnyCancellable>()

    func signIn(email: String, password: String) {
        Factory.userService.signin(email: email, password: password)
            .receive(on: DispatchQueue.main)
            .sink { [weak self] state in
                switch state {
                case .loading:
                    self?.isLoading = true
                case .success:
                    self?.isLoading = false
                    self?.loadSpaces()
                case let .error(error):
                    self?.isLoading = false
                    self?.errorMessage = error.localizedDescription
                }
            }
            .store(in: &cancellables)
    }

    func loadSpaces() {
        Factory.spaceService.spaces()
            .receive(on: DispatchQueue.main)
            .sink { [weak self] state in
                switch state {
                case .loading:
                    self?.isLoading = true
                case let .success(spaces):
                    self?.isLoading = false
                    self?.spaces = spaces
                case let .error(error):
                    self?.isLoading = false
                    self?.errorMessage = error.localizedDescription
                }
            }
            .store(in: &cancellables)
    }
}

Two habits worth forming now:

  • Store the AnyCancellable. Releasing it cancels the request. That is exactly what you want when a screen disappears mid-request — and exactly what silently breaks a call if the cancellable was a local variable.
  • Main queue. Service results are delivered on the main queue. BLEManager’s published properties and onSSEMessage are not guaranteed to be — hop to main there.

A space id is the key to everything else: pass it to Factory.deviceService.devices(spaceId), Factory.spaceService.events(spaceId:queryItems:sortItem:), and so on.


Signing out

Factory.userService.logout()          // unregisters push on the server, closes the real-time channel
    .sink { _ in }
    .store(in: &cancellables)

Factory.userService.localLogout()     // clear the stored token from the keychain
LiveViewObjectStore.logout()          // tear down any live WebRTC sessions

logout() does not clear the stored token — without localLogout() the next launch still finds it and re-opens the real-time channel. Always call LiveViewObjectStore.logout() too, or a stream started by the previous user can outlive their session.


Next steps

   
Sign-up, sign-in and profile Accounts & Users
Pair a camera, over Bluetooth or a QR code Pairing a Camera
Show live video Live View
Features of your product line Home Security · Baby · Pet · Bird · Trail
InstaSDK configuration and callbacks InstaSDK