This tutorial describes how to add live video chat to your iOS applications using Swift and the Agora Video SDK.
With this sample app, you can:
- Join/leave a channel
- Set user role as a broadcaster or audience member
- Mute/unmute audio
- Switch camera views
- Set up the resolution, the frame rate, and the bit rate display
-
Xcode 8.0+
-
Physical iOS device (iPhone or iPad)
Note: Use a physical device to run the sample. Some simulators lack the functionality or the performance needed to run the sample.
This section shows you how to prepare, build, and run the sample application.
To build and run the sample application, you must obtain an app ID:
- Create a developer account at agora.io. Once you finish the sign-up process, you are redirected to the dashboard.
- Navigate in the dashboard tree on the left to Projects > Project List.
- Copy the app ID that you obtained from the dashboard into a text file. You will use this when you launch the app.
-
Open
OpenLive.xcodeproj
and edit theKeyCenter.swift
file. In theagoraKit
declaration, update<#Your App Id#>
with your app ID.static let AppId: String = <#Your App Id#>
-
Download the Agora Video SDK. Unzip the downloaded SDK package and copy the
libs/AograRtcEngineKit.framework
file from the SDK folder into the sample application'sOpenLive
folder. -
Connect your iPhone or iPad device and run the project. Ensure a valid provisioning profile is applied, or your project will not run.
- Set Permissions and Add Frameworks and Libraries
- Design the User Interface
- Create the MainViewController Class
- Create the MainViewController Class Delegates
- Create the RoomViewController
- Create RoomViewController Agora Methods and Delegates
- Create the ChatMessageViewController
- Create the SettingsViewController
Under the Capabilities tab, enable Audio, AirPlay, and Picture in Picture mode.
Open the info.plist
file. Enable the camera and microphone privacy settings for the application. Ensure background mode enables AirPlay to play audio/video.
Under the Build Phases tab, add the following frameworks and libraries to your project:
libresolv.tbd
libc++.tbd
AgoraRtcEngineKit.framework
SystemConfiguration.framework
CoreTelephony.framework
VideoToolbox.framework
AudioToolbox.framework
CoreMotion.framework
CoreMedia.framework
AVFoundation.framework
Accelerate.framework
- Add Assets
- Create the MainViewController UI
- Create the LiveRoomViewController UI
- Create the SettingsViewController UI
Add the following assets to Assets.xcassets
.
Note: Use Xcode to import assets to Assets.xcassets
. PDF files are used for these assets, which contain images for each iOS screen resolution.
Asset | Description |
---|---|
btn_close |
An image of an X to close a window |
btn_join and btn_join_cancel |
Images of a camera to join/leave a call |
btn_mute and btn_mute_cancel |
Images of a microphone to mute/unmute audio |
btn_overturn |
An image of a camera and rotational arrows to switch between the two cameras |
btn_setting |
An image of a cog to open the settings window |
Create the layout for the MainViewController
.
Note: This layout includes a navigation segue
to move from screen to screen.
Create the layout for the LiveRoomViewController
.
Note: The LiveRoomViewController
layout includes tap recognizer for handling user interaction.
Create the layout for the SettingsViewController
.
MainViewController.swift defines and connects application functionality with the MainViewController UI.
- Define Global Variables
- Override the prepare() Segue Method
- Create the MainViewController Extensions
- Create MainViewController Delegates
The MainViewController
class has three IBOutlet
variables. These map to the MainViewController UI elements.
Variable | Description |
---|---|
roomNameTextField |
Maps to the UITextField in the MainViewController layout |
popoverSourceView |
Maps to the placeholder UIView in the MainViewController layout |
import UIKit
import AgoraRtcEngineKit
class MainViewController: UIViewController {
@IBOutlet weak var roomNameTextField: UITextField!
@IBOutlet weak var popoverSourceView: UIView!
...
}
The MainViewController
class has one private variable. The videoProfile
variable is initialized with the Agora 640 x 360 video profile using AgoraVideoDimension640x360
.
fileprivate var videoProfile = AgoraVideoDimension640x360
Override the prepare()
segue method to manage the application navigation.
If the segueId
is mainToSettings
, prepare the settings view through the segue destination SettingsViewController
:
- Set
settingsVC.videoProfile
to the currentvideoProfile
. - Set
settingsVC.delegate
toself
.
If the segueId
is mainToLive
, prepare the room view through the segue destination LiveRoomViewController
:
- Set
roomVC.roomName
toroomNameTextField.text
. - Set
roomVC.videoProfile
to the currentvideoProfile
. - Set
liveVC.clientRole
torole
.role
is retrieved by convertingsender
into anAgoraClientRole
. - Set
roomVC.delegate
toself
.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let segueId = segue.identifier else {
return
}
switch segueId {
case "mainToSettings":
let settingsVC = segue.destination as! SettingsViewController
settingsVC.videoProfile = videoProfile
settingsVC.delegate = self
case "mainToLive":
let liveVC = segue.destination as! LiveRoomViewController
liveVC.roomName = roomNameTextField.text!
liveVC.videoProfile = videoProfile
if let value = sender as? NSNumber, let role = AgoraClientRole(rawValue: value.intValue) {
liveVC.clientRole = role
}
liveVC.delegate = self
default:
break
}
}
The showRoleSelection()
method is an extension method to display the role selection window.
Create a UIAlertController
object and three UIAlertAction
objects broadcaster
, audience
, and cancel
.
Apply the three UIAlertAction
objects to sheet
using addAction()
and set the following properties for sheet.popoverPresentationController?
:
sourceView
aspopoverSourceView
, which is the placeholderUIView
created in MainViewController UI.permittedArrowDirections
as.up
to indicate the window should animate from the bottom to the top of the screen.
Display sheet
using the present()
method. The window will automatically listen for the following user actions.
- If the user chooses the
broadcaster
action, invokeself?.join()
with the broadcaster role. - If the user chooses the
audience
action, invokeself?.join()
with the audience member role. - If the user chooses the
cancel
action, close the role chooser window.
private extension MainViewController {
func showRoleSelection() {
let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let broadcaster = UIAlertAction(title: "Broadcaster", style: .default) { [weak self] _ in
self?.join(withRole: .broadcaster)
}
let audience = UIAlertAction(title: "Audience", style: .default) { [weak self] _ in
self?.join(withRole: .audience)
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
sheet.addAction(broadcaster)
sheet.addAction(audience)
sheet.addAction(cancel)
sheet.popoverPresentationController?.sourceView = popoverSourceView
sheet.popoverPresentationController?.permittedArrowDirections = .up
present(sheet, animated: true, completion: nil)
}
}
The join()
method is an extension method to begin joining the channel with a specific role
.
This method navigates from the main screen to the room screen using the performSegue()
method.
private extension MainViewController {
func join(withRole role: AgoraClientRole) {
performSegue(withIdentifier: "mainToLive", sender: NSNumber(value: role.rawValue as Int))
}
}
The settingsVC()
method is a delegate method for the SettingsViewController
. This method is invoked when the video profile for the SettingsViewController
changes. It updates the videoProfile
, and dismisses the view using dismiss()
.
extension MainViewController: SettingsVCDelegate {
func settingsVC(_ settingsVC: SettingsViewController, didSelectProfile profile: CGSize) {
videoProfile = profile
dismiss(animated: true, completion: nil)
}
}
The liveVCNeedClose()
method is a delegate method for the LiveRoomVCDelegate
. This method is invoked when the user leaves the room, and reviews the view using navigationController?.popViewController()
.
extension MainViewController: LiveRoomVCDelegate {
func liveVCNeedClose(_ liveVC: LiveRoomViewController) {
let _ = navigationController?.popViewController(animated: true)
}
}
The textFieldShouldReturn()
method is a delegate method for the UITextField
objects in MainViewController
. This method is invoked when the user presses the keyboard return.
If the current text field is textField
and the field is not empty, show the role selection UI using showRoleSelection()
.
extension MainViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if let string = textField.text , !string.isEmpty {
showRoleSelection()
}
return true
}
}
LiveRoomViewController.swift defines and connects application functionality with the LiveRoomViewController UI.
- Define the LiveRoomVCDelegate Protocol and IBOutlet Variables
- Define Public Variables
- Define Private Variables
- Create Superclass and IBAction Methods
- Create Private User Interface Methods
- Create Private Interface Update and Session Methods
The liveVCNeedClose()
method is used for communication between the LiveRoomViewController
class and its delegate. The method informs the delegate to close the room.
import UIKit
import AgoraRtcEngineKit
protocol LiveRoomVCDelegate: NSObjectProtocol {
func liveVCNeedClose(_ liveVC: LiveRoomViewController)
}
The LiveRoomViewController
class contains IBOutlet
variables to manage buttons, view containers, and handle other UI elements. The variables map to the LiveRoomViewController UI elements.
Variable | Description |
---|---|
roomNameLabel |
Label for the room name in the header of the layout |
remoteContainerView |
Container for the videos in the room |
broadcastButton |
Button for broadcasting |
sessionButtons |
Array of buttons for session management |
audioMuteButton |
Button to mute/unmute the audio |
class LiveRoomViewController: UIViewController {
@IBOutlet weak var roomNameLabel: UILabel!
@IBOutlet weak var remoteContainerView: UIView!
@IBOutlet weak var broadcastButton: UIButton!
@IBOutlet var sessionButtons: [UIButton]!
@IBOutlet weak var audioMuteButton: UIButton!
...
}
The LiveRoomViewController
class has six public variables. These variables manage the LiveRoomViewController
settings.
Variable | Description |
---|---|
roomName |
The name of the room |
clientRole |
The role of the user. Default value is set to audience member. When this variable is set, it invokes the updateButtonsVisiablity() method. |
videoProfile |
The video profile for the room |
delegate |
The delegate for the LiveRoomViewController class |
AgoraRtcEngineKit |
The Agora RTC Engine SDK object |
var roomName: String!
var clientRole = AgoraClientRole.audience {
didSet {
updateButtonsVisiablity()
}
}
var videoProfile: CGSize!
weak var delegate: LiveRoomVCDelegate?
//MARK: - engine & session view
var rtcEngine: AgoraRtcEngineKit!
The LiveRoomViewController
class has five private variables. These variables manage the LiveRoomViewController
settings.
The isBroadcaster
variable indicates if the user is a broadcaster or not.
fileprivate var isBroadcaster: Bool {
return clientRole == .broadcaster
}
The isMuted
variable indicates if the user is a muted or not. Initialized to a value of false
. When this variable is set, the rtcEngine?.muteLocalAudioStream()
method is invoked and the mute button image is updated using setImage()
.
fileprivate var isMuted = false {
didSet {
rtcEngine?.muteLocalAudioStream(isMuted)
audioMuteButton?.setImage(UIImage(named: isMuted ? "btn_mute_cancel" : "btn_mute"), for: .normal)
}
}
The videoSessions
variable is an srray of video sessions for the connected users. Initialized with an empty array of VideoSession
objects. When this variable is set, the updateInterface()
method is invoked if remoteContainerView
is not nil
.
fileprivate var videoSessions = [VideoSession]() {
didSet {
guard remoteContainerView != nil else {
return
}
updateInterface(withAnimation: true)
}
}
The fullSession
variable is the video session for the full view video. When this variable is set, the updateInterface()
method is invoked if the value has changed and remoteContainerView
is not nil
.
fileprivate var fullSession: VideoSession? {
didSet {
if fullSession != oldValue && remoteContainerView != nil {
updateInterface(withAnimation: true)
}
}
}
The viewLayouter
variable is a VideoViewLayouter
object for handling the layout for the videos.
fileprivate let viewLayouter = VideoViewLayouter()
The viewDidLoad()
method initializes the LiveRoomViewController
:
- Set the
roomNameLabel
text toroomName
. - Update the buttons for the view using
updateButtonsVisiablity()
. - Load the Agora RTC engine SDK using
loadAgoraKit()
.
override func viewDidLoad() {
super.viewDidLoad()
roomNameLabel.text = roomName
updateButtonsVisiablity()
loadAgoraKit()
}
The IBAction
methods map to the UI elements for the LiveRoomViewController
.
- Create the doSwitchCameraPressed() IBAction Method
- Create the doMutePressed() IBAction Method
- Create the doBroadcastPressed() IBAction Method
- Create the doDoubleTapped() IBAction Method
- Create the doLeavePressed() IBAction Method
The doSwitchCameraPressed()
method is invoked by the camera UI button action and switches the camera view using rtcEngine?.switchCamera()
.
//MARK: - user action
@IBAction func doSwitchCameraPressed(_ sender: UIButton) {
rtcEngine?.switchCamera()
}
The doMutePressed()
method is invoked by the audio UI button and updates isMuted
.
@IBAction func doMutePressed(_ sender: UIButton) {
isMuted = !isMuted
}
The doBroadcastPressed()
method is invoked by the broadcast UI button.
If the user is a broadcaster set clientRole
to .audience
and set fullSession
to nil
if fullSession?.uid
is equal to 0
. Otherwise, set clientRole
to .broadcaster
.
Set the new client role using rtcEngine.setClientRole()
and update the interface using updateInterface()
.
@IBAction func doBroadcastPressed(_ sender: UIButton) {
if isBroadcaster {
clientRole = .audience
if fullSession?.uid == 0 {
fullSession = nil
}
} else {
clientRole = .broadcaster
}
rtcEngine.setClientRole(clientRole)
updateInterface(withAnimation :true)
}
The doDoubleTapped()
method is invoked by the UITapGestureRecognizer
gesture recognizer created in the storyboard.
- If
fullSession
isnil
, detect the video session usingviewLayouter.responseSession()
, and setfullSession
to the selected video session. - If
fullSession
already exists, setfullSession
to nil.
@IBAction func doDoubleTapped(_ sender: UITapGestureRecognizer) {
if fullSession == nil {
if let tappedSession = viewLayouter.responseSession(of: sender, inSessions: videoSessions, inContainerView: remoteContainerView) {
fullSession = tappedSession
}
} else {
fullSession = nil
}
}
The doLeavePressed()
method is invoked by the close UI button action and invokes the leaveChannel()
method.
@IBAction func doLeavePressed(_ sender: UIButton) {
leaveChannel()
}
The private user interface methods for the LiveRoomViewController
are created as functions in a private extension.
private extension RoomViewController {
...
}
- Create the updateSelfViewVisiable() Method
- Create the leaveChannel() Method
- Create the setIdleTimerActive() Method
- Create the alert() Method
The updateSelfViewVisiable()
method sets the session buttons to hidden/not hidden.
Update the image for broadcastButton
using setImage()
.
For each button
in sessionButtons
, show the button if the user is a broadcaster. Otherwise, show the button.
private extension LiveRoomViewController {
func updateButtonsVisiablity() {
guard let sessionButtons = sessionButtons else {
return
}
broadcastButton?.setImage(UIImage(named: isBroadcaster ? "btn_join_cancel" : "btn_join"), for: UIControlState())
for button in sessionButtons {
button.isHidden = !isBroadcaster
}
}
The leaveChannel()
method enables the user to leave the video session.
- Set the idle timer to active using
setIdleTimerActive()
. - Clear the local video and leave the channel by applying
nil
as the parameter foragoraKit.setupLocalVideo()
andagoraKit.leaveChannel()
. - If the user is a broadcaster, stop the video preview using
rtcEngine.stopPreview()
. - Loop through
videoSessions
and remove itshostingView
from the superview usingremoveFromSuperview()
. - Clear the video sessions array using
videoSessions.removeAll()
. - Complete the method by invoking the room to close using
delegate?.roomVCNeedClose()
.
func leaveChannel() {
setIdleTimerActive(true)
rtcEngine.setupLocalVideo(nil)
rtcEngine.leaveChannel(nil)
if isBroadcaster {
rtcEngine.stopPreview()
}
for session in videoSessions {
session.hostingView.removeFromSuperview()
}
videoSessions.removeAll()
delegate?.liveVCNeedClose(self)
}
The setIdleTimerActive()
method updates the idle timer of the sample application to be either active or inactive using UIApplication.shared.isIdleTimerDisabled()
.
func setIdleTimerActive(_ active: Bool) {
UIApplication.shared.isIdleTimerDisabled = !active
}
The alert()
method appends an alert message to the chat message box.
Ensure string
is not empty before creating an UIAlertController
object with the provided string
and add an UIAlertAction
to alert
.
Display the alert using present()
.
func alert(string: String) {
guard !string.isEmpty else {
return
}
let alert = UIAlertController(title: nil, message: string, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
}
The interface update and session methods are contained within a private extension.
private extension LiveRoomViewController {
...
}
The updateInterface()
methods handle layout updates for the video session.
The updateInterface()
method with animation
checks if animation is used for the update, and animates the update within 0.3
seconds using UIView.animate()
.
func updateInterface(withAnimation animation: Bool) {
if animation {
UIView.animate(withDuration: 0.3, animations: { [weak self] _ in
self?.updateInterface()
self?.view.layoutIfNeeded()
})
} else {
updateInterface()
}
}
The updateInterface()
method without animation updates the layout for the video views.
-
If the user is not a broadcaster and
displaySessions
is empty, remove the first session usingdisplaySessions.removeFirst()
. -
Use
viewLayouter.layout()
to update the layout ofdisplaySessions
. -
Set the stream type for the sessions using
setStreamType()
.
func updateInterface() {
var displaySessions = videoSessions
if !isBroadcaster && !displaySessions.isEmpty {
displaySessions.removeFirst()
}
viewLayouter.layout(sessions: displaySessions, fullSession: fullSession, inContainer: remoteContainerView)
setStreamType(forSessions: displaySessions, fullSession: fullSession)
}
The setStreamType()
method sets the stream type for each session.
Set each session's remote video stream type using rtcEngine.setRemoteVideoStream
.
- If
fullSession
is valid, and the session is equal tofullSession
, set the type to.high
. Otherwise, set the stream type to.low
. - If
fullSession
is not valid, set all session video stream types to.high
.
func setStreamType(forSessions sessions: [VideoSession], fullSession: VideoSession?) {
if let fullSession = fullSession {
for session in sessions {
rtcEngine.setRemoteVideoStream(UInt(session.uid), type: (session == fullSession ? .high : .low))
}
} else {
for session in sessions {
rtcEngine.setRemoteVideoStream(UInt(session.uid), type: .high)
}
}
}
The addLocalSession()
method adds a new local session.
Create the session using VideoSession.localSession()
and appending it to videoSessions
using append()
.
Set up the local video for the session using rtcEngine.setupLocalVideo()
.
func addLocalSession() {
let localSession = VideoSession.localSession()
videoSessions.append(localSession)
rtcEngine.setupLocalVideo(localSession.canvas)
}
The fetchSession()
method returns the VideoSession
for a specified user. Loop through videoSessions
until the session.uid
matches the uid
.
func fetchSession(ofUid uid: Int64) -> VideoSession? {
for session in videoSessions {
if session.uid == uid {
return session
}
}
return nil
}
The videoSession()
method returns the VideoSession
for the user. The difference between this method and the fetchSession()
method is that if no fetchSession()
exists a new VideoSession
object is created and appended to videoSessions
.
func videoSession(ofUid uid: Int64) -> VideoSession {
if let fetchedSession = fetchSession(ofUid: uid) {
return fetchedSession
} else {
let newSession = VideoSession(uid: uid)
videoSessions.append(newSession)
return newSession
}
}
The methods for the Agora SDK are placed within extensions for LiveRoomViewController
.
- Create the loadAgoraKit Method
- Create the didJoinedOfUid Event Listener
- Create the firstLocalVideoFrameWith Event Listener
- Create the didOfflineOfUid Event Listener
The loadAgoraKit()
method initializes the Agora RTC engine using AgoraRtcEngineKit.sharedEngine()
.
Create an AgoraRtcEngineKit
shared engine object with KeyCenter.AppId
using AgoraRtcEngineKit.sharedEngine()
.
Apply the following settings for rtcEngine
:
- Set channel profile to live broadcasting using
rtcEngine.setChannelProfile()
. - Enable dual stream mode using
rtcEngine.enableDualStreamMode()
. - Enable video using
rtcEngine.enableVideo()
. - Set the video encoder configuration using
rtcEngine.setVideoEncoderConfiguration()
.
private extension LiveRoomViewController {
func loadAgoraKit() {
rtcEngine = AgoraRtcEngineKit.sharedEngine(withAppId: KeyCenter.AppId, delegate: self)
rtcEngine.setChannelProfile(.liveBroadcasting)
rtcEngine.enableDualStreamMode(true)
rtcEngine.enableVideo()
rtcEngine.setVideoEncoderConfiguration(AgoraVideoEncoderConfiguration(size: videoProfile,
frameRate: .fps15,
bitrate: AgoraVideoBitrateStandard,
orientationMode: .adaptative))
rtcEngine.setClientRole(clientRole)
...
}
}
If the user is a broadcaster, start the preview using rtcEngine.startPreview()
and invoke addLocalSession()
.
if isBroadcaster {
rtcEngine.startPreview()
}
addLocalSession()
Join the channel roomName
using rtcEngine.joinChannel()
:
-
If the
code
is equal to0
, the channel join is successful. Disable the idle timer usingsetIdleTimerActive
and enable the speakerphone usingrtcEngine.setEnableSpeakerphone()
. -
If the channel join is not successful, display an error message alert using
self.alert()
.
let code = rtcEngine.joinChannel(byToken: nil, channelId: roomName, info: nil, uid: 0, joinSuccess: nil)
if code == 0 {
setIdleTimerActive(false)
rtcEngine.setEnableSpeakerphone(true)
} else {
DispatchQueue.main.async(execute: {
self.alert(string: "Join channel failed: \(code)")
})
}
The Agora delegate methods are placed within an extension of LiveRoomViewController
. The remaining methods in this section are contained within this delegate extension.
extension LiveRoomViewController: AgoraRtcEngineDelegate {
...
}
The didJoinedOfUid
event listener is triggered when a user joins the channel.
-
Retrieve the video session of the user using
videoSession()
. -
Set the remove video for the user using
rtcEngine.setupRemoteVideo()
.
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
let userSession = videoSession(ofUid: Int64(uid))
rtcEngine.setupRemoteVideo(userSession.canvas)
}
The firstLocalVideoFrameWith
event listener is triggered when the first local video frame has elapsed
.
Retrieve the first video session using videoSessions.first
and update the video interface using updateInterface()
.
func rtcEngine(_ engine: AgoraRtcEngineKit, firstLocalVideoFrameWith size: CGSize, elapsed: Int) {
if let _ = videoSessions.first {
updateInterface(withAnimation: false)
}
}
The didOfflineOfUid
is triggered when a user goes offline.
Loop through videoSessions
to retrieve the video session of the offline user:
-
If the video session is found, remove the video session from the
videoSessions
array, remove the session'shostingView
from the superview usingremoveFromSuperview()
and setfullSession
tonil
if it is set to thedeletedSession
. -
If the offline user session is
fullSession
, setfullSession
tonil
.
func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
var indexToDelete: Int?
for (index, session) in videoSessions.enumerated() {
if session.uid == Int64(uid) {
indexToDelete = index
}
}
if let indexToDelete = indexToDelete {
let deletedSession = videoSessions.remove(at: indexToDelete)
deletedSession.hostingView.removeFromSuperview()
if deletedSession == fullSession {
fullSession = nil
}
}
}
SettingsViewController.swift defines and connects application functionality with the SettingsViewController UI.
- Create Variables, Protocols, and the IBAction Method
- Create Delegate and DataSource Methods
- Create Agora Size Method
The settingsVC()
protocol method is used by external classes to update the video profile.
protocol SettingsVCDelegate: NSObjectProtocol {
func settingsVC(_ settingsVC: SettingsViewController, didSelectProfile profile: CGSize)
}
he profileTableView
IBOutlet
variable maps to the profile table created in the SettingsViewController UI.
class SettingsViewController: UIViewController {
@IBOutlet weak var profileTableView: UITableView!
...
}
When the videoProfile
is set, profileTableView?.reloadData()
is invoked to update the profile table information.
var videoProfile: CGSize! {
didSet {
profileTableView?.reloadData()
}
}
The delegate
variable is an optional SettingsVCDelegate
object.
The private profiles
variable is an array of AgoraVideoProfile
objects and is initialized with AgoraVideoProfile.list()
.
weak var delegate: SettingsVCDelegate?
fileprivate let profiles: [CGSize] = CGSize.list()
The doConfirmPressed()
IBAction
method is invoked by the upper right button in the UI layout. This method updates the video profile by invoking delegate?.settingsVC()
.
@IBAction func doConfirmPressed(_ sender: UIButton) {
delegate?.settingsVC(self, didSelectProfile: videoProfile)
}
The tableView()
data source methods are added as an extension to the SettingsViewController
.
Determine the number of rows for the table by returning the profiles.count
.
extension SettingsViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return profiles.count
}
...
}
Create the table cell using tableView.dequeueReusableCell()
. Set the cell's selectedProfile
using cell.update
and return the resulting cell.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "profileCell", for: indexPath) as! ProfileCell
let selectedProfile = profiles[(indexPath as NSIndexPath).row]
cell.update(withProfile: selectedProfile, isSelected: (selectedProfile == videoProfile))
return cell
}
The tableView()
delegate method is added in an extension to the SettingsViewController
.
When a table row is selected, set videoProfile
to the selectedProfile
.
extension SettingsViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedProfile = profiles[(indexPath as NSIndexPath).row]
videoProfile = selectedProfile
}
}
The CGSize
private extension adds a list()
method, which returns an array of Agora video dimension objects.
private extension CGSize {
static func list() -> [CGSize] {
return [AgoraVideoDimension160x120,
AgoraVideoDimension320x180,
AgoraVideoDimension320x240,
AgoraVideoDimension640x360,
AgoraVideoDimension640x480,
AgoraVideoDimension1280x720]
}
}
- Find full API documentation in the Document Center
- File bugs about this sample
- 1 to 1 Video Tutorial for iOS/Swift
- Agora OpenLive samples are also available for the following platforms:
- OpenLive for iOS (Objective-C)
- OpenLive for Android
- OpenLive for Windows
- OpenLive for MacOS
This software is licensed under the MIT License (MIT). View the license.