[ios] Refined custom callout view support

Renamed MGLCalloutView to MGLCompactCalloutView and MGLCalloutViewProtocol to MGLCalloutView to avoid an awkward workaround for a Swift name collision. Replaced individual title and subtitle properties with a single representedObject property that lets you use custom annotation properties in the custom callout view. Overrode a problematic SMCalloutView method. Added lots more documentation.
This commit is contained in:
Minh Nguyễn
2016-01-06 15:13:39 -08:00
committed by Minh Nguyễn
parent b70fd7ae69
commit 11d99bd890
13 changed files with 151 additions and 82 deletions

View File

@@ -68,9 +68,9 @@
'../include/mbgl/ios/MGLAnnotationImage.h',
'../platform/ios/src/MGLAnnotationImage_Private.h',
'../platform/ios/src/MGLAnnotationImage.m',
'../include/mbgl/ios/MGLCalloutViewProtocol.h',
'../platform/ios/src/MGLCalloutView.h',
'../platform/ios/src/MGLCalloutView.m',
'../include/mbgl/ios/MGLCalloutView.h',
'../platform/ios/src/MGLCompactCalloutView.h',
'../platform/ios/src/MGLCompactCalloutView.m',
'../platform/ios/src/MGLCategoryLoader.h',
'../platform/ios/src/MGLCategoryLoader.m',
'../platform/ios/src/NSBundle+MGLAdditions.h',

View File

@@ -0,0 +1,67 @@
#import <Foundation/Foundation.h>
#import "MGLTypes.h"
NS_ASSUME_NONNULL_BEGIN
@protocol MGLCalloutViewDelegate;
@protocol MGLAnnotation;
/**
A protocol for a `UIView` subclass that displays information about a selected annotation near that annotation.
*/
@protocol MGLCalloutView <NSObject>
/**
An object conforming to the `MGLAnnotation` protocol whose details this callout view displays.
*/
@property (nonatomic, strong) id <MGLAnnotation> representedObject;
/**
A view that the user may tap to perform an action. This view is conventionally positioned on the left side of the callout view.
*/
@property (nonatomic, strong) UIView *leftAccessoryView;
/**
A view that the user may tap to perform an action. This view is conventionally positioned on the right side of the callout view.
*/
@property (nonatomic, strong) UIView *rightAccessoryView;
/**
An object conforming to the `MGLCalloutViewDelegate` method that receives messages related to the callout view’s interactive subviews.
*/
@property (nonatomic, weak) id<MGLCalloutViewDelegate> delegate;
/**
Presents a callout view by adding it to `inView` and pointing at the given rect of `inView`’s bounds. Constrains the callout to the bounds of the given view.
*/
- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated;
/**
Dismisses the callout view.
*/
- (void)dismissCalloutAnimated:(BOOL)animated;
@end
/**
The MGLCalloutViewDelegate protocol defines a set of optional methods that you can use to receive messages from an object that conforms to the MGLCalloutView protocol. The callout view uses these methods to inform the delegate that the user has interacted with the the callout view.
*/
@protocol MGLCalloutViewDelegate <NSObject>
@optional
/**
Returns a Boolean value indicating whether the entire callout view “highlights” when tapped. The default value is `YES`, which means the callout view highlights when tapped.
The return value of this method is ignored unless the delegate also responds to the `-calloutViewTapped` method.
*/
- (BOOL)calloutViewShouldHighlight:(UIView<MGLCalloutView> *)calloutView;
/**
Tells the delegate that the callout view has been tapped.
*/
- (void)calloutViewTapped:(UIView<MGLCalloutView> *)calloutView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,35 +0,0 @@
#import <Foundation/Foundation.h>
#import "MGLTypes.h"
NS_ASSUME_NONNULL_BEGIN
@protocol MGLCalloutViewDelegate;
@protocol MGLCalloutViewProtocol <NSObject>
@property (nonatomic, copy) NSString *title, *subtitle;
@property (nonatomic, strong) UIView *leftAccessoryView, *rightAccessoryView;
@property (nonatomic, weak) id<MGLCalloutViewDelegate> delegate;
// Presents a callout view by adding it to "inView" and pointing at the given rect of inView's bounds.
// Constrains the callout to the bounds of the given view.
- (void)presentCalloutFromRect:(CGRect)rect inView:(UIView *)view constrainedToView:(UIView *)constrainedView animated:(BOOL)animated;
- (void)dismissCalloutAnimated:(BOOL)animated;
@end
@protocol MGLCalloutViewDelegate <NSObject>
@optional
// Controls whether the callout "highlights" when pressed. default YES. You must also respond to `-calloutViewClicked` below.
- (BOOL)calloutViewShouldHighlight:(UIView<MGLCalloutViewProtocol> *)calloutView;
// Called when the callout view is clicked.
- (void)calloutViewClicked:(UIView<MGLCalloutViewProtocol> *)calloutView;
@end
NS_ASSUME_NONNULL_END

View File

@@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol MGLMapViewDelegate;
@protocol MGLAnnotation;
@protocol MGLOverlay;
@protocol MGLCalloutViewProtocol;
@protocol MGLCalloutView;
/**
An interactive, customizable map view with an interface similar to the one
@@ -963,15 +963,15 @@ IB_DESIGNABLE
- (BOOL)mapView:(MGLMapView *)mapView annotationCanShowCallout:(id <MGLAnnotation>)annotation;
/**
Returns a custom callout view to display for the specified annotation.
Returns a callout view to display for the specified annotation.
If the method is present in the delegate, it must return a new instance of a view dedicated to display the callout bubble. It will be configured by the map view.
If this method is present in the delegate, it must return a new instance of a view dedicated to display the callout bubble. It will be configured by the map view. If this method is not present, or if it returns `nil`, a standard, two-line, bubble-like callout view is displayed by default.
@param mapView The map view that requested the callout view.
@param annotation The object representing the annotation.
@return A view following the MGLCalloutView protocol.
@return A view conforming to the `MGLCalloutView` protocol, or `nil` to use the default callout view.
*/
- (nullable UIView <MGLCalloutViewProtocol> *)mapView:(MGLMapView *)mapView customCalloutViewForAnnotation:(id <MGLAnnotation>)annotation;
- (nullable UIView <MGLCalloutView> *)mapView:(MGLMapView *)mapView calloutViewForAnnotation:(id <MGLAnnotation>)annotation;
/**
Returns the view to display on the left side of the standard callout bubble.

View File

@@ -1,7 +1,7 @@
#import "MGLAccountManager.h"
#import "MGLAnnotation.h"
#import "MGLAnnotationImage.h"
#import "MGLCalloutViewProtocol.h"
#import "MGLCalloutView.h"
#import "MGLGeometry.h"
#import "MGLMapCamera.h"
#import "MGLMapView.h"

View File

@@ -1,16 +1,10 @@
#import <UIKit/UIKit.h>
#import <mbgl/ios/MGLCalloutViewProtocol.h>
#import <mbgl/ios/MGLCalloutView.h>
/*
/**
* Basic custom callout view to demonstrate how to
* add your own on your app. Will only show the
* callout title for demonstration purpose.
*/
@interface MBXCustomCalloutView : UIView <MGLCalloutViewProtocol>
@property (nonatomic, copy) NSString *title, *subtitle;
@property (nonatomic, strong) UIView *leftAccessoryView, *rightAccessoryView;
@property (nonatomic, weak) id<MGLCalloutViewDelegate> delegate;
@interface MBXCustomCalloutView : UIView <MGLCalloutView>
@end

View File

@@ -1,5 +1,7 @@
#import "MBXCustomCalloutView.h"
#import <mbgl/darwin/MGLAnnotation.h>
static CGFloat const tipHeight = 10.0;
static CGFloat const tipWidth = 10.0;
@@ -9,7 +11,17 @@ static CGFloat const tipWidth = 10.0;
@end
@implementation MBXCustomCalloutView
@implementation MBXCustomCalloutView {
id <MGLAnnotation> _representedObject;
UIView *_leftAccessoryView;
UIView *_rightAccessoryView;
__weak id <MGLCalloutViewDelegate> _delegate;
}
@synthesize representedObject = _representedObject;
@synthesize leftAccessoryView = _leftAccessoryView;
@synthesize rightAccessoryView = _rightAccessoryView;
@synthesize delegate = _delegate;
- (instancetype)initWithFrame:(CGRect)frame
{
@@ -32,8 +44,11 @@ static CGFloat const tipWidth = 10.0;
{
[view addSubview:self];
// prepare title label
self.mainLabel.text = self.title;
[self.mainLabel sizeToFit];
if ([self.representedObject respondsToSelector:@selector(title)])
{
self.mainLabel.text = self.representedObject.title;
[self.mainLabel sizeToFit];
}
// prepare our frame
CGFloat frameWidth = self.mainLabel.bounds.size.width;
CGFloat frameHeight = self.mainLabel.bounds.size.height * 2.0;

View File

@@ -153,7 +153,7 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
@"Add 10,000 Points",
@"Add Test Shapes",
@"Start World Tour",
@"Add 1 custom Point",
@"Add Custom Callout Point",
@"Remove Annotations",
@"Toggle Custom Style Layer",
nil];
@@ -599,13 +599,13 @@ static const CLLocationCoordinate2D WorldTourDestinations[] = {
}];
}
- (UIView<MGLCalloutViewProtocol> *)mapView:(__unused MGLMapView *)mapView customCalloutViewForAnnotation:(id<MGLAnnotation>)annotation
- (UIView<MGLCalloutView> *)mapView:(__unused MGLMapView *)mapView calloutViewForAnnotation:(id<MGLAnnotation>)annotation
{
if ([annotation respondsToSelector:@selector(title)]
&& [annotation.title isEqualToString:kCustomCalloutTitle])
{
MBXCustomCalloutView *calloutView = [[MBXCustomCalloutView alloc] init];
calloutView.title = annotation.title;
calloutView.representedObject = annotation;
return calloutView;
}
return nil;

View File

@@ -1,6 +0,0 @@
#import "SMCalloutView.h"
#import "MGLCalloutViewProtocol.h"
@interface MGLCalloutView : SMCalloutView <MGLCalloutViewProtocol>
@end

View File

@@ -1,5 +0,0 @@
#import "MGLCalloutView.h"
@implementation MGLCalloutView
@end

View File

@@ -0,0 +1,11 @@
#import "SMCalloutView.h"
#import "MGLCalloutView.h"
/**
A concrete implementation of `MGLCalloutView` based on [SMCalloutView](https://github.com/nfarina/calloutview). This callout view displays the represented annotation’s title, subtitle, and accessory views in a compact, two-line layout.
*/
@interface MGLCompactCalloutView : SMCalloutView <MGLCalloutView>
+ (instancetype)platformCalloutView;
@end

View File

@@ -0,0 +1,31 @@
#import "MGLCompactCalloutView.h"
#import "MGLAnnotation.h"
@implementation MGLCompactCalloutView
{
id <MGLAnnotation> _representedObject;
}
@synthesize representedObject = _representedObject;
+ (instancetype)platformCalloutView
{
return [[self alloc] init];
}
- (void)setRepresentedObject:(id <MGLAnnotation>)representedObject
{
_representedObject = representedObject;
if ([representedObject respondsToSelector:@selector(title)])
{
self.title = representedObject.title;
}
if ([representedObject respondsToSelector:@selector(subtitle)])
{
self.subtitle = representedObject.subtitle;
}
}
@end

View File

@@ -39,7 +39,7 @@
#import "MGLAccountManager_Private.h"
#import "MGLAnnotationImage_Private.h"
#import "MGLMapboxEvents.h"
#import "MGLCalloutView.h"
#import "MGLCompactCalloutView.h"
#import <algorithm>
#import <cstdlib>
@@ -138,7 +138,7 @@ public:
/// Mapping from reusable identifiers to annotation images.
@property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, MGLAnnotationImage *) *annotationImagesByIdentifier;
/// Currently shown popover representing the selected annotation.
@property (nonatomic) UIView<MGLCalloutViewProtocol> *calloutViewForSelectedAnnotation;
@property (nonatomic) UIView<MGLCalloutView> *calloutViewForSelectedAnnotation;
@property (nonatomic) MGLUserLocationAnnotationView *userLocationAnnotationView;
@property (nonatomic) CLLocationManager *locationManager;
@property (nonatomic) CGPoint centerPoint;
@@ -1341,12 +1341,12 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
}
}
- (BOOL)calloutViewShouldHighlight:(__unused MGLCalloutView *)calloutView
- (BOOL)calloutViewShouldHighlight:(__unused MGLCompactCalloutView *)calloutView
{
return [self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)];
}
- (void)calloutViewClicked:(__unused MGLCalloutView *)calloutView
- (void)calloutViewTapped:(__unused MGLCompactCalloutView *)calloutView
{
if ([self.delegate respondsToSelector:@selector(mapView:tapOnCalloutForAnnotation:)])
{
@@ -2514,9 +2514,9 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
[self.delegate mapView:self annotationCanShowCallout:annotation])
{
// build the callout
if ([self.delegate respondsToSelector:@selector(mapView:customCalloutViewForAnnotation:)])
if ([self.delegate respondsToSelector:@selector(mapView:calloutViewForAnnotation:)])
{
self.calloutViewForSelectedAnnotation = [self.delegate mapView:self customCalloutViewForAnnotation:annotation];
self.calloutViewForSelectedAnnotation = [self.delegate mapView:self calloutViewForAnnotation:annotation];
}
if (!self.calloutViewForSelectedAnnotation)
{
@@ -2576,13 +2576,10 @@ std::chrono::steady_clock::duration MGLDurationInSeconds(float duration)
}
}
- (MGLCalloutView *)calloutViewForAnnotation:(id <MGLAnnotation>)annotation
- (MGLCompactCalloutView *)calloutViewForAnnotation:(id <MGLAnnotation>)annotation
{
MGLCalloutView *calloutView = (MGLCalloutView *)[MGLCalloutView platformCalloutView];
if ([annotation respondsToSelector:@selector(title)]) calloutView.title = annotation.title;
if ([annotation respondsToSelector:@selector(subtitle)]) calloutView.subtitle = annotation.subtitle;
MGLCompactCalloutView *calloutView = [MGLCompactCalloutView platformCalloutView];
calloutView.representedObject = annotation;
calloutView.tintColor = self.tintColor;
return calloutView;