Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support with documentation #28

Open
ElFredo311 opened this issue Nov 30, 2022 · 4 comments
Open

Support with documentation #28

ElFredo311 opened this issue Nov 30, 2022 · 4 comments

Comments

@ElFredo311
Copy link

This is not an issue perse but more like a feature request, the README.md has a few lines on how to draw polylines on map elements but doesn't seem to work or is not clear, is possible to add a basic example (even here, if it works ill create a pull request) of a basic implementation.

Thanks

@pauljohanneskraft
Copy link
Owner

Sure - there are currently two ways of adding overlays to this Map implementation. You can either use MKPolyline (i.e. the more UIKit way of doing UI) or specify your own Identifiable structs and then create custom MapOverlays from that (i.e. the SwiftUI way of doing things). Which one would you like more help on?

For the SwiftUI approach, I just created a code example for circles that could be transformed into using MapPolyline instead of MapCircle in the closure that is given as a parameter of the Map initializer. (See #29 (comment))

For the UIKit approach, you would have something like this:

struct MyMapView: View {

    @State private var overlays = [MKOverlay]()
    @State private var region = MKCoordinateRegion()

    var body: some View {
        Map(
            coordinateRegion: $region,
            overlays: overlays
        ) { overlay in
            // Create a MKOverlayRenderer here, for example something like this:
            let renderer = MKPolylineRenderer(overlay: overlay)
            renderer.lineWidth = 4
            renderer.strokeColor = .black
            return renderer
        }
    }
    
}

@skyvalleystudio
Copy link

skyvalleystudio commented Apr 20, 2023

You mentioned adapting the #29 MapCircle to a MapPolyline but I could use a little help with that.

I have a set of locations from a @FetchRquest and want to make a polyline overlay from them. They are coredata objects from the fetch and I'm not sure where I turn them into array for the MapPolyline [CLLocationCoordinate2D]?

@FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \RoutePoint.timestamp, ascending: true)],
        animation: .default)
    private var route: FetchedResults<RoutePoint>

// I have something like this in the Map creation:
           overlayItems: route,
           overlayContent: { routePoint in
              MapPolyline(coordinates: [CLLocationCoordinate2D(latitude: routePoint.latitude, longitude: routePoint.longitude)],
                            level: .aboveRoads,
                            strokeColor: .systemIndigo,
                            lineWidth: 5)
            }

If I provide [CLLocationCoordinate2d] in overlayItems it complains it is not Identifiable (of course), so I'm not sure how these two parameters work together. I think an example would be helpful.

Your package looks quite helpful, but I haven't got it past showing the ocean at 0.0,0.0 yet either :^).

@pauljohanneskraft
Copy link
Owner

pauljohanneskraft commented Apr 21, 2023

Could you make RoutePoint Identifiable? Maybe by using the timestamp or some unique identifier as id property? Since RoutePoint further needs to conform to NSObject, you might just want to use ObjectIdentifier as in the following example. It essentially uses the memory address of that object as its id, so as long as CoreData does not create new objects each time the request is fired but simply adapts the objects itself, this should work flawlessly.

extension RoutePoint: Identifiable {
    public var id: ObjectIdentifier {
        ObjectIdentifier(self)
    }
}

And regarding the issue with showing the ocean at (0,0) - could it be that you are using .constant(.init()) as the region/mapRect parameter in the initializer of the Map? If you do this, then you will always force the map to use that region, which leads it to not move at all. What you would want to do instead is have a @State wrapped property for either mapRect or region. If you don't modify it, it will simply be the user-defined value (and start at the value that you set it to initially), but you can then simply set that property and the map will move to that region/mapRect.

Edit: I think I misunderstood your point about the RoutePoint thing: You want to create one MapPolyline from all RoutePoints combined, right? Above is the solution for creating one MapPolyline per RoutePoint. Okay, in the case of one MapPolyline for all RoutePoints, it would probably make most sense to use something similar to what I did in this comment, where you would have some struct (let's call it OverlayItem here) and you would create a new array of OverlayItem with onChange(of:_:) whenever the CoreData result changes, i.e.

struct OverlayItem: Identifiable {
    let id = UUID()
    let coordinates: [CLLocationCoordinate2D]
}

struct MyView: View {

    @State private var region: MKCoordinateRegion = ... // or use `mapRect: MKMapRect ` instead
    @State private var overlayItems = [OverlayItem]()
    
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \RoutePoint.timestamp, ascending: true)],
        animation: .default)
    private var route: FetchedResults<RoutePoint>
    
    var body: some View {
        Map(
            coordinateRegion: $region, // or mapRect: $mapRect
            overlayItems: overlayItems,
            overlayContent: {
            }
        )
        .onChange(of: route) { route in // not sure, if FetchedResults<RoutePoint> conforms to Equatable - if not, you might want to extend it accordingly
            self.overlayItems = [
                OverlayItem(
                    coordinates: route.map { point in
                        CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude)
                    }
                )
            ]
        }
    }

}

@skyvalleystudio
Copy link

skyvalleystudio commented Apr 21, 2023

You are correct, I am trying to show a MapPolyline for a list of locations in CoreData. I think this may be a common use case. The challenge is that you are trying to support multiple overlays not just one polyline.

FetchedResults does not conform to Equatable but I don't want to do a complete comparison on it since Swifui responds to changes from an @FetchRequest (that's its reason for existing.) Now I'm trying to prepare the overlayItems in the view
and it seems to work, but I have to scroll the map to my current location (see below.)

My problem with the Ocean seems to be order of when my locationManager is getting set up. I changed it to have a default at Buckingham palace ;^). The thing I'm not seeing is the map following the location as I expected with the userTrackingMode I set.

I will be adding Annotations as well in the future, so I like your general solution to all of this.

The code I'm currently trying:


    @State var region = MKCoordinateRegion(center: LocationPublisher.shared.lastLocation?.coordinate ??           
                          CLLocationCoordinate2D(latitude: 51.5, longitude: -0.12),
                                    latitudinalMeters: 1000,
                                    longitudinalMeters: 1000)
    @State private var userTrackingMode = UserTrackingMode.follow

    var body: some View {
        let overlayItems = [
            OverlayItem(
                coordinates: route.map { point in
                    CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude)
                }
            )
        ]

        Map(
            coordinateRegion: $region,
            type: .standard,
            pointOfInterestFilter: .excludingAll,
            informationVisibility: .default.union(.userLocation),
            interactionModes: .all,
            userTrackingMode: $userTrackingMode,
            overlayItems: overlayItems,
            overlayContent: { overlayItem in
                MapPolyline(coordinates: overlayItem.coordinates,
                            level: .aboveRoads,
                            strokeColor: .systemIndigo,
                            lineWidth: 5)
            }

        )

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants