|
@@ -0,0 +1,332 @@
|
|
|
|
|
+//
|
|
|
|
|
+// ApexMapView.m
|
|
|
|
|
+// OpenStreetMap
|
|
|
|
|
+//
|
|
|
|
|
+// Created by Jack on 2019/1/18.
|
|
|
|
|
+// Copyright © 2019 Jack Template. All rights reserved.
|
|
|
|
|
+//
|
|
|
|
|
+
|
|
|
|
|
+#import "ApexMapView.h"
|
|
|
|
|
+#import "UIView+RAConstraint.h"
|
|
|
|
|
+
|
|
|
|
|
+static NSString * const kTileSource = @"https://map.apexshipping.com/osm_tiles/{z}/{x}/{y}.png";
|
|
|
|
|
+static NSString * const kOpenStreetMapURL = @"https://www.openstreetmap.org/copyright";
|
|
|
|
|
+static NSString * const kCopyright = @"OpenStreetMap";
|
|
|
|
|
+
|
|
|
|
|
+@interface ApexMapView () <MKMapViewDelegate>
|
|
|
|
|
+
|
|
|
|
|
+@property (nonatomic,strong) UIButton *legalBtn;
|
|
|
|
|
+@property (nonatomic,strong) MKTileOverlay *tileOverlay;
|
|
|
|
|
+@property (nonatomic,weak) id<MKMapViewDelegate> internalDelegate;
|
|
|
|
|
+
|
|
|
|
|
+@end
|
|
|
|
|
+
|
|
|
|
|
+@implementation ApexMapView
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Initial
|
|
|
|
|
+
|
|
|
|
|
+- (instancetype)initWithFrame:(CGRect)frame {
|
|
|
|
|
+ if (self = [super initWithFrame:frame]) {
|
|
|
|
|
+ [self setupMapView];
|
|
|
|
|
+ }
|
|
|
|
|
+ return self;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)awakeFromNib {
|
|
|
|
|
+ [super awakeFromNib];
|
|
|
|
|
+ [self setupMapView];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)setupMapView {
|
|
|
|
|
+
|
|
|
|
|
+ self.rotateEnabled = NO;
|
|
|
|
|
+ [self apex_setDelegate:self];
|
|
|
|
|
+
|
|
|
|
|
+ [self addOverlay:self.tileOverlay];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)layoutSubviews {
|
|
|
|
|
+ [super layoutSubviews];
|
|
|
|
|
+
|
|
|
|
|
+ NSArray<UIView *> *subViews = self.subviews;
|
|
|
|
|
+ for (UIView *subView in subViews) {
|
|
|
|
|
+
|
|
|
|
|
+ if ([subView isMemberOfClass:NSClassFromString(@"MKAttributionLabel")]) {
|
|
|
|
|
+ [subView removeFromSuperview];
|
|
|
|
|
+ } else if ([subView isMemberOfClass:[UIImageView class]]) {
|
|
|
|
|
+ [subView removeFromSuperview];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (![subViews containsObject:self.legalBtn]) {
|
|
|
|
|
+ [self setupLegalView];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)setupLegalView {
|
|
|
|
|
+
|
|
|
|
|
+ [self addSubview:self.legalBtn];
|
|
|
|
|
+
|
|
|
|
|
+ [self.legalBtn ra_applyConstraints:^(RAConstraintMaker *maker) {
|
|
|
|
|
+
|
|
|
|
|
+ maker.left.ra_equalTo(self.left).ra_offset(5.0f);
|
|
|
|
|
+ maker.bottom.ra_equalTo(self.bottom).ra_offset(-5.0f);
|
|
|
|
|
+ }];
|
|
|
|
|
+
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Override
|
|
|
|
|
+
|
|
|
|
|
+- (void)setDelegate:(id<MKMapViewDelegate>)delegate {
|
|
|
|
|
+ if (delegate) {
|
|
|
|
|
+ __weak typeof(delegate) weakDelegate = delegate;
|
|
|
|
|
+ _internalDelegate = weakDelegate;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ _internalDelegate = nil;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)apex_setDelegate:(id<MKMapViewDelegate>)delegate {
|
|
|
|
|
+ [super setDelegate:delegate];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - Getter
|
|
|
|
|
+
|
|
|
|
|
+- (MKTileOverlay *)tileOverlay {
|
|
|
|
|
+ if (!_tileOverlay) {
|
|
|
|
|
+ _tileOverlay = [[MKTileOverlay alloc] initWithURLTemplate:kTileSource];
|
|
|
|
|
+ _tileOverlay.canReplaceMapContent = YES;
|
|
|
|
|
+ _tileOverlay.minimumZ = 1;
|
|
|
|
|
+ _tileOverlay.maximumZ = 18;
|
|
|
|
|
+ }
|
|
|
|
|
+ return _tileOverlay;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (UIButton *)legalBtn {
|
|
|
|
|
+ if (!_legalBtn) {
|
|
|
|
|
+ _legalBtn = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
|
|
|
+ _legalBtn.titleLabel.font = [UIFont systemFontOfSize:12.0f];
|
|
|
|
|
+ [_legalBtn setTitle:kCopyright forState:UIControlStateNormal];
|
|
|
|
|
+ [_legalBtn addTarget:self action:@selector(legalButtonDidClick:) forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
|
+ }
|
|
|
|
|
+ return _legalBtn;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - User Action
|
|
|
|
|
+
|
|
|
|
|
+- (void)legalButtonDidClick:(id)sender {
|
|
|
|
|
+
|
|
|
|
|
+ NSURL *url = [NSURL URLWithString:kOpenStreetMapURL];
|
|
|
|
|
+ if ([[UIApplication sharedApplication] canOpenURL:url]) {
|
|
|
|
|
+ if (@available(iOS 10, *)) {
|
|
|
|
|
+ [[UIApplication sharedApplication] openURL:url options:@{UIApplicationOpenURLOptionsSourceApplicationKey: @YES} completionHandler:^(BOOL success) {
|
|
|
|
|
+
|
|
|
|
|
+ }];
|
|
|
|
|
+ } else{
|
|
|
|
|
+ [[UIApplication sharedApplication] openURL:url];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#pragma mark - MKMapViewDelegate
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:regionWillChangeAnimated:)]) {
|
|
|
|
|
+ [_internalDelegate mapView:mapView regionWillChangeAnimated:animated];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:regionDidChangeAnimated:)]) {
|
|
|
|
|
+ [_internalDelegate mapView:mapView regionDidChangeAnimated:animated];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapViewDidChangeVisibleRegion:(MKMapView *)mapView {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapViewDidChangeVisibleRegion:)]) {
|
|
|
|
|
+ [_internalDelegate mapViewDidChangeVisibleRegion:mapView];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapViewWillStartLoadingMap:(MKMapView *)mapView {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapViewWillStartLoadingMap:)]) {
|
|
|
|
|
+ [_internalDelegate mapViewWillStartLoadingMap:mapView];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapViewDidFinishLoadingMap:)]) {
|
|
|
|
|
+ [_internalDelegate mapViewDidFinishLoadingMap:mapView];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapViewDidFailLoadingMap:withError:)]) {
|
|
|
|
|
+ [_internalDelegate mapViewDidFailLoadingMap:mapView withError:error];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapViewWillStartRenderingMap:(MKMapView *)mapView {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapViewWillStartRenderingMap:)]) {
|
|
|
|
|
+ [_internalDelegate mapViewWillStartRenderingMap:mapView];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapViewDidFinishRenderingMap:(MKMapView *)mapView fullyRendered:(BOOL)fullyRendered {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapViewDidFinishRenderingMap:fullyRendered:)]) {
|
|
|
|
|
+ [_internalDelegate mapViewDidFinishRenderingMap:mapView fullyRendered:fullyRendered];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// mapView:viewForAnnotation: provides the view for each annotation.
|
|
|
|
|
+// This method may be called for all or some of the added annotations.
|
|
|
|
|
+// For MapKit provided annotations (eg. MKUserLocation) return nil to use the MapKit provided annotation view.
|
|
|
|
|
+- (nullable MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:viewForAnnotation:)]) {
|
|
|
|
|
+ return [_internalDelegate mapView:mapView viewForAnnotation:annotation];
|
|
|
|
|
+ }
|
|
|
|
|
+ return nil;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// mapView:didAddAnnotationViews: is called after the annotation views have been added and positioned in the map.
|
|
|
|
|
+// The delegate can implement this method to animate the adding of the annotations views.
|
|
|
|
|
+// Use the current positions of the annotation views as the destinations of the animation.
|
|
|
|
|
+- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray<MKAnnotationView *> *)views {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:didAddAnnotationViews:)]) {
|
|
|
|
|
+ [_internalDelegate mapView:mapView didAddAnnotationViews:views];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
|
|
+// mapView:annotationView:calloutAccessoryControlTapped: is called when the user taps on left & right callout accessory UIControls.
|
|
|
|
|
+- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:annotationView:calloutAccessoryControlTapped:)]) {
|
|
|
|
|
+ [_internalDelegate mapView:mapView annotationView:view calloutAccessoryControlTapped:control];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:didSelectAnnotationView:)]) {
|
|
|
|
|
+ [_internalDelegate mapView:mapView didSelectAnnotationView:view];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:didDeselectAnnotationView:)]) {
|
|
|
|
|
+ [_internalDelegate mapView:mapView didDeselectAnnotationView:view];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapViewWillStartLocatingUser:(MKMapView *)mapView {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapViewWillStartLocatingUser:)]) {
|
|
|
|
|
+ [_internalDelegate mapViewWillStartLocatingUser:mapView];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapViewDidStopLocatingUser:(MKMapView *)mapView {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapViewDidStopLocatingUser:)]) {
|
|
|
|
|
+ [_internalDelegate mapViewDidStopLocatingUser:mapView];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:didUpdateUserLocation:)]) {
|
|
|
|
|
+ [_internalDelegate mapView:mapView didUpdateUserLocation:userLocation];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapView:(MKMapView *)mapView didFailToLocateUserWithError:(NSError *)error {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:didFailToLocateUserWithError:)]) {
|
|
|
|
|
+ [_internalDelegate mapView:mapView didFailToLocateUserWithError:error];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view didChangeDragState:(MKAnnotationViewDragState)newState
|
|
|
|
|
+ fromOldState:(MKAnnotationViewDragState)oldState {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:annotationView:didChangeDragState:fromOldState:)]) {
|
|
|
|
|
+ [_internalDelegate mapView:mapView annotationView:view didChangeDragState:newState fromOldState:oldState];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
|
|
+- (void)mapView:(MKMapView *)mapView didChangeUserTrackingMode:(MKUserTrackingMode)mode animated:(BOOL)animated {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:didChangeUserTrackingMode:animated:)]) {
|
|
|
|
|
+ [_internalDelegate mapView:mapView didChangeUserTrackingMode:mode animated:animated];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay {
|
|
|
|
|
+
|
|
|
|
|
+ if (overlay == self.tileOverlay) {
|
|
|
|
|
+
|
|
|
|
|
+ return [[MKTileOverlayRenderer alloc] initWithTileOverlay:overlay];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:rendererForOverlay:)]) {
|
|
|
|
|
+ return [_internalDelegate mapView:mapView rendererForOverlay:overlay];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return nil;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+- (void)mapView:(MKMapView *)mapView didAddOverlayRenderers:(NSArray<MKOverlayRenderer *> *)renderers {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:didAddOverlayRenderers:)]) {
|
|
|
|
|
+ [_internalDelegate mapView:mapView didAddOverlayRenderers:renderers];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#if TARGET_OS_IPHONE
|
|
|
|
|
+// Prefer -mapView:rendererForOverlay:
|
|
|
|
|
+- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
|
|
|
|
|
+
|
|
|
|
|
+ if (overlay == self.tileOverlay) {
|
|
|
|
|
+
|
|
|
|
|
+ return [[MKOverlayView alloc] initWithOverlay:overlay];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:viewForOverlay:)]) {
|
|
|
|
|
+ return [_internalDelegate mapView:mapView viewForOverlay:overlay];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return nil;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// Called after the provided overlay views have been added and positioned in the map.
|
|
|
|
|
+// Prefer -mapView:didAddOverlayRenderers:
|
|
|
|
|
+- (void)mapView:(MKMapView *)mapView didAddOverlayViews:(NSArray *)overlayViews {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:didAddOverlayViews:)]) {
|
|
|
|
|
+ [_internalDelegate mapView:mapView didAddOverlayViews:overlayViews];
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
|
|
|
|
+// Return nil for default MKClusterAnnotation, it is illegal to return a cluster annotation not containing the identical array of member annotations given.
|
|
|
|
|
+- (MKClusterAnnotation *)mapView:(MKMapView *)mapView clusterAnnotationForMemberAnnotations:(NSArray<id<MKAnnotation>>*)memberAnnotations {
|
|
|
|
|
+
|
|
|
|
|
+ if (_internalDelegate && [_internalDelegate respondsToSelector:@selector(mapView:clusterAnnotationForMemberAnnotations:)]) {
|
|
|
|
|
+ return [_internalDelegate mapView:mapView clusterAnnotationForMemberAnnotations:memberAnnotations];
|
|
|
|
|
+ }
|
|
|
|
|
+ return nil;
|
|
|
|
|
+}
|
|
|
|
|
+@end
|