# Review, annotate, and collaborate with users directly in your app’s video chat ## Introduction Previously, in the iOS (UIKit) quickstart sample app [blog](/blog/ios-vsdk-getting-started-guide), we provided a step-by-step guide on integrating Zoom’s Video SDK (VSDK) and adding essential features like core audio and video, along with some basic functionalities. In this tutorial, we’ll build upon the same sample app to explore screen sharing and annotation features, using a realistic scenario—such as a digital consultation with your family doctor when reviewing your annual health report (PDF). Zoom VSDK supports several screen sharing methods. In this blog, we’ll introduce two types: ShareWithView and InAppScreenShare, each with corresponding annotation options. - ShareWithView: Shares a specific UIView from your app—only the chosen view is visible to others. - InAppScreenShare (Introduced in the recent version 2.3.10): Shares the whole app interface, including system elements like Apple’s File Explorer and alert dialogs. For ShareWithView we will be adding the sample PDF (sample_health_report.pdf) and using Apple's UniformTypeIdentifiers and PDFKit API to display the PDF for local user while for InAppScreenShare we will be allowing the local user to open up the file explorer and select the same PDF. Note: Zoom VSDK also supports screen sharing via Apple’s ReplayKit; however, due to certain restriction, annotation is not available when using ReplayKit for screen sharing. You should familiarize yourself with the iOS (UIKit) quickstart sample app [blog](/blog/ios-vsdk-getting-started-guide) before proceeding. In this blog, we’ll discuss some of the recent changes as well as cover the following topics: - [SDK contents](#sdk-contents) - [Quickstart app contents](#quickstart-app-contents) - [Integrating the SDK](#integrating-the-sdk) - [Setup for share button and PDFKit](#setup-for-share-button-and-pdfkit) - [Setup for annotation](#setup-for-annotation) - [Bonus section](#bonus-section) You can find the completed project on [GitHub](https://github.com/zoom/videosdk-ios-uikit-screenshare-annotation). ## SDK contents The Video SDK for iOS package includes the following XCFramework bundles under `/Sample-Libs/lib `that can be added to your project as needed: - `ZoomVideoSDK.xcframework` and `ZoomTask.xcframework`: Interfaces to support all services related to Zoom sessions, such as initializing the SDK, creating and joining sessions, in-session services, and more. - `CptShare.xcframework`: Interfaces to support screen sharing a single `UIView`. Required to receive annotation by others when sharing a single UIView, as opposed to full broadcasting. - `zm_annoter_dynamic.xcframework`: Interfaces to support the annotation service when sharing. For this tutorial, we do not need these xcframeworks: - `ZoomVideoSDKScreenShare.xcframework`: Interfaces to support the full screen share service, for broadcasting a device screen. - `zoomcml.xcframework`: Interfaces to support virtual background filter and 3D avatar. - `Whiteboard.xcframework`: Interfaces to support whiteboard. ## Quickstart app contents `StartViewController` is the entry point for the app where the Video SDK is initialized. There is no change for this. ![Start View](/img/blog/boonjuntan/uikit-quick-start-joinsession.png) ![Loading View](/img/blog/boonjuntan/uikit-quick-start-sessionloading.png) Previously, the `SessionViewController` contains a `UITabBar` which holds the controls for toggling the user’s video, toggling audio and ending the Zoom session. We've added one more additional "Screen Share" button on the `UITabBar`. ![Default session view](/img/blog/boonjuntan/iOS-videosdk-screenshare-annotation-start-onvideo.png) ![Share Options](/img/blog/boonjuntan/iOS-videosdk-screenshare-annotation-share-options.png) ![Annotation](/img/blog/boonjuntan/iOS-videosdk-screenshare-annotation-share-annotate.png) The four options are tracked in the `ControlOption` enum. ```swift enum ControlOption: Int { case toggleVideo, toggleAudio, toggleShare, leaveSession } ``` ## Integrating the SDK Starting from version 2.3.10, the Zoom VSDK SPM consists of each of the xcframeworks instead of coupling all into 1. Hence, you have the option to choose which xcframeworks you need. **If you used Swift Package Manager to add the Zoom Video SDK**, your Xcode project's Package Dependencies should now look like this: ![VSDK iOS SPM Changes](/img/blog/boonjuntan/iOS-videosdk-screenshare-annotation-spm-changes.png) The `General > Frameworks, Libraries, and Embedded Content` settings should look like this: ![Frameworks, Libraries, and Embedded Content with SPM install](/img/blog/boonjuntan/iOS-videosdk-screenshare-annotation-spmframeworks.png) **If you added the Zoom Video SDK manually**, do the following: In the Video SDK package that was downloaded from the Zoom Marketplace, navigate to `/Sample-Libs/lib`. ![SDK framework libaries](/img/blog/boonjuntan/iOS-videosdk-screenshare-annotation-lib.png) The Video SDK is a dynamic library, so it must be included in the project as an embedded binary. In your Xcode project, navigate to your app's target and then `General > Frameworks, Libraries, and Embedded Content` and add `ZoomVideoSDK.xcframework`, `ZoomTask.xcframework`, `CptShare.xcframework` and `zm_annoter_dynamic.xcframework` for the main SDK interfaces and set to `Embed & Sign`. ![Embed frameworks](/img/blog/boonjuntan/iOS-videosdk-screenshare-annotation-embed.png) ## Setup for share button and PDFKit We will be adding one more button (screen share) to the tab bar under `SessionViewController`, add in some conditional check to see if shares are allowed and also a new `ShareSelection` enum. ```swift import UIKit import PDFKit // Add import ZoomVideoSDK enum ControlOption: Int { case toggleVideo, toggleAudio, toggleShare, leaveSession } enum ShareSelection { case InAppScreenShare case ShareWithView } class SessionViewController: UIViewController { // ... var toggleVideoBarItem: UITabBarItem = .init(title: "Stop Video", image: UIImage(systemName: "video.slash"), tag: ControlOption.toggleVideo.rawValue) var toggleAudioBarItem: UITabBarItem = .init(title: "Mute", image: UIImage(systemName: "mic.slash"), tag: ControlOption.toggleAudio.rawValue) // Add the toggleShareBarItem below the other video and audio toggle bar item var toggleShareBarItem: UITabBarItem = .init(title: "Share Locked", image: UIImage(systemName: "rectangle.on.rectangle.slash"), tag: ControlOption.toggleShare.rawValue) // For Screen Share and Annotation purpose var sharerView: UIView = .init() // Share view when remote user is sharing var localViewDuringShare: UIView = .init() // Container (UIView) for actualLocalViewDuringShare var actualLocalViewDuringShare: (view: UIView, placeholder: UIView)? // Local user video view or placeholder view var chosenShareType: ShareSelection? // InAppScreenShare and ShareWithView var shareBtnStackView: UIStackView = .init() // StackView for holding PDF and Draw buttons var sharePDFBtn = UIButton(type: .system) var shareDrawBtn = UIButton(type: .system) let sharedPDFView = PDFView() // PDFView for local sharer var annotationStarted: Bool = false var annotationHelper: ZoomVideoSDKAnnotationHelper? // ... // Replace the onSessionJoin with the following one with screen share check logic and also a more simplified onSessionJoin logic func onSessionJoin() { addLocalViewToGrid() actualLocalViewDuringShare = addLocalViewDuringShare() // Add local video view during screen share self.loadingLabel.isHidden = true self.tabBar.isHidden = false // Check if the local user is host, if yes then enable share and disable multi share. if let localUserIsHost = ZoomVideoSDK.shareInstance()?.getSession()?.getMySelf()?.isHost(), localUserIsHost { ZoomVideoSDK.shareInstance()?.getShareHelper()?.enableMultiShare(false) ZoomVideoSDK.shareInstance()?.getShareHelper()?.lockShare(false) } // For local and remote user, check if share is enabled. if let shareHelper = ZoomVideoSDK.shareInstance()?.getShareHelper() { if shareHelper.isMultiShareEnabled() == false && !shareHelper.isShareLocked() { toggleShareBarItem.title = "Start Share" toggleShareBarItem.image = UIImage(systemName: "rectangle.on.rectangle") } } } // Replace the onUserVideoStatusChanged with the following one with screen share subscribe/unsubscribe logic and also a more simplified onUserVideoStatusChanged logic func onUserVideoStatusChanged(_: ZoomVideoSDKVideoHelper?, user: [ZoomVideoSDKUser]?) { guard let users = user, let myself = ZoomVideoSDK.shareInstance()?.getSession()?.getMySelf() else { return } for user in users { if user.getID() == myself.getID() { if let canvas = user.getVideoCanvas(), let isVideoOn = canvas.videoStatus()?.on { Task(priority: .background) { if isVideoOn { canvas.subscribe(with: self.localView, aspectMode: .panAndScan, andResolution: ._Auto) canvas.subscribe(with: self.actualLocalViewDuringShare?.view, aspectMode: .panAndScan, andResolution: ._Auto) } else { canvas.unSubscribe(with: self.localView) canvas.unSubscribe(with: self.actualLocalViewDuringShare?.view) } self.localPlaceholder?.isHidden = isVideoOn self.toggleVideoBarItem.title = isVideoOn ? "Stop Video" : "Start Video" self.toggleVideoBarItem.image = UIImage(systemName: isVideoOn ? "video.slash" : "video") self.actualLocalViewDuringShare?.placeholder.isHidden = isVideoOn } } } else { if let canvas = user.getVideoCanvas(), let isVideoOn = canvas.videoStatus()?.on, let views = remoteUserViews[user.getID()] { Task(priority: .background) { views.placeholder.isHidden = isVideoOn } } } } } // Add the Zoom VSDK's onShareSettingChanged callback to listen for changes func onShareSettingChanged(_ setting: ZoomVideoSDKShareSetting) { if setting == .singleShare { toggleShareBarItem.title = "Start Share" toggleShareBarItem.image = UIImage(systemName: "rectangle.on.rectangle") } } func onUserShareStatusChanged(_ helper: ZoomVideoSDKShareHelper?, user: ZoomVideoSDKUser?, shareAction: ZoomVideoSDKShareAction?) { guard let user = user, let myself = ZoomVideoSDK.shareInstance()?.getSession()?.getMySelf(), let shareAction = shareAction else { return } let shareStatus = shareAction.getShareStatus() if user.getID() == myself.getID() { // Local user share status changed if shareStatus == .start || shareStatus == .resume { sharerView.isHidden = true shareBtnStackView.isHidden = false } else { sharedPDFView.isHidden = true shareBtnStackView.isHidden = true } } else { // Remote user share status changed if shareStatus == .start || shareStatus == .resume { shareAction.getShareCanvas()?.subscribe(with: sharerView, aspectMode: .letterBox, andResolution: ._Auto) sharerView.isHidden = false shareBtnStackView.isHidden = false toggleShareBarItem.title = "Share Locked" toggleShareBarItem.image = UIImage(systemName: "rectangle.on.rectangle.slash") } else { shareAction.getShareCanvas()?.unSubscribe(with: sharerView) sharerView.isHidden = true shareBtnStackView.isHidden = true } } localViewDuringShare.isHidden = !(shareStatus == .start || shareStatus == .resume) } // ... } /* Under the UITabBarDelegate, we will have to do the following: 1. Add the ControlOption.toggleShare.rawValue and handleShareToggle 2. Add handleShareToggle - Method that contains the share button logic when local user click onto it 3. Add handleShareSelection - Method that contains handling of the share selected (InAppScreenShare or ShareWithView) */ extension SessionViewController: UITabBarDelegate { func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { tabBar.selectedItem = nil switch item.tag { case ControlOption.toggleVideo.rawValue: handleVideoToggle(tabBar) case ControlOption.toggleAudio.rawValue: handleAudioToggle(tabBar) case ControlOption.toggleShare.rawValue: handleShareToggle(tabBar) case ControlOption.leaveSession.rawValue: tabBar.isUserInteractionEnabled = false ZoomVideoSDK.shareInstance()?.leaveSession(false) default: break } } // ... private func handleShareToggle(_ tabBar: UITabBar) { #if targetEnvironment(simulator) showError(message: "Simulator detected, share is not supported", dismiss: false) #else guard let shareHelper = ZoomVideoSDK.shareInstance()?.getShareHelper() else { return } // 1. Check if share is enabled. guard shareHelper.isMultiShareEnabled() != true || shareHelper.isShareLocked() else { showError(message: "Screen sharing is locked. Wait for host to be in.", dismiss: false) return } // 2. Check if someone is sharing - Single share only guard !shareHelper.isOtherSharing() else { showError(message: "Others is sharing. Only 1 share allowed.", dismiss: false) return } // 3. Check if InAppscreenShare is supported guard shareHelper.isSupportInAppScreenShare() else { showError(message: "In app screen share is not supported.", dismiss: false) return } toggleShareBarItem.isEnabled = false // 4.1 Create an UIAlertController for user to select if they want InAppScreenShare or ShareWithView. let alert = UIAlertController( title: "Screen share mode", message: "1. InAppScreenShare - Share your entire app + any system API UI (FilePicker, AlertBox and etc).\n2. ShareWithView - Share a specific view of your choice. In the sample app we have simplify the process with a sample_health_report.pdf.", preferredStyle: .alert ) // 4.2 Check if local user is currently sharing out. if shareHelper.isSharingOut() { // 4.3 Local user is already sharing, so by clicking on the share button we should stop share let error = shareHelper.stopShare() if error == .Errors_Success { print("stopShare") toggleShareBarItem.title = "Start Share" toggleShareBarItem.image = UIImage(systemName: "rectangle.on.rectangle") } else { print("Fail stopShare") } } else { // 4.4 Local user is not sharing, so by clicking on the share button we should provide the 2 UIAlertAction options (InAppScreenShare and ShareWithView) alert.addAction(UIAlertAction(title: "InAppScreenShare", style: .default, handler: { _ in self.handleShareSelection(with: .InAppScreenShare) })) alert.addAction(UIAlertAction(title: "ShareWithView", style: .default, handler: { _ in self.handleShareSelection(with: .ShareWithView) })) alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in print("Cancel share") })) present(alert, animated: true, completion: nil) } toggleShareBarItem.isEnabled = true #endif } func handleShareSelection(with chosenShare: ShareSelection) { guard let shareHelper = ZoomVideoSDK.shareInstance()?.getShareHelper() else { return } chosenShareType = chosenShare var error = ZoomVideoSDKError.Errors_Audio_Module_Error switch chosenShare { // User selected InAppScreenShare case .InAppScreenShare: error = shareHelper.startInAppScreenShare() if error == .Errors_Success { print("startInAppScreenShare") } else { print("Fail startInAppScreenShare") } // User selected ShareWithView case .ShareWithView: openBundledPDF() // This methiod is for opening up the sample PDF (sample_health_report.pdf) added earlier. error = shareHelper.startShare(with: sharedPDFView) if error == .Errors_Success { print("startShareWithView") } else { print("Fail startShareWithView") } } if error == .Errors_Success { toggleShareBarItem.title = "Stop Share" toggleShareBarItem.image = UIImage(systemName: "rectangle.on.rectangle.slash") } } } ``` In `SessionViewControllerExtension.swift`, we will also need to handle the additional screen share button logic and sample PDF (sample_health_report.pdf) logic. ```swift import UIKit import UniformTypeIdentifiers // Add import PDFKit // Add import ZoomVideoSDK extension SessionViewController: UIDocumentPickerDelegate { private func setupTabBar() { tabBar.delegate = self tabBar.isHidden = true let leaveSessionBarItem = UITabBarItem(title: "Leave Session", image: UIImage(systemName: "phone.down"), tag: ControlOption.leaveSession.rawValue) tabBar.items = [toggleVideoBarItem, toggleAudioBarItem, toggleShareBarItem, leaveSessionBarItem] } // ... @objc func pickPDF() { let picker = UIDocumentPickerViewController(forOpeningContentTypes: [UTType.pdf]) picker.delegate = self present(picker, animated: true) } func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { guard let url = urls.first else { return } // Gain temporary read access to files outside your sandbox. let didAccess = url.startAccessingSecurityScopedResource() defer { if didAccess { url.stopAccessingSecurityScopedResource() } } // Coordinate reading (helps with iCloud/third-party providers ensuring the file is available). let coordinator = NSFileCoordinator() var coordError: NSError? var readableURL: URL? coordinator.coordinate(readingItemAt: url, options: [], error: &coordError) { securedURL in // Copy to a local temp URL you fully control (robust across providers). let tmpURL = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("pdf") do { // If the provider streams the file, copying forces a local, complete file. try FileManager.default.copyItem(at: securedURL, to: tmpURL) readableURL = tmpURL } catch { // If copy fails (e.g., already local), fall back to using the securedURL directly. readableURL = securedURL } } if let e = coordError { print("NSFileCoordinator error: \(e)") } guard let openURL = readableURL else { print("No readable URL resolved.") return } // Try opening with URL first. if let doc = PDFDocument(url: openURL) { sharedPDFView.document = doc sharedPDFView.autoScales = true sharedPDFView.isHidden = false shareBtnStackView.isHidden = false } } func openBundledPDF() { // Make sure your PDF is added to the project and "Target Membership" is checked if let pdfURL = Bundle.main.url(forResource: "sample_health_report", withExtension: "pdf") { if let pdfDoc = PDFDocument(url: pdfURL) { sharedPDFView.autoScales = true sharedPDFView.document = pdfDoc sharedPDFView.isHidden = false shareBtnStackView.isHidden = true } } else { print("Could not find PDF in bundle") } } } ``` ## Setup for annotation In `SessionViewControllerExtension`, we will need to add the draw logic first. ```swift extension SessionViewController: UIDocumentPickerDelegate { // ... @objc func toggleDraw() { guard let shareHelper = ZoomVideoSDK.shareInstance()?.getShareHelper() else { return } // 1. If local share owner is sharing - Allow viewer to annotation shareHelper.disableViewerAnnotation(false) // 2. Check if annotation is supported and if viewer annotation is disable guard shareHelper.isAnnotationFeatureSupport(), !shareHelper.isViewerAnnotationDisabled() else { showError(message: "Annotation is not supported", dismiss: false) return } // 3. Check if annotationHelper exist, if so we will destory it. if annotationHelper != nil { shareHelper.destroy(annotationHelper) } // 4. Check if local user is the one sharing out. if shareHelper.isSharingOut() { // 4.1 Local user is the one sharing, first check if sharedPDFView contains the sample PDF or a selected PDF. Then create the annotationHelper. guard sharedPDFView.document != nil else { showError(message: "Select a PDF first.", dismiss: false) return } annotationHelper = shareHelper.createAnnotationHelper(nil) // Nil for self sharing as mentioned for shareHelper.createAnnotationHelper } else if shareHelper.isOtherSharing() { // 4.2 Check if other is sharing. If so, create a annotation helper. annotationHelper = shareHelper.createAnnotationHelper(sharerView) } // 5. If annotationHelper is still nil at this stage, means annotation is invalid. guard let annotationHelper = annotationHelper else { showError(message: "You are not allowed to annotate", dismiss: false) return } // 6. Check the current status of annotation if annotationStarted { // 6.1 Annotation has already been started before, so by toggle this button again we will stop annotation. let error = annotationHelper.stopAnnotation() if error == .Errors_Success { shareDrawBtn.setImage(UIImage(systemName: "pencil")?.withConfiguration(UIImage.SymbolConfiguration(pointSize: 30, weight: .regular)), for: .normal) annotationStarted = false } } else { // 6.2 Annotation not yet started, so by toggle this button again we will start annotation. let error = annotationHelper.startAnnotation() if error == .Errors_Success { var errorInAppAnnotationResult: ZoomVideoSDKError? // Check what is the chosenShareType. switch chosenShareType { case .InAppScreenShare: // For InAppScreenShare, we will need to use setAnnotationView for annotation. // Check if local user or remote user is sharing, then set annotation view accordingly. if (shareHelper.isSharingOut()) { errorInAppAnnotationResult = shareHelper.setAnnotationView(sharedPDFView) } else { errorInAppAnnotationResult = shareHelper.setAnnotationView(sharerView) } if errorInAppAnnotationResult == .Errors_Success { // Set the annotation tool type, color and width. There are various configurations available. Check the ZoomVideoSDKAnnotationHelper for more information. annotationHelper.setToolType(.pen) annotationHelper.setToolColor(.red) annotationHelper.setToolWidth(2) shareDrawBtn.setImage(UIImage(systemName: "pencil.slash")?.withConfiguration(UIImage.SymbolConfiguration(pointSize: 30, weight: .regular)), for: .normal) } else { print("Fail to set annotation") } case .ShareWithView: // For ShareWithView, we will directly set the tool type, color and width annotationHelper.setToolType(.pen) annotationHelper.setToolColor(.red) annotationHelper.setToolWidth(2) shareDrawBtn.setImage(UIImage(systemName: "pencil.slash")?.withConfiguration(UIImage.SymbolConfiguration(pointSize: 30, weight: .regular)), for: .normal) default: return } annotationStarted = true } else { print("Fail to start annotation") } } } } ``` ## Bonus section If you are using the simulator for testing, you will realise that there are a few limitations such as no camera, screen share and a few features that aren't supported. Hence it will be great to also include the necessary checks. In `SessionViewController`, add the following check: ```swift private func handleVideoToggle(_ tabBar: UITabBar) { #if targetEnvironment(simulator) showError(message: "Simulator detected, video is not supported", dismiss: false) #else // ... #endif } ``` Next, we will also want to add the Zoom VSDK onError handling. In `SessionViewController`, update the showError method and add the `onError` callback from Zoom VSDK. ```swift class SessionViewController: UIViewController { // ... public func showError(message: String, dismiss: Bool = true) { Task { @MainActor in let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default) { _ in if (dismiss) { self.dismiss(animated: true) } }) present(alert, animated: true) } } } // MARK: - ZoomVideoSDKDelegate extension SessionViewController: ZoomVideoSDKDelegate { func onError(_ ErrorType: ZoomVideoSDKError, detail details: Int) { showError(message: "Error occured: \(ErrorType.rawValue)", dismiss: false) } // ... } ``` And that’s how you integrate the screen share and annotation features. You can find more information under the _Add Features_ section in our Video SDK [docs](/docs/video-sdk/ios/).