Jekyll2023-09-22T02:40:40+00:00http://exploringswift.com/feed.xmlExploring SwiftLearning and Experimenting with Swift.Implementing Core Data In SwiftUI Using MVVM Architecture2023-01-23T00:00:00+00:002023-01-23T00:00:00+00:00http://exploringswift.com/blog/Implementing-Core-Data-in-SwiftUI-using-MVVM-architecture<p>I would like to start off by saying that this is not the only way to implement CoreData and there are various ways you can do it. This is just my way of implementing CoreData using MVVM architecture. Any suggestions, or feedback is highly appreciated.
<!--more--></p>
<p><img width="858" alt="Screenshot 2023-01-23 at 2 20 53 PM" src="https://user-images.githubusercontent.com/38010678/214130391-30ab3426-b5c4-4482-a01f-f161564b9e3a.png" /></p>
<h2>Model-View-ViewModel Architecture</h2>
<p>With SwiftUI and Combine, we are beginning to see more and more projects using MVVM architecture. Here are the basics of such an architecture. We want the View and the ViewModel to communicate with each other. The view observe certain properties in the ViewModel, and when those properties change, the view gets reconfigured. The model is basically a simple domain object that we use to organise our data.</p>
<p>In this project, the CoreData is storing all of our data, and we will use viewModel to implement the logic for fetching, adding, deleting and updating data. The view will ask the viewModel to perform the aforementioned tasks, and when the viewModel perform those tasks it will tell the view to reconfigure itself to reflect the changes.</p>
<p>While CoreData comes with property wrappers such as <code class="language-plaintext highlighter-rouge">@FetchRequest</code> that helps us fetch entity data from core data, this property wrapper only works inside a <code class="language-plaintext highlighter-rouge">View</code> and not inside a <code class="language-plaintext highlighter-rouge">ObservableObject</code> which our ViewModel is. So, we will create our own fetch request method and update the <code class="language-plaintext highlighter-rouge">@Published</code> once we get the data.</p>
<p>Our project also contains a <code class="language-plaintext highlighter-rouge">One to Many</code> relationship between two entities - <code class="language-plaintext highlighter-rouge">Company -->> Employees</code>. We will implement a viewModel that takes care of such a relationship as well.</p>
<h3>Core Data Setup </h3>
<p>When you create an Xcode project using SwiftUI and CoreData, Xcode generates a <code class="language-plaintext highlighter-rouge">PersistenceController</code> class which is a singleton. This singleton will help us access <code class="language-plaintext highlighter-rouge">ViewContext</code> across various ViewModel files. This is how the <code class="language-plaintext highlighter-rouge">PersistenceController</code> file will look like:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">CoreData</span>
<span class="kd">struct</span> <span class="kt">PersistenceController</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">let</span> <span class="nv">shared</span> <span class="o">=</span> <span class="kt">PersistenceController</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">container</span><span class="p">:</span> <span class="kt">NSPersistentContainer</span>
<span class="k">var</span> <span class="nv">viewContext</span><span class="p">:</span> <span class="kt">NSManagedObjectContext</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">container</span><span class="o">.</span><span class="n">viewContext</span>
<span class="p">}</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">inMemory</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span><span class="p">)</span> <span class="p">{</span>
<span class="n">container</span> <span class="o">=</span> <span class="kt">NSPersistentContainer</span><span class="p">(</span><span class="nv">name</span><span class="p">:</span> <span class="s">"CoreDataDemo"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">inMemory</span> <span class="p">{</span>
<span class="n">container</span><span class="o">.</span><span class="n">persistentStoreDescriptions</span><span class="o">.</span><span class="n">first</span><span class="o">!.</span><span class="n">url</span> <span class="o">=</span> <span class="kt">URL</span><span class="p">(</span><span class="nv">fileURLWithPath</span><span class="p">:</span> <span class="s">"/dev/null"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">container</span><span class="o">.</span><span class="nf">loadPersistentStores</span><span class="p">(</span><span class="nv">completionHandler</span><span class="p">:</span> <span class="p">{</span> <span class="p">(</span><span class="n">storeDescription</span><span class="p">,</span> <span class="n">error</span><span class="p">)</span> <span class="k">in</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">error</span> <span class="o">=</span> <span class="n">error</span> <span class="k">as</span> <span class="kt">NSError</span><span class="p">?</span> <span class="p">{</span>
<span class="nf">fatalError</span><span class="p">(</span><span class="s">"Unresolved error </span><span class="se">\(</span><span class="n">error</span><span class="se">)</span><span class="s">, </span><span class="se">\(</span><span class="n">error</span><span class="o">.</span><span class="n">userInfo</span><span class="se">)</span><span class="s">"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="n">container</span><span class="o">.</span><span class="n">viewContext</span><span class="o">.</span><span class="n">automaticallyMergesChangesFromParent</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Next head over to the CoreData data model file and add an entity and attributes. We will add two entities. First will be <code class="language-plaintext highlighter-rouge">Company</code> that will have three properties: <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">title</code>, <code class="language-plaintext highlighter-rouge">owner</code>. The other entity will be <code class="language-plaintext highlighter-rouge">Employee</code> which will contain two attributes <code class="language-plaintext highlighter-rouge">id</code>, <code class="language-plaintext highlighter-rouge">name</code> - Set up the relation between company and employee as one to many and generate the class files, we will tweak these files later on.</p>
<h3>View Models</h3>
<p>So, now that our CoreData model files have been generated and set up. Now its time to create the ViewModels. Probably the most important part of this whole architecture. Let’s start with <code class="language-plaintext highlighter-rouge">CompanyViewModel</code>.</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">import</span> <span class="kt">Foundation</span>
<span class="kd">import</span> <span class="kt">CoreData</span>
<span class="kd">class</span> <span class="kt">CompanyViewModel</span><span class="p">:</span> <span class="kt">ObservableObject</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">viewContext</span> <span class="o">=</span> <span class="kt">PersistenceController</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">viewContext</span>
<span class="kd">@Published</span> <span class="k">var</span> <span class="nv">companyArray</span><span class="p">:</span> <span class="p">[</span><span class="kt">Company</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
<span class="nf">init</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">fetchCompanyData</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">fetchCompanyData</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">request</span> <span class="o">=</span> <span class="kt">NSFetchRequest</span><span class="o"><</span><span class="kt">Company</span><span class="o">></span><span class="p">(</span><span class="nv">entityName</span><span class="p">:</span> <span class="s">"Company"</span><span class="p">)</span>
<span class="k">do</span> <span class="p">{</span>
<span class="n">companyArray</span> <span class="o">=</span> <span class="k">try</span> <span class="n">viewContext</span><span class="o">.</span><span class="nf">fetch</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
<span class="p">}</span><span class="k">catch</span> <span class="p">{</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"DEBUG: Some error occured while fetching"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">addDataToCoreData</span><span class="p">(</span><span class="nv">companyTitle</span><span class="p">:</span> <span class="kt">String</span><span class="p">,</span> <span class="nv">companyOwner</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">company</span> <span class="o">=</span> <span class="kt">Company</span><span class="p">(</span><span class="nv">context</span><span class="p">:</span> <span class="n">viewContext</span><span class="p">)</span>
<span class="n">company</span><span class="o">.</span><span class="n">id</span> <span class="o">=</span> <span class="kt">UUID</span><span class="p">()</span>
<span class="n">company</span><span class="o">.</span><span class="n">title</span> <span class="o">=</span> <span class="n">companyTitle</span>
<span class="n">company</span><span class="o">.</span><span class="n">owner</span> <span class="o">=</span> <span class="n">companyOwner</span>
<span class="nf">save</span><span class="p">()</span>
<span class="k">self</span><span class="o">.</span><span class="nf">fetchCompanyData</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">save</span><span class="p">()</span> <span class="p">{</span>
<span class="k">do</span> <span class="p">{</span>
<span class="k">try</span> <span class="n">viewContext</span><span class="o">.</span><span class="nf">save</span><span class="p">()</span>
<span class="p">}</span><span class="k">catch</span> <span class="p">{</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"Error saving"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The above ViewModel file has one published property which is an array of type <code class="language-plaintext highlighter-rouge">Company</code>. Then we have a <code class="language-plaintext highlighter-rouge">fetchCompanyData</code> function in which we have created a <code class="language-plaintext highlighter-rouge">NSFetchRequest</code>. The beauty of doing the whole Core Data fetch this way is that we can implement custom sorting descriptors and predicate inside this ViewModel. So, if a user wants to order the items in a particular way, we can pass in the parameter, generate a predicate and pass it to our fetch request. We can access our <code class="language-plaintext highlighter-rouge">ViewContext</code> the main class that allows us to communicate with the persistence container using the <code class="language-plaintext highlighter-rouge">PersistenceController</code> singleton. We pass our fetch request and get the array of type company. We assign it to the <code class="language-plaintext highlighter-rouge">@Published</code> property and thats it!</p>
<p>Similarly, we can add data to the db using the <code class="language-plaintext highlighter-rouge">addDataToCoreData(companyTitle: String, companyOwner: String)</code> function.</p>
<h4>Employees View Model </h4>
<p>This one is interesting, since this is connected to the company entity. So, we need access to the Company entity. Here is how we can implement such a ViewModel:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">EmployeesViewModel</span><span class="p">:</span> <span class="kt">ObservableObject</span> <span class="p">{</span>
<span class="kd">private</span> <span class="k">let</span> <span class="nv">viewContext</span> <span class="o">=</span> <span class="kt">PersistenceController</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">viewContext</span>
<span class="kd">@Published</span> <span class="k">var</span> <span class="nv">employeesArray</span> <span class="o">=</span> <span class="p">[</span><span class="kt">Employee</span><span class="p">]()</span>
<span class="k">var</span> <span class="nv">company</span><span class="p">:</span> <span class="kt">Company</span> <span class="c1">// (NSManagedObject)</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">company</span><span class="p">:</span> <span class="kt">Company</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">company</span> <span class="o">=</span> <span class="n">company</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">fetchEmployees</span><span class="p">()</span> <span class="p">{</span>
<span class="n">employeesArray</span> <span class="o">=</span> <span class="n">company</span><span class="o">.</span><span class="n">employeesArray</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">addEmployee</span><span class="p">(</span><span class="nv">employeeName</span><span class="p">:</span> <span class="kt">String</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">employee</span> <span class="o">=</span> <span class="kt">Employee</span><span class="p">(</span><span class="nv">context</span><span class="p">:</span> <span class="n">viewContext</span><span class="p">)</span>
<span class="n">employee</span><span class="o">.</span><span class="n">id</span> <span class="o">=</span> <span class="kt">UUID</span><span class="p">()</span>
<span class="n">employee</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">employeeName</span>
<span class="n">company</span><span class="o">.</span><span class="nf">addToEmployees</span><span class="p">(</span><span class="n">employee</span><span class="p">)</span>
<span class="nf">save</span><span class="p">()</span>
<span class="nf">fetchEmployees</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">save</span><span class="p">()</span> <span class="p">{</span>
<span class="k">do</span> <span class="p">{</span>
<span class="k">try</span> <span class="n">viewContext</span><span class="o">.</span><span class="nf">save</span><span class="p">()</span>
<span class="p">}</span><span class="k">catch</span> <span class="p">{</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"Error saving"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Before, explaining the above code let’s tweak the NSManagedObject files the Xcode generated for us. Since, there is one to many relationship between <code class="language-plaintext highlighter-rouge">Company</code> and <code class="language-plaintext highlighter-rouge">Employee</code>, the company model contains an <code class="language-plaintext highlighter-rouge">NSSet</code> of type <code class="language-plaintext highlighter-rouge">Employee</code> called <code class="language-plaintext highlighter-rouge">Employees</code>. We will convert this to an array so that we can use it with SwiftUI easily:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">Company</span> <span class="p">{</span>
<span class="kd">@nonobjc</span> <span class="kd">public</span> <span class="kd">class</span> <span class="kd">func</span> <span class="nf">fetchRequest</span><span class="p">()</span> <span class="o">-></span> <span class="kt">NSFetchRequest</span><span class="o"><</span><span class="kt">Company</span><span class="o">></span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">NSFetchRequest</span><span class="o"><</span><span class="kt">Company</span><span class="o">></span><span class="p">(</span><span class="nv">entityName</span><span class="p">:</span> <span class="s">"Company"</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">@NSManaged</span> <span class="kd">public</span> <span class="k">var</span> <span class="nv">id</span><span class="p">:</span> <span class="kt">UUID</span><span class="p">?</span>
<span class="kd">@NSManaged</span> <span class="kd">public</span> <span class="k">var</span> <span class="nv">owner</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span>
<span class="kd">@NSManaged</span> <span class="kd">public</span> <span class="k">var</span> <span class="nv">title</span><span class="p">:</span> <span class="kt">String</span><span class="p">?</span>
<span class="kd">@NSManaged</span> <span class="kd">public</span> <span class="k">var</span> <span class="nv">employees</span><span class="p">:</span> <span class="kt">NSSet</span><span class="p">?</span>
<span class="kd">public</span> <span class="k">var</span> <span class="nv">employeesArray</span><span class="p">:</span> <span class="p">[</span><span class="kt">Employee</span><span class="p">]</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">employeeSet</span> <span class="o">=</span> <span class="n">employees</span> <span class="k">as?</span> <span class="kt">Set</span><span class="o"><</span><span class="kt">Employee</span><span class="o">></span> <span class="p">??</span> <span class="p">[]</span>
<span class="k">return</span> <span class="n">employeeSet</span><span class="o">.</span><span class="n">sorted</span> <span class="p">{</span>
<span class="nv">$0</span><span class="o">.</span><span class="n">unwrappedName</span> <span class="o">></span> <span class="nv">$1</span><span class="o">.</span><span class="n">unwrappedName</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Here we are creating a computed property of <code class="language-plaintext highlighter-rouge">employeesArray</code> that returns the <code class="language-plaintext highlighter-rouge">NSSet</code> as an array. Now, lets get back to the ViewModel. We will use dependency injection to initialize the class. We will pass the <code class="language-plaintext highlighter-rouge">Company</code> entity to the ViewModel, and then assign <code class="language-plaintext highlighter-rouge">company.employeesArray</code> to the <code class="language-plaintext highlighter-rouge">@Published</code> property of employees in the <code class="language-plaintext highlighter-rouge">fetchEmployees</code> function. Similarly, we can implement the <code class="language-plaintext highlighter-rouge">addEmployee</code> function as well and save the new object in Core Data.</p>
<p>Now, let’s move our attention the <code class="language-plaintext highlighter-rouge">Views</code>. I will discuss <code class="language-plaintext highlighter-rouge">EmployeesView</code> since it requires DI to be able to initialize the <code class="language-plaintext highlighter-rouge">EmployeesViewModel</code>. On tapping the company item in the list, we will navigate to the <code class="language-plaintext highlighter-rouge">EmployeesView</code>. However, the view model for this view requires company entity so we will initialize this view like this:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">EmployeesView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kd">@ObservedObject</span> <span class="k">var</span> <span class="nv">viewModel</span> <span class="p">:</span> <span class="kt">EmployeesViewModel</span>
<span class="kd">@State</span> <span class="k">var</span> <span class="nv">employeeName</span><span class="p">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="s">""</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">company</span><span class="p">:</span> <span class="kt">Company</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">viewModel</span> <span class="o">=</span> <span class="kt">EmployeesViewModel</span><span class="p">(</span><span class="nv">company</span><span class="p">:</span> <span class="n">company</span><span class="p">)</span>
<span class="n">viewModel</span><span class="o">.</span><span class="nf">fetchEmployees</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kt">VStack</span> <span class="p">{</span>
<span class="kt">HStack</span> <span class="p">{</span>
<span class="kt">TextField</span><span class="p">(</span><span class="s">"Enter Employee Name"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">employeeName</span><span class="p">)</span>
<span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="n">headline</span><span class="p">)</span>
<span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="o">.</span><span class="n">leading</span><span class="p">)</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">height</span><span class="p">:</span> <span class="mi">55</span><span class="p">)</span>
<span class="o">.</span><span class="nf">background</span><span class="p">(</span><span class="kt">Color</span><span class="p">(</span><span class="nv">uiColor</span><span class="p">:</span> <span class="o">.</span><span class="n">systemGray5</span><span class="p">))</span>
<span class="o">.</span><span class="nf">cornerRadius</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<span class="kt">Button</span> <span class="p">{</span>
<span class="n">viewModel</span><span class="o">.</span><span class="nf">addEmployee</span><span class="p">(</span><span class="nv">employeeName</span><span class="p">:</span> <span class="n">employeeName</span><span class="p">)</span>
<span class="k">self</span><span class="o">.</span><span class="n">employeeName</span> <span class="o">=</span> <span class="s">""</span>
<span class="p">}</span> <span class="nv">label</span><span class="p">:</span> <span class="p">{</span>
<span class="kt">Text</span><span class="p">(</span><span class="s">"Add"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="o">.</span><span class="n">horizontal</span><span class="p">)</span>
<span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="o">.</span><span class="n">bottom</span><span class="p">)</span>
<span class="kt">ScrollView</span> <span class="p">{</span>
<span class="kt">ForEach</span><span class="p">(</span><span class="n">viewModel</span><span class="o">.</span><span class="n">employeesArray</span><span class="p">,</span> <span class="nv">id</span><span class="p">:</span> <span class="p">\</span><span class="o">.</span><span class="n">id</span><span class="p">)</span> <span class="p">{</span> <span class="n">item</span> <span class="k">in</span>
<span class="kt">VStack</span><span class="p">(</span><span class="nv">alignment</span><span class="p">:</span> <span class="o">.</span><span class="n">leading</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">Text</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">unwrappedName</span><span class="p">)</span>
<span class="o">.</span><span class="nf">fontWeight</span><span class="p">(</span><span class="o">.</span><span class="n">semibold</span><span class="p">)</span>
<span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="n">headline</span><span class="p">)</span>
<span class="kt">Text</span><span class="p">(</span><span class="n">item</span><span class="o">.</span><span class="n">company</span><span class="p">?</span><span class="o">.</span><span class="n">title</span> <span class="p">??</span> <span class="s">""</span><span class="p">)</span>
<span class="o">.</span><span class="nf">font</span><span class="p">(</span><span class="o">.</span><span class="n">subheadline</span><span class="p">)</span>
<span class="p">}</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">maxWidth</span><span class="p">:</span> <span class="o">.</span><span class="n">infinity</span><span class="p">,</span> <span class="nv">alignment</span><span class="p">:</span> <span class="o">.</span><span class="n">leading</span><span class="p">)</span>
<span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="o">.</span><span class="n">horizontal</span><span class="p">)</span>
<span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="o">.</span><span class="n">bottom</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span><span class="o">.</span><span class="nf">navigationTitle</span><span class="p">(</span><span class="s">"Employees"</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>You can see we have passing the company through the constructor, and the ViewModel is initialized inside the init method by passing that company model to it. Now, we can access the employees for that company inside the ForEach loop by just saying <code class="language-plaintext highlighter-rouge">viewModel.employeesArray</code></p>
<p>Full project code available here: https://github.com/Onaeem26/CoreDataMVVMSwiftUI</p>Osama NaeemI would like to start off by saying that this is not the only way to implement CoreData and there are various ways you can do it. This is just my way of implementing CoreData using MVVM architecture. Any suggestions, or feedback is highly appreciated.Making Textfield First Responder in SwiftUI using FocusedState2021-07-03T00:00:00+00:002021-07-03T00:00:00+00:00http://exploringswift.com/blog/Making-TextField-First-Responder-in-SwiftUI-using-FocusedState<p>Apple unveiled SwiftUI - a UI framework for native iOS development back in 2019. Since then it has improved quite a lot and while some may (me included) still think that it has still long way to go however, annual updates to SwiftUI has made it immensely powerful and is now closer than ever to reach that point of completion.
<!--more--></p>
<p><img src="/assets/focusedState.png" alt="" /></p>
<p>One thing that used to irritate me quite a lot was that we had no way to make a textfield a first responder in the responder chain. This led to bad UX and you had to either go for a UIKit wrapper or looked for ways to bypass the need for making a textfield focused. However, Apple has now finally listened to us and provided an API to make a textfield first responder programmatically. This new API is called FocusedState.</p>
<p>FocusedState is a property wrapper. So start off my writing this bit code in <code class="language-plaintext highlighter-rouge">ContentView</code> struct:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">@FocusState</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">loginFieldFocus</span><span class="p">:</span> <span class="kt">LogInField</span><span class="p">?</span></code></pre></figure>
<p>The property <code class="language-plaintext highlighter-rouge">loginFieldFocus</code> is of type enum <code class="language-plaintext highlighter-rouge">LogInField</code>. So let’s create an enum:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">enum</span> <span class="kt">LogInField</span> <span class="p">{</span>
<span class="k">case</span> <span class="n">username</span>
<span class="k">case</span> <span class="n">password</span>
<span class="p">}</span></code></pre></figure>
<p>Now let’s quickly create two textfields and a button:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kt">VStack</span> <span class="p">{</span>
<span class="kt">TextField</span><span class="p">(</span><span class="s">"Username"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">username</span><span class="p">)</span>
<span class="o">.</span><span class="nf">focused</span><span class="p">(</span><span class="err">$</span><span class="n">loginFieldFocus</span><span class="p">,</span> <span class="nv">equals</span><span class="p">:</span> <span class="o">.</span><span class="n">username</span><span class="p">)</span>
<span class="kt">SecureField</span><span class="p">(</span><span class="s">"Password"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">password</span><span class="p">)</span>
<span class="o">.</span><span class="nf">focused</span><span class="p">(</span><span class="err">$</span><span class="n">loginFieldFocus</span><span class="p">,</span> <span class="nv">equals</span><span class="p">:</span> <span class="o">.</span><span class="n">password</span><span class="p">)</span>
<span class="kt">Button</span><span class="p">(</span><span class="s">"Submit"</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">username</span><span class="o">.</span><span class="n">isEmpty</span> <span class="p">{</span>
<span class="n">loginFieldFocus</span> <span class="o">=</span> <span class="o">.</span><span class="n">username</span>
<span class="p">}</span><span class="k">else</span> <span class="k">if</span> <span class="n">password</span><span class="o">.</span><span class="n">isEmpty</span> <span class="p">{</span>
<span class="n">loginFieldFocus</span> <span class="o">=</span> <span class="o">.</span><span class="n">password</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">Spacer</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></figure>
<p>In the V-Stack we have two textfields (one being a SecureField) for password entry and then a “Submit” button. Now let’s talk about the <code class="language-plaintext highlighter-rouge">.focused</code> modifier that we have added to the textfields.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="o">.</span><span class="nf">focused</span><span class="p">(</span><span class="err">$</span><span class="n">loginFieldFocus</span><span class="p">,</span> <span class="nv">equals</span><span class="p">:</span> <span class="o">.</span><span class="n">username</span><span class="p">)</span>
</code></pre></figure>
<p>We are adding this modifier to the username textfield where we are assigning the <code class="language-plaintext highlighter-rouge">FocusedState</code> for that particular textfield to our enum case of ‘username’. We are doing the same for the password textfield as well. So now whenever we have to bring any of the two textfields to focus we can just assign the <code class="language-plaintext highlighter-rouge">FocusedState</code> to that particular enum case and the textfield will be focused.
For example, when we press the <code class="language-plaintext highlighter-rouge">Submit</code> button, I do a simple check to see if any of the two textfields are empty, if they are then the corresponding textfield gets in focus.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="k">if</span> <span class="n">username</span><span class="o">.</span><span class="n">isEmpty</span> <span class="p">{</span>
<span class="n">loginFieldFocus</span> <span class="o">=</span> <span class="o">.</span><span class="n">username</span>
<span class="p">}</span><span class="k">else</span> <span class="k">if</span> <span class="n">password</span><span class="o">.</span><span class="n">isEmpty</span> <span class="p">{</span>
<span class="n">loginFieldFocus</span> <span class="o">=</span> <span class="o">.</span><span class="n">password</span>
<span class="p">}</span></code></pre></figure>
<p>Another thing I would like to mention here is the fact that most of the time we want a textfield to get in focused as soon as the view appears. You can use the <code class="language-plaintext highlighter-rouge">.onAppear</code> modifier to assign the <code class="language-plaintext highlighter-rouge">FocusedState</code> property the textfield case. However, we need to add a bit of a delay of around 0.2, 0.3 seconds after the view appears. Here’s how:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="o">.</span><span class="n">onAppear</span> <span class="p">{</span>
<span class="kt">DispatchQueue</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="nf">asyncAfter</span><span class="p">(</span><span class="nv">deadline</span><span class="p">:</span> <span class="o">.</span><span class="nf">now</span><span class="p">()</span> <span class="o">+</span> <span class="mf">0.3</span><span class="p">)</span> <span class="p">{</span>
<span class="n">loginFieldFocus</span> <span class="o">=</span> <span class="o">.</span><span class="n">username</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></figure>
<p>There is another way to focus a certain textfield and thats just by injecting a boolean value. Let’s take a look at it as well:</p>
<p>Again create a FocusState property but this time it shall be a Boolean value</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">@FocusState</span> <span class="kd">private</span> <span class="k">var</span> <span class="nv">fieldFocus</span><span class="p">:</span> <span class="kt">Bool</span></code></pre></figure>
<p>In this case, I am assigning <code class="language-plaintext highlighter-rouge">fieldFocus</code> to just password textfield. So I have removed the <code class="language-plaintext highlighter-rouge">.focused</code> modifier from username and I have added the following modifier to the SecureField:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kt">TextField</span><span class="p">(</span><span class="s">"Username"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">username</span><span class="p">)</span>
<span class="kt">SecureField</span><span class="p">(</span><span class="s">"Password"</span><span class="p">,</span> <span class="nv">text</span><span class="p">:</span> <span class="err">$</span><span class="n">password</span><span class="p">)</span>
<span class="o">.</span><span class="nf">focused</span><span class="p">(</span><span class="err">$</span><span class="n">fieldFocus</span><span class="p">)</span>
</code></pre></figure>
<p>Now inside the <code class="language-plaintext highlighter-rouge">Submit</code> button action clause, I am checking if the password which has been added has a count of less than 8, in which case it will focus the password field again for you to rewrite the password:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kt">Button</span><span class="p">(</span><span class="s">"Submit"</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">password</span><span class="o">.</span><span class="n">count</span> <span class="o"><</span> <span class="mi">8</span> <span class="p">{</span>
<span class="n">fieldFocus</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="c1">//</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></figure>Osama NaeemApple unveiled SwiftUI - a UI framework for native iOS development back in 2019. Since then it has improved quite a lot and while some may (me included) still think that it has still long way to go however, annual updates to SwiftUI has made it immensely powerful and is now closer than ever to reach that point of completion.Drawing Smooth Cubic Bezier Curve through prescribed points using Swift2020-04-27T00:00:00+00:002020-04-27T00:00:00+00:00http://exploringswift.com/blog/Drawing-Smooth-Cubic-Bezier-Curve-through-prescribed-points%20using-Swift<p>So I wanted to plot some data and decided to create a simple line graph using <code class="language-plaintext highlighter-rouge">UIBezierPath</code>. It worked but I wasn’t happy with it. Then I decided to use the quadratic bezier curve method that requires two end points and one control point. Still wasn’t happy with result. Then came the turn of the Cubic bezier curve, that requires two control points and two end points. I knew I needed to use this to create a curve that went through the prescribed points and also creates the smooth and perfect curve that I was looking for. However, it isn’t as straight forward as it first seems.
<!--more--></p>
<p>The bezier cubic curve method signature is this:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">func</span> <span class="nf">addCurve</span><span class="p">(</span><span class="n">to</span> <span class="nv">endPoint</span><span class="p">:</span> <span class="kt">CGPoint</span><span class="p">,</span> <span class="nv">controlPoint1</span><span class="p">:</span> <span class="kt">CGPoint</span><span class="p">,</span> <span class="nv">controlPoint2</span><span class="p">:</span> <span class="kt">CGPoint</span><span class="p">)</span></code></pre></figure>
<p>It requires the next end point, and then two control points. I have the end points (calculated from the data set) but not the control points. This is where my research started. I wanted to figure out how to calculate the control points of a cubic bezier and de Casteljau curve.</p>
<p>Before I get into the algorithm, maths and the code, I would like to say that this article will be far more theoretical and mathematical than any other article that I have posted on this website. It will contain the coding bit as well, but that will be pretty straight forward once you understand the actual logic behind calculating control points. You will need some understanding of Calculus and Linear Algebra.</p>
<p>Now let’s focus on how a bezier curve is actually formed. A quadratic bezier curve has two end points and one control point. The control point can be pictured as a magnet that pushes the main line towards itself giving us the “curve” that we want.</p>
<p><img src="/assets/quad1.png" alt="" /></p>
<p>As you can see we have the gray colored point that is basically acting as the control point. To go further in detail, the control point is connected to the two end points. The two lines projecting from the control point are tangent to the curve. Moving the control point will change the gradient of the two lines in turn changing the curve.</p>
<p>But you may ask, how does it actually work? Like it’s not actually a magnet right? Obviously, it’s not a magnet - basically we have simple coordinate geometry going on here. Let’s find out:</p>
<p>The two lines that are projecting from the control point and in tangent to the two end points. Now let’s take a parameter say <code class="language-plaintext highlighter-rouge">t</code> which goes from 0 to 1. Also, we will picture that there is a point on each of these gray lines. We will call them <code class="language-plaintext highlighter-rouge">p01</code> and <code class="language-plaintext highlighter-rouge">p12</code>. The <code class="language-plaintext highlighter-rouge">p01</code> is moving from endpoint <code class="language-plaintext highlighter-rouge">p0</code> to control point <code class="language-plaintext highlighter-rouge">p1</code> as <code class="language-plaintext highlighter-rouge">t</code> goes from 0 to 1. And similarly, we have a point <code class="language-plaintext highlighter-rouge">p12</code> that moves from control point <code class="language-plaintext highlighter-rouge">p1</code> to end point <code class="language-plaintext highlighter-rouge">p2</code> and <code class="language-plaintext highlighter-rouge">t</code> goes from 0 to 1.</p>
<p><img src="/assets/quad02.gif" alt="" /></p>
<p>Imagine there is a line drawn between the point <code class="language-plaintext highlighter-rouge">p01</code> and <code class="language-plaintext highlighter-rouge">p12</code> and there is another point on this line <code class="language-plaintext highlighter-rouge">p012</code>. This point moves from <code class="language-plaintext highlighter-rouge">p01</code> to <code class="language-plaintext highlighter-rouge">p12</code> as <code class="language-plaintext highlighter-rouge">t</code> goes from 0 to 1. So now when you start the <code class="language-plaintext highlighter-rouge">t</code> parameter, you will see that are curve is drawn with respect to the position of the control point.</p>
<p>This is just the basic concept of how bezier curves are formed using one control point. The problem with the quadratic control point is that it only gives us one control point and it doesn’t provide us with the flexibility that we are looking for to create a smooth curve. This same understanding can be applied to cubic bezier curve as well that contains two control points.</p>
<p><img src="/assets/cubic01.gif" alt="" /></p>
<p>To better understand how the bezier curve is drawn, take a look at this extremely well made video/animation:</p>
<iframe src="https://player.vimeo.com/video/106757336?color=7d90ff&title=0&byline=0&portrait=0" width="640" height="360" frameborder="0" allow="autoplay; fullscreen" allowfullscreen=""></iframe>
<p><br /></p>
<h3>The Equation:</h3>
<p>Now that we have the basic understanding of how bezier curves work, and can picture how the curve is drawn, now it’s time to represent this entire thing with a mathematical equation and here it is:</p>
<p><img src="/assets/be1.png" alt="" /> <b>(Eq 1)</b></p>
<p>This is the parameterized equation of a cubic bezier curve, where <code class="language-plaintext highlighter-rouge">t</code> which is the parameter can go from 0 to 1. In this equation, the point <code class="language-plaintext highlighter-rouge">p0</code> and <code class="language-plaintext highlighter-rouge">p3</code> are the end point and the point <code class="language-plaintext highlighter-rouge">p1</code> and <code class="language-plaintext highlighter-rouge">p2</code> are the control points. Also note that when <code class="language-plaintext highlighter-rouge">t</code> = 0, the output of the equation is <code class="language-plaintext highlighter-rouge">p0</code> and when <code class="language-plaintext highlighter-rouge">t</code> = 1, the output is <code class="language-plaintext highlighter-rouge">p3</code>. Now as you move the <code class="language-plaintext highlighter-rouge">t</code> from 0 to 1 (say in 0.1 increments), the control point contains more weightage in the output. Till a certain <code class="language-plaintext highlighter-rouge">t</code> one control point has more weightage and effects the curve more, and for some other range of values for <code class="language-plaintext highlighter-rouge">t</code>, the next control point has more weightage and effects the curve.</p>
<p>For example, when <code class="language-plaintext highlighter-rouge">t</code> = 0.3:</p>
<p><img src="/assets/t03barchart.png" alt="" /></p>
<p>when <code class="language-plaintext highlighter-rouge">t</code> = 0.7, the second control point and the second end point has the most weightage in the curve:</p>
<p><img src="/assets/t07chart.png" alt="" /></p>
<p>Now let’s take a closer look at how to create smooth curves. The first thing we need to observe is that while this above equation is a 3rd degree polynomial, we can use higher degree polynomials as well which of course means more control points and much precise curve control.</p>
<p><code class="language-plaintext highlighter-rouge">Number of control points = degree of polynomial - 1 </code></p>
<p>However, higher polynomial degree means more complex computations. Therefore, we will stick with 3 degree polynomial or cubic curve. In order to get a long curve, we will stitch cubic curves together.</p>
<h3>Conditions:</h3>
<p>Since multiple curves will be stitched together, they need to follow a certain protocol:</p>
<ol>
<li>Each curve will have two end points (<code class="language-plaintext highlighter-rouge">p0</code> & <code class="language-plaintext highlighter-rouge">p3</code>) and two control points (<code class="language-plaintext highlighter-rouge">p1</code> & <code class="language-plaintext highlighter-rouge">p2</code>).</li>
<li>The last endpoint of one curve will be first endpoint of the next curve. (shared point)</li>
<li>To get a <b>smooth</b> curve we need to make sure the slope of the line connecting <code class="language-plaintext highlighter-rouge">p2</code> and <code class="language-plaintext highlighter-rouge">p3</code> of the curve is same as the slope of line connecting <code class="language-plaintext highlighter-rouge">P3</code> and <code class="language-plaintext highlighter-rouge">Q1</code> of the next curve.</li>
</ol>
<p><img src="/assets/slopepic.png" alt="" /></p>
<p>Let’s talk about the derivatives now. The first derivative of any polynomial curve is it’s slope or the gradient at a particular point. The second derivative of the equation tells us about the maxima/minima and continuity behavior. We will use these derivatives to derive equations for our curve. Let’s take a closer look at the third condition of the protocol.</p>
<p>The slope of the line connected <code class="language-plaintext highlighter-rouge">p2</code> and <code class="language-plaintext highlighter-rouge">p3</code> - which is the second control point and last end point of curve should be same as the slope of line connecting the control point <code class="language-plaintext highlighter-rouge">Q1</code> and end point <code class="language-plaintext highlighter-rouge">P3</code>. Let’s first write the derivative of the cubic bezier equation:</p>
<p><img src="/assets/bdeq2.png" alt="" /> <b>(Eq 2)</b></p>
<p>Now before we go any further let’s talk about the segments and points. If you have <code class="language-plaintext highlighter-rouge">n</code> data points, and you wish to create a curve that pass through all the data points, then you shall have <code class="language-plaintext highlighter-rouge">n - 1</code> segments or curves. In the above picture you can see that there are three data points, <code class="language-plaintext highlighter-rouge">p0</code>, <code class="language-plaintext highlighter-rouge">p3</code> and <code class="language-plaintext highlighter-rouge">Q3</code> and there are 2 curves or segments (from <code class="language-plaintext highlighter-rouge">p0</code> to <code class="language-plaintext highlighter-rouge">p3</code> and from <code class="language-plaintext highlighter-rouge">p3</code> to <code class="language-plaintext highlighter-rouge">Q3</code>).</p>
<p>The two curves have a shared point (<code class="language-plaintext highlighter-rouge">p3</code>) and we need to make sure that the slope for both curves at this point is same. Note that <code class="language-plaintext highlighter-rouge">p3</code> is the last endpoint for the first segment and the first end point of the next segment.</p>
<p>As I showed earlier in the article, if we put <code class="language-plaintext highlighter-rouge">t = 0</code> in the cubic bezier equation we get the first endpoint as the output and <code class="language-plaintext highlighter-rouge">t = 1</code> yeilds the last endpoint. Similarly, in the Eq 2 (derivative equation of the cubic bezier curve) - we will put <code class="language-plaintext highlighter-rouge">t = 1</code> for the <code class="language-plaintext highlighter-rouge">i - 1</code>th segment and <code class="language-plaintext highlighter-rouge">t = 0</code> for the <code class="language-plaintext highlighter-rouge">i</code>th segment, where (<code class="language-plaintext highlighter-rouge">i > 0</code>).</p>
<p><img src="/assets/bdeq3.png" alt="" /> <b>(Eq 3)</b></p>
<p>Once you have substituted the correct <code class="language-plaintext highlighter-rouge">t</code> values in Eq 2 and evaluated Eq 3 you will get this equation:</p>
<p><img src="/assets/bdeq4.png" alt="" /> <b>(Eq 4)</b></p>
<p>Do note here the second condition of our protocol, that states that the last endpoint of one curve will be first endpoint of the next curve. So point <code class="language-plaintext highlighter-rouge">p0</code> of <code class="language-plaintext highlighter-rouge">i</code> segment and point <code class="language-plaintext highlighter-rouge">p3</code> of <code class="language-plaintext highlighter-rouge">i - 1</code> segment are shared or same. We will call them as <code class="language-plaintext highlighter-rouge">k</code>. <code class="language-plaintext highlighter-rouge">k</code> from now on will represent an endpoint which is shared between two segments.</p>
<p><img src="/assets/bdeqm.png" alt="" /></p>
<p>Using the above identity in Equation 4 gives us the following important equation:</p>
<p><img src="/assets/bdeq5.png" alt="" /> <b>(Eq 5)</b></p>
<p>We will now move to the second derivative. The second derivative tells us about the continuity of the curve. The second derivative of the bezier curve is:</p>
<p><img src="/assets/bdeqm1.png" alt="" /> <b>(Eq 6)</b></p>
<p>The second derivative tells us if there is any discontinuity at a particular point in our graph. Since the third condition in our protocol mentions that the curve is continuous so the second derivative shall also be same at the shared point. So go ahead and substitue <code class="language-plaintext highlighter-rouge">t = 0</code> and <code class="language-plaintext highlighter-rouge">t = 1</code> in Eq 6. Make sure that you follow the same concept which was followed earlier:</p>
<p>Substitute <code class="language-plaintext highlighter-rouge">t = 0</code> for the <code class="language-plaintext highlighter-rouge">i th</code> segment and <code class="language-plaintext highlighter-rouge">t = 1</code> for <code class="language-plaintext highlighter-rouge">i - 1 th</code> segment in the Eq 6 and make them equal to each other. This is what you will get after simplification:</p>
<p><img src="/assets/bdeq6.png" alt="" /> <b>(Eq 7)</b></p>
<p>This is our first of three equations which we will use to do most of our evaluation. Also keep in mind that the above equation is in terms of <code class="language-plaintext highlighter-rouge">P2</code> and <code class="language-plaintext highlighter-rouge">P1</code> - the two control points.</p>
<p>Now we will take a look at the extremities - where the curve starts and ends. These two points have a discontinuity or the second derivative of the curve at these points is <code class="language-plaintext highlighter-rouge">0</code>. Let’s begin by taking the very first segment of the curve. Since the extreme point is when <code class="language-plaintext highlighter-rouge">t = 0</code>, so we will substitute <code class="language-plaintext highlighter-rouge">t = 0</code> in Eq 6.</p>
<p><img src="/assets/bdeq7.png" height="22" width="240" /> <br /></p>
<p>In the above equation you can see we have <code class="language-plaintext highlighter-rouge">P0</code> which is an end point and not the control point, so we will substitute it with <code class="language-plaintext highlighter-rouge">K0</code> and this is the equation we will get now:</p>
<p><img src="/assets/bdeq7s.png" alt="" /> <b>(Eq 8)</b></p>
<p>Let’s do the same with <code class="language-plaintext highlighter-rouge">t = 1</code> now for the last segment which will be <code class="language-plaintext highlighter-rouge">n - 1 th</code> segment, again make sure to substitute <code class="language-plaintext highlighter-rouge">Kn</code> in place of <code class="language-plaintext highlighter-rouge">P3</code> which is an extreme end point of the last segment.</p>
<p><img src="/assets/bdeq8.png" alt="" /> <b>(Eq 9)</b></p>
<p>We are almost there. Here is the situation right now: Using our understanding of basic calculus and a bit of algebra, we have been able to get three equations in terms of the unknowns which are the control points <code class="language-plaintext highlighter-rouge">P1</code> and <code class="language-plaintext highlighter-rouge">P2</code>. We will first try to solve for one control point and then once <code class="language-plaintext highlighter-rouge">P1</code> is found, we can find <code class="language-plaintext highlighter-rouge">P2</code>. So in <b>Eq 7</b>, <b>Eq 8</b> & <b>Eq 9</b> we have <code class="language-plaintext highlighter-rouge">P1</code> and <code class="language-plaintext highlighter-rouge">P2</code>. We will now get all these three equations in terms of <code class="language-plaintext highlighter-rouge">P1</code> and the <code class="language-plaintext highlighter-rouge">K</code> - (which is nothing but the known end points for relative segments).</p>
<p>In order to get this sort of an equation, we will substitute <code class="language-plaintext highlighter-rouge">P2</code> with <b>Eq 5</b> and we will get the following 3 equations:</p>
<p><img src="/assets/bdeqf2.png" alt="" /> <b>(Eq 10.1)</b> <br />
<img src="/assets/dbeqf1.png" alt="" /> <b>(Eq 10.2)</b> <br />
<img src="/assets/bdeqf3.png" alt="" /> <b>(Eq 10.3)</b> <br /></p>
<h3>The Equations:</h3>
<p>The Eq 10.1, 10.2 & 10.3 are the three mains equations that we be using. The equations are in the form of <code class="language-plaintext highlighter-rouge">P1</code> which is unknown and <code class="language-plaintext highlighter-rouge">K</code> which is just the end point. Now keep this in mind that the total number of equations will obviously depend on the number of data points. The number of equations will be same as the number of segments or total data points - 1.</p>
<p>Equation 10.1 is the equation to find the <code class="language-plaintext highlighter-rouge">P1</code> of the first segment. Similarly, Equation 10.3 is to find the <code class="language-plaintext highlighter-rouge">P1</code> for the last segment (n - 1). The equation 10.2 is the generic equation, and the quantity of this equation will depend upon the data set. Let’s take a closer look at Eq 10.2.</p>
<h3> Equation 10.2 In Depth: </h3>
<p>The Equation 10.2 is the most important one when it comes to understanding the whole applied mathematics point of view. We have a system of equations and we will be using a bit of linear algebra to solve for the unknowns.</p>
<p>The <code class="language-plaintext highlighter-rouge">i</code> subscript in this equation refers to the index of the data point. For example, <code class="language-plaintext highlighter-rouge">i = 0</code> means, the first element in data array and <code class="language-plaintext highlighter-rouge">i = 1</code> means the second element of the data array. If <code class="language-plaintext highlighter-rouge">i = 0</code>, we will use the first equation (10.1), when the <code class="language-plaintext highlighter-rouge">i = n - 2</code>, this means we are dealing with second last data point in our array and so we will use the equation 10.3. For <code class="language-plaintext highlighter-rouge">i = 1</code> all the way till <code class="language-plaintext highlighter-rouge">i < n - 2</code>, we will use equation 10.2. Confused? Let’s clear it up:</p>
<p>Imagine we have an array p, that contains 7 data points:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">p</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">6</span><span class="p">,</span><span class="mi">5</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">7</span><span class="p">,</span><span class="mi">4</span><span class="p">),</span> <span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="mi">8</span><span class="p">),</span> <span class="p">(</span><span class="mi">7</span><span class="p">,</span><span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">8</span><span class="p">,</span><span class="mi">9</span><span class="p">),</span> <span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="mi">3</span><span class="p">)]</span></code></pre></figure>
<p>Fetching first data point is pretty trivial = <code class="language-plaintext highlighter-rouge">p[0] -> (6,5)</code>.
Since in our above equations, we require data points for the right hand side only where <code class="language-plaintext highlighter-rouge">Ki</code> is the data point from the array. When we are dealing with first equation (10.1), we need the first and second element of the data set; <code class="language-plaintext highlighter-rouge">i = 0 & i = 1</code>.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">var</span> <span class="nv">p0</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">var</span> <span class="nv">p3</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span></code></pre></figure>
<p>Then for the last segment we need the second last element from the data point, the <code class="language-plaintext highlighter-rouge">i</code> value to fetch this will be <code class="language-plaintext highlighter-rouge">i = n - 2</code> or <code class="language-plaintext highlighter-rouge">i = 7 - 2 = 5</code>.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="k">var</span> <span class="nv">p0</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span>
<span class="k">var</span> <span class="nv">p3</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span>
</code></pre></figure>
<p>Now, we will use the second equation and our <code class="language-plaintext highlighter-rouge">i</code> value will go from <code class="language-plaintext highlighter-rouge">i = 1</code> to <code class="language-plaintext highlighter-rouge">i = 4</code>, which returns us the second element till the fifth element:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="o">...</span><span class="mi">4</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">p0</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
<span class="k">var</span> <span class="nv">p3</span> <span class="o">=</span> <span class="n">p</span><span class="p">[</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></figure>
<p>The above example should help us when we are coding this whole thing up.</p>
<h3> The Matrix </h3>
<p>Now is the time to create a matrix where we will use the above equations and create a tri-diagonal matrix. For this example we will assume that we have 7 points, so the total segments will be (7 - 1) = 6. Which means our matrix A will be 6 x 6. Let’s start with Eq 10.1 and Eq 10.3 first:</p>
<p><img src="/assets/bdm1.png" height="200" width="200" /> <br /></p>
<p>(M1.0)</p>
<p>We are only concerned with the left hand side of the equations for now.</p>
<p>Now let’s begin adding the remaining elements. The remaining elements will be filled by using the Eq 10.2. The picture below shows the full tri-diagonal matrix:</p>
<p><img src="/assets/bdmatrix.jpg" alt="" /></p>
<p>The tridiagonal matrix essentially contains three diagonals, one main diagonal and then one on top and one on the bottom of the main diagonal. Such matrices are quite easy to compute and takes less computational resources. So now we have the left hand side of the equation. The right hand side is just the data points. Now we will use the Thomas Algorithm to calculate for P1.
<code class="language-plaintext highlighter-rouge">A * P1 = B</code></p>
<p>So this is how our system will look like:</p>
<p><img src="/assets/bdm3.jpg" alt="" /></p>
<h3>Thomas Algorithm:</h3>
<p>Before we get into the coding bit, let’s use the Thomas Algorithm to calculate the unknown <code class="language-plaintext highlighter-rouge">P1's</code>. Thomas Algorithm work by working with the three diagonals only and doing simple row operations on them.</p>
<p>The first thing we will do is to make the first element of the main diagonal <code class="language-plaintext highlighter-rouge">d</code> = 1.</p>
<p><img src="/assets/ta1.png" alt="" /></p>
<p>To do this, we divide first row by <code class="language-plaintext highlighter-rouge">d1</code> and we get:</p>
<p><code class="language-plaintext highlighter-rouge">a1' -> a1 / d1</code></p>
<p><code class="language-plaintext highlighter-rouge">B1' -> b1 / d1</code></p>
<p>Next, we will use the first row and use it as a pivot to eliminate the element b2:</p>
<p><img src="/assets/ta2.png" alt="" /></p>
<p>We do this by multiplying first row with <code class="language-plaintext highlighter-rouge">b2</code> and then is subtracted with the second row, we get:</p>
<p><code class="language-plaintext highlighter-rouge">d2' -> d2 - b2a1'</code></p>
<p><code class="language-plaintext highlighter-rouge">B2' -> B2 - B1b2</code></p>
<p>We will do the same operations for all the remaining rows, eliminating essentially the bottom diagonal. So basically we will normalize the second row by dividing the 2nd row with <code class="language-plaintext highlighter-rouge">d2'</code> which makes the element <code class="language-plaintext highlighter-rouge">d2' = 1</code>. Then we will use this element as the pivot and multiply the entire row with <code class="language-plaintext highlighter-rouge">b3</code> and then is subtracted with the third row, eliminating the <code class="language-plaintext highlighter-rouge">b3</code> element in the below diagonal.</p>
<p>In the end we will get such a matrix:</p>
<p><img src="/assets/ta3.png" alt="" /></p>
<p>The last row’s last column is 1, which allows for simple back substitution and finding all the <code class="language-plaintext highlighter-rouge">p1's</code>.</p>
<p>Let’s see how we can make all these operations generic, so that we can use a simple for loop and have all these operations take place.</p>
<p>Take three arrays for three diagonals</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">var</span> <span class="nv">d</span> <span class="o">=</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span>
<span class="k">var</span> <span class="nv">b</span> <span class="o">=</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span> <span class="c1">//below diagonal</span>
<span class="k">var</span> <span class="nv">a</span> <span class="o">=</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span> <span class="c1">// above diagonal</span>
<span class="k">var</span> <span class="nv">B</span> <span class="o">=</span> <span class="p">[</span><span class="kt">Int</span><span class="p">]</span> <span class="c1">// Unknown variable</span></code></pre></figure>
<p>When <code class="language-plaintext highlighter-rouge">i = 1</code>, we will use:</p>
<p><code class="language-plaintext highlighter-rouge">a1'</code> = <code class="language-plaintext highlighter-rouge">a1 / d1</code></p>
<p><code class="language-plaintext highlighter-rouge">B1'</code> = <code class="language-plaintext highlighter-rouge">B1 / d1</code></p>
<p>For <code class="language-plaintext highlighter-rouge">i = 2 to i = n - 1</code>:
<br />
<img src="/assets/ta4.png" height="75" width="375" /> <br /></p>
<p>and when <code class="language-plaintext highlighter-rouge">i = n</code>:
<br /></p>
<p><img src="/assets/ta5.png" height="60" width="180" /> <br /></p>
<p>At the end we will do the back substitution to get the values for unknown. We will do this step once we get to the coding portion. Now that we have our <code class="language-plaintext highlighter-rouge">P1</code> control point, we can use these points to get the other control point <code class="language-plaintext highlighter-rouge">P2</code>. To get <code class="language-plaintext highlighter-rouge">P2</code>, we will use <b>Eq 5</b> and <b>Eq 9</b>.</p>
<p>If we make <code class="language-plaintext highlighter-rouge">P2</code> the subject we get the following two equations:</p>
<p><img src="/assets/bdp2eq1.png" alt="" /> <b>(Eq 11.1)</b></p>
<p><img src="/assets/bdp2eq2.png" alt="" /> <b>(Eq 11.2)</b></p>
<p>That’s all the maths that we need to do. So now we have the system of equations for <code class="language-plaintext highlighter-rouge">P1</code>, and also the equations for <code class="language-plaintext highlighter-rouge">P2</code>. Next, we have discussed the thomas algorithm which will help us solve for the system of equations. Now it’s all about the code. The code is pretty straight forward. Let’s code it all out 🚀</p>
<h3>Let's Code:</h3>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">BezierSegmentControlPoints</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">firstControlPoint</span><span class="p">:</span> <span class="kt">CGPoint</span>
<span class="k">var</span> <span class="nv">secondControlPoint</span><span class="p">:</span> <span class="kt">CGPoint</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="kt">BezierConfiguration</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">firstControlPoints</span><span class="p">:</span> <span class="p">[</span><span class="kt">CGPoint</span><span class="p">?]</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">var</span> <span class="nv">secondControlPoints</span><span class="p">:</span> <span class="p">[</span><span class="kt">CGPoint</span><span class="p">?]</span> <span class="o">=</span> <span class="p">[]</span>
<span class="kd">func</span> <span class="nf">configureControlPoints</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="p">[</span><span class="kt">CGPoint</span><span class="p">])</span> <span class="o">-></span> <span class="p">[</span><span class="kt">BezierSegmentControlPoints</span><span class="p">]</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">segments</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">count</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">segments</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">{</span>
<span class="c1">// straight line calculation here</span>
<span class="k">let</span> <span class="nv">p0</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">let</span> <span class="nv">p3</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">return</span> <span class="p">[</span><span class="kt">BezierSegmentControlPoints</span><span class="p">(</span><span class="nv">firstControlPoint</span><span class="p">:</span> <span class="n">p0</span><span class="p">,</span> <span class="nv">secondControlPoint</span><span class="p">:</span> <span class="n">p3</span><span class="p">)]</span>
<span class="p">}</span><span class="o">.....</span></code></pre></figure>
<p>The above block of code basically states that there is a new class called <code class="language-plaintext highlighter-rouge">BezierConfiguration</code> that contains a <code class="language-plaintext highlighter-rouge">configureControlPoints(data: [CGPoint])</code> method that takes in an array of <code class="language-plaintext highlighter-rouge">CGPoint</code>and returns a custom type of <code class="language-plaintext highlighter-rouge">BezierSegmentControlPoints</code> that houses the first and second control point per segment.</p>
<p>In side the function, we are first grabbing the total number of segments. If there is just 1 segment or 2 data points, then there will be a straight line and here I am sending the end points as the control points, so in essence there should be no curve on the line.</p>
<p>If segments are more than 1, then we will do the following:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">else</span> <span class="k">if</span> <span class="n">segments</span> <span class="o">></span> <span class="mi">1</span> <span class="p">{</span>
<span class="c1">//left hand side coefficients</span>
<span class="k">var</span> <span class="nv">ad</span> <span class="o">=</span> <span class="p">[</span><span class="kt">CGFloat</span><span class="p">]()</span>
<span class="k">var</span> <span class="nv">d</span> <span class="o">=</span> <span class="p">[</span><span class="kt">CGFloat</span><span class="p">]()</span>
<span class="k">var</span> <span class="nv">bd</span> <span class="o">=</span> <span class="p">[</span><span class="kt">CGFloat</span><span class="p">]()</span>
<span class="k">var</span> <span class="nv">rhsArray</span> <span class="o">=</span> <span class="p">[</span><span class="kt">CGPoint</span><span class="p">]()</span>
<span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..<</span><span class="n">segments</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">rhsXValue</span> <span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">var</span> <span class="nv">rhsYValue</span> <span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">let</span> <span class="nv">p0</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
<span class="k">let</span> <span class="nv">p3</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
<span class="n">bd</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="mf">0.0</span><span class="p">)</span>
<span class="n">d</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="mf">2.0</span><span class="p">)</span>
<span class="n">ad</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="mf">1.0</span><span class="p">)</span>
<span class="n">rhsXValue</span> <span class="o">=</span> <span class="n">p0</span><span class="o">.</span><span class="n">x</span> <span class="o">+</span> <span class="mf">2*</span><span class="n">p3</span><span class="o">.</span><span class="n">x</span>
<span class="n">rhsYValue</span> <span class="o">=</span> <span class="n">p0</span><span class="o">.</span><span class="n">y</span> <span class="o">+</span> <span class="mf">2*</span><span class="n">p3</span><span class="o">.</span><span class="n">y</span>
<span class="p">}</span><span class="k">else</span> <span class="k">if</span> <span class="n">i</span> <span class="o">==</span> <span class="n">segments</span> <span class="o">-</span> <span class="mi">1</span> <span class="p">{</span> <span class="c1">// 5</span>
<span class="n">bd</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="mf">2.0</span><span class="p">)</span>
<span class="n">d</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="mf">7.0</span><span class="p">)</span>
<span class="n">ad</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="mf">0.0</span><span class="p">)</span>
<span class="n">rhsXValue</span> <span class="o">=</span> <span class="mf">8*</span><span class="n">p0</span><span class="o">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">p3</span><span class="o">.</span><span class="n">x</span>
<span class="n">rhsYValue</span> <span class="o">=</span> <span class="mf">8*</span><span class="n">p0</span><span class="o">.</span><span class="n">y</span> <span class="o">+</span> <span class="n">p3</span><span class="o">.</span><span class="n">y</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="n">bd</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="mf">1.0</span><span class="p">)</span>
<span class="n">d</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="mf">4.0</span><span class="p">)</span>
<span class="n">ad</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="mf">1.0</span><span class="p">)</span>
<span class="n">rhsXValue</span> <span class="o">=</span> <span class="mf">4*</span><span class="n">p0</span><span class="o">.</span><span class="n">x</span> <span class="o">+</span> <span class="mf">2*</span><span class="n">p3</span><span class="o">.</span><span class="n">x</span>
<span class="n">rhsYValue</span> <span class="o">=</span> <span class="mf">4*</span><span class="n">p0</span><span class="o">.</span><span class="n">y</span> <span class="o">+</span> <span class="mf">2*</span><span class="n">p3</span><span class="o">.</span><span class="n">y</span>
<span class="p">}</span>
<span class="n">rhsArray</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="n">rhsXValue</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="n">rhsYValue</span><span class="p">))</span>
<span class="k">let</span> <span class="nv">solution1</span> <span class="o">=</span> <span class="nf">thomasAlgorithm</span><span class="p">(</span><span class="nv">bd</span><span class="p">:</span> <span class="n">bd</span><span class="p">,</span> <span class="nv">d</span><span class="p">:</span> <span class="n">d</span><span class="p">,</span> <span class="nv">ad</span><span class="p">:</span> <span class="n">ad</span><span class="p">,</span> <span class="nv">rhsArray</span><span class="p">:</span> <span class="n">rhsArray</span><span class="p">,</span> <span class="nv">segments</span><span class="p">:</span> <span class="n">segments</span><span class="p">,</span> <span class="nv">data</span><span class="p">:</span> <span class="n">data</span><span class="p">)</span>
<span class="k">return</span> <span class="n">solution1</span>
<span class="p">}</span></code></pre></figure>
<p>In this block of code, we are first creating three arrays, one for the main diagonal <code class="language-plaintext highlighter-rouge">d</code>, then one for the diagonal above the main one, <code class="language-plaintext highlighter-rouge">ad</code> and one for below, <code class="language-plaintext highlighter-rouge">bd</code>. Then we will create a <code class="language-plaintext highlighter-rouge">rhsArray</code> that will house all the coefficients for the right hand side of our system of equations. The <code class="language-plaintext highlighter-rouge">d</code>, <code class="language-plaintext highlighter-rouge">ad</code> and <code class="language-plaintext highlighter-rouge">bd</code> will house all the coefficients for the left hand side of the system of equations. Again we are using <code class="language-plaintext highlighter-rouge">i</code> to keep track of the index for our original points in the data set. So now we run the for loop that will go from <code class="language-plaintext highlighter-rouge">0</code> to <code class="language-plaintext highlighter-rouge">n - 2</code> or <code class="language-plaintext highlighter-rouge">number of segments - 1</code>.</p>
<p>We need to be careful here not to run a loop where the <code class="language-plaintext highlighter-rouge">i</code> is not in the range of the array, so make sure you understand why we are running the for loop till a certain value.</p>
<p>At the start of each iteration, we are calculating the two endpoints for each segment <code class="language-plaintext highlighter-rouge">p0</code> and <code class="language-plaintext highlighter-rouge">p3</code>. Then if it is the first point <code class="language-plaintext highlighter-rouge">i = 0</code>, then we use the <code class="language-plaintext highlighter-rouge">Eq 10.1</code>. we append the respective coefficients to <code class="language-plaintext highlighter-rouge">bd</code>, <code class="language-plaintext highlighter-rouge">d</code> and <code class="language-plaintext highlighter-rouge">ad</code> and the <code class="language-plaintext highlighter-rouge">rhsXValue</code> and <code class="language-plaintext highlighter-rouge">rhsYValue</code>.</p>
<p>If the index value/ <code class="language-plaintext highlighter-rouge">i</code> is equal to <code class="language-plaintext highlighter-rouge">segments - 1</code>, or the second last point, then we will append the respective coefficients to the diagonal arrays and also create the <code class="language-plaintext highlighter-rouge">rhsXValue</code> and <code class="language-plaintext highlighter-rouge">rhsYValue</code> for that equation. Lastly, we have all the segments between the first and last segment. Again we do the same thing and append the coefficients to the diagonals arrays and create the respective <code class="language-plaintext highlighter-rouge">rhsXValue</code> and <code class="language-plaintext highlighter-rouge">rhsYValue</code> which then we append to the <code class="language-plaintext highlighter-rouge">rhsArray</code>.</p>
<p>Thus far, we have created a system of equations. We have the right hand side and left hand side of the equations. Now we will implement the Thomas algorithm. So let’s create a new function and send the relevant data to the function:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">func</span> <span class="nf">thomasAlgorithm</span><span class="p">(</span><span class="nv">bd</span><span class="p">:</span> <span class="p">[</span><span class="kt">CGFloat</span><span class="p">],</span> <span class="nv">d</span><span class="p">:</span> <span class="p">[</span><span class="kt">CGFloat</span><span class="p">],</span> <span class="nv">ad</span><span class="p">:</span> <span class="p">[</span><span class="kt">CGFloat</span><span class="p">],</span> <span class="nv">rhsArray</span><span class="p">:</span> <span class="p">[</span><span class="kt">CGPoint</span><span class="p">],</span> <span class="nv">segments</span><span class="p">:</span> <span class="kt">Int</span><span class="p">,</span> <span class="nv">data</span><span class="p">:</span> <span class="p">[</span><span class="kt">CGPoint</span><span class="p">])</span> <span class="o">-></span> <span class="p">[</span><span class="kt">BezierSegmentControlPoints</span><span class="p">]</span> <span class="p">{</span>
</code></pre></figure>
<p>In this function, we are getting all the diagonal matrices, the number of segments and the right hand side array and of course our data values. Now we will implement the thomas algorithm. It’s pretty simple and we will just be implementing the equations that we have already discussed earier:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="k">var</span> <span class="nv">controlPoints</span> <span class="p">:</span> <span class="p">[</span><span class="kt">BezierSegmentControlPoints</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span> <span class="c1">//Final solution array</span>
<span class="k">var</span> <span class="nv">solutionSet1</span> <span class="o">=</span> <span class="p">[</span><span class="kt">CGPoint</span><span class="p">?]()</span>
<span class="n">solutionSet1</span> <span class="o">=</span> <span class="kt">Array</span><span class="p">(</span><span class="nv">repeating</span><span class="p">:</span> <span class="kc">nil</span><span class="p">,</span> <span class="nv">count</span><span class="p">:</span> <span class="n">segments</span><span class="p">)</span>
<span class="c1">//For the first equation</span>
<span class="n">ad</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">ad</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">/</span> <span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">rhsArray</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">rhsArray</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">x</span> <span class="o">/</span> <span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">rhsArray</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">rhsArray</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">y</span> <span class="o">/</span> <span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</code></pre></figure>
<p>The first thing we do inside this function is create a <code class="language-plaintext highlighter-rouge">solutionSet1</code> array and initialize the array with specific number of elements. Next we will solve the first equation, and for that we simply implement the equation already mentioned earlier. The <code class="language-plaintext highlighter-rouge">B</code> array that we took in our Thomas Algorithm section will now be replaced with the <code class="language-plaintext highlighter-rouge">rhsArray</code> and since we have a point here, so make sure you do the operation to both the <code class="language-plaintext highlighter-rouge">x</code> and <code class="language-plaintext highlighter-rouge">y</code> coordinates.</p>
<p>Next we will do this for the middle segments or equations. One thing to note here is the fact that we am using the if statement to check if the <code class="language-plaintext highlighter-rouge">segments</code> are more than 2. If there are 2 segments only, then we only want the first segment and the last segment code to run.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="c1">//Middle Elements</span>
<span class="k">if</span> <span class="n">segments</span> <span class="o">></span> <span class="mi">2</span> <span class="p">{</span>
<span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="o">...</span><span class="n">segments</span> <span class="o">-</span> <span class="mi">2</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">rhsValueX</span> <span class="o">=</span> <span class="n">rhsArray</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">x</span>
<span class="k">let</span> <span class="nv">prevRhsValueX</span> <span class="o">=</span> <span class="n">rhsArray</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">x</span>
<span class="k">let</span> <span class="nv">rhsValueY</span> <span class="o">=</span> <span class="n">rhsArray</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">y</span>
<span class="k">let</span> <span class="nv">prevRhsValueY</span> <span class="o">=</span> <span class="n">rhsArray</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">y</span>
<span class="n">ad</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">ad</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">/</span> <span class="p">(</span><span class="n">d</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">-</span> <span class="n">bd</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">*</span> <span class="n">ad</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
<span class="k">let</span> <span class="nv">exp1x</span> <span class="o">=</span> <span class="p">(</span><span class="n">rhsValueX</span> <span class="o">-</span> <span class="p">(</span><span class="n">bd</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">*</span> <span class="n">prevRhsValueX</span><span class="p">))</span>
<span class="k">let</span> <span class="nv">exp1y</span> <span class="o">=</span> <span class="p">(</span><span class="n">rhsValueY</span> <span class="o">-</span> <span class="p">(</span><span class="n">bd</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">*</span> <span class="n">prevRhsValueY</span><span class="p">))</span>
<span class="k">let</span> <span class="nv">exp2</span> <span class="o">=</span> <span class="p">(</span><span class="n">d</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">-</span> <span class="n">bd</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">*</span> <span class="n">ad</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
<span class="n">rhsArray</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">exp1x</span> <span class="o">/</span> <span class="n">exp2</span>
<span class="n">rhsArray</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">exp1y</span> <span class="o">/</span> <span class="n">exp2</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Again we are simplying creating a for loop and iterating through all the remaining <code class="language-plaintext highlighter-rouge">rhsArray</code> and diagonals coefficients. Lastly, we will implement the last equation:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="c1">//Last Element</span>
<span class="k">let</span> <span class="nv">lastElementIndex</span> <span class="o">=</span> <span class="n">segments</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">let</span> <span class="nv">exp1</span> <span class="o">=</span> <span class="p">(</span><span class="n">rhsArray</span><span class="p">[</span><span class="n">lastElementIndex</span><span class="p">]</span><span class="o">.</span><span class="n">x</span> <span class="o">-</span> <span class="n">bd</span><span class="p">[</span><span class="n">lastElementIndex</span><span class="p">]</span> <span class="o">*</span> <span class="n">rhsArray</span><span class="p">[</span><span class="n">lastElementIndex</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">x</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">exp1y</span> <span class="o">=</span> <span class="p">(</span><span class="n">rhsArray</span><span class="p">[</span><span class="n">lastElementIndex</span><span class="p">]</span><span class="o">.</span><span class="n">y</span> <span class="o">-</span> <span class="n">bd</span><span class="p">[</span><span class="n">lastElementIndex</span><span class="p">]</span> <span class="o">*</span> <span class="n">rhsArray</span><span class="p">[</span><span class="n">lastElementIndex</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">y</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">exp2</span> <span class="o">=</span> <span class="p">(</span><span class="n">d</span><span class="p">[</span><span class="n">lastElementIndex</span><span class="p">]</span> <span class="o">-</span> <span class="n">bd</span><span class="p">[</span><span class="n">lastElementIndex</span><span class="p">]</span> <span class="o">*</span> <span class="n">ad</span><span class="p">[</span><span class="n">lastElementIndex</span> <span class="o">-</span> <span class="mi">1</span><span class="p">])</span>
<span class="n">rhsArray</span><span class="p">[</span><span class="n">lastElementIndex</span><span class="p">]</span><span class="o">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">exp1</span> <span class="o">/</span> <span class="n">exp2</span>
<span class="n">rhsArray</span><span class="p">[</span><span class="n">lastElementIndex</span><span class="p">]</span><span class="o">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">exp1y</span> <span class="o">/</span> <span class="n">exp2</span></code></pre></figure>
<p>Now, that we have implemented the algorithm, it’s time to do the back substitution. The beauty of this whole Thomas algorithm is that it provides us with the value of the last unknown variable <code class="language-plaintext highlighter-rouge">P1(6)</code> and then we will substitute this to the equation above it and keep doing it to find all the unknown. So here we will start with a for loop that is reversed, so it starts with last index value and goes all the way back.</p>
<p>The reason why we initialized the solutionSet1 to <code class="language-plaintext highlighter-rouge">nil</code> but with a specific count was so that we can add the last <code class="language-plaintext highlighter-rouge">rhsArray</code> element in the last position of the solution set:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="n">solutionSet1</span><span class="p">[</span><span class="n">lastElementIndex</span><span class="p">]</span> <span class="o">=</span> <span class="n">rhsArray</span><span class="p">[</span><span class="n">lastElementIndex</span><span class="p">]</span></code></pre></figure>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">for</span> <span class="n">i</span> <span class="nf">in</span> <span class="p">(</span><span class="mi">0</span><span class="o">..<</span><span class="n">lastElementIndex</span><span class="p">)</span><span class="o">.</span><span class="nf">reversed</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">controlPointX</span> <span class="o">=</span> <span class="n">rhsArray</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">x</span> <span class="o">-</span> <span class="p">(</span><span class="n">ad</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">solutionSet1</span><span class="p">[</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span><span class="o">!.</span><span class="n">x</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">controlPointY</span> <span class="o">=</span> <span class="n">rhsArray</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">.</span><span class="n">y</span> <span class="o">-</span> <span class="p">(</span><span class="n">ad</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">*</span> <span class="n">solutionSet1</span><span class="p">[</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span><span class="o">!.</span><span class="n">y</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"CPX"</span><span class="p">,</span> <span class="n">controlPointX</span><span class="p">,</span> <span class="s">"CPY"</span><span class="p">,</span> <span class="n">controlPointY</span><span class="p">)</span>
<span class="n">solutionSet1</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="n">controlPointX</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="n">controlPointY</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">firstControlPoints</span> <span class="o">=</span> <span class="n">solutionSet1</span>
</code></pre></figure>
<p>So we start a for loop again but reversed, and then find the remaining control points (unknown points) and append it to the <code class="language-plaintext highlighter-rouge">solutionSet1</code> array. Now we have all the first control points. Next we will use the equation 11.1 and 11.2 to find <code class="language-plaintext highlighter-rouge">p2</code>.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="k">for</span> <span class="n">i</span> <span class="nf">in</span> <span class="p">(</span><span class="mi">0</span><span class="o">..<</span><span class="n">segments</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">==</span> <span class="p">(</span><span class="n">segments</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">lastDataPoint</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span>
<span class="k">let</span> <span class="nv">p1</span> <span class="o">=</span> <span class="n">firstControlPoints</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">controlPoint1</span> <span class="o">=</span> <span class="n">p1</span> <span class="k">else</span> <span class="p">{</span> <span class="k">continue</span> <span class="p">}</span>
<span class="k">let</span> <span class="nv">controlPoint2X</span> <span class="o">=</span> <span class="p">(</span><span class="mf">0.5</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">lastDataPoint</span><span class="o">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">controlPoint1</span><span class="o">.</span><span class="n">x</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">controlPoint2y</span> <span class="o">=</span> <span class="p">(</span><span class="mf">0.5</span><span class="p">)</span> <span class="o">*</span> <span class="p">(</span><span class="n">lastDataPoint</span><span class="o">.</span><span class="n">y</span> <span class="o">+</span> <span class="n">controlPoint1</span><span class="o">.</span><span class="n">y</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">controlPoint2</span> <span class="o">=</span> <span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="n">controlPoint2X</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="n">controlPoint2y</span><span class="p">)</span>
<span class="n">secondControlPoints</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">controlPoint2</span><span class="p">)</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">dataPoint</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span>
<span class="k">let</span> <span class="nv">p1</span> <span class="o">=</span> <span class="n">firstControlPoints</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">controlPoint1</span> <span class="o">=</span> <span class="n">p1</span> <span class="k">else</span> <span class="p">{</span> <span class="k">continue</span> <span class="p">}</span>
<span class="k">let</span> <span class="nv">controlPoint2X</span> <span class="o">=</span> <span class="mf">2*</span><span class="n">dataPoint</span><span class="o">.</span><span class="n">x</span> <span class="o">-</span> <span class="n">controlPoint1</span><span class="o">.</span><span class="n">x</span>
<span class="k">let</span> <span class="nv">controlPoint2Y</span> <span class="o">=</span> <span class="mf">2*</span><span class="n">dataPoint</span><span class="o">.</span><span class="n">y</span> <span class="o">-</span> <span class="n">controlPoint1</span><span class="o">.</span><span class="n">y</span>
<span class="n">secondControlPoints</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="n">controlPoint2X</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="n">controlPoint2Y</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Now we have two arrays one with all the first control points and the other with second control points. Now we will create any array of <code class="language-plaintext highlighter-rouge">BezierSegmentControlPoints</code> where there will <code class="language-plaintext highlighter-rouge">P1</code> and <code class="language-plaintext highlighter-rouge">P2</code> for each segement.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">for</span> <span class="n">i</span> <span class="nf">in</span> <span class="p">(</span><span class="mi">0</span><span class="o">..<</span><span class="n">segments</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">firstCP</span> <span class="o">=</span> <span class="n">firstControlPoints</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">else</span> <span class="p">{</span> <span class="k">continue</span> <span class="p">}</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">secondCP</span> <span class="o">=</span> <span class="n">secondControlPoints</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">else</span> <span class="p">{</span> <span class="k">continue</span> <span class="p">}</span>
<span class="k">let</span> <span class="nv">segmentControlPoint</span> <span class="o">=</span> <span class="kt">BezierSegmentControlPoints</span><span class="p">(</span><span class="nv">firstControlPoint</span><span class="p">:</span> <span class="n">firstCP</span><span class="p">,</span> <span class="nv">secondControlPoint</span><span class="p">:</span> <span class="n">secondCP</span><span class="p">)</span>
<span class="n">controlPoints</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">segmentControlPoint</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">controlPoints</span></code></pre></figure>
<h3> UI and Implementation </h3>
<p>In the view controller, create an array of data points and instance of <code class="language-plaintext highlighter-rouge">UIBezierPath</code>:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="k">let</span> <span class="nv">bezierPath</span> <span class="o">=</span> <span class="kt">UIBezierPath</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">data</span> <span class="o">=</span> <span class="p">[</span><span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="mi">40</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="mi">150</span><span class="p">),</span> <span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="mi">140</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="mi">300</span><span class="p">),</span><span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="mi">230</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="mi">140</span><span class="p">),</span> <span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="mi">310</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="mi">250</span><span class="p">),</span> <span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="mi">390</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="mi">100</span><span class="p">),</span> <span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="mi">490</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="mi">200</span><span class="p">),</span> <span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="mi">580</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="mi">270</span><span class="p">),</span><span class="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="mi">650</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="mi">30</span><span class="p">)</span> <span class="p">]</span>
</code></pre></figure>
<p>In the <code class="language-plaintext highlighter-rouge">ViewDidLoad</code>:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">override</span> <span class="kd">func</span> <span class="nf">viewDidLoad</span><span class="p">()</span> <span class="p">{</span>
<span class="k">super</span><span class="o">.</span><span class="nf">viewDidLoad</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">config</span> <span class="o">=</span> <span class="kt">BezierConfiguration</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">controlPoints</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="nf">configureControlPoints</span><span class="p">(</span><span class="nv">data</span><span class="p">:</span> <span class="n">data</span><span class="p">)</span>
<span class="o">...</span>
<span class="p">}</span></code></pre></figure>
<p>So now we have all the control points. Now after you have grabbed all the control points, start a for loop and create a bezier path:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">0</span><span class="o">..<</span><span class="n">data</span><span class="o">.</span><span class="n">count</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">point</span> <span class="o">=</span> <span class="n">data</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
<span class="n">bezierPath</span><span class="o">.</span><span class="nf">move</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="n">point</span><span class="p">)</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">segment</span> <span class="o">=</span> <span class="n">controlPoints</span><span class="p">[</span><span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span>
<span class="n">bezierPath</span><span class="o">.</span><span class="nf">addCurve</span><span class="p">(</span><span class="nv">to</span><span class="p">:</span> <span class="n">point</span><span class="p">,</span> <span class="nv">controlPoint1</span><span class="p">:</span> <span class="n">segment</span><span class="o">.</span><span class="n">firstControlPoint</span><span class="p">,</span> <span class="nv">controlPoint2</span><span class="p">:</span> <span class="n">segment</span><span class="o">.</span><span class="n">secondControlPoint</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>If <code class="language-plaintext highlighter-rouge">i == 0</code>, then we will move the <code class="language-plaintext highlighter-rouge">bezierPath</code> to that point or in other words start the bezier path. Then for all the remaining points we will grab the segement from the control points array and hen use the <code class="language-plaintext highlighter-rouge">addCurve</code> method on <code class="language-plaintext highlighter-rouge">bezierPath</code> so provide it with the next point and the control points.</p>
<p>Lastly, we will create the <code class="language-plaintext highlighter-rouge">CAShapeLayer</code> and give it some color and animation:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">shapeLayer</span> <span class="o">=</span> <span class="kt">CAShapeLayer</span><span class="p">()</span>
<span class="n">shapeLayer</span><span class="o">.</span><span class="n">path</span> <span class="o">=</span> <span class="n">bezierPath</span><span class="o">.</span><span class="n">cgPath</span>
<span class="n">shapeLayer</span><span class="o">.</span><span class="n">lineWidth</span> <span class="o">=</span> <span class="mi">4</span>
<span class="n">shapeLayer</span><span class="o">.</span><span class="n">strokeColor</span> <span class="o">=</span> <span class="kt">UIColor</span><span class="o">.</span><span class="n">black</span><span class="o">.</span><span class="n">cgColor</span>
<span class="n">shapeLayer</span><span class="o">.</span><span class="n">fillColor</span> <span class="o">=</span> <span class="o">.</span><span class="k">none</span>
<span class="n">shapeLayer</span><span class="o">.</span><span class="n">lineCap</span> <span class="o">=</span> <span class="o">.</span><span class="n">round</span>
<span class="n">view</span><span class="o">.</span><span class="n">layer</span><span class="o">.</span><span class="nf">addSublayer</span><span class="p">(</span><span class="n">shapeLayer</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">animation</span> <span class="o">=</span> <span class="kt">CABasicAnimation</span><span class="p">(</span><span class="nv">keyPath</span><span class="p">:</span> <span class="s">"strokeEnd"</span><span class="p">)</span>
<span class="n">animation</span><span class="o">.</span><span class="n">fromValue</span> <span class="o">=</span> <span class="mf">0.0</span>
<span class="n">animation</span><span class="o">.</span><span class="n">toValue</span> <span class="o">=</span> <span class="mf">1.0</span>
<span class="n">animation</span><span class="o">.</span><span class="n">duration</span> <span class="o">=</span> <span class="mf">2.5</span>
<span class="n">shapeLayer</span><span class="o">.</span><span class="nf">add</span><span class="p">(</span><span class="n">animation</span><span class="p">,</span> <span class="nv">forKey</span><span class="p">:</span> <span class="s">"drawKeyAnimation"</span><span class="p">)</span>
</code></pre></figure>
<p>And that’s it! We are done 🎉🚀</p>
<p><img src="/assets/bezierCurve1.gif" alt="" /></p>
<p>⭐️ The full project is available on my
<a href="https://github.com/Onaeem26/CubicBezierCurveSwift">Github</a></p>Osama NaeemSo I wanted to plot some data and decided to create a simple line graph using UIBezierPath. It worked but I wasn’t happy with it. Then I decided to use the quadratic bezier curve method that requires two end points and one control point. Still wasn’t happy with result. Then came the turn of the Cubic bezier curve, that requires two control points and two end points. I knew I needed to use this to create a curve that went through the prescribed points and also creates the smooth and perfect curve that I was looking for. However, it isn’t as straight forward as it first seems.Making an Interactive Card based UI using Swift (Part 2 / 2)2020-01-26T00:00:00+00:002020-01-26T00:00:00+00:00http://exploringswift.com/blog/making-an-interactive-card-based-ui-using-swift-part-2-2<p>In the first part of this tutorial we set up the view controller and created a Glide class where we wrote code regarding segmentation, calculated the segmentation heights and implemented the pan gesture recognizer.
<!--more--></p>
<p>In this part we will do the following things:</p>
<ol>
<li>Implement animations from one state to another.</li>
<li>Animate alpha value with user’s swipe.</li>
<li>Figure out a way to detect scrollViews inside the card view controller and handle the scroll view’s pan gesture and the card’s panGesture which we have implemented previously.</li>
</ol>
<p><img src="/assets/Screen-Shot-2020-01-26-at-9.03.29-PM-1024x708.png" alt="" /></p>
<h3 id="animations">Animations</h3>
<p>Let’s start with the animations. We will be animating cards from one state to the other depending upon the direction of the pan gesture recognizer. Here is how:</p>
<p>Let’s write a method and call it <code class="language-plaintext highlighter-rouge">showCard(state: )</code></p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">guard</span> <span class="k">let</span> <span class="nv">card</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">card</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">container</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">containerView</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">self</span><span class="o">.</span><span class="n">window</span><span class="o">.</span><span class="nf">layoutIfNeeded</span><span class="p">()</span>
<span class="k">if</span> <span class="p">(</span><span class="n">configuration</span><span class="o">.</span><span class="n">segmented</span><span class="p">)</span> <span class="p">{</span>
<span class="k">switch</span> <span class="n">state</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">compressed</span><span class="p">:</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">compressedSegmentHeight</span> <span class="o">=</span> <span class="n">calculatedSegmentHeightsDictionary</span><span class="p">\[</span><span class="o">.</span><span class="n">compressed</span><span class="p">\]</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"No Compressed Segment Height in Configuration File"</span><span class="p">)</span>
<span class="k">return</span> <span class="p">}</span>
<span class="n">cardTopAnchorConstraint</span><span class="o">!.</span><span class="n">constant</span> <span class="o">=</span> <span class="n">compressedSegmentHeight</span>
<span class="k">let</span> <span class="nv">showCard</span> <span class="o">=</span> <span class="kt">UIViewPropertyAnimator</span><span class="p">(</span><span class="nv">duration</span><span class="p">:</span> <span class="mf">0.8</span><span class="p">,</span> <span class="nv">dampingRatio</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">animations</span><span class="p">:</span> <span class="p">{</span> <span class="k">self</span><span class="o">.</span><span class="n">window</span><span class="o">.</span><span class="nf">layoutIfNeeded</span><span class="p">()</span> <span class="p">})</span>
<span class="n">showCard</span><span class="o">.</span><span class="n">addAnimations</span> <span class="p">{</span>
<span class="n">card</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">layer</span><span class="o">.</span><span class="n">cornerRadius</span> <span class="o">=</span> <span class="mi">15</span>
<span class="k">self</span><span class="o">.</span><span class="n">blackView</span><span class="o">.</span><span class="n">alpha</span> <span class="o">=</span> <span class="mf">0.4</span>
<span class="p">}</span>
<span class="n">showCard</span><span class="o">.</span><span class="n">addCompletion</span> <span class="p">{</span> <span class="n">position</span> <span class="k">in</span>
<span class="k">switch</span> <span class="n">position</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">end</span><span class="p">:</span>
<span class="k">self</span><span class="o">.</span><span class="n">currentState</span> <span class="o">=</span> <span class="o">.</span><span class="n">compressed</span>
<span class="nf">print</span><span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">currentState</span><span class="p">)</span>
<span class="k">default</span><span class="p">:</span>
<span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">showCard</span><span class="o">.</span><span class="nf">startAnimation</span><span class="p">()</span>
<span class="k">if</span> <span class="n">showCard</span><span class="o">.</span><span class="n">isRunning</span> <span class="p">{</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">detectedScrollView</span> <span class="o">=</span> <span class="nf">detectScrollView</span><span class="p">()</span> <span class="p">{</span>
<span class="n">detectedScrollView</span><span class="o">.</span><span class="n">panGestureRecognizer</span><span class="o">.</span><span class="n">isEnabled</span> <span class="o">=</span> <span class="kc">false</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">break</span></code></pre></figure>
<p>In this method we are calling a Switch case statement on all three states. I have implemented only one case, but the remaining two will do exactly the same thing for respective dimensions/constraints constant. First we check if the segmentation is enabled or not. If enabled then we use the Switch case to go through the compressed state. We use the <code class="language-plaintext highlighter-rouge">calculatedSegentHeightsDictionary[.compressed]</code> to grab the constraint constant which we will apply to the <code class="language-plaintext highlighter-rouge">cardTopAnchorConstraint</code>.</p>
<p>Then we will use <code class="language-plaintext highlighter-rouge">the UIViewPropertyAnimator</code>. We create a property animator calling <code class="language-plaintext highlighter-rouge">it showCard</code> and providing it with duration of <code class="language-plaintext highlighter-rouge">0.8</code>, and damping ratio of <code class="language-plaintext highlighter-rouge">1</code> and then simply pass the <code class="language-plaintext highlighter-rouge">self.window.layoutIfNeeded()</code> that will refresh the layout and animate the card change from <code class="language-plaintext highlighter-rouge">.open</code> to <code class="language-plaintext highlighter-rouge">.compressed.</code> Then we will add some more animations to the property view animator. Firstly being the <code class="language-plaintext highlighter-rouge">cornerRadius</code> of the card and then <code class="language-plaintext highlighter-rouge">blackView</code>’s alpha value to <code class="language-plaintext highlighter-rouge">0.4</code>. We can add completion code as well so as soon as the animation completes the <code class="language-plaintext highlighter-rouge">currentState gets</code> updated to <code class="language-plaintext highlighter-rouge">.compressed</code>. </p>
<p>Then simply do <code class="language-plaintext highlighter-rouge">showCard.startAnimation()</code>. The code after this line till break is the code that I added later after I realized that there is a slight bug when the user swipes down the card from <code class="language-plaintext highlighter-rouge">.open</code> state to <code class="language-plaintext highlighter-rouge">.compressed</code>. Without the above code, the scrollView is scrollable that cause issues with the scrolling logic which we will implement later on. Basically we don’t want any scrolling to the place when the card is in closed position or compressed position. In fact the gesture of scrolling should expand the card from the closed state to compressed state and finally to open state. Once the open state has been reached only then we can scroll through the scrollView.</p>
<p>In order to fix this, I implement this logic:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="k">if</span> <span class="n">showCard</span><span class="o">.</span><span class="n">isRunning</span> <span class="p">{</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">detectedScrollView</span> <span class="o">=</span> <span class="nf">detectScrollView</span><span class="p">()</span> <span class="p">{</span>
<span class="n">detectedScrollView</span><span class="o">.</span><span class="n">panGestureRecognizer</span><span class="o">.</span><span class="n">isEnabled</span> <span class="o">=</span> <span class="kc">false</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>If the animation is running, then I am using a <code class="language-plaintext highlighter-rouge">detectScrollView()</code> method, more on this later to grab the scrollView if it is available inside the card view controller and disabling it’s panGestureRecognizer.</p>
<p>In doing so, the user is not able to scroll while the card is in transit from <code class="language-plaintext highlighter-rouge">.open</code> to <code class="language-plaintext highlighter-rouge">.compresse</code>d state.</p>
<p>The code for the remaining two states are pretty similar and just require few changes, for example <code class="language-plaintext highlighter-rouge">.alpha </code>value should be 0 for the closed state and the respective calculated segment heights must be assigned to the constraints for respective states.</p>
<h3 id="animating-alpha-values">Animating Alpha Values:</h3>
<p>When the user animates the card from closed state to the compressed state notice how the alpha value goes to it’s max set value with card’s position in the screen. This is achieved in the following method. Let’s take a look at it:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">private</span> <span class="kd">func</span> <span class="nf">dimAlphaWithCardTopConstraint</span><span class="p">(</span><span class="nv">value</span><span class="p">:</span> <span class="kt">CGFloat</span><span class="p">)</span> <span class="o">-></span> <span class="kt">CGFloat</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">fullDimAlpha</span> <span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mf">0.4</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">safeAreaHeight</span> <span class="o">=</span> <span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">keyWindow</span><span class="p">?</span><span class="o">.</span><span class="n">safeAreaLayoutGuide</span><span class="o">.</span><span class="n">layoutFrame</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">height</span><span class="p">,</span>
<span class="k">let</span> <span class="nv">bottomPadding</span> <span class="o">=</span> <span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">keyWindow</span><span class="p">?</span><span class="o">.</span><span class="n">safeAreaInsets</span><span class="o">.</span><span class="n">bottom</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">fullDimAlpha</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">fullDimPosition</span> <span class="o">=</span> <span class="p">(</span><span class="n">safeAreaHeight</span> <span class="o">+</span> <span class="n">bottomPadding</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0</span>
<span class="k">let</span> <span class="nv">noDimPosition</span> <span class="o">=</span> <span class="p">(</span><span class="n">safeAreaHeight</span> <span class="o">+</span> <span class="n">bottomPadding</span> <span class="o">-</span> <span class="p">(</span><span class="n">headerHeight</span> <span class="p">??</span> <span class="mi">0</span><span class="p">))</span>
<span class="k">if</span> <span class="n">value</span> <span class="o"><</span> <span class="n">fullDimPosition</span> <span class="p">{</span> <span class="k">return</span> <span class="n">fullDimAlpha</span> <span class="p">}</span>
<span class="k">if</span> <span class="n">value</span> <span class="o">></span> <span class="n">noDimPosition</span> <span class="p">{</span>
<span class="k">return</span> <span class="mf">0.0</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">fullDimAlpha</span> <span class="o">-</span> <span class="n">fullDimAlpha</span> <span class="p">\</span><span class="o">*</span> <span class="p">((</span><span class="n">value</span> <span class="o">-</span> <span class="n">fullDimPosition</span><span class="p">)</span> <span class="o">/</span> <span class="n">fullDimPosition</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>First I have created a <code class="language-plaintext highlighter-rouge">fullDimAlpha</code> property that is equal to the max alpha value you require when the card is in the open state. Then we grab the <code class="language-plaintext highlighter-rouge">safeAreaHeight</code> and <code class="language-plaintext highlighter-rouge">bottom</code> inset heights. Next property is <code class="language-plaintext highlighter-rouge">fullDimPosition</code> - the position at which the alpha should be equal to <code class="language-plaintext highlighter-rouge">the fullDimAlpha</code>. This should be equal to the height of the open state or compressed state. However, I have made it simple for this version. The alpha value of the blackView should reach 0.4 (fullDimAlpha) when the card reaches the height that is half of the parent view controller. If segmentation is present, then from compressed state to open state the alpha value remains the same. However from compressed to closed state the alpha value changes.</p>
<p><em>I might make it more dynamic - so that it works precisely with the open or compressed state dimensions - something for the next version!</em></p>
<p>Now the actual method takes in a parameter called <code class="language-plaintext highlighter-rouge">value</code> as well.</p>
<p>The next property we define is <code class="language-plaintext highlighter-rouge">noDimPosition</code>. In this we have the <code class="language-plaintext highlighter-rouge">safeAreaHeight + bottomPadding</code> minus any <code class="language-plaintext highlighter-rouge">headerHeight</code>. So now we have two extreme values. Between these two variables we will be monitoring the progress of the swipe/card position and return the alpha value.</p>
<p>If the card position is above the <code class="language-plaintext highlighter-rouge">fullDimPosition</code> then simply return the <code class="language-plaintext highlighter-rouge">fullDimAlpha</code> value or if the position of the card is below the noDimPosition then return 0. If the card is between the two variables then do this:</p>
<p><code class="language-plaintext highlighter-rouge">fullDimAlpha - fullDimAlpha \* ((value - fullDimPosition) / fullDimPosition)</code></p>
<p>Whatever value we are getting from the method argument minus the <code class="language-plaintext highlighter-rouge">fullDimPosition</code> <em>divided</em> by <code class="language-plaintext highlighter-rouge">fullDimPosition</code>. What we are doing here is basically calculating how much the card value has moved between the <code class="language-plaintext highlighter-rouge">noDimPosition</code> and <code class="language-plaintext highlighter-rouge">fullDimPosition</code> and how much that equates to the <code class="language-plaintext highlighter-rouge">fullDimAlpha</code> value. So if the card has covered 50% of the distance between the <code class="language-plaintext highlighter-rouge">noDimPosition</code> and <code class="language-plaintext highlighter-rouge">fullDimPosition</code>, then the alpha value should be <code class="language-plaintext highlighter-rouge">50%</code> of the max value as well.</p>
<p>We are using this method in the panGesture handle method.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">case</span> <span class="o">.</span><span class="nv">changed</span><span class="p">:</span>
<span class="k">let</span> <span class="nv">translationY</span> <span class="o">=</span> <span class="n">recognizer</span><span class="o">.</span><span class="nf">translation</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="n">card</span><span class="o">.</span><span class="n">view</span><span class="p">)</span><span class="o">.</span><span class="n">y</span>
<span class="k">if</span> <span class="k">self</span><span class="o">.</span><span class="n">startingConstant</span> <span class="o">+</span> <span class="n">translationY</span> <span class="o">></span> <span class="mi">0</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">cardTopAnchorConstraint</span><span class="o">!.</span><span class="n">constant</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">startingConstant</span> <span class="o">+</span> <span class="n">translationY</span>
<span class="n">blackView</span><span class="o">.</span><span class="n">alpha</span> <span class="o">=</span> <span class="nf">dimAlphaWithCardTopConstraint</span><span class="p">(</span><span class="nv">value</span><span class="p">:</span> <span class="n">cardTopAnchorConstraint</span><span class="o">!.</span><span class="n">constant</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>Inside the <code class="language-plaintext highlighter-rouge">.changed</code> case, we are setting the topAnchor constraint constant and then also passing this value to the dim alpha method.</p>
<h3 id="handling-scroll-views">Handling Scroll Views:</h3>
<p>The next thing we will talk about is handling any scrollView that the card view controller may contain. We need to come up with a way to switch between the panGesture recognizer and the scrollView at the right state and time. So let’s start with detecting the scrollView.</p>
<p>This is a method that I have created that returns an optional UIScrollView:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">func</span> <span class="nf">detectScrollView</span><span class="p">()</span> <span class="o">-></span> <span class="kt">UIScrollView</span><span class="p">?</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">cardViewController</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">card</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">nil</span><span class="p">}</span>
<span class="k">var</span> <span class="nv">detectedScrollView</span> <span class="p">:</span> <span class="kt">UIScrollView</span><span class="p">?</span> <span class="o">=</span> <span class="kt">UIScrollView</span><span class="p">()</span>
<span class="k">for</span> <span class="n">subview</span> <span class="k">in</span> <span class="n">cardViewController</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">subviews</span> <span class="p">{</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">view</span> <span class="o">=</span> <span class="n">subview</span> <span class="k">as?</span> <span class="kt">UIScrollView</span> <span class="p">{</span>
<span class="n">detectedScrollView</span> <span class="o">=</span> <span class="n">view</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">detectedScrollView</span>
<span class="p">}</span></code></pre></figure>
<p>I am using a simple for loop that iterates through all the subviews of the card View controller and checks if the subview is of type UIScrollView or not. Even if you have a tableView implemented or a collectionView - all these classes inherit the UIScrollView class. The detected view is then returned and can be used in the next method.</p>
<p><em><strong>NOTE: This is a very basic implementation of detecting scroll views method. A more advanced version could be if there are more than one scrollView in a particular view controller and to be able to correctly identify which scrollView should be tracked. </strong></em></p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">gestureRecognizer</span><span class="p">(\</span><span class="n">_</span> <span class="nv">gestureRecognizer</span><span class="p">:</span> <span class="kt">UIGestureRecognizer</span><span class="p">,</span> <span class="n">shouldRecognizeSimultaneouslyWith</span> <span class="nv">otherGestureRecognizer</span><span class="p">:</span> <span class="kt">UIGestureRecognizer</span><span class="p">)</span> <span class="o">-></span> <span class="kt">Bool</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">panGestureRecognzier</span> <span class="o">=</span> <span class="n">gestureRecognizer</span> <span class="k">as?</span> <span class="kt">UIPanGestureRecognizer</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">true</span> <span class="p">}</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">cardViewController</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">card</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">false</span><span class="p">}</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">detectedScrollView</span> <span class="o">=</span> <span class="nf">detectScrollView</span><span class="p">()</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="kc">false</span><span class="p">}</span>
<span class="k">let</span> <span class="nv">velocity</span> <span class="o">=</span> <span class="n">panGestureRecognzier</span><span class="o">.</span><span class="nf">velocity</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="n">cardViewController</span><span class="o">.</span><span class="n">view</span><span class="p">)</span>
<span class="n">detectedScrollView</span><span class="o">.</span><span class="n">panGestureRecognizer</span><span class="o">.</span><span class="n">isEnabled</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">detectedScrollView</span><span class="o">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="k">self</span>
<span class="k">if</span> <span class="n">otherGestureRecognizer</span> <span class="o">==</span> <span class="n">detectedScrollView</span><span class="o">.</span><span class="n">panGestureRecognizer</span> <span class="p">{</span>
<span class="k">switch</span> <span class="n">currentState</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">compressed</span><span class="p">:</span>
<span class="n">detectedScrollView</span><span class="o">.</span><span class="n">panGestureRecognizer</span><span class="o">.</span><span class="n">isEnabled</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">return</span> <span class="kc">false</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">closed</span><span class="p">:</span>
<span class="n">detectedScrollView</span><span class="o">.</span><span class="n">panGestureRecognizer</span><span class="o">.</span><span class="n">isEnabled</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">return</span> <span class="kc">false</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">open</span><span class="p">:</span>
<span class="k">if</span> <span class="n">velocity</span><span class="o">.</span><span class="n">y</span> <span class="o">></span> <span class="mf">0.0</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">detectedScrollView</span><span class="o">.</span><span class="n">contentOffset</span><span class="o">.</span><span class="n">y</span> <span class="o">></span> <span class="mf">0.0</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="n">shouldHandleGesture</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">detectedScrollView</span><span class="o">.</span><span class="n">panGestureRecognizer</span><span class="o">.</span><span class="n">isEnabled</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">return</span> <span class="kc">false</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="n">shouldHandleGesture</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">return</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="k">default</span><span class="p">:</span>
<span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">false</span>
<span class="p">}</span></code></pre></figure>
<p>The <code class="language-plaintext highlighter-rouge">shouldRecognizeSimultaneously</code> method is a <code class="language-plaintext highlighter-rouge">UIGestureRecognizerDelegate</code> method and for this to work you need to conform to the <code class="language-plaintext highlighter-rouge">UIGestureRecognizerDelegate</code> and make sure to assign panGesture’s delegate to self.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">class</span> <span class="kt">Glide</span> <span class="p">:</span> <span class="kt">NSObject</span><span class="p">,</span> <span class="kt">UIGestureRecognizerDelegate</span><span class="p">,</span> <span class="kt">UIScrollViewDelegate</span>
<span class="n">gestureRecognizer</span><span class="o">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="k">self</span> <span class="c1">// Make sure to assign the delegate of panGestureRecognizer to self</span></code></pre></figure>
<p>Now lets talk about what is happening in <code class="language-plaintext highlighter-rouge">the shouldRecognizeSimultaneously </code> method. In this method we get two gesture recognizer and we will apply few conditions as to when both the detectedScrollView’s scrollView can work and when the pan gesture recognizer should work and when both these recognizers should work. The otherRecognizer is scrollView’s pan gesture recognizer whereas panGestureRecognizer is the main card’s gesture recognizer we have implemented.</p>
<p>Let’s take a look at another bool property <code class="language-plaintext highlighter-rouge">called shouldHandleGesture</code>. This boolean property is like a flag here. If this is true then all the code inside <code class="language-plaintext highlighter-rouge">handlePanRecognizer</code> will work and if it is false then the pan gesture code won’t work. We have implemented this in <code class="language-plaintext highlighter-rouge">handlePanRecognizer</code> method here:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">@objc</span> <span class="kd">func</span> <span class="nf">handlePanRecognizer</span><span class="p">(</span><span class="nv">recognizer</span><span class="p">:</span> <span class="kt">UIPanGestureRecognizer</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">safeAreaLayout</span> <span class="o">=</span> <span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">keyWindow</span><span class="p">?</span><span class="o">.</span><span class="n">safeAreaLayoutGuide</span><span class="o">.</span><span class="n">layoutFrame</span><span class="o">.</span><span class="n">height</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">bottomAreaInset</span> <span class="o">=</span> <span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">keyWindow</span><span class="p">?</span><span class="o">.</span><span class="n">safeAreaInsets</span><span class="o">.</span><span class="n">bottom</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="o">**</span><span class="k">guard</span> <span class="n">shouldHandleGesture</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span> <span class="c1">// This should stop any bottom code from running if false, else gesture code should work fine.**</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">container</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">containerView</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">card</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">card</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">switch</span> <span class="n">recognizer</span><span class="o">.</span><span class="n">state</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">began</span><span class="p">:</span>
<span class="c1">//</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">ended</span><span class="p">:</span>
<span class="c1">//</span></code></pre></figure>
<p>Now going back to <code class="language-plaintext highlighter-rouge">the shouldRecognizeSimultaneously</code>,</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">switch</span> <span class="n">currentState</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">compressed</span><span class="p">:</span>
<span class="n">detectedScrollView</span><span class="o">.</span><span class="n">panGestureRecognizer</span><span class="o">.</span><span class="n">isEnabled</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">return</span> <span class="kc">false</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">closed</span><span class="p">:</span>
<span class="n">detectedScrollView</span><span class="o">.</span><span class="n">panGestureRecognizer</span><span class="o">.</span><span class="n">isEnabled</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">return</span> <span class="kc">false</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">open</span><span class="p">:</span>
<span class="k">if</span> <span class="n">velocity</span><span class="o">.</span><span class="n">y</span> <span class="o">></span> <span class="mf">0.0</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">detectedScrollView</span><span class="o">.</span><span class="n">contentOffset</span><span class="o">.</span><span class="n">y</span> <span class="o">></span> <span class="mf">0.0</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="n">shouldHandleGesture</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">detectedScrollView</span><span class="o">.</span><span class="n">panGestureRecognizer</span><span class="o">.</span><span class="n">isEnabled</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">return</span> <span class="kc">false</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="n">shouldHandleGesture</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">return</span> <span class="kc">true</span>
<span class="p">}</span></code></pre></figure>
<p>In the switch statement we are checking the current state of the card and if it is .<code class="language-plaintext highlighter-rouge">compressed </code> then that means both gestures shouldn’t work simultaneously so we will first disable the detectedScrollView’s panGestureRecognizer and return false. Similarly in the <code class="language-plaintext highlighter-rouge">.closed</code> position. In the <code class="language-plaintext highlighter-rouge">.open</code> position, we check if the user is scrolling upwards . Then we check if the scrollView’s <code class="language-plaintext highlighter-rouge">contentOffset</code> is greater than 0 or not which means if there is still some content to go and we have not reached the top of the scrollView. During this we are returning true.</p>
<p>Since we are simultaneously recognizing both gestures, as soon as the top of the scrollview is reached, we assign the bool value <code class="language-plaintext highlighter-rouge">of shouldHandleGesture </code> to true and disable the <code class="language-plaintext highlighter-rouge">scrollView</code>’s pan gesture recognizer and return false which causes the card to move down to the compressed state.</p>
<p>If the user is scrolling the card downwards, keep recognizing both gestures but make sure that the pan gesture doesn’t run the code. (shouldHandleGesture = false). </p>
<p>Then we will use a <code class="language-plaintext highlighter-rouge">UIScrollViewDelegate</code> function called <code class="language-plaintext highlighter-rouge">scrollViewDidScroll</code></p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">func</span> <span class="nf">scrollViewDidScroll</span><span class="p">(\</span><span class="n">_</span> <span class="nv">scrollView</span><span class="p">:</span> <span class="kt">UIScrollView</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">contentOffset</span> <span class="o">=</span> <span class="n">scrollView</span><span class="o">.</span><span class="n">contentOffset</span><span class="o">.</span><span class="n">y</span>
<span class="k">if</span> <span class="n">contentOffset</span> <span class="o"><=</span> <span class="mf">0.0</span> <span class="o">&&</span>
<span class="n">currentState</span> <span class="o">==</span> <span class="o">.</span><span class="kd">open</span> <span class="o">&&</span>
<span class="n">gestureRecognizer</span><span class="o">.</span><span class="nf">velocity</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="n">gestureRecognizer</span><span class="o">.</span><span class="n">view</span><span class="p">)</span><span class="o">.</span><span class="n">y</span> <span class="o">!=</span> <span class="mf">0.0</span> <span class="p">{</span>
<span class="n">shouldHandleGesture</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">scrollView</span><span class="o">.</span><span class="n">isScrollEnabled</span> <span class="o">=</span> <span class="kc">false</span>
<span class="n">scrollView</span><span class="o">.</span><span class="n">isScrollEnabled</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Which is pretty much doing the same thing - if the top of the scroll view is reached and the user wants to scroll upwards and the current state is .open then make the <code class="language-plaintext highlighter-rouge">shouldHandleGesture</code> boolean true and the card will then move down to the compressed state etc.</p>
<p><img src="/assets/glideUI.gif" alt="glideUI.gif" /></p>
<p>That is about it really! We have a fully functional Interactive card based UI ready. The UI is designed in such that it could be reused easily.Most of the logic takes place inside the Glide class. To configure the card and set its dimensions we have a GlideConfiguration file. To make any view controller work as a card, you just need to conform it to Glideable protocol. Most important feature is that it has a simple set up - Just 3 to 4 lines of code to get the UI working.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">glideConfig</span> <span class="o">=</span> <span class="kt">Configuration</span><span class="p">()</span> <span class="c1">// Glide Configuration File</span>
<span class="k">let</span> <span class="nv">cardController</span> <span class="o">=</span> <span class="kt">CardViewController</span><span class="p">()</span> <span class="c1">// Card View Controller</span>
<span class="c1">/// One line manager initializer</span>
<span class="n">glideManager</span> <span class="o">=</span> <span class="kt">Glide</span><span class="p">(</span><span class="nv">parentViewController</span><span class="p">:</span> <span class="k">self</span><span class="p">,</span> <span class="nv">configuration</span><span class="p">:</span> <span class="n">glideConfig</span><span class="p">,</span> <span class="nv">card</span><span class="p">:</span> <span class="n">cardController</span><span class="p">)</span></code></pre></figure>
<p>More features will be added to this project in the near future. Things like being able to receive notifications inside the parent view controller when the card is dismissed or is in the compressed state and more.</p>
<p>The full project is available on my <a href="https://github.com/Onaeem26/GlideUI">GitHub</a>.</p>Osama NaeemIn the first part of this tutorial we set up the view controller and created a Glide class where we wrote code regarding segmentation, calculated the segmentation heights and implemented the pan gesture recognizer.Making an Interactive Card based UI using Swift (Part 1/2)2020-01-18T00:00:00+00:002020-01-18T00:00:00+00:00http://exploringswift.com/blog/making-an-interactive-card-based-ui-using-swift-part-1-2<p>Most apps nowadays use this card based UI to pop up information or show an action menu etc. Twitter, Instagram, Facebook all are using some sort of a multi-state, interactive, gesture based card flow to show information to users. I have always been intrigued by such a design element and thought I should try building one from scratch.
<!--more--></p>
<p>While building this, I also wanted to make sure that I make this in such a way that people could use it. As in if I ever want to release this as a framework, I could easily do it. So while I have not released this project as a framework, I have tried to code it in such a way to practice framework building skills and make it reusable.</p>
<p><img src="/assets/Screen-Shot-2020-01-19-at-2.49.51-AM-1024x792.png" alt="" />
<strong>The entire project is available on my Github <a href="https://github.com/Onaeem26/GlideUI">here</a>.</strong></p>
<h3 id="the-idea">THE IDEA:</h3>
<p>The whole project revolved around how reusable, and quickly a user can set up a card UI. There were few principles that this project had to follow:</p>
<ol>
<li>Make sure that the configuration and set up is quick.</li>
<li>User should get two options. Segmented(multi states) or non segmented(dual states). (more on this later)</li>
<li>Granular control over card visible height for each state.</li>
<li>The gestures need to be smooth and intuitive / animating the background alpha values as well.</li>
<li>The ability to show the card floating at the bottom as a closed state. (Something we see in Maps app, Uber etc)</li>
<li>The ability to detect a scrollView inside the controller and switch between scrollView & Pan Gesture recognizer at right times.</li>
</ol>
<p>So let’s get started. First we will create couple of protocols. The first protocol we will create is called: Glideable. This Glideable protocol contains only one property which is headerHeight.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">protocol</span> <span class="kt">Glideable</span><span class="p">:</span> <span class="kd">class</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">headerHeight</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="p">{</span> <span class="k">get</span> <span class="k">set</span> <span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Any View controller that conforms to the <code class="language-plaintext highlighter-rouge">Glideable</code> protocol could be used as the card View Controller. The <code class="language-plaintext highlighter-rouge">headerHeight</code> allows user to set a visible height for the card view controller. So if you want the card to float around at the bottom then use this property to provide the visible height for the card.</p>
<p>The next protocol we will create is called <code class="language-plaintext highlighter-rouge">GlideConfiguration</code>. This protocol will contain all the necessary protocols required to set up and customize the entire Glide animation. For now this protocol will only have four properties.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">protocol</span> <span class="kt">GlideConfiguration</span> <span class="p">:</span> <span class="kd">class</span> <span class="p">{</span>
<span class="c1">/// When Segmentation Enabled, you get three States</span>
<span class="c1">///Open - Max Height</span>
<span class="c1">///Compressed - Intermediate Height</span>
<span class="c1">///Closed - Min Height of the card</span>
<span class="k">var</span> <span class="nv">segmented</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="c1">///When Segmentation is ENABLED</span>
<span class="c1">///A dictionary that takes the state enum (.open, .compressed, .closed) & corresponding heights</span>
<span class="k">var</span> <span class="nv">segmentHeightDictionary</span><span class="p">:</span> <span class="p">\[</span><span class="kt">State</span><span class="p">:</span> <span class="kt">CGFloat</span><span class="p">\]</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="c1">///If Segmentation is turned OFF</span>
<span class="c1">///Assign a concrete height to the card. It can be:</span>
<span class="c1">///Fullscreen - Takes the fullscreen of the parent view</span>
<span class="c1">///Half - half of the parent view</span>
<span class="c1">///OneFourth - 1/4 of the parent view</span>
<span class="c1">///OneThird - 1/3 of the parent View</span>
<span class="c1">///Custom - Give a custom dimension and it should open till that point.</span>
<span class="c1">//Concrete Dimensions only have two states - close(0 or headerHeight) or opened (selected from above options)</span>
<span class="k">var</span> <span class="nv">concreteDimension</span><span class="p">:</span> <span class="kt">GlideConcreteDimension</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="c1">//To show a grey handle top indicate this is a card/ interactable</span>
<span class="k">var</span> <span class="nv">popUpIndicator</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span> <span class="k">get</span> <span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>This properties have been explained through the comments in the above code snippet but I will explain them again here:</p>
<p><code class="language-plaintext highlighter-rouge">Segmented</code>: When enabled, it will allow users to have three states. Close, Compressed & Open. When disabled: only two states will be available. Close & Open.</p>
<p><code class="language-plaintext highlighter-rouge">SegmentHeightDictionary</code>: This is a dictionary that requires the state and the height for that state. The key for this dictionary is “State” enum that has three cases: .open, .compressed & .closed. The values for each key is a CGFloat.</p>
<p><code class="language-plaintext highlighter-rouge">ConcreteDimension</code>: When the segmented bool is false, then a dimension needs to be given to which the card needs to open. This could be one of the preselected enum cases (<code class="language-plaintext highlighter-rouge">fullscreen</code>, <code class="language-plaintext highlighter-rouge">half</code>, <code class="language-plaintext highlighter-rouge">onefourth</code>, <code class="language-plaintext highlighter-rouge">onethird</code>) or it could be custom - so just provide a custom CGFloat number.</p>
<p><code class="language-plaintext highlighter-rouge">popUpIndicator</code>: This provides a grey colored handle top of the card view controller just to indicate that this view is draggable.</p>
<p>Next let’s talk about the Glide class. The Glide class is created in such a manner so as to provide a one line setup to the developer. So if you want to set up a new glide manager class this is how it should go:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">var</span> <span class="nv">glideManager</span> <span class="p">:</span> <span class="kt">Glide</span><span class="o">!</span>
<span class="n">glideManager</span> <span class="o">=</span> <span class="kt">Glide</span><span class="p">(</span><span class="nv">parentViewController</span><span class="p">:</span> <span class="k">self</span><span class="p">,</span> <span class="nv">configuration</span><span class="p">:</span> <span class="n">glideConfig</span><span class="p">,</span> <span class="nv">card</span><span class="p">:</span> <span class="n">cardController</span><span class="p">)</span></code></pre></figure>
<p>Just created a Glide property and initialize the object using a simple initializer that takes in the parent view controller, the configuration file which is of <code class="language-plaintext highlighter-rouge">GlideConfiguration</code> protocol type and the view controller that needs to be of type Glide.</p>
<p>Let’s take a look at how the Glide class works. This class is pretty much doing the heavy lifting for us. This is where the whole logic sits in. From setting up the card view controller and the parent view controller, to animations, to different configuration options etc.</p>
<p>We will divide this class in to three components:</p>
<h3 id="1-setting-up-the-parent-view-controllerwindow-and-the-card-view-controller">1. Setting up the parent view controller/Window and the card view controller:</h3>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">private</span> <span class="k">var</span> <span class="nv">configuration</span><span class="p">:</span> <span class="kt">GlideConfiguration</span><span class="o">!</span>
<span class="kd">private</span> <span class="k">weak</span> <span class="k">var</span> <span class="nv">parentViewController</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">?</span>
<span class="kd">private</span> <span class="k">var</span> <span class="nv">card</span><span class="p">:</span> <span class="p">(</span><span class="kt">Glideable</span> <span class="o">&</span> <span class="kt">UIViewController</span><span class="p">)?</span></code></pre></figure>
<p>First thing I have done is to declare three properties. c_onfiguration_ - for the GlideConfiguration, parentViewController - UIViewController type and then <em>card</em> which is of type Glide and UIViewController. In the initializer we are setting these properties.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">self</span><span class="o">.</span><span class="n">parentViewController</span> <span class="o">=</span> <span class="n">parentViewController</span>
<span class="k">self</span><span class="o">.</span><span class="n">configuration</span> <span class="o">=</span> <span class="n">configuration</span>
<span class="k">self</span><span class="o">.</span><span class="n">card</span> <span class="o">=</span> <span class="n">card</span>
<span class="kt">Next</span> <span class="n">we</span> <span class="n">will</span> <span class="n">add</span> <span class="n">the</span> <span class="n">card</span> <span class="n">to</span> <span class="n">the</span> <span class="n">container</span> <span class="n">or</span> <span class="n">the</span> <span class="n">parentViewController</span><span class="o">.</span> <span class="kt">To</span> <span class="k">do</span> <span class="n">this</span> <span class="kt">I</span> <span class="n">have</span> <span class="n">created</span> <span class="n">couple</span> <span class="n">of</span> <span class="n">function</span> <span class="n">just</span> <span class="n">to</span> <span class="n">make</span> <span class="n">the</span> <span class="n">code</span> <span class="n">readable</span> <span class="n">and</span> <span class="n">easier</span> <span class="n">to</span> <span class="n">understand</span><span class="o">.</span>
<span class="kd">private</span> <span class="kd">func</span> <span class="nf">setupPreviewLayer</span><span class="p">(</span><span class="nv">parentViewController</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">,</span> <span class="nv">cardViewController</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">)</span> <span class="p">{</span>
<span class="n">window</span> <span class="o">=</span> <span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">keyWindow</span><span class="o">!</span>
<span class="n">blackView</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="kt">UIColor</span><span class="o">.</span><span class="n">black</span>
<span class="n">blackView</span><span class="o">.</span><span class="n">alpha</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">window</span><span class="o">.</span><span class="nf">addSubview</span><span class="p">(</span><span class="n">blackView</span><span class="p">)</span>
<span class="n">blackView</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">window</span><span class="o">.</span><span class="n">frame</span>
<span class="n">blackView</span><span class="o">.</span><span class="nf">addGestureRecognizer</span><span class="p">(</span><span class="kt">UITapGestureRecognizer</span><span class="p">(</span><span class="nv">target</span><span class="p">:</span> <span class="k">self</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="kd">#selector(</span><span class="nf">handleTapRecognizer</span><span class="kd">)</span><span class="p">))</span>
<span class="n">window</span><span class="o">.</span><span class="nf">addSubview</span><span class="p">(</span><span class="n">cardViewController</span><span class="o">.</span><span class="n">view</span><span class="p">)</span>
<span class="nf">showPopUpIndicator</span><span class="p">()</span>
<span class="p">}</span></code></pre></figure>
<p>In this function, I am taking two parameters, parentViewController and the cardViewController. Even though I am only adding the cardViewController to the UIWindow and not the parentViewController - however if you want to change things up and add the card on the parentVC directly you can do it as well. Then you may ask why are we using the parentVC? We will use it further in the code for sizing and dimensions.</p>
<p>So in this function we are creating a window, adding a UIView named as blackView on the window and setting the frame of the UIView as that of the whole window. Then I am adding the gesture recognizer on the blackView and adding the cardViewController’s view as a subview to the window. This gesture recognizer is mainly to dismiss the card when the user taps elsewhere. So essentially what we get here is a blackView with alpha = 0 but this will be animated to a different value in order to dim the background when the card is enabled or showing. As shown in the picture below.</p>
<p><img src="/assets/Screen-Shot-2020-01-19-at-1.02.31-AM-1024x602.png" alt="" />
</p>
<h3 id="2-card-view-controller-states-and-dimensions">2. Card View Controller States and Dimensions:</h3>
<p>This next function will be used to add constraints to the card view controller’s view.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">private</span> <span class="kd">func</span> <span class="nf">addChildToContainer</span><span class="p">(</span><span class="nv">containerView</span><span class="p">:</span> <span class="kt">UIView</span><span class="p">,</span> <span class="nv">card</span><span class="p">:</span> <span class="kt">UIView</span><span class="p">)</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">safeAreaLayout</span> <span class="o">=</span> <span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">keyWindow</span><span class="p">?</span><span class="o">.</span><span class="n">safeAreaLayoutGuide</span><span class="o">.</span><span class="n">layoutFrame</span><span class="o">.</span><span class="n">height</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">bottomAreaInset</span> <span class="o">=</span> <span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">keyWindow</span><span class="p">?</span><span class="o">.</span><span class="n">safeAreaInsets</span><span class="o">.</span><span class="n">bottom</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="c1">///Calculating Segment Heights if Segmentation is enabled in Configuration file</span>
<span class="nf">calculateSegmentHeights</span><span class="p">()</span>
<span class="c1">///The headerheight is over written if segmentation is enabled</span>
<span class="c1">///Segmentation closed height then becomes the headerHeight</span>
<span class="k">let</span> <span class="nv">visibleHeight</span> <span class="o">=</span> <span class="n">configuration</span><span class="o">.</span><span class="n">segmented</span> <span class="p">?</span> <span class="p">(</span><span class="n">calculatedSegmentHeightsDictionary</span><span class="p">\[</span><span class="o">.</span><span class="n">closed</span><span class="p">\]</span> <span class="p">??</span> <span class="mi">0</span><span class="p">)</span> <span class="p">:</span> <span class="p">(</span><span class="n">safeAreaLayout</span> <span class="o">+</span> <span class="n">bottomAreaInset</span> <span class="o">-</span> <span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">headerHeight</span> <span class="p">??</span> <span class="mi">0</span><span class="p">))</span>
<span class="n">card</span><span class="o">.</span><span class="n">translatesAutoresizingMaskIntoConstraints</span> <span class="o">=</span> <span class="kc">false</span>
<span class="n">cardTopAnchorConstraint</span> <span class="o">=</span> <span class="n">card</span><span class="o">.</span><span class="n">topAnchor</span><span class="o">.</span><span class="nf">constraint</span><span class="p">(</span><span class="nv">equalTo</span><span class="p">:</span> <span class="n">window</span><span class="o">.</span><span class="n">safeAreaLayoutGuide</span><span class="o">.</span><span class="n">topAnchor</span><span class="p">,</span> <span class="nv">constant</span><span class="p">:</span> <span class="n">visibleHeight</span><span class="p">)</span>
<span class="n">card</span><span class="o">.</span><span class="n">leadingAnchor</span><span class="o">.</span><span class="nf">constraint</span><span class="p">(</span><span class="nv">equalTo</span><span class="p">:</span> <span class="n">window</span><span class="o">.</span><span class="n">leadingAnchor</span><span class="p">)</span><span class="o">.</span><span class="n">isActive</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">card</span><span class="o">.</span><span class="n">trailingAnchor</span><span class="o">.</span><span class="nf">constraint</span><span class="p">(</span><span class="nv">equalTo</span><span class="p">:</span> <span class="n">window</span><span class="o">.</span><span class="n">trailingAnchor</span><span class="p">)</span><span class="o">.</span><span class="n">isActive</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">card</span><span class="o">.</span><span class="n">bottomAnchor</span><span class="o">.</span><span class="nf">constraint</span><span class="p">(</span><span class="nv">equalTo</span><span class="p">:</span> <span class="n">window</span><span class="o">.</span><span class="n">bottomAnchor</span><span class="p">)</span><span class="o">.</span><span class="n">isActive</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">cardTopAnchorConstraint</span><span class="o">.</span><span class="n">isActive</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">}</span></code></pre></figure>
<p> </p>
<p>The function starts with first getting the safeAreaLayout - this essentially caters for the status bar, the home bar at the bottom and more. So after we get these two properties we get call a new function called <code class="language-plaintext highlighter-rouge">calculateSegmentHeight</code>.</p>
<p>Let’s take a look at this function now:</p>
<p>In the configuration file of the Glide we have few options that the developer can set. One of them is <code class="language-plaintext highlighter-rouge">segmented</code>. This is a bool value that lets user add states to the card flow. So we have <code class="language-plaintext highlighter-rouge">.open</code>, <code class="language-plaintext highlighter-rouge">.compressed</code>, <code class="language-plaintext highlighter-rouge">.closed</code>. So if the user has enabled this option we need to carefully set up the dimension of the card’s closed/open/compressed state. This calculation is done here but will be used in the animation stage of the class where we need these values to animate the card to different state values.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">private</span> <span class="kd">func</span> <span class="nf">calculateSegmentHeights</span><span class="p">()</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">safeAreaLayout</span> <span class="o">=</span> <span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">keyWindow</span><span class="p">?</span><span class="o">.</span><span class="n">safeAreaLayoutGuide</span><span class="o">.</span><span class="n">layoutFrame</span><span class="o">.</span><span class="n">height</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">bottomAreaInset</span> <span class="o">=</span> <span class="kt">UIApplication</span><span class="o">.</span><span class="n">shared</span><span class="o">.</span><span class="n">keyWindow</span><span class="p">?</span><span class="o">.</span><span class="n">safeAreaInsets</span><span class="o">.</span><span class="n">bottom</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">if</span> <span class="n">configuration</span><span class="o">.</span><span class="n">segmented</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">segmentationHeights</span> <span class="o">=</span> <span class="n">configuration</span><span class="o">.</span><span class="n">segmentHeightDictionary</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">compressedHeight</span> <span class="o">=</span> <span class="n">segmentationHeights</span><span class="p">\[</span><span class="o">.</span><span class="n">compressed</span><span class="p">\]</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"No Compressed Heights Available in Configuration File"</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">openHeight</span> <span class="o">=</span> <span class="n">segmentationHeights</span><span class="p">\[</span><span class="o">.</span><span class="kd">open</span><span class="p">\]</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"No Open Heights Available in Configuration File"</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">closeHeight</span> <span class="o">=</span> <span class="n">segmentationHeights</span><span class="p">\[</span><span class="o">.</span><span class="n">closed</span><span class="p">\]</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">print</span><span class="p">(</span><span class="s">"No closed Heights Available in Configuration File"</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">let</span> <span class="nv">compressedStateConstraintConstant</span> <span class="o">=</span> <span class="p">(</span><span class="n">safeAreaLayout</span> <span class="o">+</span> <span class="n">bottomAreaInset</span> <span class="o">-</span> <span class="n">compressedHeight</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">openStateConstraintConstant</span> <span class="o">=</span> <span class="p">(</span><span class="n">safeAreaLayout</span> <span class="o">+</span> <span class="n">bottomAreaInset</span> <span class="o">-</span> <span class="n">openHeight</span><span class="p">)</span>
<span class="k">let</span> <span class="nv">closedStateConstraintConstant</span> <span class="o">=</span> <span class="p">(</span><span class="n">safeAreaLayout</span> <span class="o">+</span> <span class="n">bottomAreaInset</span> <span class="o">-</span> <span class="n">closeHeight</span><span class="p">)</span>
<span class="n">calculatedSegmentHeightsDictionary</span><span class="p">\[</span><span class="o">.</span><span class="n">compressed</span><span class="p">\]</span> <span class="o">=</span> <span class="n">compressedStateConstraintConstant</span>
<span class="n">calculatedSegmentHeightsDictionary</span><span class="p">\[</span><span class="o">.</span><span class="kd">open</span><span class="p">\]</span> <span class="o">=</span> <span class="n">openStateConstraintConstant</span>
<span class="n">calculatedSegmentHeightsDictionary</span><span class="p">\[</span><span class="o">.</span><span class="n">closed</span><span class="p">\]</span> <span class="o">=</span> <span class="n">closedStateConstraintConstant</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>So in this method we are again grabbing the safeAreaLayout and bottomAreaInset heights. Then we access the configuration file which was passed to this class through the initializer and check if the segmented bool is true or false. If it is true, then we grab the values provided to us by the configuration file of the closed / compressed and open state. This is passed through a dictionary. Essentially we are just getting the segmentationHeights for respective states and saving them to the <code class="language-plaintext highlighter-rouge">compressedHeight</code>, <code class="language-plaintext highlighter-rouge">openHeight</code><code class="language-plaintext highlighter-rouge"> and closeHeight</code>. Then we are calculating the constraint constants catering for the <code class="language-plaintext highlighter-rouge">bottomAreaInset</code> and the <code class="language-plaintext highlighter-rouge">safeAreaLayout</code> height and saving it to <code class="language-plaintext highlighter-rouge">calculatedSegmentedHeightsDictionary</code> property.</p>
<p>Going back to the initial function, the next line states the visible height of the card. This visible height is the height that the developer sets initially when the cardViewController conforms to the <code class="language-plaintext highlighter-rouge">Glideable</code> protocol.</p>
<p>Do note here that when the segmentation is enabled, this visibleHeight property is overridden. So in order to get visible card showing at the bottom even when the segmentation is enabled, add some height to the .closed state <code class="language-plaintext highlighter-rouge">in segmentHeightsDictionary</code> in the configuration file.</p>
<p>Then we are simply setting up the auto layout constraints of the card view over the UIWindow and making sure that we can access the top constraint by storing it in a <code class="language-plaintext highlighter-rouge">NSLayoutConstraint</code> property named:<code class="language-plaintext highlighter-rouge"> cardTopAnchorConstraint</code>. </p>
<h3 id="3-animating-the-cards">3. Animating the cards:</h3>
<p>Let’s add a pan gesture recognizer to the <code class="language-plaintext highlighter-rouge">cardViewController</code>.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="n">gestureRecognizer</span> <span class="o">=</span> <span class="kt">UIPanGestureRecognizer</span><span class="p">(</span><span class="nv">target</span><span class="p">:</span> <span class="k">self</span><span class="p">,</span> <span class="nv">action</span><span class="p">:</span> <span class="kd">#selector(</span><span class="nf">handlePanRecognizer(recognizer: )</span><span class="kd">)</span><span class="p">)</span>
<span class="n">gestureRecognizer</span><span class="o">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="k">self</span>
<span class="n">card</span><span class="o">.</span><span class="nf">addGestureRecognizer</span><span class="p">(</span><span class="n">gestureRecognizer</span><span class="p">)</span></code></pre></figure>
<p>In the handlePanRecognizer method we are basically checking the recognizer state and doing setting the card’s constraint constant.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">switch</span> <span class="n">recognizer</span><span class="o">.</span><span class="n">state</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">began</span><span class="p">:</span>
<span class="n">startingConstant</span> <span class="o">=</span> <span class="p">(</span><span class="n">cardTopAnchorConstraint</span><span class="p">?</span><span class="o">.</span><span class="n">constant</span><span class="p">)</span><span class="o">!</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">changed</span><span class="p">:</span>
<span class="k">let</span> <span class="nv">translationY</span> <span class="o">=</span> <span class="n">recognizer</span><span class="o">.</span><span class="nf">translation</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="n">card</span><span class="o">.</span><span class="n">view</span><span class="p">)</span><span class="o">.</span><span class="n">y</span>
<span class="k">if</span> <span class="k">self</span><span class="o">.</span><span class="n">startingConstant</span> <span class="o">+</span> <span class="n">translationY</span> <span class="o">></span> <span class="mi">0</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">cardTopAnchorConstraint</span><span class="o">!.</span><span class="n">constant</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">startingConstant</span> <span class="o">+</span> <span class="n">translationY</span>
<span class="n">blackView</span><span class="o">.</span><span class="n">alpha</span> <span class="o">=</span> <span class="nf">dimAlphaWithCardTopConstraint</span><span class="p">(</span><span class="nv">value</span><span class="p">:</span> <span class="n">cardTopAnchorConstraint</span><span class="o">!.</span><span class="n">constant</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">ended</span><span class="p">:</span>
<span class="k">let</span> <span class="nv">velocityY</span> <span class="o">=</span> <span class="n">recognizer</span><span class="o">.</span><span class="nf">velocity</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="n">card</span><span class="o">.</span><span class="n">view</span><span class="p">)</span><span class="o">.</span><span class="n">y</span>
<span class="k">let</span> <span class="nv">topAnchorConstant</span> <span class="o">=</span> <span class="n">configuration</span><span class="o">.</span><span class="n">segmented</span> <span class="p">?</span> <span class="n">calculatedSegmentHeightsDictionary</span><span class="p">\[</span><span class="o">.</span><span class="n">compressed</span><span class="p">\]</span><span class="o">!</span> <span class="p">:</span> <span class="nf">configureCardSize</span><span class="p">(</span><span class="nv">parentView</span><span class="p">:</span> <span class="n">container</span><span class="p">,</span> <span class="nv">configuration</span><span class="p">:</span> <span class="n">configuration</span><span class="p">)</span>
<span class="k">if</span> <span class="n">cardTopAnchorConstraint</span><span class="o">!.</span><span class="n">constant</span> <span class="o"><</span> <span class="n">topAnchorConstant</span> <span class="p">{</span> <span class="k">if</span> <span class="n">velocityY</span> <span class="o">></span> <span class="mi">0</span> <span class="p">{</span>
<span class="c1">//card moving down</span>
<span class="nf">showCard</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="o">.</span><span class="n">compressed</span><span class="p">)</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="c1">//card moving up</span>
<span class="nf">showCard</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="o">.</span><span class="kd">open</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">cardTopAnchorConstraint</span><span class="o">!.</span><span class="n">constant</span> <span class="o"><</span> <span class="p">(</span><span class="n">safeAreaLayout</span><span class="p">)</span> <span class="p">{</span> <span class="k">if</span> <span class="n">velocityY</span> <span class="o">></span> <span class="mi">0</span> <span class="p">{</span>
<span class="c1">//Card moving down</span>
<span class="nf">showCard</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="o">.</span><span class="n">closed</span><span class="p">)</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="c1">//card moving upwards</span>
<span class="n">configuration</span><span class="o">.</span><span class="n">segmented</span> <span class="p">?</span> <span class="nf">showCard</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="o">.</span><span class="n">compressed</span><span class="p">)</span> <span class="p">:</span> <span class="nf">showCard</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="o">.</span><span class="kd">open</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="nf">dismissCard</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">default</span><span class="p">:</span>
<span class="k">break</span>
<span class="p">}</span></code></pre></figure>
<p>In this switch statement we are checking the state of the gesture, be that <code class="language-plaintext highlighter-rouge">.began</code>, .<code class="language-plaintext highlighter-rouge">changed</code> & <code class="language-plaintext highlighter-rouge">.ended</code>. In the began block, we are first storing the constraint value of the card to a property <code class="language-plaintext highlighter-rouge">called startingConstant</code>. In the changed block, we are using the translation property of the pan recognizer to detect how much the user has swiped on the card. This value is stored <code class="language-plaintext highlighter-rouge">in translationY</code> property. Then in the if block I am checking if the <code class="language-plaintext highlighter-rouge">translationY + startingConstant</code> is greater than 0. If it is then that means the card is moving upwards. So in order to respect this, we will add the <code class="language-plaintext highlighter-rouge">translationY</code> to the <code class="language-plaintext highlighter-rouge">startingConstant</code> and assign it as a constant to the card constraint. We are also setting the alpha value of the <code class="language-plaintext highlighter-rouge">blackView</code> with a special function but I will talk more on this later.</p>
<p>Similarly in the <code class="language-plaintext highlighter-rouge">.ended</code> case we will check the velocity of the swipe as it ended. So basically this means when the user stopped the gesture, was the card going upwards or downwards? We can get this from using recognizer .velocity property.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">topAnchorConstant</span> <span class="o">=</span> <span class="n">configuration</span><span class="o">.</span><span class="n">segmented</span> <span class="p">?</span> <span class="n">calculatedSegmentHeightsDictionary</span><span class="p">\[</span><span class="o">.</span><span class="n">compressed</span><span class="p">\]</span><span class="o">!</span> <span class="p">:</span> <span class="nf">configureCardSize</span><span class="p">(</span><span class="nv">parentView</span><span class="p">:</span> <span class="n">container</span><span class="p">,</span> <span class="nv">configuration</span><span class="p">:</span> <span class="n">configuration</span><span class="p">)</span></code></pre></figure>
<p>In this line, we are checking if the segmentation is enabled or not? If it is enabled then we grab the <code class="language-plaintext highlighter-rouge">.compressedheight</code> from the <code class="language-plaintext highlighter-rouge">calculatedSegmentHeightsDictionary</code> which we have created earlier, if the <code class="language-plaintext highlighter-rouge">configuration.segmented</code> is false then a new function called <code class="language-plaintext highlighter-rouge">configureCardSize is</code> called that grabs the card size which the user has put in the configuration file. Let’s talk about this <code class="language-plaintext highlighter-rouge">configurationCardSize</code> function:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">private</span> <span class="kd">func</span> <span class="nf">configureCardSize</span><span class="p">(</span><span class="nv">parentView</span><span class="p">:</span> <span class="kt">UIView</span><span class="p">,</span> <span class="nv">configuration</span><span class="p">:</span> <span class="kt">GlideConfiguration</span><span class="p">)</span> <span class="o">-></span> <span class="kt">CGFloat</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">configuration</span><span class="o">.</span><span class="n">concreteDimension</span><span class="o">.</span><span class="nf">translateView</span><span class="p">(</span><span class="nv">containerView</span><span class="p">:</span> <span class="n">parentView</span><span class="p">,</span> <span class="nv">navControllerPresent</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>This code accesses the configuration file’s <code class="language-plaintext highlighter-rouge">concreteDimension</code> enum. This enum consists of five cases and a <code class="language-plaintext highlighter-rouge">translateView</code> function.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">enum</span> <span class="kt">GlideConcreteDimension</span> <span class="p">{</span>
<span class="k">case</span> <span class="n">fullScreen</span>
<span class="k">case</span> <span class="n">half</span>
<span class="k">case</span> <span class="n">oneFourth</span>
<span class="k">case</span> <span class="n">oneThird</span>
<span class="k">case</span> <span class="nf">custom</span><span class="p">(</span><span class="kt">CGFloat</span><span class="p">)</span>
<span class="kd">func</span> <span class="nf">translateView</span><span class="p">(</span><span class="n">containerView</span> <span class="nv">parent</span><span class="p">:</span> <span class="kt">UIView</span><span class="p">,</span> <span class="nv">navControllerPresent</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="o">-></span> <span class="kt">CGFloat</span> <span class="p">{</span>
<span class="k">switch</span> <span class="k">self</span> <span class="p">{</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">fullScreen</span><span class="p">:</span>
<span class="k">let</span> <span class="nv">parentHeight</span> <span class="o">=</span> <span class="n">parent</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">height</span>
<span class="k">var</span> <span class="nv">constraintConstant</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">let</span> <span class="nv">navBar</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="n">navControllerPresent</span> <span class="p">?</span> <span class="mi">44</span> <span class="p">:</span> <span class="mi">0</span>
<span class="n">constraintConstant</span> <span class="o">=</span> <span class="n">navBar</span>
<span class="k">return</span> <span class="n">constraintConstant</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">half</span><span class="p">:</span>
<span class="nf">return</span> <span class="p">(</span><span class="n">parent</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">height</span> <span class="o">-</span> <span class="n">parent</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">height</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">oneFourth</span><span class="p">:</span>
<span class="nf">return</span> <span class="p">(</span><span class="n">parent</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">height</span> <span class="o">-</span> <span class="n">parent</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">height</span> <span class="o">/</span> <span class="mi">4</span><span class="p">)</span>
<span class="k">case</span> <span class="o">.</span><span class="nv">oneThird</span><span class="p">:</span>
<span class="nf">return</span> <span class="p">(</span><span class="n">parent</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">height</span> <span class="o">-</span> <span class="n">parent</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">height</span> <span class="o">/</span> <span class="mi">3</span><span class="p">)</span>
<span class="k">case</span> <span class="kd">let</span> <span class="o">.</span><span class="nf">custom</span><span class="p">(</span><span class="n">height</span><span class="p">):</span>
<span class="nf">return</span> <span class="p">(</span><span class="n">height</span> <span class="o">></span> <span class="n">parent</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">height</span><span class="p">)</span> <span class="p">?</span> <span class="n">parent</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="nv">height</span><span class="p">:</span> <span class="p">(</span><span class="n">parent</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">height</span> <span class="o">-</span> <span class="n">height</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>So in the configuration file the user has to give a concrete dimension if the segmentation is disabled. In the enum the user can select from four basic options and one custom option. So the user can provide a custom height value and the card will be opened till that position. In the <strong>translateView</strong> function the respective heights are found with respect to the parentViewController’s view. With this the navigation bar will not be fully covered if the developer selects the .<strong>fullScreen</strong> option as the height now solely depends on the parent View Controller’s view.</p>
<p><em><strong>If segmentation is enabled and concrete dimension is also given, the segmentation will take precedence.</strong></em></p>
<p>Heading back to the pan gesture recognizer method, inside the changed state, once we have the topConstraint constant for the card we need to decided whether we should now change the card constraint depending upon the current constraint value. If the current value of the card constraint is less than what we calculated above and the velocity of the swipe is also moving down then then that means the card is above the compressed state and should be opened to the compressed state.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">if</span> <span class="n">cardTopAnchorConstraint</span><span class="o">!.</span><span class="n">constant</span> <span class="o"><</span> <span class="n">topAnchorConstant</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">velocityY</span> <span class="o">></span> <span class="mi">0</span> <span class="p">{</span>
<span class="c1">//card moving down</span>
<span class="nf">showCard</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="o">.</span><span class="n">compressed</span><span class="p">)</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="c1">//card moving up</span>
<span class="nf">showCard</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="o">.</span><span class="kd">open</span><span class="p">)</span>
<span class="p">}</span></code></pre></figure>
<p>The showCard function gets the <code class="language-plaintext highlighter-rouge">.compressed</code> state and will animate the card. If the velocity of the gesture is upwards when it ended, then this means the card was swiped all the above the compressed state threshold and will since the velocity’s direction was upward, the card should continue to open and reach the .open state.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">else</span> <span class="k">if</span> <span class="n">cardTopAnchorConstraint</span><span class="o">!.</span><span class="n">constant</span> <span class="o"><</span> <span class="p">(</span><span class="n">safeAreaLayout</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">velocityY</span> <span class="o">></span> <span class="mi">0</span> <span class="p">{</span>
<span class="c1">//Card moving down</span>
<span class="nf">showCard</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="o">.</span><span class="n">closed</span><span class="p">)</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="c1">//card moving upwards</span>
<span class="n">configuration</span><span class="o">.</span><span class="n">segmented</span> <span class="p">?</span> <span class="nf">showCard</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="o">.</span><span class="n">compressed</span><span class="p">)</span> <span class="p">:</span> <span class="nf">showCard</span><span class="p">(</span><span class="nv">state</span><span class="p">:</span> <span class="o">.</span><span class="kd">open</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="nf">dismissCard</span><span class="p">()</span>
<span class="p">}</span></code></pre></figure>
<p>If the card’s constraint is below the compressed state threshold then again we check the velocity of the gesture and if it is moving down, then the card shown be in the .closed state or dismissed else the card should keep moving upwards but either go the compressed state or the open state depending on the configuration file and if the segmentation is enabled or not.</p>
<p><img src="/assets/glideUI.gif" alt="glideUI.gif" /></p>
<p>In part 2 we will take a look at the actual animation code and then will look at how to detect/handle a scrollView inside the card view controller, and how to animate the alpha value as well as the user swipes on the card.</p>
<p>The full project is available here on my <a href="https://github.com/Onaeem26/GlideUI">Github</a>.</p>Osama NaeemMost apps nowadays use this card based UI to pop up information or show an action menu etc. Twitter, Instagram, Facebook all are using some sort of a multi-state, interactive, gesture based card flow to show information to users. I have always been intrigued by such a design element and thought I should try building one from scratch.Solving Min Stack Problem using Swift2019-09-09T00:00:00+00:002019-09-09T00:00:00+00:00http://exploringswift.com/blog/solving-min-stack-problem-using-swift<p>It’s been a while since I last posted an article on an algorithm and data structure problem. This time I decided to solve the Min Stack problem. In Min Stack you need to find the minimum element inside a stack in constant time.
<!--more--></p>
<p>An alternate question is Max Stack where you have to find the maximum element in a stack. Both are pretty similar conceptually.</p>
<p>The problem explicitly states that each <strong>stack method should run at constant time - O(1)</strong>.</p>
<p>This is the <a href="https://leetcode.com/problems/min-stack/">problem</a> statement:</p>
<p><b>Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.</b></p>
<p><code class="language-plaintext highlighter-rouge">push(x)</code> - Push element x onto stack.</p>
<p><code class="language-plaintext highlighter-rouge">pop()</code> - Removes the element on top of the stack.</p>
<p><code class="language-plaintext highlighter-rouge">top()</code> - Get the top element.</p>
<p><code class="language-plaintext highlighter-rouge">getMin()</code> - Retrieve the minimum element in the stack.</p>
<p>Before continuing, make sure you try this problem yourself.</p>
<p>We will create a class calling it MinStack and inside it we will have two arrays.</p>
<p><code class="language-plaintext highlighter-rouge">private var elements: [Int] = []
private var minElements: [Int] = []</code></p>
<p>The elements array will be our main stack where the user appends or pushes new elements into. The <code class="language-plaintext highlighter-rouge">minElement</code> array is going to act as a secondary stack which will be used to keep track of the minimum element value at different stages.</p>
<p>Let’s code the first method - <code class="language-plaintext highlighter-rouge">push(x)</code>:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">push</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="p">{</span>
<span class="n">elements</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="k">if</span> <span class="n">minElements</span><span class="o">.</span><span class="n">count</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
<span class="n">minElements</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">minElement</span> <span class="o">=</span> <span class="n">minElements</span><span class="o">.</span><span class="n">last</span><span class="p">,</span> <span class="k">let</span> <span class="nv">lastElement</span> <span class="o">=</span> <span class="n">elements</span><span class="o">.</span><span class="n">last</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">lastElement</span> <span class="o"><=</span> <span class="n">minElement</span> <span class="p">{</span>
<span class="n">minElements</span><span class="o">.</span><span class="nf">append</span><span class="p">(</span><span class="n">lastElement</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>In this method, we are appending or pushing the element passed in by the function argument to the main ‘<code class="language-plaintext highlighter-rouge">elements</code>’ array. Then if the minElements array is empty we are simply appending the passed element into it. This is to “warm up” both stacks.</p>
<p>The next time the user pushes an element, we will simply append it in the elements array however, we will apply some bit of logic before appending it to the minElements stack.</p>
<p>We are using <code class="language-plaintext highlighter-rouge">last()</code> method which comes by default with <code class="language-plaintext highlighter-rouge">Array</code>. This method returns the last element in the array. The element type returned is optional (if array is empty, nil will be returned). Therefore, we are safely unwrapping last elements from both arrays using If - let statements.</p>
<p>Once we get the elements, we are checking if the top element in the element stack is less than or equal to the top element in the minElement stack. If it is, that element is appended or pushed to minElements stack as well.</p>
<p>Next, let’s implement <code class="language-plaintext highlighter-rouge">pop()</code> method:</p>
<p>In this method we will be removing the top element in the stack. We will use the .popLast() method. This method removes the last element in the array and also returns that element.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">pop</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">popElement</span> <span class="o">=</span> <span class="n">elements</span><span class="o">.</span><span class="nf">popLast</span><span class="p">()</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">poppedElement</span> <span class="o">=</span> <span class="n">popElement</span><span class="p">,</span> <span class="k">let</span> <span class="nv">minElementPop</span> <span class="o">=</span> <span class="n">minElements</span><span class="o">.</span><span class="n">last</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">poppedElement</span> <span class="o">==</span> <span class="n">minElementPop</span> <span class="p">{</span>
<span class="n">minElements</span><span class="o">.</span><span class="nf">popLast</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>We grab the returned last element from the main elements stack, and then again check the top element of <code class="language-plaintext highlighter-rouge">minElements</code> stack and check if the popped element and the top element of <code class="language-plaintext highlighter-rouge">minElements</code> stack is equal or not. If they are equal, then the top element is removed from minElements stack as well.</p>
<p>The remaining two methods are <code class="language-plaintext highlighter-rouge">top()</code> and <code class="language-plaintext highlighter-rouge">getMin()</code> - in both these methods we are simply popping the last element in the arrays and returning them.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"> <span class="kd">func</span> <span class="nf">top</span><span class="p">()</span> <span class="o">-></span> <span class="kt">Int</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">elements</span><span class="o">.</span><span class="n">last</span> <span class="p">??</span> <span class="mi">0</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">getMin</span><span class="p">()</span> <span class="o">-></span> <span class="kt">Int</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">minElements</span><span class="o">.</span><span class="n">last</span> <span class="p">??</span> <span class="mi">0</span>
<span class="p">}</span></code></pre></figure>
<p>The main thing here to notice is that the methods run at constant time. Nowhere inside any method did we iterated through the entire array - (this will be O(n)). With O(1) time complexity our algorithm works at constant time and not dependent to the size of the stack.</p>Osama NaeemIt’s been a while since I last posted an article on an algorithm and data structure problem. This time I decided to solve the Min Stack problem. In Min Stack you need to find the minimum element inside a stack in constant time.Using @EnvironmentObject to handle Log In / Log Out condition in SwiftUI2019-08-04T00:00:00+00:002019-08-04T00:00:00+00:00http://exploringswift.com/blog/using-environmentobject-to-handle-log-in-log-out-condition-in-swiftui<p>So I started using SwiftUI this last week and thought I would write about something I have been able to make over the brief time I have used it for. While SwiftUI makes designing new applications extremely easy; I couldn’t help but notice how much Apple has simplified UI components. Sometimes a bit too much.
<!--more-->
So I have been able to make a simple on boarding flow and a log in page. I have not integrated the app with Firebase as yet, but I just wanted to see how to go about creating a flow where when user taps on a button, he / she is taken to the main app (tab bar View).</p>
<p>SwiftUI framework has numerous property wrappers and understanding each one and knowing which to use and when to use should be your main target.</p>
<p>In this article, I will be using <code class="language-plaintext highlighter-rouge">@EnvironmentObject</code>. EnvironmentObject is combination of <code class="language-plaintext highlighter-rouge">@ObjectBinding</code> (another wrapper) and <code class="language-plaintext highlighter-rouge">Singleton</code>. In principle, it should be injected in the initial view of the view hierarchy. Something which is done in <code class="language-plaintext highlighter-rouge">SceneDelegate</code>. Once done, this property will be usable throughout any view that your initial view presents or hosts.</p>
<p><code class="language-plaintext highlighter-rouge">EnvironmentObject</code> can also be used to share data - but it’s very easy to over use this everywhere. Be very careful when and where you use this property wrapper.</p>
<p>After you have imported <code class="language-plaintext highlighter-rouge">Combine</code>, create a class that will conform to <code class="language-plaintext highlighter-rouge">ObservableObject</code> protocol and create a boolean property called <code class="language-plaintext highlighter-rouge">loggedIn</code>.</p>
<p><code class="language-plaintext highlighter-rouge">@Published var loggedIn : Bool = false</code></p>
<p><code class="language-plaintext highlighter-rouge">@Published</code> annotation means that this property stores both a value (false/true) in this case and a publisher which will send a message to any views that there has been a change to the value.</p>
<p>This is how Apple explains <code class="language-plaintext highlighter-rouge">@Published</code> as:</p>
<p><em>Properties annotated with `@Published` contain both the stored value and a publisher which sends any new values after the property value has been sent. New subscribers will receive the current value of the property first.</em></p>
<p>So this is our class now:</p>
<p><code class="language-plaintext highlighter-rouge">class UserSettings: ObservableObject {
@Published var loggedIn : Bool = false
}</code></p>
<p>Let’s create an instance of this class and inject this to our first view that we will load up. In our case it will be called <code class="language-plaintext highlighter-rouge">StartView()</code></p>
<p>So in <code class="language-plaintext highlighter-rouge">SceneDelegate.swift</code> file, look for this method:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">func</span> <span class="nf">scene</span><span class="p">(\</span><span class="n">_</span> <span class="nv">scene</span><span class="p">:</span> <span class="kt">UIScene</span><span class="p">,</span> <span class="n">willConnectTo</span> <span class="nv">session</span><span class="p">:</span> <span class="kt">UISceneSession</span><span class="p">,</span> <span class="n">options</span> <span class="nv">connectionOptions</span><span class="p">:</span> <span class="kt">UIScene</span><span class="o">.</span><span class="kt">ConnectionOptions</span><span class="p">)</span></code></pre></figure>
<p>and add this in:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">settings</span> <span class="o">=</span> <span class="kt">UserSettings</span><span class="p">()</span>
<span class="n">window</span><span class="o">.</span><span class="n">rootViewController</span> <span class="o">=</span> <span class="kt">UIHostingController</span><span class="p">(</span><span class="nv">rootView</span><span class="p">:</span> <span class="kt">StartView</span><span class="p">()</span><span class="o">.</span><span class="nf">environmentObject</span><span class="p">(</span><span class="n">settings</span><span class="p">))</span></code></pre></figure>
<p>The <code class="language-plaintext highlighter-rouge">StartView()</code> now contains a property called settings and it is annotated as <code class="language-plaintext highlighter-rouge">@EnvironmentObject:</code></p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">StartView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kd">@EnvironmentObject</span> <span class="k">var</span> <span class="nv">settings</span><span class="p">:</span> <span class="kt">UserSettings</span>
<span class="k">var</span> <span class="nv">body</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">View</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">settings</span><span class="o">.</span><span class="n">loggedIn</span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span><span class="kt">TabbarView</span><span class="p">())</span>
<span class="p">}</span><span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">AnyView</span><span class="p">(</span><span class="kt">ContentView</span><span class="p">())</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>In <code class="language-plaintext highlighter-rouge">StartView</code> we are checking the <code class="language-plaintext highlighter-rouge">loggedIn</code> property and if it is false will show <code class="language-plaintext highlighter-rouge">ContentView()</code> else it will show <code class="language-plaintext highlighter-rouge">TabbarView()</code>. Since <code class="language-plaintext highlighter-rouge">loggedIn</code> variable is <code class="language-plaintext highlighter-rouge">@published,</code> it will grab the new value and then send the new value to any view that subscribes (reads) to this property and handles the condition for it.</p>
<p>UserSettings is now shared and can be used anywhere in the app, in as many views as you want. Here we will add UserSettings type property in <code class="language-plaintext highlighter-rouge">LogInView()</code>, where we when the user taps on the log in button we will toggle the value of the <code class="language-plaintext highlighter-rouge">isLoggedIn</code> and this will run the if block above in the StartView.</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">struct</span> <span class="kt">LogInView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
<span class="kd">@EnvironmentObject</span> <span class="k">var</span> <span class="nv">settings</span><span class="p">:</span> <span class="kt">UserSettings</span>
<span class="kt">Button</span><span class="p">(</span><span class="nv">action</span><span class="p">:</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">settings</span><span class="o">.</span><span class="n">loggedIn</span> <span class="o">=</span> <span class="kc">true</span>
<span class="p">})</span> <span class="p">{</span>
<span class="kt">HStack</span> <span class="p">{</span>
<span class="kt">Text</span><span class="p">(</span><span class="s">"Log In"</span><span class="p">)</span>
<span class="p">}</span>
<span class="o">.</span><span class="nf">padding</span><span class="p">()</span>
<span class="o">.</span><span class="nf">frame</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="n">geometry</span><span class="o">.</span><span class="n">size</span><span class="o">.</span><span class="n">width</span> <span class="o">-</span> <span class="mi">40</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">40</span><span class="p">)</span>
<span class="o">.</span><span class="nf">foregroundColor</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="n">white</span><span class="p">)</span>
<span class="o">.</span><span class="nf">background</span><span class="p">(</span><span class="kt">Color</span><span class="o">.</span><span class="n">blue</span><span class="p">)</span>
<span class="o">.</span><span class="nf">cornerRadius</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<span class="p">}</span>
<span class="o">.</span><span class="nf">padding</span><span class="p">(</span><span class="o">.</span><span class="n">bottom</span><span class="p">,</span> <span class="mi">40</span><span class="p">)</span>
<span class="o">.../</span></code></pre></figure>
<p>Let’s revise this:</p>
<p>We injected an instance of UserSettings in StartView(). This means UserSettings instance is available in any view that StartView hosts or presents, or is a descendent of StartView(). Then by using the <code class="language-plaintext highlighter-rouge">@EnvironmentObject</code> property wrapper we are able to access that instance in any view.</p>
<p>We access the instance in <code class="language-plaintext highlighter-rouge">StartView()</code> and <code class="language-plaintext highlighter-rouge">LogInView()</code>. In LogInView, we write a value to <code class="language-plaintext highlighter-rouge">isLoggedIn</code> bool = true. On doing that, the property (being <code class="language-plaintext highlighter-rouge">@Published</code>) notifies any views to refresh itself - so in <code class="language-plaintext highlighter-rouge">StartView()</code> we are reading the value of this <code class="language-plaintext highlighter-rouge">isLoggedIn</code> property and showing the <code class="language-plaintext highlighter-rouge">TabbarView()</code>.</p>
<p>We can access this instance in <code class="language-plaintext highlighter-rouge">TabbarView()</code> as well and create a Log Out button. On pressing <code class="language-plaintext highlighter-rouge">Log Out</code>, we set the <code class="language-plaintext highlighter-rouge">isLoggedIn</code> bool to false. In doing that it again refreshes any view and goes to <code class="language-plaintext highlighter-rouge">ContentView()</code>.</p>
<p>Here is a quick diagram to sum it all up:</p>
<p><img src="/assets/Screen-Shot-2019-08-04-at-10.10.10-pm.png" alt="" /></p>
<p>
Here is the app in action:</p>
<p><img src="/assets/SwiftUILogInWorkflow.gif" alt="" /></p>Osama NaeemSo I started using SwiftUI this last week and thought I would write about something I have been able to make over the brief time I have used it for. While SwiftUI makes designing new applications extremely easy; I couldn’t help but notice how much Apple has simplified UI components. Sometimes a bit too much.Getting started with FaceID/TouchID and LocalAuthentication using Swift2019-07-31T00:00:00+00:002019-07-31T00:00:00+00:00http://exploringswift.com/blog/how-to-use-localauthentication-in-your-ios-app-using-swift<p>Since 2013, all iPhones have been launched with some sort of biometric sensors. From iPhone 5s to iPhone 8, Apple embedded a Touch ID sensor in the home button and from iPhone X onwards they started using Face ID to detect user’s face before unlocking the device.
<!--more-->
Apple provides APIs for developers to use these security features in their apps as well. Today I will talk about how to go about setting up biometric security features in your iOS app.</p>
<p>Before starting, make sure you open Info.plist file in Xcode and enter a new row for key <code class="language-plaintext highlighter-rouge">NSFaceIDUsageDescription</code> and enter a string value for this key. The value should talk about the reason you are using Face ID.</p>
<p>For Touch ID users, the reason is added within the code itself and you don’t need to do it in <code class="language-plaintext highlighter-rouge">Info.plist</code>.</p>
<p>Make sure you <code class="language-plaintext highlighter-rouge">import LocalAuthentication</code> framework.</p>
<p>So you first do this:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">import</span> <span class="kt">LocalAuthentication</span>
<span class="k">let</span> <span class="nv">context</span> <span class="o">=</span> <span class="kt">LAContext</span><span class="p">()</span>
<span class="k">var</span> <span class="nv">error</span><span class="p">:</span> <span class="kt">NSError</span><span class="p">?</span>
<span class="k">if</span> <span class="n">context</span><span class="o">.</span><span class="nf">canEvaluatePolicy</span><span class="p">(</span><span class="o">.</span><span class="n">deviceOwnerAuthenticationWithBiometrics</span><span class="p">,</span> <span class="nv">error</span><span class="p">:</span> <span class="o">&</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">reason</span> <span class="o">=</span> <span class="s">"Touch ID identification needed"</span> <span class="c1">// For touch ID only</span>
<span class="n">context</span><span class="o">.</span><span class="nf">evaluatePolicy</span><span class="p">(</span><span class="o">.</span><span class="n">deviceOwnerAuthenticationWithBiometrics</span><span class="p">,</span> <span class="nv">localizedReason</span><span class="p">:</span> <span class="n">reason</span><span class="p">)</span> <span class="p">{</span>
<span class="p">\[</span><span class="k">weak</span> <span class="k">self</span><span class="p">\]</span> <span class="n">success</span><span class="p">,</span> <span class="n">authenticationError</span> <span class="k">in</span>
<span class="kt">DispatchQueue</span><span class="o">.</span><span class="n">main</span><span class="o">.</span><span class="n">async</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">success</span> <span class="p">{</span>
<span class="c1">// success</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// error</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// no biometry on your device</span>
<span class="p">}</span></code></pre></figure>
<p>So we are first creating an instance of <code class="language-plaintext highlighter-rouge">LAContext()</code> and checking if the user’s device even has biometry (Touch ID or Face ID).</p>
<p>If yes, then the system checks if the Face ID or Touch ID is correctly identified and verified. If this is the case we get in to the closure with two parameters - <code class="language-plaintext highlighter-rouge">success</code> and <code class="language-plaintext highlighter-rouge">authenticationError</code>. We can use success bool value to then execute any code that we want to get executed if the Face ID / Touch ID has worked correctly. If the Touch ID or Face ID thinks the input is wrong then we can do any error handling inside else block.</p>
<p>Make sure that what ever UI based code you execute, should be done on main thread. Therefore, we are using <code class="language-plaintext highlighter-rouge">DspatchQueue.main.async{}</code> when executing the if - else statement. That’s about it! In order to test Face ID /Touch ID in Xcode Simulator - go to <code class="language-plaintext highlighter-rouge">Hardware -> TouchID/FaceID -> Enrolled State</code>. Then when your app asks you for identification, head back to <code class="language-plaintext highlighter-rouge">Hardware -> Touch ID/FaceID -> Matching or non Matching</code> to test our the above code.</p>Osama NaeemSince 2013, all iPhones have been launched with some sort of biometric sensors. From iPhone 5s to iPhone 8, Apple embedded a Touch ID sensor in the home button and from iPhone X onwards they started using Face ID to detect user’s face before unlocking the device.Jony Ive Leaving Apple2019-06-28T00:00:00+00:002019-06-28T00:00:00+00:00http://exploringswift.com/blog/jony-ive-leaving-apple<p>I am far from a designer. I can barely draw let alone design something. But at the same time I always appreciate a beautifully designed product. That can be a laptop, or an app. A car or some building. Design has always been something that gets to us very naturally from consumer point of you - but when you look at it from creation side of things, then you truly need something special. That special thing has multiple roots in philosophy, culture and science. When you look at design from a perspective of a creator, you wonder how that design got around to its physical existence.
<!--more-->
Jony Ive had studied all those roots perfectly and combined them to create the products we use and love. The MacBooks, iMacs, iPhones, software, typefaces and even architecture such as Apple Park. He was probably the best find of Steve Jobs. While one may say a thing or two about his obsession with everything being minimalistic and thin, but that’s what he openly promoted in his product design.</p>
<p><img src="/assets/jonathan_ive_660_062819092207.jpg" alt="" /></p>
<p><br />
Apart from his notable hardware works, he also gave Apple a typeface called “San Francisco”. This type face is used everywhere. From macOS, to iOS. From product boxes to billboards. One single type face for both iPhone screens and 10ft wide billboard surfaces. Wow.</p>
<p>The full white room and Jony’s British accent - explaining the product design will be missed. His philosophical reasoning mixed with scientific explanation for a certain design or choice of material was a great insight to what went into a creation of an Apple product. Jony Ive leaving Apple certainly marks the end of an era. 30 years of design and setting a standard.</p>
<p>Yes, his acute obsession with thinness did result in Apple completely changing the keyboard mechanism and it’s something that’s still a bit of a headache for customers. 😂</p>
<p>Jony will be starting a new company called “LoveFrom” with Marc Newson. It’s going to be a design company and supposedly Apple will be its primary client.</p>
<p>Let’s see what happens next. Where the design pattern go from here. Having used Apple products and also seen Apple Park and Steve Jobs Theatre, you gotta say - He was one hell of a designer!</p>Osama NaeemI am far from a designer. I can barely draw let alone design something. But at the same time I always appreciate a beautifully designed product. That can be a laptop, or an app. A car or some building. Design has always been something that gets to us very naturally from consumer point of you - but when you look at it from creation side of things, then you truly need something special. That special thing has multiple roots in philosophy, culture and science. When you look at design from a perspective of a creator, you wonder how that design got around to its physical existence.WWDC Guide - Traveling, lodging, Scholarship orientation & lots more! (Part 2)2019-04-20T00:00:00+00:002019-04-20T00:00:00+00:00http://exploringswift.com/blog/wwdc-guide-traveling-lodging-scholarship-orientation-lots-more<p>WWDC2019 scholarship results were announced earlier this week and I would like to congratulate everyone who got selected 🎉. For those who were unable to get the scholarship, don’t stress out and continue working, grinding and learning. Make sure to try next year.</p>
<p>This is going to be part 2 of WWDC series where I will talk about the traveling, lodging, other events that are going on along side WWDC in San Jose, and more.</p>
<p>As you may tell from my <a href="http://instagram.com/madebyon">Instagram</a>, I love traveling. So let’s talk about it! ✈️
<!--more--></p>
<p><img src="/assets/wwdc-2018-scholars-1024x611.jpg" alt="" /></p>
<h3 id="san-jose-california-"><strong>San Jose, California 🇺🇸</strong></h3>
<p><img src="/assets/IMG_0414.jpg" alt="" /></p>
<p>San Jose, a major tech hub in California’s bay area is where Apple is holding its World Wide Developer Conference since 2017. Getting there is a bit of a challenge if you are traveling from Asia.</p>
<p>My travel itinerary was this:</p>
<p><strong>✈️ Isb -> Abu Dhabi -> Los Angeles -> San Jose ✈️</strong></p>
<p>I took Etihad Airways, which took me from Islamabad to Abu Dhabi. This was a 3 hour flight. Then a 3 hour stay in Abu Dhabi after which I had to take a connecting flight to Los Angeles. This flight is long…. like really long. Around <code class="language-plaintext highlighter-rouge">16.5</code> hrs long. From there I had around 2 hour stay after which I had to take another flight to San Jose. This was a short 50 minute flight.</p>
<p>Around 20 hours of flying, 2 connections, and good bit of jet lag later, I reached San Jose on Saturday at around 7 pm.</p>
<p>Another option could be to fly to San Francisco, and then take Uber/Lyft/Caltrain to San Jose.</p>
<h3 id="lodging"><strong>Lodging:</strong></h3>
<p>Apple provides free lodging to the scholarship students in San Jose State University dorm rooms. The university is situated at a walking distance from the McEnery convention center.</p>
<h3 id="scholarship-orientation-ceremony"><strong>Scholarship Orientation Ceremony:</strong></h3>
<h3><img src="/assets/IMG_0477.jpg" alt="" /></h3>
<p>Last year, Apple held the orientation ceremony at Steve Jobs Theatre for the very first time. The venue was kept a secret till the very end. I remember while we were walking towards the convention center on Sunday to get our badges etc we all were talking about the “secret location”. We all were hoping it to be Steve Jobs Theatre and as it turned out; it was.</p>
<p>We got on the buses and went to Steve Jobs Theatre; It was an experience of a lifetime. I am pretty sure that this year Apple will probably do the same and hold the scholarship orientation at Steve Jobs Theatre as well. It’s an absolutely glorious piece of architecture. Simply breathtaking.</p>
<h3 id="other-events">Other Events:</h3>
<p>Make sure to download <a href="https://itunes.apple.com/us/app/parties-for-wwdc/id879924066?mt=8">Parties</a> for WWDC app. This app lists all the events that will take place around San Jose during WWDC week. It even lists the timing, dates, and ticket information. These events start usually at around evening, around 5-6pm and I highly recommend to go to these events for networking and lots of fun.</p>
<p><img src="/assets/IMG_0595.jpg" alt="" /></p>
<p>Make sure you guys go to <strong>The Talk Show. </strong>A show hosted by John Gruber. It takes place in California Theatre on Tuesday at around 7pm. The tickets are made available around a week prior to the WWDC week. Tickets go out in a flash so you gotta be really quick to grab one.</p>
<p><a href="https://www.youtube.com/watch?v=L392cVz28xw">Last year,</a> Gruber was joined by Greg Joswiak and Mike Rockwell. They talked about all the WWDC announcements and it was absolutely incredible.</p>
<p>Highly recommended! 💯</p>
<h3 id="tips"><strong>Tips:</strong></h3>
<ol>
<li>Make sure to network and socialize as much as you can.</li>
<li>👨🏻💻 Start a project and take it with you to WWDC. Ask Apple engineers and other developers about any issues/features etc</li>
<li>👩🏼💻Make sure you attend the labs and ask Apple engineers about any development issues you are having. Labs are exceptionally useful. Avail them as much as you can!</li>
<li>Take it slow. There is no need to attend all the sessions. You can watch them later. Sit down in the scholarship lounge, chat with other scholars, ask about their playgrounds and just enjoy the moment.</li>
<li>🥤Did I mention Odwalla?</li>
</ol>Osama NaeemWWDC2019 scholarship results were announced earlier this week and I would like to congratulate everyone who got selected 🎉. For those who were unable to get the scholarship, don’t stress out and continue working, grinding and learning. Make sure to try next year. This is going to be part 2 of WWDC series where I will talk about the traveling, lodging, other events that are going on along side WWDC in San Jose, and more. As you may tell from my Instagram, I love traveling. So let’s talk about it! ✈️