iOS video call SDK
Add a video call to an iOS app
MeetoWidget is a Swift Package that shows a Meeto video call inside your iOS app. It is a thin wrapper: a WKWebView that loads Meeto's hosted call widget, plus a native bridge for events and commands. You get the same lobby, video grid and chat as on the web, a SwiftUI view and a UIKit view. It builds for iOS 14, needs iOS 14.3 or newer for camera and microphone, and uses a public mk_live_… key.
On this page
How does the iOS SDK work?
The SDK wraps the hosted Meeto widget in a web view. MeetoWidgetView loads https://meeto.me/embed with your key in a WKWebView, sends commands into the page and turns the page's messages into Swift events. Every call is encrypted in transit (WebRTC, DTLS-SRTP) through Meeto's media servers in Europe and South-East Asia.
The upside is size and upkeep: no WebRTC framework in your binary, and calls improve when the hosted widget does. People in your app get the same lobby, video grid and chat as on the web and see the screens others share in the call. To simply join a meeting, they can also open the browser link or the Meeto app.
Install the Swift Package
Add the package by its repository URL in Xcode, or declare it in Package.swift. The current tag is 0.1.2, and the library product is MeetoWidget.
// Xcode → File → Add Package Dependencies…
// https://github.com/meeto-app/meeto-widget-ios.git
// or in Package.swift:
dependencies: [
.package(url: "https://github.com/meeto-app/meeto-widget-ios.git", from: "0.1.2")
]Set up the key and permissions
A native app has no web origin, so the SDK presents itself as app:// plus your bundle identifier. In Settings → Website widget, create a key and add that origin, for example app://com.example.myapp. Without it the widget reports an error instead of loading the call.
Add both usage descriptions to Info.plist. iOS terminates an app that touches the camera or microphone without them.
<key>NSCameraUsageDescription</key>
<string>The camera is used for video calls.</string>
<key>NSMicrophoneUsageDescription</key>
<string>The microphone is used for video calls.</string>An app origin is declared by the SDK rather than checked by a browser, so treat the mk_live_… key as public, as it is on the web. The server key mk_secret_… never belongs in an app.
Show a call in SwiftUI
MeetoWidget is a SwiftUI view that takes a configuration and an event closure. Give it a frame; the call fills it.
import SwiftUI
import MeetoWidget
struct SupportCallView: View {
@Binding var isPresented: Bool
var body: some View {
MeetoWidget(config: .init(key: "mk_live_…", roomName: "support", userName: "Anna")) { event in
switch event.type {
case "joined":
print("joined room", event.payload["room"] as? String ?? "")
case "left":
isPresented = false
case "error":
print("Meeto error", event.payload["code"] as? String ?? "")
default:
break
}
}
.frame(maxWidth: .infinity, minHeight: 520)
}
}The configuration is fixed when the view is created. To switch rooms or keys, give the view a new identity with .id(roomCode) so SwiftUI builds a fresh one. For join, leave or mute buttons, use MeetoWidgetView below, which takes commands, or wrap it in your own UIViewRepresentable.
| Field | Purpose |
|---|---|
| key | Required public key mk_live_… |
| roomName | Persistent named room shared by everyone using it, such as support |
| room | Code of an existing open room, such as the slug POST /api/embed/rooms returns to your server |
| userName / lang / accent | Prefilled display name, interface language and hex accent color |
| autoJoin | Skip the lobby in open-door rooms; default false |
| baseURL | The Meeto address; defaults to https://meeto.me |
Use UIKit and send commands
MeetoWidgetView is a UIView that starts loading as soon as you create it. Pin it with constraints and keep a reference for commands.
import UIKit
import MeetoWidget
final class CallViewController: UIViewController {
private var widget: MeetoWidgetView!
override func viewDidLoad() {
super.viewDidLoad()
// An open room your server created with POST /api/embed/rooms
widget = MeetoWidgetView(config: .init(key: "mk_live_…", room: "misty-fox-a18f493cb027fe823cb6", userName: "Anna"))
widget.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(widget)
NSLayoutConstraint.activate([
widget.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
widget.bottomAnchor.constraint(equalTo: view.bottomAnchor),
widget.leadingAnchor.constraint(equalTo: view.leadingAnchor),
widget.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
widget.onEvent = { [weak self] event in
if event.type == "left" { self?.dismiss(animated: true) }
}
}
func muteMe() { widget.setMicrophone(false) }
func leaveCall() { widget.leave() }
}| Name | Details |
|---|---|
| ready / phase | The widget loaded; phase reports prejoin, room, finished or error |
| joined / left | payload has room and identity; left has reason and durationSec |
| participants / error | payload has count; error has code and message |
| join(name:) / leave() | Join from the lobby of an open-door room, or leave the call |
| setMicrophone(_:) / setCamera(_:) | Turn the microphone or camera on or off during the call |
Events arrive on the main thread as MeetoEvent values: a type string and the full payload dictionary of the widget protocol, the same events the JavaScript SDK emits. Commands can be called from any thread.
Production checklist and plan limits
Register the bundle ID
Each app target with its own bundle identifier, including a debug variant, needs its own app:// origin on the key.
Ask for permission first
Show your own explanation before the system camera prompt. If the person declines, the web view gets no camera or microphone.
Test on a device
The Simulator has no real camera. Test a second participant from a browser on another device.
| Plan | Limit |
|---|---|
| Free | Up to 10 participants and 60-minute calls once two people are in |
| Pro | Up to 25 participants; no time limit for up to 2 simultaneous meetings per account |
| Every plan | The SDK is free; the Powered by Meeto badge appears in the call |
Limits follow the plan of the account that owns the key. Recording and AI summaries are started by a Pro or Team host; see pricing.
Frequently asked questions
How does MeetoWidget run the call?
In a WKWebView around Meeto's hosted call page, with a Swift bridge for events and commands. Media runs through WebKit's WebRTC and Meeto's media servers, so you do not ship or update a video engine yourself.
Which iOS versions are supported?
The package builds for iOS 14 and later, and camera and microphone inside a WKWebView work from iOS 14.3. From iOS 15 the SDK approves the web view's camera and microphone request itself, so people see only the system permission prompt.
How do I put two app users into the same call?
Give both the same roomName, or create an open room on your server with POST /api/embed/rooms and the secret key, and pass its code to both apps as room. A roomName room is shared by everyone who uses that name with your key, so pick names that are not guessable for private calls.
Try it on your next call
Start a meeting and send the link. Guests join from the browser.
- 60-minute meetings on the free plan
- Guests join without an account
Put it to work
Open your Meeto account to set it up, or explore the working product first.