Add custom node annotations with basic callout views

This commit is contained in:
Joshua Pirihi 2021-12-24 20:06:21 +13:00
parent 951ecec688
commit 96589b8a1a
5 changed files with 189 additions and 3 deletions

View file

@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
C9483F6D2773017500998F6B /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9483F6C2773017500998F6B /* MapView.swift */; };
C9A7BC1027759A9600760B50 /* PositionAnnotationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */; };
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */; };
DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD2E65252767A01F00E45FC5 /* NodeDetail.swift */; };
DD47E3CE26F103C600029299 /* NodeList.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD47E3CD26F103C600029299 /* NodeList.swift */; };
@ -67,6 +69,8 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
C9483F6C2773017500998F6B /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = "<group>"; };
C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PositionAnnotationView.swift; sourceTree = "<group>"; };
DD23A50E26FD1B4400D9B90C /* PeripheralModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeripheralModel.swift; sourceTree = "<group>"; };
DD2E65252767A01F00E45FC5 /* NodeDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeDetail.swift; sourceTree = "<group>"; };
DD47E3CD26F103C600029299 /* NodeList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeList.swift; sourceTree = "<group>"; };
@ -141,6 +145,23 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
C9483F6B2773016700998F6B /* Map */ = {
isa = PBXGroup;
children = (
C9A7BC0E27759A6800760B50 /* Custom */,
C9483F6C2773017500998F6B /* MapView.swift */,
);
path = Map;
sourceTree = "<group>";
};
C9A7BC0E27759A6800760B50 /* Custom */ = {
isa = PBXGroup;
children = (
C9A7BC0F27759A9600760B50 /* PositionAnnotationView.swift */,
);
path = Custom;
sourceTree = "<group>";
};
DD47E3CA26F0E50300029299 /* Nodes */ = {
isa = PBXGroup;
children = (
@ -261,6 +282,7 @@
DDC2E18726CE24E40042C5E4 /* Views */ = {
isa = PBXGroup;
children = (
C9483F6B2773016700998F6B /* Map */,
DDC2E18D26CE25CB0042C5E4 /* Helpers */,
DD47E3D726F2F21A00029299 /* Bluetooth */,
DD47E3CA26F0E50300029299 /* Nodes */,
@ -506,11 +528,13 @@
DDAF8C5D26ED09490058C060 /* portnums.pb.swift in Sources */,
DD9D8F2F2764403B00080993 /* Meshtastic.xcdatamodeld in Sources */,
DD23A50F26FD1B4400D9B90C /* PeripheralModel.swift in Sources */,
C9A7BC1027759A9600760B50 /* PositionAnnotationView.swift in Sources */,
DD47E3CE26F103C600029299 /* NodeList.swift in Sources */,
DD47E3D626F17ED900029299 /* CircleText.swift in Sources */,
DDC2E18F26CE25FE0042C5E4 /* ContentView.swift in Sources */,
DDAF8C6326ED0A230058C060 /* admin.pb.swift in Sources */,
DD539500276C452400AD86B1 /* Preferences.swift in Sources */,
C9483F6D2773017500998F6B /* MapView.swift in Sources */,
DDAF8C5826ED07FD0058C060 /* mesh.pb.swift in Sources */,
DD8169FB271F1F3A00F4AB02 /* MeshLog.swift in Sources */,
DD2E65262767A01F00E45FC5 /* NodeDetail.swift in Sources */,

View file

@ -0,0 +1,63 @@
//
// PositionAnnotationView.swift
// MeshtasticClient
//
// Created by Joshua Pirihi on 24/12/21.
//
import UIKit
import MapKit
class PositionAnnotation: NSObject, MKAnnotation {
// This property must be key-value observable, which the `@objc dynamic` attributes provide.
@objc dynamic var coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
// Required if you set the annotation view's `canShowCallout` property to `true`
var title: String? = "Title"
var shortName: String?
// This property defined by `MKAnnotation` is not required.
//var subtitle: String? = NSLocalizedString("SAN_FRANCISCO_SUBTITLE", comment: "SF annotation")
}
class PositionAnnotationView: MKAnnotationView {
private let annotationFrame = CGRect(x: 0, y: 0, width: 32, height: 32)
private let label: UILabel
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
self.label = UILabel(frame: annotationFrame.offsetBy(dx: 0, dy: 0))
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
self.frame = annotationFrame
self.label.font = UIFont.preferredFont(forTextStyle: .caption2)
self.label.textColor = .white
self.label.textAlignment = .center
self.backgroundColor = .clear
self.addSubview(label)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) not implemented!")
}
public var name: String = "" {
didSet {
self.label.text = name
}
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
let circleRect = CGRect(x: 1, y: 1, width: 30, height: 30)
context.setFillColor(CGColor(red: 0, green: 0.5, blue: 1.0, alpha: 1.0))
context.fillEllipse(in: circleRect)
}
}

View file

@ -0,0 +1,96 @@
//
// MapView.swift
// MeshtasticClient
//
// Created by Joshua Pirihi on 22/12/21.
//
import Foundation
import UIKit
import MapKit
import SwiftUI
import CoreData
struct MapView: UIViewRepresentable {
//@Binding var route: MKPolyline?
var nodes: FetchedResults<NodeInfoEntity>
let mapViewDelegate = MapViewDelegate()
func makeUIView(context: Context) -> MKMapView {
let map = MKMapView(frame: .zero)
map.userTrackingMode = .follow
map.mapType = .satellite
map.register(PositionAnnotationView.self, forAnnotationViewWithReuseIdentifier: NSStringFromClass(PositionAnnotationView.self))
return map
}
func updateUIView(_ view: MKMapView, context: Context) {
view.delegate = mapViewDelegate // (1) This should be set in makeUIView, but it is getting reset to `nil`
view.translatesAutoresizingMaskIntoConstraints = false // (2) In the absence of this, we get constraints error on rotation; and again, it seems one should do this in makeUIView, but has to be here
//addRoute(to: view)
showNodePositions(to: view)
}
}
private extension MapView {
//func addRoute(to view: MKMapView) {
// if !view.overlays.isEmpty {
// view.removeOverlays(view.overlays)
// }
//guard let route = route else { return }
//let mapRect = route.boundingMapRect
//view.setVisibleMapRect(mapRect, edgePadding: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10), animated: true)
//view.addOverlay(route)
//}
func showNodePositions(to view: MKMapView) {
if !view.annotations.isEmpty {
view.removeAnnotations(view.annotations)
}
for node in self.nodes {
//try and get the last position
if (node.positions?.count ?? 0) > 0 {
let annotation = PositionAnnotation()
annotation.coordinate = (node.positions!.lastObject as! PositionEntity).coordinate ?? CLLocationCoordinate2D(latitude: 0, longitude: 0)
annotation.title = node.user?.longName ?? "Unknown"
annotation.shortName = node.user?.shortName?.uppercased() ?? "???"
view.addAnnotation(annotation)
}
}
}
}
class MapViewDelegate: NSObject, MKMapViewDelegate {
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !annotation.isKind(of: MKUserLocation.self) else {
// Make a fast exit if the annotation is the `MKUserLocation`, as it's not an annotation view we wish to customize.
return nil
}
var annotationView: MKAnnotationView?
if let annotation = annotation as? PositionAnnotation {
annotationView = self.setupPositionAnnotationView(for: annotation, on: mapView)
}
return annotationView
}
private func setupPositionAnnotationView(for annotation: PositionAnnotation, on mapView: MKMapView) -> PositionAnnotationView {
let identifier = NSStringFromClass(PositionAnnotationView.self)
let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? PositionAnnotationView ?? PositionAnnotationView()
annotationView.name = annotation.shortName ?? "???"
annotationView.canShowCallout = true
return annotationView
}
}

View file

@ -51,12 +51,12 @@ struct NodeMap: View {
Map(coordinateRegion: regionBinding,
/*Map(coordinateRegion: regionBinding,
interactionModes: [.all],
showsUserLocation: true,
userTrackingMode: .constant(.follow),
annotationItems: self.locationNodes.filter({ nodeinfo in
return nodeinfo.positions != nil && nodeinfo.positions!.count > 0
return nodeinfo.positions != nil && nodeinfo.positions!.count > 0// && (nodeinfo.positions?.lastObject as? AnyObject)?.coordinate != nil
})
) { locationNode in
@ -67,7 +67,10 @@ struct NodeMap: View {
}
)
}
}*/
MapView(nodes: self.locationNodes)
//}
.frame(maxHeight: .infinity)
.ignoresSafeArea(.all, edges: [.leading, .trailing])