Integrating SwiftUI with UIKit

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

To work with UIViews and UIViewControllers in SwiftUI, you must create types that conform to the UIViewRepresentable and UIViewControllerRepresentable protocols. SwiftUI will manage these views’ life cycles, so you only need to create and configure the views. The underlying frameworks will take care of the rest. In this segment, you’ll make a view that shows a map with the starting and ending locations of each flight, with a line indicating the path of an airplane between the cities.

The SwiftUI MapKit view lets you add a MapPolyline, which could draw a straight line connecting the locations. Mapping the near-spherical Earth onto a flat display adds some distortion. Over short distances, this distortion is barely noticeable but over large distances, such as between distant cities, the difference becomes more relevant. The direct path between distant locations in the real world appears on a flat map as a curve instead of a straight line. This curved shape more accurately represents the path an airplane would take.

SwiftUI doesn’t provide a way to draw this curved shape on a Map view, but the MapKit UIKit framework can. That makes integrating the MapKit view into your SwiftUI app a good approach to adding this functionality.

Create a Swift file — not a SwiftUI view — named FlightMapView.swift in the Timeline group.

Replace the contents of FlightMapView.swift with:

import SwiftUI
import MapKit

struct FlightMapView: UIViewRepresentable {
  var startCoordinate: CLLocationCoordinate2D
  var endCoordinate: CLLocationCoordinate2D
  var progress: Double
}

This code imports both SwiftUI and the MapKit framework for this file. Instead of the standard SwiftUI view, you define a type that implements the UIViewRepresentable protocol with three properties. Two hold CLLocationCoordinate2Ds, a structure that stores the latitude and longitude associated with a location. You’ll store the starting and ending coordinates to display on the map there. The other property will hold a Double, representing the flight’s progress using a value between zero and one.

The compiler will complain that your type doesn’t conform to protocol. You must implement two methods for the UIViewRepresentable protocol: makeUIView(context:), and updateUIView(_:context):).

Add the following code to the struct below the progress property:

func makeUIView(context: Context) -> MKMapView {
  return MKMapView(frame: .zero) 
}

This code implements the UIViewRepresentable protocol. SwiftUI will call makeUIView(context:) only once when it creates your UIKit view for the first time. For now, you create an MKMapView programmatically and return it. Any UIKit view would work here, and you’ll see a more complex setup later in this lesson.

Now, add this code to the end of the struct to implement the second method:

func updateUIView(_ view: MKMapView, context: Context) {
  // 1
  let startPoint = MKMapPoint(startCoordinate)
  let endPoint = MKMapPoint(endCoordinate)

  // 2
  let minXPoint = min(startPoint.x, endPoint.x)
  let minYPoint = min(startPoint.y, endPoint.y)
  let maxXPoint = max(startPoint.x, endPoint.x)
  let maxYPoint = max(startPoint.y, endPoint.y)

  // 3
  let mapRect = MKMapRect(
    x: minXPoint,
    y: minYPoint,
    width: maxXPoint - minXPoint,
    height: maxYPoint - minYPoint
  )
  // 4
  let padding = UIEdgeInsets(
    top: 10.0,
    left: 10.0,
    bottom: 10.0,
    right: 10.0
  )
  // 5
  view.setVisibleMapRect(
    mapRect,
    edgePadding: padding,
    animated: true
  )
  // 6
  view.mapType = .mutedStandard
  view.isScrollEnabled = false
}

SwiftUI calls updateUIView(_:context:) to let you update the presented view’s configuration. Much of the setup you typically do in viewDidLoad() in a UIKit view will instead go into this method. For the moment, here’s how you define the map to show.

  1. Some distortion occurs when you project the Earth’s curved surface onto a flat surface, such as a device screen. You convert the starting and ending coordinates on the globe to MKMapPoint values in the flattened map. Using MKMapPoint simplifies the calculations to follow.
  2. Next, you determine the minimum and maximum x and y values among these points.
  3. You create an MKMapRect from those minimum and maximum values. The resulting rectangle covers the space between the two points along the rectangle’s edge.
  4. Next, you create a UIEdgeInsets struct with all sides set to an inset of 10 points.
  5. You use the setVisibleMapRect(_:edgePadding:animated:) method to set the map’s viewable area. This method uses the rectangle calculated in Step 3 as the area to show. The edgePadding adds the padding you set up in Step 4, so the airports’ locations aren’t directly at the view’s edge and, therefore, are easier to see.
  6. You set the map type to mutedStandard, emphasizing the data you’ll add to the map over the map details and turning off the user’s ability to scroll the map.

You didn’t get a preview by default because you created a Swift file and not a SwiftUI view. To add the preview, add the following code at the bottom of the file after the FlightMapView struct:

#Preview {
  FlightMapView(
    startCoordinate: CLLocationCoordinate2D(
      latitude: 35.655, longitude: -83.4411
    ),
    endCoordinate: CLLocationCoordinate2D(
      latitude: 36.0840, longitude: -115.1537
    ),
    progress: 0.67
  )
  .frame(width: 300, height: 300)
}

This code creates a preview with a frame so the preview better represents what the view will look like in your app.

Wrapped MapView
Wrapped MapView

You’ve created a MapKit view in your SwiftUI app, but it lacks functionality. In the next segment, you’ll see how the Coordinator class allows you to fill in this map.

See forum comments
Download course materials from Github
Previous: Introduction Next: Building a Coordinator Class