Peter 9bc996374f Add 'submodules/AsyncDisplayKit/' from commit '02bedc12816e251ad71777f9d2578329b6d2bef6'
git-subtree-dir: submodules/AsyncDisplayKit
git-subtree-mainline: d06f423e0ed3df1fed9bd10d79ee312a9179b632
git-subtree-split: 02bedc12816e251ad71777f9d2578329b6d2bef6
2019-06-11 18:42:43 +01:00

336 lines
15 KiB
Objective-C

//
// MapHandlerNode.m
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import "MapHandlerNode.h"
#import "CustomMapAnnotation.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import <AsyncDisplayKit/UIImage+ASConvenience.h>
@interface MapHandlerNode () <ASEditableTextNodeDelegate, MKMapViewDelegate>
@property (nonatomic, strong) ASEditableTextNode *latEditableNode;
@property (nonatomic, strong) ASEditableTextNode *lonEditableNode;
@property (nonatomic, strong) ASEditableTextNode *deltaLatEditableNode;
@property (nonatomic, strong) ASEditableTextNode *deltaLonEditableNode;
@property (nonatomic, strong) ASButtonNode *updateRegionButton;
@property (nonatomic, strong) ASButtonNode *liveMapToggleButton;
@property (nonatomic, strong) ASMapNode *mapNode;
@end
@implementation MapHandlerNode
#pragma mark - Lifecycle
- (instancetype)init
{
if (!(self = [super init]))
return nil;
self.automaticallyManagesSubnodes = YES;
_mapNode = [[ASMapNode alloc] init];
_mapNode.mapDelegate = self;
_latEditableNode = [[ASEditableTextNode alloc] init];
_lonEditableNode = [[ASEditableTextNode alloc] init];
_deltaLatEditableNode = [[ASEditableTextNode alloc] init];
_deltaLonEditableNode = [[ASEditableTextNode alloc] init];
_updateRegionButton = [[ASButtonNode alloc] init];
_liveMapToggleButton = [[ASButtonNode alloc] init];
UIImage *backgroundImage = [UIImage as_resizableRoundedImageWithCornerRadius:5
cornerColor:[UIColor whiteColor]
fillColor:[UIColor lightGrayColor]];
UIImage *backgroundHiglightedImage = [UIImage as_resizableRoundedImageWithCornerRadius:5
cornerColor:[UIColor whiteColor]
fillColor:[[UIColor lightGrayColor] colorWithAlphaComponent:0.4]
borderColor:[UIColor lightGrayColor]
borderWidth:2.0];
[_updateRegionButton setBackgroundImage:backgroundImage forState:UIControlStateNormal];
[_updateRegionButton setBackgroundImage:backgroundHiglightedImage forState:UIControlStateHighlighted];
[_liveMapToggleButton setBackgroundImage:backgroundImage forState:UIControlStateNormal];
[_liveMapToggleButton setBackgroundImage:backgroundHiglightedImage forState:UIControlStateHighlighted];
_updateRegionButton.contentEdgeInsets = UIEdgeInsetsMake(5, 5, 5, 5);
[_updateRegionButton setTitle:@"Update Region" withFont:nil withColor:[UIColor blueColor] forState:UIControlStateNormal];
[_updateRegionButton addTarget:self action:@selector(updateRegion) forControlEvents:ASControlNodeEventTouchUpInside];
[_liveMapToggleButton setTitle:[self liveMapStr] withFont:nil withColor:[UIColor blueColor] forState:UIControlStateNormal];
[_liveMapToggleButton addTarget:self action:@selector(toggleLiveMap) forControlEvents:ASControlNodeEventTouchUpInside];
return self;
}
- (void)didLoad
{
[super didLoad];
[self configureEditableNodes:_latEditableNode];
[self configureEditableNodes:_lonEditableNode];
[self configureEditableNodes:_deltaLatEditableNode];
[self configureEditableNodes:_deltaLonEditableNode];
[self updateLocationTextWithMKCoordinateRegion:_mapNode.region];
// avoiding retain cycles
__weak MapHandlerNode *weakSelf = self;
self.mapNode.imageForStaticMapAnnotationBlock = ^UIImage *(id<MKAnnotation> annotation, CGPoint *centerOffset){
MapHandlerNode *grabbedSelf = weakSelf;
if (grabbedSelf) {
if ([annotation isKindOfClass:[CustomMapAnnotation class]]) {
CustomMapAnnotation *customAnnotation = (CustomMapAnnotation *)annotation;
return customAnnotation.image;
}
}
return nil;
};
[self addAnnotations];
}
/**
* ------------------------------------ASStackLayoutSpec-----------------------------------
* | ---------------------------------ASInsetLayoutSpec-------------------------------- |
* | | ------------------------------ASStackLayoutSpec----------------------------- | |
* | | | ---------------------------ASStackLayoutSpec-------------------------- | | |
* | | | | -----------------ASStackLayoutSpec---------------- | | | |
* | | | | | --------------ASStackLayoutSpec------------- | | | | |
* | | | | | | ASEditableTextNode ASEditableTextNode | | | | | |
* | | | | | -------------------------------------------- | | | | |
* | | | | | --------------ASStackLayoutSpec------------- | ASButtonNode | | | |
* | | | | | | ASEditableTextNode ASEditableTextNode | | | | | |
* | | | | | -------------------------------------------- | | | | |
* | | | | -------------------------------------------------- | | | |
* | | | ---------------------------------------------------------------------- | | |
* | | | ASButtonNode | | |
* | | ---------------------------------------------------------------------------- | |
* | ---------------------------------------------------------------------------------- |
* | ASMapNode |
* ----------------------------------------------------------------------------------------
*
* This diagram was created by setting a breakpoint on the returned `layoutSpec`
* and calling "po [layoutSpec asciiArtString]" in the debugger.
*/
#define SPACING 5
#define HEIGHT 30
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
_latEditableNode.style.width = ASDimensionMake(@"50%");
_lonEditableNode.style.width = ASDimensionMake(@"50%");
_deltaLatEditableNode.style.width = ASDimensionMake(@"50%");
_deltaLonEditableNode.style.width = ASDimensionMake(@"50%");
_liveMapToggleButton.style.maxHeight = ASDimensionMake(HEIGHT);
_mapNode.style.flexGrow = 1.0;
ASStackLayoutSpec *lonlatSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
spacing:SPACING
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsCenter
children:@[_latEditableNode, _lonEditableNode]];
ASStackLayoutSpec *deltaLonlatSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
spacing:SPACING
justifyContent:ASStackLayoutJustifyContentSpaceBetween
alignItems:ASStackLayoutAlignItemsCenter
children:@[_deltaLatEditableNode, _deltaLonEditableNode]];
ASStackLayoutSpec *lonlatConfigSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
spacing:SPACING
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsStretch
children:@[lonlatSpec, deltaLonlatSpec]];
lonlatConfigSpec.style.flexGrow = 1.0;
ASStackLayoutSpec *dashboardSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionHorizontal
spacing:SPACING
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsStretch
children:@[lonlatConfigSpec, _updateRegionButton]];
ASStackLayoutSpec *headerVerticalStack = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
spacing:SPACING
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsStretch
children:@[dashboardSpec, _liveMapToggleButton]];
dashboardSpec.style.flexGrow = 1.0;
ASInsetLayoutSpec *insetSpec = [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(40, 10, 0, 10)
child:headerVerticalStack];
ASStackLayoutSpec *layoutSpec = [ASStackLayoutSpec stackLayoutSpecWithDirection:ASStackLayoutDirectionVertical
spacing:SPACING
justifyContent:ASStackLayoutJustifyContentStart
alignItems:ASStackLayoutAlignItemsStretch
children:@[insetSpec, _mapNode]];
return layoutSpec;
}
#pragma mark - Button Actions
- (void)updateRegion
{
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
f.numberStyle = NSNumberFormatterDecimalStyle;
double const lat = [f numberFromString:_latEditableNode.attributedText.string].doubleValue;
double const lon = [f numberFromString:_lonEditableNode.attributedText.string].doubleValue;
double const deltaLat = [f numberFromString:_deltaLatEditableNode.attributedText.string].doubleValue;
double const deltaLon = [f numberFromString:_deltaLonEditableNode.attributedText.string].doubleValue;
// TODO: check for valid latitude / longitude coordinates
MKCoordinateRegion region = MKCoordinateRegionMake(CLLocationCoordinate2DMake(lat, lon),
MKCoordinateSpanMake(deltaLat, deltaLon));
_mapNode.region = region;
}
- (void)toggleLiveMap
{
_mapNode.liveMap = !_mapNode.liveMap;
NSString * const liveMapStr = [self liveMapStr];
[_liveMapToggleButton setTitle:liveMapStr withFont:nil withColor:[UIColor blueColor] forState:UIControlStateNormal];
[_liveMapToggleButton setTitle:liveMapStr withFont:[UIFont systemFontOfSize:14] withColor:[UIColor blueColor] forState:UIControlStateHighlighted];
}
- (void)updateLocationTextWithMKCoordinateRegion:(MKCoordinateRegion)region
{
_latEditableNode.attributedText = [self attributedStringFromFloat:region.center.latitude];
_lonEditableNode.attributedText = [self attributedStringFromFloat:region.center.longitude];
_deltaLatEditableNode.attributedText = [self attributedStringFromFloat:region.span.latitudeDelta];
_deltaLonEditableNode.attributedText = [self attributedStringFromFloat:region.span.longitudeDelta];
}
#pragma mark - Helper Methods
- (NSAttributedString *)attributedStringFromFloat:(CGFloat)value
{
return [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:@"%0.3f", value]];
}
- (void)addAnnotations {
MKPointAnnotation *brno = [MKPointAnnotation new];
brno.coordinate = CLLocationCoordinate2DMake(49.2002211, 16.6078411);
brno.title = @"Brno City";
CustomMapAnnotation *atlantic = [CustomMapAnnotation new];
atlantic.coordinate = CLLocationCoordinate2DMake(38.6442228, -29.9956942);
atlantic.title = @"Atlantic Ocean";
atlantic.image = [UIImage imageNamed:@"Water"];
CustomMapAnnotation *kilimanjaro = [CustomMapAnnotation new];
kilimanjaro.coordinate = CLLocationCoordinate2DMake(-3.075833, 37.353333);
kilimanjaro.title = @"Kilimanjaro";
kilimanjaro.image = [UIImage imageNamed:@"Hill"];
CustomMapAnnotation *mtblanc = [CustomMapAnnotation new];
mtblanc.coordinate = CLLocationCoordinate2DMake(45.8325, 6.864444);
mtblanc.title = @"Mont Blanc";
mtblanc.image = [UIImage imageNamed:@"Hill"];
self.mapNode.annotations = @[brno, atlantic, kilimanjaro, mtblanc];
}
-(NSString *)liveMapStr
{
return _mapNode.liveMap ? @"Live Map is ON" : @"Live Map is OFF";
}
-(void)configureEditableNodes:(ASEditableTextNode *)node
{
node.returnKeyType = node == _deltaLonEditableNode ? UIReturnKeyDone : UIReturnKeyNext;
node.delegate = self;
}
#pragma mark - ASEditableTextNodeDelegate
- (BOOL)editableTextNode:(ASEditableTextNode *)editableTextNode shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if([text isEqualToString:@"\n"]) {
if(editableTextNode == _latEditableNode)
[_lonEditableNode becomeFirstResponder];
else if(editableTextNode == _lonEditableNode)
[_deltaLatEditableNode becomeFirstResponder];
else if(editableTextNode == _deltaLatEditableNode)
[_deltaLonEditableNode becomeFirstResponder];
else if(editableTextNode == _deltaLonEditableNode) {
[_deltaLonEditableNode resignFirstResponder];
[self updateRegion];
}
return NO;
}
NSMutableCharacterSet * s = [NSMutableCharacterSet characterSetWithCharactersInString:@".-"];
[s formUnionWithCharacterSet:[NSCharacterSet decimalDigitCharacterSet]];
[s invert];
NSRange r = [text rangeOfCharacterFromSet:s];
if(r.location != NSNotFound) {
return NO;
}
if([editableTextNode.attributedText.string rangeOfString:@"."].location != NSNotFound &&
[text rangeOfString:@"."].location != NSNotFound) {
return NO;
}
if ([editableTextNode.attributedText.string rangeOfString:@"-"].location != NSNotFound &&
[text rangeOfString:@"-"].location != NSNotFound &&
range.location > 0) {
return NO;
}
return YES;
}
- (MKAnnotationView *)annotationViewForAnnotation:(id<MKAnnotation>)annotation
{
MKAnnotationView *av;
if ([annotation isKindOfClass:[CustomMapAnnotation class]]) {
av = [[MKAnnotationView alloc] init];
av.centerOffset = CGPointMake(21, 21);
av.image = [(CustomMapAnnotation *)annotation image];
} else {
av = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""];
}
av.opaque = NO;
return av;
}
#pragma mark - MKMapViewDelegate
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
[self updateLocationTextWithMKCoordinateRegion:mapView.region];
}
- (MKAnnotationView *)mapView:(MKMapView *)__unused mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
return [self annotationViewForAnnotation:annotation];
}
@end