Nosmai / docs
Nosmai Effects Nosmai Moderation Nosmai Try-ons coming soon
Docs menu iOS
docs / nosmai effects / platform guides / ios

iOS

Add Nosmai Effects to a native iOS app: camera preview, beauty, makeup, color, filters, capture and recording.

Overview

The iOS SDK provides public APIs for:

  • SDK initialization and license handling
  • front and back camera preview
  • built-in beauty, makeup, and face shaping
  • local and cloud .nosmai filters
  • background blur and replacement
  • active-effect state
  • photo capture
  • processed video recording
  • processed output for live streaming

The SDK is written with Objective-C public interfaces and can be used from both Objective-C and Swift applications. The examples below use Objective-C so that every method name matches the public headers exactly.

The official iOS repository contains releases and sample code:

github.com/nosmai/camera-sdk-ios

Requirements

RequirementValue
Minimum iOS versioniOS 15.0
Supported architecturearm64
Camera frameworkAVFoundation
Privacy manifestIncluded in nosmai.framework
Recommended camera frame rate30 FPS
Test environmentPhysical iPhone or iPad

Use a physical arm64 device for camera preview, face tracking, recording, and performance testing. The simulator is not a reliable environment for these features.

Install

Option 1: Use CocoaPods

Add the current Nosmai pod to the application:

platform :ios, '15.0'

target 'CameraApp' do
  use_frameworks!
  pod 'NosmaiCameraSDK', '3.0.0'
end

Then run:

pod install --repo-update

Open the generated .xcworkspace after CocoaPods finishes. Do not add or modify NosmaiModerationSDK; it is a separate product.

The native framework contains PrivacyInfo.xcprivacy. Keep it inside the framework so App Store archives include the SDK privacy manifest.

Option 2: Add the framework manually

Download nosmai.framework.zip and SHA256SUMS from the iOS SDK v3.0.0 release.

shasum -a 256 -c SHA256SUMS

After the checksum reports nosmai.framework.zip: OK:

  1. Unzip nosmai.framework.zip.
  2. Drag nosmai.framework into the Xcode project.
  3. Select the application target and open General.
  4. Add the framework under Frameworks, Libraries, and Embedded Content.
  5. Set it to Embed & Sign.
  6. Set the deployment target to iOS 15.0 or later.

The current release is a physical-device ARM64 framework and does not include an iOS Simulator slice. Confirm that the framework appears in the final application target, not only in a test target.

Do not remove PrivacyInfo.xcprivacy from the manually embedded framework.

Permissions

Add permission descriptions before starting the camera:

<key>NSCameraUsageDescription</key>
<string>This app uses the camera for real-time filters and effects.</string>

<key>NSMicrophoneUsageDescription</key>
<string>This app uses the microphone when recording video or streaming.</string>

<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app saves captured photos and videos to your library.</string>
PermissionRequired when
CameraShowing a camera preview or capturing a photo
MicrophoneRecording or streaming with audio
Photo library add accessSaving a captured photo or video to Photos

Do not request microphone or photo library permission unless the application uses the related feature.

Import the SDK

Use the umbrella header:

#import <nosmai/Nosmai.h>

In Swift:

import nosmai

If the framework module name in a specific release is shown differently by Xcode, use the module name included with that release.

Initialize

Initialize once before opening the camera experience:

#import <nosmai/Nosmai.h>

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    [[NosmaiCore shared]
        initializeWithAPIKey:@"NOSMAI-YOUR-LICENSE-KEY"
                  completion:^(BOOL success, NSError *error) {
        if (!success) {
            NSLog(@"Nosmai initialization failed: %@",
                  error.localizedDescription);
            return;
        }

        NSLog(@"Nosmai is ready");
    }];

    return YES;
}

Initialization is asynchronous. Open the camera only after the completion reports success.

Do not initialize the SDK from every view controller. Keep one application-level owner and reuse the shared instance.

For production applications, keep the license key outside committed source code. Use the application configuration or another protected configuration source.

Display the camera preview

Create a view for the preview and attach the Nosmai camera after initialization:

#import <nosmai/Nosmai.h>

@interface CameraViewController ()
@property(nonatomic, strong) UIView *cameraPreviewView;
@end

@implementation CameraViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.cameraPreviewView =
        [[UIView alloc] initWithFrame:self.view.bounds];
    self.cameraPreviewView.autoresizingMask =
        UIViewAutoresizingFlexibleWidth |
        UIViewAutoresizingFlexibleHeight;
    [self.view addSubview:self.cameraPreviewView];
}

- (void)startNosmaiCamera {
    NosmaiCore *core = [NosmaiCore shared];

    if (!core.isInitialized || core.camera == nil) {
        return;
    }

    NosmaiCameraConfig *config = [[NosmaiCameraConfig alloc] init];
    config.position = NosmaiCameraPositionFront;
    config.sessionPreset = AVCaptureSessionPresetHigh;
    config.frameRate = 30;
    config.orientation = NosmaiVideoOrientationPortrait;
    config.enableMirroring = YES;

    [core.camera updateConfiguration:config];
    [core.camera attachToView:self.cameraPreviewView];

    if (![core.camera startCapture]) {
        NSLog(@"Nosmai camera could not start");
    }
}

@end

Keep the preview view attached while the camera is active. Replacing the view repeatedly causes camera and graphics resources to be recreated.

Camera controls

Switch the camera

BOOL switched = [[NosmaiCore shared].camera switchCamera];

Or choose a specific position:

[[NosmaiCore shared].camera
    switchToPosition:NosmaiCameraPositionBack];

The SDK updates the input orientation and active face effects for the selected camera.

Set the frame rate

BOOL accepted = [[NosmaiCore shared].camera setFrameRate:30];

The device may reject an unsupported frame rate. Use 30 FPS as the normal starting point and test all supported target devices.

Focus and exposure

The point uses normalized coordinates from 0.0 to 1.0:

CGPoint point = CGPointMake(0.5, 0.5);
[[NosmaiCore shared].camera setFocusPointOfInterest:point];
[[NosmaiCore shared].camera setExposurePointOfInterest:point];

Reset to automatic behavior:

[[NosmaiCore shared].camera resetFocusAndExposure];

Zoom

NosmaiCamera *camera = [NosmaiCore shared].camera;
CGFloat requestedZoom = 2.0;
CGFloat zoom = MIN(requestedZoom, camera.maxZoomFactor);
[camera rampToZoomFactor:zoom withDuration:0.2];

Torch

Check support before enabling it:

NosmaiCamera *camera = [NosmaiCore shared].camera;

if (camera.hasTorch) {
    [camera toggleTorch];
}

The front camera normally does not provide a hardware torch.

Apply a .nosmai filter

Use the same method for filter, effect, beauty_effect, and background packages:

NSString *path =
    [[NSBundle mainBundle] pathForResource:@"heart_face"
                                   ofType:@"nosmai"
                              inDirectory:@"filters"];

[[NosmaiCore shared].effects
    applyEffect:path
     completion:^(BOOL success, NSError *error) {
        if (!success) {
            NSLog(@"Filter failed: %@", error.localizedDescription);
        }
    }];

Always use the asynchronous completion result. A valid path does not guarantee that a package can be loaded successfully.

The SDK reads the package type and places it in the correct active category.

Adjust effect parameters

Some .nosmai packages expose adjustable values such as intensity, animation speed, or display text.

Read parameter metadata:

NSArray<NSDictionary *> *parameters =
    [[NosmaiCore shared].effects getEffectParameters];

Set and read a numeric value:

BOOL updated =
    [[NosmaiCore shared].effects
        setEffectParameter:@"intensity"
                     value:0.70f];

float intensity =
    [[NosmaiCore shared].effects
        getEffectParameterValue:@"intensity"];

Set a text value through the shared SDK:

BOOL updated =
    [[NosmaiSDK sharedInstance]
        setEffectParameter:@"headerText"
               stringValue:@"Hello"];

Use the exact parameter names and types returned by the selected package. Unknown names and incompatible value types return NO.

List local filters

After NosmaiCore initialization, the shared NosmaiSDK instance provides typed local filter information:

NosmaiSDK *sdk = [NosmaiSDK sharedInstance];
NSArray<NosmaiFilterInfo *> *filters = [sdk getFilters];

for (NosmaiFilterInfo *filter in filters) {
    NSLog(@"%@, type: %@, path: %@",
          filter.displayName,
          filter.typeKey,
          filter.path);
}

List one type only:

NSArray<NosmaiFilterInfo *> *beautyFilters =
    [[NosmaiSDK sharedInstance]
        getFiltersOfType:NosmaiFilterTypeBeautyEffect];

Supported values are:

  • NosmaiFilterTypeFilter
  • NosmaiFilterTypeEffect
  • NosmaiFilterTypeBeautyEffect
  • NosmaiFilterTypeBackground

Apply a typed item:

NosmaiFilterInfo *filter = filters.firstObject;

[[NosmaiSDK sharedInstance]
    applyEffectInfo:filter
         completion:^(BOOL success, NSError *error) {
        if (!success) {
            NSLog(@"Apply failed: %@", error.localizedDescription);
        }
    }];

Remove that item:

[[NosmaiSDK sharedInstance] removeEffectInfo:filter];

Development-only filter discovery

During development, filters can be discovered even when external manifest and preview files are not present:

[[NosmaiSDK sharedInstance]
    getDebugFiltersOfType:NosmaiFilterTypeEffect
               completion:^(NSArray<NosmaiFilterInfo *> *filters,
                            NSError *error) {
        if (error != nil) {
            NSLog(@"Discovery failed: %@", error.localizedDescription);
            return;
        }

        NSLog(@"Found %lu effects", (unsigned long)filters.count);
    }];

Use getFilters and getFiltersOfType: for the production catalog. The debug methods may inspect package metadata and are intended for development tools.

Built-in beauty

Built-in beauty can remain active with a regular color filter.

Makeup

NosmaiEffectsEngine *effects = [NosmaiCore shared].effects;

[effects applyLipstickWithStyle:NosmaiLipstickStyleMatte
                     colorIndex:0];
[effects setLipstickIntensity:0.65f];

[effects applyEyeshadowWithStyle:NosmaiEyeshadowStyleNatural
                      colorIndex:0];
[effects setEyeshadowIntensity:0.35f];

[effects applyBlusherWithStyle:NosmaiBlusherStyleNatural
                    colorIndex:0];
[effects setBlusherIntensity:0.30f];

Apply eyelashes and eyebrows:

[effects applyEyelashWithStyle:NosmaiEyelashStyleNatural];
[effects setEyelashIntensity:0.45f];

[effects applyEyebrowWithStyle:NosmaiEyebrowStyleNatural
                    colorIndex:0];
[effects setEyebrowIntensity:0.35f];

Remove one feature:

[effects removeLipstick];

Remove all makeup:

[effects removeAllMakeup];

Face shaping

[effects setFaceSlimLevel:0.20f];
[effects setEyeSizeLevel:0.10f];
[effects setNoseSlimLevel:0.10f];
[effects setJawlineLevel:0.15f];

Keep default values subtle. Strong values can look unnatural and can expose tracking limits during quick movement.

Remove face shaping:

[effects removeAllMorphing];

Remove makeup, face shaping, and eye coloring together:

[effects removeAllBeautyEffects];

Background effects

Blur

NosmaiBackgroundSegmentationConfig *config =
    [[NosmaiBackgroundSegmentationConfig alloc] init];
config.mode = NosmaiBackgroundSegmentationModeBlur;
config.blurStrength = 50.0f;

[[NosmaiCore shared].effects setBackgroundSegmentation:config];

Solid color

NosmaiBackgroundSegmentationConfig *config =
    [[NosmaiBackgroundSegmentationConfig alloc] init];
config.mode = NosmaiBackgroundSegmentationModeColor;
config.replacementColor = [UIColor colorWithRed:0.08
                                          green:0.08
                                           blue:0.10
                                          alpha:1.0];

[[NosmaiCore shared].effects setBackgroundSegmentation:config];

Image

NosmaiBackgroundSegmentationConfig *config =
    [[NosmaiBackgroundSegmentationConfig alloc] init];
config.mode = NosmaiBackgroundSegmentationModeImage;
config.replacementImage = [UIImage imageNamed:@"studio_background"];

[[NosmaiCore shared].effects setBackgroundSegmentation:config];

Video

NSURL *url =
    [[NSBundle mainBundle] URLForResource:@"background_loop"
                           withExtension:@"mp4"];

NosmaiBackgroundSegmentationConfig *config =
    [[NosmaiBackgroundSegmentationConfig alloc] init];
config.mode = NosmaiBackgroundSegmentationModeVideo;
config.replacementVideoURL = url;

[[NosmaiCore shared].effects setBackgroundSegmentation:config];

Clear the active background:

[[NosmaiCore shared].effects clearBackgroundSegmentation];

Background processing is more demanding than a basic color filter. Test it together with recording or streaming on the lowest supported device.

Cloud filters

Request the cloud catalog:

NosmaiCloudFilterRequestOptions *options =
    [NosmaiCloudFilterRequestOptions defaultOptions];
options.page = 1;
options.limit = 20;
options.version = NosmaiCloudFilterVersion2;
options.filterType = @"beauty_effect";

[[NosmaiCore shared].effects
    getCloudFiltersWithOptions:options
                    completion:^(NSArray<NSDictionary *> *filters,
                                 NosmaiCloudFilterPaginationInfo *pagination,
                                 NSError *error) {
        if (error != nil) {
            NSLog(@"Cloud catalog failed: %@", error.localizedDescription);
            return;
        }

        NSLog(@"Loaded %lu filters", (unsigned long)filters.count);
    }];

Valid cloud categories are:

  • effects
  • filter
  • bg
  • beauty_effect

Download and apply a selected item:

NSString *filterId = @"CLOUD-FILTER-ID";

[[NosmaiCore shared].effects
    downloadCloudFilter:filterId
               progress:^(float progress) {
        NSLog(@"Download: %.0f%%", progress * 100.0f);
    }
             completion:^(BOOL success,
                          NSString *localPath,
                          NSError *error) {
        if (!success) {
            NSLog(@"Download failed: %@", error.localizedDescription);
            return;
        }

        [[NosmaiCore shared].effects
            applyEffect:localPath
             completion:nil];
    }];

Use the version value supported by the installed SDK. The current public version constant is NosmaiCloudFilterVersion2.

Observe active state

The SDK posts one notification whenever a filter, effect, beauty feature, or background changes:

[[NSNotificationCenter defaultCenter]
    addObserver:self
       selector:@selector(nosmaiStateChanged:)
           name:NosmaiPipelineStateDidChangeNotification
         object:nil];

Read the complete state:

- (void)nosmaiStateChanged:(NSNotification *)notification {
    NosmaiPipelineState *state =
        notification.userInfo[NosmaiPipelineStateUserInfoKey];

    BOOL hasFilter = state.activeFilterPath != nil;
    BOOL hasEffect = state.activeEffectPath != nil;
    BOOL hasBackground = state.backgroundActive;

    NSLog(@"filter=%d effect=%d background=%d",
          hasFilter,
          hasEffect,
          hasBackground);
}

Remove the observer when the owning object is released if the application supports an iOS version or observer style that requires manual removal.

Capture a photo

Capture the processed image:

[[NosmaiCore shared]
    capturePhoto:^(UIImage *image, NSError *error) {
        if (image == nil) {
            NSLog(@"Photo capture failed: %@",
                  error.localizedDescription);
            return;
        }

        UIImageWriteToSavedPhotosAlbum(
            image,
            nil,
            nil,
            nil
        );
    }];

Request photo library access before saving to Photos. Capturing into memory only requires camera access.

Record video

Start recording:

[[NosmaiCore shared]
    startRecordingWithCompletion:^(BOOL success, NSError *error) {
        if (!success) {
            NSLog(@"Recording could not start: %@",
                  error.localizedDescription);
        }
    }];

Stop recording and receive the temporary file URL:

[[NosmaiCore shared]
    stopRecordingWithCompletion:^(NSURL *videoURL, NSError *error) {
        if (videoURL == nil) {
            NSLog(@"Recording failed: %@",
                  error.localizedDescription);
            return;
        }

        NSLog(@"Recorded video: %@", videoURL.path);
    }];

The application decides whether to save, upload, move, or delete the returned file.

Lifecycle

When the application enters the background

- (void)applicationDidEnterBackground:(UIApplication *)application {
    [[NosmaiCore shared] pause];
}

When the application becomes active

- (void)applicationDidBecomeActive:(UIApplication *)application {
    [[NosmaiCore shared] resume];
}

When leaving the camera screen

Stop capture and detach the preview:

- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

    NosmaiCamera *camera = [NosmaiCore shared].camera;
    [camera stopCapture];
    [camera detachFromView];
}

When returning, attach the new preview view and call startCapture again.

Full cleanup

[[NosmaiCore shared] cleanup];

Use full cleanup only when the application is finished with the SDK and accepts that initialization will be required again. Do not call it for a short navigation transition.

Clear active features

Choose the narrowest method that matches the user action:

NosmaiEffectsEngine *effects = [NosmaiCore shared].effects;

[effects clearFilter];
[effects clearAREffect];
[effects clearBackgroundSegmentation];
[effects removeAllBeautyEffects];

Reset every active visual feature:

[[NosmaiCore shared].effects clearAll];

Release checklist

Before distributing the application:

  1. Use a license key issued for the final bundle identifier.
  2. Test first launch with network access.
  3. Test front and back camera switching.
  4. Test leaving and returning to the camera screen.
  5. Test background and foreground transitions.
  6. Test camera and microphone permission denial.
  7. Test local and cloud filter failure handling.
  8. Test recording while beauty or background effects are active.
  9. Test on the oldest and slowest supported physical device.
  10. Disable SDK debug logging in the release build:
[[NosmaiCore shared] setDebugLoggingEnabled:NO];

Common issues

Initialization fails

Confirm that:

  • the device has internet access
  • the license key is complete
  • the bundle identifier matches the Nosmai Console project
  • the SDK is not already initialized with a different key

The preview is blank

Confirm that:

  • camera permission was granted
  • initialization completed successfully
  • the preview view has a non-zero size
  • attachToView: was called before startCapture
  • no other camera session is using the device camera

A filter does not appear

Confirm that:

  • the path points to an existing .nosmai file
  • the apply completion reports success
  • the package was built for the installed SDK version
  • the license includes the required feature

The camera does not return after navigation

Stop capture and detach the old preview when leaving. Attach the new preview and start capture when returning. Do not keep an old hidden preview view attached.

Performance drops

Test one feature at a time. Background replacement, face-tracked makeup, recording, and streaming each add work. Use 30 FPS as the starting target and avoid stacking features that are not visible.

Nosmai

We make advanced camera and AI technology accessible to every developer. By packaging hard problems into simple

developers
legal
newsletter

Product updates and release notes. No spam.

© 2026 nosmai, inc · all rights reserved