Skip to main content
Version: 2.0.x

Quick Start using SwiftUI - iOS

VideoSDK enables the opportunity to integrate video & audio calling to Web, Android, iOS applications. It provides Programmable SDKs and REST APIs to build scalable video conferencing applications.

This guide will get you running with the VideoSDK video & audio calling in minutes.

Prerequisites

  • iOS 11.0+
  • Xcode 12.0+
  • Swift 5.0+

App Architecture

This App will contain two screen :

  1. Join Screen : This screen allows user to either create meeting or join predefined meeting.

  2. Meeting Screen : This screen basically contain local and remote participant view and some meeting controls such as Enable / Disable Mic & Camera, Screen Sharing and Leave meeting.

Getting Started With the Code!

Create App

Step 1: Create a new application by selecting Create a new Xcode project

Step 2: Choose App then click Next

Step 3: Add Product Name and Save the project.

VideoSDK Installation

To install VideoSDK, you must initialise the pod on the project by running the following command.

pod init

It will create the Podfile in your project folder, open that file and add the dependency for the VideoSDK like below:

pod 'VideoSDKRTC', :git => 'https://github.com/videosdk-live/videosdk-rtc-ios-sdk.git'

then run the below code to install the pod:

pod install

then declare the permissions in Info.plist :

<key>NSCameraUsageDescription</key>
<string>Camera permission description</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone permission description</string>

Project Structure

iOSQuickStartDemo
├── iOSQuickStartDemo.swift // Default
├── Screens
├── JoinScreenView
└── JoinScreenView.swift
├── MeetingView
└── MeetingView.swift
└── MeetingViewController.swift
├── RoomStruct.swift
└── Info.plist // Default
Pods
└── Podfile

Create models

Create swift file for RoomStruct class model for setting data in object pattern.

RoomStruct.swift
struct RoomsStruct: Codable {
let createdAt, updatedAt, roomID: String?
let links: Links?
let id: String?

enum CodingKeys: String, CodingKey {
case createdAt, updatedAt
case roomID = "roomId"
case links, id
}
}

struct Links: Codable {
let getRoom, getSession: String?

enum CodingKeys: String, CodingKey {
case getRoom = "get_room"
case getSession = "get_session"
}
}

Step 1 : Create JoinScreen View

We will create a joining screen that will act as our main screen. Create a new Swift UI file and paste the following code.

JoinScreenView.swift
import SwiftUI

struct JoinScreenView: View {

// State variables for
@State var meetingId: String
@State var name: String

var body: some View {

NavigationView {

VStack {

Text("VideoSDK")
.font(.largeTitle)
.fontWeight(.bold)
Text("Swift UI Quickstart")
.font(.largeTitle)
.fontWeight(.semibold)
.padding(.bottom)

TextField("Enter MeetingId", text: $meetingId)
.foregroundColor(Color.black)
.autocorrectionDisabled()
.font(.headline)
.overlay(
Image(systemName: "xmark.circle.fill")
.padding()
.offset(x: 10)
.foregroundColor(Color.gray)
.opacity(meetingId.isEmpty ? 0.0 : 1.0)
.onTapGesture {
UIApplication.shared.endEditing()
meetingId = ""
}
, alignment: .trailing)
.padding()
.background(
RoundedRectangle(cornerRadius: 25)
.fill(Color.secondary.opacity(0.5))
.shadow(color: Color.gray.opacity(0.10), radius: 10))
.padding(.leading)
.padding(.trailing)

Text("Enter Meeting Id to join an existing meeting")

TextField("Enter Your Name", text: $name)
.foregroundColor(Color.black)
.autocorrectionDisabled()
.font(.headline)
.overlay(
Image(systemName: "xmark.circle.fill")
.padding()
.offset(x: 10)
.foregroundColor(Color.gray)
.opacity(name.isEmpty ? 0.0 : 1.0)
.onTapGesture {
UIApplication.shared.endEditing()
name = ""
}
, alignment: .trailing)
.padding()
.background(
RoundedRectangle(cornerRadius: 25)
.fill(Color.secondary.opacity(0.5))
.shadow(color: Color.gray.opacity(0.10), radius: 10))
.padding()

if meetingId.isEmpty == false {
NavigationLink(destination:{
if meetingId.isEmpty == false {
MeetingView(meetingId: self.meetingId, userName: name ?? "Guest")
.navigationBarBackButtonHidden(true)
} else {
}
}) {
Text("Join Meeting")
.foregroundColor(Color.white)
.padding()
.background(
RoundedRectangle(cornerRadius: 25.0)
.fill(Color.blue)) }
}

NavigationLink(destination: MeetingView(userName: name ?? "Guest")
.navigationBarBackButtonHidden(true)) {
Text("Start Meeting")
.foregroundColor(Color.white)
.padding()
.background(
RoundedRectangle(cornerRadius: 25.0)
.fill(Color.blue)) }
}
}
}
}

// preview to show screen into your canvas
#Preview {
JoinScreenView(meetingId: "", name: "")
}

extension UIApplication {

func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}

Also add the JoinScreenView in main app as shown below

iOSQuickStartDemo.swift
import SwiftUI

@main
struct iOSQuickStartDemo: App {
var body: some Scene {
WindowGroup {
JoinScreenView(meetingId: "", name: "")
}
}
}

Step 2 : Create Meeting View

Create a view for the meeting screen.

MeetingView will implement the main meeting screen and the control buttons for the local participant.

MeetingView.swift
import SwiftUI
import VideoSDKRTC
import WebRTC

struct MeetingView: View{

@Environment(\.presentationMode) var presentationMode

// instance of MeetingViewController which we will implement in next step
@ObservedObject var meetingViewController = MeetingViewController()
// Variables for keeping the state of various controls
@State var meetingId: String?
@State var userName: String?
@State var isUnMute: Bool = true
@State var camEnabled: Bool = true
@State var isScreenShare: Bool = false

var body: some View {

VStack {
if meetingViewController.participants.count == 0 {
Text("Meeting Initializing")
} else {
VStack {
VStack(spacing: 20) {
Text("Meeting ID: \(meetingViewController.meetingID)")
.padding(.vertical)

List {
ForEach(meetingViewController.participants.indices, id: \.self) { index in
Text("Participant Name: \(meetingViewController.participants[index].displayName)")
ZStack {
ParticipantView(track: meetingViewController.participants[index].streams.first(where: { $1.kind == .state(value: .video) })?.value.track as? RTCVideoTrack).frame(height: 250)
if meetingViewController.participants[index].streams.first(where: { $1.kind == .state(value: .video) }) == nil {
Color.white.opacity(1.0).frame(width: UIScreen.main.bounds.width, height: 250)
Text("No media")
}
}
}
}
}

VStack {
HStack(spacing: 15) {
// mic button
Button {
if isUnMute {
isUnMute = false
meetingViewController.meeting?.muteMic()
}
else {
isUnMute = true
meetingViewController.meeting?.unmuteMic()
}
} label: {
Text("Toggle Mic")
.foregroundStyle(Color.white)
.font(.caption)
.padding()
.background(
RoundedRectangle(cornerRadius: 25)
.fill(Color.blue))
}
// camera button
Button {
if camEnabled {
camEnabled = false
meetingViewController.meeting?.disableWebcam()
}
else {
camEnabled = true
meetingViewController.meeting?.enableWebcam()
}
} label: {
Text("Toggle WebCam")
.foregroundStyle(Color.white)
.font(.caption)
.padding()
.background(
RoundedRectangle(cornerRadius: 25)
.fill(Color.blue))
}
}
HStack{
// screenshare button (OPTIONAL)
Button {
if isScreenShare {
isScreenShare = false
Task {
await meetingViewController.meeting?.disableScreenShare()
}
}
else {
isScreenShare = true
Task {
await meetingViewController.meeting?.enableScreenShare()
}
}
} label: {
Text("Toggle ScreenShare")
.foregroundStyle(Color.white)
.font(.caption)
.padding()
.background(
RoundedRectangle(cornerRadius: 25)
.fill(Color.blue))
}

// end meeting button
Button {
meetingViewController.meeting?.end()
presentationMode.wrappedValue.dismiss()
} label: {
Text("End Call")
.foregroundStyle(Color.white)
.font(.caption)
.padding()
.background(
RoundedRectangle(cornerRadius: 25)
.fill(Color.red))
}
}
.padding(.bottom)
}
}
}
}.onAppear() {
/// MARK :- configuring the videoSDK
VideoSDK.config(token: meetingViewController.token)
if meetingId?.isEmpty == false {
// join an existing meeting with provided meeting Id
meetingViewController.joinMeeting(meetingId: meetingId!, userName: userName!)
}
else {
// create a new meeting
meetingViewController.joinRoom(userName: userName!)
}
}
}
}

/// VideoView for participant's video
class VideoView: UIView {

var videoView: RTCMTLVideoView = {
let view = RTCMTLVideoView()
view.videoContentMode = .scaleAspectFill
view.backgroundColor = UIColor.black
view.clipsToBounds = true
view.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 250)

return view
}()

init(track: RTCVideoTrack?) {
super.init(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 250))
backgroundColor = .clear
DispatchQueue.main.async {
self.addSubview(self.videoView)
self.bringSubviewToFront(self.videoView)
track?.add(self.videoView)
}
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

/// ParticipantView for showing and hiding VideoView
struct ParticipantView: UIViewRepresentable {
var track: RTCVideoTrack?

func makeUIView(context: Context) -> VideoView {
let view = VideoView(track: track)
view.frame = CGRect(x: 0, y: 0, width: 250, height: 250)
return view
}

func updateUIView(_ uiView: VideoView, context: Context) {
print("ui updated")
if track != nil {
track?.add(uiView.videoView)
} else {
track?.remove(uiView.videoView)
}
}
}

Step 3: Implement MeetingViewController

Here, we will write the code for initialising meeting and controls for the same. You will require Auth token, you can generate it using either using videosdk-server-api-example or generate it from the Video SDK Dashboard for developer.

MeetingViewController will implement various event listeners such as MeetingEventListener, ParticipantEventListener.

MeetingViewController.swift
import Foundation
import VideoSDKRTC
import WebRTC

class MeetingViewController: ObservableObject {

var token = "Your_Token"
var meetingId: String = ""
var name: String = ""

@Published var meeting: Meeting? = nil
@Published var localParticipantView: VideoView? = nil
@Published var videoTrack: RTCVideoTrack?
@Published var participants: [Participant] = []
@Published var meetingID: String = ""

func initializeMeeting(meetingId: String, userName: String) {
meeting = VideoSDK.initMeeting(
meetingId: meetingId,
participantName: userName,
micEnabled: true,
webcamEnabled: true
)

meeting?.addEventListener(self)
meeting?.join(cameraPosition: .front)
}
}

extension MeetingViewController: MeetingEventListener {

func onMeetingJoined() {

guard let localParticipant = self.meeting?.localParticipant else { return }

// add to list
participants.append(localParticipant)

// add event listener
localParticipant.addEventListener(self)

localParticipant.setQuality(.high)
}

func onParticipantJoined(_ participant: Participant) {

participants.append(participant)

// add listener
participant.addEventListener(self)

participant.setQuality(.high)
}

func onParticipantLeft(_ participant: Participant) {
participants = participants.filter({ $0.id != participant.id })
}

func onMeetingLeft() {

meeting?.localParticipant.removeEventListener(self)
meeting?.removeEventListener(self)
}

func onMeetingStateChanged(meetingState: MeetingState) {
switch meetingState {

case .CLOSED:
participants.removeAll()

default:
print("")
}
}
}

extension MeetingViewController: ParticipantEventListener {
func onStreamEnabled(_ stream: MediaStream, forParticipant participant: Participant) {

if participant.isLocal {
if let track = stream.track as? RTCVideoTrack {
DispatchQueue.main.async {
self.videoTrack = track
}
}
} else {
if let track = stream.track as? RTCVideoTrack {
DispatchQueue.main.async {
self.videoTrack = track
}
}
}
}

func onStreamDisabled(_ stream: MediaStream, forParticipant participant: Participant) {

if participant.isLocal {
if let _ = stream.track as? RTCVideoTrack {
DispatchQueue.main.async {
self.videoTrack = nil
}
}
} else {
self.videoTrack = nil
}
}
}

extension MeetingViewController {
// create a new meeting id
func joinRoom(userName: String) {

let urlString = "https://api.videosdk.live/v2/rooms"
let session = URLSession.shared
let url = URL(string: urlString)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue(self.token, forHTTPHeaderField: "Authorization")

session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) in

if let data = data, let utf8Text = String(data: data, encoding: .utf8)
{
print("UTF =>=>\(utf8Text)") // original server data as UTF8 string
do{
let dataArray = try JSONDecoder().decode(RoomsStruct.self,from: data)
DispatchQueue.main.async {
print(dataArray.roomID)
self.meetingID = dataArray.roomID!
self.joinMeeting(meetingId: dataArray.roomID!, userName: userName)
}
print(dataArray)
} catch {
print(error)
}
}
}
).resume()
}

// initialise a meeting with give meeting id (either new or existing)
func joinMeeting(meetingId: String, userName: String) {

if !token.isEmpty {
// use provided token for the meeting
self.meetingID = meetingId
self.initializeMeeting(meetingId: meetingId, userName: userName)
}
else {
print("Auth token required")
}
}
}

Step 4: Adding Broadcast Extension (Optional)

To make your app capable of sharing screen, you will need to add broadcast upload extension into you app. Head over to the ScreenShare guide and follow the given steps to add the screen sharing capability in your app.

Output

tip

Stuck anywhere? Check out this example code on GitHub

Got a Question? Ask us on discord