<?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>iOS – IMG.LY Blog</title><description>Posts tagged iOS on the IMG.LY blog.</description><link>https://img.ly/blog/tag/ios/</link><language>en-us</language><image><url>https://img.ly/apple-touch-icon.png</url><title>iOS – IMG.LY Blog</title><link>https://img.ly/blog/tag/ios/</link></image><atom:link href="https://img.ly/blog/tag/ios/rss.xml" rel="self" type="application/rss+xml"/><generator>Astro</generator><lastBuildDate>Tue, 09 Jun 2026 09:48:33 GMT</lastBuildDate><ttl>60</ttl><item><title>Building Custom Variable SF Symbols in Figma for our CreativeEditor SDK for iOS</title><link>https://img.ly/blog/building-custom-variable-sf-symbols-in-figma-for-our-creativeeditor-sdk-for-ios/</link><guid isPermaLink="true">https://img.ly/blog/building-custom-variable-sf-symbols-in-figma-for-our-creativeeditor-sdk-for-ios/</guid><description>How we went from community Figma plugins to our own “SF Symbol Generator” to create custom SF Symbols for our CreativeEditor SDK (CE.SDK) for iOS.</description><pubDate>Tue, 13 May 2025 07:00:06 GMT</pubDate><content:encoded>&lt;p&gt;If you’ve ever wrestled with vector paths in Figma just to get a custom variable SF Symbol out the door, you know the frustration: one missed node here, one stray smoothing there, and suddenly your interpolation breaks. Plus, you would design your icons within an SVG file you exported beforehand from the SF Symbols App.&lt;/p&gt;
&lt;p&gt;When Apple introduced &lt;a href=&quot;https://developer.apple.com/sf-symbols/&quot;&gt;SF Symbols&lt;/a&gt; in 2019, they changed the game for iOS design. Suddenly, we had access to thousands of scalable, weight-variable icons that integrate seamlessly with system type. Our &lt;a href=&quot;https://img.ly/products/creative-sdk&quot;&gt;CreativeEditor SDK&lt;/a&gt; (CE.SDK) is a customizable design and video editor you can integrate into your product. Yet, as it grew, we needed icons as unique as its use cases—icons that simply didn’t exist in Apple’s library.&lt;/p&gt;
&lt;p&gt;In this article, I’ll share our journey from relying on two community Figma plugins—SF Symbols Organizer and Vector Path Editor—to building our own tool, &lt;a href=&quot;https://www.figma.com/community/plugin/1487107089082698461/sf-symbol-generator&quot;&gt;&lt;strong&gt;SF Symbol Generator&lt;/strong&gt;&lt;/a&gt;. Along the way, I’ll show you how we wrestled with aspect ratios, node counts, and path order, and how we eventually streamlined the entire process into a few simple clicks.&lt;/p&gt;
&lt;h2 id=&quot;what-are-variable-sf-symbols&quot;&gt;What Are Variable SF Symbols?&lt;/h2&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/imgly-team/vibe-coding/recording-of-SF-Symbols-app.mp4&quot; controls autoplay muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;At their core, variable SF Symbols behave much like variable fonts: they allow you to interpolate smoothly between multiple “master” designs, rather than shipping separate static assets for each weight. To create one, you supply three distinct versions of your icon—typically &lt;em&gt;Ultra Light&lt;/em&gt;, &lt;em&gt;Regular&lt;/em&gt;, and &lt;em&gt;Black&lt;/em&gt;—each sharing the exact same path structure and node count. When the SF Symbols app or iOS runtime renders your symbol, it calculates intermediate weights on the fly by linearly interpolating between those three masters. This approach not only shrinks your asset footprint but also gives you rich typographic control, letting you fine-tune how heavy or light an icon appears in any context.&lt;/p&gt;
&lt;h2 id=&quot;the-early-struggle&quot;&gt;The Early Struggle&lt;/h2&gt;
&lt;p&gt;From the moment we discovered the Figma plugin &lt;a href=&quot;https://www.figma.com/community/plugin/1265484574589944515/sf-symbols-optimizer&quot;&gt;&lt;strong&gt;SF Symbols Organizer&lt;/strong&gt;&lt;/a&gt;, it became an indispensable part of our workflow. Suddenly, what had been a tedious, manual export process turned into a single click: select your three masters, batch-export them as SF Symbols-ready SVGs, and you were halfway home. The plugin’s enforcement of a 1:1 aspect-ratio rule was actually a blessing when you’re just getting started, ensuring that every icon fit out of the gate.&lt;/p&gt;
&lt;p&gt;However, as our icon library expanded beyond a handful of simple glyphs, we began to feel the boundaries of that magic square. Some of our more ambitious designs needed wider canvases or asymmetrical compositions, and shoehorning them into perfect squares led to awkward scaling and extra cropping steps. Meanwhile, although &lt;strong&gt;SF Symbols Organizer&lt;/strong&gt; reliably generated valid SVGs, it didn’t warn us about deeper issues—like mismatched node counts or flipped path directions—until we imported into the SF Symbols app and discovered the interpolation errors firsthand.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;This plugin exports icons created in Figma as a SF Symbol SVG bundle.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 293px) 293px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;293&quot; height=&quot;448&quot; src=&quot;https://img.ly/_astro/image_1KRyNB.webp&quot; srcset=&quot;/_astro/image_1KRyNB.webp 293w&quot;&gt;&lt;/p&gt;
&lt;p&gt;That’s where &lt;a href=&quot;https://www.figma.com/community/plugin/1391765568770221941/vector-path-editor&quot;&gt;Vector Path Editor&lt;/a&gt; pitched in. It couldn’t automate our entire workflow, but it did let us quickly correct path order and direction when Figma’s flattening shuffled our nodes. Even with that help, though, we still found ourselves jumping between tools to catch errors after the fact—proof that, while both plugins were fantastic at what they did, we needed something more proactive.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Change path order and direction (and fill rules if you need that 😉, too) with this plugin.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 395px) 395px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;395&quot; height=&quot;434&quot; src=&quot;https://img.ly/_astro/image-1_Z2fiS7U.webp&quot; srcset=&quot;/_astro/image-1_Z2fiS7U.webp 395w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Yet, as powerful as these two plugins were together, their reactive nature meant we still spent long sessions hopping between Figma and Apple’s toolchain, correcting mistakes after they happened instead of catching them up front. It was a great start—and without these plugins, we wouldn’t have made it this far—but we knew there had to be a way to bake most of the missing quality checks into our design process itself.&lt;/p&gt;
&lt;h2 id=&quot;why-we-wanted-sf-symbol-generator&quot;&gt;Why We Wanted “SF Symbol Generator”&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;Generate static of variable SF Symbols with the SF Symbol Generator plugin.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1920px) 1920px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1920&quot; height=&quot;1080&quot; src=&quot;https://img.ly/_astro/Slide-16_9---v1.0.0_ZfH6Vg.webp&quot; srcset=&quot;/_astro/Slide-16_9---v1.0.0_1kcS5V.webp 640w, /_astro/Slide-16_9---v1.0.0_Z42non.webp 750w, /_astro/Slide-16_9---v1.0.0_azGtA.webp 828w, /_astro/Slide-16_9---v1.0.0_Z1jLQKi.webp 1080w, /_astro/Slide-16_9---v1.0.0_sQEc.webp 1280w, /_astro/Slide-16_9---v1.0.0_VJRFd.webp 1668w, /_astro/Slide-16_9---v1.0.0_ZfH6Vg.webp 1920w&quot;&gt;&lt;/p&gt;
&lt;p&gt;After enough trial and error, we realized we needed more than just export and pray. We needed flexibility and foresight.&lt;/p&gt;
&lt;p&gt;First, we didn’t want our icons locked into a square. Some of our designs needed wider canvases. Second, we craved live feedback. We wanted to know if our three icons had matching node counts, aligned first nodes, and consistent path winding before exporting. Lastly, we longed for a single, unified workflow that could handle scanning, validating, and exporting without leaving Figma.&lt;/p&gt;
&lt;p&gt;It quickly became clear that the only way to achieve this was to build our own plugin. And so &lt;a href=&quot;https://www.figma.com/community/plugin/1487107089082698461/sf-symbol-generator&quot;&gt;SF Symbol Generator&lt;/a&gt; was born.&lt;/p&gt;
&lt;h2 id=&quot;inside-sf-symbol-generator&quot;&gt;Inside SF Symbol Generator&lt;/h2&gt;
&lt;p&gt;SF Symbol Generator transforms the way we build custom symbols. Rather than flattening paths prematurely, it encourages us to keep boolean groups intact, flattening only at export time. When you run the plugin, it scans all three masters in situ, then displays a detailed readout of node counts, first-node anchors, and path directions. Any discrepancies are highlighted in red, so you can go back to your designs and fix them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;*Pro Tip:&lt;/strong&gt; Sometimes Figma’s automatic flattening doesn’t handle rounded corners cleanly and can introduce extra nodes. If the plugin’s scan flags a weight with mismatched node counts or errant anchors, you can manually flatten that master ahead of time, clean up extra nodes using Figma’s vector editing tools, and then re-run the scan to ensure perfect parity.*&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;For simple bezier curves, Figma sometimes still adds extra nodes that need cleaning up.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2010px) 2010px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2010&quot; height=&quot;907&quot; src=&quot;https://img.ly/_astro/image-2_Z2pqOYN.webp&quot; srcset=&quot;/_astro/image-2_NHQz1.webp 640w, /_astro/image-2_ZjLgAB.webp 750w, /_astro/image-2_Z1kK9V6.webp 828w, /_astro/image-2_Z1QBkIj.webp 1080w, /_astro/image-2_ZJ6iNQ.webp 1280w, /_astro/image-2_ZTPqcz.webp 1668w, /_astro/image-2_Z2pqOYN.webp 2010w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Once everything checks out, a single click exports a ready-to-use variable SVG bundle—no detours into external editors required.&lt;/p&gt;
&lt;h2 id=&quot;our-step-by-step-workflow&quot;&gt;Our Step-By-Step Workflow&lt;/h2&gt;
&lt;p&gt;Our process now is refreshingly simple. We start in Figma by creating a 32-pixel-tall frame that can have any width you need.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Start with a ∞×32 Frame with a safe-zone of 4px.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2960px) 2960px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2960&quot; height=&quot;1284&quot; src=&quot;https://img.ly/_astro/image-3_1ppw3a.webp&quot; srcset=&quot;/_astro/image-3_Zqp2mS.webp 640w, /_astro/image-3_1PT0ut.webp 750w, /_astro/image-3_23GBzM.webp 828w, /_astro/image-3_v94Kb.webp 1080w, /_astro/image-3_hhgjn.webp 1280w, /_astro/image-3_19TJWK.webp 1668w, /_astro/image-3_Z12OfVh.webp 2048w, /_astro/image-3_Z5SVld.webp 2560w, /_astro/image-3_1ppw3a.webp 2960w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Inside that frame, we draw our Regular-weight icon using a 2.45-pixel stroke and a 3-pixel corner radius. We then duplicate the frame twice and adjust the strokes for Ultra Light (0.8 px, 2.5 px radius) and Black (4.95 px, 3.5 px radius). iOS commonly incorporates softer corners in their icon design, and you can achieve them with Figma’s corner smoothing. Though, it’s important to keep Figma’s corner smoothing at 0%, because even a sliver of smoothing can throw off interpolation. We also center-align our strokes wherever possible so that the shapes grow uniformly when the weight changes.&lt;/p&gt;

























&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Stroke size&lt;/th&gt;&lt;th&gt;Border radius&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Ultra Thin&lt;/td&gt;&lt;td&gt;0.8&lt;/td&gt;&lt;td&gt;2.5&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Regular&lt;/td&gt;&lt;td&gt;2.45&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Black&lt;/td&gt;&lt;td&gt;4.95–3.5&lt;/td&gt;&lt;td&gt;3.5&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;img alt=&quot;Three weights are needed for SF Symbol Generator to interpolate between them.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;868&quot; src=&quot;https://img.ly/_astro/image-4_PwRpg.webp&quot; srcset=&quot;/_astro/image-4_Zvff6f.webp 640w, /_astro/image-4_Z2tqQEl.webp 750w, /_astro/image-4_1XQoQq.webp 828w, /_astro/image-4_Z1BhaVV.webp 1080w, /_astro/image-4_Z1P8YnJ.webp 1280w, /_astro/image-4_Z1i6Hzq.webp 1668w, /_astro/image-4_PwRpg.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;p&gt;With your three master frames selected, launch SF Symbol Generator. In the plugin’s Variable panel, assign each frame to its corresponding weight—Ultra Light, Regular, and Black. If your frames are named correctly (for example, “Icon Ultra Light,” “Icon Regular,” “Icon Black”), the plugin will detect and map them automatically. As soon as each weight is assigned, the previews immediately show node counts, first-node positions, and path directions. Any mismatches appear in red, at which point you might need to go back and manually flatten that master, clean up extra nodes (for example, Figma sometimes adds unwanted anchors on rounded corners), and re-scan them again. When everything looks great, just hit Export to generate your ready-to-use variable SVG bundle.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/imgly-team/vibe-coding/using-SF-Symbol-generator-on-3-Icons.mp4&quot; controls autoplay muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h2 id=&quot;importing-into-the-sf-symbols-app&quot;&gt;Importing into the SF Symbols App&lt;/h2&gt;
&lt;p&gt;Once your variable SVG bundle is exported, the final import into Apple’s SF Symbols app is straightforward. Open the SF Symbols application (available on Apple’s developer site), switch to the &lt;strong&gt;Custom Symbols&lt;/strong&gt; tab, and drag your SVG file into the panel. Your new icons will appear alongside Apple’s built-in set, ready to be organized into collections or shared with teammates. When you return to Xcode, your custom symbols show up just like any system glyph, complete with full support for dynamic weights.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/imgly-team/vibe-coding/adding-custom-symbol-to-SF-symbols.mp4&quot; controls autoplay muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h3 id=&quot;optional-extra-step-preparing-more-complex-symbols&quot;&gt;Optional Extra Step: Preparing More Complex Symbols&lt;/h3&gt;
&lt;p&gt;If your icon consists of multiple layers or requires special styling per layer, take this extra step to ensure it renders perfectly in all weights. After dragging your SVG into the SF Symbols app:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Select your newly imported symbol and click the &lt;strong&gt;Rendering Inspector&lt;/strong&gt; &lt;em&gt;(the segmented button with the brush icon)&lt;/em&gt; on the right side.&lt;/li&gt;
&lt;li&gt;You’ll see each layer exposed as a separate sub-path. You can create more layers and even group them to change them individually as well.&lt;/li&gt;
&lt;li&gt;Tweak opacity, colors, or layer order at different render modes to achieve the exact look you want.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img alt=&quot;Use the SF Symbols App to prepare your more complex symbols accordingly.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1347px) 1347px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1347&quot; height=&quot;953&quot; src=&quot;https://img.ly/_astro/image-5_1GC8jI.webp&quot; srcset=&quot;/_astro/image-5_frk19.webp 640w, /_astro/image-5_ZGWE1K.webp 750w, /_astro/image-5_10HQ7a.webp 828w, /_astro/image-5_1lhxg4.webp 1080w, /_astro/image-5_j5AwY.webp 1280w, /_astro/image-5_1GC8jI.webp 1347w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Once you’re satisfied, select and export the symbol again from the SF Symbols app &lt;em&gt;(File &gt; Export Symbol)&lt;/em&gt;, and it’s truly ready for production.&lt;/p&gt;
&lt;h2 id=&quot;lessons-learned&quot;&gt;Lessons Learned&lt;/h2&gt;
&lt;p&gt;Building SF Symbol Generator taught us several key lessons.&lt;br&gt;
First, node parity is king: the number and order of path segments must match across masters. Early validation saves hours, since catching mismatches before export means fewer cycles in Apple’s SF Symbols app.&lt;br&gt;
Second, non-destructive workflows pay dividends: boolean groups let you iterate quickly without losing stroke data.&lt;br&gt;
And finally, automation is your friend: what once took dozens of manual steps now happens in a single click.&lt;/p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Variable SF Symbols unlock a level of typographic and iconographic dynamism that feels truly native to iOS. While community tools like SF Symbols Organizer and Vector Path Editor can get you pretty far already, building our own SF Symbol Generator transformed our workflow from reactive to proactive. Now, we design freely with any aspect ratio, validate instantly, and export with confidence—no more praying, just seamless symbol-making.&lt;/p&gt;
&lt;p&gt;For our CE.SDK for iOS, we’ll be gradually rolling out updated Custom Symbols created with &lt;strong&gt;SF Symbol Generator&lt;/strong&gt;, ensuring every icon feels right at home on Apple devices. Stay tuned for those releases!&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;We are gradually updating our old Symbols with newer ones – that look and feel more native.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;642&quot; src=&quot;https://img.ly/_astro/Image-8_2o1l0p.webp&quot; srcset=&quot;/_astro/Image-8_1rfREl.webp 640w, /_astro/Image-8_Z1lCdhe.webp 750w, /_astro/Image-8_Z18OBbU.webp 828w, /_astro/Image-8_Z2eraUU.webp 1080w, /_astro/Image-8_Z2siYmI.webp 1280w, /_astro/Image-8_2o1l0p.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you’re ready to streamline your own custom SF Symbol workflow, check out &lt;a href=&quot;https://www.figma.com/community/plugin/1487107089082698461/sf-symbol-generator&quot;&gt;&lt;strong&gt;SF Symbol Generator&lt;/strong&gt;&lt;/a&gt; on the Figma Community and take it for a spin. Happy symbol-making!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;3,000+ creative professionals gain early access to new features and updates—don’t miss out, and&lt;/strong&gt; &lt;a href=&quot;https://share.hsforms.com/1IgAOV1wASXGPnFG4ZPLejg1hk3i?ref=img.ly&quot;&gt;&lt;strong&gt;subscribe&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;to our newsletter.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Dustin</dc:creator><media:content url="https://blog.img.ly/2025/05/Building-Custom-Variable-SF-Symbols-in-Figma-for-our-iOS-SDK-2.png" medium="image"/><category>iOS</category><category>Figma</category><category>Swift</category><category>SF Symbols</category></item><item><title>How to Build a TikTok Clone for iOS with Swift &amp; CreativeEditor SDK</title><link>https://img.ly/blog/how-to-build-a-tiktok-clone-for-ios/</link><guid isPermaLink="true">https://img.ly/blog/how-to-build-a-tiktok-clone-for-ios/</guid><description>Learn how to build a TikTok clone for iOS with Swift and CreativeEditor SDK with this step-by-step in-depth tutorial.</description><pubDate>Thu, 30 Mar 2023 11:20:26 GMT</pubDate><content:encoded>&lt;p&gt;Now that TikTok is facing a ban in the US any day now, we better wait in the wings with an alternative ready to go and scoop up those millions of hobby dancers, micro bloggers, and would-be influencers.&lt;/p&gt;
&lt;p&gt;In this article, you’ll learn how to use Swift, SwiftUI, and the IMG.LY CreativeEditor SDK to build a simple iOS app for recording, editing, and viewing short videos. This app features views providing core functionalities similar to making a post in a video-sharing platform like TikTok.&lt;/p&gt;
&lt;p&gt;The editing controller is the central hub. It allows users to capture video clips and enhance their videos by adding filters, stickers, music, and other effects. CreativeEditor SDK calls the overall project a “scene” and the parts (clips, stickers, text, etc.) “blocks”. Once the scene is ready, the user can compose it into a standard video file and export it. Finally, the playback controller showcases the finished projects using Apple’s standard VideoPlayer struct. For recording and editing, the CreativeEditor SDK’s Camera and Video Editor handle the heavy lifting, enabling swift development of a robust app. You can easily extend its features, filters, and creative options as your users demand.&lt;/p&gt;
&lt;p&gt;Follow along to see how to capture video, enhance it, and export the finished product as a polished movie file.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-your-project&quot;&gt;Setting Up Your Project&lt;/h2&gt;
&lt;p&gt;You can try out the TikTok clone by cloning the &lt;a href=&quot;https://github.com/imgly/tiktok-clone-ios-cesdk&quot;&gt;GitHub repository supporting the article&lt;/a&gt;. Otherwise, follow this step-by-step tutorial and learn how to build the TikTok clone with CreativeEditor SDK by yourself.&lt;/p&gt;
&lt;p&gt;To add the CreativeEditor SDK to an Xcode project, include it with your other package dependencies. Use the URL below to add the package, either to your &lt;code&gt;Package.swift&lt;/code&gt; file or using &lt;code&gt;File &gt; Add Packages...&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;astro-code github-dark&quot; tabindex=&quot;0&quot; data-language=&quot;bash&quot;&gt;&lt;code&gt;&lt;span class=&quot;line&quot;&gt;&lt;span&gt;https://github.com/imgly/IMGLYUI-swift&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The package includes a number of different frameworks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IMGLYUI, an umbrella framework of all of the different editors and a camera.&lt;/li&gt;
&lt;li&gt;IMGLYCamera, a standalone camera View. This is the same camera as the editors use.&lt;/li&gt;
&lt;li&gt;IMGLYApparelEditor, IMGLYDesignEditor, IMGLYPostcardEditor, and IMGLYVideoEditor which are four different configurations to support different use cases.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these frameworks uses the IMG.LY Engine for image processing. To include them in your project, navigate to the ‘Frameworks, Libraries, and Embedded Content’ section in your app’s General settings. Click the ’+’ button and select the desired frameworks. Initially, consider adding the IMGLYUI package, as it encompasses all other packages. Before releasing your app, review and include only the necessary components to minimize app size and avoid unused code.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Adding a framework to the app&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 590px) 590px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;590&quot; height=&quot;646&quot; src=&quot;https://img.ly/_astro/frameworks_ZpiIEt.webp&quot; srcset=&quot;/_astro/frameworks_ZpiIEt.webp 590w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Once Xcode downloads and resolves the packages, you just need to &lt;code&gt;include IMGLYVideoEditor&lt;/code&gt; in any of the files that will reference the editor in your app. When you’re working with the underlying engine directly you’ll also want to &lt;code&gt;include IMGLYEngine&lt;/code&gt;. You’ll only work with the engine as you start to customize the workflow.&lt;/p&gt;
&lt;h2 id=&quot;asking-for-permissions&quot;&gt;Asking for Permissions&lt;/h2&gt;
&lt;p&gt;Before any app can access the phone’s microphone, camera, documents or photo library, the user must specifically give permission to record audio or video as well as access the user’s Photo library. If your app doesn’t secure these permissions properly before trying to use the camera the app will crash and also probably won’t get through Apple’s app review. In earlier versions of iOS you could develop an app on the Simulator without asking permissions, but always on the device the app will crash. In current versions of iOS the app crashes regardless of platform. The dialogue requesting permissions is a system dialog and looks like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;permission dialog&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 820px) 820px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;820&quot; height=&quot;453&quot; src=&quot;https://img.ly/_astro/permission1_ZWFkBh.webp&quot; srcset=&quot;/_astro/permission1_1LPnuv.webp 640w, /_astro/permission1_OFysD.webp 750w, /_astro/permission1_ZWFkBh.webp 820w&quot;&gt;&lt;/p&gt;
&lt;p&gt;You do not have the ability to change this dialog. You can supply the reason you are asking for the permission using a key in the &lt;code&gt;info.plist&lt;/code&gt; file in the Xcode project. In the example above “Lets you record videos” is in the &lt;code&gt;info.plist&lt;/code&gt;. For the video camera you will have to ask for video and audio permission. The first step is to add a short message to the user in the &lt;code&gt;info.plist&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Info plist example&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 723px) 723px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;723&quot; height=&quot;102&quot; src=&quot;https://img.ly/_astro/infoplist_FispA.webp&quot; srcset=&quot;/_astro/infoplist_1Jamxn.webp 640w, /_astro/infoplist_FispA.webp 723w&quot;&gt;&lt;/p&gt;
&lt;p&gt;For video the key to add is &lt;code&gt;NSCameraUsageDescription&lt;/code&gt; and for the microphone you need to add &lt;code&gt;NSMicrophoneUsageDescription&lt;/code&gt;. Whenever your app attempts to use the camera, iOS will first check to see if the user has already granted access. If not, it will then display the dialogs using your entries in the &lt;code&gt;info.plist&lt;/code&gt; or crash if you have not provided entries. The user might be surprised by these dialog boxes and may accidentally tap &lt;code&gt;Don&apos;t Allow&lt;/code&gt; if they are trying to quickly launch the camera. It is better practice to set up some kind of onboarding view and secure the permissions before you need them. You might have a view that explains what you are going to ask for and then displays the permission dialog.&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;switch&lt;/span&gt;&lt;span&gt; AVCaptureDevice.&lt;/span&gt;&lt;span&gt;authorizationStatus&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .video) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  case&lt;/span&gt;&lt;span&gt; .authorized&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //the user has authroized permission!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    break&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  case&lt;/span&gt;&lt;span&gt; .denied&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //the user has previously denied permission!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //perhaps we should ask them again&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    break&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  case&lt;/span&gt;&lt;span&gt; .restricted&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //the user cannot grant permission!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    break&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  case&lt;/span&gt;&lt;span&gt; .notDetermined&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //the user has never been asked for permission&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //so let&apos;s ask them now&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    AVCaptureDevice.&lt;/span&gt;&lt;span&gt;requestAccess&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .video) { accessGranted &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; accessGranted {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        //the user has authorized permission!&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;        //the user has denied permission!&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;  @unknown&lt;/span&gt;&lt;span&gt; default:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    break&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 snippet above lets your app read the permission status for the video camera and ask for permission if it has not been granted. To request access to the microphone pass &lt;code&gt;.audio&lt;/code&gt; to the &lt;code&gt;AVCaptureDevice.authorizationStatus(for:)&lt;/code&gt; and &lt;code&gt;AVCaptureDevice.requestAccess(for:)&lt;/code&gt; functions. The &lt;code&gt;AVCaptureDevice.requestAccess(for:)&lt;/code&gt; command is what displays the system dialog actually requesting access. The &lt;code&gt;accessGranted&lt;/code&gt; variable reports back to your app what the user chose. You can take care of getting the permissions any time but when the CreativeEditor SDK first launches it will attempt to access the camera, so the dialog will appear if the user has not already granted permission.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;.restricted&lt;/code&gt; case is fairly new. In this case, there are policies on the phone that prohibit the user from granting permission to your app. In addition to asking permissions during onboarding, it is good practice to check for permission every time before you attempt to run any code that uses the camera. The user can change permissions using the Settings app on their phone at any time. If the user has denied permissions and you present the video camera anyway, your app will record black video frames and silence on audio tracks.&lt;/p&gt;
&lt;p&gt;In addition to asking for camera and microphone permissions, your app will probably want to access the photos on the user’s phone. You will need to add an entry to the &lt;code&gt;info.plist&lt;/code&gt; for &lt;code&gt;NSPhotoLibraryUsageDescription&lt;/code&gt;. As with the camera and the microphone, the dialog will appear when the Video Editor first attempts access, but you can give your user some ability to grant permission during onboarding. For the user’s photos, you can check the authorization status using&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; status &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; PHPhotoLibrary.&lt;/span&gt;&lt;span&gt;authroizationStatus&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .readWrite)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As with the camera, the photo library has &lt;code&gt;PHPhotoLibrary.requestAuthroization(for: .readWrite)&lt;/code&gt; but instead of just returning a “granted” or “denied” status, this command returns the actual new status. In addition to the status values for the camera, the photo library may return a &lt;code&gt;.limited&lt;/code&gt; status meaning that the user has granted permission to only some of the photos. If the user has chosen to share only some of their photos, Apple provides some views you can present so that the user can manage the photos. Any videos that your app saves to the user’s photo library will always be available when the user has chosen &lt;code&gt;.limited&lt;/code&gt;. You can read more about how to work with permissions and the user’s photo library by reading this Apple article &lt;a href=&quot;https://developer.apple.com/documentation/photokit/delivering-an-enhanced-privacy-experience-in-your-photos-app&quot;&gt;Delivering an Enhanced Privacy Experience in Your Photos App&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;making-video-recordings&quot;&gt;Making Video Recordings&lt;/h2&gt;
&lt;p&gt;The start of a great TikTok is the camera. When our user wants to make a new video clip with our app, they tap on the button at the bottom of the initial screen to start the creation workflow.&lt;/p&gt;
&lt;p&gt;Here there is a decision to make. TikTok and the CreativeEditor SDK can each start with the creation controls and a video preview. When using the CreativeEditor SDK though, you can also start with some video that came from somewhere else. So if you already have some camera code you like, or if you want to start with some video clip from somewhere else, you can insert that into the editor when it launches. You can do that by leveraging the &lt;code&gt;onCreate&lt;/code&gt; modifier of the editor.&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; engine &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; EngineSettings&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;license&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&amp;#x3C;your license code&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                              userID&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;&amp;#x3C;your unique user id&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 class=&quot;line&quot;&gt;&lt;span&gt;VideoEditor&lt;/span&gt;&lt;span&gt;(engine)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  .imgly.&lt;/span&gt;&lt;span&gt;onCreate&lt;/span&gt;&lt;span&gt;({ engine &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    try&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.scene.&lt;/span&gt;&lt;span&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;fromVideo&lt;/span&gt;&lt;span&gt;: Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;dog_water&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mov&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;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //set other defaults&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;In the code above, the app initializes an instance of the Video Editor view and then reads the &lt;code&gt;dog_water.mov&lt;/code&gt; file from the app bundle. launches the editor with that.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Starting scene with a dog video&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 480px) 480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;480&quot; height=&quot;1039&quot; src=&quot;https://img.ly/_astro/prepopulated_2uzsKx.webp&quot; srcset=&quot;/_astro/prepopulated_2uzsKx.webp 480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Whether starting with a video or starting with a blank canvas a user can add video clips to their creation using the embedded camera.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;difference between tiktok camera and creativeeditor&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 640px) 640px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;640&quot; height=&quot;674&quot; src=&quot;https://img.ly/_astro/updated_editor_compare_1tFFER.webp&quot; srcset=&quot;/_astro/updated_editor_compare_1tFFER.webp 640w&quot;&gt;&lt;/p&gt;
&lt;p&gt;The image above compares two different camera controllers: the left one from the TikTok app and the right showing the default settings of the MobileCamera, either standalone or within a Video Editor scene. Key controls in each are marked with blue numbers:&lt;/p&gt;
&lt;p&gt;1 - Flip camera button&lt;br&gt;
2 - Flash button&lt;br&gt;
3 - Recording time indicator&lt;br&gt;
4 - Add video and finish buttons&lt;br&gt;
5 - Cancel button&lt;/p&gt;
&lt;p&gt;While the TikTok camera integrates several editing functions during video capture, the Video Editor separates these tasks, focusing the camera purely on recording. Both systems allow users to start and stop video recording to compile a series of clips before moving to the editing phase.&lt;/p&gt;
&lt;h3 id=&quot;configuring-the-camera&quot;&gt;Configuring the Camera&lt;/h3&gt;
&lt;p&gt;The camera used inside of the Video Editor UI isn’t customizable. You have limited options to customize when you use the standalone camera though. You can set the colors of the buttons, helpful if you have a preferred color scheme, and you can set a maximum duration for the video recording allowed.&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; config &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CameraConfiguration&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  recordingColor&lt;/span&gt;&lt;span&gt;: .orange,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  highlightColor&lt;/span&gt;&lt;span&gt;: .blue,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  maxTotalDuration&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;30&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  allowExceedingMaxDuration&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;Camera&lt;/span&gt;&lt;span&gt;(engine, &lt;/span&gt;&lt;span&gt;config&lt;/span&gt;&lt;span&gt;: config) { result &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; //handle the recorded video collection&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 sets the record button to orange while recording and the clip view to orange. There is a maximum duration of 30 seconds for all videos in the collection and the button to save the clips highlighted in blue.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;customized camera&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 480px) 480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;480&quot; height=&quot;1039&quot; src=&quot;https://img.ly/_astro/customcamera_Z2tXDPj.webp&quot; srcset=&quot;/_astro/customcamera_Z2tXDPj.webp 480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;If your app requires further customization, you’ll probably want to make your own camera view and work with the IMG.LY Creative Engine directly.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://img.ly/docs/cesdk/ios/get-started/overview-e18f40/&quot;&gt;documentation website&lt;/a&gt; is a great resource for understanding how much you can add to and customize the IMGLYUI resources and when you’ll need to drop down to the level of the engine.&lt;/p&gt;
&lt;h3 id=&quot;presenting-the-controller&quot;&gt;Presenting the Controller&lt;/h3&gt;
&lt;p&gt;As demonstrated above, both the Video Editor and the Camera are View structures that can be presented directly or through overlays such as overlay or fullScreenCover. However, they are typically enclosed in a NavigationView to utilize the navigation bar for displaying export and other buttons. Therefore, when triggering the Video Editor from a button, it’s crucial to embed it within a NavigationView.&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;Button&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Edit the Video&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  isPresented &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&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;fullScreenCover&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;isPresented&lt;/span&gt;&lt;span&gt;: $isPresented) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;   NavigationView&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     VideoEditor&lt;/span&gt;&lt;span&gt;(engine)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      //any setup modifiers&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;building-your-creation&quot;&gt;Building Your Creation&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;compare editors&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 640px) 640px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;640&quot; height=&quot;674&quot; src=&quot;https://img.ly/_astro/updated_editor_compare-1_ZncnjS.webp&quot; srcset=&quot;/_astro/updated_editor_compare-1_ZncnjS.webp 640w&quot;&gt;&lt;/p&gt;
&lt;p&gt;After capturing a video, the app displays an editor to complete the creation. The editing tools are in the red circles. TikTok provides about ten different tools on the editing screen. Additionally, TikTok provided some editing tools on the capture screen. The CreativeEditor SDK apps provide a scrolling set of asset libraries along the bottom for items to add to the scene. But when any of the elements of the scene, like your video or an audio clip, are highlighted, the bottom row becomes tools to manipulate that element. The basic Asset Library types are uploads, videos, audio, images, text, shapes, and stickers.&lt;/p&gt;
&lt;p&gt;How to configure and customize each of these tools is beyond the scope of this tutorial. But, a lot of the customization of the assets, fonts, blurs and even filters can be done without code. By design, the CreativeEditor SDK downloads assets from a central server. This is a great way for you to be able to push updated filters, stickers, and other assets to your users without going through the app update process. In the next section, we’ll walk through that and provide some links for further research.&lt;/p&gt;
&lt;h3 id=&quot;configuring-the-editors-assets&quot;&gt;Configuring the Editors Assets&lt;/h3&gt;
&lt;p&gt;The IMG.LY Engine looks for assets for stickers, vector shapes, LUT filters, color filters, color palettes, blurs, and fonts using a URL by default. The design of the system is that you would have your own server supporting your app. However, assets in the app bundle are also accessible by URL, so as long as the assets are in the folder format that the engine expects, it doesn’t matter whether they are local or remote.&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;https://img.ly/docs/cesdk/ios/serve-assets-b0827c/&quot;&gt;documentation for this process&lt;/a&gt; you can find a URL to the default asset bundle. Once you download that bundle you can add it to your app. The bundle is structured much like a regular iOS Assets.catalog with some JSON containing metadata about the asset and a file that provides the actual asset. For some of the assets like filters and vectors, no file is needed as everything can be defined in the JSON.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;asset files and metadata listing&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 700px) 700px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;700&quot; height=&quot;656&quot; src=&quot;https://img.ly/_astro/assets_ZLXreD.webp&quot; srcset=&quot;/_astro/assets_21dhby.webp 640w, /_astro/assets_ZLXreD.webp 700w&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the image above, you can see the JSON to describe the ape sticker and the &lt;code&gt;emoji_ape&lt;/code&gt; image file after the IMGLYAssets bundle has been added to our app. You can edit these assets and add your own.&lt;/p&gt;
&lt;p&gt;Once you’re done editing the asset bundle, you can use it instead of the default bundle during the &lt;code&gt;onCreate&lt;/code&gt; modifier 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;VideoEditor&lt;/span&gt;&lt;span&gt;(engine)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  .imgly.&lt;/span&gt;&lt;span&gt;onCreate&lt;/span&gt;&lt;span&gt;({ engine &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     try&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.scene.&lt;/span&gt;&lt;span&gt;load&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt;: VideoEditor.defaultScene)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;              // Add asset sources&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     let&lt;/span&gt;&lt;span&gt; bundleURL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;IMGLYAssets&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;bundle&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;     try&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.&lt;/span&gt;&lt;span&gt;addDefaultAssetSources&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;baseURL&lt;/span&gt;&lt;span&gt;: bundleURL)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     try&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.&lt;/span&gt;&lt;span&gt;addDemoAssetSources&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;sceneMode&lt;/span&gt;&lt;span&gt;: engine.scene.&lt;/span&gt;&lt;span&gt;getMode&lt;/span&gt;&lt;span&gt;(),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       withUploadAssetSources&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;     try&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; engine.asset.&lt;/span&gt;&lt;span&gt;addSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;TextAssetSource&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;engine&lt;/span&gt;&lt;span&gt;: engine))&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;In the code above, we create a blank &lt;code&gt;scene&lt;/code&gt; using &lt;code&gt;VideoEditor.defaultScene&lt;/code&gt; static method. Then set a URL to point to the bundle in the app instead of a remote bundle and make that the source of &lt;code&gt;addDefaultAssetSources&lt;/code&gt;. Now our app will use the assets from the bundle.&lt;/p&gt;
&lt;h3 id=&quot;exporting-the-finished-video&quot;&gt;Exporting the Finished Video&lt;/h3&gt;
&lt;p&gt;Once the user has finished with their creation, they tap the share button and the Video Editor composes all of the video, audio, filters, and static items into a single &lt;code&gt;.mp4&lt;/code&gt; file. Then it displays a share sheet. If you’d rather do something different, like save the creation locally so you can play it back, you can override this default behavior.&lt;/p&gt;
&lt;p&gt;In your app, after the &lt;code&gt;.onCreate&lt;/code&gt; modifier, you can define a &lt;code&gt;.onExport&lt;/code&gt; modifier. When the user taps on the share button you can export the video and then save it to the documents directory to playback later.&lt;/p&gt;
&lt;p&gt;In the &lt;a href=&quot;https://img.ly/docs/cesdk/ios/user-interface/events-514b70/&quot;&gt;documentation for modifiers&lt;/a&gt; you can find the source code for the Video Editor’s default behavior. Instead of duplicating it in detail here, we can just summarize and provide a little snippet for our change.&lt;/p&gt;
&lt;p&gt;First, the &lt;code&gt;.onCreate&lt;/code&gt; handler takes an &lt;code&gt;engine&lt;/code&gt; and a &lt;code&gt;eventHandler&lt;/code&gt; parameter. The &lt;code&gt;engine&lt;/code&gt; is the underlying IMG.LY Engine object we’ve been using. The &lt;code&gt;eventHandler&lt;/code&gt; lets us send information back to the Video Editor UI about the progress or any errors.&lt;/p&gt;
&lt;p&gt;In the documentation, there is a helper function to export the video. After it runs it returns the &lt;code&gt;data&lt;/code&gt; of the exported video and a &lt;code&gt;mimeType&lt;/code&gt;, which will be &lt;code&gt;.mp4&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;let&lt;/span&gt;&lt;span&gt; data: Date, mimeType: MIMEType&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; mode &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try&lt;/span&gt;&lt;span&gt; mainEngine.scene.&lt;/span&gt;&lt;span&gt;getMode&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;guard&lt;/span&gt;&lt;span&gt; mode &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; .video &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; { &lt;/span&gt;&lt;span&gt;throw&lt;/span&gt;&lt;span&gt; CallbackError.unknownSceneMode; &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;(data, mimeType) &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try&lt;/span&gt;&lt;span&gt; await&lt;/span&gt;&lt;span&gt; exportVideo&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code in the documentation handles static files from the &lt;code&gt;DesignEditor&lt;/code&gt; as well as videos from the &lt;code&gt;VideoEditor&lt;/code&gt;. The above code makes it so that only video can be exported, since our app only works with video.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;exportVideo&lt;/code&gt; function, the code first checks to make sure it has a properly formed &lt;code&gt;scene&lt;/code&gt; object to work with. Then it starts the export using &lt;code&gt;mainEngine.block.exportVideo(_, mimeType:)&lt;/code&gt;. This begins an async throwing stream during the export. The VideoExport objects in the stream are either &lt;code&gt;.progress&lt;/code&gt; or &lt;code&gt;.finished&lt;/code&gt;. When a &lt;code&gt;.progress&lt;/code&gt; object arrives, the &lt;code&gt;exportVideo&lt;/code&gt; function sends an update to the &lt;code&gt;eventHandler&lt;/code&gt; to display in the UI. When the &lt;code&gt;.finished&lt;/code&gt; object arrives, it has an associated object that is the data for the video.&lt;/p&gt;
&lt;p&gt;Now instead of opening the share sheet, we can write the video to the documents directory for the app.&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;do&lt;/span&gt;&lt;span&gt; {&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; documentDirectory &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .documentDirectory, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: .userDomainMask, &lt;/span&gt;&lt;span&gt;appropriateFor&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;create&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;        else&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          logger.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Documents directory not found&quot;&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span&gt;return&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 class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; filePath &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; documentDirectory.&lt;/span&gt;&lt;span&gt;appending&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;component&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;UUID&lt;/span&gt;&lt;span&gt;().uuidString)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; data.&lt;/span&gt;&lt;span&gt;write&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: filePath)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; error &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; NSError) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  logger.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;could not write finished file: &lt;/span&gt;&lt;span&gt;\(error.&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;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;eventHandler.&lt;/span&gt;&lt;span&gt;send&lt;/span&gt;&lt;span&gt;(.&lt;/span&gt;&lt;span&gt;exportCompleted&lt;/span&gt;&lt;span&gt; {})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above takes the &lt;code&gt;data&lt;/code&gt; from the &lt;code&gt;exportVideo&lt;/code&gt; function and writes it to the app’s documents directory, giving it a UUID string value as a filename. Then it updates the UI to let it know the export is done but sends an empty action block.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-a-playback-controller&quot;&gt;Setting Up a Playback Controller&lt;/h2&gt;
&lt;p&gt;For this example app, the playback controller will play any clips in the app’s &lt;code&gt;Documents&lt;/code&gt; directory. Each clip plays on a loop. The user can swipe up to get the next clip and tap to pause or restart the clip. If there are no clips, the user will be encouraged to make a new one.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;screen shot of player&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 963px) 963px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;963&quot; height=&quot;982&quot; src=&quot;https://img.ly/_astro/viewer_Z21kKPe.webp&quot; srcset=&quot;/_astro/viewer_WarKA.webp 640w, /_astro/viewer_2gQt5P.webp 750w, /_astro/viewer_2mRkuK.webp 828w, /_astro/viewer_Z21kKPe.webp 963w&quot;&gt;&lt;/p&gt;
&lt;p&gt;When the video player view appears, the first task is to see if there are any videos to load. Any video files in the Documents directory are assumed to be ready to play.&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; loadVideos&lt;/span&gt;&lt;span&gt;() {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //get a handle to the documents directory for this app&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; documentDirectory &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: .documentDirectory, &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: .userDomainMask, &lt;/span&gt;&lt;span&gt;appropriateFor&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;create&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;else&lt;/span&gt;&lt;span&gt; { logger.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Documents directory not found&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;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; files &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;contentsOfDirectory&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;at&lt;/span&gt;&lt;span&gt;: documentDirectory, &lt;/span&gt;&lt;span&gt;includingPropertiesForKeys&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;    videos &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; files&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;The code above reads all of the filenames from the documents directory into a &lt;code&gt;files&lt;/code&gt; array and then assigns that array to a &lt;code&gt;videos&lt;/code&gt; variable.&lt;/p&gt;
&lt;p&gt;Each time the app is launched or the &lt;code&gt;VideoEditor&lt;/code&gt; view is dismissed, the video player view will get the &lt;code&gt;onAppear&lt;/code&gt; message. This is a good place to check for videos using the &lt;code&gt;loadVideos&lt;/code&gt; function. After the videos are loaded, the app can start to play the first 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;func&lt;/span&gt;&lt;span&gt; setupVideoPlayer&lt;/span&gt;&lt;span&gt;() {&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; currentVideo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; videos.&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt; else&lt;/span&gt;&lt;span&gt; {logger.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;No videos to play&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;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.shouldHideEmptyDirectoryText &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;       let&lt;/span&gt;&lt;span&gt; endMonitor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; NotificationCenter.default.&lt;/span&gt;&lt;span&gt;publisher&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;: NSNotification.Name.AVPlayerItemDidPlayToEndTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&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; AVPlayerItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: currentVideo)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.currentVideo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; currentVideo&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&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;In the code above, we first check to see if there are any video files. If there are, then hide the message about videos and create a new &lt;code&gt;AVPlayerItem&lt;/code&gt; using the &lt;code&gt;URL&lt;/code&gt; of the first video. The &lt;code&gt;endMonitor&lt;/code&gt;, &lt;code&gt;shouldHideEmptyDirectoryText&lt;/code&gt;, and &lt;code&gt;player&lt;/code&gt; are both being watched by the &lt;code&gt;View&lt;/code&gt;. The AVPlayer gets rendered in a regular SwiftUI 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;VideoPlayer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;player&lt;/span&gt;&lt;span&gt;: player)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  .&lt;/span&gt;&lt;span&gt;frame&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;640&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;360&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;onAppear&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    player.&lt;/span&gt;&lt;span&gt;play&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;onReceive&lt;/span&gt;&lt;span&gt;(endMonitor) { &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;    player.&lt;/span&gt;&lt;span&gt;seek&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: .zero)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    player.&lt;/span&gt;&lt;span&gt;play&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;When the video reaches the end, it will &lt;code&gt;seek(to: .zero)&lt;/code&gt; which rewinds the video and loops it.&lt;/p&gt;
&lt;p&gt;In the TikTok app, you can advance to the next video by swiping up. Additionally, you can start and stop any video by tapping on the screen. We can add both of those features using gesture recognizers.&lt;/p&gt;
&lt;p&gt;The code for play and pause is attached to a Tap Gesture modifier we can append to the player.&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;.&lt;/span&gt;&lt;span&gt;onTapGesture&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; player.rate &lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; 0.0&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    player.rate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0.0&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;    player.rate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 1.0&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;We can just adjust the playback rate. A value of &lt;code&gt;1.0&lt;/code&gt; is a normal speed and &lt;code&gt;0.0&lt;/code&gt; is paused. The player also has a &lt;code&gt;.pause&lt;/code&gt; and &lt;code&gt;.play&lt;/code&gt; methods, but sometimes these seem buggy.&lt;/p&gt;
&lt;p&gt;Swipe is a little more difficult since SwiftUI only has swiping on List items. So we can use a &lt;code&gt;drag&lt;/code&gt; gesture and then evaluate the translations when it’s complete.&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;.&lt;/span&gt;&lt;span&gt;gesture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    DragGesture&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;minimumDistance&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;3.0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;coordinateSpace&lt;/span&gt;&lt;span&gt;: .local)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span&gt;onEnded&lt;/span&gt;&lt;span&gt; { value &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 a vertical, upward swipe with minimal horizontal deviation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;          if&lt;/span&gt;&lt;span&gt; abs&lt;/span&gt;&lt;span&gt;(value.translation.width) &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; 100&lt;/span&gt;&lt;span&gt; &amp;#x26;&amp;#x26;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             value.translation.height &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                // It was an up swipe!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;		viewModel.&lt;/span&gt;&lt;span&gt;advanceVideo&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;span class=&quot;line&quot;&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then advancing the video is a simple matter of refreshing the &lt;code&gt;player&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;func&lt;/span&gt;&lt;span&gt; advanceVideo&lt;/span&gt;&lt;span&gt;() {&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; currentVideo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.currentVideo &lt;/span&gt;&lt;span&gt;else&lt;/span&gt;&lt;span&gt; { logger.&lt;/span&gt;&lt;span&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;No current video.&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;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; currentIndex &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; videos.&lt;/span&gt;&lt;span&gt;firstIndex&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;: currentVideo)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  var&lt;/span&gt;&lt;span&gt; nextVideo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; videos.&lt;/span&gt;&lt;span&gt;index&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;after&lt;/span&gt;&lt;span&gt;: currentIndex&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;  if&lt;/span&gt;&lt;span&gt; nextVideo &lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt; videos.&lt;/span&gt;&lt;span&gt;count&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    nextVideo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 0&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 class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.currentvideo &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; videos[nextVideo]&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;url&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.currentVideo&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;
&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 code will get the next file URL from the array of videos. When it reaches the end, it will loop back and get the file at index 0. Then it creates a new player. Because the view is observing that &lt;code&gt;player&lt;/code&gt; variable, it will update with the new video.&lt;/p&gt;
&lt;p&gt;With this view, the user can create new videos by tapping the creation button and swipe to view all of their created videos.&lt;/p&gt;
&lt;h2 id=&quot;where-to-go-from-here&quot;&gt;Where to Go From Here&lt;/h2&gt;
&lt;p&gt;This tutorial has focused on how the CreativeEditor SDK can help you quickly make a video creation app like TikTok. Good next steps would be to further customize the editing tools and build out the network for sharing and tagging videos. Something that is important to consider is the data structure for each video. The iOS system is optimized to read only as much of a video file off of disk as is needed at any moment. Your app should use those optimizations to run faster. So, don’t load the whole video into memory as a &lt;code&gt;Data&lt;/code&gt; object. Your data structures should keep the large video files somehow separate from the much smaller metadata (likes, comments, etc.). Consider storing the &lt;code&gt;URL&lt;/code&gt; or filename of the video in the same object as the likes and comments. This will also allow you to cache video files after they have been downloaded so that you don’t need to redownload them when other data such as comments or number of likes changes.&lt;/p&gt;
&lt;p&gt;Thanks for reading! We hope that you’ve gotten a better understanding for how a tool like the &lt;a href=&quot;https://img.ly/products/creative-sdk&quot;&gt;CreativeEditor SDK&lt;/a&gt; can bring your ideas to market faster. Feel free to reach out to us with any questions, comments, or suggestions.&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/camera-sdk&quot;&gt;Camera SDK&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To stay in the loop, subscribe to our&lt;/strong&gt; &lt;a href=&quot;https://share.hsforms.com/1IgAOV1wASXGPnFG4ZPLejg1hk3i&quot;&gt;&lt;strong&gt;Newsletter&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;or follow us on&lt;/strong&gt; &lt;a href=&quot;https://www.linkedin.com/company/img.ly&quot;&gt;&lt;strong&gt;LinkedIn&lt;/strong&gt;&lt;/a&gt; &lt;strong&gt;and&lt;/strong&gt; &lt;a href=&quot;https://x.com/imgly&quot;&gt;&lt;strong&gt;Twitter&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2023/03/Build_TikTok_app_iOS_VE-SDK-1.jpg" medium="image"/><category>How-To</category><category>iOS</category><category>Video Editing</category><category>Social Media</category><category>Tech</category><category>Learning</category><category>Tutorial</category></item><item><title>CE.SDK v1.8.0 Release</title><link>https://img.ly/blog/creative-editor-sdk-v_1_8_0-release-notes/</link><guid isPermaLink="true">https://img.ly/blog/creative-editor-sdk-v_1_8_0-release-notes/</guid><description>This release includes Node.js Platform Support, enhanced image control, and more!</description><pubDate>Tue, 18 Oct 2022 14:48:05 GMT</pubDate><content:encoded>&lt;p&gt;Since our CE.SDK &lt;a href=&quot;https://img.ly/blog/creative-editor-sdk-v_1_7_0-release-notes/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=releasenotes&quot;&gt;v1.7.0&lt;/a&gt; Release, we implemented more highly requested features. Today, we shine light on them. As always, we are excited to hear your feedback and suggestions to build powerful features for your application. Our &lt;a href=&quot;https://roadmap.img.ly/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=releasenotes&quot;&gt;Product Roadmap&lt;/a&gt; is the best place to make your case and be the first one to know of future releases.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/cesdk_v1-8-0-summary.mp4&quot; controls playsinline poster=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/videos/cesdk-v-1-8-0_cover.jpg&quot;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h2 id=&quot;cesdk-v180&quot;&gt;CE.SDK v1.8.0&lt;/h2&gt;
&lt;p&gt;This release includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js Platform Support&lt;/li&gt;
&lt;li&gt;Common Touch Gesture Support for iOS&lt;/li&gt;
&lt;li&gt;Emoji Support ?&lt;/li&gt;
&lt;li&gt;Image Straightening in Advanced &amp;#x26; Default UI&lt;/li&gt;
&lt;li&gt;Multi Selection Rotation&lt;/li&gt;
&lt;li&gt;Outlook&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;nodejs-platform-support&quot;&gt;Node.js Platform Support&lt;/h3&gt;
&lt;p&gt;Since the release of our &lt;a href=&quot;https://www.npmjs.com/package/@cesdk/node&quot;&gt;Node Package&lt;/a&gt;, you can run a headless version of CE.SDK on the server. Now with this release, you have the full power of our Creative Engine at your fingertips, enabling you to programmatically generate images on the server or command line, render scenes created with the web SDK, and implement any creative automation workflow.&lt;/p&gt;
&lt;h3 id=&quot;common-touch-gesture-support-for-ios&quot;&gt;Common Touch Gesture Support for iOS&lt;/h3&gt;
&lt;p&gt;We know that a mobile presence and functionality is crucial for your application. If users find it difficult to complete a task or are frustrated by poor usability, they could quickly bid adieu to your app and ditch your hard work for a competitor. So with this update, we add common touch gesture to improve the editing experience on touchpads and mobile devices. This includes gestures like pinching, spreading, dragging, rotating, tap and double tapping.&lt;/p&gt;
&lt;h3 id=&quot;emoji-support&quot;&gt;Emoji Support&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;643&quot; src=&quot;https://img.ly/_astro/Emoticon_Support_Z14LJgA.webp&quot; srcset=&quot;/_astro/Emoticon_Support_Z2sV7lX.webp 640w, /_astro/Emoticon_Support_cm4FH.webp 750w, /_astro/Emoticon_Support_Z28eEIM.webp 828w, /_astro/Emoticon_Support_Z8mMhQ.webp 1080w, /_astro/Emoticon_Support_ZB4KLI.webp 1280w, /_astro/Emoticon_Support_Z14LJgA.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Sometimes, a party face says more than a thousand words ?: Emojis have become an essential part of modern communication and are omnipresent, especially on mobile platforms, but on desktop as well. We added Emoji rendering support for more fun edits and colorful messaging.&lt;/p&gt;
&lt;h3 id=&quot;image-straightening-in-advanced--default-ui&quot;&gt;Image Straightening in Advanced &amp;#x26; Default UI&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;643&quot; src=&quot;https://img.ly/_astro/Image-Straighten-in-Advanced---Desktop-UI_25ivqN.webp&quot; srcset=&quot;/_astro/Image-Straighten-in-Advanced---Desktop-UI_1Ig67Y.webp 640w, /_astro/Image-Straighten-in-Advanced---Desktop-UI_dKO90.webp 750w, /_astro/Image-Straighten-in-Advanced---Desktop-UI_ZvWPQk.webp 828w, /_astro/Image-Straighten-in-Advanced---Desktop-UI_1cOROz.webp 1080w, /_astro/Image-Straighten-in-Advanced---Desktop-UI_ZSwmLh.webp 1280w, /_astro/Image-Straighten-in-Advanced---Desktop-UI_25ivqN.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;While cropping your image, you can now also straighten, flip and rotate it! This feature gives you full control over your image in one place – available in both Advanced and Default User Interfaces.&lt;/p&gt;
&lt;h3 id=&quot;multi-selection-rotation&quot;&gt;Multi Selection Rotation&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1480px) 1480px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1480&quot; height=&quot;643&quot; src=&quot;https://img.ly/_astro/multi-selection-rotation-small_-1-_1wSd7Y.webp&quot; srcset=&quot;/_astro/multi-selection-rotation-small_-1-_p9U93.webp 640w, /_astro/multi-selection-rotation-small_-1-_KG4mg.webp 750w, /_astro/multi-selection-rotation-small_-1-_ZM2FD2.webp 828w, /_astro/multi-selection-rotation-small_-1-_ZF1y1u.webp 1080w, /_astro/multi-selection-rotation-small_-1-_qqk3f.webp 1280w, /_astro/multi-selection-rotation-small_-1-_1wSd7Y.webp 1480w&quot;&gt;&lt;/p&gt;
&lt;p&gt;For an accelerated design and editing process, the engine and user interface now allow rotating several blocks at once. To multi-rotate, simply hold your &lt;em&gt;Shift&lt;/em&gt; key while selecting the blocks you would like to move simultaneously.&lt;/p&gt;
&lt;h3 id=&quot;outlook&quot;&gt;Outlook&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Native iOS Platform Support:&lt;/strong&gt; With this feature, we will add support for developing native iOS applications with the SDK. It will come with full Swift API support and many integration examples.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video for Web with CreativeEditor SDK:&lt;/strong&gt; Users will be able to edit and create fantastic videos based on templates in their browser with our SDK in your application soon! &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=4833b9cfb6&quot;&gt;Sign up&lt;/a&gt; for &lt;strong&gt;early access&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Thanks for reading! To stay in the loop, feel free to subscribe to our &lt;a href=&quot;https://share.hsforms.com/1IgAOV1wASXGPnFG4ZPLejg1hk3i&quot;&gt;Newsletter&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Neslihan</dc:creator><media:content url="https://blog.img.ly/2022/10/creative-editor-sdk-design-editor-sdk-1-8-0-imgly.png" medium="image"/><category>Release Notes</category><category>Releases</category><category>Design Editor</category><category>Web Development</category><category>Web Application</category><category>iOS</category></item><item><title>Time-Based Sprites for VE.SDK on iOS and Android</title><link>https://img.ly/blog/time-based-sprites-for-ve-sdk-on-ios-and-android/</link><guid isPermaLink="true">https://img.ly/blog/time-based-sprites-for-ve-sdk-on-ios-and-android/</guid><description>Time-Based Sprites allow basic keyframing by setting the duration of your Text and Stickers in Videos. </description><pubDate>Mon, 05 Sep 2022 07:06:05 GMT</pubDate><content:encoded>&lt;p&gt;We are happy to extend VideoEditor SDK with a highly-requested feature: &lt;strong&gt;Time-Based Sprites&lt;/strong&gt;. This new feature sets the &lt;strong&gt;duration of text&lt;/strong&gt; and &lt;strong&gt;stickers&lt;/strong&gt; in your video timeline. Users may now place fun stickers and text at the right moment, and give their videos a special touch.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/basic-keyframe-video-editing.MP4&quot; controls autoplay muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h2 id=&quot;time-based-sprites&quot;&gt;Time-Based Sprites&lt;/h2&gt;
&lt;p&gt;The popular feature known from TikTok and Instagram Reels is now available in VE.SDK: set the starting and end point of your text or sticker. Tap your text or sticker and select &lt;em&gt;Duration&lt;/em&gt; to determine the duration of your asset.&lt;/p&gt;
&lt;p&gt;&lt;video src=&quot;https://storage.googleapis.com/imgly-static-assets/static/blog/video-editor-sdk-white-label-edit.MOV&quot; controls autoplay muted loop playsinline&gt;&lt;/video&gt;&lt;/p&gt;
&lt;p&gt;Unless you have specified a custom set of sticker or text actions, this feature is &lt;strong&gt;enabled by default&lt;/strong&gt; since VE.SDK v10.3.0 for Android and v11.3.0 for iOS. See the official &lt;a href=&quot;https://img.ly/docs/vesdk/ios/guides/trim/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=releasenotes&quot;&gt;documentation for iOS&lt;/a&gt; or &lt;a href=&quot;https://img.ly/docs/vesdk/android/guides/trim/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=releasenotes#trim-sprite-duration&quot;&gt;Android&lt;/a&gt; on Time-Based Sprites.&lt;/p&gt;
&lt;h3 id=&quot;why-is-video-important&quot;&gt;Why is Video Important?&lt;/h3&gt;
&lt;p&gt;Video content has become an essential medium for social media, marketing, sales, and support teams. According to marketing &lt;a href=&quot;https://www.wyzowl.com/video-marketing-statistics/&quot;&gt;statistics&lt;/a&gt;, people are watching an average of &lt;strong&gt;19 hours&lt;/strong&gt; of online video per week in 2022. &lt;strong&gt;88%&lt;/strong&gt; of people say that they felt convinced to buy a product or service by watching a brand’s video. The growing trend and preference of consumers are why businesses are shifting their attention toward including videos in their strategies and applications.&lt;/p&gt;
&lt;p&gt;We commit to extending our video editing features to help developers meet the demand, save resources and streamline the process of building great applications. Let us have a look at our &lt;strong&gt;previous VE.SDK releases&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Video Composition&lt;/strong&gt;&lt;br&gt;
Users may seamlessly edit their footage by trimming and adjusting video files with advanced filters. Finally, they can set the correct order of their video sequences to create a single composition.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Audio Support&lt;/strong&gt;&lt;br&gt;
Replace or add sound in videos by loading audio files. Users can trim their audio according to their footage. Developers can provide media libraries for audio and video files. That way, users may access media by choosing from labeled folders, such as genre, theme, or artist names.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Force Trim&lt;/strong&gt;&lt;br&gt;
The VE.SDK trim tool allows users to determine the start and end frame of a video clip and change the duration of their footage. Now you can enforce a &lt;strong&gt;minimum and maximum length&lt;/strong&gt; of videos. Force Trim will come in handy for use cases, such as social media stories and posts popularly limited to bite size 15 or 60 seconds by widely loved apps – see TikTok or Instagram. Adopting a ready-to-use solution like &lt;a href=&quot;https://img.ly/video-sdk/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=releasenotes&quot;&gt;VE.SDK&lt;/a&gt; will set you on your path to creating equally beautiful apps.&lt;/p&gt;
&lt;p&gt;Thanks for reading! Let us know what you think on &lt;a href=&quot;https://twitter.com/imgly&quot;&gt;Twitter&lt;/a&gt; – or check out our &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;Newsletter&lt;/a&gt; for more accelerating updates.&lt;/p&gt;</content:encoded><dc:creator>Neslihan</dc:creator><media:content url="https://blog.img.ly/2022/08/video-editor-sdk-duration-trim-stickers-keyframes.png" medium="image"/><category>Release Notes</category><category>VE.SDK</category><category>Android</category><category>Android App Development</category><category>iOS</category><category>iOS App Development</category><category>Video Editing</category><category>Video App</category><category>Keyframe</category><category>Company</category></item><item><title>Build a Simple Real-Time Video Editor with Metal for iOS</title><link>https://img.ly/blog/build-a-simple-real-time-video-editor-with-metal-for-ios/</link><guid isPermaLink="true">https://img.ly/blog/build-a-simple-real-time-video-editor-with-metal-for-ios/</guid><description>Learn to extract frames from live camera streams, regular movie files and streamed movie files and display them on a MetalKit View. </description><pubDate>Tue, 30 Aug 2022 20:06:49 GMT</pubDate><content:encoded>&lt;p&gt;In this tutorial you’ll see how to extract frames from live camera streams, regular movie files and streamed movie files and display them on a MetalKit View. Using Metal allows you to have a great deal of control over how the pixels are rendered and ensures that the GPU is used for rendering, which keeps from slowing down the CPU.&lt;/p&gt;
&lt;p&gt;The tutorial code will always convert the video frames into &lt;code&gt;CIImage&lt;/code&gt; objects before display. This is to help you adapt the code to your own applications. Applying filters, resizing and other effects are fast and easy when working with &lt;code&gt;CIImage&lt;/code&gt;. Additionally, as long as you set your &lt;code&gt;CIContext&lt;/code&gt; to the GPU, you can be sure that your code uses the GPU and the CPU efficiently.&lt;/p&gt;
&lt;p&gt;When working with Video, Apple provides a number of frameworks that operate at different levels. For the higher level frameworks such as AVKit or CoreImage, the system determines when to use the GPU and when to use the CPU for rendering and computation. The higher-level frameworks also offer a tradeoff between ease of use and granularity of control. If you want to have direct access to tell the GPU how to render every pixel, then you will want to use Metal. Remember, though, that you are now responsible for keeping video and audio in sync, encoding and decoding data and determining a reasonable UI for your user. For many use cases, using a higher level framework is probably a better choice. Apple’s engineers have worked to ensure the graphics frameworks use the GPU and CPU in reasonable ways. However, when you need to use Metal, you you need it. So, let’s get started.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-a-mtkview&quot;&gt;Setting Up a MTKView&lt;/h2&gt;
&lt;p&gt;An &lt;code&gt;MTKView&lt;/code&gt; is a subclass of a &lt;code&gt;UIView&lt;/code&gt; so it has a frame and bounds and other properties. Its drawing and rendering is backed directly by drawables and textures rendered on the GPU, so drawing to it can be quite fast. In addition to displaying video, you can render 3D model objects and graphics shaders, so for a graphics rich application it can be a powerful tool.&lt;/p&gt;
&lt;p&gt;Before you can send data to the view it needs to be configured. MetalKit Views are still heavily influenced by UIKit, so if you are working in a SwiftUI project, you will need to wrap them in ViewRepresentable 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;//MetalKit Variables&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;@IBOutlet&lt;/span&gt;&lt;span&gt; var&lt;/span&gt;&lt;span&gt; displayView: MTKView&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; metalDevice : MTLDevice&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; metalCommandQueue : MTLCommandQueue&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//CoreImage Variables&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; ciContext : CIContext&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; filteredImage: CIImage&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; cleanImage: CIImage&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&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;//get a reference to the GPU&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;metalDevice &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; MTLCreateSystemDefaultDevice&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//link the GPU to our MTKView&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;displayView.device &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; metalDevice&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//link our command queue variable to the GPU&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;metalCommandQueue &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; metalDevice.&lt;/span&gt;&lt;span&gt;makeCommandQueue&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//associate our CIContext with the metal stack&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;ciContext &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIContext&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mtlCommandQueue&lt;/span&gt;&lt;span&gt;: metalCommandQueue)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You will need to get a reference to the GPU of the iOS device. At least for now, iOS devices only have one GPU. Then you will need to link the &lt;code&gt;displayView&lt;/code&gt; and the &lt;code&gt;metalCommandQueue&lt;/code&gt; to the &lt;code&gt;metalDevice&lt;/code&gt;. Finally we will link the &lt;code&gt;ciContext&lt;/code&gt; to the GPU so that all of our image manipulation code will run in the same place.&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;//tell our MTKView that we want to call .draw() to make updates&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;displayView.isPaused &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; true&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;displayView.enableSetNeedsDisplay &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//let it&apos;s drawable texture be writen to dynamically&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;displayView.framebufferOnly &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; false&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//set our code to be our MTKView&apos;s delegate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;displayView.delegate &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;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, we set the &lt;code&gt;MTKView&lt;/code&gt; to be in a &lt;code&gt;isPaused&lt;/code&gt; state and do not enable &lt;code&gt;setNeedsDisplay&lt;/code&gt;. This will ensure that it will only redraw when we explicitly tell it to redraw using a &lt;code&gt;.draw()&lt;/code&gt; method in our delegate. By setting &lt;code&gt;framebufferOnly&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; you are telling the view that you will be writing to it multiple times and may also read from it.&lt;/p&gt;
&lt;h2 id=&quot;drawing-in-an-mtkview&quot;&gt;Drawing in an MTKView&lt;/h2&gt;
&lt;p&gt;Now the MTKView is configured. The next step is to update the delegate methods. The &lt;code&gt;MTKViewDelegate&lt;/code&gt; has two methods. If your code needs to respond to the view dimensions changing (to support device rotation, or if you want the user to be able to resize the window) make your adjustments in &lt;code&gt;func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize)&lt;/code&gt;. For this example we are only interested in the delegate method: &lt;code&gt;func draw(in view: MTKView)&lt;/code&gt;. The code below draws a &lt;code&gt;CIImage&lt;/code&gt; to the &lt;code&gt;MTKView&lt;/code&gt;. When showing video, we can extract each frame of the video as a &lt;code&gt;CIImage&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;//create a buffer to hold this round of draw commands&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; commandBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; metalCommandQueue.&lt;/span&gt;&lt;span&gt;makeCommandBuffer&lt;/span&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;  return&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 class=&quot;line&quot;&gt;&lt;span&gt;//grab the filtered or a clean image to display&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; ciImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; filteredImage &lt;/span&gt;&lt;span&gt;??&lt;/span&gt;&lt;span&gt; cleanImage &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;  return&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 class=&quot;line&quot;&gt;&lt;span&gt;//get a drawable if the GPU is not busy&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; currentDrawable &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; view.currentDrawable &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;  return&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 class=&quot;line&quot;&gt;&lt;span&gt;//make sure frame is centered on screen&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; heightOfImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; ciImage.extent.height&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; heightOfDrawable &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; view.drawableSize.height&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; yOffset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (heightOfDrawable &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; heightOfImage)&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//render into the metal texture&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.ciContext.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(ciImage,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;             to&lt;/span&gt;&lt;span&gt;: currentDrawable.texture,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  commandBuffer&lt;/span&gt;&lt;span&gt;: commandBuffer,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;         bounds&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;origin&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;: &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;-&lt;/span&gt;&lt;span&gt;yOffset),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                          size&lt;/span&gt;&lt;span&gt;: view.drawableSize),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     colorSpace&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CGColorSpaceCreateDeviceRGB&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//present the drawable and buffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;commandBuffer.&lt;/span&gt;&lt;span&gt;present&lt;/span&gt;&lt;span&gt;(currentDrawable)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//send the commands to the GPU&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;commandBuffer.&lt;/span&gt;&lt;span&gt;commit&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For each pass of the draw method, we will create a new &lt;code&gt;MTLCommandBuffer&lt;/code&gt; then we will get the &lt;code&gt;MTKView&lt;/code&gt;’s &lt;code&gt;.currentDrawable&lt;/code&gt; which contains a &lt;code&gt;texture&lt;/code&gt; we can send pixel data to. The texture will have a size and a color space. Though we are using Metal to render images to the screen, Metal can be used to send any valid commands to the GPU for things like computations. Once the buffer has been filled with commands it can be assigned to the drawable and committed. When the buffer is committed, the GPU will execute all of the commands. If another &lt;code&gt;.draw()&lt;/code&gt; gets called while the buffer is being executed, the GPU will not interrupt the current buffer in order to start the new one.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;bounds&lt;/code&gt; of the render allow you to move where the &lt;code&gt;CIImage&lt;/code&gt; gets displayed in the &lt;code&gt;MTKView&lt;/code&gt; and resize the &lt;code&gt;CIImage&lt;/code&gt; within the view. This can be valuable when compositing multiple images or video streams onto the same &lt;code&gt;MTKView&lt;/code&gt;. Remember that unlike a &lt;code&gt;UIView&lt;/code&gt; the origin point is the bottom left of the texture and &lt;code&gt;CIImage&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Since iOS 11 Apple provides a new lighter weight API for sending renders to the buffer. Use whichever form makes sense to you.&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;//render into the metal texture&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; destination &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIRenderDestination&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mtlTexture&lt;/span&gt;&lt;span&gt;: currentDrawable.texture,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                   commandBuffer&lt;/span&gt;&lt;span&gt;: commandBuffer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;do&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; try&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.ciContext.&lt;/span&gt;&lt;span&gt;startTask&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;toRender&lt;/span&gt;&lt;span&gt;: ciImage, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: destination)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;} &lt;/span&gt;&lt;span&gt;catch&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;(error)&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;CIRenderDestination&lt;/code&gt; has some other optional parameters such as &lt;code&gt;height&lt;/code&gt; and &lt;code&gt;width&lt;/code&gt; but will always display with an origin of 0,0. So, unlike the earlier call, if you need to rotate or change the dimensions of the image, you will need to do it to the &lt;code&gt;CIImage&lt;/code&gt; earlier in the code. However, if your app always displays the video at full size in the &lt;code&gt;MTKView&lt;/code&gt; this may be easier. Also the &lt;code&gt;startTask&lt;/code&gt; call will return immediately, instead of waiting for other tasks on the CPU to complete.&lt;/p&gt;
&lt;p&gt;It is important to remember that you can have any number of renders in the &lt;code&gt;commandBuffer&lt;/code&gt; before the calls to &lt;code&gt;.present&lt;/code&gt; and &lt;code&gt;.commit&lt;/code&gt;. This means that the same &lt;code&gt;MTKView&lt;/code&gt; can display video from multiple streams as well as static images or animations.&lt;/p&gt;
&lt;h2 id=&quot;working-with-local-files&quot;&gt;Working with Local Files&lt;/h2&gt;
&lt;p&gt;In order to extract individual frames from a local file, we can use an &lt;code&gt;AVAssetReader&lt;/code&gt; to get pixel data to render in the &lt;code&gt;MTKView&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;When using higher-level frameworks, a quick way to display a video file for playback is to load it into an &lt;code&gt;AVPlayer&lt;/code&gt;. However, an alternative is to use an &lt;code&gt;AVAssetReader&lt;/code&gt; to read the tracks and the individual frames from the video tracks.&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; asset &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAsset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;grocery-train&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mov&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;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; reader &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try!&lt;/span&gt;&lt;span&gt; AVAssetReader&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: asset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&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; track &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; asset.&lt;/span&gt;&lt;span&gt;tracks&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;withMediaType&lt;/span&gt;&lt;span&gt;: .video).&lt;/span&gt;&lt;span&gt;last&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;  return&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 class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; outputSettings: [&lt;/span&gt;&lt;span&gt;String&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Any&lt;/span&gt;&lt;span&gt;] &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;        kCVPixelBufferPixelFormatTypeKey &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; kCVPixelFormatType_32ARGB&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;let&lt;/span&gt;&lt;span&gt; trackOutput &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAssetReaderTrackOutput&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;track&lt;/span&gt;&lt;span&gt;: track, &lt;/span&gt;&lt;span&gt;outputSettings&lt;/span&gt;&lt;span&gt;: outputSettings)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    reader.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(trackOutput)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    reader.&lt;/span&gt;&lt;span&gt;startReading&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is important that the &lt;code&gt;outputSettings&lt;/code&gt; of the reader match the image format of the video track. If there is a mismatch the color may be off or the reader may be unable to extract a usable buffer. If your code is generating empty or black buffers, the &lt;code&gt;outputSettings&lt;/code&gt; are the first place to troubleshoot. Once the reader starts reading then it can extract pixel buffers. You can use a &lt;code&gt;while&lt;/code&gt; loop to get all of the buffers from the track and send them to the &lt;code&gt;MTKView&lt;/code&gt; for display.&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;var&lt;/span&gt;&lt;span&gt; sampleBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; trackOutput.&lt;/span&gt;&lt;span&gt;copyNextSampleBuffer&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;while&lt;/span&gt;&lt;span&gt; sampleBuffer &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;  guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; cvBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMSampleBufferGetImageBuffer&lt;/span&gt;&lt;span&gt;(sampleBuffer&lt;/span&gt;&lt;span&gt;!&lt;/span&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;   return&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;  print&lt;/span&gt;&lt;span&gt;(track.preferredTransform)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  print&lt;/span&gt;&lt;span&gt;(sampleBuffer&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.outputPresentationTimeStamp)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //get a CIImage out of the CVImageBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  cleanImage &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;: cvBuffer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  displayView.&lt;/span&gt;&lt;span&gt;draw&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  sampleBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; trackOutput.&lt;/span&gt;&lt;span&gt;copyNextSampleBuffer&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;In the code above, use &lt;code&gt;CMSampleBufferGetImageBuffer&lt;/code&gt; to ensure that we have a valid image. Then assign the image to a &lt;code&gt;CoreImage&lt;/code&gt; and execute the &lt;code&gt;.draw&lt;/code&gt; method of the &lt;code&gt;MTKView&lt;/code&gt;. Afterwards, get the next sample buffer. This will continue until the end of the track, when &lt;code&gt;.copyNextSampleBuffer()&lt;/code&gt; will return &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the code above, there are two &lt;code&gt;print&lt;/code&gt; statements to note some valuable information that your app may want to store for later use. Remember, when working with buffers and the GPU directly, much of the higher-level metadata about the video is lost, so you’ll need to keep track of it in your code if you want to use it later. An image may be rotated because of the way it was originally generated in the video. The &lt;code&gt;preferredTransform&lt;/code&gt; will provide data that your app can use to transform the image to the “proper” orientation before display. Additionally the &lt;code&gt;outputPresentationTimeStamp&lt;/code&gt; tells when the image represented by the buffer appeared in the original video. This can be helpful when you are trying to sync individual frames back to audio tracks or if your app only wants to modify specific frames and then reinsert them into the original clip.&lt;/p&gt;
&lt;h2 id=&quot;working-with-streams&quot;&gt;Working with Streams&lt;/h2&gt;
&lt;p&gt;In addition to local files, an app may want to use a video stream as the input. The process is largely the same, as there is a method to extract a &lt;code&gt;CVPixelBuffer&lt;/code&gt; from an &lt;code&gt;AVPlayer&lt;/code&gt; that is streaming. You can find the complete example code for pushing pixel buffers from a stream to an &lt;code&gt;MTKView&lt;/code&gt; &lt;a href=&quot;https://img.ly/blog/how-to-add-a-filter-to-a-video-stream-in-ios/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;in this blog post about streaming&lt;/a&gt;. However, the important part of the code perhaps looks familiar by now:&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; 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;
&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;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     self&lt;/span&gt;&lt;span&gt;.currentFrame &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; frameImage &lt;/span&gt;&lt;span&gt;//a CIImage var&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;draw&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;//our MTKView&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;The code above uses a &lt;code&gt;CADisplayLink&lt;/code&gt; to query the stream on a regular basis for new video frames. It then extracts a buffer and sends it over the &lt;code&gt;MTKView&lt;/code&gt; for display.&lt;/p&gt;
&lt;h2 id=&quot;working-with-the-cameras&quot;&gt;Working with the Cameras&lt;/h2&gt;
&lt;p&gt;Much like a video stream with an &lt;code&gt;AVPlayerItemVideoOutput&lt;/code&gt; to output pixel buffer data, a standard object to use with the camera is an &lt;code&gt;AVCaptureVideoDataOutput&lt;/code&gt; object. First, you need to set up a standard capture session for either the front or back camera. Then attach a video data output object to the session.&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;videoOutput &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVCaptureVideoDataOutput&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; videoQueue &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; DispatchQueue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;label&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;captureQueue&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;qos&lt;/span&gt;&lt;span&gt;: .userInteractive)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;videoOutput.&lt;/span&gt;&lt;span&gt;setSampleBufferDelegate&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;queue&lt;/span&gt;&lt;span&gt;: videoQueue)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; captureSession.&lt;/span&gt;&lt;span&gt;canAddOutput&lt;/span&gt;&lt;span&gt;(videoOutput) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  captureSession.&lt;/span&gt;&lt;span&gt;addOutput&lt;/span&gt;&lt;span&gt;(videoOutput)&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;  fatalError&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;configuration failed&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;Whenever the camera has collected enough data to generate a pixel buffer it will send that to its delegate. The delegate can then convert that data to a &lt;code&gt;CIImage&lt;/code&gt; and send it to the &lt;code&gt;MTKView&lt;/code&gt; to get rendered. The method in a &lt;code&gt;AVCaptureVideoDataOutputSampleBufferDelegate&lt;/code&gt; would 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;func&lt;/span&gt;&lt;span&gt; captureOutput&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt; output: AVCaptureOutput, &lt;/span&gt;&lt;span&gt;didOutput&lt;/span&gt;&lt;span&gt; sampleBuffer: CMSampleBuffer, &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; connection: AVCaptureConnection) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //get a CVImageBuffer from the camera&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; cvBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMSampleBufferGetImageBuffer&lt;/span&gt;&lt;span&gt;(sampleBuffer) &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;    return&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 class=&quot;line&quot;&gt;&lt;span&gt;  //get a CIImage out of the CVImageBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  cleanImage &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;: cvBuffer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  displayView.&lt;/span&gt;&lt;span&gt;draw&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;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;In this tutorial, you saw how to extract pixel buffers from the camera, local file and remote streams and convert them to &lt;code&gt;CIImage&lt;/code&gt; Then you saw how to render a &lt;code&gt;CIImage&lt;/code&gt; to fill all or part of an &lt;code&gt;MTKView&lt;/code&gt;. If your application needs to resize or apply filters to the images, you can do that before calling the &lt;code&gt;.draw&lt;/code&gt; method of the &lt;code&gt;MTKView&lt;/code&gt;. If the only reason you are considering using &lt;code&gt;MKTViews&lt;/code&gt; is because of a Metal or OpenGL kernel you want to use, consider &lt;a href=&quot;https://img.ly/blog/how-to-create-image-filters-in-ios/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;our tutorial on how to wrap kernels into Core Image filters&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If your application needs to gather streams of video, filter and then render them to the screen with precision, then drawing to an &lt;code&gt;MTKView&lt;/code&gt; may be sufficient. However, if you want to let your users dictate how and where to filter the streams, then you may want to consider an SDK like &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;VideoEditor SDK&lt;/a&gt; or &lt;a href=&quot;https://img.ly/products/creative-sdk?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;CreativeEditor SDK&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2022/08/real-time-video-editor-with-Metal-for-iOS.png" medium="image"/><category>How-To</category><category>Tech</category><category>iOS</category><category>iOS Metal</category><category>Tutorial</category></item><item><title>How to Merge Videos in iOS with Swift</title><link>https://img.ly/blog/combine-video-clips-into-a-new-file-in-ios-with-swift/</link><guid isPermaLink="true">https://img.ly/blog/combine-video-clips-into-a-new-file-in-ios-with-swift/</guid><description>Learn how to combine videos in iOS with Swift and jump into advanced creations.</description><pubDate>Wed, 12 Jan 2022 12:12:55 GMT</pubDate><content:encoded>&lt;p&gt;Combining video clips or parts of video clips into a single video may appear complicated, but the things that add complexity also provide flexibility. In this tutorial, you will see how to combine a few short clips into a single video. You will also gain an understanding of building on this basic pattern to make more advanced creations. This tutorial was created and tested with Xcode 13 and Swift 5.&lt;/p&gt;
&lt;p&gt;As with most &lt;code&gt;AVFoundation&lt;/code&gt; code, the Xcode simulator is not always the best platform for running the code. Test on an actual device if you can. A demo project with code that supports this tutorial is available on &lt;a href=&quot;https://github.com/waltertyree/animated-robot&quot;&gt;Github&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;making-a-composition&quot;&gt;Making a Composition&lt;/h2&gt;
&lt;p&gt;Whenever you want to edit media or add effects, the first place to look is &lt;code&gt;AVComposition&lt;/code&gt;. This class in &lt;code&gt;AVFoundation&lt;/code&gt; has the purpose of arranging different assets and types of assets into a single asset for playback or processing. Asset types can include audio and video subtitles, metadata, and text. When working with &lt;code&gt;AVComposition&lt;/code&gt; it is helpful to think about &lt;code&gt;AVAsset&lt;/code&gt; and &lt;code&gt;AVAssetTrack&lt;/code&gt; objects. Unfortunately, because these items are so similar (&lt;code&gt;AVComposition&lt;/code&gt; is a subclass of &lt;code&gt;AVAsset&lt;/code&gt;) it is easy to get confused. Try to remember that the &lt;code&gt;AVTrack&lt;/code&gt; is a wrapper around the actual pixel, sound-wave, or other data and that &lt;code&gt;AVAsset&lt;/code&gt; is a collection of &lt;code&gt;AVTrack&lt;/code&gt; objects. When we want to combine and manipulate &lt;code&gt;AVAsset&lt;/code&gt; objects, an &lt;code&gt;AVComposition&lt;/code&gt; helps us do that. Changes made to any &lt;code&gt;AVAsset&lt;/code&gt; by an &lt;code&gt;AVComposition&lt;/code&gt; will not impact the original media file.&lt;/p&gt;
&lt;p&gt;As an extra layer, Apple keeps the editing features of an &lt;code&gt;AVComposition&lt;/code&gt; separate from the playback features by creating an &lt;code&gt;AVMutableComposition&lt;/code&gt; object. You will work with an &lt;code&gt;AVMutableComposition&lt;/code&gt; object in this tutorial.&lt;/p&gt;
&lt;p&gt;Each track in our composition will have a start time and a duration. The composition will ensure that all of the data it holds displays at the right time and that the tracks stay in sync.&lt;/p&gt;
&lt;p&gt;At the simplest, a video clip is a single &lt;code&gt;AVTrack&lt;/code&gt; of audio data and a single &lt;code&gt;AVTrack&lt;/code&gt; of video data.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;avasset&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 414px) 414px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;414&quot; height=&quot;373&quot; src=&quot;https://img.ly/_astro/avasset_dtjkd.webp&quot; srcset=&quot;/_astro/avasset_dtjkd.webp 414w&quot;&gt;&lt;/p&gt;
&lt;p&gt;So, to set up an empty &lt;code&gt;AVComposition&lt;/code&gt; for combining clips, use code such as 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;    let&lt;/span&gt;&lt;span&gt; movie &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVMutableComposition&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; videoTrack &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; movie.&lt;/span&gt;&lt;span&gt;addMutableTrack&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;withMediaType&lt;/span&gt;&lt;span&gt;: .video, &lt;/span&gt;&lt;span&gt;preferredTrackID&lt;/span&gt;&lt;span&gt;: kCMPersistentTrackID_Invalid)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; audioTrack &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; movie.&lt;/span&gt;&lt;span&gt;addMutableTrack&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;withMediaType&lt;/span&gt;&lt;span&gt;: .audio, &lt;/span&gt;&lt;span&gt;preferredTrackID&lt;/span&gt;&lt;span&gt;: kCMPersistentTrackID_Invalid)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above creates a new, empty &lt;code&gt;AVMutableComposition&lt;/code&gt; and then adds two tracks. One track will be able to hold &lt;code&gt;.video&lt;/code&gt; assets, and the other will hold &lt;code&gt;.audio&lt;/code&gt; assets. &lt;code&gt;AVFoundation&lt;/code&gt; supports many different kinds of audio and video formats, but the &lt;code&gt;.video&lt;/code&gt; track cannot hold &lt;code&gt;.audio&lt;/code&gt; formats. The &lt;code&gt;kCMPersistendTrackID_Invalid&lt;/code&gt; signals that you want a new, unique track id generated.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;empty-composition&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 130px) 130px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;130&quot; height=&quot;450&quot; src=&quot;https://img.ly/_astro/empty-composition_2frt4p.webp&quot; srcset=&quot;/_astro/empty-composition_2frt4p.webp 130w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AVFoundation&lt;/code&gt; can manipulate any of the ISO media formats. The Quicktime (&lt;code&gt;.mov&lt;/code&gt;) and MPEG (&lt;code&gt;.mp4&lt;/code&gt;) are the most common, however, &lt;code&gt;.heif&lt;/code&gt;, &lt;code&gt;.3gp&lt;/code&gt; and other formats are all supported.&lt;/p&gt;
&lt;h2 id=&quot;adding-video-clips&quot;&gt;Adding Video Clips&lt;/h2&gt;
&lt;p&gt;After creation, the &lt;code&gt;AVMutableComposition&lt;/code&gt; has a start time of &lt;code&gt;CMTime.zero&lt;/code&gt; and a duration of &lt;code&gt;CMTime.zero&lt;/code&gt;. Use the &lt;code&gt;insertTimeRange(_ timeRange: CMTimeRange, of track: AVAssetTrack, at startTime: CMTime) throws&lt;/code&gt; function on the audio and again on the video track to add data from the clip. In order to add data to the tracks, you load an &lt;code&gt;AVAsset&lt;/code&gt;, determine what range of time will go into the new composition, and then call the insert function. Your code might 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;  let&lt;/span&gt;&lt;span&gt; beachMovie &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVURLAsset&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;: Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;beach&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mov&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;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; beachAudioTrack &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; beachMovie.&lt;/span&gt;&lt;span&gt;tracks&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;withMediaType&lt;/span&gt;&lt;span&gt;: .audio).&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt; //2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; beachVideoTrack &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; beachMovie.&lt;/span&gt;&lt;span&gt;tracks&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;withMediaType&lt;/span&gt;&lt;span&gt;: .video).&lt;/span&gt;&lt;span&gt;first&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; beachRange &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTimeRangeMake&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;: CMTime.zero, &lt;/span&gt;&lt;span&gt;duration&lt;/span&gt;&lt;span&gt;: beachMovie.duration) &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; videoTrack&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;insertTimeRange&lt;/span&gt;&lt;span&gt;(beachRange, &lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;: beachVideoTrack, &lt;/span&gt;&lt;span&gt;at&lt;/span&gt;&lt;span&gt;: CMTime.zero) &lt;/span&gt;&lt;span&gt;//4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  try&lt;/span&gt;&lt;span&gt; audioTrack&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;insertTimeRange&lt;/span&gt;&lt;span&gt;(beachRange, &lt;/span&gt;&lt;span&gt;of&lt;/span&gt;&lt;span&gt;: beachAudioTrack, &lt;/span&gt;&lt;span&gt;at&lt;/span&gt;&lt;span&gt;: CMTime.zero)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what this code does:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Load a video file from a local URL. In the example, the video is in the application bundle, but it could also be a URL that points to a file somewhere else. This will not work with a streaming URL. It needs to be a video file.&lt;/li&gt;
&lt;li&gt;Extract the main videos and audio tracks from the video file. There may be multiple video and audio tracks in a file, but this code just grabs the first one.&lt;/li&gt;
&lt;li&gt;Create a time range. In this example, the range is the same duration as the clip we loaded.&lt;/li&gt;
&lt;li&gt;Insert the video track from the clip at the beginning of the video track of the composition and insert the audio track from the clip at the beginning of the audio track of the composition. A common mistake in this step is to use the &lt;code&gt;.duration&lt;/code&gt; property of the composition to insert the audio and video at the end. However, as soon as media is inserted into any track, the duration of the entire composition will change. So the audio and video would be played one after the other and you would see a blank screen when the audio is playing, and have no audio when the video is playing.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Repeat the steps for each clip that you want to add to the final composition.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;completed-composition&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 630px) 630px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;630&quot; height=&quot;398&quot; src=&quot;https://img.ly/_astro/completed-composition_AtrLx.webp&quot; srcset=&quot;/_astro/completed-composition_AtrLx.webp 630w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;using-the-composition&quot;&gt;Using the Composition&lt;/h2&gt;
&lt;p&gt;After all of the clips have been combined into the composition, it can be played in an &lt;code&gt;AVPlayer&lt;/code&gt; or exported using an &lt;code&gt;AVAssetExportSession&lt;/code&gt;. The code for these is the exact same as for any other &lt;code&gt;AVAsset&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;  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;: &lt;/span&gt;&lt;span&gt;AVPlayerItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: movie)) &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; playerLayer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVPlayerLayer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;player&lt;/span&gt;&lt;span&gt;: player) &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  playerLayer.frame &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; playerView.layer.bounds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  playerLayer.videoGravity &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; .resizeAspect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  playerView.layer.&lt;/span&gt;&lt;span&gt;addSublayer&lt;/span&gt;&lt;span&gt;(playerLayer) &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what the code above is doing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an &lt;code&gt;AVPlayer&lt;/code&gt; using a &lt;code&gt;movie&lt;/code&gt; object, which is the &lt;code&gt;AVMutableComposition&lt;/code&gt; that was created earlier.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;playerLayer&lt;/code&gt; to display the video and set its aspect ratio&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;playerLayer&lt;/code&gt; to &lt;code&gt;playerView&lt;/code&gt; which is a regular &lt;code&gt;UIView&lt;/code&gt; in a &lt;code&gt;UIViewController&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Play the video clip&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Alternatively you may want to export the new composition as a new movie file. Again, because the composition is an &lt;code&gt;AVAsset&lt;/code&gt; using a standard &lt;code&gt;AVAssetExportSession&lt;/code&gt; will write the movie to disk.&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 exporter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; exporter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAssetExportSession&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: movie,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                 presetName&lt;/span&gt;&lt;span&gt;: AVAssetExportPresetHighestQuality) &lt;/span&gt;&lt;span&gt;//1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //configure exporter&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.outputURL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; outputMovieURL &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.outputFileType &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; .mov&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //export!&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;exportAsynchronously&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;completionHandler&lt;/span&gt;&lt;span&gt;: { [&lt;/span&gt;&lt;span&gt;weak&lt;/span&gt;&lt;span&gt; exporter] &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    DispatchQueue.main.&lt;/span&gt;&lt;span&gt;async&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; let&lt;/span&gt;&lt;span&gt; error &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;error&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;        print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;failed &lt;/span&gt;&lt;span&gt;\(error.&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;movie has been exported to &lt;/span&gt;&lt;span&gt;\(outputMovieURL)&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;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 this code is doing:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create an export session using the &lt;code&gt;movie&lt;/code&gt; composition you made earlier.&lt;/li&gt;
&lt;li&gt;Set the save location to some file URL on your device.&lt;/li&gt;
&lt;li&gt;Wait for the exporter to finish and display the result.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Don’t forget that exporting an &lt;code&gt;AVAsset&lt;/code&gt; can take a long time and is asynchronous. You will want to display a spinner or a message to your user.&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;You can now combine clips into a longer clip for playback and export. However, trimming the original clips, adding filters, and rearranging the order will require more work before your application is ready for the store. Using an SDK like the IMG.LY’s &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;VideoEditor SDK&lt;/a&gt; can help you provide an app that has the features users will expect in addition to stitching clips together.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;VideoEditorSDK&lt;/code&gt; offers a &lt;a href=&quot;https://img.ly/docs/vesdk/ios/guides/video-composition/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;Video Composition Controller&lt;/a&gt; which will allow the users to stitch clips together. Additionally, it will allow the user to add new clips and edit clips using the other editing tools. Because the Video Composition Controller is a subclass of &lt;code&gt;UIViewController&lt;/code&gt; it can just be configured and dropped into your application. Instead of the code in our example, you pass the videos to the controller:&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; videoClips &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;  Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;stream&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mov&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;beach&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mov&quot;&lt;/span&gt;&lt;span&gt;),&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Bundle.main.&lt;/span&gt;&lt;span&gt;url&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forResource&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mountain&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withExtension&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;mov&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;compactMap&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;map&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;AVURLAsset&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;$0&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; video &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Video&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;assets&lt;/span&gt;&lt;span&gt;: videoClips)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; videoEditViewController &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; VideoEditViewController&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;videoAsset&lt;/span&gt;&lt;span&gt;: video, &lt;/span&gt;&lt;span&gt;configuration&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Configuration&lt;/span&gt;&lt;span&gt;())&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;present&lt;/span&gt;&lt;span&gt;(videoEditViewController, &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;completion&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above loads the same video clips as our demo application and then uses the &lt;code&gt;VideoEditViewController&lt;/code&gt; to present them. From there the user can make further edits preview and export the composition.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;stitch-clips&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 414px) 414px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;414&quot; height=&quot;896&quot; src=&quot;https://img.ly/_astro/stitch-clips_Ceb6W.webp&quot; srcset=&quot;/_astro/stitch-clips_Ceb6W.webp 414w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this example, you saw how to combine several video clips, each with one video and one audio track into a single movie file with one video and one audio track. To make more complex movie files consider building on this tutorial by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a second audio track, &lt;code&gt;AVFoundation&lt;/code&gt; will play sounds from both tracks at times specified at equal volume. Use &lt;code&gt;AVAudioMix&lt;/code&gt; to adjust the volume of the tracks at different times (the sample project has an example of adding a second audio track).&lt;/li&gt;
&lt;li&gt;Add more video tracks and use &lt;code&gt;AVMutableCompositionLayerInstruction&lt;/code&gt; to determine which track is visible at any time in the final video and to make fancy transitions between tracks.&lt;/li&gt;
&lt;/ul&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, comments, or suggestions!&lt;/p&gt;
&lt;p&gt;Looking to integrate video capabilities into your app? Check out our &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;Video Editor SDK&lt;/a&gt;, &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/camera-sdk&quot;&gt;Camera SDK&lt;/a&gt;!&lt;/p&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2022/01/combine-merge-clips-iOS-swift.png" medium="image"/><category>Video Editing</category><category>iOS App Development</category><category>iOS</category><category>Swift</category><category>How-To</category><category>Tutorial</category></item><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/camera-sdk&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?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&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?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&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>How to Create Image Filters for iOS</title><link>https://img.ly/blog/how-to-create-image-filters-in-ios/</link><guid isPermaLink="true">https://img.ly/blog/how-to-create-image-filters-in-ios/</guid><description>Write a custom `CIFilter` in Metal and Swift, usable in any CoreImage pipeline.</description><pubDate>Tue, 24 Aug 2021 08:09:37 GMT</pubDate><content:encoded>&lt;p&gt;In this tutorial, you will learn how to write a custom &lt;code&gt;CIFilter&lt;/code&gt; in Metal and Swift. You can use this filter in any CoreImage pipeline.&lt;/p&gt;
&lt;p&gt;Apple offers over 200 image filters in the CoreImage framework, but sometimes you need a little extra to make your images perfect. Apple provides two ways to create customize image filters. You can chain &lt;code&gt;CIFilter&lt;/code&gt;s together in a &lt;code&gt;CIFilter&lt;/code&gt; subclass or wrap a &lt;code&gt;CIKernel&lt;/code&gt; in a &lt;code&gt;CIFilter&lt;/code&gt; subclass. Either method creates a filter that CoreImage can execute on the GPU. That makes the filter fast. CoreImage filters can filter live video without impacting the frame rate. Before iOS 11 the only way to write a custom &lt;code&gt;CIKernel&lt;/code&gt; was to pass a string containing the code to the GPU at runtime using the &lt;a href=&quot;https://www.khronos.org/opengl/wiki/OpenGL_Shading_Language&quot;&gt;OpenGL Shading Language&lt;/a&gt;. This had two drawbacks: it was difficult to find errors in the string of commands and the kernel was not compiled until runtime. This tutorial will show you how to add a custom filter with a Metal-based &lt;code&gt;CIKernel&lt;/code&gt; to any Swift project in Xcode. The code samples in this tutorial use Xcode 12.5 and Swift 5.&lt;/p&gt;
&lt;p&gt;Clone &lt;a href=&quot;https://github.com/waltertyree/core-image-metal&quot;&gt;this repository&lt;/a&gt; for a sample project and example code that supports this tutorial. The repo also contains some other custom filters, demonstrating other aspects of Metal filters.&lt;/p&gt;
&lt;h2 id=&quot;adding-metal-support-to-an-xcode-project&quot;&gt;Adding Metal Support to an Xcode Project&lt;/h2&gt;
&lt;p&gt;The first step is to add a Metal source code file to your project.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;new-file&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 738px) 738px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;738&quot; height=&quot;529&quot; src=&quot;https://img.ly/_astro/new-file_2hEg5C.webp&quot; srcset=&quot;/_astro/new-file_Z1TNBBR.webp 640w, /_astro/new-file_2hEg5C.webp 738w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Find the “Metal File” template, select it and click &lt;code&gt;Next&lt;/code&gt;. Name the file and save it. At compile time, Xcode combines all of the &lt;code&gt;.metal&lt;/code&gt; files in your project into a single &lt;code&gt;.metallib&lt;/code&gt; file. So, organize your Metal code into lots of files or just one file as you prefer. The last step before you start writing code is to add compiler flags for &lt;code&gt;CIKernel&lt;/code&gt; objects.&lt;/p&gt;
&lt;p&gt;In the Build Settings, find the &lt;code&gt;other Metal Compiler Flags&lt;/code&gt; and add an entry of &lt;code&gt;-fcikernel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;build-settings-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 630px) 630px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;630&quot; height=&quot;485&quot; src=&quot;https://img.ly/_astro/build-settings-1_1ww0y8.webp&quot; srcset=&quot;/_astro/build-settings-1_1ww0y8.webp 630w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Then find the &lt;code&gt;Other Metal Linker Flags&lt;/code&gt; and add an entry of &lt;code&gt;-cikernel&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;other-metal-linker-flag&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 630px) 630px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;630&quot; height=&quot;145&quot; src=&quot;https://img.ly/_astro/other-metal-linker-flag_Z2a5E9F.webp&quot; srcset=&quot;/_astro/other-metal-linker-flag_Z2a5E9F.webp 630w&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Important note:&lt;/em&gt; any project without &lt;code&gt;.metal&lt;/code&gt; files hides the Metal Compiler and Linker sections from build Settings.&lt;/p&gt;
&lt;h2 id=&quot;setting-up-a-metal-file-for-coreimage-kernels&quot;&gt;Setting Up a Metal File for CoreImage Kernels&lt;/h2&gt;
&lt;p&gt;Metal is a general-purpose technology for writing code that will execute on the GPU. The compiler flags tell Metal that you will be writing code to work with &lt;code&gt;CoreImage&lt;/code&gt;. With a Metal source file in your project and the compiler flags set, you’re finally ready to write some code! Open the Metal file you created above. It should be empty except for&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;#include &amp;#x3C;metal_stdlib&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;using namespace metal;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Below these lines, import the CoreImage headers by adding:&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;#include &amp;#x3C;CoreImage/CoreImage.h&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and add a stub for your filter code with:&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;extern &quot;C&quot; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  namespace coreimage {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    // KERNEL GOES HERE&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;Prefix your kernel code with &lt;code&gt;extern &quot;C&quot;&lt;/code&gt; and the CoreImage namespace. You write kernel code in the &lt;a href=&quot;https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf&quot;&gt;Metal Shader Language&lt;/a&gt;, which is a variation of C. If you only know Swift, you should be able to rely on Xcode’s code-completion and symbol lookup to help you as you’re learning to write Metal code. In addition to the general language reference, Apple provides a &lt;a href=&quot;https://developer.apple.com/metal/MetalCIKLReference6.pdf&quot;&gt;Metal Shading Language for Core Image Kernels&lt;/a&gt; document.&lt;/p&gt;
&lt;p&gt;The example below creates a &lt;code&gt;CIColorKernel&lt;/code&gt;. This kernel is optimized for changing the color value of each pixel in an image. It will change each pixel to a grayscale version of itself. A filter applying his kernel will send the color value of each pixel of the image to the kernel.&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;float4 grayscaleFilterKernel(sample_t s) { //1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  float gray = (s.r + s.g + s.b) / 3;      //2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  return float4(gray, gray, gray, s.a);    //3&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 how this kernel works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The filter provides the color of the current pixel to the kernel as a &lt;code&gt;sample_t&lt;/code&gt; type. The kernel will return the new color for that pixel as a &lt;code&gt;float4&lt;/code&gt; type. The &lt;code&gt;sample_t&lt;/code&gt; type is equivalent to a &lt;code&gt;float4&lt;/code&gt; type and is only named differently because of historical convention. A &lt;code&gt;float4&lt;/code&gt; type contains four float values. In the case of color, they are the red, green, blue and alpha values for the pixel. Each float has a value between zero and 1.&lt;/li&gt;
&lt;li&gt;Calculate a grayscale version of the pixel by averaging the three color channels.&lt;/li&gt;
&lt;li&gt;Create a new &lt;code&gt;float4&lt;/code&gt; with the averaged value for the three color channels and the original alpha value for the fourth.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;CoreImage also provides a &lt;code&gt;CIWarpKernel&lt;/code&gt; class that is optimized for changing the position of each pixel and a &lt;code&gt;CIBlendKernel&lt;/code&gt; for blending two images. For more complex tasks, CoreImage also provides a general &lt;code&gt;CIKernel&lt;/code&gt;. Apple suggests that chaining together optimized kernels in a pipeline is usually more efficient than trying to write a larger, general kernel. A color-optimized kernel only has access to the color of the current pixel. A transform-optimized kernel can only affect the position of the current pixel. A general kernel can impact color and position as well as read other values from the source image.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-a-cikernel-with-a-cifilter&quot;&gt;Wrapping a `CIKernel` with a `CIFilter`&lt;/h2&gt;
&lt;p&gt;Now you’ll make a &lt;code&gt;CIFilter&lt;/code&gt; subclass as the interface between the Metal code and your Swift code. Every &lt;code&gt;CIFilter&lt;/code&gt; has an &lt;code&gt;outputImage&lt;/code&gt; variable that returns a &lt;code&gt;CIImage&lt;/code&gt;. Generally, input variables for a filter begin with &lt;code&gt;input&lt;/code&gt;. The grayscale filter you are making has a single input and a single output. Create a new Swift file in your project and title it “GrayscaleFilter.swift”. Then be sure to import CoreImage by adding&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;import CoreImage&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to the top of the file. Then create the filter as a subclass of &lt;code&gt;CIFilter&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;class GrayscaleFilter: CIFilter {&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, create a &lt;code&gt;kernel&lt;/code&gt; variable as either &lt;code&gt;static&lt;/code&gt; or &lt;code&gt;lazy&lt;/code&gt;, so that it only gets created one time, regardless of how often it’s called.&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;static var kernel: CIColorKernel = { () -&gt; CIColorKernel in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let url = Bundle.main.url(forResource: &quot;default&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                            withExtension: &quot;metallib&quot;)! //1&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let data = try! Data(contentsOf: url)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return try! CIColorKernel(functionName: &quot;grayscaleFilterKernel&quot;,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                      fromMetalLibraryData: data) //2&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;ol&gt;
&lt;li&gt;Look in the bundle for the compiled metal code. The metal code will have a filename of “default”.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.metalib&lt;/code&gt; may contain many kernels so use the one called “grayscaleFilterKernel”&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now add a variable to hold the input image.&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;var inputImage: CIImage?&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally override the &lt;code&gt;outputImage&lt;/code&gt; variable.&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;override var outputImage: CIImage? {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    guard let inputImage = inputImage else { return .none }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    return GrayscaleFilter.kernel.apply(extent: inputImage.extent,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                   roiCallback: { (index, rect) -&gt; CGRect in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                                  return rect&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                                  }, arguments: [inputImage])&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;apply&lt;/code&gt; function of the kernel takes an &lt;code&gt;extent&lt;/code&gt; argument. For a &lt;code&gt;CIImage&lt;/code&gt; the &lt;code&gt;extent&lt;/code&gt; property is the width and height of the image. The function has an &lt;code&gt;roiCallback&lt;/code&gt; that allows you to specify what part of the image the kernel uses for each calculation. For a color filter, you generally just pass back the &lt;code&gt;rect&lt;/code&gt;. Finally, pass in any &lt;code&gt;arguments&lt;/code&gt; as an array. Notice that there is not any type checking here. It is up to you to pass in the correct types in the correct order.&lt;/p&gt;
&lt;h2 id=&quot;using-the-filter&quot;&gt;Using the Filter&lt;/h2&gt;
&lt;p&gt;With the Metal code wrapped in a &lt;code&gt;CIFilter&lt;/code&gt; you can now apply the filter images the same as using one of the built-in filters. Create an instance of the filter, then assign the &lt;code&gt;inputImage&lt;/code&gt; variable a &lt;code&gt;CIImage&lt;/code&gt; and display the &lt;code&gt;outputImage&lt;/code&gt;. Your code might look something like this:&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 filter = GrayscaleFilter()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;filter.inputImage = image&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;displayView.image = UIImage(ciImage: (filter.outputImage ?? image) ?? CIImage())&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and it can render a grayscale version of an image, like in this example.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;filter-example&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 820px) 820px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;820&quot; height=&quot;766&quot; src=&quot;https://img.ly/_astro/filter-example_Z1xaewd.webp&quot; srcset=&quot;/_astro/filter-example_5m9Hh.webp 640w, /_astro/filter-example_ZL1iK4.webp 750w, /_astro/filter-example_Z1xaewd.webp 820w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;You can write custom filters and chain them to the built-in filters to invent new effects. Many of the math formulas for effects are available on the Internet. Most are easy to translate into the Metal Shader Language to make them perfect for your application. Writing an entire image or video editing application to showcase your filters is a much larger task. Using an SDK like the &lt;a href=&quot;https://img.ly/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;IMG.LY&lt;/a&gt; &lt;code&gt;PhotoEditorSDK&lt;/code&gt; or &lt;code&gt;VideoEditorSDK&lt;/code&gt; can help you provide an app that has the features that users will expect in addition to your cool filters.&lt;/p&gt;
&lt;p&gt;To add your filter to the &lt;code&gt;PhotoEditorSDK&lt;/code&gt; you first wrap your &lt;code&gt;CIFilter&lt;/code&gt; with an &lt;code&gt;Effect&lt;/code&gt;. For a basic filter, you only need to override the &lt;code&gt;newEffectFilter&lt;/code&gt; variable.&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;import PhotoEditorSDK&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;class GrayscaleEffect: Effect {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  override var newEffectFilter: CIFilter? {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;      GrayscaleFilter()&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;Then add the filter to the &lt;code&gt;Effect.all&lt;/code&gt; array when configuring the SDK, using something like&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;Effect.all = [NoEffect(), GrayscaleEffect(identifier: &quot;grayscaleFilter&quot;, displayName: &quot;Best Gray&quot;)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now your filter appears the same as the other 60+ filters that ship with the SDK.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 316px) 316px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;316&quot; height=&quot;640&quot; src=&quot;https://img.ly/_astro/create-filter-for-iOS-2_1EN1kE.webp&quot; srcset=&quot;/_astro/create-filter-for-iOS-2_1EN1kE.webp 316w&quot;&gt;&lt;/p&gt;
&lt;p&gt;It applies to live camera previews as well as still images.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this tutorial, you learned how to configure Xcode for custom Metal code. You also learned how to create a color filter kernel in Metal and wrap it in a &lt;code&gt;CIFilter&lt;/code&gt;. By combining your filters with Apple’s filters in pipelines, you can create unique effects for your next app. Further, using an SDK such as &lt;code&gt;PhotoEditorSDK&lt;/code&gt; or &lt;code&gt;VideoEditorSDK&lt;/code&gt; allows you to showcase your filters in a full-featured image or video editing application. The PhotoEditor SDK for iOS &lt;a href=&quot;https://img.ly/docs/pesdk/ios/introduction/overview/&quot;&gt;documentation&lt;/a&gt; will give you deeper look into all other image adjustments including, ofcourse, the image filtering we discussed above.&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/08/create-a-photo-filter-for-iOS.jpg" medium="image"/><category>App Development</category><category>iOS App Development</category><category>iOS</category><category>Photo Editing</category><category>Tutorial</category><category>Development</category><category>Mobile App Development</category><category>Photo Filter</category><category>How-To</category></item><item><title>Flutter: Our new plugins made for Dart developers</title><link>https://img.ly/blog/flutter-native-plugins-made-for-dart-developers/</link><guid isPermaLink="true">https://img.ly/blog/flutter-native-plugins-made-for-dart-developers/</guid><description>We are happy to announce our new official Flutter plugins wrapping our native PhotoEditor SDK and VideoEditor SDK for one of the most popular cross-platform frameworks.</description><pubDate>Thu, 11 Mar 2021 14:12:09 GMT</pubDate><content:encoded>&lt;p&gt;Flutter enables developers to build native iOS and Android applications with a single Dart codebase whilst the application’s performance is kept at a very high native-like level. This is even more ensured by the huge amount of Flutter plugins, which wrap native code for the use in Dart.&lt;/p&gt;
&lt;p&gt;With the rising demand for photo and video editing solutions, we were committed to provide plugins that enable developers to use our &lt;a href=&quot;https://img.ly/docs/pesdk/&quot;&gt;native PhotoEditor SDK&lt;/a&gt; and our &lt;a href=&quot;https://img.ly/docs/vesdk/&quot;&gt;native VideoEditor SDK&lt;/a&gt; within their Flutter applications. This said we are proud to announce that the Flutter plugins &lt;a href=&quot;https://pub.dev/packages/photo_editor_sdk&quot;&gt;&lt;code&gt;photo_editor_sdk&lt;/code&gt;&lt;/a&gt; and &lt;a href=&quot;https://pub.dev/packages/video_editor_sdk&quot;&gt;&lt;code&gt;video_editor_sdk&lt;/code&gt;&lt;/a&gt; are now extending our list of cross-platform modules. The combination of both products allows you to add comprehensive image and video editing tools to your Flutter application for iOS and Android - within minutes.&lt;/p&gt;
&lt;h2 id=&quot;usage&quot;&gt;Usage&lt;/h2&gt;
&lt;p&gt;While building the plugins, we focused on making the use and integration of our existing SDKs as easy as possible and minimizing the required platform-specific knowledge and skillset. Therefore, using the plugins is as easy as writing a single line of Dart code:&lt;/p&gt;

&lt;h3 id=&quot;customization&quot;&gt;Customization&lt;/h3&gt;
&lt;p&gt;Furthermore, we wanted to keep the high level of customization options like cropping or resizing that our customers are used to in our products. In this sense, we created a dedicated &lt;a href=&quot;https://pub.dev/documentation/imgly_sdk/latest/imgly_sdk/Configuration-class.html&quot;&gt;&lt;code&gt;Configuration&lt;/code&gt;&lt;/a&gt; Dart class which serves the option to customize the SDK to your needs without ever leaving the Flutter world.&lt;br&gt;
Using the configuration is fast and easy:&lt;/p&gt;

&lt;h2 id=&quot;where-to-go-from-here&quot;&gt;Where to go from here?&lt;/h2&gt;
&lt;p&gt;Feel free to explore our packages, usage examples as well as the API documentation on &lt;a href=&quot;https://pub.dev/publishers/img.ly/packages&quot;&gt;pub.dev&lt;/a&gt; and/ or &lt;a href=&quot;https://github.com/imgly?q=flutter&quot;&gt;GitHub&lt;/a&gt;.&lt;br&gt;
We released our new Flutter plugins under open-source licenses, so feedback and pull requests are welcome.&lt;/p&gt;</content:encoded><dc:creator>Leon</dc:creator><media:content url="https://blog.img.ly/2021/03/flutter-plugin-for-photo-video-editor-dart.jpg" medium="image"/><category>Flutter</category><category>Photo Editor</category><category>Developer</category><category>Video Editor</category><category>Android</category><category>iOS</category><category>Stickers</category><category>Tech</category><category>How-To</category><category>Company</category></item><item><title>Make it pop - introducing Animated and Smart Stickers</title><link>https://img.ly/blog/smart-animated-stickers/</link><guid isPermaLink="true">https://img.ly/blog/smart-animated-stickers/</guid><description>Stickers have become an essential part of digital expression and a popular feature across use cases, from marketing campaigns and social media posts to annotation. We are happy to announce our newest feature.</description><pubDate>Thu, 13 Aug 2020 18:31:18 GMT</pubDate><content:encoded>&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Our latest release: brand-new sticker types for iOS and Android&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We are always on the lookout to improve our editor tools to engage users to be creative and add beautifully designed content.&lt;/p&gt;
&lt;p&gt;Our latest release for both mobile PhotoEditor SDK and VideoEditor SDK takes our stickers to the next level: With animated and smart stickers, we added popular features best known from apps like Instagram and TikTok – making editing more dynamic, expressive, and engaging. You can now capture your favorite moments by adding more context to your photos and videos. Maybe you finally landed that kickflip on a warm Monday morning, so you add the date and weather information. You can now also add your animated logo.  &lt;/p&gt;
&lt;p&gt;Our animated stickers not only provide you with a set of premade assets that come with the latest version of the editor but also let you integrate your custom ones tailored to your company and brand.&lt;/p&gt;
&lt;p&gt;Smart Stickers on the other hand open up a whole range of new possibilities, visualizing dynamic information like weather data, location, or dates. Like our text design feature, it’s the ease of use that makes these stickers special – simply cycle through different variations with a tap.&lt;/p&gt;
&lt;p&gt;Take a look at all updates and assets on our Mobile SDK page.    &lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://photoeditorsdk.com/mobile-sdk/?utm_campaign=Projects&amp;#x26;utm_medium=Blog&amp;#x26;utm_source=Main%20Blog&amp;#x26;utm_content=Sticker%20Types&quot;&gt;&lt;img alt=&quot;Check it out!&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 150px) 150px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;150&quot; height=&quot;47&quot; src=&quot;https://img.ly/_astro/check_it_out_Z21PgLn.webp&quot; srcset=&quot;/_astro/check_it_out_Z21PgLn.webp 150w&quot;&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;PS: Do you have feedback or suggestions for new smart stickers you want to see from us? Let us know in our &lt;a href=&quot;https://sales20200811.typeform.com/to/sP6dhXMc&quot;&gt;Quick-Survey&lt;/a&gt;. We’re happy to hear from you!&lt;/p&gt;</content:encoded><dc:creator>Michael K.</dc:creator><media:content url="https://blog.img.ly/2020/08/6mb-smartsticker-animatedsticker-pesdk.gif" medium="image"/><category>iOS</category><category>Android</category><category>Video Editing</category><category>Photo Editing</category><category>SDK</category><category>Colors</category><category>Stickers</category><category>Company</category></item><item><title>React Native: Native Modules made for React developers</title><link>https://img.ly/blog/react-native-native-modules-made-for-react-developers-59ca93c41541/</link><guid isPermaLink="true">https://img.ly/blog/react-native-native-modules-made-for-react-developers-59ca93c41541/</guid><description>On the developer experience with 3rd-party libraries for RN 0.60+</description><pubDate>Thu, 20 Feb 2020 23:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;*&lt;/strong&gt;TL;DR******: How much of “native” does a React Native developer need? “Nativ”, “nati”, “nat”, “na”, “n”, or none? Version 0.60 drastically shifts this requirement towards none, even when native modules depend on external native libraries!*&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ideally, React Native (RN) should enable web developers to write React code once and thus magically create truly native Android and iOS apps without ever having to touch a single line of platform-specific native code. In reality, developers that use RN to build cross-platform apps need to be quite experienced with the respective development tool stacks and programming languages of the native platforms. This unfortunate truth is particularly the case when integrating native third-party libraries from scratch as we detailed in a &lt;a href=&quot;https://img.ly/blog/photoeditor-sdk-react-native-15179c589a55/&quot;&gt;previous blog post&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;react-native-060--a-relief-for-ios&quot;&gt;React Native 0.60+ — A relief for iOS&lt;/h2&gt;
&lt;p&gt;There are two ingredients to the 0.60 release that will save non-native developers countless hours of integration trouble and also make experienced native developers very happy.&lt;/p&gt;
&lt;p&gt;First, RN 0.60 reconsidered CocoaPods as the primary build and dependency management system for the iOS platform. Using &lt;a href=&quot;https://reactnative.dev/blog/2019/07/03/version-60#cocoapods-by-default&quot;&gt;CocoaPods is now the default&lt;/a&gt; for every new React Native app. This choice greatly simplifies the installation and integration process of native third-party frameworks.&lt;/p&gt;
&lt;p&gt;Second, &lt;a href=&quot;https://reactnative.dev/blog/2019/07/03/version-60#native-modules-are-now-autolinked&quot;&gt;autolinking native modules&lt;/a&gt; per default finally removes the need to remember the &lt;code&gt;react-native link&lt;/code&gt; command after installing any React Native library with &lt;code&gt;npm&lt;/code&gt; or &lt;code&gt;yarn&lt;/code&gt; in favor of &lt;a href=&quot;https://github.com/react-native-community/cli/blob/main/docs/autolinking.md&quot;&gt;manually issuing another command&lt;/a&gt;, namely &lt;code&gt;(cd ios &amp;#x26;&amp;#x26; pod install)&lt;/code&gt; to trigger CocoaPods. This change highlights the new integral role of CocoaPods for RN. The utmost benefit of this transition is that all native dependencies of your project are installed in a fully automatic manner. Even nested native dependencies are properly resolved and installed as one would expect.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No&lt;/strong&gt; more juggling with &lt;code&gt;.framework&lt;/code&gt; bundles for third-party libraries with CocoaPods support!&lt;/p&gt;
&lt;p&gt;Eventually, even the “burden” of manually pulling the CocoaPods trigger might be gone in future RN versions…&lt;/p&gt;
&lt;p&gt;Technically, if writing headlines with Unicode characters would be a good idea, the title of this blog post should have been:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;N͟a͟t͟i͟v͟e͟ M͟o͟d͟u͟l͟e͟s͟ made for React N̶a̶t̶i̶v̶e̶ developers&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;to underline the vanishingly required attribute “native” for developers that are capable of creating stunning React Native apps by using powerful external native libraries without the native tooling hassle.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;A React Native app with 28 lines of code showcasing the customization features of the all-new React Native module for PhotoEditor SDK&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2000px) 2000px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2000&quot; height=&quot;1460&quot; src=&quot;https://img.ly/_astro/image-24_15C0Mt.webp&quot; srcset=&quot;/_astro/image-24_ZseCaJ.webp 640w, /_astro/image-24_ZDKILL.webp 750w, /_astro/image-24_1PbNnB.webp 828w, /_astro/image-24_1PhIzE.webp 1080w, /_astro/image-24_uKBpL.webp 1280w, /_astro/image-24_211IR.webp 1668w, /_astro/image-24_15C0Mt.webp 2000w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;certificate-of-happiness&quot;&gt;Certificate of happiness&lt;/h2&gt;
&lt;p&gt;Building on this strong and developer-friendly foundation of RN 0.60, we are proud to introduce the all-new React Native integrations &lt;code&gt;[react-native-photoeditorsdk](https://www.npmjs.com/package/react-native-photoeditorsdk)&lt;/code&gt; and &lt;code&gt;[react-native-videoeditorsdk](https://www.npmjs.com/package/react-native-videoeditorsdk)&lt;/code&gt; for our lineup of native SDKs — &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; and &lt;a href=&quot;https://img.ly/video-sdk&quot;&gt;VideoEditor SDK&lt;/a&gt;. The interplay of both products allows you to add comprehensive image and video editing tools to your React Native app for iOS and Android — within minutes.&lt;/p&gt;
&lt;p&gt;While crafting our new modules, the primary objective was and always will be to foster their adoption by reducing the required platform-specific knowledge and skill set to a bare minimum. We are thrilled to put a wealth of &lt;a href=&quot;https://github.com/imgly/pesdk-react-native/blob/master/configuration.ts&quot;&gt;configuration and customization options&lt;/a&gt; at the developers’ finger tips without having them to leave the JavaScript tooling and programming environment. The complete &lt;a href=&quot;https://github.com/imgly/pesdk-react-native/blob/master/index.d.ts&quot;&gt;API&lt;/a&gt; is typed and thoroughly documented so that any decent source code editor will auto-import missing types and display context-sensitive quick help pop-ups alongside your code.&lt;/p&gt;
&lt;h2 id=&quot;bye-bye-native-platform-specific-asset-management&quot;&gt;Bye-bye native platform-specific asset management&lt;/h2&gt;
&lt;p&gt;Hello &lt;code&gt;require&lt;/code&gt;! Our new JavaScript API heavily relies on this well-known pseudo-keyword-like function which makes handling &lt;a href=&quot;https://reactnative.dev/docs/0.60/images#static-image-resources&quot;&gt;static images&lt;/a&gt; as well as &lt;a href=&quot;https://reactnative.dev/docs/0.60/images#static-non-image-resources&quot;&gt;static non-image resources&lt;/a&gt; a breeze. By using it, e.g., in your &lt;code&gt;App.js&lt;/code&gt; file, RN will do the heavy lifting and bundle all static assets with the native apps for you. The following snippet demonstrates this convenience for customizing the sticker tool (line &lt;code&gt;8&lt;/code&gt;, &lt;code&gt;11&lt;/code&gt;, &lt;code&gt;14&lt;/code&gt;) and for passing an input image to the editor (line &lt;code&gt;24&lt;/code&gt;). It presumes that the corresponding images are available in your app’s project folder.&lt;/p&gt;

&lt;h2 id=&quot;further-examples&quot;&gt;Further examples&lt;/h2&gt;
&lt;p&gt;We created two separate repositories for a &lt;a href=&quot;https://github.com/imgly/pesdk-react-native-demo&quot;&gt;photo editor example project&lt;/a&gt; and a &lt;a href=&quot;https://github.com/imgly/vesdk-react-native-demo&quot;&gt;video editor example project&lt;/a&gt;, so that our sample assets won’t increase the size of your &lt;code&gt;node_modules&lt;/code&gt; folder when installing our modules.&lt;/p&gt;
&lt;p&gt;If you are looking for a step-by-step guide that explains in detail how to supercharge your RN app with image and video editing capabilities, then stay tuned for upcoming additions to our &lt;a href=&quot;https://www.youtube.com/playlist?list=PLTchY1qyq9BvoVKpMkMWmAOII7NvWp0KS&quot;&gt;screencasts&lt;/a&gt;. You will also learn how to use advanced SDK features, such as serializing and reusing previously applied editing operations directly in your RN app.&lt;/p&gt;
&lt;h2 id=&quot;open-source-community&quot;&gt;Open-source community&lt;/h2&gt;
&lt;p&gt;We release our new RN modules that expose our native SDKs to the RN ecosystem under open-source licenses. Feedback and pull requests are welcome. The sources are also a valuable basis for bridging other cross-platform frameworks that we are currently not supporting with official integrations.&lt;/p&gt;
&lt;p&gt;*&lt;strong&gt;*Thanks for reading! To stay in the loop, subscribe to our&lt;/strong&gt; &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;&lt;strong&gt;Newsletter&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.**&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Alexander</dc:creator><media:content url="https://blog.img.ly/2020/04/1-BON-euRy0GuMYNqdf3pgRw.jpeg" medium="image"/><category>React Native</category><category>React</category><category>iOS</category><category>Android</category><category>Photo Editing</category><category>Tech</category><category>How-To</category><category>Insights</category></item><item><title>The future is Video — the future is now!</title><link>https://img.ly/blog/the-future-is-video-the-future-is-now-f5b3911023f6/</link><guid isPermaLink="true">https://img.ly/blog/the-future-is-video-the-future-is-now-f5b3911023f6/</guid><description>Introducing VideoEditor SDK for iOS and Android</description><pubDate>Tue, 13 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://img.ly/video-sdk&quot;&gt;VideoEditor SDK&lt;/a&gt; expands the powerful tools and features of &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; into the realm of mobile video creation, empowering you to seamlessly integrate a cinematic experience into your mobile applications.With an intuitive and elegant UI, an extensive filter gallery, advanced adjustment tools, and crops for social aspect ratios you’ll gift your users with the ability to create engaging and professional-looking videos on the fly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/embed/FDuwEtBf8N8?feature=oembed&quot;&gt;https://www.youtube.com/embed/FDuwEtBf8N8?feature=oembed&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Video is one of the most effective means of communication in our era. According to &lt;a href=&quot;https://blog.hubspot.com/marketing/state-of-video-marketing-new-data&quot;&gt;Hubspot&lt;/a&gt;, the usage and consumption of video content continue to grow and still haven’t reached their saturation point. Video is changing the way we interact on a daily basis, and it’s here to stay.&lt;/p&gt;
&lt;p&gt;Although most videos are shot spontaneously and then posted instantly, even slight editing can have a tremendous effect on the content’s impact. VideoEditor SDK lets your users create professional-looking footage without having to rely on external apps while using your solution. The SDK is packed with intuitive yet powerful tools that allow for the creation of an endless variety of stunning visual effects. All tools come with an instant preview and can be tailored to fit your app perfectly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;trimming--transformation-keeping-itrelevant&quot;&gt;Trimming &amp;#x26; Transformation: Keeping it relevant&lt;/h2&gt;
&lt;p&gt;The Trimming tool helps your users to keep their content on point and to get rid of unnecessary, boring, or unwanted parts with ease. Furthermore, the SDK is equipped with a Transform tool kit that unifies cropping, flipping, and rotating operations. Featuring various preset video aspect ratios most popular for Instagram (1:1), YouTube (16:9) or other platforms like Snapchat (9:16) — the tool lets your users present their content in the most appropriate format.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;video-grading-the-hollywood-look-andfeel&quot;&gt;Video grading: The Hollywood look and feel&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://img.ly/video-sdk&quot;&gt;VideoEditor SDK&lt;/a&gt; ships with over 60 video filters that let your users achieve a cinematic look for their videos with a single tap. Additionally, the Adjustment section holds a variety of handy tools to tweak and fine-tune video content ranging from basic operations like brightness and contrast to more sophisticated options like exposure and gamma. Through instant preview, your users can directly see how their changes affect the final video.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;captioning-and-assets-compelling-storytelling&quot;&gt;Captioning and Assets: Compelling storytelling&lt;/h2&gt;
&lt;p&gt;Video is a narrational medium and VideoEditor SDK provides all necessary functions for your users to quickly turn their footage into engaging and attention-grabbing stories. They can customize their videos with captions using the Text tool or add a stunning typography design with the Text Design tool that automatically merges input text with designer-grade typography layouts.&lt;/p&gt;
&lt;p&gt;On top of that, the SDK ships with various asset libraries for your users to personalize their content with Stickers, Frames, or Overlays to create something they’ll love to share.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;learn-more-about-videoeditor-sdk-and-delight-your-users-with-powerful-video-editing-capabilities&quot;&gt;Learn more about &lt;a href=&quot;https://img.ly/video-sdk&quot;&gt;VideoEditor SDK&lt;/a&gt; and delight your users with powerful video editing capabilities.&lt;/h2&gt;</content:encoded><dc:creator>Felix</dc:creator><media:content url="https://blog.img.ly/2020/04/image-30.png" medium="image"/><category>Videos</category><category>iOS</category><category>Android</category><category>App Development</category><category>Video Editing</category><category>Tech</category><category>How-To</category><category>Learning</category></item><item><title>How to integrate a Photo Editor into your iOS App</title><link>https://img.ly/blog/how-to-integrate-a-photo-editor-into-your-ios-app-ced008a7088b/</link><guid isPermaLink="true">https://img.ly/blog/how-to-integrate-a-photo-editor-into-your-ios-app-ced008a7088b/</guid><description>7-minute Video Tutorial</description><pubDate>Wed, 10 Jul 2019 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This tutorial is going to walk you through the integration process of the PhotoEditor SDK for iOS. You’ll learn how to setup the editor in seven minutes using CocoaPods. We created similar tutorials for &lt;a href=&quot;https://img.ly/blog/how-to-integrate-a-photo-editor-into-your-android-app-ee4148816e25/&quot;&gt;Android&lt;/a&gt; and &lt;a href=&quot;https://img.ly/blog/how-to-integrate-a-photo-editor-into-your-website-4578cc6ef6f3/&quot;&gt;HTML5&lt;/a&gt;, so make sure to check those out as well.(Please make sure to get a trial license for the &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; before integrating it.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://www.youtube.com/embed/AHzD4Mnm-NA?feature=oembed&quot;&gt;https://www.youtube.com/embed/AHzD4Mnm-NA?feature=oembed&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;transcript&quot;&gt;&lt;strong&gt;Transcript&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In this tutorial, we’re going to show you how to integrate the &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; into your iOS app. We’re going to use Xcode and the &lt;a href=&quot;https://img.ly/docs/pesdk/&quot;&gt;PhotoEditor SDK’s documentation&lt;/a&gt;. So, first of all, we navigate to the “Getting Started” guide for iOS. We’re going to install the SDK using CocoaPods, because it is the recommended way to initialize this dependency.&lt;/p&gt;
&lt;p&gt;We create a new Xcode project and select the “Single View App” template. Then, we assign a name to our project. For this tutorial, we’ll go with “PhotoEditorApp.” Here, it is essential that the given bundle identifier is identical to the one you used to request your license with. We decide to save our project files to the documents folder and create the new project.&lt;/p&gt;
&lt;p&gt;Now, we change the iOS Deployment Target to version 9.0 as the SDK supports all iOS versions from 9.0 upwards, and thus, our app will be available for the highest possible number of devices. After that, we insert our previously downloaded license file into our app by simply dragging and dropping the file into our app’s folder. Here, it’s important that “Copy items if needed” is checked so that our license file will be copied to the appropriate place in our project instead of just being referenced.&lt;/p&gt;
&lt;p&gt;Now, we can close our Xcode project. We open our folder structure and see that our project has been created in the documents folder. Now, we need the terminal to install the PhotoEditor SDK via CocoaPods. We type &lt;code&gt;cd&lt;/code&gt; which is short for “change directory”, to change the directory of our source files. We can copy the project’s path via drag and drop. We now instruct CocoaPods to prepare Xcode for the use with CocoaPods by typing &lt;code&gt;pod init&lt;/code&gt;. After that, CocoaPods creates the file “podfile.” We open the podfile in a text editor by double-clicking.&lt;/p&gt;
&lt;p&gt;Here, we uncomment the second line and thus define that our project shall run from iOS version 9.0 upwards. Next, we copy this line (&lt;code&gt;pod &apos;PhotoEditorSDK&apos;&lt;/code&gt;) from the &lt;a href=&quot;https://img.ly/docs/pesdk/&quot;&gt;PhotoEditor SDK’s documentation&lt;/a&gt; and paste it under &lt;code&gt;# Pods for PhotoEditorApp&lt;/code&gt; to create a dependency between our project and the PhotoEditor SDK. We save the podfile and close the text editor. Now, we return to the terminal and instruct CocoaPods to install all dependencies by typing &lt;code&gt;pod install&lt;/code&gt; and executing our command with the return key.&lt;/p&gt;
&lt;p&gt;Here we can see that CocoaPods advises us to open our Xcode project with the xcworkspace file only. Furthermore, we see that there’s now a new folder called “pods.” Ideally, you wouldn’t check that folder into your source control system as it potentially contains large files, which can result in some issues. Github, for example, has a limit for the maximum file size, which the PhotoEditor SDK would exceed. So, it would be best if every user that works on the project activates &lt;code&gt;pod install&lt;/code&gt; to install the dependencies for each processing machine.&lt;/p&gt;
&lt;p&gt;Now, we can open the project file to incorporate our license into the file &lt;code&gt;AppDelegate.swift&lt;/code&gt;. We copy this line from our documentation and paste it under the method &lt;code&gt;UIApplication, didfinishLaunchingWithOptions&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;if let licenseURL = Bundle.main.url(forResource: &quot;license&quot;, withExtension: &quot;&quot;) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    PESDK.unlockWithLicense(at: licenseURL)&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;It is important that we name our license file exactly like we did when we incorporated it into our project. In our case, that would be “ios_license” instead of “license.” Also, we can see that there is an error message, because we haven’t imported the PhotoEditor SDK yet. We simply type &lt;code&gt;**import** PhotoEditorSDK&lt;/code&gt;, and the issue is resolved. Now our project is prepared so that we can use the PhotoEditor SDK with an active license.&lt;/p&gt;
&lt;p&gt;Next, we’re going to prepare our app, so that it actually uses the PESDK. We configure our app with the &lt;code&gt;Main.storyboard&lt;/code&gt; by opening the &lt;code&gt;assistantview&lt;/code&gt; and creating a new button by just dragging and dropping it on the canvas. We’re going to use Alignment Constraints so that the button will always be displayed in the middle of the screen no matter the form factor of the device in use.&lt;/p&gt;
&lt;p&gt;We’re going to name our button after our app and link it from our storyboard with our source code on the right-hand side via drag and drop. Now, we’re going to set a name for our method that is going to be called once the button is pressed. For this tutorial, we’ll go with &lt;code&gt;startPhotoEditor&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We’ve decided that we want to start the PhotoEditor SDK with a CameraViewController. So, we copy the respective code from our documentation and paste it in the body of our method under &lt;code&gt;startPhotoEditor&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 cameraViewController = CameraViewController()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;present(cameraViewController, animated: true, completion: nil)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When we save the file, we get yet another error message that the camera can’t be found because we haven’t imported the &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; for this source file either. Once we’ve done that, the error message disappears.&lt;/p&gt;
&lt;p&gt;As we want our editor to start in camera mode, we have to create some entries in the Info.plist file that will allow us to use our device’s camera and grant access to our device’s image library. That is a restriction by Apple. If we didn’t create these entries, we wouldn’t be able to use our app as it would crash as soon as we open the camera. All necessary entries can be found under “Privacy.” First, we need “Camera Usage Description.” Here, we have to give a reason why we want access to our user’s camera. Said reason should be as meaningful as possible. For this tutorial, we’ll go with “We use your camera to take photos for editing.” We also want to allow the import of photos from the device’s library, so we add the identifier “Photo Library Usage Description” and type “We use your photos so that you can edit them” as description.&lt;/p&gt;
&lt;p&gt;Now, we can run the app in the simulator. If we wanted to run the app on a physical device, we’d have to provide an Apple iOS developer account under “Team” so that the app can be signed because only registered developers are allowed to install apps on physical devices. However, we’ll go with the simulator.&lt;/p&gt;
&lt;p&gt;*&lt;strong&gt;*Thanks for reading! To stay in the loop, subscribe to our&lt;/strong&gt; &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;&lt;strong&gt;Newsletter&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.**&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Felix</dc:creator><media:content url="https://blog.img.ly/2020/04/image-32.png" medium="image"/><category>iOS</category><category>App Development</category><category>CocoaPods</category><category>Tutorial</category><category>Technology</category><category>Tech</category><category>How-To</category><category>Learning</category></item><item><title>Updates to the PhotoEditor SDK</title><link>https://img.ly/blog/updates-to-the-photoeditor-sdk-7df794757979/</link><guid isPermaLink="true">https://img.ly/blog/updates-to-the-photoeditor-sdk-7df794757979/</guid><description>Color Pipette, DuoTone Filters, Folders, Snapping, Light Theme</description><pubDate>Tue, 28 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Over the course of the last weeks, we’ve integrated a couple of handy features into the &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt;. It’s now easier than ever for you to equip your service with the tools that make it the one-stop creative solution for your users.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;color-pipette&quot;&gt;Color Pipette&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;&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;636&quot; src=&quot;https://img.ly/_astro/1-GwN_5xBj2mqiOVnc8TZBiA_Zmv8Fh.webp&quot; srcset=&quot;/_astro/1-GwN_5xBj2mqiOVnc8TZBiA_Zmv8Fh.webp 300w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Colors are important for virtually every aspect of the digital world and a powerful tool when it comes to design, branding, social media and marketing. Color is one of the most important things to consider when communicating online. If used right these visual cues harmonize the impression and emphasize the message. But if used wrong, colors may also wreck design and content just as thoroughly as Comic Sans.&lt;/p&gt;
&lt;p&gt;Whether it is for creating content, ads, special offers or a brand logo, finding the right color is just too important a decision to be taken lightly. The PhotoEditor SDK’s color pipette helps your users to discover and marry their content with the right colors for any purpose.&lt;/p&gt;
&lt;p&gt;The Color Pipette is available for iOS and Android.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;duotone-filters&quot;&gt;DuoTone Filters&lt;/h3&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;500&quot; src=&quot;https://img.ly/_astro/1-n-7aDM2hWU8oEpJKzB0fsQ_Sk1AA.webp&quot; srcset=&quot;/_astro/1-n-7aDM2hWU8oEpJKzB0fsQ_Sk1AA.webp 400w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Duotone is amongst the big trends in modern web design. What once served as a way of saving money on printed materials, today, adds boldness to designs and can be used to many ends. Duotone keeps things minimalistic yet captivating. In a world of full-color imagery, duotone images are a refreshing novelty as they add visual appeal by subtracting colors.&lt;/p&gt;
&lt;p&gt;We added 8 filters to the PhotoEditor SDK’s gallery that map contrasting colors to an image based on its luminance values. Via a slider, your users can control which color the creative should lean to.&lt;/p&gt;
&lt;p&gt;DuoTone filters are available for iOS, Android, and HTML5.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;folders&quot;&gt;Folders&lt;/h3&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 200px) 200px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;200&quot; height=&quot;400&quot; src=&quot;https://img.ly/_astro/1-iED1WxauYJPb5B2SrTFRlw_1aWfHm.webp&quot; srcset=&quot;/_astro/1-iED1WxauYJPb5B2SrTFRlw_1aWfHm.webp 200w&quot;&gt;&lt;/p&gt;
&lt;p&gt;To streamline your users’ workflow and facilitate the exploration of the PhotoEditor SDK’s vast filter library, we categorized them into 7 folders. We also exchanged all filter names for more descriptive ones.&lt;/p&gt;
&lt;p&gt;Folders are available for all platforms and completely customizable, you can rename the folders, add your own and rearrange the filters.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;snapping&quot;&gt;Snapping&lt;/h3&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;711&quot; src=&quot;https://img.ly/_astro/1-zQX5j0j67HMC3K-w16gRTQ_1ItsWj.webp&quot; srcset=&quot;/_astro/1-zQX5j0j67HMC3K-w16gRTQ_1ItsWj.webp 400w&quot;&gt;&lt;/p&gt;
&lt;p&gt;To facilitate the precise layout of creatives, we added a snapping feature to our Text, Text Design, and Sticker tool. The visual elements snap to the center and borders as well as to certain angles.&lt;/p&gt;
&lt;p&gt;You can customize the snapping properties to your wishes. Snapping is available for HTML5 iOS and Android.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;light-theme&quot;&gt;Light Theme&lt;/h3&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;577&quot; src=&quot;https://img.ly/_astro/1-1L2FsQgLNJyP5YzlHL9zOQ_12lG5X.webp&quot; srcset=&quot;/_astro/1-1L2FsQgLNJyP5YzlHL9zOQ_12lG5X.webp 400w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Rumor has it that Apple is going to introduce light and dark theming in iOS 13. With our latest update, we complemented our standard dark themed UI with a light counterpart. Without any customization efforts, you can switch from dark to light theming with a single line of code.&lt;/p&gt;
&lt;p&gt;Light theme is available for iOS and Android. For more on that, please check out our documentation.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;if-you-think-that-the-photoeditor-sdk-would-be-the-right-fit-for-your-project-let-us-know-and-well-be-intouch&quot;&gt;If you think that the &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; would be the right fit for your project, let us know and we’ll be in touch.&lt;/h2&gt;
&lt;p&gt;*&lt;strong&gt;*Thanks for reading! To stay in the loop, subscribe to our&lt;/strong&gt; &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;&lt;strong&gt;Newsletter&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.**&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Felix</dc:creator><media:content url="https://blog.img.ly/downloaded_images/Updates-to-the-PhotoEditor-SDK/1-6e8ra3u3F139pgLKSRMBEQ.png" medium="image"/><category>Design</category><category>Colors</category><category>Photo Editing</category><category>iOS</category><category>Android</category><category>Tech</category><category>How-To</category><category>Company</category></item><item><title>Text ❤ Design [Pt. II]</title><link>https://img.ly/blog/text-design-pt-ii-97ce7b752147/</link><guid isPermaLink="true">https://img.ly/blog/text-design-pt-ii-97ce7b752147/</guid><description>Let images do the talking </description><pubDate>Wed, 28 Nov 2018 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;After giving you a sneak peek at the Text Design tool in a previous &lt;a href=&quot;https://img.ly/blog/text-design-ef84fe708d02/&quot;&gt;blog post&lt;/a&gt;, we can now finally announce that it is available as a part of the &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; for all platforms. Over the last weeks, we finalized the Text Design tool and added functionalities that make it a handy creative tool for many industries and use cases.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of our main goals is to make good design more accessible to everyone by developing technology that facilitates and streamlines creative processes so that even people without previous knowledge of sophisticated editing tools can yield appealing results. The Text Design tool enables you to provide your users with the power and features necessary to give speech to their ideas and create a narrative for their visual communication.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 750px) 750px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;750&quot; height=&quot;370&quot; src=&quot;https://img.ly/_astro/1-ftHB1QK5wuwkHg8PRoSHHw_T8Eqv.webp&quot; srcset=&quot;/_astro/1-ftHB1QK5wuwkHg8PRoSHHw_1eqsdy.webp 640w, /_astro/1-ftHB1QK5wuwkHg8PRoSHHw_T8Eqv.webp 750w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Attention-grabbing and precise visual communication is key to engaging one’s target audience. Our novel tool automates typesetting and lets your users combine photos and text fast and easy to create stunning imagery for any tone and task. The Text Design tool features complex text layouts based on recipes crafted by professional designers. Their balanced combination of fonts, sizes, alignments, and decorations allow for the fast creation of assets with a designer look for various purposes like social media, online ads, short-term offers, flyers, product images, posters, promo material and so on. Especially for small companies and teams, without the budget for a professional designer or a dedicated design department, the Text Design Tool can save a lot of time and stress by reducing what would usually take hours to a single tap.&lt;/p&gt;
&lt;h2 id=&quot;all-it-takes-is-an-idea-the-text-design-tool-tends-to-90-of-the-execution&quot;&gt;All it takes is an idea. The Text Design Tool tends to 90% of the execution.&lt;/h2&gt;
&lt;p&gt;&lt;img alt=&quot;&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;634&quot; src=&quot;https://img.ly/_astro/1-RY7-Zm8SSkyjE4kBKygPnw_Z1f5tJI.webp&quot; srcset=&quot;/_astro/1-RY7-Zm8SSkyjE4kBKygPnw_Z1f5tJI.webp 300w&quot;&gt;&lt;/p&gt;
&lt;p&gt;The tool makes design just as fast as your users’ ideas. Combined with the &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK’s&lt;/a&gt; extensive feature suite, branding opportunities through sticker upload and the photo roll with endless source material, your users can achieve a harmonious and nuanced visual communication off the bat to stand out against their competitors. The designs help convey your users’ message and can be used to create a textural hierarchy to guide the spectators view to the essential infos for your users’ goals. Furthermore, the shuffle functionality randomizes fonts, alignments, and decorations thus offering endless possibilities for creative expression.&lt;/p&gt;
&lt;p&gt;A recent change we made to the mobile versions of the Text Design tool is lifting the limitation of one design per creative to allow further artistic expression and get the most out of the interplay and combination of the various designs. On top of that, we optimized the inverting feature of the tool to allow for the adjustment of the inverted designs’ padding and size.&lt;/p&gt;
&lt;h2 id=&quot;if-you-want-to-empower-your-users-to-give-a-voice-to-their-creatives-head-over-to-imgly-and-get-in-touch-with-our-salesteam&quot;&gt;If you want to empower your users to give a voice to their creatives head over to &lt;a href=&quot;https://img.ly/&quot;&gt;IMG.LY&lt;/a&gt; and get in touch with our sales team.&lt;/h2&gt;
&lt;p&gt;*&lt;strong&gt;*Thanks for reading! To stay in the loop, subscribe to our&lt;/strong&gt; &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;&lt;strong&gt;Newsletter&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.**&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Felix</dc:creator><media:content url="https://blog.img.ly/downloaded_images/Text---Design--Pt--II-/1-a-fU1Z1hZwEV1xctiTudcA.png" medium="image"/><category>Design</category><category>Typography</category><category>Android</category><category>iOS</category><category>App Development</category><category>Web Development</category><category>Insights</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/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/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><item><title>Text ❤ Design</title><link>https://img.ly/blog/text-design-ef84fe708d02/</link><guid isPermaLink="true">https://img.ly/blog/text-design-ef84fe708d02/</guid><description>Introducing the Text Design Tool </description><pubDate>Thu, 12 Apr 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 2500px) 2500px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;2500&quot; height=&quot;900&quot; src=&quot;https://img.ly/_astro/1-K3lFT5eIyK_UxXyudh88MA_ZCByzT.webp&quot; srcset=&quot;/_astro/1-K3lFT5eIyK_UxXyudh88MA_1McfOi.webp 640w, /_astro/1-K3lFT5eIyK_UxXyudh88MA_Z8MjGX.webp 750w, /_astro/1-K3lFT5eIyK_UxXyudh88MA_fHXSv.webp 828w, /_astro/1-K3lFT5eIyK_UxXyudh88MA_Z1OTTtC.webp 1080w, /_astro/1-K3lFT5eIyK_UxXyudh88MA_1z40CH.webp 1280w, /_astro/1-K3lFT5eIyK_UxXyudh88MA_zBrfg.webp 1668w, /_astro/1-K3lFT5eIyK_UxXyudh88MA_1tUw9H.webp 2048w, /_astro/1-K3lFT5eIyK_UxXyudh88MA_ZCByzT.webp 2500w&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;introducing-the-text-designtool&quot;&gt;Introducing the Text Design Tool&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;There is power in words just as there is in design. In another advance in making good design more accessible to everyone, today, we are proud to give you a sneak peek at an upcoming product we created in addition to the &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt;: The Text Design Tool merges input text with typography, creating stunning designs for a multitude of use-cases.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 270px) 270px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;270&quot; height=&quot;480&quot; src=&quot;https://img.ly/_astro/1-zrRqqw0uZiXg450dHHjTrA_Z22VKDc.webp&quot; srcset=&quot;/_astro/1-zrRqqw0uZiXg450dHHjTrA_Z22VKDc.webp 270w&quot;&gt;&lt;/p&gt;
&lt;p&gt;While most of us are accustomed to the use of photo editing and enhancement tools, results often lack the desired appeal when it comes to typography and layout. Simply because most people don’t know about character or line spacing and other rules of typesetting and let’s be honest, why should they? But as well-designed text layout can add a lot to its expressiveness, we wanted to equip everyone with a tool that automates typesetting and layout, similar to apps like Typorama.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 270px) 270px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;270&quot; height=&quot;585&quot; src=&quot;https://img.ly/_astro/1-Q6iCuQaFeMe-UrUOCr7u9w_Z2bDYnF.webp&quot; srcset=&quot;/_astro/1-Q6iCuQaFeMe-UrUOCr7u9w_Z2bDYnF.webp 270w&quot;&gt;&lt;/p&gt;
&lt;p&gt;To transform our vision into a user-friendly tool, we first created recipes and set layout rules for different combinations of fonts with decoration elements. The Text Design Tool currently holds 16 layout designs for various use-cases. We worked close with the designers Tommi Gutscher and Ramona Schratt to conceptualize the designs and their different flavors. Through simple keyboard entries, you can populate the designs with your own words. The tool then lays out your text according to our recipes upon a single tap. You can then fine-tune your creative by choosing from 15 different text colors or by using the randomize functionality that shuffles the fonts, alignments and decorations until the creative lives up to your vision. You can even create a mask from your text that lets the background image shine through.&lt;/p&gt;
&lt;p&gt;But that is not the end of the road, as, for the future, we will work with a wide range of artists and designers to expand our tool with a broad spectrum of new and exciting designs that can be applied to a variety of use-cases.&lt;/p&gt;
&lt;p&gt;The Text Design Tool is currently available for iOS only. Versions for Android and HTML5 are going to be released in the next weeks.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Download our &lt;a href=&quot;https://apps.apple.com/de/app/imgly-photo-editor-camera/id589839231?l=en&quot;&gt;Photo Editor App&lt;/a&gt; for iOS to get a hands-on experience with the Text Design Tool.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;*&lt;strong&gt;*Thanks for reading! To stay in the loop, subscribe to our&lt;/strong&gt; &lt;a href=&quot;https://photoeditorsdk.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;&lt;strong&gt;Newsletter&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt;.**&lt;/strong&gt;&lt;/p&gt;</content:encoded><dc:creator>Felix</dc:creator><media:content url="https://blog.img.ly/2020/04/image-36.png" medium="image"/><category>Design</category><category>Typography</category><category>iOS</category><category>Creativity</category><category>App Development</category><category>Insights</category></item><item><title>How to recreate Snapchat’s UI within a single day with the PhotoEditor SDK for Android</title><link>https://img.ly/blog/how-to-recreate-snapchats-ui-within-a-single-day-with-the-photoeditor-sdk-for-android-e985b348c52c/</link><guid isPermaLink="true">https://img.ly/blog/how-to-recreate-snapchats-ui-within-a-single-day-with-the-photoeditor-sdk-for-android-e985b348c52c/</guid><pubDate>Sun, 18 Feb 2018 23:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Is it possible to completely rejig the UI of the &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; within a day? Principally, everything is possible, however, for me as a prospective Java developer this whole undertaking seemed rather tough at first. The task at hand was to rebuild the SDK’s UI to match popular apps like Snapchat or Instagram Stories in only a few steps. A few weeks ago, we already did something similar with the iOS SDK, however, as there are some differences between the platforms the whole process had to be repeated for Android.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Our default UI vs Story UI&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 900px) 900px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;900&quot; height=&quot;600&quot; src=&quot;https://img.ly/_astro/image-13_Z155gLz.webp&quot; srcset=&quot;/_astro/image-13_SB6P9.webp 640w, /_astro/image-13_Z1YEFsp.webp 750w, /_astro/image-13_16X6CG.webp 828w, /_astro/image-13_Z155gLz.webp 900w&quot;&gt;&lt;/p&gt;
&lt;p&gt;As my colleague Malte Baumann already pointed out in his &lt;a href=&quot;https://img.ly/blog/how-to-build-instagrams-story-editor-in-a-day-23be9adff9b/&quot;&gt;blog post&lt;/a&gt;, the main idea behind this endeavor is to explore the possibilities and limitations of the SDK’s customizable UI. But above that, it is also a great way of making oneself familiar with the SDK’s architecture and functionalities. Below, I’m going to explain some of the technical aspects as well as the setup of the story UI in a little more detail.&lt;/p&gt;
&lt;h2 id=&quot;the-architecture&quot;&gt;The Architecture&lt;/h2&gt;
&lt;p&gt;The interface is composed of several layers. The first layer is the &lt;code&gt;EditorRootView&lt;/code&gt; that contains all other views and is, therefore, their parent object. Beneath that lies the &lt;code&gt;EditorPreview&lt;/code&gt; that is responsible for the display of the selected image. Following comes the &lt;code&gt;BrushLayer&lt;/code&gt; that can be drawn upon. In this specific layer array, the brush cannot be placed in front of a sticker. The stickers, however, are all generated in dedicated layers which makes it easy to adjust their order. Once selected, a sticker will automatically be brought to the front. The UI elements can be switched visible and invisible at will; they lie at the very top so that no sticker can cover them.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The view hierarchy. The user facing controls are on the bottom, the root window is on the top.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 473px) 473px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;473&quot; height=&quot;579&quot; src=&quot;https://img.ly/_astro/image-14_Z35Yqi.webp&quot; srcset=&quot;/_astro/image-14_Z35Yqi.webp 473w&quot;&gt;&lt;/p&gt;
&lt;p&gt;At the heart of the UI lies the &lt;code&gt;StateHandler&lt;/code&gt; that functions as the communication interface with the SDK. The &lt;code&gt;StateHandler&lt;/code&gt; includes the &lt;code&gt;EditorShowState&lt;/code&gt; and the &lt;code&gt;HistoryState&lt;/code&gt;. The &lt;code&gt;HistoryState&lt;/code&gt; allows for the retrieval of methods that are necessary for the function of the “undo” button. For example, every new brush stroke adds a new memory state that can be retrieved by the &lt;code&gt;HistoryState&lt;/code&gt; and hence reversed. The &lt;code&gt;EditorShowState&lt;/code&gt; manages the &lt;code&gt;EditMode&lt;/code&gt; that allows for the activation of the different SDK tools. To draw with the brush tool, the &lt;code&gt;EditMode&lt;/code&gt; simply has to be set to brush. Color and stroke width are then controlled by the &lt;code&gt;BrushLayerSettings&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There is a dedicated button to add emojis that displays a &lt;code&gt;RecyclerView&lt;/code&gt; filled with every sticker available in the config upon press. Once a sticker is selected a new layer with matching &lt;code&gt;StickerLayerSettings&lt;/code&gt; is added to the image.&lt;/p&gt;
&lt;p&gt;Although text is realized as a sticker as well within the SDK, it is a little more complicated to generate a text sticker as the required &lt;code&gt;TextStickerConfig&lt;/code&gt; has to be generated first. The text tool can create text stickers through keyboard entries. The text and background color, as well as the text alignment, can be adjusted separately. Those functions can be controlled via different buttons. However, as the SDK has no canned methods for these operations, I had to write widgets that control the buttons. The different button states are interpreted and executed in the &lt;code&gt;TextPanel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To export an image, you simply have to launch the &lt;code&gt;saveImage&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;The customized Story UI in action.&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;581&quot; src=&quot;https://img.ly/_astro/1-o1meOAWBo6YIzb_xRishgQ_O6YP6.webp&quot; srcset=&quot;/_astro/1-o1meOAWBo6YIzb_xRishgQ_O6YP6.webp 300w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-result&quot;&gt;The Result&lt;/h2&gt;
&lt;p&gt;Thanks to the documentation, the execution of the UI revamp was pretty straightforward even without previous knowledge or source code access. As main functionalities like the brush tool or the undo function are already built into the SDK, it was a manageable task to integrate those into the new UI. Against that, the styling of the &lt;code&gt;SeekBar&lt;/code&gt; that is needed to adjust the brush size was a little more time-consuming.&lt;/p&gt;
&lt;p&gt;The design was mainly realized through layouts with fairly simple code. Even though there were some minor hurdles to overcome, the allotted time of a working day should be adequate for a more experienced developer.&lt;/p&gt;
&lt;p&gt;You can find the code for this example UI in &lt;a href=&quot;https://github.com/imgly/pesdk-blog-instagram-ui&quot;&gt;our repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Thanks to &lt;a href=&quot;https://medium.com/@Phoenix_Raw&quot;&gt;Felix Rau&lt;/a&gt; and &lt;a href=&quot;https://medium.com/@codingdivision&quot;&gt;Malte Baumann&lt;/a&gt;!&lt;/strong&gt;&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>Niklas</dc:creator><media:content url="https://blog.img.ly/2020/04/image-46.png" medium="image"/><category>iOS</category><category>Android</category><category>Photo Editor</category><category>User Interface</category><category>JavaScript</category><category>Tech</category><category>How-To</category><category>Learning</category></item><item><title>How to build Instagram’s Story Editor in a Day</title><link>https://img.ly/blog/how-to-build-instagrams-story-editor-in-a-day-23be9adff9b/</link><guid isPermaLink="true">https://img.ly/blog/how-to-build-instagrams-story-editor-in-a-day-23be9adff9b/</guid><pubDate>Wed, 13 Dec 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A common question we get asked from our customers, is whether they’ll be able to create an entirely different user interface using our &lt;a href=&quot;https://img.ly/products/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt;. The SDK comes with it’s own customizable UI, but customization are of course limited to a certain extent. As our SDK is used in many different use cases and contexts, we like to explore what’s possible with the PhotoEditor SDK and its included components. Here, &lt;strong&gt;we decided to build a UI similar to Instagram’s Stories or Snapchat using our SDK&lt;/strong&gt;. By now, this ‘Story UI’ has become a popular way of quickly designing with different elements (stickers, brush and text) rather than enhancing and styling the image only.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Our default UI vs Story UI&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;590&quot; src=&quot;https://img.ly/_astro/1-Rv39RvsHUCI4TXKC1rHXZQ_Z1l5n2b.webp&quot; srcset=&quot;/_astro/1-Rv39RvsHUCI4TXKC1rHXZQ_Z2eFP2.webp 640w, /_astro/1-Rv39RvsHUCI4TXKC1rHXZQ_1qqAh8.webp 750w, /_astro/1-Rv39RvsHUCI4TXKC1rHXZQ_Z1l5n2b.webp 800w&quot;&gt;&lt;/p&gt;
&lt;p&gt;So we grabbed our own &lt;a href=&quot;https://img.ly/docs/pesdk/&quot;&gt;docs&lt;/a&gt; and headed out to create a demo in which we recreate the Instagram Stories using components from the PhotoEditor SDK. This article presents our approach by starting with a general overview and then diving into the different view controllers to look at specific implementation details. You can follow along by downloading the accompanying &lt;a href=&quot;https://github.com/imgly/pesdk-blog-instagram-ui&quot;&gt;code&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;Our customized Story UI in action.&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 290px) 290px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;290&quot; height=&quot;580&quot; src=&quot;https://img.ly/_astro/1-s-ZFgd4EuUcuy_s4MDz2sQ_Z9PmrR.webp&quot; srcset=&quot;/_astro/1-s-ZFgd4EuUcuy_s4MDz2sQ_Z9PmrR.webp 290w&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;architecture-andoverview&quot;&gt;Architecture and Overview&lt;/h2&gt;
&lt;p&gt;Any image editing interface is naturally centered around some sort of canvas or preview. This is where the image with all operations applied is rendered and any changes are shown to the user. All tools add their own interface elements to allow modifications like adding stickers, text or brush strokes to the image. To create such a hierarchy of tools, the &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; makes heavy use of an iOS pattern called &lt;em&gt;view controller containment&lt;/em&gt;. The root view controller, an &lt;code&gt;EditViewController&lt;/code&gt; in this case, manages a series of child view controllers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;PhotoEditPreviewController&lt;/code&gt; that handles the internal model and all rendering, as well as the rendering canvas itself&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;TextSpriteEditController&lt;/code&gt; above the &lt;code&gt;PhotoEditPreviewController&lt;/code&gt; that allows selection and manipulation of text sprites&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;StickerSpriteEditController&lt;/code&gt; above the &lt;code&gt;PhotoEditPreviewController&lt;/code&gt;manages selection and manipulation of stickers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/max/1024/1*iNnmjNai7mPywoKsgLz-xg.png&quot; alt=&quot;The view hierarchy within the EditViewController. The user facing controls are on the left, the root window is on the right.&quot;&gt;&lt;/p&gt;
&lt;p&gt;The child view controllers may manage additional view controllers themselves, but we only need to manage the topmost objects and wire their interfaces together. This is done within the &lt;code&gt;EditViewController&lt;/code&gt; who is also responsible for presenting the view controllers that implement the different tools, &lt;code&gt;StickerViewController&lt;/code&gt;, &lt;code&gt;BrushViewController&lt;/code&gt; and &lt;code&gt;TextViewController&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These are mainly responsible for managing the interface and adjusting the model accordingly, while the ‘real’ work is being done in the background by the &lt;code&gt;PhotoEditPreviewController&lt;/code&gt; whenever the model gets updated. Thanks to the SDK, creating our &lt;code&gt;BrushViewController&lt;/code&gt; boils down to creating and wiring a &lt;code&gt;BrushEditController&lt;/code&gt; and adding its view, color and size controls to the &lt;code&gt;BrushViewControllers&lt;/code&gt; view. This creates a fully fledged brush tool with OpenGL rendering, color and brush size adjustments, and great performance in just &lt;strong&gt;89 lines&lt;/strong&gt; of code.&lt;/p&gt;
&lt;h2 id=&quot;editviewcontroller&quot;&gt;EditViewController&lt;/h2&gt;
&lt;p&gt;As described in the previous section, the &lt;code&gt;EditViewController&lt;/code&gt; is the root view controller of our demo application and is responsible for showing a preview, presenting tools and rendering the final output. To do so, we add a &lt;code&gt;PreviewEditViewController&lt;/code&gt; and &lt;code&gt;SpriteEditControllers&lt;/code&gt; to the root view and register the &lt;code&gt;EditViewController&lt;/code&gt; as their delegate. Registering as a delegate, allows the &lt;code&gt;EditViewController&lt;/code&gt; to wire all child view controllers and pass data between them. In order to do so, we just need to return objects of other view controllers or react to model changes. All controllers then use the &lt;code&gt;EditViewController&lt;/code&gt; to ask for objects or data they need, for example the current size of the preview view, in order to rearrange interface elements or calculate model updates. As an example, the &lt;code&gt;spriteEditControllerPreviewView(_ spriteEditController:)&lt;/code&gt; method of the &lt;code&gt;SpriteEditControllerDelegate&lt;/code&gt; protocol, asks for the current preview view. All we need to do in our &lt;code&gt;EditViewController&lt;/code&gt; is to get this view from our &lt;code&gt;PreviewEditViewController&lt;/code&gt; and return it:&lt;/p&gt;

&lt;h3 id=&quot;sprite-handling&quot;&gt;Sprite Handling&lt;/h3&gt;
&lt;p&gt;Stickers and text sprites are both sprites, but there are different &lt;code&gt;SpriteEditController&lt;/code&gt; subclasses for each sprite type, because of differing gestures and UI elements. The Instagram UI allows the editing of both sticker and text sprites within a single view, so we need to add both specific controllers, &lt;code&gt;StickerSprite&lt;/code&gt;- and &lt;code&gt;TextSpriteEditController&lt;/code&gt; to our &lt;code&gt;EditViewController&lt;/code&gt; and dynamically enable the appropriate one depending on the currently selected sprite.&lt;/p&gt;
&lt;h2 id=&quot;stickerviewcontroller&quot;&gt;StickerViewController&lt;/h2&gt;
&lt;p&gt;For adding stickers, the Instagram Stories UI offers a single button that presents a grid of all available stickers. Replicating this using the PhotoEditor SDK is easy to do, as the SDK uses a collection view for presentation as well. This collection view is embedded in a &lt;code&gt;StickerSelectionController&lt;/code&gt; which we add to our &lt;code&gt;StickerViewController&lt;/code&gt;. Once again, after registering as the &lt;code&gt;StickerSelectionController&lt;/code&gt;s delegate, we get notified upon selection of any sticker and just need to change the &lt;code&gt;PhotoEditModel&lt;/code&gt; accordingly. To do so, we create a link between the root and the currently presented view controller using the &lt;code&gt;StickerViewControllerDelegate&lt;/code&gt; protocol. Once a sticker has been selected, we create or update a &lt;code&gt;StickerSpriteModel&lt;/code&gt;, pass it to the &lt;code&gt;EditViewController&lt;/code&gt;, which updates the &lt;code&gt;PreviewEditViewController&lt;/code&gt; model:&lt;/p&gt;

&lt;p&gt;This triggers a new rendering pass and the sticker appears on screen. All that’s left to do now is closing the &lt;code&gt;StickerViewController&lt;/code&gt;. Again, we use the delegate pattern to ask the &lt;code&gt;EditViewController&lt;/code&gt; about the &lt;code&gt;referenceSize&lt;/code&gt; that’s needed to calculate the sticker’s normalized size.&lt;/p&gt;
&lt;h2 id=&quot;brushviewcontroller&quot;&gt;BrushViewController&lt;/h2&gt;
&lt;p&gt;This view controller, just as described above, is just a wrapper around the &lt;code&gt;BrushEditController&lt;/code&gt; that adds a color and size selection. Once again, all interaction is handled through delegates and upon closing of the brush tool, the internal model is updated. To support &lt;em&gt;undo&lt;/em&gt;, we need to pass the &lt;code&gt;PreviewEditViewController&lt;/code&gt;’s &lt;code&gt;undoController&lt;/code&gt; and begin a new group, whenever we fire up the tool.&lt;/p&gt;
&lt;h2 id=&quot;textviewcontroller&quot;&gt;TextViewController&lt;/h2&gt;
&lt;p&gt;The text editor Instagram created within their Stories UI is essentially a fullscreen textfield with additional controls for color and size selection. Ignoring the customized design, this can easily be recreated using stock iOS components and essentially only requires listening for keyboard notifications to handle the layout and adjusting the textfield’s properties according to user interactions. Once finished, we create a corresponding &lt;code&gt;TextSpriteModel&lt;/code&gt; that is positioned below the &lt;code&gt;UITextField&lt;/code&gt; and notify the &lt;code&gt;EditViewController&lt;/code&gt; using the delegate. After dismissing the &lt;code&gt;TextViewController&lt;/code&gt; the text rendering is handled by the &lt;code&gt;PhotoEditPreviewController&lt;/code&gt;. When reselecting an existing label, we just prefill the &lt;code&gt;UITextField&lt;/code&gt; with it’s contents and update the model when the label textfield changes. To ensure the &lt;code&gt;UITextField&lt;/code&gt; is the only place the text is currently visible, we hide the &lt;code&gt;spriteView&lt;/code&gt; we previously selected and show it again, once editing has finished.&lt;/p&gt;
&lt;h2 id=&quot;the-result&quot;&gt;The Result&lt;/h2&gt;
&lt;p&gt;Recreating the Instagram Stories UI seemed rather challenging at first, but using our &lt;a href=&quot;https://img.ly/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt;, it quickly turned into a very rewarding project. Being able to create something like a brush tool without the need to actually implement any drawing or gesture handling is amazing and helped to create a working prototype in no time. Tricky details like sprite transformations, remote loading of stickers or undo management were entirely handled by the SDK and we could completely focus on the interface itself.&lt;/p&gt;
&lt;p&gt;For more details and some actual code, take a look at the &lt;a href=&quot;https://github.com/imgly/pesdk-blog-instagram-ui&quot;&gt;repository&lt;/a&gt;. We documented all tricky parts and you should be able to start your own implementation using the code. Please keep in mind, that the code may target a slightly older PhotoEditor SDK version, so check for a new version before you start your journey.&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/downloaded_images/How-to-build-Instagram-s-Story-Editor-in-a-Day/1-iNnmjNai7mPywoKsgLz-xg.png" medium="image"/><category>iOS</category><category>Swift</category><category>Instagram</category><category>Snapchat</category><category>Mobile App Development</category><category>Tech</category><category>How-To</category><category>Tutorial</category><category>Learning</category></item></channel></rss>