<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://johankool.nl/feed.xml" rel="self" type="application/atom+xml" /><link href="https://johankool.nl/" rel="alternate" type="text/html" /><updated>2026-06-24T08:19:21+00:00</updated><id>https://johankool.nl/feed.xml</id><title type="html">Johan Kool</title><subtitle>I write apps for iOS and macOS. I am based in Rotterdam, the Netherlands.</subtitle><entry><title type="html">Adding Wedge and Touch to Angle Input Control in SwiftUI</title><link href="https://johankool.nl/2022/03/20/angle.html" rel="alternate" type="text/html" title="Adding Wedge and Touch to Angle Input Control in SwiftUI" /><published>2022-03-20T15:00:00+00:00</published><updated>2022-03-20T15:00:00+00:00</updated><id>https://johankool.nl/2022/03/20/angle</id><content type="html" xml:base="https://johankool.nl/2022/03/20/angle.html"><![CDATA[<h1 id="aim">Aim</h1>

<p><a href="/2022/03/13/angle.html">Last time</a> I showed how I had written a control for inputting an angle. This time I am improving the look of the control as well as adding touch input.</p>

<p><img src="/assets/posts/angle/angle-move.gif" alt="Movie showing angle input control being edited" /></p>

<h1 id="from-cgfloat-to-angle">From CGFloat to Angle</h1>

<p>But first I want to make an improvement to the code. Now the angle is stored as a <code class="language-plaintext highlighter-rouge">CGFloat</code> value with convenience functions to convert between radians and degrees. However, this means that it is up to me to remember in which unit the value is at any given moment. Turns out SwiftUI has a struct that solves this: <code class="language-plaintext highlighter-rouge">Angle</code>. You instantiate it with <code class="language-plaintext highlighter-rouge">Angle.degrees(90)</code> or <code class="language-plaintext highlighter-rouge">Angle.radians(.pi)</code> and you can ask for the angle in either unit with <code class="language-plaintext highlighter-rouge">.degrees</code> and <code class="language-plaintext highlighter-rouge">.radians</code>.</p>

<h1 id="making-angle-animatable">Making Angle animatable</h1>

<p>Now however, things get a bit weird. Sadly the animation has stopped working. Tapping on the stepper makes the line jump instead of animating. Turns out that while <code class="language-plaintext highlighter-rouge">Angle</code> conforms to the <code class="language-plaintext highlighter-rouge">Animatable</code> protocol itself, it doesn’t conform to the <code class="language-plaintext highlighter-rouge">VectorArithmetic</code> protocol. This is needed for the <code class="language-plaintext highlighter-rouge">Shape</code> to be animatable. Conforming to <code class="language-plaintext highlighter-rouge">VectorArithmetic</code> also means conforming to <code class="language-plaintext highlighter-rouge">AdditiveArithmetic</code>.</p>

<p>Luckily it is simple enough to implement.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">Angle</span><span class="p">:</span> <span class="kt">VectorArithmetic</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="k">mutating</span> <span class="kd">func</span> <span class="nf">scale</span><span class="p">(</span><span class="n">by</span> <span class="nv">rhs</span><span class="p">:</span> <span class="kt">Double</span><span class="p">)</span> <span class="p">{</span>
        <span class="k">self</span> <span class="o">=</span> <span class="o">.</span><span class="nf">radians</span><span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">radians</span> <span class="o">*</span> <span class="n">rhs</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">public</span> <span class="k">var</span> <span class="nv">magnitudeSquared</span><span class="p">:</span> <span class="kt">Double</span> <span class="p">{</span>
        <span class="k">self</span><span class="o">.</span><span class="n">radians</span> <span class="o">*</span> <span class="k">self</span><span class="o">.</span><span class="n">radians</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="kd">extension</span> <span class="kt">Angle</span><span class="p">:</span> <span class="kt">AdditiveArithmetic</span> <span class="p">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="k">var</span> <span class="nv">zero</span><span class="p">:</span> <span class="k">Self</span> <span class="p">{</span> <span class="o">.</span><span class="nf">radians</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">}</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">func</span> <span class="o">+</span> <span class="p">(</span><span class="nv">lhs</span><span class="p">:</span> <span class="k">Self</span><span class="p">,</span> <span class="nv">rhs</span><span class="p">:</span> <span class="k">Self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="o">.</span><span class="nf">radians</span><span class="p">(</span><span class="n">lhs</span><span class="o">.</span><span class="n">radians</span> <span class="o">+</span> <span class="n">rhs</span><span class="o">.</span><span class="n">radians</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">func</span> <span class="o">+=</span> <span class="p">(</span><span class="nv">lhs</span><span class="p">:</span> <span class="k">inout</span> <span class="k">Self</span><span class="p">,</span> <span class="nv">rhs</span><span class="p">:</span> <span class="k">Self</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">lhs</span> <span class="o">=</span> <span class="o">.</span><span class="nf">radians</span><span class="p">(</span><span class="n">lhs</span><span class="o">.</span><span class="n">radians</span> <span class="o">+</span> <span class="n">rhs</span><span class="o">.</span><span class="n">radians</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">func</span> <span class="o">-</span> <span class="p">(</span><span class="nv">lhs</span><span class="p">:</span> <span class="k">Self</span><span class="p">,</span> <span class="nv">rhs</span><span class="p">:</span> <span class="k">Self</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">Self</span> <span class="p">{</span>
        <span class="o">.</span><span class="nf">radians</span><span class="p">(</span><span class="n">lhs</span><span class="o">.</span><span class="n">radians</span> <span class="o">-</span> <span class="n">rhs</span><span class="o">.</span><span class="n">radians</span><span class="p">)</span>
    <span class="p">}</span>

    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">func</span> <span class="o">-=</span> <span class="p">(</span><span class="nv">lhs</span><span class="p">:</span> <span class="k">inout</span> <span class="k">Self</span><span class="p">,</span> <span class="nv">rhs</span><span class="p">:</span> <span class="k">Self</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">lhs</span> <span class="o">=</span> <span class="o">.</span><span class="nf">radians</span><span class="p">(</span><span class="n">lhs</span><span class="o">.</span><span class="n">radians</span> <span class="o">-</span> <span class="n">rhs</span><span class="o">.</span><span class="n">radians</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And just by adding those two conformances the input animates again!</p>

<h1 id="drawing-wedge-shape">Drawing wedge shape</h1>

<p>Next I want draw a wedge shape to fill the space between the two lines. To be able to draw this wedge in a different color I need to create a separate shape. Here having the angle in the <code class="language-plaintext highlighter-rouge">Angle</code> struct is convenient as that can be used in the call to draw a part of an arc.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">WedgeShape</span><span class="p">:</span> <span class="kt">Shape</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">angle</span><span class="p">:</span> <span class="kt">Angle</span>
  
    <span class="k">var</span> <span class="nv">animatableData</span><span class="p">:</span> <span class="kt">Angle</span> <span class="p">{</span>
        <span class="k">get</span> <span class="p">{</span> <span class="n">angle</span> <span class="p">}</span>
        <span class="k">set</span> <span class="p">{</span> <span class="n">angle</span> <span class="o">=</span> <span class="n">newValue</span> <span class="p">}</span>
    <span class="p">}</span>
    
    <span class="k">var</span> <span class="nv">insets</span><span class="p">:</span> <span class="kt">UIEdgeInsets</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">top</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span> <span class="nv">left</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span> <span class="nv">bottom</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span> <span class="nv">right</span><span class="p">:</span> <span class="mi">100</span><span class="p">)</span>
  
    <span class="kd">func</span> <span class="nf">path</span><span class="p">(</span><span class="k">in</span> <span class="nv">rect</span><span class="p">:</span> <span class="kt">CGRect</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Path</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">insetRect</span> <span class="o">=</span> <span class="n">rect</span><span class="o">.</span><span class="nf">inset</span><span class="p">(</span><span class="nv">by</span><span class="p">:</span> <span class="n">insets</span><span class="p">)</span>
        <span class="nf">assert</span><span class="p">(</span><span class="n">insetRect</span><span class="o">.</span><span class="n">width</span> <span class="o">==</span> <span class="n">insetRect</span><span class="o">.</span><span class="n">height</span><span class="p">,</span> <span class="s">"Rect must be square"</span><span class="p">)</span>
      
        <span class="k">var</span> <span class="nv">path</span> <span class="o">=</span> <span class="kt">Path</span><span class="p">()</span>
      
        <span class="k">let</span> <span class="nv">center</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">insetRect</span><span class="o">.</span><span class="n">maxX</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="n">insetRect</span><span class="o">.</span><span class="n">maxY</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">radius</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="n">insetRect</span><span class="o">.</span><span class="n">maxX</span> <span class="o">-</span> <span class="n">insetRect</span><span class="o">.</span><span class="n">minX</span>
    
        <span class="k">let</span> <span class="nv">startAngle</span> <span class="o">=</span> <span class="kt">Angle</span><span class="o">.</span><span class="nf">degrees</span><span class="p">(</span><span class="o">-</span><span class="mi">180</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">endAngle</span> <span class="o">=</span> <span class="n">startAngle</span> <span class="o">+</span> <span class="n">angle</span>
        
        <span class="n">path</span><span class="o">.</span><span class="nf">addArc</span><span class="p">(</span><span class="nv">center</span><span class="p">:</span> <span class="n">center</span><span class="p">,</span> <span class="nv">radius</span><span class="p">:</span> <span class="n">radius</span> <span class="o">-</span> <span class="mi">20</span><span class="p">,</span> <span class="nv">startAngle</span><span class="p">:</span> <span class="n">startAngle</span><span class="p">,</span> <span class="nv">endAngle</span><span class="p">:</span> <span class="n">endAngle</span><span class="p">,</span> <span class="nv">clockwise</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span>
        <span class="n">path</span><span class="o">.</span><span class="nf">addArc</span><span class="p">(</span><span class="nv">center</span><span class="p">:</span> <span class="n">center</span><span class="p">,</span> <span class="nv">radius</span><span class="p">:</span> <span class="n">radius</span> <span class="o">-</span> <span class="mi">40</span><span class="p">,</span> <span class="nv">startAngle</span><span class="p">:</span> <span class="n">endAngle</span><span class="p">,</span> <span class="nv">endAngle</span><span class="p">:</span> <span class="n">startAngle</span><span class="p">,</span> <span class="nv">clockwise</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
        <span class="n">path</span><span class="o">.</span><span class="nf">closeSubpath</span><span class="p">()</span>
        
        <span class="k">return</span> <span class="n">path</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now I have two shapes: <code class="language-plaintext highlighter-rouge">WedgeShape</code> and <code class="language-plaintext highlighter-rouge">AngleShape</code> which can each have the desired fill or stroke colors and be stacked on top of each other using a <code class="language-plaintext highlighter-rouge">ZStack</code>.</p>

<h1 id="adding-touch-input">Adding Touch Input</h1>

<p>Having an input control that doesn’t take touch isn’t much of a control. Next up is adding the ability to use your finger to drag the angle.</p>

<p>A <code class="language-plaintext highlighter-rouge">DragGesture</code> can facilitate this. When the drag gesture recognizes a change I set the variable <code class="language-plaintext highlighter-rouge">isEditing</code> to true so that the view can show the user it is being manipulated. And more importantly, the new angle is calculated from the point of rotation to where the user touches the control. Here I am using <code class="language-plaintext highlighter-rouge">atan2</code> which takes care of the division so you don’t have to worry about dividing by zero.</p>

<p>When the drag ends, the variable <code class="language-plaintext highlighter-rouge">isEditing</code> should be false again.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">@State</span> <span class="k">var</span> <span class="nv">isEditing</span><span class="p">:</span> <span class="kt">Bool</span> <span class="o">=</span> <span class="kc">false</span>

 <span class="k">var</span> <span class="nv">dragGesture</span><span class="p">:</span> <span class="kd">some</span> <span class="kt">Gesture</span> <span class="p">{</span>
    <span class="kt">DragGesture</span><span class="p">()</span>
        <span class="o">.</span><span class="n">onChanged</span> <span class="p">{</span> <span class="n">value</span> <span class="k">in</span>
            <span class="n">isEditing</span> <span class="o">=</span> <span class="kc">true</span>
            <span class="k">let</span> <span class="nv">corner</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="mi">300</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="k">let</span> <span class="nv">deltaX</span> <span class="o">=</span> <span class="n">corner</span><span class="o">.</span><span class="n">x</span> <span class="o">-</span> <span class="n">value</span><span class="o">.</span><span class="n">location</span><span class="o">.</span><span class="n">x</span>
            <span class="k">let</span> <span class="nv">deltaY</span> <span class="o">=</span> <span class="n">corner</span><span class="o">.</span><span class="n">y</span> <span class="o">-</span> <span class="n">value</span><span class="o">.</span><span class="n">location</span><span class="o">.</span><span class="n">y</span>
            <span class="k">let</span> <span class="nv">dragAngle</span> <span class="o">=</span> <span class="nf">atan2</span><span class="p">(</span><span class="n">deltaY</span><span class="p">,</span> <span class="n">deltaX</span><span class="p">)</span>
            
            <span class="n">angle</span> <span class="o">=</span> <span class="o">.</span><span class="nf">radians</span><span class="p">(</span><span class="n">dragAngle</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="o">.</span><span class="n">onEnded</span> <span class="p">{</span> <span class="n">value</span> <span class="k">in</span>
            <span class="n">isEditing</span> <span class="o">=</span> <span class="kc">false</span>
        <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now I am ready to add the gesture to the <code class="language-plaintext highlighter-rouge">AngleView</code> using the view modifier <code class="language-plaintext highlighter-rouge">.gesture(dragGesture)</code>.</p>

<p>Now, while this works, it doesn’t feel very responsive. The UI feels laggy. This is because the change in angle is animated, so when changing the angle from the drag gesture animations should be disabled. To achieve this I wrapped the line where the angle is set in a <code class="language-plaintext highlighter-rouge">Transaction</code> which has animations disabled.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">var</span> <span class="nv">transaction</span> <span class="o">=</span> <span class="kt">Transaction</span><span class="p">(</span><span class="nv">animation</span><span class="p">:</span> <span class="o">.</span><span class="k">none</span><span class="p">)</span>
<span class="n">transaction</span><span class="o">.</span><span class="n">disablesAnimations</span> <span class="o">=</span> <span class="kc">true</span>

<span class="nf">withTransaction</span><span class="p">(</span><span class="n">transaction</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">angle</span> <span class="o">=</span> <span class="o">.</span><span class="nf">radians</span><span class="p">(</span><span class="n">dragAngle</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="editing-mode">Editing mode</h1>

<p>To let the user know the control is being edited I pass the value of the <code class="language-plaintext highlighter-rouge">isEditing</code> var to the <code class="language-plaintext highlighter-rouge">AngleShape</code> and draw a circle at the end of the path.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">isEditing</span> <span class="p">{</span>
    <span class="k">let</span> <span class="nv">editCircleRect</span> <span class="o">=</span> <span class="kt">CGRect</span><span class="p">(</span><span class="nv">origin</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">x</span> <span class="o">-</span> <span class="mi">10</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="n">y</span> <span class="o">-</span> <span class="mi">10</span><span class="p">),</span> <span class="nv">size</span><span class="p">:</span> <span class="kt">CGSize</span><span class="p">(</span><span class="nv">width</span><span class="p">:</span> <span class="mi">20</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">20</span><span class="p">))</span>
    <span class="n">path</span><span class="o">.</span><span class="nf">addEllipse</span><span class="p">(</span><span class="nv">in</span><span class="p">:</span> <span class="n">editCircleRect</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="code">Code</h1>

<p>You can find all the code created in this post <a href="https://github.com/johankool/blog-angle/tree/blog-post-2">on GitHub</a>.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Aim]]></summary></entry><entry><title type="html">Angle Input Control with SwiftUI</title><link href="https://johankool.nl/2022/03/13/angle.html" rel="alternate" type="text/html" title="Angle Input Control with SwiftUI" /><published>2022-03-13T15:00:00+00:00</published><updated>2022-03-13T15:00:00+00:00</updated><id>https://johankool.nl/2022/03/13/angle</id><content type="html" xml:base="https://johankool.nl/2022/03/13/angle.html"><![CDATA[<h1 id="aim">Aim</h1>

<p>The idea for this first step is quite simple: draw a shape consisting of two lines on screen representing an angle between 0 and 90 degrees.</p>

<p><img src="/assets/posts/angle/angle-light.png" alt="Screenshot showing angle input control" /></p>

<h1 id="setup">Setup</h1>

<p>Above the shape I’ll add a label called “Angle” and use the <code class="language-plaintext highlighter-rouge">arrow.clockwise</code> symbol from SF Symbols. Below the shape a <code class="language-plaintext highlighter-rouge">Stepper</code> control displays the current angle. For now it suffices to store the angle in an <code class="language-plaintext highlighter-rouge">@State</code> var. Later on this can be exposed as an <code class="language-plaintext highlighter-rouge">@Binding</code>.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">AngleView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    
    <span class="kd">@State</span> <span class="k">var</span> <span class="nv">angle</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">45</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">Label</span><span class="p">(</span><span class="s">"Angle"</span><span class="p">,</span> <span class="nv">systemImage</span><span class="p">:</span> <span class="s">"arrow.clockwise"</span><span class="p">)</span>
            <span class="kt">AngleShape</span><span class="p">(</span><span class="nv">angle</span><span class="p">:</span> <span class="n">angle</span><span class="p">)</span>
            <span class="kt">Stepper</span><span class="p">(</span><span class="s">"</span><span class="se">\(</span><span class="kt">Int</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="n">angle</span><span class="p">))</span><span class="se">)</span><span class="s">°"</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="err">$</span><span class="n">angle</span><span class="p">,</span> <span class="nv">step</span><span class="p">:</span> <span class="mi">5</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="custom-shape">Custom Shape</h1>

<p>When the angle is changed through the stepper, I want to animate the change. To let SwiftUI know how to change the shape between two angles a new struct conforming to the <code class="language-plaintext highlighter-rouge">Shape</code> protocol is needed. This is done in the method <code class="language-plaintext highlighter-rouge">func path(in rect: CGRect) -&gt; Path</code>. The <code class="language-plaintext highlighter-rouge">Shape</code> protocol in turn conforms to the <code class="language-plaintext highlighter-rouge">Animatable</code> protocol, which adds a <code class="language-plaintext highlighter-rouge">var animatableData</code> to the struct.</p>

<p>So I create a new struct called AngleShape. For this shape just the angle is enough to be able create a path for all values between two angles, so the <code class="language-plaintext highlighter-rouge">animatableData</code> is a <code class="language-plaintext highlighter-rouge">CGFloat</code>.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">AngleShape</span><span class="p">:</span> <span class="kt">Shape</span> <span class="p">{</span>
    <span class="k">var</span> <span class="nv">angle</span><span class="p">:</span> <span class="kt">CGFloat</span>
    
    <span class="k">var</span> <span class="nv">animatableData</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="p">{</span>
        <span class="k">get</span> <span class="p">{</span> <span class="n">angle</span> <span class="p">}</span>
        <span class="k">set</span> <span class="p">{</span> <span class="n">angle</span> <span class="o">=</span> <span class="n">newValue</span> <span class="p">}</span>
    <span class="p">}</span>
       
    <span class="kd">func</span> <span class="nf">path</span><span class="p">(</span><span class="k">in</span> <span class="nv">rect</span><span class="p">:</span> <span class="kt">CGRect</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Path</span> <span class="p">{</span>
        <span class="c1">// …</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="trigonometry">Trigonometry</h1>

<p>It looks better if there is some additional space around the angle, so I am adding edge insets. The calculation of the first two points is then simple enough after insetting the passed in rect. The third point can be calculated with some trigonometry. Here paper and pencil are still essential tools for developers! Just be sure that the used rect is square so the hypotenuse is always the same lenght.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="k">var</span> <span class="nv">insets</span><span class="p">:</span> <span class="kt">UIEdgeInsets</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">top</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span> <span class="nv">left</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span> <span class="nv">bottom</span><span class="p">:</span> <span class="mi">100</span><span class="p">,</span> <span class="nv">right</span><span class="p">:</span> <span class="mi">100</span><span class="p">)</span>

    <span class="kd">func</span> <span class="nf">path</span><span class="p">(</span><span class="k">in</span> <span class="nv">rect</span><span class="p">:</span> <span class="kt">CGRect</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kt">Path</span> <span class="p">{</span>
        <span class="k">let</span> <span class="nv">insetRect</span> <span class="o">=</span> <span class="n">rect</span><span class="o">.</span><span class="nf">inset</span><span class="p">(</span><span class="nv">by</span><span class="p">:</span> <span class="n">insets</span><span class="p">)</span>
        <span class="nf">assert</span><span class="p">(</span><span class="n">insetRect</span><span class="o">.</span><span class="n">width</span> <span class="o">==</span> <span class="n">insetRect</span><span class="o">.</span><span class="n">height</span><span class="p">,</span> <span class="s">"Rect must be square"</span><span class="p">)</span>
        
        <span class="k">var</span> <span class="nv">path</span> <span class="o">=</span> <span class="kt">Path</span><span class="p">()</span>
        <span class="n">path</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="kt">CGPoint</span><span class="p">(</span><span class="nv">x</span><span class="p">:</span> <span class="n">insetRect</span><span class="o">.</span><span class="n">minX</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="n">insetRect</span><span class="o">.</span><span class="n">maxY</span><span class="p">))</span>
        <span class="n">path</span><span class="o">.</span><span class="nf">addLine</span><span class="p">(</span><span class="nv">to</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">insetRect</span><span class="o">.</span><span class="n">maxX</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="n">insetRect</span><span class="o">.</span><span class="n">maxY</span><span class="p">))</span>
        
        <span class="k">let</span> <span class="nv">angleInRadians</span> <span class="o">=</span> <span class="n">angle</span><span class="o">.</span><span class="nf">degreesToRadians</span><span class="p">()</span>
        <span class="k">let</span> <span class="nv">hypotenuse</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="n">insetRect</span><span class="o">.</span><span class="n">maxX</span> <span class="o">-</span> <span class="n">insetRect</span><span class="o">.</span><span class="n">minX</span>
        <span class="k">let</span> <span class="nv">adjacent</span> <span class="o">=</span> <span class="n">hypotenuse</span> <span class="o">*</span> <span class="nf">cos</span><span class="p">(</span><span class="n">angleInRadians</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">opposite</span> <span class="o">=</span> <span class="n">hypotenuse</span> <span class="o">*</span> <span class="nf">sin</span><span class="p">(</span><span class="n">angleInRadians</span><span class="p">)</span>
        <span class="k">let</span> <span class="nv">x</span> <span class="o">=</span> <span class="n">insetRect</span><span class="o">.</span><span class="n">minX</span> <span class="o">+</span> <span class="n">hypotenuse</span> <span class="o">-</span> <span class="n">adjacent</span>
        <span class="k">let</span> <span class="nv">y</span> <span class="o">=</span> <span class="n">insetRect</span><span class="o">.</span><span class="n">minY</span> <span class="o">+</span> <span class="n">hypotenuse</span> <span class="o">-</span> <span class="n">opposite</span>
        <span class="n">path</span><span class="o">.</span><span class="nf">addLine</span><span class="p">(</span><span class="nv">to</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">x</span><span class="p">,</span> <span class="nv">y</span><span class="p">:</span> <span class="n">y</span><span class="p">))</span>
        
        <span class="k">return</span> <span class="n">path</span>
    <span class="p">}</span>
</code></pre></div></div>

<h1 id="radians-and-degrees">Radians and Degrees</h1>

<p>In the above calculation the angle is given in degrees, since that’s easiest for the user, but to use it with Swift it needs to converted to radians. This is done by adding an extension on <code class="language-plaintext highlighter-rouge">CGFloat</code>.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">CGFloat</span> <span class="p">{</span>
    <span class="kd">func</span> <span class="nf">radiansToDegrees</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">CGFloat</span> <span class="p">{</span>
        <span class="k">self</span> <span class="o">*</span> <span class="mi">360</span> <span class="o">/</span> <span class="p">(</span><span class="o">.</span><span class="n">pi</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span>
    <span class="p">}</span>
    
    <span class="kd">func</span> <span class="nf">degreesToRadians</span><span class="p">()</span> <span class="o">-&gt;</span> <span class="kt">CGFloat</span> <span class="p">{</span>
        <span class="k">self</span> <span class="o">*</span> <span class="o">.</span><span class="n">pi</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">/</span> <span class="mi">360</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="drawing-style">Drawing Style</h1>

<p>The AngleShape is now ready to be used. I used a somewhat abritrary frame of 400 by 400 pixels. A good choice of color is <code class="language-plaintext highlighter-rouge">.foreground</code> so that it looks as expected in both light and dark mode. For the <code class="language-plaintext highlighter-rouge">StrokeStyle</code> use rounded line caps and line joins to avoid the issue where the pointy bit of the angle becomes long or gets replaced by a bevel. Lastly add <code class="language-plaintext highlighter-rouge">.animation(.default, value: angle)</code> to let SwiftUI animate the angle changes.</p>

<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">struct</span> <span class="kt">AngleView</span><span class="p">:</span> <span class="kt">View</span> <span class="p">{</span>
    
    <span class="kd">@State</span> <span class="k">var</span> <span class="nv">angle</span><span class="p">:</span> <span class="kt">CGFloat</span> <span class="o">=</span> <span class="mi">45</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">Label</span><span class="p">(</span><span class="s">"Angle"</span><span class="p">,</span> <span class="nv">systemImage</span><span class="p">:</span> <span class="s">"arrow.clockwise"</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">title</span><span class="p">)</span>
            <span class="kt">AngleShape</span><span class="p">(</span><span class="nv">angle</span><span class="p">:</span> <span class="n">angle</span><span class="p">)</span>
                <span class="o">.</span><span class="nf">stroke</span><span class="p">(</span><span class="o">.</span><span class="n">foreground</span><span class="p">,</span>  <span class="nv">style</span><span class="p">:</span> <span class="kt">StrokeStyle</span><span class="p">(</span><span class="nv">lineWidth</span><span class="p">:</span> <span class="mi">10</span><span class="p">,</span> <span class="nv">lineCap</span><span class="p">:</span> <span class="o">.</span><span class="n">round</span><span class="p">,</span> <span class="nv">lineJoin</span><span class="p">:</span> <span class="o">.</span><span class="n">round</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="mi">400</span><span class="p">,</span> <span class="nv">height</span><span class="p">:</span> <span class="mi">400</span><span class="p">)</span>
                <span class="o">.</span><span class="nf">animation</span><span class="p">(</span><span class="o">.</span><span class="k">default</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="n">angle</span><span class="p">)</span>
            <span class="kt">Stepper</span><span class="p">(</span><span class="s">"</span><span class="se">\(</span><span class="kt">Int</span><span class="p">(</span><span class="nf">round</span><span class="p">(</span><span class="n">angle</span><span class="p">))</span><span class="se">)</span><span class="s">°"</span><span class="p">,</span> <span class="nv">value</span><span class="p">:</span> <span class="err">$</span><span class="n">angle</span><span class="p">,</span> <span class="nv">step</span><span class="p">:</span> <span class="mi">5</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h1 id="code">Code</h1>

<p>You can find all the code created in this post <a href="https://github.com/johankool/blog-angle/tree/blog-post-1">on GitHub</a>.</p>

<p><img src="/assets/posts/angle/angle-dark.png" alt="Screenshot showing angle input control in dark mode" /></p>]]></content><author><name></name></author><summary type="html"><![CDATA[Aim]]></summary></entry><entry><title type="html">Welcome to johankool.nl!</title><link href="https://johankool.nl/update/2019/01/10/welcome-to-johankool-nl.html" rel="alternate" type="text/html" title="Welcome to johankool.nl!" /><published>2019-01-10T19:06:28+00:00</published><updated>2019-01-10T19:06:28+00:00</updated><id>https://johankool.nl/update/2019/01/10/welcome-to-johankool-nl</id><content type="html" xml:base="https://johankool.nl/update/2019/01/10/welcome-to-johankool-nl.html"><![CDATA[<p>A fresh new site. Perhaps content may appear here eventually.</p>]]></content><author><name></name></author><category term="update" /><summary type="html"><![CDATA[A fresh new site. Perhaps content may appear here eventually.]]></summary></entry></feed>