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
.nosmaifilters - 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
| Requirement | Value |
|---|---|
| Minimum iOS version | iOS 15.0 |
| Supported architecture | arm64 |
| Camera framework | AVFoundation |
| Privacy manifest | Included in nosmai.framework |
| Recommended camera frame rate | 30 FPS |
| Test environment | Physical 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:
- Unzip
nosmai.framework.zip. - Drag
nosmai.frameworkinto the Xcode project. - Select the application target and open General.
- Add the framework under Frameworks, Libraries, and Embedded Content.
- Set it to Embed & Sign.
- 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>
| Permission | Required when |
|---|---|
| Camera | Showing a camera preview or capturing a photo |
| Microphone | Recording or streaming with audio |
| Photo library add access | Saving 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:
NosmaiFilterTypeFilterNosmaiFilterTypeEffectNosmaiFilterTypeBeautyEffectNosmaiFilterTypeBackground
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:
effectsfilterbgbeauty_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:
- Use a license key issued for the final bundle identifier.
- Test first launch with network access.
- Test front and back camera switching.
- Test leaving and returning to the camera screen.
- Test background and foreground transitions.
- Test camera and microphone permission denial.
- Test local and cloud filter failure handling.
- Test recording while beauty or background effects are active.
- Test on the oldest and slowest supported physical device.
- 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 beforestartCapture- no other camera session is using the device camera
A filter does not appear
Confirm that:
- the path points to an existing
.nosmaifile - 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.