Jekyll2024-03-02T15:18:30-05:00https://www.jumhyn.com/feed.xmlJumhynSoftware engineer in NYC.Frederick Kellison-LinnCustom UIViewController presentation over current context2021-03-13T03:00:00-05:002021-03-13T03:00:00-05:00https://www.jumhyn.com/2021/03/13/view-controller-current-context-custom<p>I recently went down a rabbit hole trying to replicate the <code class="highlighter-rouge">.currentContext</code> or <code class="highlighter-rouge">.overCurrentContext</code> presentation style for a view controller using a <code class="highlighter-rouge">.custom</code> <code class="highlighter-rouge">modalPresentationStyle</code> (<code class="highlighter-rouge">UIModalPresentationCurrentContext</code>, <code class="highlighter-rouge">UIModalPresentationOverCurrentContext</code>, and <code class="highlighter-rouge">UIModalPresentationStyleCustom</code> in Objective-C). I came up with a solution that works pretty well, so I thought I’d share it here for anyone scouring the internet looking for a solution!</p>
<h2 id="the-setup">The setup</h2>
<p>Let’s start with a basic setup that has a container view controller which splits its view into “top” and “bottom” controllers, with the bottom controller having <code class="highlighter-rouge">definesPresentationContext</code> set to <code class="highlighter-rouge">true</code>, and a button that presents a (translucent) child view controller over the current context:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">ContainerViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">topViewController</span> <span class="o">=</span> <span class="kt">TopViewController</span><span class="p">()</span>
<span class="k">let</span> <span class="nv">bottomViewController</span> <span class="o">=</span> <span class="kt">BottomViewController</span><span class="p">()</span>
<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">topContainerView</span> <span class="o">=</span> <span class="kt">UIView</span><span class="p">()</span>
<span class="n">topContainerView</span><span class="o">.</span><span class="n">translatesAutoresizingMaskIntoConstraints</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">let</span> <span class="nv">bottomContainerView</span> <span class="o">=</span> <span class="kt">UIView</span><span class="p">()</span>
<span class="n">bottomContainerView</span><span class="o">.</span><span class="n">translatesAutoresizingMaskIntoConstraints</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="nf">addSubview</span><span class="p">(</span><span class="n">topContainerView</span><span class="p">)</span>
<span class="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="nf">addSubview</span><span class="p">(</span><span class="n">bottomContainerView</span><span class="p">)</span>
<span class="kt">NSLayoutConstraint</span><span class="o">.</span><span class="nf">activate</span><span class="p">([</span>
<span class="n">topContainerView</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">topAnchor</span><span class="p">),</span>
<span class="n">topContainerView</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">leadingAnchor</span><span class="p">),</span>
<span class="n">topContainerView</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">trailingAnchor</span><span class="p">),</span>
<span class="n">topContainerView</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">centerYAnchor</span><span class="p">),</span>
<span class="n">bottomContainerView</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">centerYAnchor</span><span class="p">),</span>
<span class="n">bottomContainerView</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">leadingAnchor</span><span class="p">),</span>
<span class="n">bottomContainerView</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">trailingAnchor</span><span class="p">),</span>
<span class="n">bottomContainerView</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">bottomAnchor</span><span class="p">),</span>
<span class="p">])</span>
<span class="k">self</span><span class="o">.</span><span class="nf">addChild</span><span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">topViewController</span><span class="p">)</span>
<span class="n">topContainerView</span><span class="o">.</span><span class="nf">addSubview</span><span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">topViewController</span><span class="o">.</span><span class="n">view</span><span class="p">)</span>
<span class="k">self</span><span class="o">.</span><span class="n">topViewController</span><span class="o">.</span><span class="nf">didMove</span><span class="p">(</span><span class="nv">toParent</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span>
<span class="k">self</span><span class="o">.</span><span class="nf">addChild</span><span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">bottomViewController</span><span class="p">)</span>
<span class="n">bottomContainerView</span><span class="o">.</span><span class="nf">addSubview</span><span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">bottomViewController</span><span class="o">.</span><span class="n">view</span><span class="p">)</span>
<span class="k">self</span><span class="o">.</span><span class="n">bottomViewController</span><span class="o">.</span><span class="nf">didMove</span><span class="p">(</span><span class="nv">toParent</span><span class="p">:</span> <span class="k">self</span><span class="p">)</span>
<span class="k">self</span><span class="o">.</span><span class="n">topViewController</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">translatesAutoresizingMaskIntoConstraints</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">self</span><span class="o">.</span><span class="n">bottomViewController</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">translatesAutoresizingMaskIntoConstraints</span> <span class="o">=</span> <span class="kc">false</span>
<span class="kt">NSLayoutConstraint</span><span class="o">.</span><span class="nf">activate</span><span class="p">([</span>
<span class="k">self</span><span class="o">.</span><span class="n">topViewController</span><span class="o">.</span><span class="n">view</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">topContainerView</span><span class="o">.</span><span class="n">topAnchor</span><span class="p">),</span>
<span class="k">self</span><span class="o">.</span><span class="n">topViewController</span><span class="o">.</span><span class="n">view</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">topContainerView</span><span class="o">.</span><span class="n">leadingAnchor</span><span class="p">),</span>
<span class="k">self</span><span class="o">.</span><span class="n">topViewController</span><span class="o">.</span><span class="n">view</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">topContainerView</span><span class="o">.</span><span class="n">trailingAnchor</span><span class="p">),</span>
<span class="k">self</span><span class="o">.</span><span class="n">topViewController</span><span class="o">.</span><span class="n">view</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">topContainerView</span><span class="o">.</span><span class="n">bottomAnchor</span><span class="p">),</span>
<span class="k">self</span><span class="o">.</span><span class="n">bottomViewController</span><span class="o">.</span><span class="n">view</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">bottomContainerView</span><span class="o">.</span><span class="n">topAnchor</span><span class="p">),</span>
<span class="k">self</span><span class="o">.</span><span class="n">bottomViewController</span><span class="o">.</span><span class="n">view</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">bottomContainerView</span><span class="o">.</span><span class="n">leadingAnchor</span><span class="p">),</span>
<span class="k">self</span><span class="o">.</span><span class="n">bottomViewController</span><span class="o">.</span><span class="n">view</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">bottomContainerView</span><span class="o">.</span><span class="n">trailingAnchor</span><span class="p">),</span>
<span class="k">self</span><span class="o">.</span><span class="n">bottomViewController</span><span class="o">.</span><span class="n">view</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">bottomContainerView</span><span class="o">.</span><span class="n">bottomAnchor</span><span class="p">)</span>
<span class="p">])</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="kt">TopViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<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">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="o">.</span><span class="n">green</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="kt">BottomViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<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">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="o">.</span><span class="n">blue</span>
<span class="k">self</span><span class="o">.</span><span class="n">definesPresentationContext</span> <span class="o">=</span> <span class="kc">true</span>
<span class="k">let</span> <span class="nv">button</span> <span class="o">=</span> <span class="kt">UIButton</span><span class="p">(</span>
<span class="nv">primaryAction</span><span class="p">:</span> <span class="kt">UIAction</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">_</span> <span class="k">in</span>
<span class="k">self</span><span class="p">?</span><span class="o">.</span><span class="nf">presentChild</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">)</span>
<span class="n">button</span><span class="o">.</span><span class="n">translatesAutoresizingMaskIntoConstraints</span> <span class="o">=</span> <span class="kc">false</span>
<span class="n">button</span><span class="o">.</span><span class="nf">setTitle</span><span class="p">(</span><span class="s">"Present"</span><span class="p">,</span> <span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">normal</span><span class="p">)</span>
<span class="n">button</span><span class="o">.</span><span class="nf">setTitleColor</span><span class="p">(</span><span class="o">.</span><span class="n">label</span><span class="p">,</span> <span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">normal</span><span class="p">)</span>
<span class="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="nf">addSubview</span><span class="p">(</span><span class="n">button</span><span class="p">)</span>
<span class="kt">NSLayoutConstraint</span><span class="o">.</span><span class="nf">activate</span><span class="p">([</span>
<span class="n">button</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">topAnchor</span><span class="p">),</span>
<span class="n">button</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">leadingAnchor</span><span class="p">),</span>
<span class="n">button</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">trailingAnchor</span><span class="p">),</span>
<span class="n">button</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">bottomAnchor</span><span class="p">)</span>
<span class="p">])</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">presentChild</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">presentedViewController</span> <span class="o">=</span> <span class="kt">PresentedViewController</span><span class="p">()</span>
<span class="n">presentedViewController</span><span class="o">.</span><span class="n">modalPresentationStyle</span> <span class="o">=</span> <span class="o">.</span><span class="n">overCurrentContext</span>
<span class="k">self</span><span class="o">.</span><span class="nf">present</span><span class="p">(</span><span class="n">presentedViewController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">class</span> <span class="kt">PresentedViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<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">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="o">.</span><span class="n">red</span><span class="o">.</span><span class="nf">withAlphaComponent</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>This gives us the UI shown below:</p>
<p><img src="/assets/custom-presentation-setup.gif" alt="GIF showing the setup described above" width="200" /></p>
<h2 id="the-first-attempt">The first attempt</h2>
<p>Now, suppose we wanted to customize the presentation here to, say, show a dimming view behind the view controller and present the child over only half the container. Our first attempt might be to set <code class="highlighter-rouge">modalPresentationStyle</code> to <code class="highlighter-rouge">.custom</code> and implement a custom <code class="highlighter-rouge">UIPresentationController</code>. Most of the functionality can be achieved by following the <a href="https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/DefiningCustomPresentations.html">Apple docs</a>. First, implement the <code class="highlighter-rouge">UIPresentationController</code> subclass:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">PresentedViewController</span> <span class="p">{</span>
<span class="kd">class</span> <span class="kt">PresentationController</span><span class="p">:</span> <span class="kt">UIPresentationController</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">dimmingView</span><span class="p">:</span> <span class="kt">UIView</span> <span class="o">=</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">view</span> <span class="o">=</span> <span class="kt">UIView</span><span class="p">(</span><span class="nv">frame</span><span class="p">:</span> <span class="o">.</span><span class="n">zero</span><span class="p">)</span>
<span class="n">view</span><span class="o">.</span><span class="n">translatesAutoresizingMaskIntoConstraints</span> <span class="o">=</span> <span class="kc">false</span>
<span class="n">view</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="o">.</span><span class="n">black</span>
<span class="n">view</span><span class="o">.</span><span class="n">alpha</span> <span class="o">=</span> <span class="mf">0.0</span>
<span class="k">return</span> <span class="n">view</span>
<span class="p">}()</span>
<span class="k">override</span> <span class="k">var</span> <span class="nv">frameOfPresentedViewInContainerView</span><span class="p">:</span> <span class="kt">CGRect</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">containerView</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="o">.</span><span class="n">zero</span> <span class="p">}</span>
<span class="k">return</span> <span class="kt">CGRect</span><span class="p">(</span>
<span class="nv">x</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
<span class="nv">y</span><span class="p">:</span> <span class="n">containerView</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">midY</span><span class="p">,</span>
<span class="nv">width</span><span class="p">:</span> <span class="n">containerView</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">width</span><span class="p">,</span>
<span class="nv">height</span><span class="p">:</span> <span class="n">containerView</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="mf">2.0</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="k">override</span> <span class="kd">func</span> <span class="nf">presentationTransitionWillBegin</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">containerView</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">containerView</span><span class="o">!</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">presentedView</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">presentedView</span> <span class="p">{</span>
<span class="n">containerView</span><span class="o">.</span><span class="nf">insertSubview</span><span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">dimmingView</span><span class="p">,</span> <span class="nv">belowSubview</span><span class="p">:</span> <span class="n">presentedView</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">containerView</span><span class="o">.</span><span class="nf">addSubview</span><span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">dimmingView</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">self</span><span class="o">.</span><span class="n">dimmingView</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">containerView</span><span class="o">.</span><span class="n">bounds</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">coordinator</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">presentedViewController</span><span class="o">.</span><span class="n">transitionCoordinator</span> <span class="p">{</span>
<span class="n">coordinator</span><span class="o">.</span><span class="n">animate</span> <span class="p">{</span> <span class="n">_</span> <span class="k">in</span>
<span class="k">self</span><span class="o">.</span><span class="n">dimmingView</span><span class="o">.</span><span class="n">alpha</span> <span class="o">=</span> <span class="mf">0.5</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">dimmingView</span><span class="o">.</span><span class="n">alpha</span> <span class="o">=</span> <span class="mf">0.5</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">override</span> <span class="kd">func</span> <span class="nf">presentationTransitionDidEnd</span><span class="p">(</span><span class="n">_</span> <span class="nv">completed</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">completed</span> <span class="o">==</span> <span class="kc">false</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">dimmingView</span><span class="o">.</span><span class="nf">removeFromSuperview</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">override</span> <span class="kd">func</span> <span class="nf">dismissalTransitionWillBegin</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="k">let</span> <span class="nv">coordinator</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">presentedViewController</span><span class="o">.</span><span class="n">transitionCoordinator</span> <span class="p">{</span>
<span class="n">coordinator</span><span class="o">.</span><span class="n">animate</span> <span class="p">{</span> <span class="n">_</span> <span class="k">in</span>
<span class="k">self</span><span class="o">.</span><span class="n">dimmingView</span><span class="o">.</span><span class="n">alpha</span> <span class="o">=</span> <span class="mf">0.0</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">dimmingView</span><span class="o">.</span><span class="n">alpha</span> <span class="o">=</span> <span class="mf">0.0</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">override</span> <span class="kd">func</span> <span class="nf">dismissalTransitionDidEnd</span><span class="p">(</span><span class="n">_</span> <span class="nv">completed</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">dimmingView</span><span class="o">.</span><span class="nf">removeFromSuperview</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Next, implement the <code class="highlighter-rouge">presentationController(forPresented:presenting:source:)</code> method from <code class="highlighter-rouge">UIViewControllerTransitioningDelegate</code> and return our custom presentation controller:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">extension</span> <span class="kt">PresentedViewController</span><span class="p">:</span> <span class="kt">UIViewControllerTransitioningDelegate</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">presentationController</span><span class="p">(</span>
<span class="n">forPresented</span> <span class="nv">presented</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">,</span>
<span class="nv">presenting</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">?,</span>
<span class="nv">source</span><span class="p">:</span> <span class="kt">UIViewController</span>
<span class="p">)</span> <span class="o">-></span> <span class="kt">UIPresentationController</span><span class="p">?</span> <span class="p">{</span>
<span class="k">return</span> <span class="kt">PresentationController</span><span class="p">(</span><span class="nv">presentedViewController</span><span class="p">:</span> <span class="n">presented</span><span class="p">,</span> <span class="nv">presenting</span><span class="p">:</span> <span class="n">presenting</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Finally, update <code class="highlighter-rouge">BottomViewController.presentChild</code> to use our custom presentation:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>func presentChild() {
let presentedViewController = PresentedViewController()
presentedViewController.modalPresentationStyle = .custom
presentedViewController.transitioningDelegate = presentedViewController
self.present(presentedViewController, animated: true)
}
</code></pre></div></div>
<p>Now, build and run the app, hit the “Present” button, and…</p>
<p><img src="/assets/custom-presentation-first-attempt.gif" alt="GIF showing modal presentation that dims the entire screen, not just the bottom container" width="200" /></p>
<p>Uh oh! By changing the <code class="highlighter-rouge">presentedViewController.modalPresentationStyle</code> to <code class="highlighter-rouge">.custom</code>, we’ve lost the <code class="highlighter-rouge">.overCurrentContext</code> behavior that confined the presentation to the bottom container, so UIKit is presenting the <code class="highlighter-rouge">PresentedViewController</code> over the entire screen. Looks like we’ll have to try something different…</p>
<h2 id="uipresentationcontrollershouldpresentinfullscreen-to-the-rescue-or-not"><code class="highlighter-rouge">UIPresentationController.shouldPresentInFullscreen</code> to the rescue… or not</h2>
<p>Reading the docs for <code class="highlighter-rouge">UIPresentationController</code>, you might find <code class="highlighter-rouge">shouldPresentInFullscreen</code> and think that the description matches exactly what we’re trying to do:</p>
<blockquote>
<p>The default implementation of this method returns <code class="highlighter-rouge">true</code>, indicating that the presentation covers the entire screen. You can override this method and return <code class="highlighter-rouge">false</code> to force the presentation to display only in the current context.</p>
</blockquote>
<p>That <em>sounds</em> great, but if we override the method like it says and return <code class="highlighter-rouge">false</code>, we get the exact same behavior as before:</p>
<p><img src="/assets/custom-presentation-first-attempt.gif" alt="GIF showing the same behavior as the previous GIF, with the modal presentation covering the entire screen" width="200" /></p>
<p>There are countless questions online from engineers who are similarly confused about the utility of <code class="highlighter-rouge">shouldPresentInFullscreen</code>, but I haven’t seen any clear answers. As of iOS 14, it’s still not apparent to me what this property does. If you know, send me an email and I can update this post if it solves some of our problems!</p>
<h2 id="calculating-the-frame-ourselves">Calculating the frame ourselves</h2>
<p>We might not be able to automatically achieve the current context presentation, but <code class="highlighter-rouge">UIPresentationController</code> gives us a lot of flexibility. Notably, we have complete control over <code class="highlighter-rouge">frameOfPresentedViewInContainerView</code> and the layout of any custom views (such as <code class="highlighter-rouge">dimmerView</code>), so it shouldn’t be too difficult to do our own calculations.</p>
<p>The first step is to make sure that our <code class="highlighter-rouge">PresentationController</code> has access to the appropriate presentation context. Let’s add a property and a new initializer to <code class="highlighter-rouge">PresentationController</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">contextViewController</span><span class="p">:</span> <span class="kt">UIViewController</span>
<span class="nf">init</span><span class="p">(</span><span class="nv">presentedViewController</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">,</span> <span class="nv">presenting</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">?,</span> <span class="nv">context</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">contextViewController</span> <span class="o">=</span> <span class="n">context</span>
<span class="k">super</span><span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">presentedViewController</span><span class="p">:</span> <span class="n">presentedViewController</span><span class="p">,</span> <span class="nv">presenting</span><span class="p">:</span> <span class="n">presenting</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now, update the implementation for <code class="highlighter-rouge">frameOfPresentedViewInContainerView</code> to use the bounds of the <code class="highlighter-rouge">contextViewController</code> instead of the <code class="highlighter-rouge">containerView</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="k">var</span> <span class="nv">frameOfPresentedViewInContainerView</span><span class="p">:</span> <span class="kt">CGRect</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">containerView</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="o">.</span><span class="n">zero</span> <span class="p">}</span>
<span class="k">let</span> <span class="nv">contextBounds</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">contextViewController</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="nf">convert</span><span class="p">(</span>
<span class="k">self</span><span class="o">.</span><span class="n">contextViewController</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">bounds</span><span class="p">,</span>
<span class="nv">to</span><span class="p">:</span> <span class="n">containerView</span>
<span class="p">)</span>
<span class="k">return</span> <span class="kt">CGRect</span><span class="p">(</span>
<span class="nv">x</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
<span class="nv">y</span><span class="p">:</span> <span class="n">contextBounds</span><span class="o">.</span><span class="n">midY</span><span class="p">,</span>
<span class="nv">width</span><span class="p">:</span> <span class="n">contextBounds</span><span class="o">.</span><span class="n">width</span><span class="p">,</span>
<span class="nv">height</span><span class="p">:</span> <span class="n">contextBounds</span><span class="o">.</span><span class="n">height</span> <span class="o">/</span> <span class="mf">2.0</span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Similarly, make sure we update the frame of <code class="highlighter-rouge">PresentationController.dimmingView</code> to confine it to the bounds of the context (<em>not</em> the entire <code class="highlighter-rouge">containerView</code>) in <code class="highlighter-rouge">presentationTransitionWillBegin</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">self</span><span class="o">.</span><span class="n">dimmingView</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">contextViewController</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="nf">convert</span><span class="p">(</span>
<span class="k">self</span><span class="o">.</span><span class="n">contextViewController</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">bounds</span><span class="p">,</span>
<span class="nv">to</span><span class="p">:</span> <span class="n">containerView</span>
<span class="p">)</span>
</code></pre></div></div>
<p>Lastly, we have to make sure that we update <code class="highlighter-rouge">PresentedViewController.presentationController(forPresented:presenting:source:)</code> to properly pass the context to <code class="highlighter-rouge">PresentationController.init</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">presentationController</span><span class="p">(</span>
<span class="n">forPresented</span> <span class="nv">presented</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">,</span>
<span class="nv">presenting</span><span class="p">:</span> <span class="kt">UIViewController</span><span class="p">?,</span>
<span class="nv">source</span><span class="p">:</span> <span class="kt">UIViewController</span>
<span class="p">)</span> <span class="o">-></span> <span class="kt">UIPresentationController</span><span class="p">?</span> <span class="p">{</span>
<span class="k">var</span> <span class="nv">context</span> <span class="o">=</span> <span class="n">source</span>
<span class="k">while</span> <span class="o">!</span><span class="n">context</span><span class="o">.</span><span class="n">definesPresentationContext</span><span class="p">,</span>
<span class="k">let</span> <span class="nv">parent</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="n">parent</span> <span class="p">{</span>
<span class="n">context</span> <span class="o">=</span> <span class="n">parent</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kt">PresentationController</span><span class="p">(</span>
<span class="nv">presentedViewController</span><span class="p">:</span> <span class="n">presented</span><span class="p">,</span>
<span class="nv">presenting</span><span class="p">:</span> <span class="n">presenting</span><span class="p">,</span>
<span class="nv">context</span><span class="p">:</span> <span class="n">context</span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Note that in this method, <code class="highlighter-rouge">source</code> is the view controller on which the programmer originally called <code class="highlighter-rouge">present(_:animated:completion:)</code>, which in our case is <code class="highlighter-rouge">BottomViewController</code>. Since we know that <code class="highlighter-rouge">BottomViewController.definesPresentationContext</code> is <code class="highlighter-rouge">true</code>, we <em>could have</em> just passed <code class="highlighter-rouge">source</code> in for <code class="highlighter-rouge">context</code> directly and gotten the same behavior. However, in the interest of more closely replicating the behavior of <code class="highlighter-rouge">.currentContext</code> and <code class="highlighter-rouge">.overCurrentContext</code>, this implementation walks up the <code class="highlighter-rouge">parent</code> chain to find the first parent above <code class="highlighter-rouge">source</code> which defines the presentation context, using the window’s root view controller if nothing else is found.</p>
<p>Now, when we present our view controller:</p>
<p><img src="/assets/custom-presentation-second-attempt.gif" alt="GIF showing a (mostly) successfull presentation. The bottom view controller is dimmed and the presented view controller covers half the container" width="200" /></p>
<p>Success! That’s exactly the outcome we wanted.</p>
<h2 id="or-is-it">Or is it?</h2>
<p>As it turns out, this approach has one more puzzle piece missing, which might not be immediately apparent. To illustrate the problem, let’s add an interactive element to the <em>top</em> view controller:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">TopViewController</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<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">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="o">.</span><span class="n">green</span>
<span class="k">let</span> <span class="nv">slider</span> <span class="o">=</span> <span class="kt">UISlider</span><span class="p">(</span><span class="nv">frame</span><span class="p">:</span> <span class="o">.</span><span class="n">zero</span><span class="p">)</span>
<span class="n">slider</span><span class="o">.</span><span class="n">translatesAutoresizingMaskIntoConstraints</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="nf">addSubview</span><span class="p">(</span><span class="n">slider</span><span class="p">)</span>
<span class="kt">NSLayoutConstraint</span><span class="o">.</span><span class="nf">activate</span><span class="p">([</span>
<span class="n">slider</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">leadingAnchor</span><span class="p">),</span>
<span class="n">slider</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">trailingAnchor</span><span class="p">),</span>
<span class="n">slider</span><span class="o">.</span><span class="n">centerYAnchor</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="k">self</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">centerYAnchor</span><span class="p">)</span>
<span class="p">])</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If we run the app, the issue becomes clear once we proceed with the custom presentation:</p>
<p><img src="/assets/custom-presentation-second-attempt-issue.gif" alt="GIF showing problem with the custom presentation. After the presented view controller covers the bottom container, the UI elements in the top container become unresponsive" width="200" /></p>
<p>A quick dip into the “Debug View Hierarchy” tool immediately reveals the root cause:</p>
<p><img src="/assets/custom-presentation-ui-inspector.png" alt="Screenshot from Debug View Hierarchy. The UITransitionView that contains the presented view controller covers the whole screen, even though the presented view controller and dimming view are confined to the presentation context." width="500" /></p>
<p>We carefully adjusted the frames of the <code class="highlighter-rouge">dimmingView</code> and the <code class="highlighter-rouge">presentedViewController</code>, but the UIKit-controlled <code class="highlighter-rouge">containerView</code> is still covering the entire screen. This means that while the view controller is presented, the <code class="highlighter-rouge">containerView</code> will intercept all the touch events destined for outside the presentation context.</p>
<p>I haven’t come up with a perfect solution for this, but I have a few approaches that can alleviate this problem.</p>
<h3 id="1-custom-dismissal-gesture">1. Custom dismissal gesture</h3>
<p>This approach is probably the least fragile, in that it won’t rely on any private or undocumented features, but it also doesn’t achieve the best user experience. The basic idea is to introduce another custom view to our presentation hierarchy that will allow us to intercept and respond to touches that occur outside the container. That looks something like this:</p>
<p>First, introduce a new <code class="highlighter-rouge">backgroundButton</code> property to <code class="highlighter-rouge">PresentationController</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="nv">backgroundButton</span><span class="p">:</span> <span class="kt">UIButton</span> <span class="o">=</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">button</span> <span class="o">=</span> <span class="kt">UIButton</span><span class="p">(</span><span class="nv">frame</span><span class="p">:</span> <span class="o">.</span><span class="n">zero</span><span class="p">)</span>
<span class="n">button</span><span class="o">.</span><span class="n">translatesAutoresizingMaskIntoConstraints</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">return</span> <span class="n">button</span>
<span class="p">}()</span>
</code></pre></div></div>
<p>Next, add a function to <code class="highlighter-rouge">PresentationController</code> that will be called when the <code class="highlighter-rouge">backgroundButton</code> receives a touch. We could do a number of things in here, but for now we will just dismiss the presented view controller:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">@objc</span> <span class="kd">func</span> <span class="nf">backgroundButtonTapped</span><span class="p">()</span> <span class="p">{</span>
<span class="k">self</span><span class="o">.</span><span class="n">presentedViewController</span><span class="o">.</span><span class="nf">dismiss</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Finally, in <code class="highlighter-rouge">PresentationController.presentationTransitionWillBegin</code>, add the background button to the hierarchy, have it fill the container, and set the target and action appropriately:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">containerView</span><span class="o">.</span><span class="nf">insertSubview</span><span class="p">(</span><span class="k">self</span><span class="o">.</span><span class="n">backgroundButton</span><span class="p">,</span> <span class="nv">belowSubview</span><span class="p">:</span> <span class="k">self</span><span class="o">.</span><span class="n">dimmingView</span><span class="p">)</span>
<span class="k">self</span><span class="o">.</span><span class="n">backgroundButton</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="n">containerView</span><span class="o">.</span><span class="n">bounds</span>
<span class="k">self</span><span class="o">.</span><span class="n">backgroundButton</span><span class="o">.</span><span class="nf">addTarget</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">backgroundButtonTapped</span><span class="kd">)</span><span class="p">,</span>
<span class="nv">for</span><span class="p">:</span> <span class="o">.</span><span class="n">touchDown</span>
<span class="p">)</span>
</code></pre></div></div>
<p>In our case, we have the view controller get dismissed on <code class="highlighter-rouge">.touchDown</code>, but obviously other control events could be used here as well. You could even set up a gesture recognizer using this approach if you needed more precise control over when to trigger <code class="highlighter-rouge">backgroundButtonTapped</code>:</p>
<p><img src="/assets/custom-presentation-background-button.gif" alt="GIF showing the dismissal when the user taps outside the presentation context. Touches inside the presentation context don't dismiss the presented view controller." width="200" /></p>
<p>Looks pretty good! The downside of this approach is that it still keeps the user from interacting with any content outside the presentation context while the view controller is presented, so it’s not a total replacement for <code class="highlighter-rouge">.overCurrentContext</code> or <code class="highlighter-rouge">.currentContext</code> functionality-wise.</p>
<h3 id="2-getting-swizzly">2. Getting swizzly</h3>
<p>As with any blog post on a UIKit issue, there must be one solution that uses <a href="https://nshipster.com/method-swizzling/">swizzling</a>. While some people are hesitant to introduce any use of swizzling to their codebase (with good reason!) it can be a powerful tool if used sparingly and carefully.</p>
<p><em>Full disclosure: swizzling can be dangerous! I’m not even completely confident that the swizzling as performed in this blog post is one hundred percent correct. There are <a href="https://github.com/steipete/InterposeKit">libraries</a> out there that can be used to make swizzling more ergonomic, but if you’re not totally confident in what you’re doing then swizzling may not be the best approach.</em></p>
<p>In this case, we’ll be swizzling the <code class="highlighter-rouge">UIView.hitTest(_:with:)</code> method of <code class="highlighter-rouge">containerView</code> to cause touches to pass “through” the container view when no other view is hit. The modifications will start from the project state before the changes in <strong>Custom dismissal gesture</strong>, so revert those changes before proceeding if you’re coding along.</p>
<p>To do the heavy lifting, we define a top-level function called <code class="highlighter-rouge">swizzleHitTest</code> that takes in a <code class="highlighter-rouge">UIView</code> and swaps out the <code class="highlighter-rouge">hitTest</code> method with our replacement logic:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">func</span> <span class="nf">swizzleHitTest</span><span class="p">(</span><span class="k">for</span> <span class="nv">view</span><span class="p">:</span> <span class="kt">UIView</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Retrieve the original `viewDidAppear` method and implementation for this view controller.</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">method</span> <span class="o">=</span> <span class="nf">class_getInstanceMethod</span><span class="p">(</span><span class="nf">type</span><span class="p">(</span><span class="nv">of</span><span class="p">:</span> <span class="n">view</span><span class="p">),</span> <span class="o">.</span><span class="n">hitTest</span><span class="p">)</span> <span class="k">else</span> <span class="p">{</span> <span class="k">return</span> <span class="p">}</span>
<span class="k">let</span> <span class="nv">originalImp</span> <span class="o">=</span> <span class="nf">method_getImplementation</span><span class="p">(</span><span class="n">method</span><span class="p">)</span>
<span class="c1">// Create the new `IMP`</span>
<span class="k">let</span> <span class="nv">newImp</span> <span class="o">=</span> <span class="nf">imp_implementationWithBlock</span><span class="p">({</span> <span class="n">_self</span><span class="p">,</span> <span class="n">point</span><span class="p">,</span> <span class="n">event</span> <span class="k">in</span> <span class="c1">// swiftlint:disable:this identifier_name</span>
<span class="c1">// An `IMP` is just a C function pointer where the first two args are `self` and `_cmd`.</span>
<span class="k">let</span> <span class="nv">originalImpFunc</span> <span class="o">=</span> <span class="nf">unsafeBitCast</span><span class="p">(</span>
<span class="n">originalImp</span><span class="p">,</span>
<span class="nv">to</span><span class="p">:</span> <span class="p">(</span><span class="kd">@convention(c)</span> <span class="p">(</span><span class="kt">Any</span><span class="p">,</span> <span class="kt">Selector</span><span class="p">,</span> <span class="kt">CGPoint</span><span class="p">,</span> <span class="kt">UIEvent</span><span class="p">?)</span> <span class="o">-></span> <span class="kt">UIView</span><span class="p">?)</span><span class="o">.</span><span class="k">self</span>
<span class="p">)</span>
<span class="c1">// Call the original implementation.</span>
<span class="k">let</span> <span class="nv">hitView</span> <span class="o">=</span> <span class="nf">originalImpFunc</span><span class="p">(</span><span class="n">_self</span><span class="p">,</span> <span class="o">.</span><span class="n">hitTest</span><span class="p">,</span> <span class="n">point</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span>
<span class="c1">// If we didn't hit a subview, then pretend we didn't hit anything.</span>
<span class="k">if</span> <span class="n">hitView</span> <span class="o">===</span> <span class="p">(</span><span class="n">_self</span> <span class="k">as?</span> <span class="kt">UIView</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// Otherwise, return the subview that was hit.</span>
<span class="k">return</span> <span class="n">hitView</span>
<span class="p">}</span>
<span class="c1">// `imp_implementationWithBlock` does not provide the block with a `_cmd` parameter.</span>
<span class="p">}</span> <span class="k">as</span> <span class="kd">@convention(block)</span> <span class="p">(</span><span class="kt">Any</span><span class="p">,</span> <span class="kt">CGPoint</span><span class="p">,</span> <span class="kt">UIEvent</span><span class="p">?)</span> <span class="o">-></span> <span class="kt">UIView</span><span class="p">?)</span>
<span class="c1">// Actually do the swizzle!</span>
<span class="nf">method_setImplementation</span><span class="p">(</span><span class="n">method</span><span class="p">,</span> <span class="n">newImp</span><span class="p">)</span>
<span class="p">}</span>
<span class="kd">private</span> <span class="kd">extension</span> <span class="kt">Selector</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">let</span> <span class="nv">hitTest</span> <span class="o">=</span> <span class="kd">#selector(</span><span class="nf">UIView.hitTest(_:with:)</span><span class="kd">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>A lot of this is just boilerplate to set up the swizzle, but the meat of the implementation is in these lines:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Call the original implementation.</span>
<span class="k">let</span> <span class="nv">hitView</span> <span class="o">=</span> <span class="nf">originalImpFunc</span><span class="p">(</span><span class="n">_self</span><span class="p">,</span> <span class="o">.</span><span class="n">hitTest</span><span class="p">,</span> <span class="n">point</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span>
<span class="c1">// If we didn't hit a subview, then pretend we didn't hit anything.</span>
<span class="k">if</span> <span class="n">hitView</span> <span class="o">===</span> <span class="p">(</span><span class="n">_self</span> <span class="k">as?</span> <span class="kt">UIView</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">nil</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// Otherwise, return the subview that was hit.</span>
<span class="k">return</span> <span class="n">hitView</span>
<span class="p">}</span>
</code></pre></div></div>
<p>We begin by accessing the <code class="highlighter-rouge">hitView</code> returned by the original implementation. This represents the subview of <code class="highlighter-rouge">_self</code> (or perhaps <code class="highlighter-rouge">_self</code> itself) that was “hit” by a touch at the location <code class="highlighter-rouge">point</code>. If <code class="highlighter-rouge">hitView</code> was the view itself, that means no subview was hit. In that case, we return <code class="highlighter-rouge">nil</code> to pretend as though no view was hit at all, so that UIKit will continue traversing the view hierarchy looking for views underneath <code class="highlighter-rouge">_self</code> that are willing to receive the touch.</p>
<p>With this function defined, now all we have to do is add the following line to <code class="highlighter-rouge">presentationTransitionWillBegin</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">swizzleHitTest</span><span class="p">(</span><span class="nv">for</span><span class="p">:</span> <span class="n">containerView</span><span class="p">)</span>
</code></pre></div></div>
<p>Let’s give it a try:</p>
<p><img src="/assets/custom-presentation-swizzle.gif" alt="GIF showing the result of the swizzling approach. The user is able to interact with the slider while the view controller remains presented." width="200" /></p>
<p>Awesome! We can now interact with content outside of the presentation context without the view controller getting dismissed.</p>
<p>For those that aren’t comfortable with swizzling or just want to avoid it, though, I do have one last approach.</p>
<h3 id="3-poking-the-containerviews-frame">3. Poking the <code class="highlighter-rouge">containerView</code>’s <code class="highlighter-rouge">frame</code></h3>
<p>Again, we’re starting over from the project state before we made any changes for the <strong>Custom dismissal gesture</strong> or <strong>Getting swizzly</strong> sections. This is the simplest of the three approaches, but it also makes me the most uneasy, since it’s not clear that this usage of the APIs is supported. Use it at your own risk!</p>
<p>The basic idea is that rather than let the <code class="highlighter-rouge">containerView</code> fill the entire window and position the <code class="highlighter-rouge">dimmingView</code> and presented view within it according to the presentation context, we will just adjust the frame of the <code class="highlighter-rouge">containerView</code> based on the presentation context. In <code class="highlighter-rouge">presentationTransitionWillBegin</code>, we add the following line immediately after initializing <code class="highlighter-rouge">containerView</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">containerView</span><span class="o">.</span><span class="n">frame</span> <span class="o">=</span> <span class="k">self</span><span class="o">.</span><span class="n">contextViewController</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="nf">convert</span><span class="p">(</span>
<span class="k">self</span><span class="o">.</span><span class="n">contextViewController</span><span class="o">.</span><span class="n">view</span><span class="o">.</span><span class="n">bounds</span><span class="p">,</span>
<span class="nv">to</span><span class="p">:</span> <span class="n">containerView</span><span class="o">.</span><span class="n">superview</span>
<span class="p">)</span>
</code></pre></div></div>
<p>and below, we update the line where we set <code class="highlighter-rouge">self.dimmingView.frame</code>:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>self.dimmingView.frame = containerView.bounds
</code></pre></div></div>
<p>Finally, we revert the implementation of <code class="highlighter-rouge">frameOfPresentedViewInContainerView</code> to what we had in <strong>The first attempt</strong> above:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="k">var</span> <span class="nv">frameOfPresentedViewInContainerView</span><span class="p">:</span> <span class="kt">CGRect</span> <span class="p">{</span>
<span class="k">guard</span> <span class="k">let</span> <span class="nv">containerView</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="o">.</span><span class="n">zero</span> <span class="p">}</span>
<span class="k">return</span> <span class="kt">CGRect</span><span class="p">(</span>
<span class="nv">x</span><span class="p">:</span> <span class="mf">0.0</span><span class="p">,</span>
<span class="nv">y</span><span class="p">:</span> <span class="n">containerView</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">midY</span><span class="p">,</span>
<span class="nv">width</span><span class="p">:</span> <span class="n">containerView</span><span class="o">.</span><span class="n">bounds</span><span class="o">.</span><span class="n">width</span><span class="p">,</span>
<span class="nv">height</span><span class="p">:</span> <span class="n">containerView</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="mf">2.0</span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Running this version, we get the same behavior as in <strong>Getting swizzly</strong> above. Interaction with the top view controller is permitted while the presented view controller covers the bottom context.</p>
<p>If we open up “Debug View Hierarchy” one last time, we can see the difference from our first approach.</p>
<p><img src="/assets/custom-presentation-ui-inspector-2.png" alt="Screenshot from Debug View Hierarchy. The UITransitionView that contains the presented view controller now only covers the region of the screen corresponding to the presentation context." width="500" /></p>
<p>Now, the <code class="highlighter-rouge">UITransitionView</code> that contains our presented view controller only covers the presentation context defined by the <code class="highlighter-rouge">BottomViewController</code>.</p>
<h2 id="disclaimer">Disclaimer</h2>
<p>The biggest downside to all these approaches (and the root cause of all our issues) is that the <code class="highlighter-rouge">presentingViewController</code> is <em>not</em> <code class="highlighter-rouge">BottomViewController</code>, but <code class="highlighter-rouge">ContainerViewController</code>. This means that we would have issues using this custom “current context” presentation in multiple branches of a view controller hierarchy at the same time, since we’d end up trying to present two view controllers on top of the top container at once.</p>
<p>If you have any suggestions about better ways to achieve this effect, please reach out and I will add them to this post!</p>
<h3 id="4-addendum-using-an-intermediate-presentation">4. (Addendum) Using an intermediate presentation</h3>
<p>Of course, after publishing I stumbled upon yet another approach that is similarly promising. It requires a bit more setup work, but it avoids the issuse mentioned above about <code class="highlighter-rouge">presentingViewController</code>.</p>
<p>I was tipped onto this approach by <a href="https://stackoverflow.com/a/60101052">this StackOverflow answer</a> on a question about <code class="highlighter-rouge">shouldPresentInFullscreen</code>. The short version: it seems that while <code class="highlighter-rouge">shouldPresentInFullscreen</code> won’t work if the custom presention is for the view controller <em>directly</em> presented on the view controller that defined the presentation context, it <em>will</em> have an effect if the custom presentation is taking place over <em>another</em> view controller that is presented in the context. Let’s take a closer look.</p>
<p>We will begin from the project state in <strong>The first attempt</strong>, with our presentation that covered the entire screen. The high level approach we will be taking is to have a view controller that sits in between <code class="highlighter-rouge">BottomViewController</code> and <code class="highlighter-rouge">PresentedViewController</code> in order to force the latter to be presented within the context of <code class="highlighter-rouge">BottomViewController</code>.</p>
<p>First, we will do what the documentation for <code class="highlighter-rouge">shouldPresentInFullscreen</code> suggests and override the property in <code class="highlighter-rouge">PresentationController</code> to return <code class="highlighter-rouge">false</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">override</span> <span class="k">var</span> <span class="nv">shouldPresentInFullscreen</span><span class="p">:</span> <span class="kt">Bool</span> <span class="p">{</span> <span class="kc">false</span> <span class="p">}</span>
</code></pre></div></div>
<p>Next, will define <code class="highlighter-rouge">PresentationHelper</code>, the view controller which will sit in between <code class="highlighter-rouge">BottomViewController</code> and <code class="highlighter-rouge">PresentedViewController</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="kt">PresentationHelper</span><span class="p">:</span> <span class="kt">UIViewController</span> <span class="p">{</span>
<span class="k">override</span> <span class="kd">func</span> <span class="nf">viewDidAppear</span><span class="p">(</span><span class="n">_</span> <span class="nv">animated</span><span class="p">:</span> <span class="kt">Bool</span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">viewController</span> <span class="o">=</span> <span class="kt">PresentedViewController</span><span class="p">()</span>
<span class="n">viewController</span><span class="o">.</span><span class="n">modalPresentationStyle</span> <span class="o">=</span> <span class="o">.</span><span class="n">custom</span>
<span class="n">viewController</span><span class="o">.</span><span class="n">transitioningDelegate</span> <span class="o">=</span> <span class="n">viewController</span>
<span class="k">self</span><span class="o">.</span><span class="nf">present</span><span class="p">(</span><span class="n">viewController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">true</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The idea here is to present the <em>actual</em> destination view controller as soon as our <code class="highlighter-rouge">PresentationHelper</code> appears. Now, all we have to do is update the implementation of <code class="highlighter-rouge">BottomViewController.presentChild</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">func</span> <span class="nf">presentChild</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="nv">helperViewController</span> <span class="o">=</span> <span class="kt">PresentationHelper</span><span class="p">()</span>
<span class="n">helperViewController</span><span class="o">.</span><span class="n">modalPresentationStyle</span> <span class="o">=</span> <span class="o">.</span><span class="n">overCurrentContext</span>
<span class="k">self</span><span class="o">.</span><span class="nf">present</span><span class="p">(</span><span class="n">helperViewController</span><span class="p">,</span> <span class="nv">animated</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Here, we make sure to present the <code class="highlighter-rouge">PresentationHelper</code> <em>un</em>animated, since its own <code class="highlighter-rouge">viewWillAppear</code> method takes care of performing the <code class="highlighter-rouge">PresentedViewController</code> presentation in an animated fashion. Since the <code class="highlighter-rouge">PresentationHelper</code> is invisible, presenting it in an animated fashion would just appear to the user as a breif delay before presenting the <code class="highlighter-rouge">PresentedViewController</code>.</p>
<p>And… that’s it! Build and run and you should see the desired presentation behavior:</p>
<p><img src="/assets/custom-presentation-double-presentation.gif" alt="GIF of the presentation behavior. The presented view controller is confined to the bottom context and the top view controller is responsive to touch." width="200" /></p>
<p>The last bit of housekeeping you’ll need to handle is to make sure that you clean up the <code class="highlighter-rouge">PresentationHelper</code> on the dismissal end, but exactly how you do this will depend on your particular presentation setup. I opted to add the following line to <code class="highlighter-rouge">PresentationController.dismissalTransitionDidEnd(_:)</code>:</p>
<div class="language-swift highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">self</span><span class="o">.</span><span class="n">presentingViewController</span><span class="o">.</span><span class="nf">dismiss</span><span class="p">(</span><span class="nv">animated</span><span class="p">:</span> <span class="kc">false</span><span class="p">)</span>
</code></pre></div></div>
<p>This ensures that once the <code class="highlighter-rouge">PresentedViewController</code> finishes its dismissal, we will immediately dismiss the <code class="highlighter-rouge">PresentationHelper</code> as well. If we didn’t then the transparent view would stick around and block all interaction with the <code class="highlighter-rouge">BottomViewController</code>.</p>Frederick Kellison-LinnI recently went down a rabbit hole trying to replicate the .currentContext or .overCurrentContext presentation style for a view controller using a .custom modalPresentationStyle (UIModalPresentationCurrentContext, UIModalPresentationOverCurrentContext, and UIModalPresentationStyleCustom in Objective-C). I came up with a solution that works pretty well, so I thought I’d share it here for anyone scouring the internet looking for a solution!Leverage2021-01-18T09:45:00-05:002021-01-18T09:45:00-05:00https://www.jumhyn.com/2021/01/18/leverage<p><em>The following are some disorganized thoughts I’ve had regarding the recent wave of minimum wage discourse. They are not well-researched and are highly opinionated. To the extent that anything I’ve written here is factually incorrect, I invite you to <a href="about">send me an email</a>. If you simply disagree with the subjective content, email me all the same. :)</em></p>
<p>After gradutating from college, several friends and I began looking for an apartment together. As it turns out, finding a place to live in New York City that has five bedrooms, is within the budget of five recent graduates, and satisfies five different sets of preferences is not an easy task, but after an extensive search we finally settled on a place in midtown.</p>
<p>The only caveat was that this apartment didn’t <em>technically</em> have five bedrooms. One of us would have to live in the living room (which, miraculously had an actual door), and one would have to live in what was, at one point, the apartment’s servant’s quarters. It did have its own bathroom, but it was small, awkwardly shaped, and contained the apartment’s laundry machine and dryer.</p>
<p>Needless to say, there were large disparities between every “bedroom” that made it impossible to divide rent evenly without someone feeling stiffed, so we had to come up with a system to split rent fairly.</p>
<p>Enter the New York Times. Based on a paper by Harvey Mudd math professor <a href="https://math.hmc.edu/su/">Francis Su</a>, the Times developed a <a href="https://www.nytimes.com/interactive/2014/science/rent-division-calculator.html">fair rent division calculator</a>. The calculator proceeds almost as a game: it goes through each resident, proposing a rent division for each room and asking them which room they would choose at these prices. Slowly, the algorithm learns which rooms are the most (and least) desirable, and adjusts the rent division accordingly.</p>
<p>If a few modest assumptions are satisfied, the process promises to find a solution which is “envy-free,” that is, when the final rents and residents are assigned to each room, no one resident would rather switch to another room at the assigned prices. This important property ensures that nobody feels like someone else is getting a better deal than they themselves are.</p>
<p>While the result is mathematically satisfying, it does lead to some interesting psycological consequences. Throughout the process, some of my roommates expressed that a proposed division wasn’t fair because roommate X’s rent for room Y “too low.” Envy-free divisions present an easy response to such assertions, though: “wanna trade?”. Every time this objection arose, the objector declined to make the trade. The rent that was supposedly too low was not, it seemed, low enough to entice the objector to snap up a great bargain—they were happier where they were.</p>
<p>In other words, the determination that roommate X’s rent was “too low” was based on assumptions about what they and every one else <em>should</em> value by the determination of the objector, with the convenient exclusion, of couse, of the objector themselves.</p>
<hr />
<p>Joe Biden’s coronavirus stimulus package that was <a href="https://www.cbsnews.com/news/biden-minimum-wage-proposal-15-dollars/">unveiled this week</a> includes, among other things, a proposal to raise the federal minimum wage to $15 an hour. The proposed dramatic increase has led to another round of minimum wage discourse across the internet, with no shortage of people weighing in on what they think a fast food worker, waiter, or other service worker “deserves” to earn for their labor. There’s a not-insignificant portion of the population who vehimently maintain that this so-called “unskilled” labor is not worth $15 per hour.</p>
<p>It’s always struck me that these attacks on a higher minimum wage suffer from a severe lack of people putting their money where their mouth is, so to speak. I highly doubt that most people who claim a $15 hourly wage is too high for fast food workers would willingly work the job for $45 per hour, let alone $15. For whom, then, is the wage “too high”?</p>
<p>One <a href="https://twitter.com/lasrina/status/1350516182277894145">defense</a> of a $15 minimum hourly wage that I read attempted to assuage the concerns of workers already earning $15 per hour who would suddenly find themselves being paid the same as so-called “unskilled” laborers. This defense touted the increased leverage that such workers would have if they were able to tell their boss “give me a raise or I’ll go get the same wage at Arby’s.”</p>
<p>Leverage is, in my experience, all too often left out of lay-discussions surrounding minimum wage, and even the labor market at large. However, the minimum wage treats only a symptom of the fundamental issue with a large portion of the labor market: there exists a dizzying leverage imbalance in favor of employers that dramatically suppresses wages, and raising the minimum wage alone cannot correct for the disparity.</p>
<hr />
<p>When the pandemic disrupted the global workforce early in 2020, many tech companies were well-positioned to have their employees pivot to an all-remote workflow. Some of the biggest names in Silicon Valley quickly announced that employees would not be expected to return to headquarters until <a href="https://www.wsj.com/articles/google-to-keep-employees-home-until-summer-2021-amid-coronavirus-pandemic-11595854201">mid-2021</a>, or <a href="https://www.forbes.com/sites/jackkelly/2020/05/13/twitter-ceo-jack-dorsey-tells-employees-they-can-work-from-home-forever-before-you-celebrate-theres-a-catch/?sh=53d8814a2e91">ever</a>. Some employees, disillusioned with exploding rents and homogenous culture, took this opportunity to entertain fantasies of an escape from the Bay Area to buy a large estate in the middle of the country at a fraction of the price of a modest townhouse in San Francisco.</p>
<p>Some executives, though, quickly put a damper on these dreams. Facebook <a href="https://www.ft.com/content/1c52a7a2-aa65-11ea-abfc-5d8dc4dd86f9">warned</a> employees that those who relocated would potentially face adjustments to their salaries come January 2021. A spokesperson called this “a market-based approach to compensation,” and emphasized that it had always been common practice—it was just more visible now due to the large number of employees wanting to work remotely for an extended period.</p>
<p>In discussions online about Facebook and other companies’ decisions to adjust compensation based on location, some commenters were baffled. If employees were doing the same work for the same employer, how could their labor suddenly be worth only, say, 60% of what it was before? The answer, of course, is that employees have never been paid based solely on what their labor is “worth” (i.e., the actual value produced by the labor). In a salary negotiation between an employee and an employer, the employer wants to pay the employee the lowest wage at which they will actually perform the job. The employee, on the other hand, wants to be paid the highest wage at which the employer will still want to hire them. The space between these two numbers, if the latter is higher than the former, is the set of possible outcomes from a salary negotiation.</p>
<p>Assuming perfect rationality, the actual worth of the employee’s labor sets the ceiling on the negotiation, since an employer would not hire an employee at a wage higher than the value the employee produces. But the lower end of the “acceptable range” is determined by the employee’s ability and propensity to walk away from an unacceptable offer. If an employee has, say, an offer from another company that pays 10% more, they have incredibly good leverage for securing a raise: if their employer refuses, the employee can put in their two weeks and accept the alternative offer.</p>
<p>On the other hand, an employee without a competing offer has comparatively weak leverage. If their employer denies them a raise, they <em>could</em> quit and search for a new job, but that’s risky for the employee and so it’s a safer bet for the employer that the employee will begrugingly accept the decision and move on.</p>
<p>When an employee moves to a new job market, their leverage changes. Their next best alternatives to their current employment are no longer part of the same market as the employer. Instead, the employer is only competing against companies willing to hire a remote employee (which are much fewer in number) and companies in the employees local market. If the local companies offer lower pay, then the employer can reduce the employees salary without fear that they will leave for another job. “Cost of living” offers a neat justification for the salary adjustment, but it really comes down to negotiating power.</p>
<hr />
<p>How much would you pay each day to have something to eat? Not how much you currently budget for, or how much you’d ideally <em>like</em> to pay, but assuming someone has held the world’s food supply hostage and is demanding your last, best offer for them to provide you with a regular food supply.</p>
<p>The answer for most people is likely “everything I can spare.” After all, the decision here is life-or-death. A malicious actor could extract every last cent from you if they held the keys to your food. The same goes for water, housing, and (to a certain extent) healthcare. It’s nearly impossible to put any kind of reasonable price on these because people would willingly give up everything else in order to secure them.</p>
<p>For many people, <em>all</em> of these life-sustaining essentials are <em>directly</em> tied to their continued employment. One missed paycheck could mean eviction, starvation, lapsed prescriptions, or death. What sort of leverage does an employee in such a position have? Effectively, none. Employees will be forced to accept <em>any</em> wage which allows them to scrape together enough to survive, even if it means working multiple jobs, skipping meals, and accepting severe mistreatment. It <em>does not matter</em> what the value of the employee’s labor is, because that only sets the <em>ceiling</em> on the acceptable range, and the employee is always forced to the floor.</p>
<p>So what, then, makes so-called “skilled” labor pay so much better? As a tech employee, it would be difficult for me to be <em>more</em> of a cliche than I already am. I grew up in Silicon Valley, raised by parents who worked in tech themselves. I learned to program as a hobby throughout grade school, and came into college (paid for by my parents) already having several years of experience in computer science. I took internships each summer and secured a well-paying job before I even graduated. My experience is not at all atypical of the millions of employees entering the so-called “skilled” work force every year.</p>
<p>But these employees are not commanding higher pay because their labor is more valuable. No, it is because overwhelmingly, they have much better <em>leverage</em>. If I had turned down an offer before graduation, the downside for me was not catastrophic. Previous internships meant that I had not-insignificant savings to bridge any employment gap that arose. Parent-funded education meant that I had no significant debt to worry about urgently. Familial wealth meant that even in the worst case I could move back home and live on my parents’ dime for the forseeable future.</p>
<p>In other words, I had a safety net that empowered me to turn down any job offer I deemed to be unacceptable, and the vast majority of my peers were in the same position.</p>
<p>“Skilled” labor is paid better not because the labor itself is more valuable but because the positions are traditionally filled by people from privileged backgrounds with safety nets, i.e., people who can afford to walk away. Furthermore, having a critical mass of high-leverage employees lifts the entire industry. It’s simply not tenable at large to pay one engineer minimum wage while another makes six figures (though, as the racial pay gap indicates, the effect is not entirely erased by industry trends).</p>
<p>In industries with a critical mass of low-leverage employees (so-called “unskilled” labor), the situation is entirely different. Employees cannot afford to walk away, and so instead must discount their labor by an amount equivalent to their desire not to starve, become homeless, etc. An employer can extract every bit of surplus wage from an employee, right up until the point that the job itself becomes more of a threat to the employee’s life (e.g., because of physical danger or mental health impact) than the lack of any income at all. And as a result, the employee is left with no extra money that they could use to break out of the cycle in the future—they cannot save, cannot generate familial wealth, and may have to take on debt just to survive.</p>
<p>The result for the bulk of society is, yes, lower prices. But the prices are lower because employers are exploiting people’s fundamental vulnerability in ways that would be criminal if it weren’t one step removed. If employers were physically withholding food, shelter, and medical care from their employees, it would not be controversial to say “hey, maybe things shouldn’t be like this.”</p>
<hr />
<p>The solution to the fundamental issue here is not a higher minimum wage. There is some logic to the now well-worn response to a minimum wage hike, “if you raise the minimum wage then employers will have to fire people to stay within budget.” The labor market is not immune to the laws of supply and demand, so demand will of course be reduced <em>some</em> if prices increase.</p>
<p>I won’t dissect this objection at length other than to say it’s not a clear-cut argument gainst minimum wage hikes. But the only reason it’s an argument against minimum wage hikes <em>at all</em> is for the same reason that so-called “unskilled” labor pays less to begin with: vast portions of the labor market depend on jobs for survival.</p>
<p>If we, as a society, provided a comprehensive social safety net, there would be no reason for pearl-clutching about layoffs and job automation. If housing, food, and healthcare were a matter of right, it would no longer be a death risk to lose one’s job. A higher minimum wage is undoubtedly a step in the right direction, but it does nothing to help those who are being exploited regardless of wage (e.g., those who are being verbally or physically abused) nor does it help people who are unable to hold stable employment (e.g., because of medical conditions or disabilities).</p>
<p>Let’s strive for an economy that is not built on the backs of workers who are negotiating with a gun to their head. Wouldn’t you feel better knowing that nothing you buy has depended on the laborers that are only working under threat of death?</p>Frederick Kellison-LinnThe following are some disorganized thoughts I’ve had regarding the recent wave of minimum wage discourse. They are not well-researched and are highly opinionated. To the extent that anything I’ve written here is factually incorrect, I invite you to send me an email. If you simply disagree with the subjective content, email me all the same. :)My favorite Swift Forums thread2020-12-28T13:07:00-05:002020-12-28T13:07:00-05:00https://www.jumhyn.com/swift/forums/2020/12/28/my-favorite-swift-forums-thread<p>I’ve been a regular participant on the Swift Forums since the language was originally open-sourced in 2015. This was back when the forums weren’t even the forums yet—there were only mailing lists.</p>
<p>In those five years, there are only a few posts that have really stuck in my memory, and only one that stands out from the others. It’s an evolution thead from April of 2018 titled <a href="https://forums.swift.org/t/even-and-odd-integers/11774">Even and Odd Integers</a>. The proposal is a straightforward one: introduce <code class="highlighter-rouge">isEven</code> and <code class="highlighter-rouge">isOdd</code> convenience properties on <code class="highlighter-rouge">BinaryInteger</code>. The prospect was quickly raised that <code class="highlighter-rouge">isEven</code> and <code class="highlighter-rouge">isOdd</code> are not sufficiently general, and that this API should take the generalized form <code class="highlighter-rouge">isDivisible(by:)</code>.</p>
<p>The reason this thread claims the ‘favorite’ title for myself begins at <a href="https://forums.swift.org/t/even-and-odd-integers/11774/58">post #58</a>. <a href="https://davedelong.com">Dave DeLong</a> suggested that <code class="highlighter-rouge">isDivisible(by:)</code> could be a misleading name—after all, any integer is technically divisble by another non-zero integer, the quotient just might not be an integer itself. A better name, Dave said, would be <code class="highlighter-rouge">isEvenlyDivisible(by:)</code>.</p>
<p>What followed this humble bikeshed was dozens of comments debating the spelling and precise semantics of the <code class="highlighter-rouge">isDivisible(by:)</code> API. Does “divisible” imply “evenly divisible” when we know we’re dealing with integers? Should <code class="highlighter-rouge">x.isDivisible(by: 0)</code> be <code class="highlighter-rouge">false</code>, or should it crash at runtime? What about <code class="highlighter-rouge">0.isDivisible(by: 0)</code>? Shouldn’t <code class="highlighter-rouge">x.isDivisible(by: y)</code> imply that <code class="highlighter-rouge">x / y</code> will always succeed?</p>
<p>This debate continued for nearly fifty posts until user <a href="https://forums.swift.org/u/dany_st-amant">Dany_St-Amant</a> <a href="https://forums.swift.org/t/even-and-odd-integers/11774/107">suggested</a> that the API be named <code class="highlighter-rouge">isMultiple(of:)</code> rather than <code class="highlighter-rouge">isDivisible(by:)</code>:</p>
<blockquote>
<p>Silly question based on one of the examples in one copy of the proposal.</p>
<blockquote>
<p>_sanityCheck(bytes > 0 && bytes.isDivisible(by: 4), “capacity must be multiple of 4 bytes”</p>
</blockquote>
<p>Is the name isDivisible(by:) derived from its implementation or its usage? Not sure if I’m alone in this position but when using the implementation of isDivisible(by:), I’m mainly looking for multiples of a specific number.</p>
<p>So would it better to use isMultiple(of:)?</p>
</blockquote>
<p>Following along with this thread contemporaneously, Dany’s post felt like a mic-drop moment. A simple name change provided obvious answers to all the questions that had dominated the preceding posts. Of <em>course</em> “multiple” implies even divisibility. Of <em>course</em> <code class="highlighter-rouge">x.isMultiple(of: 0)</code> should be <code class="highlighter-rouge">false</code> (unless <code class="highlighter-rouge">x</code> is <code class="highlighter-rouge">0</code>, in which case it should be <code class="highlighter-rouge">true</code>!). Of <em>course</em> <code class="highlighter-rouge">x.isMultiple(of: y)</code> doesn’t necessarily imply that <code class="highlighter-rouge">x / y</code> will succeed. The discussion immediately converged on <code class="highlighter-rouge">isMultiple(of:)</code> as the ‘proper’ spelling for this API.</p>
<p>This thread wasn’t elevated to my favorite simply because of an elegant solution to a long debate, though. It’s claimed that postion because the ultimate solution was so completely accessible, and presented so humbly. Anyone, at <em>any</em> level of experience with Swift, could have come up with <code class="highlighter-rouge">isMultiple(of:)</code>, and there were <em>many</em> highly intelligent individuals participating in the debate over the semantics of <code class="highlighter-rouge">isDivisible(by:)</code>. Dany began their post “[s]illy question,” not realizing that they were providing the perfect solution that everyone had been searching for.</p>
<p>(Note: I don’t mean to imply that Dany_St-Amant is an inexperienced Swift user by any means. I don’t know Dany and have no idea what their level of experience is, which is my whole point! The <code class="highlighter-rouge">isMultiple(of:)</code> naming <em>could have</em> come from someone at any level of experience, high or low.)</p>
<p>I’m not exaggerating when I say that this thread (and Dany’s post specifically) has been one of the the most important moments for combatting my own feelings of impostor syndrome when participating on the Swift forums. The lessons are multiple, and applicable beyond the forums themselves:</p>
<ul>
<li>Seemingly obvious solutions are often not so obvious in practice.</li>
<li>Just because there are smart(er) people in the room, does not mean that they’ve thought of everything.</li>
<li>Everyone, even those less experienced than yourself, may have valuable contributions simply by virtue of being a different person with a different thought process.</li>
<li>Even the best ideas can (and should!) be communicated humbly.</li>
</ul>
<p>To those that feel less expereinced or less qualified to participate in technical discussions, let Dany’s post reassure you that you are still able to provide extremely valuable perspective. To those that are more experienced or often feel like the smartest one in the room, let Dany’s post convince you that everybody’s perspective should be solicited and considered—you likely haven’t thought of everything.</p>Frederick Kellison-LinnI’ve been a regular participant on the Swift Forums since the language was originally open-sourced in 2015. This was back when the forums weren’t even the forums yet—there were only mailing lists.Debugging the Swift compiler2020-12-16T19:50:00-05:002020-12-16T19:50:00-05:00https://www.jumhyn.com/2020/12/16/debugging-the-swift-compiler<p>A few weeks ago, one of my coworkers came across a bug in the Swift compiler. The bug is filed as <a href="https://bugs.swift.org/browse/SR-13815">SR-13815</a> and deals with a strange interaciton between optionals and implicit member expressions.</p>
<p>Another coworker asked how exactly I went about tracking down the root cause of this bug, so I decided to write up a summary of my process!</p>
<h2 id="the-bug">The bug</h2>
<p>The following is a minimal reproducer for the bug, stripped of any dependence on external libraries like SwiftUI (which is where the bug was originally noticed):</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="kd">extension</span> <span class="kt">Optional</span> <span class="p">{</span>
<span class="kd">func</span> <span class="nf">foo</span><span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="kt">Int</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">}</span>
<span class="kd">struct</span> <span class="kt">S</span> <span class="p">{}</span>
<span class="kd">extension</span> <span class="kt">S</span> <span class="p">{</span>
<span class="kd">static</span> <span class="k">let</span> <span class="nv">foo</span><span class="p">:</span> <span class="kt">S</span><span class="p">?</span> <span class="o">=</span> <span class="kt">S</span><span class="p">()</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nf">takesOptS</span><span class="p">(</span><span class="nv">_</span><span class="p">:</span> <span class="kt">S</span><span class="p">?)</span> <span class="p">{}</span>
<span class="nf">takesOptS</span><span class="p">(</span><span class="o">.</span><span class="n">foo</span><span class="p">)</span> <span class="c1">// Error: Value of optional type 'S?' must be unwrapped to a value of type 'S'</span></code></pre></figure>
<p>The error message here is clearly nonsensical. After all, <code class="highlighter-rouge">takesOptS</code> accepts an argument of type <code class="highlighter-rouge">S?</code>, so unwrapping should be completely unnecessary. The presence of the <code class="highlighter-rouge">foo(_:)</code> function in our <code class="highlighter-rouge">Optional</code> extension hints at the cause, but imagine this were defined in a different file somewhere. The error message doesn’t mention <code class="highlighter-rouge">foo(_:)</code> at all, so at first glance it’s very hard to understand what’s going on.</p>
<h2 id="digging-in">Digging in</h2>
<p>So, how should we approach this issue? Thankfully, the Swift compiler has some built-in debugging functionality to understand errors like the one above. Generally, anything that deals with type mismatches comes out of some failure during type-checking, which means that we can use the <code class="highlighter-rouge">-debug-constraints</code> flag to figure out what’s going on.</p>
<p>Using the release version of the compiler, we can invoke just the type checker with debug output enabled with the following command:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ swiftc -Xfrontend -typecheck -Xfrontend -debug-constraints /path/to/file.swift
</code></pre></div></div>
<p>The output from this command is pretty dense, but it summarizes the whole process of type-checking the specified file. You can identify the particular expression via the headings:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>---Constraint solving at [/path/to/file.swift:<line>:<col> - line:<line>:<col>]---
</code></pre></div></div>
<p>In our case, we’re looking for an expression on line 13, and indeed:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>---Constraint solving at [/path/to/file.swift:13:1 - line:13:12]---
</code></pre></div></div>
<p>The actual output for this expressions contains every type that the compiler attempts to assign to various parts of this expression. That means that if we want to figure out where it’s going wrong, we should have an idea of the expected type of each part of the expression in question. Let’s look at it again:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="nf">takesOptS</span><span class="p">(</span><span class="o">.</span><span class="n">foo</span><span class="p">)</span></code></pre></figure>
<p>Luckily for us, this expression is pretty simple. There’s really just four parts that we want to think about. Let’s proceed in a ‘top-down’ manner:</p>
<ol>
<li>The type of the entire expression, that is, the result of calling <code class="highlighter-rouge">takesOptS</code>. This should clearly be <code class="highlighter-rouge">()</code>.</li>
<li>The type of the function <code class="highlighter-rouge">takesOptS</code>. There’s no fancy overloading going on, so this is also straightforward. It should be <code class="highlighter-rouge">(S?) -> ()</code>.</li>
<li>The type of the implicit member expression <code class="highlighter-rouge">.foo</code>. We <em>want</em> this to refer to <code class="highlighter-rouge">S.foo</code> if everything is working properly, so it <em>should</em> have type <code class="highlighter-rouge">S?</code>.</li>
<li>The type of the implicit base of <code class="highlighter-rouge">.foo</code>. Again, since we want <code class="highlighter-rouge">.foo</code> to refer to <code class="highlighter-rouge">S.foo</code>, the type of the base should be <code class="highlighter-rouge">S</code>.</li>
</ol>
<p>For each of these sub-expressions, the compiler creates a “type variable” for which it will try different type substitutions until it finds an assignment for all type variables that works. The next lines of the output shows the type variable assignments (edited to remove noise):</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(call_expr type='()' arg_labels=_:
(declref_expr type='(S?) -> ()' decl=takesOptS)
(paren_expr type='($T3)'
(unresolved_member_expr type='$T3' name='foo')))
</code></pre></div></div>
<p>(Note: the compiler calls implicit member expressions “unresolved member expressions”)</p>
<p>We can see right off the bat that the result of the call and the type of the function reference <code class="highlighter-rouge">takesOptS</code> have already been resolved to <code class="highlighter-rouge">()</code> and <code class="highlighter-rouge">(S?) -> ()</code>, just as we expected. Great!</p>
<p>We can also see that the implicit member expression <code class="highlighter-rouge">.foo</code> has been assigned type <code class="highlighter-rouge">$T3</code>. This is how the debug output represents a type variable with ID 3. The ID has no effect except providing a unique name for the type variable.</p>
<p>Because it isn’t written in the source, the type of the implicit base doesn’t get printed here. But if we look just below to the list of all type variables, we can see the following entry:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$T1 [noescape allowed] potentially_incomplete involves_type_vars #defaultable_bindings=1 bindings={<<unresolvedtype>>} [UnresolvedMember -> member reference base]
</code></pre></div></div>
<p>We don’t need to be concerned with most of this output—the only important part is <code class="highlighter-rouge">UnresolvedMember -> member reference base</code>, meaning that this type variable represents the base of the implicit member expression.</p>
<p>Okay great! So now, with our knowledge of the type variables involved and the expected values for each, we can analyze the output further. It seems that the error message is having trouble with the type of the <code class="highlighter-rouge">.foo</code> expression, so let’s look for output involving <code class="highlighter-rouge">$T3</code>:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(attempting type variable $T3 := S?
($T2 involves_type_vars bindings={(subtypes of) S?})
(attempting disjunction choice $T2 bound to decl bug.(file).Optional extension.foo
(overload set choice binding $T2 := (S?) -> (Int) -> ())
(increasing score due to value to optional)
(failed constraint $T2 conv $T3)
)
(skipping disjunction choice $T2 bound to decl bug.(file).Optional extension.foo [fix: allow access to instance member on type or a type member on instance])
)
(attempting type variable $T3 := S
(increasing score due to value to optional)
(overload set choice binding $T2 := S?)
(failed constraint $T2 conv $T1;)
)
</code></pre></div></div>
<p>(Note: <code class="highlighter-rouge">$T2</code> here also corresponds to the type of the expression <code class="highlighter-rouge">.foo</code>—because of the way the constraints get generated, <code class="highlighter-rouge">$T2</code> is basically “the type of the member”, and <code class="highlighter-rouge">$T3</code> is “the type of the overall expression. This allows certain conversions to take place that improve the ergonomics of implicit member expressions, but is mostly unimportant here.)</p>
<p>Interestingly, when <code class="highlighter-rouge">$T3</code> is substituted out for the “correct” type <code class="highlighter-rouge">S?</code>, the compiler only attempts to match <code class="highlighter-rouge">foo</code> with <code class="highlighter-rouge">Optional.foo(_:)</code>, the method defined in our extension. It’s only when we attempt <code class="highlighter-rouge">$T3 := S</code> that the compiler finds the expected member, <code class="highlighter-rouge">S.foo</code>.</p>
<p>So now, we know that this issue has something to do with how the compiler is looking up the <code class="highlighter-rouge">.foo</code> member. To debug further, we need to look at the compiler’s code itself.</p>
<h2 id="the-constraintsystem">The <code class="highlighter-rouge">ConstraintSystem</code></h2>
<p>All of the type checking happens within a component of the compiler known as the <code class="highlighter-rouge">ConstraintSystem</code>. This class is responsible for creating all the type variables for the expression, generating “constraints” between the type variables based on the expected relationships, and then “solving” the system by finding an assignment of types to type variables that satisfy all constraints. For instance, an expression like:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>S.foo
</code></pre></div></div>
<p>Would generate a constraint that says “the type referred to by the name <code class="highlighter-rouge">S</code> must have a member named <code class="highlighter-rouge">foo</code> with type _” (where the desired type is determined by context).</p>
<p>This type of constraint is called a “value member” constraint, and is represented by the constant <code class="highlighter-rouge">ConstraintKind::ValueMember</code>. There is also a corresponding “unresolved value member” constraint and <code class="highlighter-rouge">ConstraintKind::UnresolvedValueMember</code> constant.</p>
<p>With some preexisting knowledge of the Swift compiler (or some debugging work), we can track down the fact that the actual name lookup for both of these constraint kinds happen in a method called <code class="highlighter-rouge">ConstraintSystem::simplifyMemberConstraint</code>. When constraints are “simplified,” they are converted into simpler sets of constraints (or eliminated entirely) based on other context that has been filled in previously. For instance, with an implicit member expression like <code class="highlighter-rouge">.foo</code>, we can’t simplify the constraint until we know what type is supposed to be filled in the implicit base.</p>
<p>Once that context has been filled in, though, we can proceed with simplification. Looking through the source of <code class="highlighter-rouge">simplifyMemberConstraint</code>, we can find the following line which looks interesting:</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"> <span class="n">MemberLookupResult</span> <span class="n">result</span> <span class="o">=</span>
<span class="n">performMemberLookup</span><span class="p">(</span><span class="n">kind</span><span class="p">,</span> <span class="n">member</span><span class="p">,</span> <span class="n">baseTy</span><span class="p">,</span> <span class="n">functionRefKind</span><span class="p">,</span> <span class="n">locator</span><span class="p">,</span>
<span class="cm">/*includeInaccessibleMembers*/</span> <span class="n">shouldAttemptFixes</span><span class="p">());</span></code></pre></figure>
<p>Indeed, this is the point at which the compiler actually looks into the base type to find a member named <code class="highlighter-rouge">foo</code>. Opening the source for this method, we can see that it’s very (very) long, but if we know what we’re looking for we don’t have to analyze every single line.</p>
<h3 id="aside">Aside</h3>
<p>There’s something a bit strange going on with the implicit member expression. Conceptually, an implicit member expression like <code class="highlighter-rouge">.foo</code> does the following:</p>
<ol>
<li>Inherits the expected type from the surrounding context.</li>
<li>Substitutes that type at the base of the member reference.</li>
<li>Looks for the (static) named member in the base with the correct type (same as the base).</li>
</ol>
<p>That is, if we write <code class="highlighter-rouge">.member</code> in a location where the compiler expects a type <code class="highlighter-rouge">T</code>, that should basically be the same as writing <code class="highlighter-rouge">T.member</code>.</p>
<p>However, that doesn’t really account for what’s going on with an implicit member expression involving optionals. For instance, if we write:</p>
<figure class="highlight"><pre><code class="language-swift" data-lang="swift"><span class="k">let</span> <span class="nv">image</span><span class="p">:</span> <span class="kt">UIImage</span><span class="p">?</span> <span class="o">=</span> <span class="o">.</span><span class="nf">init</span><span class="p">(</span><span class="nv">named</span><span class="p">:</span> <span class="s">"myImage"</span><span class="p">)</span></code></pre></figure>
<p>Then both the contextual type and the result type of the <code class="highlighter-rouge">.init</code> implicit member expression have type <code class="highlighter-rouge">UIImage?</code>, but we can’t just replace the implicit member expression with <code class="highlighter-rouge">UIImage?.init(named:</code> <code class="highlighter-rouge">"</code><code class="highlighter-rouge">myImage")</code>, because <code class="highlighter-rouge">Optional<UIImage></code> has no <code class="highlighter-rouge">init(named:)</code> initializer.</p>
<p>Indeed, implicit member expressions have a little extra magic. For an expression <code class="highlighter-rouge">.member</code> with contextual type <code class="highlighter-rouge">T</code>, if <code class="highlighter-rouge">T</code> is an optional type <code class="highlighter-rouge">U?</code>, then the compiler will look for <code class="highlighter-rouge">member</code> in <code class="highlighter-rouge">U</code> as well as in <code class="highlighter-rouge">T</code>. However, based on the <code class="highlighter-rouge">-debug-constraints</code> output, it looks like that’s not happening. The compiler never attempts the <code class="highlighter-rouge">foo</code> member that it should find in <code class="highlighter-rouge">S</code>—it only attempts to use <code class="highlighter-rouge">Optional.foo(_:)</code>.</p>
<h3 id="back-to-work">Back to work</h3>
<p>Armed with this knowledge of the interaction between optional types and implicit member expressions, we have a plausible guess for what’s happening: the compiler is looking for the <code class="highlighter-rouge">foo</code> member in <code class="highlighter-rouge">Optional<S></code>, finding <code class="highlighter-rouge">Optional.foo(_:)</code>, and not continuing look into <code class="highlighter-rouge">S</code> to find <code class="highlighter-rouge">S.foo</code>.</p>
<p>Let’s look a little closer at the <code class="highlighter-rouge">ConstraintSystem::performMemberLookup</code> method. The first interesting line is:</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"> <span class="c1">// Look for members within the base.</span>
<span class="n">LookupResult</span> <span class="o">&</span><span class="n">lookup</span> <span class="o">=</span> <span class="n">lookupMember</span><span class="p">(</span><span class="n">instanceTy</span><span class="p">,</span> <span class="n">memberName</span><span class="p">);</span></code></pre></figure>
<p>This gives us the initial lookup results in the specified type. If we set a breakpoint here, run the compiler, and <code class="highlighter-rouge">expr instanceTy->dump()</code>, we get output which indicates that we’re performing lookup on the type <code class="highlighter-rouge">Optional<S></code>. Great, just as expected! Inspecting the results of this call indicates that the compiler finds the <code class="highlighter-rouge">foo(_:)</code>, again, as expected.</p>
<p>Several hundred lines further down, we find the following lines:</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"> <span class="c1">// If we're looking into a metatype for an unresolved member lookup, look</span>
<span class="c1">// through optional types.</span></code></pre></figure>
<p>This comment describes exactly the behavior we’re looking for. Let’s see how the compiler determines whether to do this lookup:</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"> <span class="k">if</span> <span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">ViableCandidates</span><span class="p">.</span><span class="n">empty</span><span class="p">()</span> <span class="o">&&</span>
<span class="n">baseObjTy</span><span class="o">-></span><span class="n">is</span><span class="o"><</span><span class="n">AnyMetatypeType</span><span class="o">></span><span class="p">()</span> <span class="o">&&</span>
<span class="n">constraintKind</span> <span class="o">==</span> <span class="n">ConstraintKind</span><span class="o">::</span><span class="n">UnresolvedValueMember</span><span class="p">)</span> <span class="p">{</span></code></pre></figure>
<p>Bingo! In addition to checking that the constraint we’re performing lookup for is from an implicit member expression, and that the type we’re looking into is a metatype (remember: implicit member expressions only work for static members), we’re <em>also</em> checking whether there are already any results. Since lookup previously found <code class="highlighter-rouge">Optional.foo(_:)</code>, we don’t proceed with lookup into <code class="highlighter-rouge">S</code>, and so our expression doesn’t type check.</p>
<h3 id="conclusion">Conclusion</h3>
<p>I hope this gave some insight into approaches that you can use to attack bugs found in the Swift compiler. I eventually fixed this issue with <a href="https://github.com/apple/swift/pull/34715">this PR</a>, but explaining the fix is a topic for another post…</p>Frederick Kellison-LinnA few weeks ago, one of my coworkers came across a bug in the Swift compiler. The bug is filed as SR-13815 and deals with a strange interaciton between optionals and implicit member expressions.