Skip to main content

iOS and macOS

By the end of this page your app shows the editor on a video, lets users add their own media through your picker, and exports the result.

Requirements​

  • iOS 16 or later, or macOS 13 or later.
  • UIKit or AppKit. SwiftUI works through a small wrapper (below).

Install​

The SDK has two modules: Nitrakit (projects, export, license, languages) and NitrakitUI (the views).

Beta access
During the beta, the SDK is shared with teams that have an invite, together with install instructions. Ask for access if you don't have it yet.

If users pick from their photo library, add NSPhotoLibraryUsageDescription to your Info.plist.

1. Install your license key Stable​

The key unlocks full-length, watermark-free exports for your bundle ID. Install it once, early, for example in your app delegate. Without a key everything still works, so you can skip this while trying the SDK.

import Nitrakit

let status = setLicense(key: "nk1.…")
if !status.problems.isEmpty { print("Nitrakit:", status.problems) }

See License key.

2. Create a project Stable​

An Editor holds one project and its undo history. Create it with your output size and frame rate, then add media: adding a file makes it a source; placing the source on a track makes it a clip the user sees in the timeline.

let editor = Editor(width: 1080, height: 1920, fpsNum: 30, fpsDen: 1)

let media = try editor.addMedia(id: assetId, file: pickedFileURL)
let track = try editor.apply(edit: .addTrack(kind: .visual, magnetic: true))
_ = try editor.apply(edit: .insertMedia(
track: track, source: media.source,
startUs: 0, sourceInUs: 0, durationUs: media.durationUs))

assetId is your app's id for the file, for example its key in your storage: the project refers to media only by these ids. The SDK keeps its own copy of the file, so editing starts right away. Times are microseconds.

3. Tell the SDK where media lives Stable​

When a project is opened on a device that doesn't have its media yet, the SDK asks your app where each media id lives. Return a URL (it can be signed) and the SDK downloads it, showing the progress on the clips:

NitrakitMedia.resolve = { id in
.remote(try await api.signedURL(for: id)) // or .file(localURL)
}

Set it once, at start-up. Skip it if your projects never leave the device. See Projects and media.

4. Show the editor Stable​

Two views make up the editor: NitrakitPreviewView plays the project and lets the user move and resize layers; NitrakitTimelineView has the timeline, tools and panels. Give both the same editor; they stay in sync with each other and with your code.

import NitrakitUI

let preview = NitrakitPreviewView(frame: .zero)
preview.editor = editor

let dock = NitrakitTimelineView(frame: .zero)
dock.editor = editor

Lay them out like any view: typically the preview on top and the editor (about 400 pt tall) below. Each starts drawing once it has a size. Create them in code; they don't load from storyboards.

SwiftUI: wrap each view in a UIViewRepresentable:

struct EditorPreview: UIViewRepresentable {
let editor: Editor
func makeUIView(context: Context) -> NitrakitPreviewView {
let view = NitrakitPreviewView(frame: .zero)
view.editor = editor
return view
}
func updateUIView(_ view: NitrakitPreviewView, context: Context) {}
}

5. Connect your pickers Stable​

The editor never opens the photo library or a file browser itself. When the user taps Add media, picks a font or a colour, it asks your app through onRequest, so these screens look like the rest of your app and media can come from wherever you keep it.

Return the answer from your picker, .cancel if the user backed out, or nil to let the SDK show its built-in picker:

dock.onRequest = { request in
switch request.kind {
case .addMedia, .addOverlay, .addAudio, .addSticker:
guard let file = await presentMediaPicker() else { return .cancel } // your picker
let id = startUpload(file) // your storage
let media = try? editor.addMedia(id: id, file: file)
return media.map { .source(id: $0.source) } ?? .cancel
default:
return nil // built-in picker
}
}

The closure is async and runs on the main actor, so you can present a picker and await its result. The built-in pickers cover fonts, colours, text, text styles and filters on iOS; adding media always needs yours. On macOS there are no built-in pickers.

See Your pickers for every request and its answer.

6. Save the project Stable​

Projects save as JSON, with media referred to by your ids. Store it on the device or sync it to your backend, and reopen it anywhere:

let json = editor.toJson()

let reopened = try Editor(open: json)
reopened.loadMedia() // from the device, else through NitrakitMedia.resolve

To save as the user works, set a listener; it's called after every change. While your app uploads a file, show the progress on its clips with editor.setMediaProgress(id:fraction:).

7. Export Stable​

Export renders the project to an MP4 in the background while the user keeps editing. Progress and the result arrive on the export thread:

final class Progress: ExportListener {
func progress(fraction: Float, frames: UInt64, total: UInt64) {
DispatchQueue.main.async { /* update your progress bar */ }
}
func finished(result: ExportResult, message: String) {
// .done: the file is at your path. .cancelled or .failed: no file (message says why).
}
}

let job = editor.export(path: outputPath,
settings: editor.exportSettingsFor(shortSide: 1080),
listener: Progress())
// job.cancel()

See Export.

Playback​

Control playback from your own buttons through the preview's player:

preview.player?.play()
preview.player?.pause()
preview.player?.seek(timeUs: 2_000_000)
preview.onPosition = { us in /* main thread */ }

The preview pauses itself when it leaves the window.

Good to know​

  • Errors: calls that can fail throw EditorError, e.g. .collision (a clip would overlap another) or .media(message:) (a file can't be read).
  • Name clashes: the SDK's functions (setLicense, setLocale…) are free functions. Inside a type with methods of the same name, write Nitrakit.setLicense(key:).

Next​