Building a 1-to-many iOS video app with Agora 4.x SDK Preview
Tags: swift, agora, videochat, livevideo
Published: 2021-09-17 15:35:09

Ever wonder how to build a video chat app? This is a guide to how to quickly and easily create a video chat app on iOS that can support multiple participants using the Agora Video SDK.

# Requirements

- Xcode 10.0+
- A physical iOS device. The iOS simulator lacks camera functionality.
- CocoaPods (If you don’t have CocoaPods installed already, you can find instructions [here](https://guides.cocoapods.org/using/getting-started.html)).
- An Agora account ([see How To Get Started with Agora](https://www.agora.io/en/blog/how-to-get-started-with-agora?utm_source=medium&utm_medium=blog&utm_campaign=de_content))
- An understanding of how to build iOS layouts with a Storyboard. If you need a refresher, there’s a great tutorial [here](https://www.raywenderlich.com/464-storyboards-tutorial-for-ios-part-1).

# Setting up the Agora library with CocoaPods

1. Create a new Xcode project (or use an existing one).
2. In Terminal, navigate to the root directory of your project and run `pod init` to initialize CocoaPods.
3. Open the Podfile that was created and add the following code to import the Agora library:

```
target 'Your App' do 
  pod 'AgoraRtcEngine_iOS', '3.4.0' 
end
```

1. Run `pod install` in Terminal to install the library.
2. From now on, open **YourApp.xcworkspace** to edit and run your app.

# Add Camera and Microphone permissions

In order to use the microphone and camera, we’ll need to ask the user for permission to do so. In your `Info.plist` add the following keys:

```
Privacy - Microphone Usage Description 
Privacy - Camera Usage Description
```

Make sure you add a value for each. These values are user-facing, and will be displayed when the app asks for these permissions from the user.

# Setting up the scene

In our `Main.storyboard` we'll need to add the views Agora will use to display the video feeds. For our demo, we'll be using a single large view to display our local feed, and a collection view to show an arbitrary number of remote users, but feel free to adjust as necessary for your own needs.

![img](https://miro.medium.com/max/1400/1*VnOuW3OWiAuZ1wXCmlNWIA.png)

The local view is in green, and the remote view template is in red, for ease of identification. Add a View object for the local stream, a UIButton to mute and hang up the call, and a UICollectionView to hold the remote streams. Your UICollectionViewCells can be as simple as a single view to hold the stream — in the example above, I’ve added an overlay to show the remote user’s name if we know it.

Make sure you hook up the views in your main View Controller, and set the View Controller as the UICollectionView’s delegate and dataSource:

```swift
class AgoraVideoViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {

    @IBOutlet weak var collectionView: UICollectionView!
    @IBOutlet weak var localVideoView: UIView!
    @IBOutlet weak var muteButton: UIButton!
    @IBOutlet weak var hangUpButton: UIButton!
```

And connect up your custom collection view cell:

```swift
class VideoCollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var videoView: UIView!
}
```

> *Tip: If you want to add overlays to your video streams, make sure you don’t add them as subviews of the view objects you’re going to use as video screens. The video canvas will be drawn on top of them. Add them as sibling views instead.*

# Initialize the Agora engine

In order to use the Agora engine, we need to create an instance of `AgoraRtcEngineKit` with our app ID.

First, we will need to retrieve our app ID by going to the [Agora Dashboard](https://dashboard.agora.io/). If you haven’t created an Agora project yet, do so now by clicking “New Project.”

Once you have a project, click the “Edit” button (or open the Project Management pane) to view that project’s details. Copy the app ID and add it to your project. If you enabled the App Certificate, you’ll also need a Token to join channels — you can generate a temporary one by clicking “Generate Temp Token.” You can also read our tutorial on generating your own tokens [here](https://docs.agora.io/en/Video/token_server?platform=CPP).

> Note: Any Agora projects made for production should require a token from the client to connect. See [Connecting to Agora with Tokens — Using Swift](https://www.agora.io/en/blog/connecting-to-agora-with-tokens-using-swift?utm_source=medium&utm_medium=blog&utm_campaign=real-time-messaging-video-dynamic-channels) for more information on how to work with tokens in an iOS project.

The first call you make to Agora must be to initialize a shared Agora engine. We’ll make sure to do this by creating a helper function that initializes the engine if we haven’t done so yet, and just returns it if we have. That way we can just call it whenever we need a reference to the engine without having to worry about who does it first.

```swift
import AgoraRtcKit

let appID = "YourAppIDHere"
var agoraKit: AgoraRtcEngineKit?
let tempToken: String? = nil //If you have a token, put it here.
var userID: UInt = 0 //This tells Agora to generate an id for you. If you have unique user IDs already, you can use those.

...

private func getAgoraEngine() -> AgoraRtcEngineKit {
    if agoraKit == nil {
        agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: appID, delegate: self)
    }
    return agoraKit!
}
```

> *Tip: This is a quick way to ensure the engine is only initialized once when you need it, but for a larger app you may want to consider wrapping it in a* [*Singleton*](https://developer.apple.com/documentation/swift/cocoa_design_patterns/managing_a_shared_resource_using_a_singleton) *instead, so you can easily access it from anywhere.*

We’ll also need to implement the AgoraRtcEngineDelegate protocol so we can respond to relevant callbacks:

```swift
extension AgoraVideoViewController: AgoraRtcEngineDelegate {
  
}
```

# Enable Video

The next step is to tell Agora we want video enabled, and to tell it where to put the local video stream. We can then call this function from our `viewDidLoad()`.

```swift
func setUpVideo() {
    getAgoraEngine().enableVideo()

    let videoCanvas = AgoraRtcVideoCanvas()
    videoCanvas.uid = userID
    videoCanvas.view = localVideoView
    videoCanvas.renderMode = .fit
    getAgoraEngine().setupLocalVideo(videoCanvas)
}
```

> *Tip: If you want to customize how the video is displayed, this is a good place to* [*configure the video profile*](https://docs.agora.io/en/Video/video_profile_apple?platform=iOS)*.*

# Join a channel

Once the engine is initialized, joining a call is as easy as calling `joinChannel()` on the Agora engine.

```swift
var channelName = "default"

func joinChannel() {
  localVideoView.isHidden = false

  let engine = getAgoraEngine()
  engine.startPreview()
  engine.setClientRole(.broadcaster)

  let joinCHRtn = engine.joinChannel(byToken: tempToken, channelId: channelName, info: nil, uid: userID) { [weak self] (sid, uid, elapsed) in
    self?.userID = uid
  }
}
```

> Note: Any Agora projects made for production should require a token from the client to connect. See [Connecting to Agora with Tokens — Using Swift](https://www.agora.io/en/blog/connecting-to-agora-with-tokens-using-swift?utm_source=medium&utm_medium=blog&utm_campaign=real-time-messaging-video-dynamic-channels) for more information on how to work with tokens in an iOS project.

# Setting up Remote Video

Now is the time to put our UICollectionView to good use. We’ll keep a list of remote user IDs, and for each one, set up a remote video canvas within our collection.

```swift
var remoteUserIDs: [UInt] = []

func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return remoteUserIDs.count
}

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "videoCell", for: indexPath)

    let remoteID = remoteUserIDs[indexPath.row]
    if let videoCell = cell as? VideoCollectionViewCell {
        let videoCanvas = AgoraRtcVideoCanvas()
        videoCanvas.uid = remoteID
        videoCanvas.view = videoCell.videoView
        videoCanvas.renderMode = .fit
        getAgoraEngine().setupRemoteVideo(videoCanvas)
    }

    return cell
}
```

> *Tip: Remember to set your custom cell’s reuse identifier in your Main.Storyboard!*

To get this list of userIDs (and maintain it), we’ll utilize the `rtcEngine(didJoinedOfUid:)` and `rtcEngine(didOfflineOfUid:)` callbacks. Inside your `AgoraRtcEngineDelegate` extension, add the following functions:

```swift
extension AgoraVideoViewController: AgoraRtcEngineDelegate {
    func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
        remoteUserIDs.append(uid)
        collectionView.reloadData()
    }

    func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
        if let index = remoteUserIDs.firstIndex(where: { $0 == uid }) {
            remoteUserIDs.remove(at: index)
            collectionView.reloadData()
        }
    }
}
```

And with that, you have a working video chat app. Beware of audio feedback if testing on multiple devices at once.

# Polish

There are a few more pieces that we should add in to make our app a little nicer. For one, our buttons don’t do anything. Lets’s fix that first. Enabling the mute button is a simple call to `muteLocalAudioStream()`:

```swift
var muted = false {
    didSet {
        if muted {
            muteButton.setTitle("Unmute", for: .normal)
        } else {
            muteButton.setTitle("Mute", for: .normal)
        }
    }
}

@IBAction func didToggleMute(_ sender: Any) {
    muted.toggle()
    getAgoraEngine().muteLocalAudioStream(muted)
}
```

We can also hang up by calling `leaveChannel()`:

```swift
@IBAction func didTapHangUp(_ sender: Any) {
    leaveChannel()
}

func leaveChannel() {
    getAgoraEngine().leaveChannel(nil)
    localVideoView.isHidden = true
    remoteUserIDs.removeAll()
    collectionView.reloadData()
}
```

> Tip: If you don’t hide the local video view (or pop the view controller) you’ll end up with a static view of the last frame recorded.

And we’re done! You can find the completed app [here](https://github.com/AgoraIO-Community/Agora-1-to-Many-iOS). Thanks for following along, and happy coding!

# Other Resources

For more information about building applications using Agora.io SDKs, take a look at [Agora Video Call Quickstart Guide](https://docs.agora.io/en/Video/start_call_ios?platform=iOS&utm_source=medium&utm_medium=blog&utm_campaign=live-audio-broadcasting-app-agora-ios) and [Agora API Reference](https://docs.agora.io/en/Video/API Reference/oc/docs/headers/Agora-Objective-C-API-Overview.html?utm_source=medium&utm_medium=blog&utm_campaign=live-audio-broadcasting-app-agora-ios).

I also invite you to [join the Agora.io Developer Slack community](http://bit.ly/2IWexJQ).