<?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>Swift – IMG.LY Blog</title><description>Posts tagged Swift on the IMG.LY blog.</description><link>https://img.ly/blog/tag/swift/</link><language>en-us</language><image><url>https://img.ly/apple-touch-icon.png</url><title>Swift – IMG.LY Blog</title><link>https://img.ly/blog/tag/swift/</link></image><atom:link href="https://img.ly/blog/tag/swift/rss.xml" rel="self" type="application/rss+xml"/><generator>Astro</generator><lastBuildDate>Fri, 12 Jun 2026 10:11:05 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>Working with Large Video and Image Files on iOS with Swift</title><link>https://img.ly/blog/working-with-large-video-and-image-files-on-ios-with-swift/</link><guid isPermaLink="true">https://img.ly/blog/working-with-large-video-and-image-files-on-ios-with-swift/</guid><description>Find your best strategy for compressing and resizing images, videos, and other files with Swift.</description><pubDate>Tue, 10 May 2022 13:27:44 GMT</pubDate><content:encoded>&lt;p&gt;Video files and image files are among the largest files. The &lt;code&gt;AVFoundation&lt;/code&gt; and &lt;code&gt;CoreImage&lt;/code&gt; libraries that Apple supplies widely store data on the disk. They try to bring into memory only as much of a file as is needed to complete a task. As you are working on an application that uses files, there are times you will want to compress or resize them. Usually, this is to upload to a server or because you have noticed performance issues, such as stuttering when scrolling or long render times.&lt;/p&gt;
&lt;p&gt;This article will cover strategies for compressing and resizing images, videos, and other files. We will also be looking at using URLSession for file uploads and downloads.&lt;/p&gt;
&lt;p&gt;As iPhone cameras can take higher-quality pictures, the file sizes have grown quite large. For the last few years, Apple has been using a &lt;code&gt;12MP&lt;/code&gt; camera that takes pictures at &lt;code&gt;3024 x 4032&lt;/code&gt; resolution. Apple uses a special &lt;code&gt;HEIC&lt;/code&gt; compression to make each image about &lt;code&gt;1.7MB&lt;/code&gt;. The same image will generate a &lt;code&gt;15MB&lt;/code&gt; file when saved as a &lt;code&gt;.png&lt;/code&gt; and a &lt;code&gt;3MB&lt;/code&gt; file when saved as a &lt;code&gt;.jpeg&lt;/code&gt;. Though this will be a high-quality image, services like Instagram or Facebook will often only display images at less than half of that size. The extra pixel data will be compressed away. Perhaps the first and most important part of working with large image files in your app is planning around what size or quality you need. Social Media services similarly resize video files. For instance, TikTok displays images and video using &lt;code&gt;1080 x 1920&lt;/code&gt;, but any iPhone after X will capture video at a larger size.&lt;/p&gt;
&lt;h2 id=&quot;writing-an-image-to-disk&quot;&gt;Writing an Image to Disk&lt;/h2&gt;
&lt;p&gt;When working with a &lt;code&gt;UIImage&lt;/code&gt; or &lt;code&gt;CoreImage&lt;/code&gt; object, normally you can save it as an NSData object. These files are lossless (the image quality will not degrade), but they are only useful inside of your application. The standards on the Internet are &lt;code&gt;jpeg&lt;/code&gt; and &lt;code&gt;png&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;Fortunately, Apple provides methods to convert &lt;code&gt;UIImage&lt;/code&gt; to &lt;code&gt;.jpegData&lt;/code&gt; or &lt;code&gt;.pngData&lt;/code&gt;. There are also ways to convert &lt;code&gt;CIImage&lt;/code&gt; and &lt;code&gt;CGImage&lt;/code&gt;, but they are slightly more complicated to configure.&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; imageToSave &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;: &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;someImage&lt;/span&gt;&lt;span&gt;&gt;&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;let&lt;/span&gt;&lt;span&gt; jpegData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; imageToSave.&lt;/span&gt;&lt;span&gt;jpegData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;compressionQuality&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1.0&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; file &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;urls&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;first&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;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; jpegData&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&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;: file&lt;/span&gt;&lt;span&gt;!&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;sleeping_dog.jpg&quot;&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;catch&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;let&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;(err.localizedDescription) &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;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what’s going on in the code above:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First we need to convert our image to a &lt;code&gt;UIImage&lt;/code&gt;. The &lt;code&gt;UIImage&lt;/code&gt; class has several different initializers. Use &lt;code&gt;UIImage(named: &quot;some image name&quot;)&lt;/code&gt; to read an image from the app bundle. Use &lt;code&gt;UIImage(contentsOfFile: &quot;some filepath&quot;)&lt;/code&gt; to read from the disk. Use &lt;code&gt;UIImage(cgImage: &quot;some cgImage&quot;)&lt;/code&gt; and &lt;code&gt;UIImage(ciImage: &quot;some ciImage&quot;)&lt;/code&gt; when you have images already in memory. If you are storing images in a &lt;code&gt;CoreData&lt;/code&gt; store or somewhere as &lt;code&gt;Data&lt;/code&gt; you can use &lt;code&gt;UIImage(data: &quot;some data&quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;However you get a &lt;code&gt;UIImage&lt;/code&gt; the next task is to convert it to either a &lt;code&gt;.jpg&lt;/code&gt; or &lt;code&gt;.png&lt;/code&gt; file. Generally, &lt;code&gt;.jpg&lt;/code&gt; is a better option when working with photographs and &lt;code&gt;.png&lt;/code&gt; produces higher quality for line art. The example above creates a &lt;code&gt;.jpg&lt;/code&gt; at the highest quality possible.&lt;/li&gt;
&lt;li&gt;Ask the &lt;code&gt;FileManager&lt;/code&gt; for a &lt;code&gt;URL&lt;/code&gt; to the user’s documents directory. That is generally a safe place to write files. Because of the way the function is formed, it returns an array of &lt;code&gt;URL&lt;/code&gt; items. In this case, there is only one, but you still need to remember to specify the &lt;code&gt;.first&lt;/code&gt; item.&lt;/li&gt;
&lt;li&gt;Writing &lt;code&gt;Data&lt;/code&gt; object to disk can throw errors, so it is good practice to place it in a &lt;code&gt;try&lt;/code&gt;…&lt;code&gt;catch&lt;/code&gt; block of code. The &lt;code&gt;URL&lt;/code&gt; to the user’s documents directory needs to have the actual filename appended.&lt;/li&gt;
&lt;li&gt;Any errors that the system throws can be handled here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In order to create a &lt;code&gt;.png&lt;/code&gt; using the &lt;code&gt;imageToSave&lt;/code&gt; above, the line would be&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; pngData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; imageToSave.&lt;/span&gt;&lt;span&gt;pngData&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;.png&lt;/code&gt; filetype does not have any compression settings. The filetype is &lt;a href=&quot;https://en.wikipedia.org/wiki/Lossless_compression&quot;&gt;“lossless”&lt;/a&gt;, so it is always compressed as much as possible without losing any data.&lt;/p&gt;
&lt;h3 id=&quot;compression-options-for-jpeg&quot;&gt;Compression Options for JPEG&lt;/h3&gt;
&lt;p&gt;When generating &lt;code&gt;jpeg&lt;/code&gt; data above, we supplied a quality value. The value is a &lt;code&gt;Float&lt;/code&gt; between &lt;code&gt;0.0&lt;/code&gt; and &lt;code&gt;1.0&lt;/code&gt;. The &lt;code&gt;.jpg&lt;/code&gt; filetype uses a &lt;a href=&quot;https://en.wikipedia.org/wiki/Lossy_compression&quot;&gt;“lossy”&lt;/a&gt; compression algorithm that is specifically designed for photographs.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;compression-quality&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;197&quot; src=&quot;https://img.ly/_astro/compression-quality_ZnCYfn.webp&quot; srcset=&quot;/_astro/compression-quality_2bNCSI.webp 640w, /_astro/compression-quality_Zi6PuL.webp 750w, /_astro/compression-quality_ZnCYfn.webp 800w&quot;&gt;&lt;/p&gt;
&lt;p&gt;In the image above saving the &lt;code&gt;jpeg&lt;/code&gt; with a quality of &lt;code&gt;1.0&lt;/code&gt; produced the dog on the right and the file is &lt;code&gt;1.3MB&lt;/code&gt;. Saving the jpeg with a quality of &lt;code&gt;0.0&lt;/code&gt; produced the image on the left and the middle image is produced saving with a quality of &lt;code&gt;0.6&lt;/code&gt;. The file on the left is only about 6% of the size of the original.&lt;/p&gt;
&lt;p&gt;Considering what the images are being used for can help you decide on an optimal compression setting for a particular app.&lt;/p&gt;
&lt;h2 id=&quot;compressing-data-with-zlib&quot;&gt;Compressing Data with &lt;code&gt;zlib&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;Though &lt;code&gt;png&lt;/code&gt; and &lt;code&gt;jpeg&lt;/code&gt; files are already compressed sometimes you will want to compress other types of files before uploading them (compressing a &lt;code&gt;png&lt;/code&gt;, &lt;code&gt;mov&lt;/code&gt; or &lt;code&gt;jpeg&lt;/code&gt; will often not affect the file size or will make the file &lt;em&gt;larger&lt;/em&gt;). A web server will often be able to accept a &lt;code&gt;POST&lt;/code&gt; that compresses its body data using &lt;code&gt;gzip&lt;/code&gt;. Additionally, some web servers will require that binary data is transformed into a &lt;code&gt;base64EncodedString&lt;/code&gt; before uploading. Base64 encoding will make data size grow by about 33%. Using &lt;code&gt;gzip&lt;/code&gt; will help mitigate that somewhat. Apple’s implementation of &lt;code&gt;gzip&lt;/code&gt; is called &lt;code&gt;.zlib&lt;/code&gt; when compressing data. Swift provides some other compression algorithms such as &lt;code&gt;.lzfse&lt;/code&gt; which will result in smaller files, but they are not widely adopted.&lt;/p&gt;
&lt;p&gt;For example, to encode something as base64 and then compress it with with &lt;code&gt;gzip&lt;/code&gt; so you can upload it, we can use code like 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;let&lt;/span&gt;&lt;span&gt; jsonToUpload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;{&quot;&lt;/span&gt;&lt;span&gt;books&lt;/span&gt;&lt;span&gt;&quot;:[{&quot;&lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;&quot;:&quot;&lt;/span&gt;&lt;span&gt;The Three Musketeers&lt;/span&gt;&lt;span&gt;&quot;,&quot;&lt;/span&gt;&lt;span&gt;author&lt;/span&gt;&lt;span&gt;&quot;:&quot;&lt;/span&gt;&lt;span&gt;Dumas&lt;/span&gt;&lt;span&gt;&quot;}]}&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; jsonAsData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jsonToUpload.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt;: .&lt;/span&gt;&lt;span&gt;utf8&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; encodedString &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; jsonAsData&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;base64EncodedString&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; compressedString &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; NSData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;: (encodedString&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt;: .&lt;/span&gt;&lt;span&gt;utf8&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;compressed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt;: .zlib) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; Data&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above creates a string of data in &lt;code&gt;JSON&lt;/code&gt; format. It then encodes the &lt;code&gt;String&lt;/code&gt; as a &lt;code&gt;Data&lt;/code&gt; object using &lt;code&gt;.utf8&lt;/code&gt; encoding. Using &lt;code&gt;.utf8&lt;/code&gt; is a standard. If the web server you are uploading to requires a different encoding, you can change it. In the final step, the encoded string is compressed using &lt;code&gt;gzip&lt;/code&gt;. Notice that the &lt;code&gt;.compressed(using:)&lt;/code&gt; method is on &lt;code&gt;NSData&lt;/code&gt; not on &lt;code&gt;Data&lt;/code&gt;. &lt;code&gt;NSData&lt;/code&gt; is an older library, so you convert the compressed &lt;code&gt;NSData&lt;/code&gt; object back to &lt;code&gt;Data&lt;/code&gt; by casting using &lt;code&gt;as Data&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The above example was just to show syntax. The &lt;code&gt;compressedString&lt;/code&gt; will be slightly larger than the &lt;code&gt;encodedString&lt;/code&gt;. Based on the kind of data your app works with and the capabilities of the web server, you will need to experiment with how to encode and compress to get the best results.&lt;/p&gt;
&lt;h2 id=&quot;transferring-data-with-a-server&quot;&gt;Transferring Data With a Server&lt;/h2&gt;
&lt;p&gt;Depending on the original format of the data, your web server may want the &lt;code&gt;.httpBody&lt;/code&gt; compressed. You can do that with some code 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; someJSONData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Data&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;var&lt;/span&gt;&lt;span&gt; request &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URLRequest&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;URL&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://example.com/uploads/&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;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;request.httpMethod &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;POST&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//set the other header variables here using&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//request.setValue(&quot;value to set&quot;, forHTTPHeaderField: &quot;header field name&quot;)&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; compressedJSONData &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; NSData&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;: someJSONData).&lt;/span&gt;&lt;span&gt;compressed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;using&lt;/span&gt;&lt;span&gt;: .zlib) &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; Data { &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  request.httpBody &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; compressedJSONData&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; task &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URLSession.shared.&lt;/span&gt;&lt;span&gt;dataTask&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: request) {data, response, error &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 errors and response data when the task is done&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //response will contain the status and other header messages&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //data will contain any payload the server returns&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;  task.&lt;/span&gt;&lt;span&gt;resume&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;ul&gt;
&lt;li&gt;Get the payload into a &lt;code&gt;Data&lt;/code&gt; object.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;URLRequest&lt;/code&gt; as a &lt;code&gt;var&lt;/code&gt; so that you can configure it. If you create it as a &lt;code&gt;let&lt;/code&gt; it will be a &lt;code&gt;GET&lt;/code&gt; request with default header fields.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;gzip&lt;/code&gt; to compress the data and if successful, assign it to the &lt;code&gt;.httpBody&lt;/code&gt; of the &lt;code&gt;request&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a data task for the upload. Upon completion, the &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;response&lt;/code&gt; and &lt;code&gt;error&lt;/code&gt; variables will have any response from the server.&lt;/li&gt;
&lt;li&gt;Actually start the task.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;uploading-a-large-file&quot;&gt;Uploading a Large File&lt;/h3&gt;
&lt;p&gt;An issue with the code above is that all of the initial data will be in memory. So for a large image or video upload, this may not work. However, Swift does provide a different method that will upload a file directly from the disk, reading into memory only what is needed at the time.&lt;/p&gt;
&lt;p&gt;Code to implement that would follow this form.&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; request &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URLRequest&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;URL&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;https://example.com/uploads&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;request.httpMethod &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &quot;POST&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//set the other header variables here using&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//request.setValue(&quot;value to set&quot;, forHTTPHeaderField: &quot;header field name&quot;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; documentsDirectory &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;urls&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;first&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; fileToUpload &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; documentsDirectory&lt;/span&gt;&lt;span&gt;?&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;the-big-giant-file.mp4&quot;&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; uploadTask &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URLSession.shared.&lt;/span&gt;&lt;span&gt;uploadTask&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: request, &lt;/span&gt;&lt;span&gt;fromFile&lt;/span&gt;&lt;span&gt;: fileToUpload) {data, response, error &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//get some information from the server when the file has been uploaded&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;uploadTask.&lt;/span&gt;&lt;span&gt;resume&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This looks very similar to the previous example but notice that you do not set an &lt;code&gt;.httpBody&lt;/code&gt;. There will be header values to set though. Most web servers will want to know how large the file is, authentication credentials, and whether the data is multi-part, or raw or other metadata. These will all be set using &lt;code&gt;.setValue(forHTTPHeaderField:)&lt;/code&gt; in the &lt;code&gt;request&lt;/code&gt; object.&lt;/p&gt;
&lt;p&gt;For larger files, it is often better to use a &lt;code&gt;URLSessionDataDelegate&lt;/code&gt; instead of the single closure with &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;response&lt;/code&gt;, &lt;code&gt;error&lt;/code&gt;. When using the delegate, you can write code to monitor the progress of the file transfer as well as respond to authentication challenges and make decisions about caching. You can also implement &lt;code&gt;URLSessionDelegate&lt;/code&gt; and &lt;code&gt;URLSessionTaskDelegate&lt;/code&gt; methods. All of the delegate methods are options, so you only need to implement ones that are important to your application.&lt;/p&gt;
&lt;h3 id=&quot;configuring-background-transfers&quot;&gt;Configuring Background Transfers&lt;/h3&gt;
&lt;p&gt;Another benefit of using the &lt;code&gt;uploadTask&lt;/code&gt; (and its related &lt;code&gt;downloadTask&lt;/code&gt;) instead of the &lt;code&gt;dataTask&lt;/code&gt; is that you can configure the transfer to continue for a time even if the app goes to the background. In that case, instead of using the &lt;code&gt;URLSession.shared&lt;/code&gt; object, you will want to configure a session that has some special properties to allow it to work in the background.&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; configuration &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URLSessionConfiguration.&lt;/span&gt;&lt;span&gt;background&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;withIdentifier&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;some.unique.identifier&quot;&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; session &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; URLSession&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;configuration&lt;/span&gt;&lt;span&gt;: configuration, &lt;/span&gt;&lt;span&gt;delegate&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;delegateQueue&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; uploadTask &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; session.&lt;/span&gt;&lt;span&gt;uploadTask&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: request, &lt;/span&gt;&lt;span&gt;fromFile&lt;/span&gt;&lt;span&gt;: fileToUpload)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Completely configuring and supporting the downloads and backgrounding is covered in &lt;a href=&quot;https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background&quot;&gt;this article by Apple&lt;/a&gt;. It will require implementing some delegate methods for the session as well as updating some of the methods in the main application delegate. The most important is the &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622941-application&quot;&gt;&lt;code&gt;handleEventsForBackgroundURLSession&lt;/code&gt;&lt;/a&gt; which is how the system will wake up your app with information about the upload or download.&lt;/p&gt;
&lt;p&gt;Additionally, download tasks can be paused and restarted. The &lt;code&gt;cancel(byProducingResumeData:)&lt;/code&gt; method on &lt;code&gt;URLSessionDownloadTask&lt;/code&gt; will cancel a download and optionally provide some resume data you can use to resume the download at a later time. You can call this method on the task when the user taps a button or when the app goes to the background or similar. The web server must support byte-range requests and &lt;code&gt;ETag&lt;/code&gt; or &lt;code&gt;Last-Modified&lt;/code&gt; headers. The &lt;a href=&quot;https://developer.apple.com/documentation/foundation/urlsessiondownloadtask/1411634-cancel&quot;&gt;documentation for pausing and restarting&lt;/a&gt; explains how to pause the download and how to resume using &lt;code&gt;downloadTaks(withResumeData:)&lt;/code&gt;. There is not a similar structure for pausing and resuming an upload.&lt;/p&gt;
&lt;h2 id=&quot;resizing-an-image&quot;&gt;Resizing an image&lt;/h2&gt;
&lt;p&gt;Perhaps the simplest way to make a file smaller is to reduce its dimensions. Using &lt;code&gt;CoreImage&lt;/code&gt; you can use the &lt;a href=&quot;https://en.wikipedia.org/wiki/Lanczos_resampling&quot;&gt;Lanczos resampling&lt;/a&gt; algorithm to resize an image while keeping high quality.&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; url &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;sleeping-dog&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;jpeg&quot;&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; image &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;contentsOf&lt;/span&gt;&lt;span&gt;: url&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;let&lt;/span&gt;&lt;span&gt; filter &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;CILanczosScaleTransform&quot;&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;filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(image, &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;filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;0.5&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;//3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; result &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.outputImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; converter &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;: result&lt;/span&gt;&lt;span&gt;!&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 this code is doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a &lt;code&gt;CIImage&lt;/code&gt; from a &lt;code&gt;URL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;CILanczosScaleTransform&lt;/code&gt; filter.&lt;/li&gt;
&lt;li&gt;Configure the filter to reduce the image dimensions by 50%.&lt;/li&gt;
&lt;li&gt;Convert the scaled image to a &lt;code&gt;UIImage&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Recently, Apple has provided a &lt;code&gt;UIGraphicsImageRenderer&lt;/code&gt; that will work directly on &lt;code&gt;UIImage&lt;/code&gt; objects. The code below will resize a &lt;code&gt;UIImage&lt;/code&gt; to 50%. Notice that the &lt;code&gt;UIGraphicsImageRenderer&lt;/code&gt; can also use an explicit value for size (unlike the &lt;code&gt;CILanczosScaleTransform&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; imageToSave &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;named&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;sleeping-dog.jpeg&quot;&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;let&lt;/span&gt;&lt;span&gt; newSize &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; imageToSave.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;applying&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;CGAffineTransform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;scaleX&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;0.5&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.5&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; renderer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIGraphicsImageRenderer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;: newSize) &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; scaledImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; renderer.&lt;/span&gt;&lt;span&gt;image&lt;/span&gt;&lt;span&gt; { (context) &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; rect &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;: CGPoint.zero, &lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;: newSize)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  imageToSave.&lt;/span&gt;&lt;span&gt;draw&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt;: rect) &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;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is what this code is doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Load the image as a &lt;code&gt;UIImage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Scale the size down by half.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;UIGraphicsImageRenderer&lt;/code&gt; that will render in this new size.&lt;/li&gt;
&lt;li&gt;Actually draw the image into the renderer at the new size.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition to rendering the image back as a &lt;code&gt;UIImage&lt;/code&gt; the renderer can create &lt;code&gt;jpegData&lt;/code&gt; and &lt;code&gt;pngData&lt;/code&gt; which would save a few steps if you are resizing and converting. The &lt;a href=&quot;https://developer.apple.com/documentation/uikit/uigraphicsimagerenderer&quot;&gt;full documentation for &lt;code&gt;UIGraphicsImageRenderer&lt;/code&gt;&lt;/a&gt; explains the details.&lt;/p&gt;
&lt;h2 id=&quot;resizing-a-video&quot;&gt;Resizing a Video&lt;/h2&gt;
&lt;p&gt;Though the &lt;code&gt;UIGraphicsImageRenderer&lt;/code&gt; is faster, using the older &lt;code&gt;CoreImage&lt;/code&gt; resizing has a benefit. &lt;code&gt;CoreImage&lt;/code&gt; filters can be applied to video files as a composition. Large video files can be scaled down. The code below will create a composition that will scale a video asset by 50%. Remember that you will need to &lt;code&gt;import AVFoundation&lt;/code&gt; to use these tools.&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; newAsset &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;jumping-man&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;var&lt;/span&gt;&lt;span&gt; newSize &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &amp;#x3C;&lt;/span&gt;&lt;span&gt;some size that you&apos;ve calculated&lt;/span&gt;&lt;span&gt;&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; resizeComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVMutableVideoComposition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: newAsset, &lt;/span&gt;&lt;span&gt;applyingCIFiltersWithHandler&lt;/span&gt;&lt;span&gt;: { request &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; filter &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;CILanczosScaleTransform&quot;&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;  filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(request.sourceImage, &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;  filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt;some scale factor&lt;/span&gt;&lt;span&gt;&gt;&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;//4&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; resultImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; filter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.outputImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  request.&lt;/span&gt;&lt;span&gt;finish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: resultImage, &lt;/span&gt;&lt;span&gt;context&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;})&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;resizeComposition.renderSize &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; newSize &lt;/span&gt;&lt;span&gt;//5&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here’s what the code above is doing to create a new composition:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create an &lt;code&gt;AVAsset&lt;/code&gt; from a &lt;code&gt;URL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a variable &lt;code&gt;newSize&lt;/code&gt; to hold the final size.&lt;/li&gt;
&lt;li&gt;In the composition, configure the &lt;code&gt;CIFilter&lt;/code&gt; that will be applied to each frame.&lt;/li&gt;
&lt;li&gt;Calculate the scale factor based on the &lt;code&gt;newSize&lt;/code&gt; variable and the actual size of the &lt;code&gt;request.sourceImage.extent.size&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;renderSize&lt;/code&gt; property of the composition to the new size.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you don’t set the &lt;code&gt;renderSize&lt;/code&gt; then there will be a black letterbox around the video.&lt;/p&gt;
&lt;p&gt;With the resizing composition, you can now export the &lt;code&gt;AVAsset&lt;/code&gt; as a &lt;code&gt;.mov&lt;/code&gt; or &lt;code&gt;.m4v&lt;/code&gt; file using an &lt;code&gt;AVExportSession&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; 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;jumping-man&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;let&lt;/span&gt;&lt;span&gt; outputMovieURL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;urls&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;first&lt;/span&gt;&lt;span&gt;?&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;exported.mov&quot;&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;//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;: asset, &lt;/span&gt;&lt;span&gt;presetName&lt;/span&gt;&lt;span&gt;: AVAssetExportPresetHighestQuality)&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;.videoComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &amp;#x3C;&lt;/span&gt;&lt;span&gt;the composition you created above&lt;/span&gt;&lt;span&gt;&gt;&lt;/span&gt;&lt;span&gt; //3&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;!&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; //4&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;
&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;file saved at &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;//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’s what the code above is doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Load an &lt;code&gt;AVAsset&lt;/code&gt; from a &lt;code&gt;URL&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;URL&lt;/code&gt; to save the resized movie.&lt;/li&gt;
&lt;li&gt;Apply the resizing composition to an &lt;code&gt;AVAssetExportSession&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Asynchronously export the asset to disk.&lt;/li&gt;
&lt;li&gt;Print the destination &lt;code&gt;URL&lt;/code&gt; of the new movie when it is saved.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another method to resize a video is to use one of the &lt;code&gt;AVAssetExportSession&lt;/code&gt; presets. Apple offers several &lt;a href=&quot;https://developer.apple.com/documentation/avfoundation/avassetexportsession/export_presets&quot;&gt;size and quality presets&lt;/a&gt; for export sessions. In the line above that creates the &lt;code&gt;exporter&lt;/code&gt;, replace &lt;code&gt;AVAssetExportPresetHighestQuality&lt;/code&gt; with one of the other values. There are generic quality presets: &lt;code&gt;AVAssetExportPresetLowQuality&lt;/code&gt;, &lt;code&gt;AVAssetExportPresetMediumQuality&lt;/code&gt; and there are size presets including: &lt;code&gt;AVAssetExportPreset1280x720&lt;/code&gt; and &lt;code&gt;AVAssetExportPreset640x480&lt;/code&gt;. When using one of the presets you do not need to use a composition unless you want to do further manipulation or unless you need a size or quality combination that is not provided by any preset. As with images, experiment with different settings until you get a balance between quality and size that works for you.&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;Some other strategies for working with large files are to chunk data file or split up video files. However, to use this strategy, you will need to coordinate with someone to stitch them back together after they are uploaded or downloaded.&lt;/p&gt;
&lt;p&gt;Using the &lt;code&gt;AVAssetExportSession&lt;/code&gt; above, you could call it multiple times and pass in a time range perhaps using code 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; timeRange &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTimeRangeFromTimeToTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;: startTime, &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt;: endTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//some code to create the 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;.timeRange &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; timeRange&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on the video and audio of the &lt;code&gt;AVAsset&lt;/code&gt; the splits in the video may be noticable. Because of modern frame rates, the audio would probably be more likely to be noticed. Time ranges can be sub-second, so you would need to experiment. However, iOS would manage the file for you so that the entire &lt;code&gt;AVAsset&lt;/code&gt; is never loaded into memory.&lt;/p&gt;
&lt;p&gt;It is also possible to split &lt;code&gt;Data&lt;/code&gt; objects using &lt;code&gt;.subData(in:)&lt;/code&gt; and looping through the bytes of the object. Again, you would need to also write code to stitch them back together later. Additionally, you would likely need to bring the entire &lt;code&gt;Data&lt;/code&gt; object into a memory buffer, which might not be desirable.&lt;/p&gt;
&lt;p&gt;Another way to handle large video files is to use Apple’s &lt;a href=&quot;https://developer.apple.com/streaming/&quot;&gt;HTTP Live Streaming&lt;/a&gt; tools. These create files that you can upload to any web server that is recognized by iOS and Mac devices. The tools will segment the video as well as create multiple versions so that your users with different bandwidth capabilities can see different quality streams. Most Android devices and web browsers will at least be able to see the video, even if they cannot dynamically switch between streams.&lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;In this tutorial, you saw some different strategies for shrinking and compressing the large image and video files that an iPhone camera can produce. You also saw some strategies for transferring larger data payloads with a web server. As mentioned in the beginning, consider what sizes the images and videos will display and use that to determine what sizes of images to store. Also, consider storing multiple versions of images or using thumbnails when displaying a gallery or list of images.&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;
&lt;h3 id=&quot;thanks-for-reading-let-us-know-what-you-think-on-twitter-or-stay-in-the-loop-with-our-newsletter&quot;&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 stay in the loop with our &lt;a href=&quot;https://img.us13.list-manage.com/subscribe?u=dc9f652839dbb620d14d6d28d&amp;#x26;id=04a306e4b2&quot;&gt;Newsletter&lt;/a&gt;.&lt;/h3&gt;</content:encoded><dc:creator>Walter</dc:creator><media:content url="https://blog.img.ly/2022/05/large-file-swift-crop-optimize.png" medium="image"/><category>How-To</category><category>Swift</category><category>iOS App Development</category><category>Tutorial</category></item><item><title>How to Make an Animated GIF Using Swift</title><link>https://img.ly/blog/how-to-make-an-animated-gif-using-swift/</link><guid isPermaLink="true">https://img.ly/blog/how-to-make-an-animated-gif-using-swift/</guid><description>Learn how to create animated GIF files with Swift, and also extract still images from a video!  </description><pubDate>Tue, 10 May 2022 12:07:58 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you will see how to use Swift to create an animated GIF file. You will also see how to extract individual frames from a video file to convert the whole video or just clips from a video into an animated GIF. The code in this article uses Swift 5 and Xcode 13. The tutorial uses AVFoundation and CoreGraphics. Clone &lt;a href=&quot;https://github.com/waltertyree/expert-eureka&quot;&gt;this repository&lt;/a&gt; for a sample project and example code.&lt;/p&gt;
&lt;h2 id=&quot;animated-gif-basics&quot;&gt;Animated GIF Basics&lt;/h2&gt;
&lt;p&gt;The GIF is one of the older file types on the Internet. But, every platform supports it (not every platform supports every video format). Unlike some other image formats, GIF files can contain one image or a series of images. When a GIF file has a series of images and some metadata to describe how long to show each one, it can substitute for animation and video. Because of this, a platform that doesn’t necessarily support video files can display moving images and animations.&lt;/p&gt;
&lt;p&gt;GIF files are easy to make. However, animated GIF files are not perfect: they are large and inefficient compared to modern video files. They have a limited color palette, and none of the images in the file have compression. For example, the video file we use in the sample project is about 5MB in mp4 format but over 100MB after it becomes an animated GIF. Finally, GIF files don’t have a soundtrack. Still, they are popular and do certain tasks well, so let’s make some using AVFoundation and Swift.&lt;/p&gt;
&lt;h2 id=&quot;the-basic-strategy&quot;&gt;The Basic Strategy&lt;/h2&gt;
&lt;p&gt;In order to create animated GIF files you need a series of images and some metadata to describe how long each image will show. This tutorial will start with how to put these images into a GIF. Then you will see some different strategies for gathering the images. A GIF file contains bitmap images, not vector graphics. When working with bitmap images, a powerful framework to explore is &lt;code&gt;CoreGraphics&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;creating-an-empty-file&quot;&gt;Creating an Empty File&lt;/h3&gt;
&lt;p&gt;The first step is to create an empty file on the disk that will become our animated GIF.&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 empty file to hold gif&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; destinationFilename &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;NSUUID&lt;/span&gt;&lt;span&gt;().uuidString &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; &quot;.gif&quot;&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; destinationURL &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;(destinationFilename)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//metadata for gif file to describe it as an animated gif&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; fileDictionary &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [kCGImagePropertyGIFDictionary &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;  kCGImagePropertyGIFLoopCount &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;//create the file and set the file properties&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; animatedGifFile &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CGImageDestinationCreateWithURL&lt;/span&gt;&lt;span&gt;(destinationURL &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; CFURL, UTType.gif.identifier &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; CFString, totalFrames, &lt;/span&gt;&lt;span&gt;nil&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;  print&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;error creating gif file&quot;&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;CGImageDestinationSetProperties&lt;/span&gt;&lt;span&gt;(animatedGifFile, fileDictionary &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; CFDictionary)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above creates a URL to hold the animated GIF in the user’s &lt;code&gt;Temporary&lt;/code&gt; directory. The location is not crucial – it just needs to be where you have write permission. Next it creates a &lt;code&gt;Dictionary&lt;/code&gt; that will describe the file as an animated GIF file that will loop forever. The &lt;a href=&quot;https://developer.apple.com/documentation/imageio/gif_image_properties&quot;&gt;Apple Documentation for GIF Image Properties&lt;/a&gt; contains the full list of options for the dictionary. Next, you create the empty file using &lt;code&gt;CGImageDestinatonCreateWithURL&lt;/code&gt;. After creating the file, set the properties dictionary. Now there is an empty file ready to accept the frames that will make up the animated GIF.&lt;/p&gt;
&lt;p&gt;When creating the file with &lt;code&gt;CGImageDestinationCreateWithURL&lt;/code&gt;, you give parameters for the URL to the file location, the &lt;code&gt;UTType&lt;/code&gt; identifier to show it will be a &lt;code&gt;.gif&lt;/code&gt; file, and the &lt;code&gt;totalFrames&lt;/code&gt; that it should expect. &lt;code&gt;totalFrames&lt;/code&gt; is just a variable that holds how many actual frames the file will eventually hold. You will need to work out the actual number of frames for your file manually. For example, if you want to display 20 images each second and your GIF will be four seconds long, the total number of frames will be 20 * 4 or 80. The last parameter is always &lt;code&gt;nil&lt;/code&gt;. The &lt;a href=&quot;https://developer.apple.com/documentation/imageio/1465361-cgimagedestinationcreatewithurl&quot;&gt;documentation&lt;/a&gt; indicates it “is reserved for future use” (but this function has been available to the Mac since 2005 and iOS since 2010, so we will see). The documentation also indicates you are responsible for releasing the memory allocated by the creation command, but Swift will handle that for you.&lt;/p&gt;
&lt;p&gt;As you work with &lt;code&gt;CoreGraphics&lt;/code&gt; classes and types, you will notice that they have a slightly different format than other frameworks. That is a hint that &lt;code&gt;CoreGraphics&lt;/code&gt; is one of the older frameworks and that it is all &lt;code&gt;C&lt;/code&gt; based. Swift has bridging code so that the system will worry about translating your &lt;code&gt;Dictionary&lt;/code&gt; to a &lt;code&gt;CFDictionary&lt;/code&gt; and your &lt;code&gt;String&lt;/code&gt; to a &lt;code&gt;CFString&lt;/code&gt; and so on.&lt;/p&gt;
&lt;h3 id=&quot;adding-images&quot;&gt;Adding Images&lt;/h3&gt;
&lt;p&gt;To add images to the empty file, you will need an image for each frame of the animation, and you will need a small &lt;code&gt;Dictionary&lt;/code&gt; to tell the system how long to display the image during the animation. As mentioned above, there are other options you can apply, such as color mapping or frame dimensions.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;Dictionary&lt;/code&gt; to display each frame for 1/20 second 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;    let&lt;/span&gt;&lt;span&gt; frameDictionary &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [kCGImagePropertyGIFDictionary &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;      kCGImagePropertyGIFDelayTime&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 1.0&lt;/span&gt;&lt;span&gt; /&lt;/span&gt;&lt;span&gt; 20.0&lt;/span&gt;&lt;span&gt;]]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that the values above are &lt;code&gt;Double&lt;/code&gt; and are not &lt;code&gt;Int&lt;/code&gt;. Using &lt;code&gt;0.05&lt;/code&gt; would have also been fine as it is equal to 1.0/20.0.&lt;/p&gt;
&lt;p&gt;Compressed video files and movies are usually 30, 60, or even 120 frames per second. Recall that animated GIF images do not have compression, so you want to have as low of a frame rate (or as small of an image frame) as you can. Experiment with different frame rates depending on the speed of the action in an animation. Often times 20 or even 10 frames per second will produce high enough quality.&lt;/p&gt;
&lt;p&gt;Now, loop through your images and append each image to the &lt;code&gt;.gif&lt;/code&gt; file on disk using code 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;CGImageDestinationAddImage&lt;/span&gt;&lt;span&gt;(animatedGifFile, frameImage, frameDictionary &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; CFDictionary)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;animatedGifFile&lt;/code&gt; is the URL to the empty file on the disk that you created above. The &lt;code&gt;frameDictionary&lt;/code&gt; is the metadata to add to each frame and the &lt;code&gt;frameImage&lt;/code&gt; is the actual image for the frame. Your image for the frame needs to be a &lt;code&gt;CGImage&lt;/code&gt;. Depending on the source of images you may have &lt;code&gt;UIImage&lt;/code&gt; or &lt;code&gt;CIImage&lt;/code&gt; or something else. Most image formats in Swift have a conversion method. For example:&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 cgImage = UIImage().cgImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let anotherCGImage = CIImage().cgImage&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are also &lt;code&gt;CGImage&lt;/code&gt; initializers to read &lt;code&gt;.jpeg&lt;/code&gt; and &lt;code&gt;.png&lt;/code&gt; data.&lt;/p&gt;
&lt;h3 id=&quot;finalizing-the-file&quot;&gt;Finalizing the File&lt;/h3&gt;
&lt;p&gt;Once you finish writing every frame image to the GIF file, close the file and instruct the system to clean up using:&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;CGImageDestinationFinalize(animatedGifFile)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function will return &lt;code&gt;true&lt;/code&gt; if it is successful. You must call this function after appending the frames or the resulting GIF will be unusable. Also, after you call this function, you cannot add more frames.&lt;/p&gt;
&lt;p&gt;At this point the &lt;code&gt;animatedGifFile&lt;/code&gt; URL points to an animated GIF file you can use as needed.&lt;/p&gt;
&lt;h2 id=&quot;collecting-frame-images&quot;&gt;Collecting Frame Images&lt;/h2&gt;
&lt;p&gt;To create the animated GIF, it is necessary to have an image in &lt;code&gt;CGImage&lt;/code&gt; for each frame. It will be easier if they are all rotated the same way and have the same dimensions. Where those images come from is not crucial. They might be a series of files on disk. It could be that your user snaps a series of photos that your app converts to a GIF in real-time. Your app could even intersperse photographs and bitmap drawings. Something to consider regardless of the source of the frame images is how much memory they consume. Loading the entire set of images into an array before making the animated GIF could cause your app to ask the system for gigabytes of memory.&lt;/p&gt;
&lt;h3 id=&quot;extracting-images-from-video&quot;&gt;Extracting Images from Video&lt;/h3&gt;
&lt;p&gt;A common use of animated GIF files is to display videos. So, let’s look at how to extract frames from a video. In some of the other tutorials, you have seen how we can use &lt;code&gt;CoreImage&lt;/code&gt; and &lt;code&gt;AVFoundation&lt;/code&gt; to manipulate each frame of a video. In those cases, you can’t easily change the frames per second. A 60 frames-per-second animated GIF would quickly become an unusably large file.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;AVFoundation&lt;/code&gt; provides the &lt;code&gt;AVAssetImageGenerator&lt;/code&gt; class specifically to extract frames from &lt;code&gt;AVAssets&lt;/code&gt; at specific times. To extract a single image from an &lt;code&gt;AVAsset&lt;/code&gt; you can use:&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;copyCGImage(at requestedTime: CMTime, actualTime: UnsafeMutablePointer&amp;#x3C;CMTime&gt;?)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create a &lt;code&gt;CGImage&lt;/code&gt; at or near the &lt;code&gt;requestedTime&lt;/code&gt; in the video. Sometimes, the requested time is between two frames, so the function will extract the closest frame it can and return the &lt;code&gt;actualTime&lt;/code&gt; of that frame. Your code can then decide if it’s an acceptable substitution and what to do about it. Usually, though, you don’t care and can pass &lt;code&gt;nil&lt;/code&gt; for the &lt;code&gt;actualTime&lt;/code&gt; parameter. Notice that the &lt;code&gt;actualTime&lt;/code&gt; is a pointer to a &lt;code&gt;CMTime&lt;/code&gt; variable. This means it is an &lt;a href=&quot;https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID545&quot;&gt;“inout” parameter&lt;/a&gt;. Using “inout” parameters was a common practice when &lt;code&gt;CoreGraphics&lt;/code&gt; and &lt;code&gt;AVFoundation&lt;/code&gt; were originally written. To use an “inout” parameter, you declare the variable before calling the function and then check its value after. Additionally, when you pass the variable into the function, place an ampersand before the variable name.&lt;/p&gt;
&lt;p&gt;For extracting a series of images, &lt;code&gt;copyCGImage&lt;/code&gt; is not efficient. It runs on the calling thread and may block your application. Apple provides a different function when you want to extract many frames from a video asset.&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;generateCGImagesAsynchronously&lt;/span&gt;&lt;span&gt;(forTimes requestedTimes&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; [NSValue], completionHandler handler&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; @escaping&lt;/span&gt;&lt;span&gt; AVAssetImageGeneratorCompletionHandler)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This function takes an array of &lt;code&gt;requestedTimes&lt;/code&gt; and attempts to extract the image at each of those timestamps. It will pass the extracted image to the &lt;code&gt;completionHandler&lt;/code&gt;. The handler will execute its code one time for every extraction attempt and will not block the calling thread.&lt;/p&gt;
&lt;p&gt;Before calling the function to extract images, you need to prepare the inputs. First, you create an &lt;code&gt;AVAssetImageGenerator&lt;/code&gt; and configure it.&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; 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;swish&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;mp4&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; frameGenerator &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAssetImageGenerator&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;frameGenerator.requestedTimeToleranceBefore &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;seconds&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;preferredTimescale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;frameGenerator.requestedTimeToleranceAfter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;seconds&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;preferredTimescale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code loads a movie file from the app bundle into an &lt;code&gt;AVAsset&lt;/code&gt; and then creates a generator for that asset. The &lt;code&gt;.requestedTimeToleranceBefore&lt;/code&gt; and &lt;code&gt;.requestedTimeToleranceAfter&lt;/code&gt; parameters say you want the exact frame for any requested time. This will make the extraction of frames slower. If you are trying to tune performance, consider modifying these parameters.&lt;/p&gt;
&lt;p&gt;Now you will need to calculate the timestamp of each frame of the animated GIF. Determine the start time and the duration of the clip you want to create. Then decide what frame rate (frames per second) to use. You can see in the image below that the entire video consists of about 480 frames at 60 frames per second.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;fps-example&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 631px) 631px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;631&quot; height=&quot;222&quot; src=&quot;https://img.ly/_astro/fps-example_11Uf6h.webp&quot; srcset=&quot;/_astro/fps-example_11Uf6h.webp 631w&quot;&gt;&lt;/p&gt;
&lt;p&gt;An animated GIF of just the player making the basketball shot would start at 2 seconds and end at 6 seconds of the original video. In the original video, that segment is 240 frames. An animated GIF of the same length at 20 frames per second only needs about 80 frames. The first frame is the one showing at the 2-second timestamp. The next frame should be the one shown at 2 + 1/20 or 2.05 seconds. The next frame would be the one for 2.10 seconds.&lt;/p&gt;
&lt;p&gt;The generator requires the array of timestamps as &lt;code&gt;CMTime&lt;/code&gt; values. You can create the array using code 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;var&lt;/span&gt;&lt;span&gt; startTime: &lt;/span&gt;&lt;span&gt;Double&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; 2.0&lt;/span&gt;&lt;span&gt; //seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; endTime: &lt;/span&gt;&lt;span&gt;Double&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt;  6.0&lt;/span&gt;&lt;span&gt; //seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; frameRate &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 20&lt;/span&gt;&lt;span&gt; //how many frames per second&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; totalFrames &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Int&lt;/span&gt;&lt;span&gt;((endTime &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; startTime) &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; Double&lt;/span&gt;&lt;span&gt;(frameRate))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; timeStamps &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [NSValue]()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt; currentFrame &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;span&gt; 0&lt;/span&gt;&lt;span&gt;..&amp;#x3C;&lt;/span&gt;&lt;span&gt;totalFrames {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; frameTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; Double&lt;/span&gt;&lt;span&gt;(currentFrame) &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; Double&lt;/span&gt;&lt;span&gt;(frameRate)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; timeStamp &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;seconds&lt;/span&gt;&lt;span&gt;: startTime &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt; Double&lt;/span&gt;&lt;span&gt;(frameTime), &lt;/span&gt;&lt;span&gt;preferredTimescale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  timeStamps.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;NSValue&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;time&lt;/span&gt;&lt;span&gt;: timeStamp))&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 code calculates a value for &lt;code&gt;totalFrames&lt;/code&gt; which must be an &lt;code&gt;Int&lt;/code&gt; so that you can use it to loop. But, &lt;code&gt;startTime&lt;/code&gt; and &lt;code&gt;endTime&lt;/code&gt; need to be &lt;code&gt;Double&lt;/code&gt; since the timestamps will be sub-second times. The &lt;code&gt;frameTime&lt;/code&gt; of the first frame is equal to zero in the animated GIF but 2.0 seconds in the original video. Convert that to the video &lt;code&gt;timeStamp&lt;/code&gt; by adding the &lt;code&gt;startTime&lt;/code&gt;. When creating the &lt;code&gt;CMTime&lt;/code&gt; using “600” as the &lt;code&gt;preferredTimescale&lt;/code&gt; is common because most frame rates are divisible into 600 evenly so the math for the timestamp will be more accurate. Finally, wrap the &lt;code&gt;timeStamp&lt;/code&gt; in an &lt;code&gt;NSValue&lt;/code&gt; object and add it to the array. The &lt;code&gt;AVAssetImageGenerator&lt;/code&gt; uses timestamps wrapped in &lt;code&gt;NSValue&lt;/code&gt; objects. &lt;code&gt;NSValue&lt;/code&gt; is just a generic type that holds values.&lt;/p&gt;
&lt;p&gt;With the generator configured and the array of timestamps created, you can extract the images using code 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;frameGenerator.generateCGImagesAsynchronously(forTimes: timeStamps) { requestedTime, frameImage, actualTime, result, error in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  guard let frameImage = frameImage else {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print(&quot;no image&quot;)&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;  if error != nil {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    print(&quot;an error&quot;)&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;  //do someting interesting with frameImage&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;frameImage&lt;/code&gt; in the completion handler will be a &lt;code&gt;CGImage&lt;/code&gt; type if the extraction was successful. As with the function to extract a single image, if the system had to choose a different time than requested, you will be informed of the actual time. You can read about the other parameters in the &lt;a href=&quot;https://developer.apple.com/documentation/avfoundation/avassetimagegeneratorcompletionhandler&quot;&gt;documentation for &lt;code&gt;AVAssetImageGeneratorCompletionHandler&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Now, you can add the frame to an open &lt;code&gt;CGImageDestination&lt;/code&gt;, you could write it to a disk to use later, and you could save it to an array. If you don’t write it to an animated GIF file right away, you will need to devise a way to keep the frames in sequence. You may notice that there is not an explicit way for the function to signal that it is finished. Because the completion handler is called for every extraction attempt, a common strategy is to make a simple counter and increment the counter in the completion handler. When the counter variable is equal to the number of frames requested, then you are finished. Another way is to match the &lt;code&gt;requestedTime&lt;/code&gt; to the last &lt;code&gt;timeStamp&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;shot-clip-1&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 337px) 337px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;337&quot; height=&quot;600&quot; src=&quot;https://img.ly/_astro/shot-clip-1_Z1FLwej.webp&quot; srcset=&quot;/_astro/shot-clip-1_Z1FLwej.webp 337w&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 create an animated GIF from a series of images. You also saw how to extract still images from a video at pre-determined timestamps. Clone &lt;a href=&quot;https://github.com/waltertyree/expert-eureka&quot;&gt;this repository&lt;/a&gt; for a simple project that contains the sample code and a video file you can experiment with.&lt;/p&gt;
&lt;p&gt;&lt;strong&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 stay in the loop with our &lt;a href=&quot;https://img.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>Walter</dc:creator><media:content url="https://blog.img.ly/2022/05/animate-gif-in-Swift.png" medium="image"/><category>How-To</category><category>Swift</category><category>Tutorial</category></item><item><title>How to Make Videos from Still Images with AVFoundation and Swift</title><link>https://img.ly/blog/how-to-make-videos-from-still-images-with-avfoundation-and-swift/</link><guid isPermaLink="true">https://img.ly/blog/how-to-make-videos-from-still-images-with-avfoundation-and-swift/</guid><description>Convert still images into video files with two strategies.</description><pubDate>Thu, 10 Feb 2022 15:35:45 GMT</pubDate><content:encoded>&lt;p&gt;Whether making a slide show or breaking up video tracks with title scenes, inserting still images into a video file can be tricky. This tutorial will show you how to convert still images into standard Quicktime video files so you can work with them further in your favorite video editor. This tutorial was created and tested with Xcode 13 and Swift 5. This tutorial will discuss two strategies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;convert each image to a separate movie and stitch them together later (this is best suited for times you want to interleave the images and other video files using some other editing workflow)&lt;/li&gt;
&lt;li&gt;create a “blank” video and use the &lt;code&gt;applyingCIFiltersWithHandler&lt;/code&gt; variation to overlay the images (this is best suited for times you want to make a quick slide show with no further editing)&lt;/li&gt;
&lt;/ul&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 this code (and some extra goodies) can be found on &lt;a href=&quot;https://github.com/waltertyree/static-image-slideshow&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&quot;the-problem-with-an-image&quot;&gt;The Problem with an Image&lt;/h2&gt;
&lt;p&gt;The basic models that are used in &lt;code&gt;AVFoundation&lt;/code&gt; for making video files are &lt;code&gt;AVAsset&lt;/code&gt; and &lt;code&gt;AVAssetTrack&lt;/code&gt;. You can compose multiple tracks and assets together to make a single video file. All tracks must have some media data and a duration. Static image files don’t have a duration. This is the primary problem you have to overcome when you want to add static images to video.&lt;br&gt;
Fortunately, you can use many of the same &lt;code&gt;AVFoundation&lt;/code&gt; tools that are used for video capture and frame manipulation. However instead of using a &lt;code&gt;CADisplayLink&lt;/code&gt; and &lt;code&gt;AVPlayerItemVideoOutput&lt;/code&gt; or an &lt;code&gt;AVCaptureDevice&lt;/code&gt; to provide frame buffers to save, you will create the frame buffers manually. Then you can use &lt;code&gt;AVAssetWriter&lt;/code&gt; to convert the frame buffers into a video file.&lt;/p&gt;
&lt;p&gt;Some items to consider before you actually generate the video file are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What framerate to use? Because the image doesn’t have any motion, you can use a very low frame rate to create smaller file sizes. However, this might cause issues if you are interleaving it with regular video, which usually has a frame rate of 30 or 60 fps (or higher, for slow motion video)&lt;/li&gt;
&lt;li&gt;What dimensions will the final video have? It is easy to resize an image and add padding using &lt;code&gt;CoreImage&lt;/code&gt; or some image editing software before it becomes a video. As the image becomes a video and moves through different steps in an &lt;code&gt;AVFoundation&lt;/code&gt; pipeline, the different classes in &lt;code&gt;AVFoundation&lt;/code&gt; will resize or crop the images in different ways when the input dimensions and output dimension don’t match.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;writing-buffers-using-avassetwriter&quot;&gt;Writing Buffers using &lt;code&gt;AVAssetWriter&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;AVAssetWriter&lt;/code&gt; exists to encode media to a file on disk. Though this tutorial will use a single video input &lt;code&gt;AVAssetWriter&lt;/code&gt; supports multiple inputs of different kinds (audio, video, metadata). It is quite similar to &lt;code&gt;AVAssetExportSession&lt;/code&gt;. The difference is that &lt;code&gt;AVAssetWriter&lt;/code&gt; uses multiple inputs and each input is a single track where the &lt;code&gt;AVAssetExportSession&lt;/code&gt; takes a single &lt;code&gt;AVAsset&lt;/code&gt; as an input. The result of both is a single file.&lt;/p&gt;
&lt;p&gt;As indicated above, our basic strategy will be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a pixel buffer that is the correct color space and size to hold the image&lt;/li&gt;
&lt;li&gt;Render the image into the pixel buffer&lt;/li&gt;
&lt;li&gt;Create an &lt;code&gt;AVAssetWriter&lt;/code&gt; with a single video input&lt;/li&gt;
&lt;li&gt;Decide how many frames will be required to create a video of the desired duration&lt;/li&gt;
&lt;li&gt;Make a loop, and each time through the loop, append the pixel buffer&lt;/li&gt;
&lt;li&gt;Clean up&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;create-the-pixel-buffer&quot;&gt;Create the Pixel Buffer&lt;/h3&gt;
&lt;p&gt;The code below creates a &lt;code&gt;CIImage&lt;/code&gt; from an image file. Then it creates a pixel buffer the same size as the image and uses a standard color space. Finally, a &lt;code&gt;CIContext&lt;/code&gt; renders the image into the pixel buffer.&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 CIImage&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; uikitImage &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;named&lt;/span&gt;&lt;span&gt;: imageName), &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; staticImage &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;image&lt;/span&gt;&lt;span&gt;: uikitImage) &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;  throw&lt;/span&gt;&lt;span&gt; ConstructionError.invalidImage &lt;/span&gt;&lt;span&gt;//this is an error type I made up&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;//create a variable to hold the pixelBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; pixelBuffer: CVPixelBuffer&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//set some standard attributes&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; attrs &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [kCVPixelBufferCGImageCompatibilityKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; kCFBooleanTrue,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;     kCVPixelBufferCGBitmapContextCompatibilityKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; kCFBooleanTrue] &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; CFDictionary&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create the width and height of the buffer to match the image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; width:&lt;/span&gt;&lt;span&gt;Int&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Int&lt;/span&gt;&lt;span&gt;(staticImage.extent.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;.width)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; height:&lt;/span&gt;&lt;span&gt;Int&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; Int&lt;/span&gt;&lt;span&gt;(staticImage.extent.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;.height)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a buffer (notice it uses an in/out parameter for the pixelBuffer variable)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;CVPixelBufferCreate&lt;/span&gt;&lt;span&gt;(kCFAllocatorDefault,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    width,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    height,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    kCVPixelFormatType_32BGRA,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    attrs,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;                    &amp;#x26;&lt;/span&gt;&lt;span&gt;pixelBuffer)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a CIContext&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; context &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;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//use the context to render the image into the pixelBuffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;context.&lt;/span&gt;&lt;span&gt;render&lt;/span&gt;&lt;span&gt;(staticImage, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: pixelBuffer&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Though we generally think of &lt;code&gt;CIImage&lt;/code&gt; as an image, Apple is clear that it is not an image by itself. &lt;code&gt;CIImage&lt;/code&gt; needs a context for rendering. This is where a lot of the power of CoreImage and filters and GPU rendering come from, the fact that &lt;code&gt;CIImage&lt;/code&gt; is just the instructions for creating an image. Note: You can also use &lt;code&gt;CGImage&lt;/code&gt; and the &lt;code&gt;CoreGraphics&lt;/code&gt; framework to create pixel buffers. I find it easier to use the &lt;code&gt;CoreImage&lt;/code&gt; framework.&lt;/p&gt;
&lt;h3 id=&quot;configure-avassetwriter&quot;&gt;Configure AVAssetWriter&lt;/h3&gt;
&lt;p&gt;Now with a buffer created, you can configure the &lt;code&gt;AVAssetWriter&lt;/code&gt;. It will take several parameters as a dictionary to determine the dimensions and format of the outputs. You can either set them individually or else Apple provides presets.&lt;br&gt;
One of the most important settings is the output dimension. If the output dimension matches the original image, the video file will show no distortion or letterboxing. However, if the output is smaller, it will compress the image until it fits. If the output is larger, the image will expand until its width or height matches the output size and then will letterbox. Note: if an image expands too much, it will appear grainy. The image below shows different output sizes for a 640 x 480 input image.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;output-sizes&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 766px) 766px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;766&quot; height=&quot;738&quot; src=&quot;https://img.ly/_astro/output-sizes_ZiCM8f.webp&quot; srcset=&quot;/_astro/output-sizes_kVhrb.webp 640w, /_astro/output-sizes_Z1186Wa.webp 750w, /_astro/output-sizes_ZiCM8f.webp 766w&quot;&gt;&lt;/p&gt;
&lt;p&gt;To create your settings for the &lt;code&gt;AVAssetWriter&lt;/code&gt; first create a dictionary containing the different values. The example here sets an output dimension of 400 x 400 and provides for &lt;code&gt;.h264&lt;/code&gt; encoding.&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; assetWriterSettings &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; [AVVideoCodecKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; AVVideoCodecType.h264, AVVideoWidthKey &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 400&lt;/span&gt;&lt;span&gt;, AVVideoHeightKey&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; 400&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;as&lt;/span&gt;&lt;span&gt; [&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;/code&gt;&lt;/pre&gt;
&lt;p&gt;To use one of the presets, use the &lt;code&gt;AVOutputSettingsAssistant&lt;/code&gt;. You can read about the different settings in the &lt;a href=&quot;https://developer.apple.com/documentation/avfoundation/avoutputsettingsassistant&quot;&gt;Apple documentation&lt;/a&gt;. An example to create 1080p video output 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;let&lt;/span&gt;&lt;span&gt; settingsAssistant &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVOutputSettingsAssistant&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;preset&lt;/span&gt;&lt;span&gt;: .preset1920x1080)&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.videoSettings&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;write-the-pixel-buffer-to-the-video-file&quot;&gt;Write the pixel buffer to the video file&lt;/h3&gt;
&lt;p&gt;With the settings configured, the asset writer can loop through and append the contents of the pixel buffer to create each frame of the video. Something important to notice in the code below is that &lt;code&gt;AVFoundation&lt;/code&gt; has a philosophy to preserve data. So, it will make copies, and it will generally not overwrite files. That is why you have to delete any old files before you can create the new &lt;code&gt;AVAssetWriter&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;//generate a file url to store the video. some_image.jpg becomes some_image.mov&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; imageNameRoot &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; imageName.&lt;/span&gt;&lt;span&gt;split&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;separator&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;.&quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span&gt;first&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; outputMovieURL &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; FileManager.default.&lt;/span&gt;&lt;span&gt;urls&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;first&lt;/span&gt;&lt;span&gt;?&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;\(imageNameRoot)&lt;/span&gt;&lt;span&gt;.mov&quot;&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;  throw&lt;/span&gt;&lt;span&gt; ConstructionError.invalidURL &lt;/span&gt;&lt;span&gt;//an error i made up&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;//delete any old file&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; FileManager.default.&lt;/span&gt;&lt;span&gt;removeItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;at&lt;/span&gt;&lt;span&gt;: outputMovieURL)&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;(&lt;/span&gt;&lt;span&gt;&quot;Could not remove 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;//create an assetwriter instance&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; assetwriter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; try?&lt;/span&gt;&lt;span&gt; AVAssetWriter&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;outputURL&lt;/span&gt;&lt;span&gt;: outputMovieURL, &lt;/span&gt;&lt;span&gt;fileType&lt;/span&gt;&lt;span&gt;: .mov) &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;  abort&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;//generate 1080p settings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; settingsAssistant &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVOutputSettingsAssistant&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;preset&lt;/span&gt;&lt;span&gt;: .preset1920x1080)&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.videoSettings&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create a single video input&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; assetWriterInput &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAssetWriterInput&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;mediaType&lt;/span&gt;&lt;span&gt;: .video, &lt;/span&gt;&lt;span&gt;outputSettings&lt;/span&gt;&lt;span&gt;: settingsAssistant)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//create an adaptor for the pixel buffer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; assetWriterAdaptor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVAssetWriterInputPixelBufferAdaptor&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;assetWriterInput&lt;/span&gt;&lt;span&gt;: assetWriterInput, &lt;/span&gt;&lt;span&gt;sourcePixelBufferAttributes&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;//add the input to the asset writer&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;assetwriter.&lt;/span&gt;&lt;span&gt;add&lt;/span&gt;&lt;span&gt;(assetWriterInput)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//begin the session&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;assetwriter.&lt;/span&gt;&lt;span&gt;startWriting&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;assetwriter.&lt;/span&gt;&lt;span&gt;startSession&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;atSourceTime&lt;/span&gt;&lt;span&gt;: CMTime.zero)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//determine how many frames we need to generate&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; framesPerSecond &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 30&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//duration is the number of seconds for the final video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; totalFrames &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; duration &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; framesPerSecond&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; frameCount &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;while&lt;/span&gt;&lt;span&gt; frameCount &lt;/span&gt;&lt;span&gt;&amp;#x3C;&lt;/span&gt;&lt;span&gt; totalFrames {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if&lt;/span&gt;&lt;span&gt; assetWriterInput.isReadyForMoreMediaData {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; frameTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTimeMake&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;value&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Int64&lt;/span&gt;&lt;span&gt;(frameCount), &lt;/span&gt;&lt;span&gt;timescale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;Int32&lt;/span&gt;&lt;span&gt;(framesPerSecond))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //append the contents of the pixelBuffer at the correct time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    assetWriterAdaptor.&lt;/span&gt;&lt;span&gt;append&lt;/span&gt;&lt;span&gt;(pixelBuffer&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;withPresentationTime&lt;/span&gt;&lt;span&gt;: frameTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    frameCount&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&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;//close everything&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;assetWriterInput.&lt;/span&gt;&lt;span&gt;markAsFinished&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;assetwriter.&lt;/span&gt;&lt;span&gt;finishWriting&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  pixelBuffer &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; nil&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  //outputMovieURL now has the video&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  Logger&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;Finished video location: &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;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, your image has become a Quicktime video of whatever duration you specified and is ready for use as any other video file! You can now continue with the like of &lt;a href=&quot;https://img.ly/products/video-sdk&quot;&gt;VideoEditor SDK&lt;/a&gt; to &lt;a href=&quot;https://img.ly/blog/new-force-trim-function-for-videoeditor-sdk/&quot;&gt;trim videos&lt;/a&gt;, and &lt;a href=&quot;https://img.ly/blog/how-to-apply-filter-effects-to-video-using-videffects-on-android/&quot;&gt;add filters&lt;/a&gt; or &lt;a href=&quot;https://img.ly/blog/how-to-add-overlays-to-a-video-in-react-native/&quot;&gt;overlays&lt;/a&gt;. You might also want to combine the video with &lt;a href=&quot;https://img.ly/blog/combine-video-clips-into-a-new-file-in-ios-with-swift/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;other video files to make a new creation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Say hi to my dog! A plain video from still images&lt;/p&gt;
&lt;h2 id=&quot;creating-a-blank-video&quot;&gt;Creating a Blank Video&lt;/h2&gt;
&lt;p&gt;The previous example created a single &lt;code&gt;.mov&lt;/code&gt; file for each of your images. The most robust way to get these images into a video with sound, transitions, etc., is to use &lt;code&gt;AVMutableComposition&lt;/code&gt; with multiple tracks and layer instructions. However, for a quick slideshow with just a few elementary transitions, a strategy is to create a blank movie and then use &lt;code&gt;AVVideoComposition&lt;/code&gt; with &lt;code&gt;(asset: AVAsset, applyingCIFiltersWithHandler applier: @escaping (AVAsynchronousCIImageFilteringRequest) -&gt; Void)&lt;/code&gt;. That will let you use &lt;code&gt;CIImage&lt;/code&gt; and &lt;code&gt;CIFilter&lt;/code&gt; to paint each frame of the blank movie with images.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Our basic strategy for this method will be:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a pixel buffer that is the right color space and size and fill it with a solid color&lt;/li&gt;
&lt;li&gt;Create an &lt;code&gt;AVAssetWriter&lt;/code&gt; with a single video input&lt;/li&gt;
&lt;li&gt;Decide how many frames will be required to create a video of the desired duration&lt;/li&gt;
&lt;li&gt;Make a loop and each time through the loop append the pixel buffer&lt;/li&gt;
&lt;li&gt;Create an &lt;code&gt;AVVideoComposition&lt;/code&gt; to add the images to the individual frames of the video&lt;/li&gt;
&lt;li&gt;Let &lt;code&gt;AVPlayer&lt;/code&gt; combine the video and the composition&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the first strategy, you started by making a pixel buffer and using an &lt;code&gt;AVAssetWriter&lt;/code&gt; to create the video file 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;guard&lt;/span&gt;&lt;span&gt; let&lt;/span&gt;&lt;span&gt; uikitImage &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;named&lt;/span&gt;&lt;span&gt;: imageName), &lt;/span&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; staticImage &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;image&lt;/span&gt;&lt;span&gt;: uikitImage) &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;  throw&lt;/span&gt;&lt;span&gt; ConstructionError.invalidImage &lt;/span&gt;&lt;span&gt;//this is an error type I made up&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;However, this time, instead of using a source image, you create a &lt;code&gt;CIImage&lt;/code&gt; that is a single color.&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; staticImage &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;color&lt;/span&gt;&lt;span&gt;: bgColor).&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;: &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;: &lt;/span&gt;&lt;span&gt;960&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;540&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;CIImage(color:)&lt;/code&gt; initializer creates an image that is of infinite size and is just a single color. Using the &lt;code&gt;.cropped(to:)&lt;/code&gt; modifier lets you make it the same size as the &lt;code&gt;AVAssetWriter&lt;/code&gt; output so that there won’t be any letterboxing. The rest of the code is the same as before and the end result will be a video file that is just the single color.&lt;/p&gt;
&lt;h3 id=&quot;adding-a-composition&quot;&gt;Adding a Composition&lt;/h3&gt;
&lt;p&gt;Once the video file has been created and saved to disk, load it as an asset, then create an &lt;code&gt;AVVideoComposition&lt;/code&gt;. This allows you to generate individual frames. The function below uses the &lt;code&gt;request&lt;/code&gt; property of the composition. This has a &lt;code&gt;CIImage&lt;/code&gt; representation of the current frame as well as the timestamp of when this frame will appear. The &lt;code&gt;.fetchSlide(forTime:)&lt;/code&gt; is a helper function in the demo app that returns the appropriate &lt;code&gt;CIImage&lt;/code&gt; for the slide to display. Then the &lt;code&gt;.sourceOverCompositing&lt;/code&gt; filter combines the original frame image with the slide. Using a &lt;code&gt;CGAffineTransform&lt;/code&gt; moves the slide across the screen as the video plays.&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; createComposition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt; asset: AVAsset) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; slideshowComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVVideoComposition&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;weak&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;] request &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; self&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt; else&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;    let&lt;/span&gt;&lt;span&gt; slide &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;fetchSlide&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;forTime&lt;/span&gt;&lt;span&gt;: request.compositionTime)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; compose &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIFilter.&lt;/span&gt;&lt;span&gt;sourceOverCompositing&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span&gt;//filter to join two images&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    compose.backgroundImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; request.sourceImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    compose.inputImage &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; slide&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transformed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;by&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CGAffineTransform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;translationX&lt;/span&gt;&lt;span&gt;: request.compositionTime.seconds &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; 50&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;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    //always finish with the last output of the pipeline&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    request.&lt;/span&gt;&lt;span&gt;finish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: compose.outputImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;context&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;  }&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  self&lt;/span&gt;&lt;span&gt;.outputSlideshow &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; slideshowComposition&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;Because the creation of the video is done asynchronously, you can composite images over the original frame and add filters without the danger of impacting the final frame rate. Recall that &lt;code&gt;CIFilter&lt;/code&gt; works as a pipeline, each &lt;code&gt;CIFilter&lt;/code&gt; has an &lt;code&gt;.outputImage&lt;/code&gt; and most have &lt;code&gt;.inputImage&lt;/code&gt; and some configuration settings. Feeding the output from one filter to the input of the next will let you build complex compositions. Once a composition has been created, &lt;code&gt;AVPlayer&lt;/code&gt; can join the original video with the composition for playback or export.&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; item &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;: &lt;/span&gt;&lt;span&gt;self&lt;/span&gt;&lt;span&gt;.outputMovie&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;item.videoComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; outputSlideshow&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;: item)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above might generate a video like this one.&lt;/p&gt;
&lt;p&gt;As with the first video, the final video is a plain &lt;code&gt;.mov&lt;/code&gt; file.&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;In this tutorial, you saw two ways to convert static images into &lt;code&gt;.mov&lt;/code&gt; files that you can then edit with any video editor. The sample project also has some code for exporting your creation as a &lt;code&gt;.mov&lt;/code&gt; file and code for adding sound to the creation.&lt;/p&gt;
&lt;p&gt;These are not the only ways to go about solving this problem. For example, instead of creating a blank video and overlaying the images, using a stock video of waves at the beach and overlaying the images might work better for your project. Conversely, because the initial pixel buffer is created from a &lt;code&gt;CIImage&lt;/code&gt;, there is no reason that the &lt;code&gt;CIImage&lt;/code&gt; cannot be the end of a long pipeline of &lt;code&gt;CIFilters&lt;/code&gt;. This way the composition is done at the beginning, and the &lt;code&gt;AVAssetWriter&lt;/code&gt; is the only tool needed. The best strategy is the one that makes sense to you and that works with the kinds of media you have.&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, 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;
&lt;p&gt;&lt;strong&gt;To stay in the loop with our latest articles and case studies, subscribe to our &lt;a href=&quot;https://img.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>Walter</dc:creator><media:content url="https://blog.img.ly/2022/02/still-image-to-video-code.png" medium="image"/><category>Video Editing</category><category>Video Editor</category><category>Mobile App Development</category><category>App Development</category><category>Swift</category><category>How-To</category><category>Tech</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 Text to Video in Swift</title><link>https://img.ly/blog/add-text-to-video-in-swift/</link><guid isPermaLink="true">https://img.ly/blog/add-text-to-video-in-swift/</guid><description>Use Swift and AVMutableVideoComposition to add a text overlay to a video clip!</description><pubDate>Fri, 15 Oct 2021 15:05:59 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you will see how to use Swift and AVMutableVideoComposition to add a text overlay to a video clip. The code in this article uses Swift 5. Clone &lt;a href=&quot;https://github.com/waltertyree/text-video&quot;&gt;this repository&lt;/a&gt; for a Swift Playground with the example code.&lt;/p&gt;
&lt;h2 id=&quot;setting-up&quot;&gt;Setting up&lt;/h2&gt;
&lt;p&gt;To access the &lt;code&gt;AVFoundation&lt;/code&gt; and &lt;code&gt;CoreImage&lt;/code&gt; objects, be sure to add these imports to your 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;import&lt;/span&gt;&lt;span&gt; AVFoundation&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; CoreImage&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;CIFilterBuiltins&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Importing the &lt;code&gt;CIFilterBuiltins&lt;/code&gt; lets you use autocompletion when working with &lt;code&gt;CIFilters&lt;/code&gt;. Otherwise, just import &lt;code&gt;CoreImage&lt;/code&gt;. But then you will need to access the filter properties using strings.&lt;/p&gt;
&lt;p&gt;When working with video clips and movie files, the basic starting point is the &lt;code&gt;AVAsset&lt;/code&gt; class. This class combines all of the timed video and audio tracks that make up a movie. Additionally there may be subtitles and timed metadata or captions.&lt;/p&gt;
&lt;h2 id=&quot;creating-a-video-composition&quot;&gt;Creating a Video Composition&lt;/h2&gt;
&lt;p&gt;After you create a video composition for an asset, you can apply it to an &lt;code&gt;AVPlayerItem&lt;/code&gt; to display on screen or to an &lt;code&gt;AVAssetExportSession&lt;/code&gt; to write to a file. The composition for this tutorial will use the &lt;code&gt;init(asset:applyingCIFiltersWithHandler:)&lt;/code&gt; initializer. Then you can apply &lt;code&gt;CIFilter&lt;/code&gt;s to each frame of the video.&lt;/p&gt;
&lt;p&gt;First, load a movie file as an &lt;code&gt;AVAsset&lt;/code&gt; using its URL.&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;//Fetch a URL for the movie from the bundle&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; waterfallURL &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;waterfall&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;//Create an AVAsset with the url&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; waterfallAsset &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;: waterfallURL&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can create a &lt;code&gt;AVMutableVideoComposition&lt;/code&gt; with the asset&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; titleComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVMutableVideoComposition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: waterfallAsset) {request &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//apply filters here&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;request.&lt;/span&gt;&lt;span&gt;finish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: request.sourceImage, &lt;/span&gt;&lt;span&gt;context&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;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code above passes the video frame unaltered. The &lt;code&gt;request&lt;/code&gt; is the current video frame. It is an &lt;code&gt;AVAsynchronousCIImageFilteringRequest&lt;/code&gt; object. The &lt;code&gt;request&lt;/code&gt; object has three properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;code&gt;sourceImage&lt;/code&gt; which is the video frame as a &lt;code&gt;CIImage&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the &lt;code&gt;renderSize&lt;/code&gt; which is the size of the video frame&lt;/li&gt;
&lt;li&gt;&lt;code&gt;compositionTime&lt;/code&gt; which is the timestamp of the current frame&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The code in the handler will run once for each frame of the video clip.&lt;/p&gt;
&lt;h2 id=&quot;generating-a-text-image&quot;&gt;Generating a Text Image&lt;/h2&gt;
&lt;p&gt;Because the &lt;code&gt;sourceImage&lt;/code&gt; is a &lt;code&gt;CIImage&lt;/code&gt;, you can use any of the over 200 &lt;code&gt;CIFilter&lt;/code&gt; objects that Core Image provides. There are two text generator filters: &lt;code&gt;CIAttributedTextImageGenerator&lt;/code&gt; and &lt;code&gt;CITextImageGenerator&lt;/code&gt;. Either of these will render a string as a &lt;code&gt;CIImage&lt;/code&gt;. This tutorial will use the Attributed Text version so you can modify the color and add a shadow.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;waterfall-text&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 642px) 642px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;642&quot; height=&quot;198&quot; src=&quot;https://img.ly/_astro/waterfall-text_Zf0kjo.webp&quot; srcset=&quot;/_astro/waterfall-text_9L6EH.webp 640w, /_astro/waterfall-text_Zf0kjo.webp 642w&quot;&gt;&lt;/p&gt;
&lt;p&gt;Start by creating a shadow and then creating a dictionary of attributes to apply to the string.&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; whiteShadow &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; NSShadow&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;whiteShadow.shadowBlurRadius &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;whiteShadow.shadowColor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIColor.white&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; attributes &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;  NSAttributedString.&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;.foregroundColor &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; UIColor.blue,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  NSAttributedString.&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;.font &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; UIFont&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;Marker Felt&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;36.0&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;  NSAttributedString.&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;.shadow &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; whiteShadow&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;Create the attributed string by combining the string and the attributes&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; waterfallText &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; NSAttributedString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Waterfall!&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;attributes&lt;/span&gt;&lt;span&gt;: attributes)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Provide the attributed string and a scale factor to the filter to generate an image like the one shown above.&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; textFilter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIFilter.&lt;/span&gt;&lt;span&gt;attributedTextImageGenerator&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;textFilter.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; waterfallText&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;textFilter.scaleFactor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 4.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;textFilter.outputImage&lt;/code&gt; will be the image of the rendered text with the attributes applied. The &lt;code&gt;extent&lt;/code&gt; of the &lt;code&gt;outputImage&lt;/code&gt; will be a rectangle that is large enough to encompass the text. The text will render on a single line, it doesn’t wrap using this method.&lt;/p&gt;
&lt;p&gt;If you were to place the text on the video image at this point, the bottom-left of the text would be at the bottom-left of the video. Unlike &lt;code&gt;UIView&lt;/code&gt; objects, the origin point (0,0) is at the bottom-left, not the top-left. To center the text and move it off of the bottom, you can apply a standard &lt;code&gt;CGAffineTransform&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; centerHorizontal &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; (request.renderSize.width &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; textFilter.outputImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.extent.width)&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; moveTextTransform &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CGAffineTransform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;translationX&lt;/span&gt;&lt;span&gt;: centerHorizontal, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;200&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; positionedText &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; textFilter.outputImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transformed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;by&lt;/span&gt;&lt;span&gt;: moveTextTransform)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now finish the pipeline by composing the new text image over the original source image.&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;positionedText.&lt;/span&gt;&lt;span&gt;composited&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;over&lt;/span&gt;&lt;span&gt;: request.sourceImage)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All together, the original composition now becomes&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; titleComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVMutableVideoComposition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: waterfallAsset) { request &lt;/span&gt;&lt;span&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Create a white shadow for the text&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; whiteShadow &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; NSShadow&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;whiteShadow.shadowBlurRadius &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;whiteShadow.shadowColor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; UIColor.white&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; attributes &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;  NSAttributedString.&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;.foregroundColor &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; UIColor.blue,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  NSAttributedString.&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;.font &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; UIFont&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;Marker Felt&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;36.0&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;  NSAttributedString.&lt;/span&gt;&lt;span&gt;Key&lt;/span&gt;&lt;span&gt;.shadow &lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; whiteShadow&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;//Create an Attributed String&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; waterfallText &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; NSAttributedString&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;Waterfall!&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;attributes&lt;/span&gt;&lt;span&gt;: attributes)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Convert attributed string to a CIImage&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; textFilter &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CIFilter.&lt;/span&gt;&lt;span&gt;attributedTextImageGenerator&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;textFilter.&lt;/span&gt;&lt;span&gt;text&lt;/span&gt;&lt;span&gt; =&lt;/span&gt;&lt;span&gt; waterfallText&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;textFilter.scaleFactor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; 4.0&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Center text and move 200 px from the origin&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//source image is 720 x 1280&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; positionedText &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; textFilter.outputImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transformed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;by&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CGAffineTransform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;translationX&lt;/span&gt;&lt;span&gt;: (request.renderSize.width &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; textFilter.outputImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.extent.width)&lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt;2&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;200&lt;/span&gt;&lt;span&gt;))&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Compose text over video image&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;request.&lt;/span&gt;&lt;span&gt;finish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: positionedText.&lt;/span&gt;&lt;span&gt;composited&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;over&lt;/span&gt;&lt;span&gt;: request.sourceImage), &lt;/span&gt;&lt;span&gt;context&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;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;displaying-the-finished-video&quot;&gt;Displaying The Finished Video&lt;/h2&gt;
&lt;p&gt;With an &lt;code&gt;AVAsset&lt;/code&gt; and an &lt;code&gt;AVMutableVideoComposition&lt;/code&gt; you can now combine the two into an &lt;code&gt;AVPlayer&lt;/code&gt; to display in an &lt;code&gt;AVPlayerViewController&lt;/code&gt; or in your own &lt;code&gt;UIViewController&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; waterFallItem &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;: waterfallAsset)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;waterFallItem.videoComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; titleComposition&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&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;: waterFallItem)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt=&quot;finished-image&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;536&quot; src=&quot;https://img.ly/_astro/finished-image_Z2bm57U.webp&quot; srcset=&quot;/_astro/finished-image_Z2bm57U.webp 300w&quot;&gt;&lt;/p&gt;
&lt;p&gt;As stated above, you can also combine the asset and the composition and write them to a new movie file using an &lt;code&gt;AVAssetExportSession&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;going-further&quot;&gt;Going Further&lt;/h2&gt;
&lt;p&gt;The method in this tutorial is suitable for adding watermark or title text to videos. If you want to give users the ability to add custom text, there is more work to do. You will need to create font and color pickers as well as code to position the text in the frame.&lt;/p&gt;
&lt;p&gt;You can use 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; 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;finished&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 852px) 852px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;852&quot; height=&quot;900&quot; src=&quot;https://img.ly/_astro/finished_xNz1m.webp&quot; srcset=&quot;/_astro/finished_tD2vg.webp 640w, /_astro/finished_ZcPb1r.webp 750w, /_astro/finished_959NM.webp 828w, /_astro/finished_xNz1m.webp 852w&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 use &lt;code&gt;AVMutableVideoComposition&lt;/code&gt; to add text to a video clip. 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;VideoEditorSDK&lt;/a&gt; allows you to annotate and enhance your clips before sharing. Including typography, audio support and video composition, IMG.LY provides a comprehensive solution for mobile video editing – find the documentation &lt;a href=&quot;https://img.ly/docs/vesdk/?utm_source=imgly&amp;#x26;utm_medium=blog&amp;#x26;utm_campaign=howtos&quot;&gt;here&lt;/a&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;&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/10/add-text-to-video-swift.jpg" medium="image"/><category>Video Editing</category><category>Video Editor</category><category>Swift</category><category>App Development</category><category>Developer Tools</category><category>How-To</category><category>Tutorial</category></item><item><title>How to Trim and Crop Video in Swift</title><link>https://img.ly/blog/trim-and-crop-video-in-swift/</link><guid isPermaLink="true">https://img.ly/blog/trim-and-crop-video-in-swift/</guid><description>Learn how to use Swift and AVKit to crop a video clip and trim a video timeline. </description><pubDate>Tue, 31 Aug 2021 16:03:25 GMT</pubDate><content:encoded>&lt;p&gt;In this article, you will see how to use Swift and &lt;code&gt;AVKit&lt;/code&gt; to crop a video clip and how to trim a video’s timeline. Then you will learn how to use &lt;code&gt;AVAssetExportSession&lt;/code&gt; to write your edited video to disk. The code in this article uses Swift 5 and Xcode 12.5. Clone &lt;a href=&quot;https://github.com/waltertyree/trim-crop-video&quot;&gt;this repository&lt;/a&gt; for a sample project and example code.&lt;/p&gt;
&lt;h2 id=&quot;anatomy-of-a-video-file&quot;&gt;Anatomy of a Video File&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;AVFoundation&lt;/code&gt; and &lt;code&gt;AVKit&lt;/code&gt; frameworks are what Swift and iOS use to manage audio and video. When starting out, you may use an &lt;code&gt;AVPlayerViewController&lt;/code&gt; for playback with the same controls and features as the native players. You can also use an &lt;code&gt;AVPlayer&lt;/code&gt; object and provide your own playback and editing controls. Whichever you choose, you begin by loading a media file (&lt;code&gt;.mp3&lt;/code&gt;, &lt;code&gt;.mov&lt;/code&gt;, etc.) into an &lt;code&gt;AVPlayerItem&lt;/code&gt;. Inside the &lt;code&gt;AVPlayerItem&lt;/code&gt;, the media becomes an &lt;code&gt;AVAsset&lt;/code&gt; which may have many &lt;code&gt;tracks&lt;/code&gt; of video, audio, text, closed captions etc.&lt;/p&gt;
&lt;p&gt;To manipulate an &lt;code&gt;AVAsset&lt;/code&gt;, Apple provides the &lt;code&gt;AVComposition&lt;/code&gt; class. The &lt;code&gt;AVComposition&lt;/code&gt; can act on a single track or many tracks to filter and mix underlying media and produces a single output. An &lt;code&gt;AVPlayer&lt;/code&gt; shows the output of an &lt;code&gt;AVComposition&lt;/code&gt; on a device. When it is time to export, you can use an &lt;code&gt;AVAssetExportSession&lt;/code&gt; with the same &lt;code&gt;AVComposition&lt;/code&gt; to write to disk or upload to a server.&lt;/p&gt;
&lt;h2 id=&quot;cropping-video-to-a-rectangle&quot;&gt;Cropping Video to a Rectangle&lt;/h2&gt;
&lt;p&gt;Apple provides some different &lt;code&gt;AVComposition&lt;/code&gt; classes optimized for common tasks. Apple recommends using one of these classes instead of writing custom &lt;code&gt;AVComposition&lt;/code&gt; classes whenever possible. The reason for this is that when Apple introduces new technologies (like HDR Video), they will ensure it works with their classes. If you’ve written your own, you will have to update it to work with the new technologies. In this article you will crop the video to a rectangle and let the audio pass through as recorded. A good class to use for this task will be the &lt;code&gt;AVMutableVideoComposition&lt;/code&gt; with the &lt;code&gt;init(asset: AVAsset, applyingCIFiltersWithHandler applier: @escaping (AVAsynchronousCIImageFilteringRequest) -&gt; Void)&lt;/code&gt; initializer. This will allow you to apply a &lt;code&gt;CIFilter&lt;/code&gt; to each frame of the video. Apple provides an efficient cropping filter called &lt;code&gt;CICrop&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; transformVideo&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;item&lt;/span&gt;&lt;span&gt;: AVPlayerItem, &lt;/span&gt;&lt;span&gt;cropRect&lt;/span&gt;&lt;span&gt;: CGRect) {&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; cropScaleComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; AVMutableVideoComposition&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;asset&lt;/span&gt;&lt;span&gt;: item.asset, &lt;/span&gt;&lt;span&gt;applyingCIFiltersWithHandler&lt;/span&gt;&lt;span&gt;: {request &lt;/span&gt;&lt;span&gt;in&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; cropFilter &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;CICrop&quot;&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;    cropFilter.&lt;/span&gt;&lt;span&gt;setValue&lt;/span&gt;&lt;span&gt;(request.sourceImage, &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: kCIInputImageKey) &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    cropFilter.&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;cgRect&lt;/span&gt;&lt;span&gt;: cropRect), &lt;/span&gt;&lt;span&gt;forKey&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;&quot;inputRectangle&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;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    let&lt;/span&gt;&lt;span&gt; imageAtOrigin &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cropFilter.outputImage&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;transformed&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;by&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;CGAffineTransform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;translationX&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;cropRect.origin.x, &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;cropRect.origin.y)) &lt;/span&gt;&lt;span&gt;//3&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;    request.&lt;/span&gt;&lt;span&gt;finish&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;with&lt;/span&gt;&lt;span&gt;: imageAtOrigin, &lt;/span&gt;&lt;span&gt;context&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;//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 class=&quot;line&quot;&gt;&lt;span&gt;  cropScaleComposition.renderSize &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cropRect.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt; //5&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  item.videoComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cropScaleComposition  &lt;/span&gt;&lt;span&gt;//6&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;applyingCIFiltersWithHandler&lt;/code&gt; will execute for every frame in the &lt;code&gt;asset&lt;/code&gt; of the &lt;code&gt;AVPlayerItem&lt;/code&gt;. Here is what the code above will do:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a CICrop filter (note that this demo uses &lt;code&gt;!&lt;/code&gt; to force unwrap, production code should handle failure)&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;.sourceImage&lt;/code&gt; (a &lt;code&gt;CIImage&lt;/code&gt;) from the request to the filter.&lt;/li&gt;
&lt;li&gt;Move the cropped image to the origin of the video frame. When you resize the frame (step 4) it will resize from the origin.&lt;/li&gt;
&lt;li&gt;Output the transformed frame image&lt;/li&gt;
&lt;li&gt;Set the size of the video frame to the cropped size&lt;/li&gt;
&lt;li&gt;Attach the composition to the &lt;code&gt;videoComposition&lt;/code&gt; property of the &lt;code&gt;asset&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notice that the transformation does not alter the underlying asset. It creates a &lt;code&gt;videoComposition&lt;/code&gt; filter to attach to the item during playback. After the transformation, &lt;code&gt;AVPlayer&lt;/code&gt; will display the new creation when executing its &lt;code&gt;.play()&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;Sharp eyed readers will have noticed that the &lt;code&gt;init&lt;/code&gt; method mentions “applying &lt;em&gt;CIFilters&lt;/em&gt;” plural. Instead of one filter, you can create an entire pipeline of &lt;code&gt;CIFilter&lt;/code&gt; objects to manipulate the visual properties of the frames. The &lt;code&gt;request&lt;/code&gt; object also contains the &lt;code&gt;compositionTime&lt;/code&gt; so filters can change at different parts of the video.&lt;/p&gt;
&lt;h2 id=&quot;trimming-the-time-of-a-video&quot;&gt;Trimming the Time of a Video&lt;/h2&gt;
&lt;p&gt;When an &lt;code&gt;AVPlayer&lt;/code&gt; loads an &lt;code&gt;AVItem&lt;/code&gt; the start time of the video will be at &lt;code&gt;CMTime.zero&lt;/code&gt;. The end will be the &lt;code&gt;duration&lt;/code&gt; property of the &lt;code&gt;AVItem&lt;/code&gt;. To adjust the playback times, you call the &lt;code&gt;.seek&lt;/code&gt; function to move to the new start time and set the &lt;code&gt;forwardPlaybackEndTime&lt;/code&gt; to the new end time. Now the player will only play the part of the clip between those times.&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;//The player object is already created and configured to play video in the ViewController&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;//load a video .mov file&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;: 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;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;//Set the start time to 5 seconds.&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; startTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTimeMakeWithSeconds&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;preferredTimescale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&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;//Convert the duration of the video to seconds&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; videoDurationInSeconds &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.player&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.currentItem&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;.duration.seconds&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;//Subtract 5 seconds from the end time&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; endTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTimeMakeWithSeconds&lt;/span&gt;&lt;span&gt;(videoDurationInSeconds &lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt; 5&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;preferredTimescale&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;600&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;//Assign the new values to the start and end time&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;seek&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt;: startTime)&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;.currentItem&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.forwardPlaybackEndTime &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; endTime&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;//Play the video&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;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;CMTime&lt;/code&gt; (Core Media Time) object uses timescales and &lt;code&gt;Int&lt;/code&gt; values to map to the individual frames of tracks. Video tracks commonly come to your app with 24, 30, 60 or 120 frames per second. Converting between these different formats using Floats or Doubles would be imprecise. When converting seconds to &lt;code&gt;CMTime&lt;/code&gt; with video, &lt;code&gt;600&lt;/code&gt; is the commonly used timescale because it is a common multiple of all of the standard frames per second.&lt;/p&gt;
&lt;h2 id=&quot;exporting-the-video&quot;&gt;Exporting the Video&lt;/h2&gt;
&lt;p&gt;As with the &lt;code&gt;videoComposition&lt;/code&gt; property, setting the start and end times of the player only modifies the playback of the video clip. The underlying asset remains unchanged. When you use the &lt;code&gt;AVAssetExportSession&lt;/code&gt;, the new video file will have the change. The export session applies time range and video composition objects as it writes the file. A function to export might look like:&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; export&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;_&lt;/span&gt;&lt;span&gt; asset: AVAsset, &lt;/span&gt;&lt;span&gt;to&lt;/span&gt;&lt;span&gt; outputMovieURL: URL, &lt;/span&gt;&lt;span&gt;startTime&lt;/span&gt;&lt;span&gt;: CMTime, &lt;/span&gt;&lt;span&gt;endTime&lt;/span&gt;&lt;span&gt;: CMTime, &lt;/span&gt;&lt;span&gt;composition&lt;/span&gt;&lt;span&gt;: AVVideoComposition) {&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;    //Create trim range&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  let&lt;/span&gt;&lt;span&gt; timeRange &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CMTimeRangeFromTimeToTime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;start&lt;/span&gt;&lt;span&gt;: startTime, &lt;/span&gt;&lt;span&gt;end&lt;/span&gt;&lt;span&gt;: endTime)&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;    //delete any old file&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; FileManager.default.&lt;/span&gt;&lt;span&gt;removeItem&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;at&lt;/span&gt;&lt;span&gt;: outputMovieURL)&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;(&lt;/span&gt;&lt;span&gt;&quot;Could not remove 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;    //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;: asset, &lt;/span&gt;&lt;span&gt;presetName&lt;/span&gt;&lt;span&gt;: AVAssetExportPresetHighestQuality)&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;    //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;.videoComposition &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; composition&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;
&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;  exporter&lt;/span&gt;&lt;span&gt;?&lt;/span&gt;&lt;span&gt;.timeRange &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; timeRange&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;    //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;
&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;Video saved 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;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;&lt;code&gt;AVKit&lt;/code&gt; and &lt;code&gt;AVFoundation&lt;/code&gt; provide simple objects for manipulating video files. The difficulty when working with video and audio tracks usually comes while providing editing controls for a user. The image below shows how the original video dimensions might change from creation to display.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;different screen dimensions of video during different stages of editing&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1453px) 1453px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1453&quot; height=&quot;716&quot; src=&quot;https://img.ly/_astro/resizing_1kxMMH.webp&quot; srcset=&quot;/_astro/resizing_ZD5fcE.webp 640w, /_astro/resizing_w8gz1.webp 750w, /_astro/resizing_160u1K.webp 828w, /_astro/resizing_1mGc6g.webp 1080w, /_astro/resizing_22qr8E.webp 1280w, /_astro/resizing_1kxMMH.webp 1453w&quot;&gt;&lt;/p&gt;
&lt;p&gt;The original 2160x3840 video appears on an iPhone in a 357x635 frame. Additionally the origin point (green dot) of the video file and the origin point of the UIViews are not equal. Passing the frame of the red, cropping rectangle to an &lt;code&gt;AVComposition&lt;/code&gt; would not work as expected. Inside of the &lt;code&gt;AVComposition&lt;/code&gt; the video resumes it’s 720x1280 dimensions while the 250x250 cropping rectangle would remain 250x250.&lt;/p&gt;
&lt;p&gt;Before using a UIView rectangle with an &lt;code&gt;AVComposition&lt;/code&gt; it needs to resize and the origin point needs to align to the video’s origin. Your app must apply a &lt;code&gt;CGAffineTransform&lt;/code&gt; to reorient the origin points.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;flip and slide rectangle&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1209px) 1209px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1209&quot; height=&quot;793&quot; src=&quot;https://img.ly/_astro/transform-rect_28A6Vy.webp&quot; srcset=&quot;/_astro/transform-rect_ZbJbv8.webp 640w, /_astro/transform-rect_Z25Ixcl.webp 750w, /_astro/transform-rect_Z9DQt7.webp 828w, /_astro/transform-rect_VNLLC.webp 1080w, /_astro/transform-rect_28A6Vy.webp 1209w&quot;&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;var&lt;/span&gt;&lt;span&gt; cropRect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span&gt;.croppingView.frame &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; originFlipTransform &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CGAffineTransform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;scaleX&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;1&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;-1&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; frameTranslateTransform &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; CGAffineTransform&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;translationX&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;: renderingSize.height)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cropRect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cropRect.&lt;/span&gt;&lt;span&gt;applying&lt;/span&gt;&lt;span&gt;(originFlipTransform) &lt;/span&gt;&lt;span&gt;//2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;cropRect &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; cropRect.&lt;/span&gt;&lt;span&gt;applying&lt;/span&gt;&lt;span&gt;(frameTranslateTransform) &lt;/span&gt;&lt;span&gt;//3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you apply regular ratio math to the dimensions of the cropping rectangle to match the video dimensions.&lt;br&gt;
&lt;img alt=&quot;resize rectangle to video dimensions&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 359px) 359px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;359&quot; height=&quot;494&quot; src=&quot;https://img.ly/_astro/translate-resize_54OyH.webp&quot; srcset=&quot;/_astro/translate-resize_54OyH.webp 359w&quot;&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; renderingSize &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; playerItem.presentationSize&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; xFactor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; renderingSize.width &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; playerView.bounds.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;.width&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; yFactor &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; renderingSize.height &lt;/span&gt;&lt;span&gt;/&lt;/span&gt;&lt;span&gt; playerView.bounds.&lt;/span&gt;&lt;span&gt;size&lt;/span&gt;&lt;span&gt;.height&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; newX &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; croppingView.frame.origin.x &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; xFactor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; newW &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; croppingView.frame.width &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; xFactor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; newY &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; croppingView.frame.origin.y &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; yFactor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let&lt;/span&gt;&lt;span&gt; newH &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; croppingView.frame.height &lt;/span&gt;&lt;span&gt;*&lt;/span&gt;&lt;span&gt; yFactor&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;var&lt;/span&gt;&lt;span&gt; cropRect &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;: newX, &lt;/span&gt;&lt;span&gt;y&lt;/span&gt;&lt;span&gt;: newY, &lt;/span&gt;&lt;span&gt;width&lt;/span&gt;&lt;span&gt;: newW, &lt;/span&gt;&lt;span&gt;height&lt;/span&gt;&lt;span&gt;: newH)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The transformations above are standard when working with &lt;code&gt;AVKit&lt;/code&gt; and &lt;code&gt;UIKit&lt;/code&gt; or &lt;code&gt;SwiftUI&lt;/code&gt; in an app. The math is not complex, but it is tedious. Unless your app is a custom video editing application, you may find that using a commercial solution 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; is a better approach. By adding 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;VideoEditorSDK&lt;/a&gt;, your app can have professional appearing trim and crop controls as well as filtering, text and more.&lt;/p&gt;
&lt;p&gt;&lt;img alt=&quot;trim crop and other controls&quot; loading=&quot;lazy&quot; decoding=&quot;async&quot; sizes=&quot;(min-width: 1131px) 1131px, 100vw&quot; data-astro-image=&quot;constrained&quot; data-astro-image-pos=&quot;center&quot; width=&quot;1131&quot; height=&quot;779&quot; src=&quot;https://img.ly/_astro/crop-trim-and-others_3Iidn.webp&quot; srcset=&quot;/_astro/crop-trim-and-others_Z2jzuUp.webp 640w, /_astro/crop-trim-and-others_1dym89.webp 750w, /_astro/crop-trim-and-others_B0bOy.webp 828w, /_astro/crop-trim-and-others_Z1WfCJ7.webp 1080w, /_astro/crop-trim-and-others_3Iidn.webp 1131w&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 crop a video image and how to trim time from a track, all in Swift. 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;VideoEditorSDK&lt;/a&gt; allows you to provide full-featured video editing for any application.&lt;br&gt;
Looking for more video capabilities? Check out our solution 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;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/resize-swift.jpg" medium="image"/><category>Swift</category><category>Software Development</category><category>Mobile App Development</category><category>Video Editing</category><category>Video Editor</category><category>Code</category><category>Developer Tools</category><category>Tutorial</category><category>App Development</category><category>How-To</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/products/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/products/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><item><title>Bringing Wide Color to PhotoEditor SDK</title><link>https://img.ly/blog/bringing-wide-color-to-photoeditor-sdk-a6ce8bb19ef7/</link><guid isPermaLink="true">https://img.ly/blog/bringing-wide-color-to-photoeditor-sdk-a6ce8bb19ef7/</guid><pubDate>Tue, 10 Jan 2017 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Inspired by &lt;a href=&quot;https://medium.com/@mikekrieger&quot;&gt;Mike Krieger&lt;/a&gt;’s &lt;a href=&quot;https://instagram-engineering.com/bringing-wide-color-to-instagram-5a5481802d7d#.2gjnzpdeb&quot;&gt;great post&lt;/a&gt; on supporting wide color images in Instagram, I decided to challenge myself by introducing the same wide color support into our &lt;a href=&quot;https://img.ly/products/photo-sdk&quot;&gt;PhotoEditor SDK&lt;/a&gt; within a day.&lt;/p&gt;
&lt;p&gt;Mike did an excellent job describing everything that is needed to support wide color images so I am not going to reiterate his explanation. Instead, here I’m going to share two findings I made in the process.&lt;/p&gt;
&lt;h2 id=&quot;image-export&quot;&gt;Image Export&lt;/h2&gt;
&lt;p&gt;Mike suggests that in order to preserve the color space while converting an &lt;code&gt;UIImage&lt;/code&gt; into a JPEG, the &lt;code&gt;UIImageJPEGRepresentation(_:_:)&lt;/code&gt; method has to be replaced with the new &lt;code&gt;UIGraphicsImageRenderer.jpegData(withCompressionQuality:actions:)&lt;/code&gt; method. Checking the color profile of the generated JPEG by &lt;code&gt;UIImageJPEGRepresentation(_:)&lt;/code&gt; I noticed that it seemed unnecessary and resulting in more work than needed. To verify this, I started &lt;a href=&quot;https://www.hopperapp.com&quot;&gt;Hopper&lt;/a&gt; and took a look at the internals of the new method. Here’s what it basically does:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It passes &lt;code&gt;actions&lt;/code&gt; to &lt;code&gt;UIGraphicsRenderer.runDrawingActions(_:completionActions:)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It obtains the resulting image using &lt;code&gt;UIGraphicsImageRendererContext.currentImage&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It passes that image to &lt;code&gt;UIImageJPEGRepresentation(_:_:)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As you can see, it makes use of &lt;code&gt;UIImageJPEGRepresentation(_:_:)&lt;/code&gt; internally too, so there really is no need to go the extra mile. If you are actually doing any drawing within the &lt;code&gt;actions&lt;/code&gt; block using wide color &lt;code&gt;UIColor&lt;/code&gt;s it absolutely makes sense to use Mike’s apoproach. But if you only want to convert an &lt;code&gt;UIImage&lt;/code&gt; into a JPEG image, using the old method is far easier.&lt;/p&gt;
&lt;h2 id=&quot;core-image&quot;&gt;Core Image&lt;/h2&gt;
&lt;p&gt;We are using &lt;code&gt;CIImage&lt;/code&gt;, &lt;code&gt;CIFilter&lt;/code&gt; and &lt;code&gt;CIContext&lt;/code&gt; for most of our processing. In our live preview we use a &lt;code&gt;CIContext&lt;/code&gt; to directly render a &lt;code&gt;CIImage&lt;/code&gt; into a &lt;code&gt;GLKView&lt;/code&gt; and I’ve run into exactly the same problem as Mike has — namely not being able to get that view to be color space-aware. Using the offscreen buffer workaround that he suggested was going to be too much work though and also I didn’t want to sacrifice any performance.&lt;/p&gt;
&lt;p&gt;After doing some research and digging further into the GLKit and Core Image assembly I found a nice solution. It looks like in order to render wide color images into OpenGL, OpenGLES 3 and a &lt;code&gt;GLKViewDrawableColorFormat&lt;/code&gt; (that is currently not defined as an &lt;code&gt;enum&lt;/code&gt; case) has to be used. The following code works like a charm for me:&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 api: EAGLRenderingAPI&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let colorFormat: GLKViewDrawableColorFormat&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;if #available(iOS 10.0, *) {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  if UIScreen.main.traitCollection.displayGamut == .P3 {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    api = .openGLES3&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    colorFormat = GLKViewDrawableColorFormat(rawValue: 10)!&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 class=&quot;line&quot;&gt;&lt;span&gt;    api = .openGLES2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;    colorFormat = .RGBA8888&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;} else {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  api = .openGLES2&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;  colorFormat = .RGBA8888&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;let context = EAGLContext(api: api)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;let previewView = GLKView(frame: CGRect.zero, context: context!)&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;previewView.delegate = self&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt;previewView.drawableColorFormat = colorFormat&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code creates a &lt;code&gt;GLKView&lt;/code&gt; that is backed by a &lt;code&gt;CAEAGLLayer&lt;/code&gt; with the following &lt;code&gt;drawableProperties&lt;/code&gt; set:&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;[&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; &quot;EAGLDrawablePropertyRetained&quot;: 0,&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;line&quot;&gt;&lt;span&gt; &quot;EAGLDrawablePropertyColorFormat&quot;: EAGLColorFormatRGBA_XR10_64BPP&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;Unfortunately &lt;code&gt;EAGLColorFormatRGBA_XR10_64BPP&lt;/code&gt; also doesn’t seem to be documented at this point, but my guess is that setting this color format on Instagram’s &lt;code&gt;EAGLView&lt;/code&gt; would also solve their problem of making the view color space-aware.&lt;/p&gt;
&lt;p&gt;Thanks to Core Image we can just switch to OpenGLES 3 without having to do any other modifications to our code. However, I am currently not sure if this is considered a private API use. I would greatly appreciate if anyone could shed some light on this.&lt;/p&gt;
&lt;p&gt;I have not yet been able to test this on any device other than the iPhone 7 Plus, but I will update this post if I run into any problems with future tests.&lt;/p&gt;
&lt;h2 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Getting an app to support wide color images is definitely worth it and not as difficult as I had imagined. Unfortunately, it currently looks like iOS is still missing (public) support for wide color in some parts of its frameworks. I hope that Apple addresses these problems soon. Nevertheless we’re optimistic that we’ll be able to include wide color image support in the next release of our SDK. Feel free to check out our &lt;a href=&quot;https://apps.apple.com/de/app/img-ly-camera-pro-photo-sharing/id589839231&quot;&gt;demo app&lt;/a&gt; in the App Store which we will update as soon as possible.&lt;/p&gt;</content:encoded><dc:creator>Sascha</dc:creator><media:content url="https://blog.img.ly/2020/03/1-VF6BW_zDoIaklWdM3u98Tw.jpeg" medium="image"/><category>iOS</category><category>Swift</category><category>Instagram</category><category>Wide Color</category><category>Core Image</category><category>Tech</category><category>How-To</category><category>Insights</category></item></channel></rss>