<?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:sy="http://purl.org/rss/1.0/modules/syndication/">
	<channel>
		<title>Author at Infinum</title>
		<atom:link href="https://infinum.com/blog/author/zvonimir-medak/feed/" rel="self" type="application/rss+xml" />
		<link></link>
		<description>Building digital products</description>
		<lastBuildDate>Wed, 08 Apr 2026 14:17:14 +0000</lastBuildDate>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>

					<item>
				<image>
					<url>49305https://infinum.com/uploads/2024/01/Help_your_QA_help_you-blogpost-hero.webp</url>
				</image>
				<title>Meet Sentinel, a Library Loved by Developers and Testers Alike</title>
				<link>https://infinum.com/blog/sentinel-develeper-qa-library/</link>
				<pubDate>Wed, 17 Jan 2024 12:48:18 +0000</pubDate>
				<dc:creator>Zvonimir Medak</dc:creator>
				<guid isPermaLink="false">https://infinum.com/?p=49305</guid>
				<description>
					<![CDATA[<p>Our open-source library packs a ton of useful features that make testing, debugging, and life in general much easier. </p>
<p>The post <a href="https://infinum.com/blog/sentinel-develeper-qa-library/">Meet Sentinel, a Library Loved by Developers and Testers Alike</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</description>
				<content:encoded>
					<![CDATA[<div
	class="wrapper"
	data-id="es-252"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-92">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-95"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-93">
	<p	class='typography typography--size-36-text js-typography block-paragraph__paragraph'
	data-id='es-94'
	>
	<strong>Our open-source library packs a ton of useful features that make testing, debugging, and life in general much easier.</strong> </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-98"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-96">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-97'
	>
	Have you ever been stuck in a loop where you have to rebuild your app constantly just so you can change a feature flag? What if you wanted to change your location or the base URL while using the app? You’re in for some more rebuilding.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-101"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-99">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-100'
	>
	And what about untraceable UI bugs or elusive network issues in your app? Don&#8217;t count on &#8220;It works on my machine!&#8221; That ship has sailed a long time ago.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-104"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-102">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-103'
	>
	Imagine a tool that would help you easily tackle the issues above and more. One that would provide you with network logs and useful information that allows you to quickly pinpoint what went wrong.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-107"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-105">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-106'
	>
	Meet Sentinel – an open-source library that will help you integrate these features with ease, allowing you and your QA colleague to find the root cause of the issues you’re experiencing.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-110"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-108">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-109'
	>
	<em>Note: This article focuses on using <a href="https://github.com/infinum/ios-sentinel" target="_blank" rel="noreferrer noopener">Sentinel in iOS</a>, but there is also an <a href="https://github.com/infinum/android-sentinel" target="_blank" rel="noreferrer noopener">Android version</a> available on GitHub. </em></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-113"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-111">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-112'
	>
	Multiple QA debugging tools at your disposal</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-116"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-114">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-115'
	>
	Let’s start with some basic info. As mentioned, Sentinel is an open-source library that we sponsor and maintain. I’ll reference a part of the readme file from the GitHub repository here:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-119"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-117">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-118'
	>
	“Sentinel is a simple library that gives developers the possibility to configure one entry point for every debug tool. The idea of Sentinel is to give the developers the ability to configure a screen with multiple debug tools, which are available via some event (e.g., shake, notification).”</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-122"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-120">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-121'
	>
	It’s important to note here that the library states a single entry point for <strong>every</strong> <strong>debug </strong>tool. This indicates that <strong>it</strong> <strong>must never end up in a production environment</strong>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-125"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-123">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-124'
	>
	Sentinel aims to give developers an easy way to integrate any number of tools that can help them adjust the application data without the need to rebuild it. That is precisely the reason why it shouldn’t be exposed in production environments.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-128"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-126">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-127'
	>
	Simple to implement, powerful when used </h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-131"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-129">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-130'
	>
	There are a number of benefits to using the Sentinel library. First of all, it is so easy to implement and use that the learning curve is not even a curve – it’s practically just the X-axis. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-133"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-132'
	>
	<strong>Sentinel can change the data in your application without Xcode and without needing to rebuild it. It also provides device and application information and shows you how the app is performing in its performance tab.</strong></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-136"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-134">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-135'
	>
	Another major benefit is that Sentinel can contain any number of debug tools, which means that you can implement your own tools, tailored to your needs.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-139"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-137">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-138'
	>
	For example, you have a logging screen that opens by shaking the device, and you have a report-a-bug feature that is triggered by using a screenshot on debug builds. That’s already two different inputs for opening a debugging tool. What if you wanted to add a database export debugging feature? How would you do that without overwhelming the tester with a new button combination? Sentinel comes in very handy here.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-142"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-140">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-141'
	>
	Finally, mastering Sentinel<strong> </strong>has a learning curve that isn’t much steeper than the one for learning to implement it. Once you’ve mastered it, you can think of new ideas to help yourself and your testing team make your app more bulletproof and easy to test out.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-145"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-143">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-144'
	>
	A lifesaver for handling multiple Bluetooth connections and network requests</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-148"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-146">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-147'
	>
	To use an example from my own experience, there’s an IoT project I work on where we use Sentinel to a great extent. The project involves a lot (and I mean a lot!) of Bluetooth communication and network requests.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-151"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-149">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-150'
	>
	Utilizing Sentinel’s ability to log every Bluetooth command and every network request, we can actually see what we sent and if the request was successful or not. Besides helping us developers, this also saves our QA engineer’s time. Once we release a build, they check the feature we implemented by examining the logs to see if we’re sending the right commands. That way, the QA engineer can also indicate if there’s an issue, and whether it’s a firmware or an app issue.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-154"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-152">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-153'
	>
	Another example is getting a bug report with all the logs attached. Most of the time, we can pinpoint where the issue is happening just by using the elaborate logging integrated within the app.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-157"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-155">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-156'
	>
	Implementing Sentinel in 3 minutes or less</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-160"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-158">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-159'
	>
	You can add Sentinel to your project via the Swift Package Manager (SPM) or CocoaPods. This example will demonstrate how to add it via SPM.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-163"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-161">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-162'
	>
	Open your project in Xcode → Files → Add package dependencies… → Search for Sentinel or use <a href="https://github.com/infinum/ios-sentinel.git" target="_blank" rel="noreferrer noopener">this link</a> → Add your project → Add package. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-166"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-164">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-165'
	>
	This will prompt you to add <a href="https://github.com/infinum/ios-loggie" target="_blank" rel="noreferrer noopener">Loggie</a> and <a href="https://github.com/infinum/ios-collar" target="_blank" rel="noreferrer noopener">Collar</a> to your project too. Both of these are optional, but if you want a simple logging tool, you can use Loggie, and if you want an analytics tool that gives you a visual representation of your events, you can add Collar. After picking the right tools for the job, this is how you set up Sentinel in your app: </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-168"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-swift github-light" data-language="swift" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">class AppDelegate</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">NSObject</span><span class="token">, </span><span class="token" style="color: #6f42c1;">UIApplicationDelegate </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">var</span><span class="token"> shouldUseAnalytics </span><span class="token" style="color: #d73a49;">= fal</span><span class="token" style="color: #005cc5;">se</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">func application</span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">_ application</span><span class="token">: </span><span class="token">UIApplication</span><span class="token">,
</span></span><span class="line"><span class="token">        </span><span class="token">didFinishLaunchingWithOptions launchOptions</span><span class="token">: </span><span class="token">[</span><span class="token">UIApplication.LaunchOptionsKey </span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #005cc5;">Any</span><span class="token">]</span><span class="token" style="color: #d73a49;">?</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #005cc5;">nil</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">-&gt;</span><span class="token"> </span><span class="token" style="color: #005cc5;">Bool</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">setupSentinel(</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #d73a49;">return</span><span class="token"> </span><span class="token" style="color: #005cc5;">true</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token" style="color: #d73a49;">private</span><span class="token"> </span><span class="token" style="color: #d73a49;">extension</span><span class="token"> </span><span class="token" style="color: #6f42c1;">AppDelegate</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">func setupSentinel</span><span class="token">(</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #d73a49;">let</span><span class="token"> configuration </span><span class="token" style="color: #d73a49;">= Sentine</span><span class="token">l.</span><span class="token">Configuration(</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">trigger:</span><span class="token"> Triggers.</span><span class="token">shake</span><span class="token">,
</span></span><span class="line"><span class="token">            </span><span class="token">tools:</span><span class="token"> [
</span></span><span class="line"><span class="token">                </span><span class="token">UserDefaultsTool(</span><span class="token">)</span><span class="token">,
</span></span><span class="line"><span class="token">                </span><span class="token">CustomLocationTool(</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">],
</span></span><span class="line"><span class="token">            </span><span class="token">preferences:</span><span class="token"> [
</span></span><span class="line"><span class="token">                .</span><span class="token" style="color: #d73a49;">init</span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">                    </span><span class="token">name:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">Analytics</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,
</span></span><span class="line"><span class="token">                    </span><span class="token">setter:</span><span class="token"> </span><span class="token">{</span><span class="token"> [</span><span class="token" style="color: #d73a49;">unowned</span><span class="token"> </span><span class="token" style="color: #005cc5;">self</span><span class="token">] </span><span class="token" style="color: #d73a49;">in</span><span class="token">  shouldUseAnalytics </span><span class="token" style="color: #d73a49;">= $0 },
</span></span><span class="line"><span class="token">                    </span><span class="token">getter:</span><span class="token"> </span><span class="token">{</span><span class="token"> [</span><span class="token" style="color: #d73a49;">unowned</span><span class="token"> </span><span class="token" style="color: #005cc5;">self</span><span class="token">] </span><span class="token" style="color: #d73a49;">in</span><span class="token"> shouldUseAnalytics </span><span class="token">}</span><span class="token">,
</span></span><span class="line"><span class="token">                    </span><span class="token">userDefaultsKey:</span><span class="token"> </span><span class="token" style="color: #005cc5;">nil</span><span class="token">
</span></span><span class="line"><span class="token">                </span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">]
</span></span><span class="line"><span class="token">        </span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">        Sentinel.</span><span class="token">shared</span><span class="token">.</span><span class="token">setup(</span><span class="token">with:</span><span class="token"> configuration</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-171"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-169">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-170'
	>
	With these 18 lines of code in the <code>setupSentinel()</code> function, we have successfully set up Sentinel,<strong> </strong>and the only thing left to do is to call it in the <code>didFinishLaunchingWithOptions</code> function. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-174"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-172">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-173'
	>
	We had to create the <code>Configuration</code> object, which is exposed by the library. The configuration takes in a trigger that will tell your application how you want to open Sentinel. Currently, there are three options – screenshot, shake, and notification.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-177"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-175">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-176'
	>
	The tools parameter takes in an array of <strong>Tool </strong>objects, which represent all the tools that are available once you launch Sentinel. The preferences array lists all the switch options for your feature flags or other boolean values you want the user to be able to change without needing to rebuild the application. These can also be saved in <strong>UserDefaults, </strong>hence the <code>userDefaultsKey</code> parameter. The last and most important thing you have to do is set up Sentinel with your configuration.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-180"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-178">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-179'
	>
	Using this code, we have set up Sentinel in two minutes and exposed two custom tools – one for changing your location and the other one to read all the UserDefault values your app is storing.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-183"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-181">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-182'
	>
	Go above and beyond with custom tools</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-186"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-184">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-185'
	>
	As shown above, setting up Sentinel takes no more than a couple of minutes. You might expect there’s some sort of a catch with custom tools, but in this case – it’s not the case. Designing and customizing tools is just as simple. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-189"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-187">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-188'
	>
	How tools are designed</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-192"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-190">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-191'
	>
	Debugging tools in Sentinel should be designed in a KISS manner. One debugging tool should only be used for one purpose. If we create a tool that inspects a database, then it shouldn’t be able to also inspect UserDefaults.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-195"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-193">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-194'
	>
	Before you start creating your own custom tools, you should check out the ones provided by the Sentinel library:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-199"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--infinum block-bullet__bullet" data-id="es-196">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<p	class='typography typography--size-20-text js-typography bullet__heading'
	data-id='es-197'
	>
	UserDefaultsTool</p><p	class='typography typography--size-20-text-roman js-typography bullet__paragraph'
	data-id='es-198'
	>
	Gives you an overview of the values stored in the UserDefaults.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-203"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--infinum block-bullet__bullet" data-id="es-200">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<p	class='typography typography--size-20-text js-typography bullet__heading'
	data-id='es-201'
	>
	TextEditingTool</p><p	class='typography typography--size-20-text-roman js-typography bullet__paragraph'
	data-id='es-202'
	>
	Gives you an interface where you can write and store a new value to a predefined variable.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-207"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--infinum block-bullet__bullet" data-id="es-204">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<p	class='typography typography--size-20-text js-typography bullet__heading'
	data-id='es-205'
	>
	CustomLocationTool</p><p	class='typography typography--size-20-text-roman js-typography bullet__paragraph'
	data-id='es-206'
	>
	Gives you the ability to change the current user location. Keep in mind that you have to restart the app to apply the location.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-211"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--infinum block-bullet__bullet" data-id="es-208">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<p	class='typography typography--size-20-text js-typography bullet__heading'
	data-id='es-209'
	>
	CustomInfoTool</p><p	class='typography typography--size-20-text-roman js-typography bullet__paragraph'
	data-id='es-210'
	>
	Gives you an interface where you can specify some items with a name and a value.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-215"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="bullet bullet--left bullet__type--dot bullet__color--infinum block-bullet__bullet" data-id="es-212">
			<div class="bullet__dot"></div>
		<div class="bullet__content">
		<p	class='typography typography--size-20-text js-typography bullet__heading'
	data-id='es-213'
	>
	Collar and Loggie tools</p><p	class='typography typography--size-20-text-roman js-typography bullet__paragraph'
	data-id='es-214'
	>
	If you are using these, you can also use their respective tools.</p>	</div>
</div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-218"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-216">
	<h3	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-217'
	>
	How to create your own custom tool</h3></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-221"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-219">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-220'
	>
	Let’s say we love the <code>UserDefaultsTool</code><em>,</em> which is provided by Sentinel, but it’s missing something we need. In our case, the ability to change the value. We can easily create a new custom tool and import it to our application.<br />
<br />
In this example, we’ll create a simple custom tool that can change a <code>UserDefaults</code> key for a system image and update the image afterward by pressing a button. To achieve this, we first need to create a <code>Constants</code> enum where we’ll save all the values we want to store in the <code>UserDefaults</code>.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-223"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-swift github-light" data-language="swift" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">enum UserDefaultsConstants</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">String </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">case</span><span class="token"> </span><span class="token">imageName
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-226"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-224">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-225'
	>
	In this case, we only have one value, but your app might require more. It’s always a good idea to extract constants like these into enums so you can namespace them easily. You potentially might want to add a <code>UserDefaultsResetTool</code><em>. </em>That way, you can easily conform to <code>CaseIterable</code><em> </em>and reset every single <code>UserDefault</code> value you added.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-229"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-227">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-228'
	>
	Let’s get back to our example.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-232"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-230">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-231'
	>
	Since I created this example project with Xcode 15, I got a SwiftUI view, and we’ll have to update it a bit.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-234"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-swift github-light" data-language="swift" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">import</span><span class="token"> </span><span class="token" style="color: #6f42c1;">SwiftUI</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">struct ContentView</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">View </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">@AppStorage</span><span class="token">(</span><span class="token">UserDefaultsConstants.</span><span class="token">imageName</span><span class="token">.</span><span class="token" style="color: #005cc5;">rawValue</span><span class="token">)</span><span class="token"> </span><span class="token">var imageSystemName:</span><span class="token"> </span><span class="token" style="color: #005cc5;">String</span><span class="token"> </span><span class="token" style="color: #d73a49;">= &quot;gl</span><span class="token" style="color: #032f62;">obe</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">var body:</span><span class="token"> </span><span class="token" style="color: #d73a49;">some</span><span class="token"> View </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">VStack</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">Image(</span><span class="token">systemName:</span><span class="token"> imageSystemName.</span><span class="token" style="color: #005cc5;">lowercased</span><span class="token">(</span><span class="token">)</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">                .</span><span class="token">imageScale(</span><span class="token">.</span><span class="token">large</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">                .</span><span class="token">foregroundStyle(</span><span class="token">.</span><span class="token">tint</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">Text(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">Hello, world!</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">        .</span><span class="token">padding(</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-237"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-235">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-236'
	>
	In this case, we added an AppStorage variable which will fetch the value from the <strong>UserDefaults</strong>, and update the view if it gets changed.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-239"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-swift github-light" data-language="swift" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">import</span><span class="token"> </span><span class="token" style="color: #6f42c1;">Sentinel</span><span class="token">
</span></span><span class="line"><span class="token" style="color: #d73a49;">import</span><span class="token"> </span><span class="token" style="color: #6f42c1;">UIKit</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token" style="color: #d73a49;">final</span><span class="token"> </span><span class="token">class SystemImageNameChangerTool</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">Tool </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">let name:</span><span class="token"> </span><span class="token" style="color: #005cc5;">String</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">private</span><span class="token"> </span><span class="token">let userDefaults:</span><span class="token"> UserDefaults</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">private</span><span class="token"> </span><span class="token">var systemImageName:</span><span class="token"> </span><span class="token" style="color: #005cc5;">String</span><span class="token"> </span><span class="token" style="color: #d73a49;">= &quot;gl</span><span class="token" style="color: #032f62;">obe</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">init(nam</span><span class="token">e</span><span class="token">: </span><span class="token" style="color: #005cc5;">String</span><span class="token"> </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">System Image Name Changer</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">, </span><span class="token">userDefaults</span><span class="token">: </span><span class="token">UserDefaults </span><span class="token" style="color: #d73a49;">=</span><span class="token"> .</span><span class="token">standard</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #005cc5;">self</span><span class="token">.</span><span class="token">name</span><span class="token"> </span><span class="token" style="color: #d73a49;">= name
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #005cc5;">self</span><span class="token">.</span><span class="token">userDefaults</span><span class="token"> </span><span class="token" style="color: #d73a49;">= userDef</span><span class="token">aults
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">func presentPreview</span><span class="token">(</span><span class="token">from viewController</span><span class="token">: </span><span class="token">UIViewController</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">TextEditingTool(</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">name:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">System Image Name</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,
</span></span><span class="line"><span class="token">            </span><span class="token">setter:</span><span class="token"> </span><span class="token">{</span><span class="token"> [</span><span class="token" style="color: #d73a49;">unowned</span><span class="token"> </span><span class="token" style="color: #005cc5;">self</span><span class="token">] </span><span class="token" style="color: #d73a49;">in</span><span class="token"> systemImageName </span><span class="token" style="color: #d73a49;">= $0 },
</span></span><span class="line"><span class="token">            </span><span class="token">getter:</span><span class="token"> </span><span class="token">{</span><span class="token"> [</span><span class="token" style="color: #d73a49;">unowned</span><span class="token"> </span><span class="token" style="color: #005cc5;">self</span><span class="token">] </span><span class="token" style="color: #d73a49;">in</span><span class="token"> systemImageName </span><span class="token">}</span><span class="token">,
</span></span><span class="line"><span class="token">            </span><span class="token">userDefaults:</span><span class="token"> .</span><span class="token">standard</span><span class="token">,
</span></span><span class="line"><span class="token">            </span><span class="token">userDefaultsKey:</span><span class="token"> UserDefaultsConstants.</span><span class="token">imageName</span><span class="token">.</span><span class="token" style="color: #005cc5;">rawValue</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">        .</span><span class="token">presentPreview(</span><span class="token">from:</span><span class="token"> viewController</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-242"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-240">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-241'
	>
	It allows you to inject your <code>UserDefaults</code><em>,</em> in which you’ll want to store the image name. In this simple example, I used TextEditingTool, which is provided by Sentinel, but if you need more complexity in your own custom tools, feel free to create your own solutions.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-245"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-243">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-244'
	>
	When creating your own custom <strong>Tool</strong> to integrate into the Sentinel configuration, you’ll need to conform to the <strong>Tool</strong> protocol. The protocol states that your custom tool has to implement a <strong>name, </strong>which is displayed in the list of your tools, and that it has to have a function <code>presentPreview</code> to handle the navigation. It’s as simple as that. Last but not least, we have to add our tool to the Sentinel configuration. So let’s update our <code>setupSentinel</code> function:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-247"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-swift github-light" data-language="swift" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">func setupSentinel</span><span class="token">(</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">let</span><span class="token"> configuration </span><span class="token" style="color: #d73a49;">=</span><span class="token"> Sentinel.</span><span class="token">Configuration(</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">trigger:</span><span class="token"> Triggers.</span><span class="token">shake</span><span class="token">,
</span></span><span class="line"><span class="token">        </span><span class="token">tools:</span><span class="token"> [
</span></span><span class="line"><span class="token">            </span><span class="token">UserDefaultsTool(</span><span class="token">)</span><span class="token">,
</span></span><span class="line"><span class="token">            </span><span class="token">CustomLocationTool(</span><span class="token">)</span><span class="token">,
</span></span><span class="line"><span class="token">            </span><span class="token">SystemImageNameChangerTool(</span><span class="token">userDefaults:</span><span class="token"> .</span><span class="token">standard</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">],
</span></span><span class="line"><span class="token">        </span><span class="token">preferences:</span><span class="token"> [
</span></span><span class="line"><span class="token">            .</span><span class="token" style="color: #d73a49;">init</span><span class="token">(</span><span class="token">
</span></span><span class="line"><span class="token">                </span><span class="token">name:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">Analytics</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,
</span></span><span class="line"><span class="token">                </span><span class="token">setter:</span><span class="token"> </span><span class="token">{</span><span class="token"> [</span><span class="token" style="color: #d73a49;">unowned</span><span class="token"> </span><span class="token" style="color: #005cc5;">self</span><span class="token">] </span><span class="token" style="color: #d73a49;">in</span><span class="token">  shouldUseAnalytics </span><span class="token" style="color: #d73a49;">= </span><span class="token" style="color: #005cc5;">$0</span><span class="token"> </span><span class="token">}</span><span class="token">,
</span></span><span class="line"><span class="token">                </span><span class="token">getter:</span><span class="token"> </span><span class="token">{</span><span class="token"> [</span><span class="token" style="color: #d73a49;">unowned</span><span class="token"> </span><span class="token" style="color: #005cc5;">self</span><span class="token">] </span><span class="token" style="color: #d73a49;">in</span><span class="token"> shouldUseAnalytics </span><span class="token">}</span><span class="token">,
</span></span><span class="line"><span class="token">                </span><span class="token">userDefaultsKey:</span><span class="token"> </span><span class="token" style="color: #005cc5;">nil</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">]
</span></span><span class="line"><span class="token">    </span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">    Sentinel.</span><span class="token">shared</span><span class="token">.</span><span class="token">setup(</span><span class="token">with:</span><span class="token"> configuration</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-250"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-248">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-249'
	>
	If we run the application now, we can shake the device and see our tool in action.</p></div>	</div>
</div>
</div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-255"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-253"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-254">
	<picture class="image__picture block-media__image-picture">
												<img
					src="https://infinum.com/uploads/2024/01/sentinelexample-1.gif"
					class="image__img block-media__image-img"
					alt=""
										height="798"
															width="386"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>		</div>
	</div>

<div
	class="wrapper"
	data-id="es-284"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-259">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-258"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-256">
	</div>	</div>
</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-262"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-260">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-261'
	>
	Why is Sentinel an efficient testing and debugging tool?</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-265"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-263">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-264'
	>
	Besides being useful for developers, Sentinel is also an efficient debugging tool. It allows both developers and testers to view some information not so easily accessible otherwise. If you’re a bit more adventurous, you can even add a functionality that updates the information from the tool – only your creativity is the limit. You can create mocking tools to help you easily recreate a situation without having to rebuild the app or even use Xcode and its breakpoints.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-268"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-266">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-267'
	>
	All of this can significantly improve collaboration and efficiency on a project, not to mention make everyone’s lives easier.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-270"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-269'
	>
	<strong>Your tester colleagues will highly appreciate every tool you create. They can even propose new ones since they probably have their own pain points they would love to see resolved. </strong></p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-273"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-271">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-272'
	>
	As I’ve been preaching, it helps out everyone included in the testing process in various scenarios, and the QA engineer will always be able to report bugs with a better understanding of <em>why</em> something happened. That way, you’ll also be able to fix those bugs with ease.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-276"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-274">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-275'
	>
	Always keep your guard up</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-279"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-277">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-278'
	>
	From its simple implementation to many possibilities offered through custom tool integration, Sentinel is a versatile open-source library that can make both developers’ and testers’ lives easier. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-282"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-280">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-281'
	>
	If you liked the pack of features presented in this article, be sure to head over to <a href="https://github.com/infinum/ios-sentinel" target="_blank" rel="noreferrer noopener">our GitHub page</a> and get that guard to your own front door.</p></div>	</div>
</div>
</div>		</div>
	</div><p>The post <a href="https://infinum.com/blog/sentinel-develeper-qa-library/">Meet Sentinel, a Library Loved by Developers and Testers Alike</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</content:encoded>
			</item>
					<item>
				<image>
					<url>28922https://infinum.com/uploads/2022/10/Live-Activities.webp</url>
				</image>
				<title>Make Your iOS Apps Come Alive with Live Activities</title>
				<link>https://infinum.com/blog/live-activities-in-ios-apps/</link>
				<pubDate>Tue, 18 Oct 2022 09:52:06 +0000</pubDate>
				<dc:creator>Zvonimir Medak</dc:creator>
				<guid isPermaLink="false">https://infinum.com/?p=28922</guid>
				<description>
					<![CDATA[<p>Live Activities are supported for all phones that can run iOS 16.1, so find out what’s required to integrate them into your iOS apps. </p>
<p>The post <a href="https://infinum.com/blog/live-activities-in-ios-apps/">Make Your iOS Apps Come Alive with Live Activities</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</description>
				<content:encoded>
					<![CDATA[<div
	class="wrapper"
	data-id="es-509"
	 data-animation-target='inner-items'>
		
			<div class="wrapper__inner">
			<div class="block-blog-content js-block-blog-content">
	
<div class="block-blog-content-sidebar" data-id="es-285">
	</div>

<div class="block-blog-content-main">
	
<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-288"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-286">
	<p	class='typography typography--size-14-text-roman js-typography block-paragraph__paragraph'
	data-id='es-287'
	>
	SUMMARY</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-291"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-289">
	<p	class='typography typography--size-36-text js-typography block-paragraph__paragraph'
	data-id='es-290'
	>
	While Android devices have generally gone in the direction of having just a camera cutout or even an under-the-screen camera, Apple has created a brand new user experience with the new camera housing – the notch. Here&#8217;s how to implement it in iOS.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-294"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-292">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-293'
	>
	Apple introduced widgets in iOS 14, allowing us to have glanceable information on our home screens. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-297"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-295">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-296'
	>
	But what if we went a step further and provided contextually relevant information that pops up when needed and doesn’t overstay its welcome? And what if that was done in a way that works seamlessly with the biggest front-facing update our iPhones have seen since the introduction of the notch? No more what ifs – Meet Dynamic Island.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-300"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-298"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-299">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2022/10/Asset-1-1400x588.webp				media='(max-width: 699px)'
				type=image/webp								height="588"
												width="1400"
				 />
								
			<source
				srcset=https://infinum.com/uploads/2022/10/Asset-1-2400x1008.webp				media='(max-width: 1199px)'
				type=image/webp								height="1008"
												width="2400"
				 />
												<img
					src="https://infinum.com/uploads/2022/10/Asset-1.webp"
					class="image__img block-media__image-img"
					alt=""
										height="1150"
															width="2738"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-303"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-301">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-302'
	>
	We’ve covered our feelings about the <a href="https://infinum.com/blog/start-designing-for-dynamic-island-and-live-activities/">Dynamic Island and live activities from the design perspective</a>, and now is the time to show you how to implement them.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-306"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-304">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-305'
	>
	iPhone 14 Pro and 14 Pro Max are the only devices that support the Dynamic Island, but Live Activities are supported for all phones that can run iOS 16.1. So let’s see what’s needed to integrate them into your applications. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-309"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-307">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-308'
	>
	The Live Activity lifecycle, explained</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-312"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-310">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-311'
	>
	Lifecycle is one of the most important things to remember when creating apps. Live Activity can be in three states:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-315"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-313">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-314'
	>
	<li>Not-started</li><li>Running</li><li>Ended</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-318"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-316">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-317'
	>
	Your application can have multiple live activities at once, so it’s important to keep track of every activity. Before starting an activity, you must specify what data it needs to use. Since we’re talking about a Live Activity, you can guess that there will be some data that will change over time. Your Live Activity data will be separated into dynamic and static data. Dynamic data can be updated over time and static can’t. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-321"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-319">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-320'
	>
	For example, we’ll take a look at the <strong>FormulaAttributes</strong> structure which will contain both types of data.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-323"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-swift github-light" data-language="swift" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">struct FormulaAttributes</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">ActivityAttributes </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">public</span><span class="token"> </span><span class="token">typealias RaceState </span><span class="token" style="color: #d73a49;">=</span><span class="token"> </span><span class="token">ContentState</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> Here you will provide the dynamic data</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">public</span><span class="token"> </span><span class="token">struct ContentState</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">Codable</span><span class="token">, </span><span class="token" style="color: #6f42c1;">Hashable </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">var driverInFront:</span><span class="token"> </span><span class="token" style="color: #005cc5;">String</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">var driverTeam:</span><span class="token"> </span><span class="token" style="color: #005cc5;">String</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> Here you will provide static data</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">var lastPlaceDriver:</span><span class="token"> </span><span class="token" style="color: #005cc5;">String</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-326"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-324">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-325'
	>
	After starting the activity, it can stay live for eight hours before it ends up being terminated by the system. Eight is enough for most activity use cases, but sometimes you can have an 8-hour flight in which case you wouldn’t be able to track the whole duration with the 8-hour limitation. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-328"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-327'
	>
	Once the Live Activity is live and running, you can update the dynamic data of the activity. One thing to note is that you <strong>can’t update</strong> <strong>the Live Activity via API calls like you can with other widgets</strong>. Instead, you must update it via your application or push notifications.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-331"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-329">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-330'
	>
	After you end it, the Live Activity will still be available on the Lock Screen for another four hours by default. You can specify if you want to terminate the Lock Screen Live Activity immediately or the system will do it for you after four hours.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-334"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-332">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-333'
	>
	Ensure that you update the Live Activity after it ends if you’re not immediately removing it from the Lock Screen to reflect its proper state. That way, your users will have the latest information and know that the Live Activity has ended with the latest results.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-337"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-335">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-336'
	>
	How many views does the Live Activity have?</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-340"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-338">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-339'
	>
	As mentioned before, you can show the Live Activity on the Lock Screen, Dynamic Island, or a banner on devices that don’t support the Dynamic Island. When you’re developing for devices that have the Dynamic Island support, there are five views:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-343"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-341">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-342'
	>
	<li>Minimal</li><li>Leading</li><li>Trailing</li><li>Expanded</li><li>Lock Screen</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-346"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-344">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-345'
	>
	Every single one of these views can be different from the other.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-349"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-347">
	<h2	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-348'
	>
	Minimal view</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-352"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-350">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-351'
	>
	As the name suggests, it should be minimal, eg. an image that uniquely represents your app. The Minimal view is shown when multiple Live Activities are running at once on the right-hand side of the Dynamic Island with slight padding. Tapping on the view will lead the user straight into your app.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-355"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-353"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-354">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2022/10/Asset-2-1400x588.webp				media='(max-width: 699px)'
				type=image/webp								height="588"
												width="1400"
				 />
								
			<source
				srcset=https://infinum.com/uploads/2022/10/Asset-2-2400x1008.webp				media='(max-width: 1199px)'
				type=image/webp								height="1008"
												width="2400"
				 />
												<img
					src="https://infinum.com/uploads/2022/10/Asset-2.webp"
					class="image__img block-media__image-img"
					alt=""
										height="1150"
															width="2738"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-358"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-356">
	<h2	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-357'
	>
	Leading and Trailing view</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-361"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-359">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-360'
	>
	The leading view will be shown on the left-hand side of Dynamic Island. You can specify some current information from your app related to the Live Activity. Just be careful not to stuff too much information inside the view.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-364"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-362">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-363'
	>
	The trailing view will be shown on the right-hand side of the Dynamic Island. It works just like the leading view, which you can see on the image below for the Formula1 tracking Live Activity ​​that we’ll develop in this example.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-367"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-365"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-366">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2022/10/Asset-3-1400x588.webp				media='(max-width: 699px)'
				type=image/webp								height="588"
												width="1400"
				 />
								
			<source
				srcset=https://infinum.com/uploads/2022/10/Asset-3-2400x1008.webp				media='(max-width: 1199px)'
				type=image/webp								height="1008"
												width="2400"
				 />
												<img
					src="https://infinum.com/uploads/2022/10/Asset-3.webp"
					class="image__img block-media__image-img"
					alt=""
										height="1150"
															width="2738"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-370"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-368">
	<h2	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-369'
	>
	Expanded view</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-373"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-371">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-372'
	>
	The expanded view will be shown once the user long-presses the Live Activity and it should be used to display more information about the activity. In our example, we’ll specify which driver is in front and which one is the last. As you can see in the image below, Max is already in first with Latifi sitting in last – a familiar sight if you’ve been watching F1 lately.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-376"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-374"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-375">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2022/10/Asset-4-1-1400x588.webp				media='(max-width: 699px)'
				type=image/webp								height="588"
												width="1400"
				 />
								
			<source
				srcset=https://infinum.com/uploads/2022/10/Asset-4-1-2400x1008.webp				media='(max-width: 1199px)'
				type=image/webp								height="1008"
												width="2400"
				 />
												<img
					src="https://infinum.com/uploads/2022/10/Asset-4-1.webp"
					class="image__img block-media__image-img"
					alt=""
										height="1150"
															width="2738"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-379"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-377">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-378'
	>
	This example is quite simple – you can customize it by setting the views in particular regions of the expanded view, and the system will try to size them correctly based on the views you’ve provided. You can check out the image below to see which regions are covered with the expanded view.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-382"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-380"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-381">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2022/10/Asset-5-1400x588.webp				media='(max-width: 699px)'
				type=image/webp								height="588"
												width="1400"
				 />
								
			<source
				srcset=https://infinum.com/uploads/2022/10/Asset-5-2400x1008.webp				media='(max-width: 1199px)'
				type=image/webp								height="1008"
												width="2400"
				 />
												<img
					src="https://infinum.com/uploads/2022/10/Asset-5.webp"
					class="image__img block-media__image-img"
					alt=""
										height="1150"
															width="2738"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-385"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-383">
	<h2	class='typography typography--size-36-text js-typography block-heading__heading'
	data-id='es-384'
	>
	Lock Screen view</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-388"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-386">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-387'
	>
	We&#8217;ll use a similar example to show how it looks on the Lock Screen. An important note here is that we can use a different view for this case. It allows us to reuse the existing UI or create new situation-dependent views for Dynamic Island and Live Activities separately. You can see an example of this below.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-391"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-389"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-390">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2022/10/Asset-7-1400x588.webp				media='(max-width: 699px)'
				type=image/webp								height="588"
												width="1400"
				 />
								
			<source
				srcset=https://infinum.com/uploads/2022/10/Asset-7-2400x1008.webp				media='(max-width: 1199px)'
				type=image/webp								height="1008"
												width="2400"
				 />
												<img
					src="https://infinum.com/uploads/2022/10/Asset-7.webp"
					class="image__img block-media__image-img"
					alt=""
										height="1150"
															width="2738"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-394"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-392">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-393'
	>
	Getting your first Live Activity Live</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-397"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-395">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-396'
	>
	First of all, Live Activities are available from iOS 16.1, and I’ll be using Beta Xcode 14.1 for this example.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-400"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-398">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-399'
	>
	After you’ve decided on which app you want to liven up, head into Xcode, and under the targets section, you can find a “+” sign where you can add a widget extension.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-403"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-media">
	<div	class="media block-media__media media__border--none media__align--center-center"
	data-id="es-401"
	 data-media-type='image'>

	<figure class="image block-media__image-figure image--size-stretch" data-id="es-402">
	<picture class="image__picture block-media__image-picture">
								
			<source
				srcset=https://infinum.com/uploads/2022/10/widget-1400x957.webp				media='(max-width: 699px)'
				type=image/webp								height="957"
												width="1400"
				 />
												<img
					src="https://infinum.com/uploads/2022/10/widget.webp"
					class="image__img block-media__image-img"
					alt=""
										height="1266"
															width="1852"
										loading="lazy"
					 />
					</picture>

	</figure></div></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-406"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-404">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-405'
	>
	After you’ve added the widget extension head into the&nbsp;<strong>Info.plist</strong>&nbsp;file of your application and add a boolean flag for the Supports Live Activities permission and set it to YES.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-409"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-407">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-408'
	>
	Now we’re ready to get this show on the road. First, we’ll define the&nbsp;<strong>FormulaAttributes</strong>&nbsp;I’ve mentioned above. It conforms to the&nbsp;<strong>ActivityAttributes</strong>&nbsp;protocol which helps us define dynamic data inside our structure. Make sure that when you create the&nbsp;<strong>FormulaAttributes.swift</strong>&nbsp;file you include it in both the App and the Widget target.&nbsp;</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-411"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-highlighted-text">
	<p	class='typography typography--size-36-text js-typography block-highlighted-text__typography'
	data-id='es-410'
	>
	Before we dive into the code, when starting your Activity your application has to be in the foreground while you can update or end it in the background.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-414"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-412">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-413'
	>
	Starting the Live Activity</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-417"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-415">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-416'
	>
	First, we’ll add a button and a reference to our Activity inside the <strong>ContentView</strong>. The Activity will be set as optional since we want to create it when we need it and the reference is kept so we can update the correct Live Activity later.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-419"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-swift github-light" data-language="swift" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">struct ContentView</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">View </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token" style="color: #d73a49;">@State</span><span class="token"> </span><span class="token">var activity:</span><span class="token"> Activity</span><span class="token">&lt;</span><span class="token">FormulaAttributes</span><span class="token">&gt;</span><span class="token" style="color: #d73a49;">?</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">var body:</span><span class="token"> </span><span class="token" style="color: #d73a49;">some</span><span class="token"> View </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">VStack(</span><span class="token">spacing:</span><span class="token"> </span><span class="token" style="color: #005cc5;">20</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">Button(</span><span class="token">action:</span><span class="token"> </span><span class="token">{</span><span class="token"> activity </span><span class="token" style="color: #d73a49;">= startActivi</span><span class="token">ty(</span><span class="token">)</span><span class="token"> </span><span class="token">}</span><span class="token">, </span><span class="token">label:</span><span class="token"> </span><span class="token">{</span><span class="token"> </span><span class="token">Text(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">Start Activity</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token"> </span><span class="token">}</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-422"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-420">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-421'
	>
	The compiler will complain that there’s no startActivity function and we’ll add it inside a private extension on the <strong>ContentView</strong>. The <strong>startActivity</strong> function will return the newly created Activity.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-424"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-swift github-light" data-language="swift" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">private</span><span class="token"> </span><span class="token" style="color: #d73a49;">extension</span><span class="token"> </span><span class="token" style="color: #6f42c1;">ContentView</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">func startActivity</span><span class="token">(</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">-&gt;</span><span class="token"> </span><span class="token">Activity</span><span class="token">&lt;</span><span class="token">FormulaAttributes</span><span class="token">&gt;</span><span class="token" style="color: #d73a49;">?</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">var activity:</span><span class="token"> Activity</span><span class="token">&lt;</span><span class="token">FormulaAttributes</span><span class="token">&gt;</span><span class="token" style="color: #d73a49;">?</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #d73a49;">let</span><span class="token"> attributes </span><span class="token" style="color: #d73a49;">= Formula</span><span class="token">Attributes(</span><span class="token">lastPlaceDriver:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">Nicholas Latifi</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #d73a49;">d</span><span class="token" style="color: #d73a49;">o</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token" style="color: #d73a49;">let</span><span class="token"> contentState </span><span class="token" style="color: #d73a49;">= FormulaAttr</span><span class="token">ibutes.</span><span class="token">ContentState(</span><span class="token">
</span></span><span class="line"><span class="token">                </span><span class="token">driverInFront:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">Max Verstappen</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,
</span></span><span class="line"><span class="token">                </span><span class="token">driverTeam:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">Red Bull racing</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">            activity </span><span class="token" style="color: #d73a49;">= try Activit</span><span class="token">y</span><span class="token" style="color: #d73a49;">&lt;FormulaAttri</span><span class="token">butes</span><span class="token" style="color: #d73a49;">&gt;.request(att</span><span class="token">ributes:</span><span class="token"> attributes</span><span class="token">, </span><span class="token">contentState:</span><span class="token"> contentState</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">}</span><span class="token"> </span><span class="token" style="color: #d73a49;">catch</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token" style="color: #005cc5;">print</span><span class="token">(</span><span class="token">error.</span><span class="token">localizedDescription</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #d73a49;">return</span><span class="token"> activity
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-427"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-425">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-426'
	>
	Now that we’ve covered the implementation of our Live Activity, we can start looking into activity updates.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-430"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-428">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-429'
	>
	Updating the Live Activity</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-433"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-431">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-432'
	>
	Add another button inside the content view just like the one we added in order to start the activity and call the <strong>updateActivity</strong> function. Inside the private extension of the <strong>ContentView</strong> add the following function:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-435"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-swift github-light" data-language="swift" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">func updateActivity</span><span class="token">(</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">Task</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #d73a49;">let</span><span class="token"> contentState </span><span class="token" style="color: #d73a49;">= Formula</span><span class="token">Attributes.</span><span class="token">ContentState(</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">driverInFront:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">not Lewis Hamilton</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">,
</span></span><span class="line"><span class="token">            </span><span class="token">driverTeam:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">Mercedes</span><span class="token" style="color: #032f62;">&quot;</span><span class="token"> 
</span></span><span class="line"><span class="token">        </span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #d73a49;">await</span><span class="token"> activity</span><span class="token" style="color: #d73a49;">?.update(</span><span class="token">using:</span><span class="token"> contentState</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-438"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-436">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-437'
	>
	The function will get the reference we kept for our activity and call the <strong>.update(using:)</strong> function with some new content. It is wrapped inside a <strong>Task</strong> since it’s an async function.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-441"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-439">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-440'
	>
	This example shows how to update our Live Activity from the app, but as mentioned before, you can also do that using push notifications. The code would differ a bit from our example and you’d have to start the Live Activity by specifying the <strong>pushType</strong> parameter. You’d need to send the <strong>pushToken</strong> from your Live activity to the server so it can receive notifications.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-444"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-442">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-443'
	>
	Ending the Live Activity</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-447"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-445">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-446'
	>
	Ending the Live Activity can also be done via notifications or from the app. As I’ve mentioned before, make sure that you have updated the Live Activity with the latest information before you end it.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-450"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-448">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-449'
	>
	Add another button to the&nbsp;<strong>ContentView</strong>&nbsp;which will reference the&nbsp;<strong>endActivity</strong>&nbsp;function. Inside the private extension of the&nbsp;<strong>ContentView</strong>&nbsp;add the following function:</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-452"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-swift github-light" data-language="swift" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">func endActivity</span><span class="token">(</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">Task</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token" style="color: #d73a49;">for</span><span class="token"> activit</span><span class="token">y </span><span class="token" style="color: #d73a49;">in</span><span class="token"> Activity</span><span class="token" style="color: #d73a49;">&lt;FormulaA</span><span class="token">ttributes</span><span class="token" style="color: #d73a49;">&gt;.activit</span><span class="token">ies </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token" style="color: #d73a49;">await</span><span class="token"> activity.</span><span class="token" style="color: #005cc5;">end</span><span class="token">(</span><span class="token">dismissalPolicy:</span><span class="token"> .</span><span class="token">immediate</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-455"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-453">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-454'
	>
	The&nbsp;<strong>endActivity</strong>&nbsp;function goes through all of the started Live Activities with the&nbsp;<strong>FormulaAttributes</strong>&nbsp;and ends them while specifying that it should remove the Lock Screen activity immediately. Just like the&nbsp;<strong>update(using:)</strong>&nbsp;function, the&nbsp;<strong>end(dismissalPolicy:)</strong>&nbsp;is an async function as well.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-458"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-456">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-457'
	>
	Live Activity user interface</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-461"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-459">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-460'
	>
	Since we’re using only the Live Activity Widget for this example, we’ll annotate it with @main. If you’re using more Widgets, please add them to a <strong>WidgetBundle</strong>. The code below shows the user interface for our formula Widget which was used for prior screenshots.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-463"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-swift github-light" data-language="swift" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token" style="color: #d73a49;">@main</span><span class="token">
</span></span><span class="line"><span class="token">struct FormulaActivityWidget</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">Widget </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">var body:</span><span class="token"> </span><span class="token" style="color: #d73a49;">some</span><span class="token"> WidgetConfiguration </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">ActivityConfiguration(</span><span class="token">for:</span><span class="token"> FormulaAttributes.</span><span class="token" style="color: #d73a49;">self</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token"> context </span><span class="token" style="color: #d73a49;">in</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> Create a view here which will be shown on the Lock Screen and on the Home Screen as a banner</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> For the devices which don&#039;t support the Dynamic Island</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">}</span><span class="token"> </span><span class="token">dynamicIsland:</span><span class="token"> </span><span class="token">{</span><span class="token"> context </span><span class="token" style="color: #d73a49;">in</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">DynamicIsland</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">                </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> This content will be shown when the user expands the island</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">                </span><span class="token">DynamicIslandExpandedRegion(</span><span class="token">.</span><span class="token">center</span><span class="token">)</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">                    </span><span class="token">VStack</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">                        </span><span class="token">Text(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">Driver in front is </span><span class="token" style="color: #032f62;">\(</span><span class="token" style="color: #032f62;">context.</span><span class="token" style="color: #032f62;">state</span><span class="token" style="color: #032f62;">.</span><span class="token" style="color: #032f62;">driverInFront</span><span class="token" style="color: #032f62;">)</span><span class="token" style="color: #032f62;"> ?</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">                        </span><span class="token">Text(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">Last place driver is </span><span class="token" style="color: #032f62;">\(</span><span class="token" style="color: #032f62;">context.</span><span class="token" style="color: #032f62;">attributes</span><span class="token" style="color: #032f62;">.</span><span class="token" style="color: #032f62;">lastPlaceDriver</span><span class="token" style="color: #032f62;">)</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">                    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">                </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">}</span><span class="token"> </span><span class="token">compactLeading:</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">                </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> This view is shown on the left side of the Dynamic Island</span><span class="token">
</span></span><span class="line"><span class="token">                </span><span class="token">Text(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">?</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">}</span><span class="token"> </span><span class="token">compactTrailing:</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">                </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> This view is shown on the right side of the Dynamic Island</span><span class="token">
</span></span><span class="line"><span class="token">                </span><span class="token">Image(</span><span class="token">systemName:</span><span class="token"> </span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">timer</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">}</span><span class="token"> </span><span class="token">minimal:</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">                </span><span class="token" style="color: #6a737d;">//</span><span class="token" style="color: #6a737d;"> This view will be shown when there are multiple activities running at ones</span><span class="token">
</span></span><span class="line"><span class="token">                </span><span class="token">Text(</span><span class="token" style="color: #032f62;">&quot;</span><span class="token" style="color: #032f62;">?</span><span class="token" style="color: #032f62;">&quot;</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-466"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-464">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-465'
	>
	Inside the Widget, we specify the <strong>ActivityConfiguration</strong> and which Attributes it’s going to use. First, we’ll set the views on the Dynamic Island, and afterwards we’ll add the Lock Screen one. </p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-469"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-467">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-468'
	>
	As it’s a simple example, I’ve only used the center region of the Expanded view to set some data inside. Afterward, there are closures describing which part of the Dynamic Island it covers so you can put whichever view you want it to show.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-472"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-470">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-471'
	>
	Now we can set the Lock Screen and the Home banner view. Here’s a Lock Screen view which will have a reference to the context from our attributes so it can be updated properly.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-474"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-code">
	<pre class="phiki language-swift github-light" data-language="swift" style="background-color: #fff;color: #24292e;"><code><span class="line"><span class="token">struct LockScreenLiveActivityView</span><span class="token">:</span><span class="token"> </span><span class="token" style="color: #6f42c1;">View </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">let context:</span><span class="token"> ActivityViewContext</span><span class="token">&lt;</span><span class="token">FormulaAttributes</span><span class="token">&gt;</span><span class="token">
</span></span><span class="line"><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">var body:</span><span class="token"> </span><span class="token" style="color: #d73a49;">some</span><span class="token"> View </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">        </span><span class="token">VStack</span><span class="token"> </span><span class="token">{</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">Text(</span><span class="token">“Driver </span><span class="token" style="color: #d73a49;">in</span><span class="token"> front </span><span class="token" style="color: #d73a49;">is</span><span class="token"> \</span><span class="token">(</span><span class="token">context.</span><span class="token">state</span><span class="token">.</span><span class="token">driverInFront</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">?“)
</span></span><span class="line"><span class="token">            </span><span class="token">Spacer(</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">            </span><span class="token">Text(</span><span class="token">“Last place driver </span><span class="token" style="color: #d73a49;">is</span><span class="token"> \</span><span class="token">(</span><span class="token">context.</span><span class="token">attributes</span><span class="token">.</span><span class="token">lastPlaceDriver</span><span class="token">)</span><span class="token"> </span><span class="token" style="color: #d73a49;">?“)
</span></span><span class="line"><span class="token">        </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">        .</span><span class="token">activitySystemActionForegroundColor(</span><span class="token">.</span><span class="token">white</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">        .</span><span class="token">activityBackgroundTint(</span><span class="token">.</span><span class="token">cyan</span><span class="token">)</span><span class="token">
</span></span><span class="line"><span class="token">    </span><span class="token">}</span><span class="token">
</span></span><span class="line"><span class="token">}</span><span class="token">
</span></span></code></pre></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-477"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-475">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-476'
	>
	After you’ve created the Lock Screen view, initialize it inside the closure for the <strong>ActivityConfiguration</strong>. And there you have it, you can now run the app and test it out.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-480"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-478">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-479'
	>
	Adding DeepLinking inside the Expanded view</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-483"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-481">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-482'
	>
	Since the Live Activity is a Widget, it can deep link into your app. Every Live Activity view can lead to a different place inside your app, but Apple still recommends that the leading and trailing views lead to the same place. DeepLinking is a great tool to add inside the expanded view where you can have multiple views, some of which could navigate where you need them.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-486"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-484">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-485'
	>
	Things to watch out for</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-489"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-487">
	<ul	class='typography typography--size-18-text-roman js-typography lists__typography'
	data-id='es-488'
	>
	<li>Live Activities can be disabled in the Settings App. You can check if that’s the case and provide a proper explanation to the user, telling them what they’re missing out on</li><li>Make sure to handle the errors with care. Users can have multiple Live Activities running at the same time, and you could also reach a state where your app started too many activities. Make sure you’re not one of the offenders that does this</li><li>The system ignores any animation modifiers when defining the user interface of the Live Activity, but you can modify the animations Apple’s using to create a more unique experience</li><li>Live Activities will be rendered in Dark Mode when the Always-On Retina is on</li><li>Use app updates alongside push notification updates. Sometimes the user won’t receive a push notification due to not having an internet connection or if the Live Activity has ended.</li><li>When using push notifications, the system might throttle your push notifications, and the user might not receive them if you’ve sent too many notifications. This is crucial if you’re building a Live Activity for Formula where you’ll have a lot of updates on the grid. One way to still send a push notification is to send a low-priority one, but no one can guarantee that the user will receive it</li><li>The system can display Live Activities even on devices that don’t support the Dynamic Island as a banner on the Home Screen, but only if the app determines that the update is important enough to interrupt people</li></ul></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-492"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-490">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-491'
	>
	The finish line</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-495"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-493">
	<p	class='typography typography--size-16-text-roman js-typography block-paragraph__paragraph'
	data-id='es-494'
	>
	Live Activities are a new way to show information at a glance. The animations and different views make Live Activities unique from other widgets. Tailor the Live Activities to your app’s needs and make it a unique yet pleasant experience that the users can enjoy.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-498"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-496">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-497'
	>
	PS. We probably don’t even need to update our Live Activity since Max most likely won.</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-501"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-paragraph" data-id="es-499">
	<p	class='typography typography--size-20-text-roman js-typography block-paragraph__paragraph'
	data-id='es-500'
	>
	Hope you enjoyed the article and happy coding, we’re looking forward to seeing your own live activities out in the wild!</p></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-504"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="block-heading" data-id="es-502">
	<h2	class='typography typography--size-52-default js-typography block-heading__heading'
	data-id='es-503'
	>
	Useful links</h2></div>	</div>

<div
	class="wrapper wrapper__use-simple--true"
	data-id="es-507"
	 data-animation='slideFade' data-animation-target='inner-items'>
		
			<div class="lists" data-id="es-505">
	<ul	class='typography typography--size-16-text-roman js-typography lists__typography'
	data-id='es-506'
	>
	<li><a target="_blank" href="https://developer.apple.com/documentation/activitykit/displaying-live-data-with-live-activities" rel="noreferrer noopener">https://developer.apple.com/documentation/activitykit/displaying-live-data-with-live-activities</a></li><li><a target="_blank" href="https://developer.apple.com/documentation/activitykit/update-and-end-your-live-activity-with-remote-push-notifications" rel="noreferrer noopener">https://developer.apple.com/documentation/activitykit/update-and-end-your-live-activity-with-remote-push-notifications</a></li><li><a target="_blank" href="https://developer.apple.com/documentation/activitykit" rel="noreferrer noopener">https://developer.apple.com/documentation/activitykit</a></li></ul></div>	</div>
</div>
</div>		</div>
	</div><p>The post <a href="https://infinum.com/blog/live-activities-in-ios-apps/">Make Your iOS Apps Come Alive with Live Activities</a> appeared first on <a href="https://infinum.com">Infinum</a>.</p>
]]>
				</content:encoded>
			</item>
		
	</channel>
</rss>