Created
April 7, 2021 11:42
-
-
Save egzonpllana/f8b6eb2c41dd90ce9a038ee31d9298a6 to your computer and use it in GitHub Desktop.
Integration of UIKit frameworks to SwiftUI ecosystem through Coordinator.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// Copyright (c) 2021 Razeware LLC | |
/// RayWenderlich SwiftUI Book 3.0 (2021) | |
/// Egzon Pllana 07.03.2021 | |
import SwiftUI | |
import MapKit | |
// MARK: - SwiftUI Coordinator (connect delegate & protocols) | |
class MapCoordinator: NSObject { | |
var mapView: FlightMapView | |
var fraction: CGFloat | |
init( | |
_ mapView: FlightMapView, | |
progress: CGFloat = 0.0 | |
) { | |
self.mapView = mapView | |
self.fraction = progress | |
} | |
} | |
// MARK: - UIKit View -> SwiftUI View (UIViewRepresentable) | |
struct FlightMapView: UIViewRepresentable { | |
var startCoordinate: CLLocationCoordinate2D | |
var endCoordinate: CLLocationCoordinate2D | |
var progress: CGFloat | |
// MARK: - UIViewRepresentable Initializers | |
func makeUIView(context: Context) -> MKMapView { | |
let view = MKMapView(frame: .zero) | |
view.delegate = context.coordinator | |
return view | |
} | |
func updateUIView(_ view: MKMapView, context: Context) { | |
// Implement the overlays that need a delegate | |
let startOverlay = MKCircle( | |
center: startCoordinate, | |
radius: 10000.0 | |
) | |
let endOverlay = MKCircle( | |
center: endCoordinate, | |
radius: 10000.0 | |
) | |
let flightPath = MKGeodesicPolyline( | |
coordinates: [startCoordinate, endCoordinate], | |
count: 2 | |
) | |
view.addOverlays([startOverlay, endOverlay, flightPath]) | |
// 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 | |
} | |
// MARK: - SwiftUI Coordinator | |
// You need to tell SwiftUI about the Coordinator class. | |
func makeCoordinator() -> MapCoordinator { | |
/* | |
This method creates the coordinator and returns it to the SwiftUI framework to pass in where necessary. SwiftUI will call makeCoordinator() before makeUIViewController(context:) so it’s available during the creation and configuration of your non-SwiftUI components. | |
*/ | |
MapCoordinator(self, progress: progress) | |
} | |
} | |
// MARK: - MapViewDelegates | |
extension MapCoordinator: MKMapViewDelegate { | |
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay ) -> MKOverlayRenderer { | |
if overlay is MKCircle { | |
let renderer = MKCircleRenderer(overlay: overlay) | |
renderer.fillColor = UIColor.black | |
renderer.strokeColor = UIColor.black | |
return renderer | |
} | |
if overlay is MKGeodesicPolyline { | |
let renderer = MKPolylineRenderer(overlay: overlay) | |
renderer.strokeColor = UIColor( | |
red: 0.0, | |
green: 0.0, | |
blue: 1.0, | |
alpha: 0.3 | |
) | |
renderer.lineWidth = 3.0 | |
renderer.strokeStart = 0.0 | |
renderer.strokeEnd = fraction | |
return renderer | |
} | |
return MKOverlayRenderer() | |
} | |
} | |
// MARK: - Preview | |
struct MapView_Previews: PreviewProvider { | |
static var previews: some View { | |
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) | |
} | |
} | |
/* Code Expainations | |
1. When you project the curved surface of the Earth onto a flat surface, such as a device screen, some distortion occurs. You convert the start and end coordinates on the globe to MKMapPoint values in the flattened map. Using MKMapPoints dramatically simplifies the calculations to follow. | |
2. Next, you determine the minimum and maximum x and y values among these points. | |
3. You create a 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 ten points. | |
5. You use the setVisibleMapRect(_:edgePadding:animated:) method to set the map's viewable area. This method uses the rectangle calculated in step three as the area to show. The edgePadding adds the padding that you set up in step four, so the airports' locations are not directly at the edge of the view and, therefore, easier to see. | |
6. You set the type of map and do not allow the user to scroll the map. | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment