<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Apple – IMG.LY Blog</title><description>Posts tagged Apple on the IMG.LY blog.</description><link>https://img.ly/blog/tag/apple/</link><language>en-us</language><image><url>https://img.ly/apple-touch-icon.png</url><title>Apple – IMG.LY Blog</title><link>https://img.ly/blog/tag/apple/</link></image><atom:link href="https://img.ly/blog/tag/apple/rss.xml" rel="self" type="application/rss+xml"/><generator>Astro</generator><lastBuildDate>Sun, 28 Jun 2026 09:26:06 GMT</lastBuildDate><ttl>60</ttl><item><title>How to Add a Filter to a Video Stream in iOS</title><link>https://img.ly/blog/how-to-add-a-filter-to-a-video-stream-in-ios/</link><guid isPermaLink="true">https://img.ly/blog/how-to-add-a-filter-to-a-video-stream-in-ios/</guid><description>Applying stunning filters to videos requires different methods than applying them to files in iOS. Find out how!</description><pubDate>Mon, 08 Nov 2021 15:33:55 GMT</pubDate><content:encoded>&lt;p&gt;Using filters and effects with video streams requires different strategies than applying them to files. In this tutorial, you will see how to apply effects and filters to video streams in real-time. The code in this tutorial compiles with Xcode 13.&lt;/p&gt;
&lt;p&gt;As with most &lt;code&gt;AVFoundation&lt;/code&gt; code, the Xcode simulator is not the best platform for running the code. Test on an actual device. A project with code that supports this tutorial can be found &lt;a href=&quot;https://github.com/waltertyree/filter-video-stream&quot;&gt;on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;streams-or-files&quot;&gt;Streams or Files&lt;/h2&gt;
&lt;p&gt;For video files stored on the device, you can use an &lt;code&gt;AVMutableVideoComposition&lt;/code&gt; to apply filters. When streaming video from a remote location, this strategy will not work. The &lt;code&gt;AVMutuableVideoComposition&lt;/code&gt; classes are not designed to work in real-time with streams. Apple provides a way to extract the pixels from the stream at regular intervals. You can manipulate the pixels and then render them to the screen. An &lt;code&gt;AVPlayerItemVideoOutput&lt;/code&gt; object gives access to the pixels that make up a video frame. Apple also supplies a &lt;code&gt;CADisplayLink&lt;/code&gt; object to ensure you are extracting the right pixels at the right time. The display link is a specialized timer that will fire in sync with the redraw rate of the current display.&lt;/p&gt;
&lt;p&gt;So, to filter a video stream, the strategy becomes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set up an AVPlayer as normal and assign it the URL of the stream&lt;/li&gt;
&lt;li&gt;Attach an &lt;code&gt;AVPlayerItemVideoOutput&lt;/code&gt; object to the stream&lt;/li&gt;
&lt;li&gt;Attach a &lt;code&gt;CADisplayLink&lt;/code&gt; object to the run loop&lt;/li&gt;
&lt;li&gt;Extract the current pixel buffer from the AVPlayerItem every time the screen redraws&lt;/li&gt;
&lt;li&gt;Convert the pixel buffer to a CoreImage object&lt;/li&gt;
&lt;li&gt;Apply filters&lt;/li&gt;
&lt;li&gt;Display the image&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;setting-up-avplayer&quot;&gt;Setting up AVPlayer&lt;/h2&gt;
&lt;p&gt;You won’t use the &lt;code&gt;AVPlayerLayer&lt;/code&gt; or &lt;code&gt;AVPlayerViewController&lt;/code&gt; to display the video, but the &lt;code&gt;AVPlayer&lt;/code&gt; plays a central role. The &lt;code&gt;AVPlayer&lt;/code&gt; keeps the video in sync with the audio, handles decoding whatever format the stream uses, controls buffering, and more. The basic setup is the same as with any other project:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a player&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; videoItem &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayerItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: streamURL)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.player &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;playerItem&lt;/span&gt;&lt;span&gt;: videoItem)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The URL can either point to a local resource or a stream.&lt;/p&gt;
&lt;h2 id=&quot;using-avplayeritemvideooutput&quot;&gt;Using AVPlayerItemVideoOutput&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;AVPlayerItemVideoOutput&lt;/code&gt; allows you to query the &lt;code&gt;AVPlayerItem&lt;/code&gt; for the pixel buffer (one screen worth of pixels) at any given time. You can specify different pixel formats and other options for the output. For this tutorial, you will you can create the object with a simple instantiation using the default formats.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let playerItemVideoOutput = AVPlayerItemVideoOutput()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Later you will add this video output to your player item. Then you can ask it to generate the pixel buffers as needed. In this tutorial, you will ask for the pixel buffer of the current frame. You could ask for the frame for any timestamp though.&lt;/p&gt;
&lt;h2 id=&quot;creating-the-display-link&quot;&gt;Creating the Display Link&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;CADisplayLink&lt;/code&gt; class is a specialized &lt;code&gt;NSTimer&lt;/code&gt; that synchronizes itself to the screen refresh rate of the display. This ensures that the pixel buffer you extract will be from the correct time of your video stream. It will get rendered at the correct time in the screen refresh. Syncing a standard &lt;code&gt;NSTimer&lt;/code&gt; to the screen refresh has always been problematic. With the new variable refresh rate screens that Apple makes, it becomes impossible. However, &lt;code&gt;CADisplayLink&lt;/code&gt; adjusts its rate as the screen refresh rate changes. Apple explains how this works in the &lt;a href=&quot;https://developer.apple.com/videos/play/wwdc2021/10147/&quot;&gt;2021 WWDC Session &lt;em&gt;Optimize for Variable Refresh Rate Displays&lt;/em&gt;&lt;/a&gt;. When you create the display link, you give it the name of a function to call each time it executes. A sample for creating a link could look like this:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;lazy&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; displayLink: CADisplayLink &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CADisplayLink&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                                  selector&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#selector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;displayLinkFired&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;link:&lt;/span&gt;&lt;span&gt;)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Elsewhere in the code, a function called &lt;code&gt;displayLinkFired(link: CADisplayLink)&lt;/code&gt; extracts the pixel buffer from the &lt;code&gt;AVPlayerItem&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;starting-the-player&quot;&gt;Starting the Player&lt;/h2&gt;
&lt;p&gt;Apple notes that loading a video file takes a measurable amount of time. It can take even longer when the video file is streaming across a network. AVFoundation allows your program to continue to execute while the setup steps and initial buffering are occurring in the background. You can run into issues if you load a video into AVFoundation and then immediately attempt to work with it. AVFoundation may not show an error or status message, but it will not perform as expected either.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;most important&lt;/em&gt; step in this entire process is using an observer to wait until the AVPlayerItem has a &lt;code&gt;.readyToPlay&lt;/code&gt; status before attaching the output and displaying link objects. In the example below &lt;code&gt;statusOberserver&lt;/code&gt;, &lt;code&gt;player&lt;/code&gt;, &lt;code&gt;playerItemVideoOutput&lt;/code&gt; and &lt;code&gt;displayLink&lt;/code&gt; are all declared at the class level. They need to persist the entire time the video is being used.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a player&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; videoItem &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayerItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: streamURL&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.player &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;playerItem&lt;/span&gt;&lt;span&gt;: videoItem) &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//*important* add the display link and the output only after it is ready to play&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.statusObserver &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; videoItem.&lt;/span&gt;&lt;span&gt;observe&lt;/span&gt;&lt;span&gt;(\.status, &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                       options&lt;/span&gt;&lt;span&gt;: [.new, .old],&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                 changeHandler&lt;/span&gt;&lt;span&gt;: { playerItem, change &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; playerItem.status &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; .readyToPlay { &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      playerItem.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.playerItemVideoOutput)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      self&lt;/span&gt;&lt;span&gt;.displayLink.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: .main, &lt;/span&gt;&lt;span&gt;forMode&lt;/span&gt;&lt;span&gt;: .common)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      self&lt;/span&gt;&lt;span&gt;.player&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;play&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;//4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what to notice about the code above&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creating the player will take a large amount of time, but the program execution will not stop and wait&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NSKeyValueObservation&lt;/code&gt; is how swift defines key value observers the code will execute every time the &lt;code&gt;videoItem.status&lt;/code&gt; property changes values&lt;/li&gt;
&lt;li&gt;Checks to see if the &lt;code&gt;.status&lt;/code&gt; property is &lt;code&gt;.readyToPlay&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;After adding the display link and the video output objects start playing the video&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;extracting-the-pixel-buffer&quot;&gt;Extracting the Pixel Buffer&lt;/h2&gt;
&lt;p&gt;Once the video begins to play, the display link function gets called for each screen refresh. Here is an example of how to extract the pixel buffer and render it to a &lt;code&gt;UIImageView&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;@objc&lt;/span&gt;&lt;span&gt; func&lt;/span&gt;&lt;span&gt; displayLinkFired&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;link&lt;/span&gt;&lt;span&gt;: CADisplayLink) { &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; currentTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; playerItemVideoOutput.&lt;/span&gt;&lt;span&gt;itemTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forHostTime&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CACurrentMediaTime&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; playerItemVideoOutput.&lt;/span&gt;&lt;span&gt;hasNewPixelBuffer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forItemTime&lt;/span&gt;&lt;span&gt;: currentTime) { &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; buffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; playerItemVideoOutput.&lt;/span&gt;&lt;span&gt;copyPixelBuffer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forItemTime&lt;/span&gt;&lt;span&gt;: currentTime, &lt;/span&gt;&lt;span&gt;itemTimeForDisplay&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;nil&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      let&lt;/span&gt;&lt;span&gt; frameImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;cvImageBuffer&lt;/span&gt;&lt;span&gt;: buffer) &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      //4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      let&lt;/span&gt;&lt;span&gt; pixelate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIFilter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;CIPixellate&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      pixelate.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(frameImage, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputImageKey)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      pixelate.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.filterSlider.&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputScaleKey)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      pixelate.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CIVector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;: frameImage.extent.midX, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: frameImage.extent.midY), &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputCenterKey)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     let&lt;/span&gt;&lt;span&gt; newFrame &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; pixelate.outputImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;cropped&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: frameImage.extent)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     self&lt;/span&gt;&lt;span&gt;.videoView.&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; UIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;ciImage&lt;/span&gt;&lt;span&gt;: newFrame) &lt;/span&gt;&lt;span&gt;//5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what the code is doing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Marks the &lt;code&gt;func&lt;/code&gt; with &lt;code&gt;@objc&lt;/code&gt; so that it works with the display link selector&lt;/li&gt;
&lt;li&gt;Asks if there is a new pixel buffer to display. Depending on the refresh rate of the screen and the frame rate of the video, there will not always be a new buffer.&lt;/li&gt;
&lt;li&gt;After extracting the pixel buffer, convert it to a CoreImage object&lt;/li&gt;
&lt;li&gt;Apply any filters. This example applies the &lt;code&gt;CIPixellate&lt;/code&gt; filter. It uses a slider to let the user change the intensity as the video plays&lt;/li&gt;
&lt;li&gt;Convert the filtered image to a &lt;code&gt;UIImage&lt;/code&gt; and assign it to the &lt;code&gt;UIImageView&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now as the pixellate scale changes the image will be filtered but the video will continue to play smoothly.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;video-intensity-iOS-filter-video-stream&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1000px) 1000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1000&quot; height=&quot;497&quot; src=&quot;https://img.ly/_astro/video-intensity-iOS-filter-video-stream_Z29kdA1.webp&quot; srcset=&quot;/_astro/video-intensity-iOS-filter-video-stream_VOjmc.webp 640w, /_astro/video-intensity-iOS-filter-video-stream_Z2eFwGs.webp 750w, /_astro/video-intensity-iOS-filter-video-stream_1oxxcg.webp 828w, /_astro/video-intensity-iOS-filter-video-stream_Z29kdA1.webp 1000w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;Using the strategy above you should be able to apply different filters to the video stream in real-time. Remember that you only have a few milliseconds though, then the video will begin to stutter. CoreImage filters are optimized to make use of the GPU and should be fast enough in most cases. For better performance, you can render the filtered image to an &lt;code&gt;MTKView&lt;/code&gt; and use Metal. Apple has also begun to create Metal Performance Shaders which are even more efficient than CoreImage filters when used with a MetalKit view. As of now, there are many more CoreImage filters, so that may give you the most flexibility.&lt;br&gt;
The strategy in this tutorial is adequate for filtering video streams. If you want to give your users more flexibility with filters and other video tweaks, an editor such as &lt;a href=&quot;https://img.ly/products/video-sdk/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;VideoEditorSDK&lt;/a&gt; can let you focus on your application’s core functions and let a team of professionals worry about the video. Using VideoEditorSDK your users can apply filters to video as well as text annotations and more.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;stream-edit-ios&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 600px) 600px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;600&quot; height=&quot;640&quot; src=&quot;https://img.ly/_astro/stream-edit-ios_Z1MTqzv.webp&quot; srcset=&quot;/_astro/stream-edit-ios_Z1MTqzv.webp 600w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Looking for more video capabilities? Check out our solutions for &lt;a href=&quot;https://img.ly/use-cases/story-reels-short-video-creation/&quot;&gt;Short Video Creation&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/products/video-sdk-mobile/&quot;&gt;Camera SDK&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;Thanks for reading! We hope that you found this tutorial helpful. Feel free to reach out to us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; with any questions or suggestions.&lt;/p&gt;
&lt;p&gt;You like what we do? Check our &lt;a href=&quot;https://img.ly/company/careers/&quot;&gt;careers page&lt;/a&gt;. Even if you can’t find your role listed, we might be interested since you are already here!&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2021/11/video-filter-ios-video-stream.jpg" medium="image"/><category>iOS App Development</category><category>iOS</category><category>Video Editing</category><category>App Development</category><category>Mobile App Development</category><category>Video Filter</category><category>Apple</category><category>Developer Tools</category><category>How-To</category><category>Tutorial</category></item><item><title>How To Record Screen Video Using ReplayKit for iOS</title><link>https://img.ly/blog/record-screen-with-swift-replaykit/</link><guid isPermaLink="true">https://img.ly/blog/record-screen-with-swift-replaykit/</guid><description>Use Swift and ReplayKit to add screen recording to your application. Also use the new rolling clips feature introduced in iOS 15!</description><pubDate>Tue, 21 Sep 2021 15:05:08 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you will see how to use Swift and &lt;code&gt;ReplayKit&lt;/code&gt; to add screen recording to your application. You will also see how to use the new rolling clips feature, introduced in iOS 15. The code in this article uses Swift 5 and Xcode 13. Clone &lt;a href=&quot;https://github.com/waltertyree/replay-screen-video&quot;&gt;this repository&lt;/a&gt; for a sample project and example code.&lt;/p&gt;
&lt;h2 id=&quot;replaykit-basics&quot;&gt;ReplayKit Basics&lt;/h2&gt;
&lt;p&gt;Apple introduced the &lt;code&gt;ReplayKit&lt;/code&gt; framework in 2015 with iOS 9. They have refined and expanded it since then, but it still has a small API. &lt;code&gt;ReplayKit&lt;/code&gt; uses the same shared components as AirPlay and the device screen recorder (accessible from the device Control Center). For this reason, &lt;code&gt;ReplayKit&lt;/code&gt; functions will not work when using AirPlay or playing video. Also, the screen recording functions do not work on the Xcode simulators. You should test using a physical device.&lt;/p&gt;
&lt;p&gt;Your app makes all its calls to the &lt;code&gt;RPScreenRecorder.shared()&lt;/code&gt; object. This is a singleton object for your app that &lt;code&gt;ReplayKit&lt;/code&gt; provides. There is no need to store it in a variable or pass it around in your code.&lt;/p&gt;
&lt;h2 id=&quot;rolling-clip-recording&quot;&gt;Rolling Clip Recording&lt;/h2&gt;
&lt;p&gt;In iOS 15, Apple introduced rolling clip recording. &lt;code&gt;ReplayKit&lt;/code&gt; will maintain an updated video buffer of the most recent fifteen seconds. Your app can request the video at any time. &lt;code&gt;ReplayKit&lt;/code&gt; comes from &lt;code&gt;GameCenter&lt;/code&gt;. Apple’s idea for this feature is for creating a short, sharable video of the moments before an exciting event in a game. Another use can be for a user to record the steps leading up to a defect during testing. When the user is unlikely to know in advance that they will want a screen recording, rolling clips may be a good solution.&lt;/p&gt;
&lt;p&gt;To start rolling clip buffering, include a method such as this one&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RPScreenRecorder.&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;startClipBuffering&lt;/span&gt;&lt;span&gt; { err &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; err {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //either the user denied permission or something like&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //AVPlayer or AirPlay is using the shared recorder resources.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error attempting to start buffering &lt;/span&gt;&lt;span&gt;\(err.&lt;/span&gt;&lt;span&gt;localizedDescription&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Clip buffering started.&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Screen recording has privacy implications. &lt;code&gt;ReplayKit&lt;/code&gt; presents the user with a modal, requesting to record before it starts. If the user denies the request or if the buffering is unable to start, &lt;code&gt;ReplayKit&lt;/code&gt; passes an &lt;code&gt;Error&lt;/code&gt; object to the closure. Unlike other privacy modals, you do not have an opportunity to add your own language to the message. The modal appears as soon as you execute the &lt;code&gt;.startClipBuffering&lt;/code&gt; command, so be sure that the user is not surprised by it. Execute the command at a logical transition in your application. After the user grants permissions, the modal will not appear again unless eight minutes pass before the next call to &lt;code&gt;.startClipBuffering&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;permissions-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 250px) 250px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;250&quot; height=&quot;193&quot; src=&quot;https://img.ly/_astro/permissions-1_2qx6ET.webp&quot; srcset=&quot;/_astro/permissions-1_2qx6ET.webp 250w&quot;&gt;&lt;/p&gt;
&lt;p&gt;While the clip buffering runs, at any time, you can save the buffer to a URL on the device. The clip you save can be any duration up to fifteen seconds. If the recorder is unable to write the clip to disk, it will send an error.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; clipURL: URL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fileURLWithPath&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;NSTemporaryDirectory&lt;/span&gt;&lt;span&gt;()).&lt;/span&gt;&lt;span&gt;appendingPathComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\(&lt;/span&gt;&lt;span&gt;UUID&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.mov&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RPScreenRecorder.&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;exportClip&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: clipURL, &lt;/span&gt;&lt;span&gt;duration&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;TimeInterval&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;15&lt;/span&gt;&lt;span&gt;)) { err &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; err {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;An error? &lt;/span&gt;&lt;span&gt;\(err.&lt;/span&gt;&lt;span&gt;localizedDescription&lt;/span&gt;&lt;span&gt; )&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Clip saved to url &lt;/span&gt;&lt;span&gt;\(clipURL)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above stores a fifteen-second clip at a URL in the user’s temporary directory. The temporary directory gets cleared by the system, but not while the app is running. To ensure that the files are not deleted except by the user, store them in the user’s documents directory instead.&lt;br&gt;
When your app is done collecting video, use &lt;code&gt;.stopClipBuffering&lt;/code&gt; to allow the system to clean up resources and stop the recording.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RPScreenRecorder.&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;stopClipBuffering&lt;/span&gt;&lt;span&gt; { err &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; err {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Error attempting to stop buffering &lt;/span&gt;&lt;span&gt;\(err.&lt;/span&gt;&lt;span&gt;localizedDescription&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  } &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Clip buffering stopped.&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;recording-screen-and-app-audio&quot;&gt;Recording Screen and App Audio&lt;/h2&gt;
&lt;p&gt;Rolling clip buffering runs in the background. You can also allow the user to control when to start and stop screen recording. The API looks similar and is available since iOS 10.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RPScreenRecorder.&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;startRecording&lt;/span&gt;&lt;span&gt; { err &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  guard&lt;/span&gt;&lt;span&gt; err &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;span&gt; else&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(err.&lt;/span&gt;&lt;span&gt;debugDescription&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //code to display recording indicator&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As with the rolling clip buffering, the system will display a permissions dialog. If the user denies permission you will get an error and the recording will not start.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;recording-permissions-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 250px) 250px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;250&quot; height=&quot;193&quot; src=&quot;https://img.ly/_astro/recording-permissions-1_ZEkXHU.webp&quot; srcset=&quot;/_astro/recording-permissions-1_ZEkXHU.webp 250w&quot;&gt;&lt;/p&gt;
&lt;p&gt;The same eight-minute rule applies as for rolling clip recording. But, since the user has just taken some action to start recording, they should expect to see this modal.&lt;br&gt;
Apple provides a basic view controller for editing and sharing the video.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ReplayKit&lt;/code&gt; passes the &lt;code&gt;RPPreviewViewController&lt;/code&gt; to the &lt;code&gt;.stopRecording&lt;/code&gt; handler.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RPScreenRecorder.&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;stopRecording&lt;/span&gt;&lt;span&gt; { preview, err &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; preview &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; preview &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;no preview window&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //update recording controls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  preview.modalPresentationStyle &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; .overFullScreen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  preview.previewControllerDelegate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;present&lt;/span&gt;&lt;span&gt;(preview, &lt;/span&gt;&lt;span&gt;animated&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;preview&lt;/code&gt; object above is the &lt;code&gt;RPPreviewViewController&lt;/code&gt; editor. You can display it as a modal or as a popover depending on your needs. The Apple provided editor will allow the user to save to their camera roll or share the clip after editing.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;apple-editor-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 400px) 400px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;400&quot; height=&quot;866&quot; src=&quot;https://img.ly/_astro/apple-editor-1_5tScc.webp&quot; srcset=&quot;/_astro/apple-editor-1_5tScc.webp 400w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Notice in the code above that there is a &lt;code&gt;previewControllerDelegate&lt;/code&gt;. You will need to add some code to the delegate to cleanup and dismiss the editor when the user is done sharing or saving. An example is below.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;func&lt;/span&gt;&lt;span&gt; previewControllerDidFinish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt; previewController: RPPreviewViewController) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  previewController.&lt;/span&gt;&lt;span&gt;dismiss&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;animated&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;true&lt;/span&gt;&lt;span&gt;) { [&lt;/span&gt;&lt;span&gt;weak&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //reset the UI and show the recording controls&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;using-many-uiwindow-objects&quot;&gt;Using Many &lt;code&gt;UIWindow&lt;/code&gt; Objects&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;ReplayKit&lt;/code&gt; recording will only capture the main window of your application. When designing for screen recording, you can place any UI elements in a separate &lt;code&gt;UIWindow&lt;/code&gt; object. Although applications typically only have one &lt;code&gt;UIWindow&lt;/code&gt;, iOS allows many windows. A &lt;code&gt;UIWindow&lt;/code&gt; requires a &lt;code&gt;.rootViewController&lt;/code&gt; to provide content. Then use it like any other view in your application.&lt;br&gt;
The UI for this application consists of three &lt;code&gt;UIWindow&lt;/code&gt; objects.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;combined-windows&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 300px) 300px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;300&quot; height=&quot;649&quot; src=&quot;https://img.ly/_astro/combined-windows_ZrRaan.webp&quot; srcset=&quot;/_astro/combined-windows_ZrRaan.webp 300w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Apple places the status bar in a separate window from your application. The recording controls are in a separate window from the main application and will not appear in the screen recording.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;exploded-windows&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 503px) 503px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;503&quot; height=&quot;360&quot; src=&quot;https://img.ly/_astro/exploded-windows_1AjROT.webp&quot; srcset=&quot;/_astro/exploded-windows_1AjROT.webp 503w&quot;&gt;&lt;/p&gt;
&lt;p&gt;To add some recording controls or a recording indicator to your app. Create a new &lt;code&gt;UIWindow&lt;/code&gt; and add it to the same &lt;code&gt;windowScene&lt;/code&gt; as your view.&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;controlsWindow &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIWindow&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;frame&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CGRect&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;: view.frame.width, &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;45&lt;/span&gt;&lt;span&gt; +&lt;/span&gt;&lt;span&gt; view.safeAreaInsets.top)) &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;controlsWindow&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.windowScene &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; view.window&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.windowScene &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;controlsWindow&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;makeKeyAndVisible&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; recordingIndicator &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIButton.&lt;/span&gt;&lt;span&gt;systemButton&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;UIImage&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;systemName&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;record.circle&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;target&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;action&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;#selector&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;recordingToggled&lt;/span&gt;&lt;span&gt;(_:))) &lt;/span&gt;&lt;span&gt;//4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; vc &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIViewController&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;controlsWindow&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.rootViewController &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; vc &lt;/span&gt;&lt;span&gt;//5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;vc.&lt;/span&gt;&lt;span&gt;view&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;addSubview&lt;/span&gt;&lt;span&gt;(recordingIndicator) &lt;/span&gt;&lt;span&gt;//6&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;recordingIndicator.center &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CGPoint&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;x&lt;/span&gt;&lt;span&gt;: vc.&lt;/span&gt;&lt;span&gt;view&lt;/span&gt;&lt;span&gt;.center.x, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: vc.&lt;/span&gt;&lt;span&gt;view&lt;/span&gt;&lt;span&gt;.center.y &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; 20&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above creates the red/blue recording button and banner at the top of the view. It relies on a &lt;code&gt;controlsWindow&lt;/code&gt; variable created at the class level. Here is what it is doing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creates a new &lt;code&gt;UIWindow&lt;/code&gt; that is the width of the view and tall enough for the button and the safe area (the area around the notch and status bar)&lt;/li&gt;
&lt;li&gt;Adds the window to the same &lt;code&gt;windowScene&lt;/code&gt; as the current view&lt;/li&gt;
&lt;li&gt;Makes the window visible (&lt;code&gt;UIWindow&lt;/code&gt; objects are &lt;code&gt;hidden&lt;/code&gt; when created) and brings it to the front of the stack of windows&lt;/li&gt;
&lt;li&gt;Creates a button and links it to a method in your class &lt;code&gt;recordingToggled(_:)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Adds a view controller to the window so that there will be some content&lt;/li&gt;
&lt;li&gt;Adds the button to the view controller&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Because the recording indicator and button are in a separate window, they will not appear in any screen recording. Be mindful when showing modal views or alert views, as they may appear behind the extra window. A good strategy is to hide the extra window when showing these views. You can use a similar strategy to hide any other UI elements that should not appear in screen recordings.&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;Though Apple provides a basic editor, you can provide your own editing tools. The examples above showed the rolling clip editor writes the video to the disk. The regular recorder can also write the video to disk. You can stop recording using this code:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;swift&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; clipURL: URL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URL&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fileURLWithPath&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;NSTemporaryDirectory&lt;/span&gt;&lt;span&gt;()).&lt;/span&gt;&lt;span&gt;appendingPathComponent&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;\(&lt;/span&gt;&lt;span&gt;UUID&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;description&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;.mov&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;RPScreenRecorder.&lt;/span&gt;&lt;span&gt;shared&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;stopRecording&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;withOutput&lt;/span&gt;&lt;span&gt;: clipURL) { err &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //check for an error and process the url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This method would replace the &lt;code&gt;.stopRecording { preview, err in&lt;/code&gt; method shown above.&lt;br&gt;
With the video saved, you can use an editor such as the &lt;a href=&quot;https://img.ly/products/video-sdk/&quot;&gt;VideoEditor SDK&lt;/a&gt; to allow your users to add annotations, text and filters to their clips. They can even add audio or combine clips to make a great creation.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;with-editor&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 400px) 400px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;400&quot; height=&quot;866&quot; src=&quot;https://img.ly/_astro/with-editor_26cWBv.webp&quot; srcset=&quot;/_astro/with-editor_26cWBv.webp 400w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this article, you saw how to capture a rolling screen recording as well as how to capture the screen manually. Further, you saw how using an SDK such as &lt;a href=&quot;https://img.ly/products/video-sdk/&quot;&gt;VideoEditor SDK&lt;/a&gt; allows you to annotate and enhance your clips before sharing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading! We hope that you found this tutorial helpful. Feel free to reach out to us on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; with any questions, comments, or suggestions!&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2021/09/screen-record-video-on-app.jpg" medium="image"/><category>iOS App Development</category><category>iOS</category><category>ReplayKit</category><category>Apple</category><category>Mobile App Development</category><category>Video Editing</category><category>How-To</category><category>Tutorial</category></item><item><title>Almost Getting Sherlocked by Apple’s Core Image Team</title><link>https://img.ly/blog/almost-getting-sherlocked-by-apples-core-image-team-76249c370320/</link><guid isPermaLink="true">https://img.ly/blog/almost-getting-sherlocked-by-apples-core-image-team-76249c370320/</guid><pubDate>Tue, 12 Jun 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;During each World Wide Developer Conference keynote, app developers all over the world are fearing their app may &lt;a href=&quot;https://www.urbandictionary.com/define.php?term=sherlocked&quot;&gt;get sherlocked&lt;/a&gt; by Apple. This has happened to flashlight apps when Apple introduced a simple toggle in the control center, to f.lux when Apple added exactly the same functionality to macOS and iOS, in parts to Dropbox with the launch of iCloud Drive and to many other developers. This year there was only one obvious of such things, the Workflow app which was acquired a few month ago and instantly turned into Siri Shortcuts by Apple. So no real ‘sherlocking’. But when we skimmed the newest APIs, release notes and session descriptions after the keynote, we found out that the technology behind our Portrait by img.ly app might have gotten sherlocked by the Core Image team. The reason is perfectly summarized in this tweet:&lt;/p&gt;
&lt;blockquote class=&quot;twitter-tweet&quot;&gt;&lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&gt;“Portrait Segmentation API&lt;br&gt;&lt;br&gt;A new API for third-party developers allows for the separation of layers in a photo, such as separating the background from the foreground.”&lt;br&gt;&lt;br&gt;Hi &lt;a href=&quot;https://twitter.com/halidecamera?ref_src=twsrc%5Etfw&quot;&gt;@halidecamera&lt;/a&gt;&lt;/p&gt;— Preshit Deorukhkar (@preshit) &lt;a href=&quot;https://twitter.com/preshit/status/1003722022466121730?ref_src=twsrc%5Etfw&quot;&gt;June 4, 2018&lt;/a&gt;&lt;/blockquote&gt;&lt;figcaption&gt;How we found out about the new Portrait Segmentation API.&lt;/figcaption&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Quick spoiler&lt;/em&gt;: While Core Images new API is impressive, our technology is still ahead in terms of general availability, required hardware and cross-platform compatibility. But let’s start from the beginning:&lt;/p&gt;
&lt;p&gt;We started working on automated image segmentation in 2016 and decided to focus on portraits in 2017. After spending most of the year on &lt;a href=&quot;https://img.ly/blog/when-creativity-meets-a-i-f48ee9a3612d/&quot;&gt;building a custom deep learning model&lt;/a&gt;, running hundreds of experiments and tweaking our post processing pipeline, we finally released the Portrait app in fall 2017. The app is able to generate portrait segmentations in real-time and allows the user to take a nice selfie, which is then automatically stylized using the segmentation mask and some post processing. This allows for sophisticated effects and got great reception all over the world. The app even got featured multiple times in many different countries. Recently we started looking into inferring depth, as we wanted to bring depth based features to our &lt;a href=&quot;https://img.ly/products/photo-sdk/&quot;&gt;PhotoEditor SDK&lt;/a&gt;, but needed support on more iPhone devices, Android and especially required depth data for existing images.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 600px) 600px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;600&quot; height=&quot;720&quot; src=&quot;https://img.ly/_astro/1-gGBarteLQ3HLyjk_BOWRqg_1P3HFh.webp&quot; srcset=&quot;/_astro/1-gGBarteLQ3HLyjk_BOWRqg_1P3HFh.webp 600w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 400px) 400px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;400&quot; height=&quot;510&quot; src=&quot;https://img.ly/_astro/1-wIEvPF-CwcYYkguN3hKyrQ_3DVOn.webp&quot; srcset=&quot;/_astro/1-wIEvPF-CwcYYkguN3hKyrQ_3DVOn.webp 400w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Stylized selfies created using the Portrait app.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 400px) 400px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;400&quot; height=&quot;533&quot; src=&quot;https://img.ly/_astro/1-SbpOElYlADGGsomoQWo1UQ_Xq8J4.webp&quot; srcset=&quot;/_astro/1-SbpOElYlADGGsomoQWo1UQ_Xq8J4.webp 400w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Reading the tweet above, it looked like Apple just added the Portrait apps core functionality to the Core Image framework, making it available to all developers and users running or targeting iOS 12. A little worried about the new competition, we immediately looked into the docs and eagerly waited for the session on last Thursday to see if we’d soon need to find a new unique way of using technology to empower creativity.&lt;/p&gt;
&lt;p&gt;We found out that all you need to do in order to get a segmentation mask along with your image is toggle the following flags when requesting a photo capture from your &lt;code&gt;AVCapturePhotoOutput&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;plaintext&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let settings: AVCapturePhotoSettings = AVCapturePhotoSettings()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;settings.isDepthDataDeliveryEnabled = true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;settings.isPortraitEffectsMatteDeliveryEnabled = true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After the photo is captured, you’re then able to extract the matte in the &lt;code&gt;didFinishProcessingPhoto&lt;/code&gt; callback. And voila, you now have the captured image, a depth map and the mask separating fore- and background available:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 400px) 400px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;400&quot; height=&quot;533&quot; src=&quot;https://img.ly/_astro/1-wNVP-7aqqOfyDpnWcKEc0w_ZrbWCi.webp&quot; srcset=&quot;/_astro/1-wNVP-7aqqOfyDpnWcKEc0w_ZrbWCi.webp 400w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 400px) 400px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;400&quot; height=&quot;533&quot; src=&quot;https://img.ly/_astro/1-k3bRAeLEIxoZ0otelumcdQ_Z1Vd9sB.webp&quot; srcset=&quot;/_astro/1-k3bRAeLEIxoZ0otelumcdQ_Z1Vd9sB.webp 400w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Input image, depth map and portrait matte generated by Core Image. (Source)&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 400px) 400px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;400&quot; height=&quot;533&quot; src=&quot;https://img.ly/_astro/1-_ti3BobfUiGJmsb8MOkJvQ_Z1FhYlR.webp&quot; srcset=&quot;/_astro/1-_ti3BobfUiGJmsb8MOkJvQ_Z1FhYlR.webp 400w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Of course, we quickly spun up our own models and algorithms and compared the portrait matte, as well as the depth map to Apples results. As there is no way to rerun Apples matting algorithm for an existing image, we took one of Apples samples and compared their mask with results from our model:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 400px) 400px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;400&quot; height=&quot;533&quot; src=&quot;https://img.ly/_astro/1-wNVP-7aqqOfyDpnWcKEc0w_ZrbWCi.webp&quot; srcset=&quot;/_astro/1-wNVP-7aqqOfyDpnWcKEc0w_ZrbWCi.webp 400w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 225px) 225px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;225&quot; height=&quot;299&quot; src=&quot;https://img.ly/_astro/1-Tr1guJROO6OdL0BDhMlJ4g_ZGYUc4.webp&quot; srcset=&quot;/_astro/1-Tr1guJROO6OdL0BDhMlJ4g_ZGYUc4.webp 225w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Input image, depth map and portrait matte generated by our algorithms and models.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 400px) 400px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;400&quot; height=&quot;533&quot; src=&quot;https://img.ly/_astro/1-2Vj62Jh6cBafzw0kN1xVuA_2h3Jco.webp&quot; srcset=&quot;/_astro/1-2Vj62Jh6cBafzw0kN1xVuA_2h3Jco.webp 400w&quot;&gt;&lt;/p&gt;
&lt;p&gt;While it may not have been the greatest idea to pick Apples shiny developer example, Apples mask is a little more detailed and for this particular image our model missed parts of the neck on the left. But overall we were still very happy with our results, especially when considering, that everything was generated from the plain image and didn’t require any dedicated hardware like dual cameras or a TrueDepth sensor. We did of course expect a superior depth map from Apple, as we’re clearly lacking data and are still actively working on the depth model used to generate the image above. But Apples depthmap interestingly has some issues around the neck as well, despite their use of a dual camera system. And keep in mind, that our results were all processed on the mobile device, entirely based on the RGB data contained in the image, and could be repeated on an Android phone, older iOS devices and even your browser.&lt;/p&gt;
&lt;p&gt;When we wanted to try more samples, we quickly noticed the major limitations of Apples API: As the portrait matte capture is only available in combination with depth data, it’s limited to the iPhone 7 Plus, 8 Plus and iPhone X. And, most important to us, portrait mattes taken using the front camera are exclusively limited to the iPhone X and it’s TrueDepth sensor array. So for our Portrait app, switching to the new API would require us to ditch our live preview and go iPhone X and iOS 12 only. This was enough to calm our minds and we started thinking deeper about Apples technology:&lt;/p&gt;
&lt;p&gt;While the depth requirement is annoying, it also explains the higher quality of Apples predictions. Our model is trained to solve the individual problems of portrait matting and depth map inference as a whole, but Apple is able to focus on improving the edges, while the ‘rough’ foreground/background segmentation is handled by masking based on the depth of the face detected within the image. We might be able to combine both models as well, but that would make the segmentation model currently used in the Portrait app obsolete and would most certainly kill the real-time functionality.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A stylized portrait of the sample image, created using our app.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 800px) 800px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;800&quot; height=&quot;1066&quot; src=&quot;https://img.ly/_astro/1-suYaswJDYtDiIFTs3w-aHQ_Z29DuwE.webp&quot; srcset=&quot;/_astro/1-suYaswJDYtDiIFTs3w-aHQ_ZJM4aC.webp 640w, /_astro/1-suYaswJDYtDiIFTs3w-aHQ_Z23jUhb.webp 750w, /_astro/1-suYaswJDYtDiIFTs3w-aHQ_Z29DuwE.webp 800w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Overall, Apples technology is, as almost always, pretty impressive and the portrait matte generation works flawless, but we can now say, that for the use within our app, we reach a good quality with our current algorithms and think that a real-time preview is more important to our users. For our &lt;a href=&quot;https://img.ly/products/photo-sdk/&quot;&gt;PhotoEditor SDK&lt;/a&gt; we’d happily use the high fidelity maps generated by iOS 12, but the hardware requirements are not yet suitable for SDK deployment. Our algorithms on the other hand, neither require a depth map, nor are we limited to processing after the image was captured, but can perform real-time inference on devices as old as the iPhone 6S. And the best of all: Once a TrueDepth camera is more common and everyone is running iOS 12, we can still use the Portrait Segmentation API and enjoy it’s simplicity.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks for reading! To stay in the loop, subscribe to our &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;Newsletter&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Malte</dc:creator><media:content url="https://blog.img.ly/2020/03/1-EXAAKrECABcaDEs3BtfzgQ.jpeg" medium="image"/><category>Apple</category><category>WWDC</category><category>Core Image</category><category>iOS</category><category>Software Development</category><category>Tech</category><category>How-To</category><category>Insights</category></item></channel></rss>