<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kevin Furjan</title>
    <description>The latest articles on DEV Community by Kevin Furjan (@kfurjan).</description>
    <link>https://dev.to/kfurjan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F763807%2F1bea5649-e02a-4c6a-bea9-086d2ec3823d.jpeg</url>
      <title>DEV Community: Kevin Furjan</title>
      <link>https://dev.to/kfurjan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kfurjan"/>
    <language>en</language>
    <item>
      <title>How to create Python library</title>
      <dc:creator>Kevin Furjan</dc:creator>
      <pubDate>Mon, 23 May 2022 19:31:18 +0000</pubDate>
      <link>https://dev.to/kfurjan/how-to-create-python-library-4l9b</link>
      <guid>https://dev.to/kfurjan/how-to-create-python-library-4l9b</guid>
      <description>&lt;p&gt;Python programming language is extremely popular not only because it is easy to use but because it has lots of third-party libraries which really help in shortening development time. But how do you make one? I wondered the same thing and decided to create a simple Python library. The process of creating a library is not exactly straightforward so I decided to author this article which will hopefully help other people who are creating Python library for the first time or who are just trying to refresh memory on a certain step.&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Article will go over how to make simple Python library. More specifically, REST API client for FreeGeoIp's API [1]. It will describe project structure, how to use &lt;code&gt;setup.py&lt;/code&gt;, how to add unit tests to project and how to publish created library to PyPI using GitHub Actions as CI/CD solution.&lt;/p&gt;

&lt;h1&gt;
  
  
  Project structure
&lt;/h1&gt;

&lt;p&gt;Project has straightforward and basic structure. There is one module - &lt;code&gt;freegeoip_client&lt;/code&gt;, actual Python library we are trying to build here. &lt;code&gt;tests&lt;/code&gt; folder which contains all unit tests and last, but not least, &lt;code&gt;setup.py&lt;/code&gt; and &lt;code&gt;setup.cfg&lt;/code&gt; files which are used to create Python package - more on them in &lt;em&gt;Setuptools&lt;/em&gt; section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python-freegeoip-client  &lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; project root
    ├── freegeoip_client &lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; Python module
    │   ├── __init__.py
    │   ├── client.py
    │   └── data
    │       └── client.cfg
    ├── setup.cfg
    ├── setup.py
    └── tests
        ├── conftest.py
        └── test_client.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Python's &lt;code&gt;__init__.py&lt;/code&gt; file
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;__init__.py&lt;/code&gt; allows us to mark a directory as a Python package directory which enables importing Python code from one Python file into another. Also, it can be useful to define any variable at the package level. Doing so is often convenient if a package defines something that will be imported frequently, in an API-like fashion. If you are interested in learning more about this, there is an interesting Reddit thread covering appropriate uses of &lt;code&gt;__init__.py&lt;/code&gt; file [2].&lt;/p&gt;

&lt;p&gt;When creating Python library, &lt;code&gt;__init__.py&lt;/code&gt; is used to import key functions and/or classes from various modules directly into the package namespace and thus enabling end-users to use Python module in an API-like fashion.&lt;/p&gt;

&lt;p&gt;Example &lt;code&gt;__int__.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
freegeoip_client package.

Provides FreeGeoIpClient object which enables consuming
FreeGeoIp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s RESTful API by providing your own API key.

Usage
-----
from freegeoip_client import FreeGeoIpClient

client = FreeGeoIpClient(api_key=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;some_api_key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)

geo_data = client.get_geo_location()
geo_data_by_ip = client.get_geo_location_for_ip_address(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8.8.8.8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;)
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FreeGeoIpClient&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Unit tests
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;In computer programming, unit testing is a software testing method by which individual units of source code—sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures—are tested to determine whether they are fit for use.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Adding unit tests is always highly recommended since tests enable us to test edge cases which in return results in higher code quality and more robust code.&lt;/p&gt;

&lt;p&gt;One of the more popular unit test frameworks for Python is &lt;em&gt;pytest&lt;/em&gt; framework [3]. It makes it easy to write small, readable tests in a &lt;em&gt;pythonic&lt;/em&gt; way. If you are not familiar with it, I would encourage you to check it out. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;python-freegeoip-client&lt;/code&gt; project uses &lt;code&gt;pytest&lt;/code&gt; framework for unit tests and &lt;code&gt;requests-mock&lt;/code&gt; library for mocking API responses [4].&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;requests_mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Mocker&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_geo_data_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_geo_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Mocker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_api_endpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/?apikey=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;current_geo_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_geo_location&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;current_geo_data&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_geo_data_output_for_ip_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;geo_data_for_ip_address&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Mocker&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_api_endpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/8.8.8.8?apikey=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;geo_data_for_ip_address&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_geo_location_for_ip_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8.8.8.8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;geo_data_for_ip_address&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Setuptools
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Setuptools is a collection of enhancements to the Python &lt;code&gt;distutils&lt;/code&gt; that allow developers to more easily build and distribute Python packages, especially ones that have dependencies on other packages.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Using &lt;code&gt;setuptools&lt;/code&gt; you package Python code into, well, a package that can be imported in different scripts, either by installing it locally or by installing it using PyPI repository. Packaging Python code can be done using &lt;code&gt;setup.py&lt;/code&gt; file, or, the preferred way, using &lt;code&gt;pyproject.toml&lt;/code&gt; and &lt;code&gt;setup.cfg&lt;/code&gt; files. I am, however, using &lt;code&gt;setup.py&lt;/code&gt; file because there is &lt;code&gt;pip&lt;/code&gt; bug where it is not possible to install packages locally when using &lt;code&gt;pyproject.toml&lt;/code&gt; and &lt;code&gt;setup.cfg&lt;/code&gt; files [5].&lt;/p&gt;

&lt;p&gt;&lt;code&gt;setup.py&lt;/code&gt; file for this Python project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;setuptools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;find_packages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setup&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_long_description&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;README.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freegeoip-client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0.5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FreeGeoIp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s RESTful API client for Python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;long_description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;get_long_description&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;long_description_content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/markdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;author&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Kevin Furjan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;author_email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kfurjan@gmail.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/kfurjan/python-freegeoip-client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;project_urls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GitHub Project&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/kfurjan/python-freegeoip-client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Issue Tracker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://github.com/kfurjan/python-freegeoip-client/issues&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;find_packages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freegeoip_client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freegeoip_client.*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;package_data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freegeoip_client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data/*.cfg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;install_requires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requests==2.27.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;setup_requires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pytest-runner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;flake8==4.0.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;tests_require&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pytest==7.1.2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;requests-mock==1.9.3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;python_requires&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;=3.6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;classifiers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Intended Audience :: Developers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Programming Language :: Python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Programming Language :: Python :: 3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Operating System :: OS Independent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;License :: OSI Approved :: MIT License&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Topic :: Software Development :: Libraries :: Python Modules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;keywords&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FreeGeoIp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;REST API Client&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;license&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MIT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you are interested in &lt;code&gt;setup.py&lt;/code&gt; file in more detail, there is an excellent article about it and how to use it [6].&lt;/p&gt;

&lt;h1&gt;
  
  
  PyPI
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;The Python Package Index (PyPI) is a repository of software for the Python programming language.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By default, &lt;code&gt;pip&lt;/code&gt; - package installer for Python - uses PyPI repository as the source for retrieving package dependencies. PyPI lets you find, install, and even publish your Python packages so that they are widely available to the public.&lt;/p&gt;

&lt;p&gt;To publish packages to PyPI, you need to register an account [7] or login with the previously registered one [8]. Once logged in to PyPI, head over to &lt;strong&gt;&lt;em&gt;Account settings&lt;/em&gt;&lt;/strong&gt; and add new API token under &lt;strong&gt;&lt;em&gt;API tokens&lt;/em&gt;&lt;/strong&gt; section. API token will be required in the next step to publish package via GitHub Actions so make sure that you saved it.&lt;/p&gt;

&lt;p&gt;Click on &lt;code&gt;Add API token&lt;/code&gt; button:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653330097683%2FXApMEzmxB.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653330097683%2FXApMEzmxB.png%2520align%3D" alt="Screenshot 2022-05-23 at 20.21.19.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; For purposes of this blog, I've selected token scope as "Entire account (all projects)", but it is advised to have each API token on PyPI scoped only to a certain project.&lt;/p&gt;
&lt;h1&gt;
  
  
  GitHub Actions
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Automate, customize, and execute your software development workflows right in your repository with GitHub Actions. You can discover, create, and share actions to perform any job you'd like, including CI/CD, and combine actions in a completely customized workflow.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GitHub Actions are free to use with each GitHub repository. You can set it up using the &lt;em&gt;Actions&lt;/em&gt; tab in GitHub repository and clicking on &lt;em&gt;New workflow&lt;/em&gt; button or you can directly add &lt;code&gt;.github/workflow&lt;/code&gt; folder to your project code with &lt;code&gt;.yml&lt;/code&gt; file in the folder that defines certain workflow.&lt;/p&gt;

&lt;p&gt;Before creating CI/CD workflow, we need to add previously created API token to GitHub Secrets. Open your GitHub project and go to the &lt;em&gt;Settings&lt;/em&gt; tab. Then select &lt;em&gt;Secrets&lt;/em&gt; and finally &lt;em&gt;Actions&lt;/em&gt;. Click on &lt;em&gt;New repository secret&lt;/em&gt; button on top of the page. Name repository secret as &lt;code&gt;PYPI_API_TOKEN&lt;/code&gt; and add API token's value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653331377134%2FSke_gif0i.png%2520align%3D" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1653331377134%2FSke_gif0i.png%2520align%3D" alt="Screenshot 2022-05-23 at 20.42.04.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once done with this step, we can move on to defining workflow using &lt;code&gt;.yml&lt;/code&gt; file that is saved within &lt;code&gt;.github/workflow&lt;/code&gt; folder.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setting up environment and dependencies
&lt;/h2&gt;

&lt;p&gt;We need to define when CI/CD workflow will run and what environment will be used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build, lint, test, and upload to PyPI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python &lt;/span&gt;&lt;span class="m"&gt;3.8&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

      &lt;span class="c1"&gt;# Install dependencies&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Installs&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dependencies"&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python3 -m pip install --upgrade pip&lt;/span&gt;
          &lt;span class="s"&gt;python3 -m pip install setuptools wheel twine flake8&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we defined the name of the workflow, when workflow will run - on each push to main branch, and what environment will be used - latest version of Ubuntu Linux. We also defined the step which will install necessary Python dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linting and running unit tests
&lt;/h2&gt;

&lt;p&gt;Before publishing package to PyPI, we need to make sure that code is formatted correctly and that all unit tests are passing. This can be achieved by adding two additional steps to the workflow.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="c1"&gt;# Lint&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint with flake8&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python3 -m flake8 freegeoip_client/&lt;/span&gt;
          &lt;span class="s"&gt;python3 -m flake8 tests/&lt;/span&gt;

      &lt;span class="c1"&gt;# Run unit tests&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test with pytest&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python3 setup.py test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building and publishing to PyPI
&lt;/h2&gt;

&lt;p&gt;The last step is to build the Python package - creating source distribution using &lt;code&gt;sdist&lt;/code&gt; - and upload it to PyPI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="c1"&gt;# Build package&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Builds&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;package"&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python3 setup.py sdist&lt;/span&gt;

      &lt;span class="c1"&gt;# Publish package to PyPI&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish a Python distribution to PyPI&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pypa/gh-action-pypi-publish@release/v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;__token__&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.PYPI_API_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we are using &lt;code&gt;pypa/gh-action-pypi-publish@release/v1&lt;/code&gt; GitHub Actions workflow from Marketplace to make publishing to PyPI easier [9]. Workflow will do all the heavy lifting to upload package to PyPI, we just need to provide our API token saved to repository secrets using &lt;code&gt;${{ secrets.PYPI_API_TOKEN }}&lt;/code&gt; syntax.&lt;/p&gt;

&lt;p&gt;And that's it! We have GitHub actions workflow that will publish our Python library to the PyPI and make sure that code is formatted correctly, and all unit tests are passing successfully before publishing.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Thank you for reading and I hope this article was useful to you! In conclusion, this article went step-by-step over creating and publishing Python library to PyPI automatically using GitHub Actions CI/CD workflow.&lt;/p&gt;




&lt;p&gt;If you like my content and find it useful, please consider following me. If you are feeling extra generous, please consider &lt;a href="https://buymeacoffee.com/kfurjan" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Connect with me on &lt;a href="https://www.linkedin.com/in/kevin-furjan/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

&lt;p&gt;[1] - &lt;a href="https://freegeoip.app/" rel="noopener noreferrer"&gt;FreeGeoIp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[2] - &lt;a href="https://www.reddit.com/r/Python/comments/1bbbwk/whats_your_opinion_on_what_to_include_in_init_py/" rel="noopener noreferrer"&gt;What to include in &lt;strong&gt;init&lt;/strong&gt;.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[3] - &lt;a href="https://docs.pytest.org/en/7.1.x/" rel="noopener noreferrer"&gt;pytest&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[4] - &lt;a href="https://requests-mock.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;requests-mock&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[5] - &lt;a href="https://github.com/pypa/pip/issues/7953" rel="noopener noreferrer"&gt;pip bug - not possible to install packages locally&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[6] - &lt;a href="https://godatadriven.com/blog/a-practical-guide-to-using-setup-py/" rel="noopener noreferrer"&gt;A Practical Guide to Using Setup.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[7] - &lt;a href="https://pypi.org/account/register/" rel="noopener noreferrer"&gt;Register an account on PyPI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[8] - &lt;a href="https://pypi.org/account/login/" rel="noopener noreferrer"&gt;Log in to PyPI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[9] - &lt;a href="https://github.com/marketplace/actions/pypi-publish" rel="noopener noreferrer"&gt;pypi-publish&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>github</category>
      <category>cicd</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to localize SwiftUI application</title>
      <dc:creator>Kevin Furjan</dc:creator>
      <pubDate>Sat, 12 Feb 2022 17:56:50 +0000</pubDate>
      <link>https://dev.to/kfurjan/how-to-localize-swiftui-application-oko</link>
      <guid>https://dev.to/kfurjan/how-to-localize-swiftui-application-oko</guid>
      <description>&lt;p&gt;By localizing your application, you are making your application more accessible to a global audience. With help of SwiftUI framework and Xcode 13, localizing your app is not particularly difficult. But how do you do it exactly? Let's see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Localization support in SwiftUI
&lt;/h2&gt;

&lt;p&gt;Localization in SwiftUI was designed to be easy. Built-in SwiftUI views that are meant to show strings on UI, for example &lt;code&gt;Text&lt;/code&gt;, &lt;code&gt;Toggle&lt;/code&gt; and &lt;code&gt;Picker&lt;/code&gt;, are using &lt;code&gt;LocalizedStringKey&lt;/code&gt; internally when &lt;em&gt;user-defined strings&lt;/em&gt; are passed to the view. &lt;code&gt;LocalizedStringKey&lt;/code&gt; is a special type that signals SwiftUI to look up localized strings in your bundle by passed key.&lt;/p&gt;

&lt;p&gt;Let's look at the &lt;code&gt;Text&lt;/code&gt; constructor in more detail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;LocalizedStringKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;StaticString&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;key&lt;/strong&gt;: The key for a string in the table identified by &lt;em&gt;tableName&lt;/em&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;tableName&lt;/strong&gt;: The name of the string table to search. If &lt;em&gt;nil&lt;/em&gt;, use the table in the Localizable.strings file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;bundle&lt;/strong&gt;: The bundle containing the strings file. If &lt;em&gt;nil&lt;/em&gt;, use the main bundle.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;comment&lt;/strong&gt;: Contextual information about this key-value pair. Using comments is very much encouraged. It gives necessary context to the &lt;strong&gt;key&lt;/strong&gt;, especially if person translating text is not developer and doesn't have access to the code. Good comments should explain where the string is visible, explain context of string and explain variable in string, if variable is used.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Localization support in custom views
&lt;/h3&gt;

&lt;p&gt;This also means that when creating custom views in SwiftUI, instead of regular &lt;code&gt;String&lt;/code&gt;, use &lt;code&gt;LocalizedStringKey&lt;/code&gt; as parameter which will display text on screen. This will make custom view ready for localization. Example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// should be avoided if possible&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;CustomView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;/// preferred way&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;CustomView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;localizedString&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;LocalizedStringKey&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;localizedString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Returning localized strings from functions
&lt;/h3&gt;

&lt;p&gt;Sometimes UI elements show some kind of state, whether it is an enum representation or something else entirely. There is usually some kind of function or computed property which returns string. Using &lt;code&gt;NSLocalizedString(key:comment:)&lt;/code&gt; you can make that string localizable. If you are targeting iOS 15 later, prefer using &lt;code&gt;String(localized:comment:)&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;/// prior to iOS 15&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;getLocalizedString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;/// ... some code&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;NSLocalizedString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"some comment"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;/// iOS 15 and later&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;getLocalizedString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;/// ... some code&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;localized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"some string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"some comment"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Xcode workflow
&lt;/h2&gt;

&lt;p&gt;Up until here we went over how to adjust code to use localized strings but how to actually add localized strings to the project? Using Xcode 13 this process is pretty straightforward. Add wanted localization to project, export localizations, localize strings, import localizations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding localizations
&lt;/h3&gt;

&lt;p&gt;Localizations can be added to the project by opening Project configuration. Once opened, head over to &lt;strong&gt;Localizations&lt;/strong&gt; section and add desired localization to the project by pressing &lt;code&gt;+&lt;/code&gt; sign.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1644684248807%2FwTP6eEa5x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1644684248807%2FwTP6eEa5x.png" alt="Screenshot 2022-02-12 at 17.36.27.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Exporting Localizations
&lt;/h3&gt;

&lt;p&gt;Once you added localization to the project, export localizations by clicking on &lt;strong&gt;Product &amp;gt; Export Localizations&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1644684508513%2FZnaH3_gHa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1644684508513%2FZnaH3_gHa.png" alt="Screenshot 2022-02-12 at 17.44.50.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will create &lt;code&gt;.xcloc&lt;/code&gt; files which can be easily opened, by double-clicking on them, and edited using Xcode. When opened, localize your strings to the desired language. Example below for Spanish language:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1644684769720%2FgHOvCUdKc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1644684769720%2FgHOvCUdKc.png" alt="Screenshot 2022-02-12 at 17.50.28.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note: If &lt;code&gt;Export Localizations&lt;/code&gt; is not working properly for you, enable the "Use Compiler to Extract Swift Strings" build setting to extract LocalizedStringKeys from code when exporting for localization in Xcode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Importing Localizations
&lt;/h3&gt;

&lt;p&gt;Once you are done with translations, importing localizations is as simple as clicking &lt;strong&gt;Product &amp;gt; Import Localizations&lt;/strong&gt;. Select &lt;code&gt;.xcloc&lt;/code&gt; files and import them. You should see newly created &lt;strong&gt;Localizable.strings&lt;/strong&gt; in Xcode project navigator which contain key value pairs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1644686186018%2FdV8Fv5CaU.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1644686186018%2FdV8Fv5CaU.png" alt="Screenshot 2022-02-12 at 18.16.16.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Previewing localizations
&lt;/h2&gt;

&lt;p&gt;Using &lt;strong&gt;SwiftUI Previews&lt;/strong&gt;, it is extremely easy to preview localizations. Example below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView_Previews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PreviewProvider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;previews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;(\&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;identifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"es"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will create preview which will use desired localization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for reading and I hope this article was useful to you! In conclusion, this article went over localizing SwiftUI application, introduction to it and how you can use it in your own projects. If you are interested in finding out more about localizing iOS applications, for example pluralization of strings, consult with references below.&lt;/p&gt;




&lt;p&gt;If you like my content and find it useful, please consider following me. If you are feeling extra generous, please consider &lt;a href="https://buymeacoffee.com/kfurjan" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Connect with me on &lt;a href="https://www.linkedin.com/in/kevin-furjan/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/Xcode/localization" rel="noopener noreferrer"&gt;Apple Developer - Localization&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10220/" rel="noopener noreferrer"&gt;Apple Developer - Localize your SwiftUI app&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2021/10221" rel="noopener noreferrer"&gt;Apple Developer - Streamline your localized strings&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/swiftui/localizedstringkey" rel="noopener noreferrer"&gt;LocalizedStringKey&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/foundation/nslocalizedstring" rel="noopener noreferrer"&gt;NSLocalizedString&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swift</category>
      <category>ios</category>
      <category>xcode</category>
      <category>localization</category>
    </item>
    <item>
      <title>Enforce Swift style and conventions with SwiftLint</title>
      <dc:creator>Kevin Furjan</dc:creator>
      <pubDate>Thu, 03 Feb 2022 17:01:24 +0000</pubDate>
      <link>https://dev.to/kfurjan/enforce-swift-style-and-conventions-with-swiftlint-137a</link>
      <guid>https://dev.to/kfurjan/enforce-swift-style-and-conventions-with-swiftlint-137a</guid>
      <description>&lt;h2&gt;
  
  
  What is a linter
&lt;/h2&gt;

&lt;p&gt;Linter is a static code analysis tool used to flag programming errors, bugs, stylistic errors, and suspicious constructs. In other words, linter checks the source code for programmatic as well as stylistic errors. By doing that, it is helpful in identifying some common and uncommon mistakes that are made during coding, meaning that it helps in increasing the code's reliability and maintaining a higher level of code discipline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is SwiftLint
&lt;/h2&gt;

&lt;p&gt;Well, it's a linter tool to enforce style and conventions for Swift programming language. SwiftLint enforces the style guide rules that are generally accepted by the Swift community, described in style guides like &lt;em&gt;Ray Wenderlich's Swift Style Guide&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;SwiftLint can be installed on OS level using &lt;em&gt;Homebrew&lt;/em&gt; package manager with the following command: &lt;code&gt;brew install swiftlint&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you, however, want to install SwiftLint as dependency only to your project, you can do that using CocoaPods by adding &lt;code&gt;pod 'SwiftLint'&lt;/code&gt; to your &lt;em&gt;Podfile&lt;/em&gt;. This will download the SwiftLint binaries and dependencies in &lt;code&gt;Pods/&lt;/code&gt; folder during your next &lt;code&gt;pod install&lt;/code&gt; execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Xcode configuration
&lt;/h2&gt;

&lt;p&gt;SwiftLint can be integrated into your Xcode project to get warnings and errors displayed in the issue navigator. To enable this, click the &lt;em&gt;Project&lt;/em&gt; in the file navigator, then click the &lt;em&gt;primary app target&lt;/em&gt;, and go to &lt;em&gt;Build Phases&lt;/em&gt;. Click the + and select "New Run Script Phase". Finally, add the following shell script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if which swiftlint &amp;gt;/dev/null; then
  swiftlint
else
  echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: If you've installed SwiftLint via CocoaPods the script should look like this &lt;code&gt;"${PODS_ROOT}/SwiftLint/swiftlint"&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rules
&lt;/h2&gt;

&lt;p&gt;SwiftLint contains more than two hundred rules which are enforcing style and conventions for Swift programming language. Full list of SwiftLint rules can be found in &lt;a href="https://realm.github.io/SwiftLint/rule-directory.html"&gt;Rule Directory&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Rules can be disabled or can be customized, by adjusting threshold for warnings and errors. This can be achieved using configuration file &lt;code&gt;.swiftlint.yml&lt;/code&gt; or by disabling a rule directly in code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuration file
&lt;/h3&gt;

&lt;p&gt;Using &lt;code&gt;.swiftlint.yml&lt;/code&gt; configuration file enables control over which rule is disabled/enabled and what thresholds are set for warnings and errors for a given rule. Also, it enables excluding some parts of your project, such as &lt;em&gt;Pods&lt;/em&gt;, so SwiftLint won't check them. Configuration file needs to be manually created and added to project root directory.&lt;/p&gt;

&lt;p&gt;Below is example of &lt;code&gt;.swiftlint.yml&lt;/code&gt; configuration file that I personally use in SwiftUI projects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;excluded: # paths to ignore during linting
  - Carthage
  - Pods
  - Source/ExcludedFolder
  - Source/ExcludedFile.swift

disabled_rules: # rule identifiers turned on by default to exclude from running
  - multiple_closures_with_trailing_closure

force_cast: warning # implicitly
force_try:
  severity: warning # explicitly

file_length:
  warning: 400
  error: 700

type_name:
  min_length: 2 # only warning
  max_length: # warning and error
    warning: 40
    error: 50
identifier_name:
  min_length: 2 # only warning
  max_length: # warning and error
    warning: 40
    error: 50
  excluded: # excluded via string array
    - id
    - URL
    - GlobalAPIKey
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration file will tell SwiftLint to exclude folders from linting where project dependencies are stored. It also optimizes some rules to be easier to work with when writing Swift and SwiftUI code. Example, by disabling &lt;code&gt;multiple_closures_with_trailing_closure&lt;/code&gt; rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disabling rules in code
&lt;/h3&gt;

&lt;p&gt;Rules can be disabled with a comment inside a source file with the following format: &lt;code&gt;// swiftlint:disable &amp;lt;rule&amp;gt;&lt;/code&gt;. The rule will be disabled until the end of the file or until the linter sees a matching enable comment: &lt;code&gt;// swiftlint:enable &amp;lt;rule&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When writing SwiftUI code, disabling rules in code can come in handy when dealing with SwiftUI Previews which can sometimes lead to longer line length. In that case, &lt;code&gt;line_length&lt;/code&gt; rule can be disabled with &lt;code&gt;// swiftlint:disable line_length&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// swiftlint:disable line_length

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        ... some long Swift code ...
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for reading and I hope this article was useful to you! In conclusion, this article went over SwiftLint, introduction to it and how you can use it in your own projects.&lt;/p&gt;




&lt;p&gt;If you like my content and find it useful, please consider following me. If you are feeling extra generous, please consider &lt;a href="https://buymeacoffee.com/kfurjan"&gt;buying me a coffee&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Connect with me on &lt;a href="https://www.linkedin.com/in/kevin-furjan/"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/realm/SwiftLint"&gt;SwiftLint&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/raywenderlich/swift-style-guide"&gt;Ray Wenderlich's Swift Style Guide&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/github/swift-style-guide"&gt;GitHub Swift Style Guide&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://academy.realm.io/posts/slug-jp-simard-swiftlint/"&gt;The Road to Cleaner Code w/ SwiftLint&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>swift</category>
      <category>codequality</category>
      <category>lint</category>
      <category>swiftlint</category>
    </item>
    <item>
      <title>Build REST API with Kotlin and Ktor</title>
      <dc:creator>Kevin Furjan</dc:creator>
      <pubDate>Tue, 21 Dec 2021 09:34:35 +0000</pubDate>
      <link>https://dev.to/kfurjan/build-rest-api-with-kotlin-and-ktor-15aa</link>
      <guid>https://dev.to/kfurjan/build-rest-api-with-kotlin-and-ktor-15aa</guid>
      <description>&lt;p&gt;According to Stack Overflow &lt;a href="https://insights.stackoverflow.com/survey/2020#most-loved-dreaded-and-wanted" rel="noopener noreferrer"&gt;survey&lt;/a&gt; from 2020, Kotlin is one of the most loved programming languages, so it is safe to say that JetBrains, a company that develops Kotlin, is doing an excellent job. Kotlin is most well known as a language for developing Android applications, but it can be used for developing backend services, such as RESTful API. JetBrains is also developing a framework named Ktor that can be used for developing RESTful API with Kotlin and as you can imagine, it is pretty great.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is REST
&lt;/h2&gt;

&lt;p&gt;REST, short for Representational state transfer, is a software architectural style that was created to guide the design and development of the architecture for internet communication. REST defines a set of constraints for how the architecture of an Internet-scale system, such as the Web, should behave.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A Web API (or Web Service) conforming to the REST architectural style is a REST API.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Ktor
&lt;/h2&gt;

&lt;p&gt;Ktor is a web framework made by JetBrains with Kotlin programming language. In their own words:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ktor is a framework to easily build connected applications – web applications, HTTP services, mobile and browser applications. Modern connected applications need to be asynchronous to provide the best experience to users, and Kotlin coroutines provide awesome facilities to do it in an easy and straightforward way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Main goals of Ktor framework are to be lightweight, unopinionated, asynchronous and testable meaning that framework itself doesn't impose what technologies project is going to use, can be easily deployable and uses Kotlin coroutines to provide asynchronous operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plugins
&lt;/h2&gt;

&lt;p&gt;Ktor uses concept of &lt;em&gt;Plugins&lt;/em&gt; to extend its functionalities. A plugin is a set of common functionalities that extend application logic, such as serialization or compression.&lt;/p&gt;

&lt;p&gt;For example, typical request-response pipeline in Ktor:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1639941216013%2FH8_c3ngV-.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1639941216013%2FH8_c3ngV-.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, using &lt;em&gt;Plugins&lt;/em&gt; in Ktor, previous request-response pipeline can be easily extended with functionalities to something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1639941644397%2Fz-aLtgGYP.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1639941644397%2Fz-aLtgGYP.png" alt="image.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which plugin(s) you will add to the request-response pipeline depends on your preferences and application needs. Need Authentication? Fine, just &lt;em&gt;install&lt;/em&gt; a plugin. Exception handling? Serialization? Great, just a simple plugin installation.&lt;/p&gt;

&lt;p&gt;Plugins are configured during the initialization phase of the server using the install function which takes a Plugin as a parameter. You can install a plugin inside the &lt;code&gt;embeddedServer&lt;/code&gt; call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.ktor.features.*&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;embeddedServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Netty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CORS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Compression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or in desired module&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;io.ktor.features.*&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;module&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CORS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Compression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, Ktor does not activate any plugin(s), and it's up to developers to install the functionality their application needs.&lt;/p&gt;

&lt;p&gt;Some of my personal favorite plugins are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ktor.io/docs/status-pages.html" rel="noopener noreferrer"&gt;Status pages&lt;/a&gt; - for exception handling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ktor.io/docs/serialization.html" rel="noopener noreferrer"&gt;Serialization&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ktor.io/docs/serving-static-content.html" rel="noopener noreferrer"&gt;Serving static content&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ktor.io/docs/cors.html" rel="noopener noreferrer"&gt;CORS&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://ktor.io/docs/jwt.html" rel="noopener noreferrer"&gt;JWT&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To find all other ready-to-use plugins, head over to &lt;a href="https://start.ktor.io/#/settings?name=ktor-sample&amp;amp;website=example.com&amp;amp;artifact=com.example.ktor-sample&amp;amp;kotlinVersion=1.6.10&amp;amp;ktorVersion=1.6.7&amp;amp;buildSystem=GRADLE_KTS&amp;amp;engine=NETTY&amp;amp;configurationIn=CODE&amp;amp;addSampleCode=true&amp;amp;plugins=" rel="noopener noreferrer"&gt;Generate Ktor project&lt;/a&gt; tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build REST API
&lt;/h2&gt;

&lt;p&gt;First, we need to create Ktor project. There are two ways to create one, using online &lt;a href="https://start.ktor.io/#/settings?name=ktor-sample&amp;amp;website=example.com&amp;amp;artifact=com.example.ktor-sample&amp;amp;kotlinVersion=1.6.10&amp;amp;ktorVersion=1.6.7&amp;amp;buildSystem=GRADLE_KTS&amp;amp;engine=NETTY&amp;amp;configurationIn=CODE&amp;amp;addSampleCode=true&amp;amp;plugins=" rel="noopener noreferrer"&gt;Generate Ktor project&lt;/a&gt; tool or, if you prefer, using &lt;a href="https://plugins.jetbrains.com/plugin/16008-ktor" rel="noopener noreferrer"&gt;Ktor plugin&lt;/a&gt; for IntelliJ IDEA.&lt;/p&gt;

&lt;p&gt;Once the project is created, it should already have all necessary dependencies added for Ktor itself, but we need one more for serialization.&lt;/p&gt;

&lt;p&gt;Dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies {
    implementation("io.ktor:ktor-server-core:$ktor_version")
    implementation("io.ktor:ktor-server-netty:$ktor_version")
    implementation("ch.qos.logback:logback-classic:$logback_version")
    implementation("io.ktor:ktor-serialization:$ktor_version")
    testImplementation("io.ktor:ktor-server-tests:$ktor_version")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's keep it simple and add every-tutorial-ever generic &lt;code&gt;Customer&lt;/code&gt; class. We will use Kotlin's data class and &lt;code&gt;kotlinx.serialization&lt;/code&gt; to serialize class to JSON, and from JSON to class in case of &lt;code&gt;POST&lt;/code&gt; method. Since we are keeping it simple, the application will use in-memory storage. For any real-world application, you would use a database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;kotlinx.serialization.Serializable&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;customerStorage&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableListOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@Serializable&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next step is to define routes. We want to respond to &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; requests on the &lt;code&gt;/customer&lt;/code&gt; endpoint. As such, let's define our routes with the corresponding HTTP methods. Let's add a new file Routing.&lt;/p&gt;

&lt;p&gt;Routing.kt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;customerRouting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;routing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/customer"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;get&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNotEmpty&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"No customers found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{id}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@get&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Missing or malformed id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BadRequest&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;customer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
                    &lt;span class="n"&gt;customerStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@get&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="s"&gt;"No customer with id $id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NotFound&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nf"&gt;post&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;customer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
                &lt;span class="n"&gt;customerStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Customer stored correctly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Created&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{id}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@delete&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeIf&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Customer removed correctly"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Accepted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Not Found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Last step is to register previously defined routes and to install &lt;code&gt;ContentNegotiation&lt;/code&gt; plugin so Ktor can handle serialization.&lt;/p&gt;

&lt;p&gt;Application.kt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;):&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ktor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;netty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EngineMain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;module&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContentNegotiation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;customerRouting&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great, our simple REST API is now ready to use and to handle those sweet customer &lt;code&gt;CRUD&lt;/code&gt; operations. :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for reading and I hope this article was useful to you! In conclusion, this article went over Ktor framework, introduction to it and how you can build simple REST API using Ktor and Kotlin. If you are interested in Ktor, head over to official &lt;a href="https://ktor.io/docs/welcome.html" rel="noopener noreferrer"&gt;Ktor documentation&lt;/a&gt; for more thorough guides and explanations.&lt;/p&gt;




&lt;p&gt;If you like my content and find it useful, please consider following me. If you are feeling extra generous, please consider &lt;a href="https://buymeacoffee.com/kfurjan" rel="noopener noreferrer"&gt;buying me a coffee&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Connect with me on &lt;a href="https://www.linkedin.com/in/kevin-furjan/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>rest</category>
      <category>api</category>
      <category>ktor</category>
    </item>
    <item>
      <title>Implement YouTube-like filtering in SwiftUI</title>
      <dc:creator>Kevin Furjan</dc:creator>
      <pubDate>Sun, 12 Dec 2021 10:10:35 +0000</pubDate>
      <link>https://dev.to/kfurjan/implement-youtube-like-filtering-in-swiftui-2efd</link>
      <guid>https://dev.to/kfurjan/implement-youtube-like-filtering-in-swiftui-2efd</guid>
      <description>&lt;p&gt;SwiftUI has its own radio button implementation using &lt;a href="https://developer.apple.com/documentation/swiftui/picker"&gt;Pickers&lt;/a&gt; that come with few default designs, such as &lt;code&gt;.pickerStyle(.segmented)&lt;/code&gt; or &lt;code&gt;.pickerStyle(.wheel)&lt;/code&gt;. Pickers can be useful for implementing filtering functionality but what if you want custom design and behavior, for example YouTube-like design and behavior?&lt;/p&gt;

&lt;p&gt;YouTube-like filtering uses horizontally scrollable view where buttons are placed, each with its own state, and selected button has different color to the others.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JUNvy3zI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1639295034971/oeGtgzbV4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JUNvy3zI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1639295034971/oeGtgzbV4.jpeg" alt="79CB4010-0B70-4ED6-9001-249DAA231B49_4_5005_c.jpeg" width="864" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;Before implementing custom YouTube-like filtering, let's define a few requirements that custom view needs to meet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;view needs to mimic YouTube-like filtering&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;view needs to use enumerations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;view needs to be generic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;view needs to be stateless&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  State handling
&lt;/h2&gt;

&lt;p&gt;For handling state, custom filter view will use &lt;a href="https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html"&gt;Swift Enumerations&lt;/a&gt;. Enumerations in Swift are quite powerful and are an ideal way to filter data displayed on view once each enum case is selected. Custom filter view will also require that enum conforms to a few protocols, such as &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;CaseIterable&lt;/code&gt; and &lt;code&gt;Identifiable&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Let's imagine that the application is showing a list of cars available for purchase, and you would like to filter them based on type. In such a case you would create &lt;code&gt;CarType&lt;/code&gt; enum to define the type of each car in the list.&lt;/p&gt;

&lt;p&gt;CarType:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;CarType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CaseIterable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Identifiable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;family&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;luxury&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;supercar&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explanation of why each protocol is needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;String&lt;/code&gt; - we want each enum case to have its &lt;em&gt;raw value&lt;/em&gt; represented as string, needed for &lt;code&gt;Text&lt;/code&gt; view in SwiftUI&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;CaseIterable&lt;/code&gt; - by conforming to this protocol, you can access a collection of all the type’s cases by using the type’s allCases property, needed for &lt;code&gt;ForEach&lt;/code&gt; loop in SwiftUI&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;Identifiable&lt;/code&gt; - to provide unique &lt;em&gt;id&lt;/em&gt; to each enum state, needed for &lt;code&gt;ForEach&lt;/code&gt; loop in SwiftUI&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementing view
&lt;/h2&gt;

&lt;p&gt;In order to meet previously defined requirements, custom view needs to support generics that match Enumeration model defined in previous step.&lt;/p&gt;

&lt;p&gt;FilterRowView:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;FilterRowView&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RawRepresentable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CaseIterable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Identifiable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;AllCases&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;RawValue&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;selectedFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allCases&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;ScrollView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;horizontal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;showsIndicators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;LazyHStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allCases&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                    &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;withAnimation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Animation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;selectedFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capitalized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subheadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttonStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;FilterButtonStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;isSelected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;selectedFilter&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, view is using custom &lt;code&gt;ButtonStyle&lt;/code&gt; to mimic YouTube-like design.&lt;/p&gt;

&lt;p&gt;FilterButtonStyle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;FilterButtonStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;ButtonStyle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;isSelected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;configuration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;white&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;maxWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;infinity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;maxHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;black&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;white&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;overlay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;RoundedRectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stroke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;white&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;lineWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FilterRowView should now look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LrRPXiXt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1639296653504/NuhW9CXrP.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LrRPXiXt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1639296653504/NuhW9CXrP.png" alt="Screenshot 2021-12-12 at 09.10.01.png" width="880" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's horizontally scrollable view with buttons, each with its own state, and selected button has different color to the others.&lt;/p&gt;

&lt;h2&gt;
  
  
  State hoisting
&lt;/h2&gt;

&lt;p&gt;As you probably noticed yourself, &lt;code&gt;FilterRowView&lt;/code&gt; doesn't really do anything so we need to add the ability to filter data, but the view must be stateless, as stated in the &lt;em&gt;Requirements&lt;/em&gt; section.&lt;/p&gt;

&lt;p&gt;How to do that? Using &lt;strong&gt;state hoisting&lt;/strong&gt; pattern. What is that? Let's explain it.&lt;/p&gt;

&lt;p&gt;State hoisting is a well-known pattern for developing &lt;a href="https://reactpatterns.js.org/docs/state-hoisting/"&gt;React applications&lt;/a&gt;, and as of recently, Google is recommending using it when developing &lt;a href="https://developer.android.com/jetpack/compose/state"&gt;Jetpack Compose&lt;/a&gt; applications.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;State hoisting is a pattern of moving state to a caller to make view stateless.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In practice that means using higher order functions, which in Swift terms, means using &lt;a href="https://docs.swift.org/swift-book/LanguageGuide/Closures.html"&gt;closures&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To start using state hoisting pattern in &lt;code&gt;FilterRowView&lt;/code&gt;, we only need two more lines of code. One line for new parameter which will accept closure and one line to provide generic type to closure.&lt;/p&gt;

&lt;p&gt;Updated FilterRowView:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;FilterRowView&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt;
    &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;RawRepresentable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CaseIterable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Identifiable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;AllCases&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;RawValue&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;selectedFilter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allCases&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;ScrollView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;horizontal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;showsIndicators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;LazyHStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allCases&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
                    &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="nf"&gt;withAnimation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Animation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;speed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="n"&gt;selectedFilter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                        &lt;span class="nf"&gt;block&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rawValue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capitalized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subheadline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttonStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;FilterButtonStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;isSelected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;selectedFilter&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, &lt;code&gt;FilterRowView&lt;/code&gt; is completely stateless and to do anything useful, it relies on its caller.&lt;/p&gt;

&lt;p&gt;To use this in your application, you would call &lt;code&gt;FilterRowView&lt;/code&gt; view with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;FilterRowView&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;CarType&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;block&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filterCars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for reading and I hope this article was useful to you! In conclusion, this article went over how to implement custom YouTube-like filtering in SwiftUI using enumerations, generics, and closures.&lt;/p&gt;




&lt;p&gt;If you like my content and find it useful, please consider following me. If you are feeling extra generous, please consider &lt;a href="https://buymeacoffee.com/kfurjan"&gt;buying me a coffee&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Connect with me on &lt;a href="https://www.linkedin.com/in/kevin-furjan/"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>ux</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to add Lottie animations to SwiftUI</title>
      <dc:creator>Kevin Furjan</dc:creator>
      <pubDate>Thu, 02 Dec 2021 21:18:06 +0000</pubDate>
      <link>https://dev.to/kfurjan/how-to-add-lottie-animations-to-swiftui-1j22</link>
      <guid>https://dev.to/kfurjan/how-to-add-lottie-animations-to-swiftui-1j22</guid>
      <description>&lt;p&gt;Using Lottie animations is an easy and popular way to add animations to your app to have that modern look and feel. All sorts of companies use Lottie animations, from startups all the way to the big boys, such as Google, Microsoft, Spotify, and Netflix to name the few.&lt;/p&gt;

&lt;p&gt;Using LottieFiles you have a choice to make your own animations using &lt;a href="https://lottiefiles.com/editor"&gt;Lottie Editor&lt;/a&gt;, buying animations from creators on &lt;a href="https://lottiefiles.com/marketplace"&gt;Lottie Marketplace&lt;/a&gt; or using &lt;a href="https://lottiefiles.com/featured"&gt;Free Animations&lt;/a&gt; to get LottieFile(s) completely free of charge to add them into you project.&lt;/p&gt;

&lt;h2&gt;
  
  
  UIKit and SwiftUI interoperability
&lt;/h2&gt;

&lt;p&gt;Unfortunately, Lottie animations cannot be added directly to SwiftUI view without some work prior to that. In order to add Lottie animation to SwiftUI, you need to implement &lt;code&gt;UIViewRepresentable&lt;/code&gt; protocol which is a wrapper for a UIKit view that you use to integrate Lottie view into your SwiftUI view hierarchy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;

&lt;p&gt;Let's get straight to the point, just make sure you &lt;a href="https://github.com/airbnb/lottie-ios#installing-lottie"&gt;previously installed&lt;/a&gt;  &lt;code&gt;lottie-ios&lt;/code&gt; library using your preferred package manager, whether it is CocoaPods, Carthage or Swift Package Manager.&lt;/p&gt;

&lt;p&gt;Add new file named LottieView and add following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Lottie&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;LottieView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewRepresentable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;animationView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AnimationView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeUIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewRepresentableContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;LottieView&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;animation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Animation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contentMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scaleAspectFill&lt;/span&gt;
        &lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loopMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loop&lt;/span&gt;

        &lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translatesAutoresizingMaskIntoConstraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="kt"&gt;NSLayoutConstraint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heightAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heightAnchor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widthAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widthAnchor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updateUIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;uiView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewRepresentableContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;LottieView&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All you need to do know is add following code to your SwiftUI view: &lt;code&gt;LottieView(fileName: "your-file-name")&lt;/code&gt; and that's it, you have fully functioning Lottie animation inside SwiftUI view. &lt;/p&gt;

&lt;h2&gt;
  
  
  How do I make it to play or pause
&lt;/h2&gt;

&lt;p&gt;In order to make animation play or pause, a bit more coding is required. It is required to provide &lt;em&gt;Coordinator&lt;/em&gt; to your LottieView. &lt;/p&gt;

&lt;p&gt;What is &lt;em&gt;Coordinator&lt;/em&gt;? Let's see what Apple says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The system doesn’t automatically communicate changes occurring within your view to other parts of your SwiftUI interface. When you want your view to coordinate with other SwiftUI views, you must provide a Coordinator instance to facilitate those interactions. For example, you use a coordinator to forward target-action and delegate messages from your view to any SwiftUI views.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's see updated code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Lottie&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;LottieView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewRepresentable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;isEnabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;animationView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;AnimationView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeUIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewRepresentableContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;LottieView&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;view&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;animation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Animation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;named&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contentMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scaleAspectFill&lt;/span&gt;
        &lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loopMode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loop&lt;/span&gt;

        &lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translatesAutoresizingMaskIntoConstraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="kt"&gt;NSLayoutConstraint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
            &lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heightAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;heightAnchor&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widthAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;widthAnchor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;updateUIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;uiView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewRepresentableContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;LottieView&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isEnabled&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coordinator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;animationView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Coordinator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSObject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;LottieView&lt;/span&gt;

        &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;LottieView&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;makeCoordinator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Coordinator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;Coordinator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this is it! You can now pass &lt;code&gt;isEnabled&lt;/code&gt; &lt;em&gt;Bool&lt;/em&gt; value to LottieView to make it play or pause.&lt;/p&gt;

&lt;h2&gt;
  
  
  One final gotcha
&lt;/h2&gt;

&lt;p&gt;If you are using &lt;a href="https://developer.apple.com/documentation/swiftui/tabview"&gt;TabView&lt;/a&gt; withing your SwiftUI application and you are switching between tabs you will notice that animations won't start playing automatically when you switch back to view with animations.&lt;/p&gt;

&lt;p&gt;All you must do is add &lt;code&gt;animationView.backgroundBehavior = .pauseAndRestore&lt;/code&gt; to &lt;code&gt;makeUIView(context:)&lt;/code&gt; method. Now animations will &lt;em&gt;pause and restore&lt;/em&gt; as the code itself suggests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for reading and I hope this article was useful to you! In conclusion, this article went over how to add Lottie animation(s) to SwiftUI view, make it reactive if needed and how to use Lottie animations with TabView.&lt;/p&gt;




&lt;p&gt;If you like my content and find it useful, please consider following me here on DEV Community. If you are feeling extra generous, please consider &lt;a href="https://buymeacoffee.com/kfurjan"&gt;buying me a coffee&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Connect with me on &lt;a href="https://www.linkedin.com/in/kevin-furjan/"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>ux</category>
      <category>design</category>
    </item>
    <item>
      <title>Create SwiftUI app with Firebase Authentication</title>
      <dc:creator>Kevin Furjan</dc:creator>
      <pubDate>Sun, 28 Nov 2021 15:39:20 +0000</pubDate>
      <link>https://dev.to/kfurjan/integrate-firebase-authentication-with-google-sign-in-to-swiftui-app-1g4i</link>
      <guid>https://dev.to/kfurjan/integrate-firebase-authentication-with-google-sign-in-to-swiftui-app-1g4i</guid>
      <description>&lt;p&gt;SwiftUI framework is already in its third iteration and with that, it is well and truly on its way to production-ready applications. Such applications are certainly going to need some sort of authentication. Since authenticating users can be tricky, you may want to rely on third-party solutions for that, such as Firebase. Question is then, how does one integrate Firebase and Google Sign-In to SwiftUI application?&lt;/p&gt;

&lt;p&gt;This article will show you how to create iOS application using SwiftUI, create Firebase project and then use Firebase Authentication to integrate Google Sign-In SDK to your SwiftUI application.&lt;/p&gt;

&lt;p&gt;Within this article we will cover following topics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Creation of Firebase project&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creation of SwiftUI Lifecycle application&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handling dependencies with CocoaPods&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handling authentication within code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Designing UI elements&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creation of Firebase project
&lt;/h2&gt;

&lt;p&gt;In order to use Firebase in our SwiftUI app, we need to create new Firebase project in &lt;a href="https://console.firebase.google.com/"&gt;Firebase console&lt;/a&gt;. Create new project by clicking on &lt;em&gt;Add project&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Once &lt;em&gt;Create a project&lt;/em&gt; page opens, enter project name.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--moT-HIho--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1637850709945/kfO4wNtGn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--moT-HIho--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1637850709945/kfO4wNtGn.png" alt="Screenshot 2021-11-25 at 15.30.35.png" width="880" height="716"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next step is either enabling or disabling Google Analytics. It is &lt;strong&gt;not&lt;/strong&gt; necessary for this demo, so it's up to you to enable it.&lt;/p&gt;

&lt;p&gt;When project's Dashboard opens, click on &lt;strong&gt;iOS icon&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jgUeTyjc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1637851061945/BFZkjwsNV.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jgUeTyjc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1637851061945/BFZkjwsNV.png" alt="Screenshot 2021-11-25 at 15.37.28.png" width="880" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, fill out form for registering the app. Apple Bundle ID is important since it needs to be the same as bundle identifier, we will use later in Xcode project. Other two fields are optional. App Store ID is only needed if app will be uploaded to the App Store which we are not doing in this tutorial.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9i2cZl5_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1638088881121/aNT2PW8G5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9i2cZl5_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1638088881121/aNT2PW8G5.png" alt="Screenshot 2021-11-28 at 09.40.27.png" width="880" height="1257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on &lt;strong&gt;Register app&lt;/strong&gt;. &lt;br&gt;
Next, we need to download &lt;em&gt;GoogleService-Info.plist&lt;/em&gt; file which will be added to Xcode project later.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--h6WS7hJl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1637851549327/fklRVppB8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--h6WS7hJl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1637851549327/fklRVppB8.png" alt="Screenshot 2021-11-25 at 15.45.33.png" width="880" height="913"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In steps 3 and 4 just click &lt;strong&gt;Next&lt;/strong&gt; since we will cover them in more detail later in the tutorial. To finish registering the app, click &lt;strong&gt;Continue to console&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Almost finished with setting up Firebase project. Lastly, we need to enable &lt;em&gt;Authentication&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Click on &lt;strong&gt;Authentication&lt;/strong&gt;, and then on &lt;strong&gt;Get started&lt;/strong&gt;.&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vrZslFYq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1637851840818/h-9R4RnYL.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vrZslFYq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1637851840818/h-9R4RnYL.png" alt="Screenshot 2021-11-25 at 15.50.15.png" width="880" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now choose Google as Sign-in method provider.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ogJ1PoXy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1637851980924/2k_9dzyN_.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ogJ1PoXy--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1637851980924/2k_9dzyN_.png" alt="Screenshot 2021-11-25 at 15.52.26.png" width="880" height="295"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, enable it by toggling &lt;strong&gt;Enable&lt;/strong&gt; button, add &lt;strong&gt;Project public-facing name&lt;/strong&gt; and &lt;strong&gt;Project support email&lt;/strong&gt;. Save this changes by clicking on &lt;strong&gt;Save&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JJ2LVv4r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1637852114664/phYZP9gIy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JJ2LVv4r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1637852114664/phYZP9gIy.png" alt="Screenshot 2021-11-25 at 15.53.33.png" width="880" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We are done with Firebase settings and now we are moving to actual coding!&lt;/p&gt;
&lt;h2&gt;
  
  
  Creation of SwiftUI Lifecycle application
&lt;/h2&gt;

&lt;p&gt;Next step is to actually create Xcode project. Open &lt;strong&gt;Xcode&lt;/strong&gt;, select option &lt;strong&gt;Create a new Xcode project&lt;/strong&gt;. Once new window opens, make sure that &lt;strong&gt;iOS&lt;/strong&gt; is selected in header and then select &lt;strong&gt;App&lt;/strong&gt;. Click &lt;strong&gt;Next&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now we need enter some details about project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Product Name: demo&lt;/li&gt;
&lt;li&gt;Organization Identifier: com.firebase&lt;/li&gt;
&lt;li&gt;Interface: SwiftUI&lt;/li&gt;
&lt;li&gt;Language: Swift&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Core Data and Tests are not needed for this tutorial, so it is up to you to include them into the project. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--toZdm6yn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1638088943581/ikMsr8pQZ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--toZdm6yn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1638088943581/ikMsr8pQZ.png" alt="Screenshot 2021-11-28 at 09.38.27.png" width="880" height="563"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Next&lt;/strong&gt; and then choose folder where you want to save Xcode project. &lt;/p&gt;

&lt;p&gt;Great! We are ready for next section, handling dependencies with CocoaPods.&lt;/p&gt;
&lt;h2&gt;
  
  
  Handling dependencies with CocoaPods
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;CocoaPods is a dependency manager for Swift and Objective-C Cocoa projects. It has over 86 thousand libraries and is used in over 3 million apps. CocoaPods can help you scale your projects elegantly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To handle project dependencies, this tutorial will use CocoaPods. If CocoaPods is not installed on your development machine, follow this &lt;a href="https://guides.cocoapods.org/using/getting-started.html#getting-started"&gt;Getting Started&lt;/a&gt; guide on official CocoaPods page.&lt;/p&gt;

&lt;p&gt;Using &lt;strong&gt;Terminal&lt;/strong&gt;, navigate to your project. Use command similar to this one: &lt;code&gt;cd your/project/path&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Once in your project path, initialize empty pod file by executing &lt;code&gt;pod init&lt;/code&gt; command. Open newly created &lt;em&gt;Podfile&lt;/em&gt; to add dependencies needed for this project.&lt;/p&gt;

&lt;p&gt;Add following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;target 'swiftui' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for swiftui
  pod 'Firebase/Core'
  pod 'Firebase/Auth'
  pod 'GoogleSignIn'
  pod 'SDWebImageSwiftUI'

end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To install these dependencies, execute &lt;code&gt;pod install&lt;/code&gt; command. &lt;/p&gt;

&lt;p&gt;Awesome! Project setup is done, and its dependencies are successfully installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling authentication within code
&lt;/h2&gt;

&lt;p&gt;First step is to take &lt;em&gt;GoogleService-Info.plist&lt;/em&gt; file we downloaded it in &lt;em&gt;Creation of Firebase project&lt;/em&gt; section and drag&amp;amp;drop it into Xcode project. Once file is added to the project, we need to add &lt;strong&gt;REVERSED_CLIENT_ID&lt;/strong&gt; key to project configuration. &lt;/p&gt;

&lt;h3&gt;
  
  
  Handling REVERSED_CLIENT_ID key
&lt;/h3&gt;

&lt;p&gt;Do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open your project configuration: double-click the project name in the left tree view. Select your app from the &lt;strong&gt;TARGETS&lt;/strong&gt; section, then select the &lt;strong&gt;Info&lt;/strong&gt; tab, and expand the &lt;strong&gt;URL Types&lt;/strong&gt; section. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click the + button, and add a URL scheme for your reversed client ID. To find this value, open the &lt;em&gt;GoogleService-Info.plist&lt;/em&gt; configuration file, and look for the &lt;strong&gt;REVERSED_CLIENT_ID&lt;/strong&gt; key. Copy the value of that key and paste it into the &lt;strong&gt;URL Schemes&lt;/strong&gt; box on the configuration page. Leave the other fields blank.&lt;br&gt;
When completed, your config should look something similar to the following (but with your application-specific values):&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7ofpnhJu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1638045931556/1MejF97k0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7ofpnhJu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1638045931556/1MejF97k0.png" alt="Screenshot 2021-11-27 at 21.45.09.png" width="880" height="234"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we can move on to some coding! &lt;/p&gt;

&lt;h3&gt;
  
  
  Adding AppDelegate
&lt;/h3&gt;

&lt;p&gt;Since iOS 14 and SwiftUI 2.0, Apple added ability to include AppDelegate to SwiftUI Lifecycle app which we will need to add to our project.&lt;/p&gt;

&lt;p&gt;Right click on project in Xcode left tree view and then select &lt;strong&gt;New file...&lt;/strong&gt;. Add new Swift file named &lt;strong&gt;AppDelegate&lt;/strong&gt;. Once create, add following contents to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Firebase
import GoogleSignIn

class AppDelegate: NSObject, UIApplicationDelegate {

    func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
    ) -&amp;gt; Bool {
        FirebaseApp.configure()
        return true
    }

    func application(
      _ app: UIApplication,
      open url: URL,
      options: [UIApplication.OpenURLOptionsKey : Any] = [:]
    ) -&amp;gt; Bool {
        return GIDSignIn.sharedInstance.handle(url)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding helper functions
&lt;/h3&gt;

&lt;p&gt;For &lt;code&gt;signInWithConfiguration:presentingViewController:callback:&lt;/code&gt; method we will use later, we need to provide &lt;em&gt;presenting ViewController&lt;/em&gt; and &lt;em&gt;GIDConfiguration&lt;/em&gt; configuration thus we need to implement two helper functions.&lt;/p&gt;

&lt;p&gt;GIDConfigurationExtensions.swift:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Firebase
import GoogleSignIn

extension GIDConfiguration {

    /// Get GIDConfiguration instance by providing clientID from GoogleService-Info.plist file
    /// - Returns: GIDConfiguration instance
    static func getGIDConfigurationInstance() -&amp;gt; GIDConfiguration {
        GIDConfiguration(clientID: FirebaseApp.app()?.options.clientID ?? "")
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;UIApplicationExtensions.swift&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import UIKit

extension UIApplication {

    /// Get root UIViewController of application. If for whatever reason, UIViewController cannot be accessed,
    /// invoke fatalError() since UIViewController instance is crucial for application to work properly.
    /// - Returns: root UIViewController
    static func getRootViewController() throws -&amp;gt; UIViewController {
        let scenes = UIApplication.shared.connectedScenes
        let windowScene = scenes.first as? UIWindowScene
        let window = windowScene?.windows.first

        guard let uiViewController = window?.rootViewController else { fatalError() }
        return uiViewController
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this finished, we can move start implementing ViewModel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementing LoginViewModel
&lt;/h3&gt;

&lt;p&gt;Create new Swift file named LoginViewModel.&lt;/p&gt;

&lt;p&gt;LoginViewModel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import Firebase
import GoogleSignIn

final class LoginViewModel: ObservableObject {

    @Published var isUserLoggedIn = false

    func signIn() {
        do {
            let config = GIDConfiguration.getGIDConfigurationInstance()
            let uiViewController = try UIApplication.getRootViewController()

            GIDSignIn.sharedInstance.signIn(with: config, presenting: uiViewController) { user, error in
                guard error == nil else { return }
                guard user != nil else { return }
                self.isUserLoggedIn = true
          }
        } catch {
            print(error.localizedDescription)
        }
    }

    func signOut() {
        GIDSignIn.sharedInstance.signOut()
        isUserLoggedIn = false
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Designing UI elements
&lt;/h2&gt;

&lt;p&gt;Now comes last piece of the puzzle. Designing UI elements using SwiftUI framework and connecting UI with business logic we implemented earlier.&lt;/p&gt;

&lt;h3&gt;
  
  
  LoginView
&lt;/h3&gt;

&lt;p&gt;Create new &lt;strong&gt;SwiftUI View&lt;/strong&gt; file named LoginView.&lt;/p&gt;

&lt;p&gt;LoginView:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import SwiftUI

struct LoginView: View {

    @EnvironmentObject var viewModel: LoginViewModel

    var body: some View {
        VStack {
            Spacer()

            Button(action: {
                withAnimation {
                    viewModel.signIn()
                }
            }) {
                HStack {
                    Text("Sign in with google")
                        .fontWeight(.bold)
                }
            }
            .foregroundColor(.white)
            .padding()
            .frame(maxWidth: .infinity)
            .background(.black)
            .cornerRadius(12)
            .padding()

            Spacer()
        }
    }
}

struct LoginView_Previews: PreviewProvider {
    static var previews: some View {
        LoginView()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LoginView will look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--JHOdnUDG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1638113786143/swXZQnBWh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--JHOdnUDG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1638113786143/swXZQnBWh.png" alt="Simulator Screen Shot - iPhone 12 - 2021-11-28 at 10.41.25.png" width="880" height="1904"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  HomeView
&lt;/h3&gt;

&lt;p&gt;Create new &lt;strong&gt;SwiftUI View&lt;/strong&gt; file named HomeView.&lt;/p&gt;

&lt;p&gt;HomeView:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import GoogleSignIn
import SwiftUI
import SDWebImageSwiftUI

struct UserDetailView: View {

    let user: GIDGoogleUser?

    var body: some View {
        VStack {
            HStack {
                Text(user?.profile?.name ?? "")
                    .bold()
                    .font(.title2)

                Spacer()

                WebImage(url: user?.profile?.imageURL(withDimension: 75))
                    .resizable()
                    .placeholder(
                        Image(systemName: "person.fill")
                    )
                    .indicator(.activity)
                    .transition(.fade(duration: 0.5))
                    .frame(width: 75, height: 75, alignment: .center)
                    .scaledToFit()
                    .clipShape(Circle())
            }
            HStack {
                Text(user?.profile?.email ?? "")
                    .bold()
                    .font(.subheadline)
                Spacer()
            }

            Spacer()
        }
        .padding()
        .frame(height: 150, alignment: .center)
        .background(.thickMaterial)
        .cornerRadius(12)
    }
}

struct HomeView: View {

    @EnvironmentObject var viewModel: LoginViewModel
    private let user = GIDSignIn.sharedInstance.currentUser

    var body: some View {
        VStack {
            UserDetailView(user: user)

            Spacer()

            Button(action: {
                withAnimation {
                    viewModel.signOut()
                }
            }) {
                HStack {
                    Text("Sign out")
                        .fontWeight(.bold)
                }
            }
            .foregroundColor(.white)
            .padding()
            .frame(maxWidth: .infinity)
            .background(.black)
            .cornerRadius(12)
            .padding()
        }
        .padding()
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;HomeView will look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2YYacQ4b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1638113753667/mpxTyhD8e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2YYacQ4b--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn.hashnode.com/res/hashnode/image/upload/v1638113753667/mpxTyhD8e.png" alt="Simulator Screen Shot - iPhone 12 - 2021-11-28 at 10.41.52.png" width="880" height="1904"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting all together
&lt;/h3&gt;

&lt;p&gt;All that is left to do is to connect it all together in &lt;strong&gt;DemoApp&lt;/strong&gt; and &lt;strong&gt;ContentView&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;DemoApp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import SwiftUI

@main
struct DemoApp: App {

    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    @StateObject var viewModel = LoginViewModel()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(viewModel)
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ContentView:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import SwiftUI

struct ContentView: View {

    @EnvironmentObject var viewModel: LoginViewModel

    var body: some View {
        if viewModel.isUserLoggedIn {
            HomeView()
        } else {
            LoginView()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is it! Integration of Firebase Authentication with Google Sign-In to SwiftUI application is done and our project is ready to be run on emulator or physical device!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for reading and and I hope this article was useful for you since integrating Firebase Authentication with SwiftUI Lifecycle application is not exactly straightforward.&lt;/p&gt;




&lt;p&gt;If you like my content and find it useful, please consider following me here on DEV community. If you are feeling extra generous, please consider &lt;a href="https://buymeacoffee.com/kfurjan"&gt;buying me a coffee&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Connect with me on &lt;a href="https://www.linkedin.com/in/kevin-furjan/"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>firebase</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
