<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Borut on Medium]]></title>
        <description><![CDATA[Stories by Borut on Medium]]></description>
        <link>https://medium.com/@borutt?source=rss-a085673da52f------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*V9d_HeowGYLLLMLvmqDRUQ.jpeg</url>
            <title>Stories by Borut on Medium</title>
            <link>https://medium.com/@borutt?source=rss-a085673da52f------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 12 Nov 2026 18:27:51 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@borutt/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[SwiftUI: Features, Best Practices, and Bottlenecks]]></title>
            <link>https://medium.com/@borutt/swiftui-features-best-practices-and-bottlenecks-fc32c7676f62?source=rss-a085673da52f------2</link>
            <guid isPermaLink="false">https://medium.com/p/fc32c7676f62</guid>
            <category><![CDATA[swiftui]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[features]]></category>
            <dc:creator><![CDATA[Borut]]></dc:creator>
            <pubDate>Fri, 12 May 2023 20:56:07 GMT</pubDate>
            <atom:updated>2023-05-12T20:57:31.055Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tOm1Gd_XYAetF8X8nEdqPA.png" /></figure><p>SwiftUI is a modern UI framework for building iOS, iPadOS, watchOS, and macOS applications. Introduced in 2019, it offers a declarative syntax that enables developers to write concise, efficient, and easy-to-read code. SwiftUI has quickly become the go-to framework for developing user interfaces in the Apple ecosystem, and its popularity continues to grow. In this article, we will discuss the features of SwiftUI, best practices for using it, and common bottlenecks that developers may encounter.</p><h3>Features of SwiftUI</h3><h4>Declarative syntax</h4><p>SwiftUI’s declarative syntax makes it easy to describe a user interface in a concise and expressive way. Developers simply declare what they want the user interface to look like, and SwiftUI handles the rest. This approach is in contrast to the traditional imperative approach, where developers have to write detailed instructions for how the user interface should be constructed.</p><h4>Live preview</h4><p>SwiftUI includes a live preview feature that allows developers to see the changes they make to the user interface in real-time. This feature makes it easier to experiment with different design choices and to catch issues early in the development process.</p><h4>Cross-platform support</h4><p>SwiftUI is designed to work across multiple Apple platforms, including iOS, iPadOS, watchOS, and macOS. This allows developers to write code that can be used across multiple platforms, reducing development time and effort.</p><h4>Customizable views</h4><p>SwiftUI allows developers to create custom views and reuse them throughout their application. This feature makes it easy to create complex user interfaces that are consistent across an application.</p><h3>Best practices for using SwiftUI</h3><h4>Use @State for mutable state</h4><p>SwiftUI uses the @State property wrapper to manage mutable state within a view. When a property is marked as @State, SwiftUI automatically handles updates to the property and updates the view accordingly. Using @State can simplify code and reduce bugs caused by mutable state.</p><h4>Use @Binding to pass state between views</h4><p>@Binding is another property wrapper that is used to pass state between views. When a view is passed a @Binding, it can update the state of the original view. This can be useful for sharing data between views or for creating reusable components.</p><h4>Use ForEach for dynamic content</h4><p>SwiftUI’s ForEach view can be used to dynamically generate views based on a collection of data. This can be useful for displaying lists of data or for creating complex user interfaces that change based on user input.</p><h4>Use GeometryReader for layout</h4><p>GeometryReader is a view that provides information about the size and position of its parent view. This can be useful for creating complex layouts that adapt to different screen sizes or for positioning views relative to each other.</p><h4>Use ZStack for layering views</h4><p>ZStack is a view that can be used to layer views on top of each other. This can be useful for creating complex user interfaces that require multiple layers of views.</p><h3>Bottlenecks in SwiftUI</h3><h4>Performance issues with large views</h4><p>SwiftUI’s declarative syntax can make it easy to create large and complex views. However, large views can cause performance issues, particularly on older devices or devices with limited memory. Developers should be mindful of the size of their views and should consider breaking them down into smaller, more manageable pieces if necessary.</p><h4>Issues with animation</h4><p>SwiftUI’s animation system is powerful and easy to use. However, it can be challenging to create complex animations that are performant and visually appealing. Developers should take care to optimize their animations and to test them on a range of devices.</p><h4>Difficulty with custom layouts</h4><p>While SwiftUI’s declarative syntax makes it easy to create simple layouts, it can be challenging to</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fc32c7676f62" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Swift 5.8: A Comprehensive Look at the Latest Release]]></title>
            <link>https://medium.com/@borutt/swift-5-8-a-comprehensive-look-at-the-latest-release-a59d6fd19a43?source=rss-a085673da52f------2</link>
            <guid isPermaLink="false">https://medium.com/p/a59d6fd19a43</guid>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[releases]]></category>
            <category><![CDATA[features]]></category>
            <dc:creator><![CDATA[Borut]]></dc:creator>
            <pubDate>Fri, 12 May 2023 20:19:55 GMT</pubDate>
            <atom:updated>2023-05-12T20:43:30.097Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*X3xVAHM1Qoje4uAsOBOY4Q.jpeg" /></figure><p>Swift is a powerful and efficient programming language, widely used to develop applications for iOS, macOS, and other Apple platforms. It is known for its safety, speed, and expressiveness. Since its first release in 2014, Swift has seen consistent updates and improvements. The latest release, Swift 5.8, introduces several new features and enhancements, making it easier and more efficient to develop Swift applications. In this article, we will take a comprehensive look at all the new features and improvements of Swift 5.8.</p><h3>Concurrency</h3><p>Concurrency is a key feature of modern programming languages, and Swift 5.8 provides developers with powerful support for writing concurrent code. The new release introduces two new language constructs, async and await, which simplify the writing of asynchronous code. The async keyword allows developers to define asynchronous functions, while the await keyword enables the blocking of code execution until an asynchronous task is completed.</p><p>Swift 5.8 also introduces the Task API, which provides a lightweight mechanism for creating and managing tasks. Tasks are a low-level construct that allows developers to write code that can be executed concurrently, making it easier to write efficient and responsive code.</p><h3>Improved error handling</h3><p>Swift 5.8 provides enhanced support for error handling, making it easier to write robust code. Previously, developers had to handle errors explicitly by writing code that caught and handled errors. With Swift 5.8, developers can use the try? operator to automatically handle errors and return a nil value if an error occurs. This makes error handling more concise and less error-prone.</p><p>Swift 5.8 also introduces a new throwsAsync function type, which allows developers to define functions that throw errors asynchronously. This new function type simplifies error handling in asynchronous code.</p><h3>Property wrappers</h3><p>Swift 5.8 introduces new support for property wrappers on local variables, which were previously only allowed on properties of structs or classes. Property wrappers provide a way to add additional behavior or validation to properties, making it easier to write cleaner and more concise code. The new feature for local variables is particularly useful when working with UI elements or other temporary values.</p><h3>Improved diagnostics</h3><p>Swift 5.8 includes several improvements to the language’s diagnostics. The compiler now provides more detailed and accurate error messages, making it easier to identify and fix issues in code. Additionally, the new release includes improvements to the syntax highlighting in Xcode, making it easier to read and understand code.</p><h3>Async sequences</h3><p>Building on the concurrency support introduced in Swift 5.8, the language now allows for async sequences. This new feature provides a way to asynchronously generate a sequence of values. Async sequences allow developers to write cleaner and more efficient code when working with streams of data, such as network requests or file processing.</p><h3>Improved memory management</h3><p>Swift 5.8 includes several improvements to the language’s memory management. The new release introduces a new withUnsafeContinuation function, which provides a way to safely pass continuations across asynchronous boundaries. Additionally, Swift 5.8 includes several improvements to the garbage collector, making it faster and more efficient.</p><h3>Enhancements to the Standard Library</h3><p>Swift 5.8 includes several enhancements to the standard library. One of the most notable additions is support for a new Zip type, which provides a way to combine two sequences into a single sequence of pairs. Additionally, the new release includes a new partition(by:) method for collections, which allows for efficient in-place partitioning of collections.</p><h3>Conclusion</h3><p>Swift 5.8 is a significant release that brings a range of new features and improvements to the language. With improved concurrency support, error handling, property wrappers, async sequences, diagnostics, and memory management, Swift developers have a powerful set of tools to build more efficient and robust code. If you’re interested in learning more about the new features of Swift 5.8, there are many resources available online, including the official Swift documentation and various online forums and communities.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a59d6fd19a43" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Measuring App size]]></title>
            <link>https://medium.com/@borutt/measuring-app-size-bc8d42f948a4?source=rss-a085673da52f------2</link>
            <guid isPermaLink="false">https://medium.com/p/bc8d42f948a4</guid>
            <category><![CDATA[app-clips]]></category>
            <category><![CDATA[app-size]]></category>
            <category><![CDATA[binary]]></category>
            <category><![CDATA[thinning]]></category>
            <category><![CDATA[measure]]></category>
            <dc:creator><![CDATA[Borut]]></dc:creator>
            <pubDate>Wed, 09 Sep 2020 07:04:21 GMT</pubDate>
            <atom:updated>2020-09-09T07:04:21.291Z</atom:updated>
            <content:encoded><![CDATA[<h3>Measuring App Size</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mPwySy-GEoxBvXKBrG1hWw.jpeg" /></figure><h3>Intro</h3><p>Developers, in general, tend to write clean, good looking, and efficient code. But the most important thing is the look &amp; feel of the final product — an app in the App Store everyone will adore.</p><p>To achieve that, we sometimes need to use 3rd party libraries to ship new features faster. And every piece of code, library, or framework we add to the project counts towards the final binary size of the app.</p><p>That’s all great since we usually don’t bother much until the app is still within an acceptable size. I will not discuss what app size is adequate and whatnot since this is a mostly subjective thing. But everyone has its opinion on that.</p><h3>App Clips</h3><p>This year WWDC brought as a new little thing to play with called <a href="https://developer.apple.com/app-clips/">App Clips</a>. Without further explanation of what they are, here is the quote from Apple’s website:</p><blockquote><em>“An App Clip is a small part of your app that’s discoverable at the moment it’s needed. App Clips are fast and lightweight so a user can open them quickly.”</em></blockquote><p>The main thing about App Clips is that they are small. But how small? Really small, and I mean it. It’s not that they are small in terms of a few dozen MBs, but they are tiny. The hard limit for App Clips is 10 MB in size, but that’s not the whole story. Someone would argue, eh, that’s fine, we can remove some chunks of the code, and we are quickly at 10 MBs. Well, maybe. Maybe not. Here is the thing. This limit refers to the uncompressed size, which is the app’s size after it is downloaded and installed on the device.</p><p>Does that make a difference? Oh, it does. In reality, it means that the size of the downloaded app is below 5 MBs. That is something.</p><p>I’ll not discuss the whole process of app <a href="https://developer.apple.com/documentation/xcode/reducing_your_app_s_size">thinning</a> in this article. But in the end, I’ve managed to shrink my app from 36+ down to 9.5 MB of uncompressed binary size.</p><h3>Measuring the app size</h3><p>Now that we are set with the requirements and the terminology, let’s see how we can measure the final binary size before it’s deployed to the App Store.</p><p>First, you’ll need to prepare an archive of the project.</p><p>Tap “Product” → “Archive” and wait until the “Organizer” is opened. If it doesn’t open automatically upon archiving is finished, open it from the menu by tapping “Window” → “Organizer”.</p><p>Now follow the next screens below…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3tlt-8vFwBQvk7PF8sivkQ.png" /><figcaption>Select any of the distribution method besides “App Store Connect” and hit “Next” button.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mumzmR2jkd5FA6G4JWovfg.png" /><figcaption>Select the product you would like to distribute. In my case AppClip is the primary target so I’ve chosen this one. Select whichever target you wish to perform size analysis to. Hit “Next” button.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*_X2elG578pUt0iezS03_QA.png" /><figcaption>Under distribution options select “All compatible device variants” for “App Thinning” and put checkmark under“Rebuild from Bitcode”. Hit “Next”.</figcaption></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zMDVcABaLLCjiMX3P_HkYQ.png" /><figcaption>To sign the product, select appropriate option. Usually “Automatically manage signing” should work. Hit “Next”.</figcaption></figure><p>You’ll be prompted with a modal sheet to select the location of the archived project. Now navigate to the just saved folder in Finder and open “App Thinning Size Report.txt” file. You should see the report details for all variants that will be distributed to the App Store.</p><p>The important thing here is to look at the last entry, named “{Product}.ipa”. In my case, this is “AppClip.ipa”, a universal variant that we should look at.</p><h3>Before</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ew3ENLSg1C5FKA_GHVYe8Q.png" /></figure><p>The size of the app before I’ve started thinning it was 16,5 MB compressed and 36,6 MB uncompressed. As you can see, the difference between downloaded and installed app size is quite significant.</p><h3>After</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mLggy5ZW33qVPuMdm8bE-g.png" /></figure><p>Here is the size of the app after the thinning process. I call it the new “normal”. Compressed size is 3,4 MB, uncompressed just under the allowed limit at 9,3 MB.</p><h3>Recap</h3><p>Whenever you need to lose some size of the app (for the sake of the App Clip or other reasons), this is an excellent way to examine the actual size of the app that will take effect in the App Store. I wasn’t aware of that until I needed it for the App Clip.</p><p><em>p.s.: I need to mention that this size can vary a bit from the size in the App Store since the encryption sums up values a bit.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bc8d42f948a4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Xcode 10 Over-scroll feature]]></title>
            <link>https://medium.com/@borutt/xcode-10-over-scroll-feature-b4ccdb12f5aa?source=rss-a085673da52f------2</link>
            <guid isPermaLink="false">https://medium.com/p/b4ccdb12f5aa</guid>
            <category><![CDATA[new-features]]></category>
            <category><![CDATA[xcode]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[over-scroll]]></category>
            <category><![CDATA[editor]]></category>
            <dc:creator><![CDATA[Borut]]></dc:creator>
            <pubDate>Tue, 25 Sep 2018 19:46:04 GMT</pubDate>
            <atom:updated>2018-09-25T19:46:58.429Z</atom:updated>
            <content:encoded><![CDATA[<p>I was using Xcode 10 beta versions for a few months before the final/GM release. I was wondering a few times but never had time to investigate if this is a feature or a bug in Xcode 🤷🏼‍♂️</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0bXn3vyojWKZHaEO3dFleg.png" /></figure><p>Now that the final version of Xcode is out it took me only a few seconds to find the setting in the Xcode that proves that this is intended behavior and can be modified or even turned off.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Y2EPk-Df5xKi5f4xXg5v3w.png" /><figcaption>Just head to the Xcode Preferences -&gt; Text Editing</figcaption></figure><p>What it does is that Xcode can add an extra space at the bottom of the screen so you can easily edit the code at the bottom of the file, presented in the middle of the screen.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/584/1*hbX_9STwYDyy9nQ5sY72_w.png" /><figcaption>You can choose between Small, Medium and Large</figcaption></figure><p>It felt weird at first, but now I like it. I’ve chosen to use Small over-scroll setting.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b4ccdb12f5aa" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Multi-lingual iOS & Android apps]]></title>
            <link>https://medium.com/@borutt/multi-lingual-ios-android-apps-efd07c538601?source=rss-a085673da52f------2</link>
            <guid isPermaLink="false">https://medium.com/p/efd07c538601</guid>
            <category><![CDATA[ruby]]></category>
            <category><![CDATA[multilingualism]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[localization]]></category>
            <category><![CDATA[translation]]></category>
            <dc:creator><![CDATA[Borut]]></dc:creator>
            <pubDate>Thu, 13 Sep 2018 06:43:29 GMT</pubDate>
            <atom:updated>2020-04-27T15:43:48.860Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*V4vJrqdSGotNd0rNc0M4mQ.jpeg" /></figure><p>Imagine an app that will be distributed around the world and needs to be translated in as many languages as possible. Easy right? Yeah, it’s not rocket science, but I’ll give a few tips and tricks on how to manage single or multilingual apps with ease.</p><p><em>As an iOS developer, the main focus here will be a localization of an iOS app. But I’ll give some tips for the Android part also.</em></p><p>iOS and android both have similart ways to handle localizations in apps. On iOS you need to create localizable.strings and on android strings.xml file. You would tipicaly create one of this file per language e.g. if you need to support English and German languages you will create {en,de}/localizable.strings for iOS and/or values-{en,de}/strings.xml . After that you create key-value entries where key is like and identificator for translation and value is an actual translation.</p><p>iOS and Android both have similar ways to handle localizations in apps. On iOS, you need to create localizable.string file and strings.xml on Android. You would typically create one of this file per language, e.g., if you need to support English and German languages you will create {en,de}/localizable.string for iOS or values-{en,de}/localizable.string for Android. After that, you create key-value entries where the key is like an identification for translation and value is an actual translation.</p><p>Here is an example of localizable.strings file on iOS</p><pre>/* General */<br>&quot;title&quot; = &quot;Title&quot;;<br>&quot;subtitle&quot; = &quot;Subtitle&quot;;</pre><pre>/* Login *?<br>&quot;login_button&quot; = &quot;Login&quot;;<br>&quot;login_forgot_password&quot; = &quot;Forgot Password&quot;;</pre><p>And example of strings.xml file on android</p><pre>&lt;!-- General --&gt;<br>&lt;string name=&quot;title&quot;&gt;Title&lt;/string&gt;<br>&lt;string name=&quot;subtitle&quot;&gt;Subtitle&lt;/string&gt;</pre><pre>&lt;!-- Login --&gt;<br>&lt;string name=&quot;login_button&quot;&gt;Login&lt;/string&gt;<br>&lt;string name=&quot;login_forgot_password&quot;&gt;Forgot Password&lt;/string&gt;</pre><p>Now imagine you have 1000+ entries in localization file like above. Again, easy right? Yes, you need to add key/value pairs, and you are good. But what if I tell you that we’ll be supporting German, Spanish, Italian, French and some other languages down the road? You’ll probably just copy/paste this files, renamed them and updated translations right? Well, this is where this article will help you out so you will not end up in a sync-up mess or forget to add/remove translations to/from all files.</p><p>The first thing you need to get familiar with an excellent library called <a href="https://github.com/netbe/Babelish">Babelish</a>. It is written in ruby and supports conversions for iOS and Android among others. The Babelish main purpose is to pick CSV file with translations and convert it to destination format (strings or XML in our case).</p><h3>Install Babelish</h3><p>The easiest way to install this library is to create Gemfile (or update if already exists in the project) with the following lines.</p><pre># Project Gemfile<br>source &quot;https://rubygems.org&quot;</pre><pre># other gems<br>gem &#39;babelish&#39;</pre><p>After saving a Gemfile, just run gem install bundler and bundle install from the Console.</p><p>The next thing is to configure a config file .babelish that Babelish will use for conversion. An <a href="https://github.com/netbe/Babelish/blob/master/.babelish.sample">example</a> is available on GitHub. Here I am sharing an example from one of my iOS projects</p><pre>filename: &quot;Project Localization&quot;</pre><pre>langs:<br>  English: &quot;en&quot;<br>  German: &quot;de&quot;</pre><pre>fetch: true<br>sheet: 0<br>excluded_states: [&quot;SKIP&quot;, &quot;android&quot;]<br>comments_column: 0<br>state_column: 4<br>keys_column: 1<br>default_lang: &quot;ENGLISH&quot;<br>output_dir: &quot;../Project/Resources/Localization/&quot;<br>output_basenames:<br>- Localizable<br>ignore_lang_path: false<br>stripping: true<br>csv_separator: &quot;,&quot;</pre><p>Notice filename and fetch properties. Both are used to load a CSV from a Google Sheets. If you want to load translations from a local CSV file, change the filename to a local file path and set fetch to false. If you are not going to use Google Sheets, you can skip the next chapter.</p><h3>Creating a Google Sheets</h3><p>Head over to <a href="https://docs.google.com/spreadsheets">Sheets</a> and create a new blank document.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/143af6ee74bc90a284d2f17b63bd2e3d/href">https://medium.com/media/143af6ee74bc90a284d2f17b63bd2e3d/href</a></iframe><p>Let’s walk you through the document briefly and explain all the columns.</p><ul><li>Section - a grouping key, this is a comment that will separate each group with an empty line</li><li>KEY - localization key that will be used in the app to get out the translation</li><li>iOS/Android - these columns are not needed but are nice to have to know if a key was added to either platform (when using the same translations for both platforms)</li><li>Action - I use this column to define which platform key/value pair should be exported. If you put in iOS, it will be only generated for *.strings file. If you put in Android, it will be only generated for *.xml file. There is another option to put in SKIP. When you put that into the comment column, this line will not be exported for any platform. This is defined in the config with key `excluded_states`.</li><li>Notes - you can put here some extra notes or explanation.</li><li>English/German - a column per language. In this example, we have translations for English and German. When you want a new language, add a new column, name it and put it in the config file under langs.</li></ul><h3>The magic</h3><p>Now that we have a spreadsheet ready with all the translations, we need one final step. Create localizable files that our apps will consume.</p><p>We need to run the script (below) that will generate files for us. The script should be in the same folder as the config file (or modify the path to the script). Note that config must be set per project. So if you have iOS and Android project, you’ll need to create two configs.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2103c3d4b17939401fea496cd234d2f3/href">https://medium.com/media/2103c3d4b17939401fea496cd234d2f3/href</a></iframe><p>Call ./Localize.sh without params to generate files for iOS platform.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/628/1*jlNwc3zUvTPKoatI9i2ufw.png" /><figcaption>Generated strings files on iOS</figcaption></figure><pre>/* General */<br>&quot;general_success&quot;  = &quot;Success&quot;;<br>&quot;general_continue&quot; = &quot;Continue&quot;;</pre><pre>/* Login */<br>&quot;login_button&quot;     = &quot;iOS only export&quot;;</pre><pre>--------------------------------------------------------------------</pre><pre>/* General */<br>&quot;general_success&quot;  = &quot;Erfolg&quot;;<br>&quot;general_continue&quot; = &quot;Fortsetzen&quot;;</pre><pre>/* Login */<br>&quot;login_button&quot;     = &quot;iOS nur exportieren&quot;;</pre><p>Call .Localize.sh android to generate files for android platform.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/622/1*GDwjAWVl5aXn7pfL-UbN7A.png" /></figure><pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;<br>&lt;resources&gt;</pre><pre>&lt;!-- General --&gt;<br> &lt;string name=&quot;general_success&quot;&gt;Success&lt;/string&gt;<br> &lt;string name=&quot;general_continue&quot;&gt;Continue&lt;/string&gt;</pre><pre>&lt;!-- Login --&gt;<br> &lt;string name=&quot;login_button&quot;&gt;Android only export&lt;/string&gt;<br>&lt;/resources&gt;</pre><pre>--------------------------------------------------------------------</pre><pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;<br>&lt;resources&gt;</pre><pre>&lt;!-- General --&gt;<br> &lt;string name=&quot;general_success&quot;&gt;Erfolg&lt;/string&gt;<br> &lt;string name=&quot;general_continue&quot;&gt;Fortsetzen&lt;/string&gt;</pre><pre>&lt;!-- Login --&gt;<br> &lt;string name=&quot;login_button&quot;&gt;Android nur exportieren&lt;/string&gt;<br>&lt;/resources&gt;</pre><h3>Conclusion</h3><p>I’ve seen a lot of projects doing localization wrong. I don’t say it’s hard, it’s just hard to make it right and easy to manage. It all complicates after you start localizing your app for the second, third or even more languages.</p><p>I’m using this technique in all projects I’ve been working on for a while now. I use this approach even if the app is monolingual. The main reason I’ve decided practicing this is feasibility to change existing translations or add new ones.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=efd07c538601" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Unit testing gesture recognizers]]></title>
            <link>https://medium.com/@borutt/unit-testing-gesture-recognizers-b55444884864?source=rss-a085673da52f------2</link>
            <guid isPermaLink="false">https://medium.com/p/b55444884864</guid>
            <category><![CDATA[uipangesturerecognizer]]></category>
            <category><![CDATA[uikit]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[unit-testing]]></category>
            <category><![CDATA[gesture-recognizer]]></category>
            <dc:creator><![CDATA[Borut]]></dc:creator>
            <pubDate>Tue, 04 Sep 2018 10:27:27 GMT</pubDate>
            <atom:updated>2018-09-04T10:27:27.009Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6eSfpRd_8og5wcUcBsreSg.png" /></figure><p>We all know tested code is an essential path to the successful app. It all starts and ends with the code quality.</p><p>If you have ever wondered how to test UIKit frameworks, you are not alone. It’s not comfortable nor elegant in any way. However, this is the path we need to follow until Apple does the job and make it friendlier and more accessible to test.</p><p>Regardless the words above we can still work around the limitations UIKit impose on us.</p><p>Testing gesture recognizers should be easier if we had a proper public APIs. However, until we do, we’ll need to make an extra effort.</p><h3>What Apple offers us?</h3><p>Let’s take a look at the <a href="https://developer.apple.com/documentation/uikit/uipangesturerecognizer">UIPanGestureRecognizer</a> public API first to see what is available and what is not.</p><pre>open class UIPanGestureRecognizer: UIGestureRecognizer {<br>  open var minimumNumberOfTouches: Int<br>  open var maximumNumberOfTouches: Int<br>  open func translation(in view: UIView?) -&gt; CGPoint<br>  open func setTranslation(_ translation: CGPoint, in view: UIView?)<br>  open func velocity(in view: UIView?) -&gt; CGPoint<br>}</pre><p>UIPanGestureRecognizer directly derives from UIGestureRecognizer which holds many more data like state, allowedTouchTypes, numberOfTouches, etc.</p><p>As you can see from the code above we can get some basic info about the gesture itself. Also, more info available from the base class. However, at this point, you are probably wondering how can we get out the info about the target and action/selector that we need for a successful unit test.</p><h3>Ground work</h3><p>We need to create a subclass of UIPanGestureRecognizer</p><pre>import UIKit</pre><pre>class UIPanGestureRecognizerMock: UIPanGestureRecognizer {<br>  let target: Any?<br>  let action: Selector?<br>  <br>  override init(target: Any?, action: Selector?) {<br>    self.target = target<br>    self.action = action<br>    super.init(target: target, action: action)<br>  }<br>}</pre><p>This is pretty much self-explanatory. We are just overriding designated initializer and exposing target and action we’ll later use in the tests.</p><p>We can also override other properties like velocity or translation so we can have broader control over gesture.</p><pre>var gestureTranslation: CGPoint?<br>var gestureVelocity: CGPoint?</pre><pre>override func translation(in view: UIView?) -&gt; CGPoint {<br>  if let gestureTranslation = gestureTranslation {<br>    return gestureTranslation<br>  }<br>  return super.translation(in: view)<br>}</pre><pre>override func velocity(in view: UIView?) -&gt; CGPoint {<br>  if let gestureVelocity = gestureVelocity {<br>    return gestureVelocity<br>  }<br>  return super.velocity(in: view)<br>}</pre><p>At this point, we can manipulate a gesture recognizer to this point so we can start writing unit tests. Before we do that, let’s set up our base viewController that handles the panning.</p><pre>import UIKit</pre><pre>class PannableViewController: UIViewController {<br>  override func viewDidLoad() {<br>    super.viewDidLoad()<br>    <br>    let action = #selector(panGesture(_:)<br>    let gesture = UIPanGestureRecognizer(target: self,<br>                                         action: action))<br>    view.addGestureRecognizer(panGesture)<br>  }<br>}</pre><pre>private extension PannableViewController {<br>  <a href="http://twitter.com/objc">@objc</a> func panGesture(_ gesture: UIPanGestureRecognizer) {<br>    let translation = gesture.translation(in: view)<br>    // pan gesture handling code goes here<br>  }<br>}</pre><p>Also, a test case class that executes all tests.</p><pre>import XCTest<br><a href="http://twitter.com/testable">@testable</a> import BaseTarget</pre><pre>class PannableViewControllerTests: XCTestCase {<br>  private let vc = PannableViewControllerInjector()<br>  <br>  override func setUp() {<br>    super.setUp()<br>    _ = vc.view<br>  }<br>}</pre><pre>private class PannableViewControllerInjector: PannableViewController {<br>  var gestureRecognizer: UIPanGestureRecognizerMock?<br>  <br>  override func viewDidLoad() {<br>    super.viewDidLoad()<br>    guard let existingGestureRecognizer = view.gestureRecognizers?.first(where: { $0 is UIPanGestureRecognizer }) as? UIPanGestureRecognizer else { return }<br>    view.removeGestureRecognizer(existingGestureRecognizer)<br>    <br>    let action = Selector(&quot;panGesture:&quot;) // an Objective-C selector instead because a caller metod is private<br>    let newGestureRecognizer = UIPanGestureRecognizerMock(target: self, action: action)<br>    view.addGestureRecognizer(newGestureRecognizer)<br>    gestureRecognizer = newGestureRecognizer<br>  }<br>}</pre><p>We have mocked our base view controller so we can take control over the gesture recognizer.</p><p>First, we have to find out the existing gesture recognizer and remove it.</p><p>Second, we create a new gesture recognizer from our mocked class we have prepared earlier and attached it to the view.</p><p>Notice how we defined a Selector? Since we need to access the action method behind a private extension, we need to create an Objective-c selector.</p><p><em>If your action method has any of the less restrictive access modifiers set, you can create Swift Selector instead.</em></p><h3>Let’s test it</h3><p>We are ready to start testing recognizer behavior.</p><pre>func testSetup() {<br>  XCTAssertNotNil(vc.view.gestureRecognizers?.first(where: { $0 is UIPanGestureRecognizerMock }))<br>}<br>  <br>func testPanBegan() {<br>  vc.gestureRecognizer?.pan(location: nil, translation: .zero, state: .began)<br>  XCTAssertEqual(vc.view.frame.minY, 0)<br>}<br>  <br>func testPanDownwards() {<br>  vc.gestureRecognizer?.pan(location: nil, translation: CGPoint(x: 0, y: 200), state: .changed)<br>  XCTAssertEqual(vc.view.frame.minY, 200)<br>}<br>  <br>func testPanEndedShouldResetDueToLowVelocity() {<br>  vc.gestureRecognizer?.gestureVelocity = .init(x: 0, y: 0)<br>  let offset = vc.view.frame.height * vc.minimumScreenRatioToHide + 1<br>  vc.gestureRecognizer?.pan(location: nil, translation: CGPoint(x: 0, y: offset), state: .ended)<br>  XCTAssertEqual(vc.view.frame.minY, 0)<br>}<br><br>func testPanCancelled() {<br>  vc.gestureRecognizer?.pan(location: nil, translation: CGPoint(x: 0, y: 1), state: .cancelled)<br>  XCTAssertEqual(vc.view.frame.minY, 0)<br>}</pre><p>You can see the whole gist <a href="https://gist.github.com/borut-t/9b1b78eb314a4f9b577b252476583083">here</a>.</p><h3>Conclusion</h3><p>I am pretty happy with the result at the end since I can test out our gesture recognizer with confidence. Sure, we only covered pan gesture, but all other gestures can be tested in a similar fashion.</p><p>I still have some hopes and wishes that Apple will bring us some good news in the not so distant future about testability of their frameworks. Until then, I’m good with this.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b55444884864" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>