<?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 Ross Freeman on Medium]]></title>
        <description><![CDATA[Stories by Ross Freeman on Medium]]></description>
        <link>https://medium.com/@ross.freeman?source=rss-3ec3b285fd5b------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*md2GDPwpGsOVBxYwovX05w.jpeg</url>
            <title>Stories by Ross Freeman on Medium</title>
            <link>https://medium.com/@ross.freeman?source=rss-3ec3b285fd5b------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 16:00:44 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@ross.freeman/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[A Deep Dive Into iOS Deep Linking]]></title>
            <link>https://medium.com/ww-tech-blog/a-deep-dive-into-ios-deep-linking-8fd8f4a00f91?source=rss-3ec3b285fd5b------2</link>
            <guid isPermaLink="false">https://medium.com/p/8fd8f4a00f91</guid>
            <category><![CDATA[software-development]]></category>
            <category><![CDATA[ios-app-development]]></category>
            <category><![CDATA[swift]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[deep-linking]]></category>
            <dc:creator><![CDATA[Ross Freeman]]></dc:creator>
            <pubDate>Tue, 11 Feb 2020 13:33:08 GMT</pubDate>
            <atom:updated>2020-02-11T20:54:21.023Z</atom:updated>
            <content:encoded><![CDATA[<p>Deep linking is a powerful way to allow users to interact with your apps. From a simple tap, users can be brought from Safari, Mail, Notes, and other apps right into your app’s content. While this interaction may seem simple on the surface, there is actually a lot going on under the hood that needs to be understood before implementing this feature in your app.</p><h3>Types of Deep Links</h3><p>iOS provides support for 2 different types of deep links: URL schemes and universal links. URL schemes allow you to create your own scheme that only corresponds to actions in your app. For example, you can have something like <strong>myapp://settings</strong> to bring users to the settings page in your app. The big advantage here is that these custom schemes can <em>only</em> be used to open your app, superseding any OS logic for determining if your app should open. However, this is also its biggest disadvantage. If someone doesn’t have your app installed and taps on one of your URL schemes, iOS will display a nasty error saying that it can’t open the URL. There’s no indication that they should download your app to continue. This creates a suboptimal experience for potential would-be users who encountered your link. Additionally, URL schemes are highlighted by Apple as being potentially susceptible to security concerns, so they highly recommend avoiding them, if possible.</p><p>Universal links look just like any regular URL that you know and are used to. For example, it could look something like <a href="https://cmx.weightwatchers.com/nui/my-day."><strong>https://cmx.weightwatchers.com/nui/my-day</strong>.</a> The advantage here is that since this is just a standard URL, iOS will open Safari and navigate to that page if the user doesn’t have your app installed. This is a far better UX than a non-descriptive error message.</p><p>The rest of this article will focus exclusively on universal links, since they are recommended more than URL schemes and contain some complexities that are worth explaining.</p><h3>How Does iOS Know to Open Your App?</h3><p>Before diving into the actual setup process, it’s important to go over how iOS determines to open your app instead of Safari when a universal link is tapped. There is a lot of complexity going on here to provide different experiences, depending on your situation.</p><p>When a user installs your app, iOS checks the app’s manifest for its associated domains (provided by you in Xcode) and specifically checks for domains beginning with <strong>applinks:</strong>, since these indicate a domain for deep linking. iOS will then take each matching domain and hit the <strong>/.well-known/apple-app-site-association </strong>path for those domains. This path should contain a specifically structured JSON file provided by you, which is explained more in-depth later. The JSON file contains information about which paths for that domain are valid deep links. iOS will store these valid paths in a local manifest containing all valid deep links for all installed apps. Note that this process only happens in two instances: when your app is first installed and for each subsequent update. If you decide to update the valid paths, you will need to release a new update to force iOS to re-check that JSON file.</p><p>When any link is tapped from a valid context, iOS will first check its local deep link manifest to see if it matches any known deep links. If a match is found, the URL is routed to that app to handle. Otherwise, the link opens normally in Safari. This process happens quickly behind the scenes and is totally hidden from the user.</p><h3>What is a Valid Context?</h3><p>In my description earlier, I mentioned that iOS will only check its local manifest if a link is tapped in a valid context. So what is a valid context? iOS has logic that determines if a tapped link should be treated as a potential deep link or a regular link. This logic is in place to provide a better UX and use the behavior that makes the most sense to the user.</p><p>iOS will consider a URL as being a potential deep link if it is opened from any non-Safari app. For example, tapping on a link in an email, text message, or note in the Notes app will cause iOS to consider it for deep linking. On the other hand, if a URL is pasted into the navigation bar in Safari, iOS will not consider it for deep linking and just navigate to that page. The only time when iOS will consider a URL for deep linking from Safari is if a user navigates to a different domain by tapping on a link on the current page. For example, if a user is searching Google and taps on a link to <a href="https://weightwatchers.com,/">https://weightwatchers.com,</a> iOS will consider that for deep linking since the domain, weightwatchers.com, is different from the original domain, google.com. Note that this may not work for redirects.</p><h3>Preparing Your App for Deep Linking</h3><p>Now that we have covered how deep links work under the hood, it’s time to actually implement deep linking!</p><p>The first thing you’ll want to do is determine which domains should be considered for deep linking. This may just be your website’s standard domain or subdomain, whichever you prefer. Once you have your list of valid domains, you will need to add them to <strong>Associated Domains</strong> under <strong>Signing &amp; Capabilities</strong> in your Xcode target. You need to prefix all domains with <strong>applink:</strong> to indicate that this associate domain is meant for deep linking.</p><p>The next step is to prepare your Apple App Site Association file, or AASA. As discussed earlier, this is an extremely important file for you to host and is vital to making sure your deep link works properly. Your AASA should look something like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ca7c2167751f2f47b4b5399d82a2c13e/href">https://medium.com/media/ca7c2167751f2f47b4b5399d82a2c13e/href</a></iframe><p>Simply replace the <strong>appID</strong> value with your app’s bundle ID, prefixed with your team ID. The paths array should also contain a list of all valid (and if necessary, invalid) paths.</p><p>You will need to host this file at the <strong>/.well-known/apple-app-site-association </strong>path for each one of your valid domains. Note that even though this is technically a JSON file, the path doesn’t contain the json suffix.</p><h3>Handling Deep Links</h3><p>Now that your app is set up for deep linking, you need to actually handle the deep link. When your app is opened due to a universal link, it will trigger the <strong>application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -&gt; Void) -&gt; Bool </strong>method in your AppDelegate. The first thing you should do is verify that there is in fact a deep link to handle. You should first check that <strong>userActivity.activityType</strong> is <strong>NSUserActivityTypeBrowsingWeb</strong> to make sure that a universal link triggered this method. Then, make sure <strong>userActivity.webpageUrl </strong>exists, since this would be the URL that you need to handle.</p><p>Now that you have access to the URL, you will have to write logic to correctly parse and handle it. While there are a multitude of ways to go about it, one of the cleaner and more efficient ways is to simply use a path tree structure. At WW, we implemented it like this:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/02e7167f81a6bbed01f98bb39a0e4a8d/href">https://medium.com/media/02e7167f81a6bbed01f98bb39a0e4a8d/href</a></iframe><p>This handler provides a few conveniences for us. It provides support for wildcard * characters in URLs, implements logic for reserving certain URLs for valid users only, and has proper error handling. An interesting detail to point out here is the lack of URL host checking. While there was much debate about this, we decided to omit this since we support a number of different domains across different markets and development environments. Checking against every valid host is an additional point of potential failure and we felt that it wasn’t worth handling. The associated domain file already limits the valid domains, so in a way that does the valid domain checking for us. We do, however, still check the URL’s scheme to provide support for URL schemes in addition to universal links.</p><p>With this structure, we can then simply just call the <strong>handleLink</strong> method.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/bf104500bea7bf6a21c5ae7cc86b3de2/href">https://medium.com/media/bf104500bea7bf6a21c5ae7cc86b3de2/href</a></iframe><p>The <strong>canDeepLink</strong> variable simply indicates if the user is a valid subscriber so that the handler can determine if the URL should be handled. If not, it throws the <strong>subscriberRequired</strong> error and stores the user activity object for handling later. When the user logs in, the pending user activity is evaluated and we will attempt to handle the URL again.</p><p>In order to actually register a deep link to handle, we can place the following code anywhere in our app:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f52b1be8a1efb2df3c33d45e4cae0bfb/href">https://medium.com/media/f52b1be8a1efb2df3c33d45e4cae0bfb/href</a></iframe><p>This makes our code clean, simple, and effective. From here, it is just a matter of architecting your app to be able to handle navigation properly. (I won’t cover that here, since it warrants a dedicated article to cover that topic.)</p><p>While your actual deep linking needs may be different from ours at WW, hopefully the information provided here is a good baseline for getting started with your own deep linking implementation. Happy deep linking!</p><blockquote><em>— Ross Freeman, iOS Engineer at WW (formerly Weight Watchers)</em></blockquote><p><strong>Interested in joining the WW team? </strong>Check out the <a href="https://www.weightwatchers.com/us/corporate-careers?utm_medium=partnerwebsite&amp;utm_source=wwtechblogmedium&amp;utm_campaign=wwtechblogmedium&amp;utm_content=wwcareerspage">careers page</a> to view technology job listings as well as open positions on other teams.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=8fd8f4a00f91" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ww-tech-blog/a-deep-dive-into-ios-deep-linking-8fd8f4a00f91">A Deep Dive Into iOS Deep Linking</a> was originally published in <a href="https://medium.com/ww-tech-blog">WW Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Architecting Siri Shortcuts in a Multi-Pod Project]]></title>
            <link>https://medium.com/ww-tech-blog/architecting-siri-shortcuts-in-a-multi-pod-project-10c68ed212ac?source=rss-3ec3b285fd5b------2</link>
            <guid isPermaLink="false">https://medium.com/p/10c68ed212ac</guid>
            <category><![CDATA[cocoapods]]></category>
            <category><![CDATA[ios]]></category>
            <category><![CDATA[siri]]></category>
            <category><![CDATA[swift]]></category>
            <dc:creator><![CDATA[Ross Freeman]]></dc:creator>
            <pubDate>Tue, 05 Nov 2019 18:27:59 GMT</pubDate>
            <atom:updated>2019-11-21T16:55:44.156Z</atom:updated>
            <content:encoded><![CDATA[<p>The WW iOS app is a massive project that encompasses a number of different features for our members. In order to help us organize our codebase into logical components, we operate with a multi-pod setup that is relatively popular in the development community. While this has helped us with code organization, it also introduced some interesting challenges along the way. When we started building our Siri Shortcuts implementation last year, we ran into some major architectural problems that every multi-pod project experiences at some point. We created a solution that’s both simple and scalable for the future.</p><h3>Basic Siri Shortcuts Architecture</h3><p>Siri Shortcuts acts as an extension to your iOS app, much like an Apple Watch app (at least pre-watchOS 6). This means that the files for it reside in the base Xcode project with your app’s primary target. The actual extension contains a number of standard files, but one of the most important is <strong>Intents.intentdefinition</strong>, which is where you define your shortcuts and configure some basic behaviors. By default, the shortcut intents you define here are available in your primary app as well, allowing you to donate them and provide a UI for users to setup a shortcut. Integration between your main app and your extension is relatively seamless and any new shortcuts you define will automatically be available in your main app target.</p><h3>But That Seems Too Easy!</h3><p>Well, that <em>is</em> too easy, at least for our project setup. In a multi-pod project, your dependency pods are unable to access any classes or objects defined in your main app. That also means your dependencies have no notion of the shortcuts you defined. Depending on how your app is architected, this might not actually be an issue for you. But if your dependencies need to access your shortcuts in any way, such as for donation or setup, you’ll have to do some extra work to get them to play nicely with each other.</p><h3>Problem, Meet Solution</h3><p>In order to handle this problem in an elegant way, we decided to use the good old method of dependency inversion. We defined three simple types in our foundational pod that all other pods depend on: <strong>ShortcutType</strong>, <strong>IntentDelegate</strong>, and <strong>IntentManager</strong> (these implementations are shown below).</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/291889503c6ad72403dec4cc1aeb0035/href">https://medium.com/media/291889503c6ad72403dec4cc1aeb0035/href</a></iframe><p>Let’s break this down a bit. <strong>IntentDelegate </strong>is a simple protocol that defines methods to perform all of the actions surrounding Siri Shortcuts that our dependencies might have to interact with. <strong>IntentManager</strong> is a caseless enum that contains an <strong>IntentDelegate</strong>, which is the object that our dependencies access. In our main project, we create a type (not shown here) that conforms to <strong>IntentDelegate</strong>. This type is then set as <strong>IntentManager</strong>’s <strong>IntentDelegate</strong> at some point during our app’s initialization sequence. This makes it so that if we want to donate an intent in one of our dependencies, we simply do the following:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/72b756ae24f6052570b9c037a9ff86dd/href">https://medium.com/media/72b756ae24f6052570b9c037a9ff86dd/href</a></iframe><h3>Why iOS 13.1?</h3><p>You might be wondering why everything is marked as only available for iOS 13.1+. After all, Siri Shortcuts <em>was </em>introduced with iOS 12. The reasoning here is because there were massive upgrades to how Siri Shortcuts function in iOS 13 to support parameters. While this code can be modified to provide backwards compatibility with iOS 12, it was excluded here for simplicity’s sake.</p><p>As far as why the minimum target is iOS 13.1, Apple released iOS 13.0 with a number of major new features excluded. One of them includes conversational shortcuts, which is central to how Siri Shortcuts now operate. Running any Siri Shortcut in iOS 13.0 will cause the Shortcuts app to open rather than having Siri respond as normal. Due to the suboptimal experience, we found it better to simply disable this feature for users running iOS 13.0.</p><h3>Adding Localizations</h3><p>At WW we currently support 13 different locales, which means 13 different translations for each user displaying string we create. While Siri Shortcuts fully support localizations, Xcode doesn’t make it easy to supply those translations.</p><p>Unlike most other places in apps where you provide your own key to localize, Xcode actually generates the keys for you for all Siri Shortcuts strings. This means that all keys will be seemingly random and look something like 0xCtgS, which provides basically no information about the usage of that string. To make matters worse, any new strings and keys generated after you generate the localizable Intents.strings files will <strong><em>not </em></strong>automatically be added to the localizable list. This means you’re required to go through and manually track down any new key-value pair that isn’t in the localizable file.</p><p>Thankfully, Xcode provides a little bit of relief here. If you select your main project file in the Xcode editor, you can select Editor -&gt; Export for Localization. This will create a new folder containing everything you need for localizing your strings for each locale. The main file you should look for is the .xliff file for each locale. This contains every Siri Shortcuts string you need along with its associated key and the original text it corresponds to. That way, all you have to do is update the files with your translations and import them back into Xcode. It’s not the most ideal way to deal with it, but it’s much better than spending hours scavenging for new keys in a plist!</p><h3>Building Up from Here</h3><p>If the recent changes between iOS 12 and 13 show us anything, it’s that Siri Shortcuts is an ever-growing and changing feature that developers will need to readily adapt to in the future. While the infrastructure described here might inevitably break with future updates, it is hopefully resilient enough to only require minor changes.</p><blockquote><em>— </em>Ross Freeman, iOS Engineer, WW</blockquote><p><strong>Interested in joining the WW team? </strong>Check out the <a href="https://www.weightwatchers.com/us/corporate-careers?utm_medium=partnerwebsite&amp;utm_source=wwtechblogmedium&amp;utm_campaign=wwtechblogmedium&amp;utm_content=wwcareerspage">careers page</a> to view technology job listings as well as open positions on other teams.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=10c68ed212ac" width="1" height="1" alt=""><hr><p><a href="https://medium.com/ww-tech-blog/architecting-siri-shortcuts-in-a-multi-pod-project-10c68ed212ac">Architecting Siri Shortcuts in a Multi-Pod Project</a> was originally published in <a href="https://medium.com/ww-tech-blog">WW Tech Blog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>