Implementing zoom controls for MapKit on iOS

map with zoom buttons

Buttons are usually much more accessible than gestures, so adding zoom buttons can be a great feature to add to any map. Unfortionaly, Apples MapKit does not support showsZoomControl for iOS, so we have to implement the controls ourselves.

Buttons

We start by creating two UIButtons. I'll use icons from Apples SF Symbols (plus.magnifyingglass and minus.magnifyingglass).

<span><span style="color: var(--shiki-token-keyword)">private</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">lazy</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">var</span><span style="color: var(--shiki-color-text)"> increaseZoomButton: UIButton </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> button </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">UIButton</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">withAutoLayout</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">true</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    button.backgroundColor </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> R.</span><span style="color: var(--shiki-token-constant)">color</span><span style="color: var(--shiki-color-text)">.</span><span style="color: var(--shiki-token-function)">color1</span><span style="color: var(--shiki-token-punctuation)">()</span></span>
<span><span style="color: var(--shiki-color-text)">    button.</span><span style="color: var(--shiki-token-function)">setImage</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">UIImage</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">systemName</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;plus.magnifyingglass&quot;</span><span style="color: var(--shiki-token-punctuation)">)</span><span style="color: var(--shiki-token-function)">, for</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> .normal</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    button.</span><span style="color: var(--shiki-token-function)">addTarget</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">self, action</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> #selector</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">increaseZoomLevel</span><span style="color: var(--shiki-token-punctuation)">)</span><span style="color: var(--shiki-token-function)">, for</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> .touchUpInside</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    button.layer.cornerRadius </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">4</span></span>
<span><span style="color: var(--shiki-color-text)">    button.layer.borderColor </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> R.</span><span style="color: var(--shiki-token-constant)">color</span><span style="color: var(--shiki-color-text)">.</span><span style="color: var(--shiki-token-function)">color2</span><span style="color: var(--shiki-token-punctuation)">()</span><span style="color: var(--shiki-token-keyword)">?</span><span style="color: var(--shiki-color-text)">.cgColor</span></span>
<span><span style="color: var(--shiki-color-text)">    button.layer.borderWidth </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">1</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> button</span></span>
<span><span style="color: var(--shiki-color-text)">}</span><span style="color: var(--shiki-token-punctuation)">()</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">private</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">lazy</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">var</span><span style="color: var(--shiki-color-text)"> decreaseZoomButton: UIButton </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> button </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">UIButton</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">withAutoLayout</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">true</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    button.backgroundColor </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> R.</span><span style="color: var(--shiki-token-constant)">color</span><span style="color: var(--shiki-color-text)">.</span><span style="color: var(--shiki-token-function)">color1</span><span style="color: var(--shiki-token-punctuation)">()</span></span>
<span><span style="color: var(--shiki-color-text)">    button.</span><span style="color: var(--shiki-token-function)">setImage</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">UIImage</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">systemName</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-string-expression)">&quot;minus.magnifyingglass&quot;</span><span style="color: var(--shiki-token-punctuation)">)</span><span style="color: var(--shiki-token-function)">, for</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> .normal</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    button.</span><span style="color: var(--shiki-token-function)">addTarget</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">self, action</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> #selector</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">decreaseZoomLevel</span><span style="color: var(--shiki-token-punctuation)">)</span><span style="color: var(--shiki-token-function)">, for</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> .touchUpInside</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    button.layer.cornerRadius </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">4</span></span>
<span><span style="color: var(--shiki-color-text)">    button.layer.borderColor </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> R.</span><span style="color: var(--shiki-token-constant)">color</span><span style="color: var(--shiki-color-text)">.</span><span style="color: var(--shiki-token-function)">color2</span><span style="color: var(--shiki-token-punctuation)">()</span><span style="color: var(--shiki-token-keyword)">?</span><span style="color: var(--shiki-color-text)">.cgColor</span></span>
<span><span style="color: var(--shiki-color-text)">    button.layer.borderWidth </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">1</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> button</span></span>
<span><span style="color: var(--shiki-color-text)">}</span><span style="color: var(--shiki-token-punctuation)">()</span></span>
<span></span>

When pressed, the buttons call the functions increaseZoomLevel and decreaseZoomLevel. We will create those functions later.

Positioning the buttons

I use autoLayout to position the buttons after adding them to the view.

<span><span style="color: var(--shiki-color-text)">view.</span><span style="color: var(--shiki-token-function)">addSubview</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">increaseZoomButton</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">view.</span><span style="color: var(--shiki-token-function)">addSubview</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">decreaseZoomButton</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)">NSLayoutConstraint.</span><span style="color: var(--shiki-token-function)">activate</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">[</span></span>
<span><span style="color: var(--shiki-token-function)">    increaseZoomButton.topAnchor.constraint</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">equalTo</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> userLocation.bottomAnchor, constant</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> .normalSpacing</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">multiplier</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">2</span><span style="color: var(--shiki-token-punctuation)">))</span><span style="color: var(--shiki-token-function)">,</span></span>
<span><span style="color: var(--shiki-token-function)">    increaseZoomButton.trailingAnchor.constraint</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">equalTo</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> view.trailingAnchor, constant</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-token-function)">.normalSpacing</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">multiplier</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">2</span><span style="color: var(--shiki-token-punctuation)">))</span><span style="color: var(--shiki-token-function)">,</span></span>
<span><span style="color: var(--shiki-token-function)">    increaseZoomButton.heightAnchor.constraint</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">equalToConstant</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">44</span><span style="color: var(--shiki-token-punctuation)">)</span><span style="color: var(--shiki-token-function)">,</span></span>
<span><span style="color: var(--shiki-token-function)">    increaseZoomButton.widthAnchor.constraint</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">equalToConstant</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">44</span><span style="color: var(--shiki-token-punctuation)">)</span><span style="color: var(--shiki-token-function)">,</span></span>
<span><span style="color: var(--shiki-token-function)">    decreaseZoomButton.topAnchor.constraint</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">equalTo</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> increaseZoomButton.bottomAnchor, constant</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> .normalSpacing</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">multiplier</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">2</span><span style="color: var(--shiki-token-punctuation)">))</span><span style="color: var(--shiki-token-function)">,</span></span>
<span><span style="color: var(--shiki-token-function)">    decreaseZoomButton.trailingAnchor.constraint</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">equalTo</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> view.trailingAnchor, constant</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-token-function)">.normalSpacing</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">multiplier</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">2</span><span style="color: var(--shiki-token-punctuation)">))</span><span style="color: var(--shiki-token-function)">,</span></span>
<span><span style="color: var(--shiki-token-function)">    decreaseZoomButton.heightAnchor.constraint</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">equalToConstant</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">44</span><span style="color: var(--shiki-token-punctuation)">)</span><span style="color: var(--shiki-token-function)">,</span></span>
<span><span style="color: var(--shiki-token-function)">    decreaseZoomButton.widthAnchor.constraint</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">equalToConstant</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">44</span><span style="color: var(--shiki-token-punctuation)">)</span><span style="color: var(--shiki-token-function)">,</span></span>
<span><span style="color: var(--shiki-token-function)">]</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span></span>

Handling button presses

To handle the button presses, I have created two functions increaseZoomLevel and decreaseZoomLevel, which both call the zoomClickHandler function with their respective direction.

<span><span style="color: var(--shiki-token-keyword)">@objc</span></span>
<span><span style="color: var(--shiki-token-keyword)">func</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">increaseZoomLevel</span><span style="color: var(--shiki-color-text)">() {</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-function)">zoomClickHandler</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">direction</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> .increase</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">@objc</span></span>
<span><span style="color: var(--shiki-token-keyword)">func</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">decreaseZoomLevel</span><span style="color: var(--shiki-color-text)">() {</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-function)">zoomClickHandler</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">direction</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> .decrease</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>

zoomClickHandler

The zoomClickHandler ensures that the user is sharing their location. It then calls the updateZoomLevel function, giving the user's current location and the requested zoom direction as arguments.

<span><span style="color: var(--shiki-token-keyword)">private</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">func</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">zoomClickHandler</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-function)">direction</span><span style="color: var(--shiki-color-text)">: Direction) {</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">if</span><span style="color: var(--shiki-color-text)"> viewModel.location.isAuthorized {</span></span>
<span><span style="color: var(--shiki-color-text)">        </span><span style="color: var(--shiki-token-function)">updateZoomLevel</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">location</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> mapView.userLocation.coordinate, direction</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> direction</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    } </span><span style="color: var(--shiki-token-keyword)">else</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)">        </span><span style="color: var(--shiki-token-function)">firstly</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)">            viewModel.location.</span><span style="color: var(--shiki-token-function)">requestAuthorization</span><span style="color: var(--shiki-token-punctuation)">()</span></span>
<span><span style="color: var(--shiki-color-text)">        }.</span><span style="color: var(--shiki-token-function)">then</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)">            self.viewModel.location.</span><span style="color: var(--shiki-token-function)">requestLocation</span><span style="color: var(--shiki-token-punctuation)">()</span></span>
<span><span style="color: var(--shiki-color-text)">        }.</span><span style="color: var(--shiki-token-function)">done</span><span style="color: var(--shiki-color-text)"> { location </span><span style="color: var(--shiki-token-keyword)">in</span></span>
<span><span style="color: var(--shiki-color-text)">            self.</span><span style="color: var(--shiki-token-function)">zoomTo</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">location</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> location.coordinate</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">        }.</span><span style="color: var(--shiki-token-function)">catch</span><span style="color: var(--shiki-color-text)"> { </span><span style="color: var(--shiki-token-constant)">_</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">in</span></span>
<span><span style="color: var(--shiki-color-text)">            self.coordinator.</span><span style="color: var(--shiki-token-function)">perform</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">action</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> .missingLocationSettings, in</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> self</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">        }</span></span>
<span><span style="color: var(--shiki-color-text)">    }</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>

Updating the zoom level

First, updateZoomLevel checks that the user's current location is valid before proceeding. It then uses a helper function to get the mapView's current location, which is then used to update the zoom level in the given direction.

We then create a new viewRegion with the updated zoom level and use mapView.setRegion to apply the changes.

<span><span style="color: var(--shiki-token-keyword)">private</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">func</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">updateZoomLevel</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-function)">location</span><span style="color: var(--shiki-color-text)">: CLLocationCoordinate2D, </span><span style="color: var(--shiki-token-function)">direction</span><span style="color: var(--shiki-color-text)">: Direction) {</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">guard</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">CLLocationCoordinate2DIsValid</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">location</span><span style="color: var(--shiki-token-punctuation)">)</span><span style="color: var(--shiki-color-text)">,</span></span>
<span><span style="color: var(--shiki-color-text)">        location.latitude </span><span style="color: var(--shiki-token-keyword)">!=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">&amp;&amp;</span><span style="color: var(--shiki-color-text)"> location.longitude </span><span style="color: var(--shiki-token-keyword)">!=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">0</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">else</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)">        </span><span style="color: var(--shiki-token-keyword)">return</span></span>
<span><span style="color: var(--shiki-color-text)">    }</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> currentLocation </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">getCurrentMapLocation</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">mapView</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> mapView</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> updatedLocation </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">getUpdatedMapLocation</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">currentLocation</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> currentLocation, direction</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> direction</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> viewRegion </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">MKCoordinateRegion</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">center</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> mapView.region.center, latitudinalMeters</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> updatedLocation.metersInLatitude, longitudinalMeters</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> updatedLocation.metersInLongitude</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    mapView.</span><span style="color: var(--shiki-token-function)">setRegion</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">viewRegion, animated</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">true</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>

Helper functions

To keep the controller clean, I have extracted a lot of the map logic to its own helper. This includes our Direction enum, a MapCoordinates struct, and the getCurrentMapLocation and getUpdatedMapLocation functions.

<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Foundation</span></span>
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">MapKit</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">enum</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Direction</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">case</span><span style="color: var(--shiki-color-text)"> increase</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">case</span><span style="color: var(--shiki-color-text)"> decrease</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">var</span><span style="color: var(--shiki-color-text)"> bool: </span><span style="color: var(--shiki-token-constant)">Bool</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)">        </span><span style="color: var(--shiki-token-keyword)">switch</span><span style="color: var(--shiki-color-text)"> self {</span></span>
<span><span style="color: var(--shiki-color-text)">        </span><span style="color: var(--shiki-token-keyword)">case</span><span style="color: var(--shiki-color-text)"> .increase</span><span style="color: var(--shiki-token-keyword)">:</span></span>
<span><span style="color: var(--shiki-color-text)">            </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">true</span></span>
<span><span style="color: var(--shiki-color-text)">        </span><span style="color: var(--shiki-token-keyword)">default:</span></span>
<span><span style="color: var(--shiki-color-text)">            </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">false</span></span>
<span><span style="color: var(--shiki-color-text)">        }</span></span>
<span><span style="color: var(--shiki-color-text)">    }</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">struct</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">MapCoordinates</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">var</span><span style="color: var(--shiki-color-text)"> metersInLatitude: </span><span style="color: var(--shiki-token-constant)">Double</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">var</span><span style="color: var(--shiki-color-text)"> metersInLongitude: </span><span style="color: var(--shiki-token-constant)">Double</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">func</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">getCurrentMapLocation</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-function)">mapView</span><span style="color: var(--shiki-color-text)">: MKMapView) </span><span style="color: var(--shiki-token-keyword)">-&gt;</span><span style="color: var(--shiki-color-text)"> MapCoordinates {</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> span </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> mapView.region.span</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> center </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> mapView.region.center</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> loc1 </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">CLLocation</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">latitude</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> center.latitude </span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-token-function)"> span.latitudeDelta </span><span style="color: var(--shiki-token-keyword)">*</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">0.5</span><span style="color: var(--shiki-token-function)">, longitude</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> center.longitude</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> loc2 </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">CLLocation</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">latitude</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> center.latitude </span><span style="color: var(--shiki-token-keyword)">+</span><span style="color: var(--shiki-token-function)"> span.latitudeDelta </span><span style="color: var(--shiki-token-keyword)">*</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">0.5</span><span style="color: var(--shiki-token-function)">, longitude</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> center.longitude</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> loc3 </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">CLLocation</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">latitude</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> center.latitude, longitude</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> center.longitude </span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-token-function)"> span.longitudeDelta </span><span style="color: var(--shiki-token-keyword)">*</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">0.5</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> loc4 </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">CLLocation</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">latitude</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> center.latitude, longitude</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> center.longitude </span><span style="color: var(--shiki-token-keyword)">+</span><span style="color: var(--shiki-token-function)"> span.longitudeDelta </span><span style="color: var(--shiki-token-keyword)">*</span><span style="color: var(--shiki-token-function)"> </span><span style="color: var(--shiki-token-constant)">0.5</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">MapCoordinates</span><span style="color: var(--shiki-token-punctuation)">(</span></span>
<span><span style="color: var(--shiki-token-function)">        metersInLatitude</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> loc1.distance</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">from</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> loc2</span><span style="color: var(--shiki-token-punctuation)">)</span><span style="color: var(--shiki-token-function)">,</span></span>
<span><span style="color: var(--shiki-token-function)">        metersInLongitude</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> loc3.distance</span><span style="color: var(--shiki-token-punctuation)">(</span><span style="color: var(--shiki-token-function)">from</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> loc4</span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-token-function)">    </span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">func</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">getUpdatedMapLocation</span><span style="color: var(--shiki-color-text)">(</span><span style="color: var(--shiki-token-function)">currentLocation</span><span style="color: var(--shiki-color-text)">: MapCoordinates, </span><span style="color: var(--shiki-token-function)">direction</span><span style="color: var(--shiki-color-text)">: Direction) </span><span style="color: var(--shiki-token-keyword)">-&gt;</span><span style="color: var(--shiki-color-text)"> MapCoordinates {</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> updatedLatitude </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> currentLocation.metersInLatitude </span><span style="color: var(--shiki-token-keyword)">+</span><span style="color: var(--shiki-color-text)"> (direction.</span><span style="color: var(--shiki-token-constant)">bool</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">?</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-color-text)">currentLocation.metersInLatitude </span><span style="color: var(--shiki-token-keyword)">*</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">0.6</span><span style="color: var(--shiki-color-text)">) </span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> (currentLocation.metersInLatitude </span><span style="color: var(--shiki-token-keyword)">*</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">2</span><span style="color: var(--shiki-color-text)">))</span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">let</span><span style="color: var(--shiki-color-text)"> updatedLongitude </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> currentLocation.metersInLongitude </span><span style="color: var(--shiki-token-keyword)">+</span><span style="color: var(--shiki-color-text)"> (direction.</span><span style="color: var(--shiki-token-constant)">bool</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">?</span><span style="color: var(--shiki-color-text)"> (</span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-color-text)">currentLocation.metersInLongitude </span><span style="color: var(--shiki-token-keyword)">*</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">0.6</span><span style="color: var(--shiki-color-text)">) </span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> (currentLocation.metersInLongitude </span><span style="color: var(--shiki-token-keyword)">*</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">2</span><span style="color: var(--shiki-color-text)">))</span></span>
<span></span>
<span><span style="color: var(--shiki-color-text)">    </span><span style="color: var(--shiki-token-keyword)">return</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">MapCoordinates</span><span style="color: var(--shiki-token-punctuation)">(</span></span>
<span><span style="color: var(--shiki-token-function)">        metersInLatitude</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> updatedLatitude,</span></span>
<span><span style="color: var(--shiki-token-function)">        metersInLongitude</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-token-function)"> updatedLongitude</span></span>
<span><span style="color: var(--shiki-token-function)">    </span><span style="color: var(--shiki-token-punctuation)">)</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>