Updates from Tue Mar 10

- [ReactNative] Make tests run on TravisCI | Alex Kotliarskyi
- [Relay] Update Relay + ES6 class containers | Christoph Pojer
- [React Native] Add RCTAdSupport.xcodeproj | Alexsander Akers
- [ReactNative][Android] Fix after a new React version was downstreamed | Philipp von Weitershausen
- [React Native] Add preliminary animation API | Alex Akers
- [ReactKit] Create test for OSS ReactKit | Alex Kotliarskyi
- [React Native][Device ID][wip] implement most basic js access | Alex Akers
- [ReactNative] OSS Picker | Spencer Ahrens
- [ReactNative] Fix typo in RCTUIManager | Tadeu Zagallo
- [ReactNative] Fix GeoLocation files letter case | Tadeu Zagallo
- Unified the method signature for addUIBlock: to further simplify porting ViewManagers | Nick Lockwood
- [ReactNative] Oss GeoMap | Tadeu Zagallo
- [ReactNative] OSS CameraRoll | Tadeu Zagallo
- [ReactNative] allowLossyConversion on NSString->NSData conversion | Andrew Rasmussen
- [React Native][RFC] Print __DEV__ value on app start | Alex Kotliarskyi
This commit is contained in:
Alex Kotliarskyi
2015-03-10 19:11:28 -07:00
parent 83581cfe6b
commit fab5ec617d
71 changed files with 4353 additions and 82 deletions

24
ReactKit/Views/RCTMap.h Normal file
View File

@@ -0,0 +1,24 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <MapKit/MapKit.h>
#import <UIKit/UIKit.h>
extern const CLLocationDegrees RCTMapDefaultSpan;
extern const NSTimeInterval RCTMapRegionChangeObserveInterval;
extern const CGFloat RCTMapZoomBoundBuffer;
@class RCTEventDispatcher;
@interface RCTMap: MKMapView
@property (nonatomic, assign) BOOL followUserLocation;
@property (nonatomic, copy) NSDictionary *JSONRegion;
@property (nonatomic, assign) CGFloat minDelta;
@property (nonatomic, assign) CGFloat maxDelta;
@property (nonatomic, assign) UIEdgeInsets legalLabelInsets;
@property (nonatomic, strong) NSTimer *regionChangeObserveTimer;
@end
#define FLUSH_NAN(value) \
(isnan(value) ? 0 : value)

130
ReactKit/Views/RCTMap.m Normal file
View File

@@ -0,0 +1,130 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTMap.h"
#import "RCTEventDispatcher.h"
#import "RCTLog.h"
#import "RCTUtils.h"
const CLLocationDegrees RCTMapDefaultSpan = 0.005;
const NSTimeInterval RCTMapRegionChangeObserveInterval = 0.1;
const CGFloat RCTMapZoomBoundBuffer = 0.01;
@interface RCTMap()
@property (nonatomic, strong) UIView *legalLabel;
@property (nonatomic, strong) CLLocationManager *locationManager;
@end
@implementation RCTMap
- (instancetype)init
{
self = [super init];
if (self) {
// Find Apple link label
for (UIView *subview in self.subviews) {
if ([NSStringFromClass(subview.class) isEqualToString:@"MKAttributionLabel"]) {
// This check is super hacky, but the whole premise of moving around Apple's internal subviews is super hacky
_legalLabel = subview;
break;
}
}
}
return self;
}
- (void)dealloc
{
[self.regionChangeObserveTimer invalidate];
}
- (void)layoutSubviews
{
[super layoutSubviews];
// Force resize subviews - only the layer is resized by default
CGRect mapFrame = self.frame;
self.frame = CGRectZero;
self.frame = mapFrame;
if (_legalLabel) {
dispatch_async(dispatch_get_main_queue(), ^{
CGRect frame = _legalLabel.frame;
if (_legalLabelInsets.left) {
frame.origin.x = _legalLabelInsets.left;
} else if (_legalLabelInsets.right) {
frame.origin.x = mapFrame.size.width - _legalLabelInsets.right - frame.size.width;
}
if (_legalLabelInsets.top) {
frame.origin.y = _legalLabelInsets.top;
} else if (_legalLabelInsets.bottom) {
frame.origin.y = mapFrame.size.height - _legalLabelInsets.bottom - frame.size.height;
}
_legalLabel.frame = frame;
});
}
}
#pragma mark Accessors
- (void)setShowsUserLocation:(BOOL)showsUserLocation
{
if (self.showsUserLocation != showsUserLocation) {
if (showsUserLocation && !_locationManager) {
_locationManager = [[CLLocationManager alloc] init];
if ([_locationManager respondsToSelector:@selector(requestWhenInUseAuthorization)]) {
[_locationManager requestWhenInUseAuthorization];
}
}
[super setShowsUserLocation:showsUserLocation];
// If it needs to show user location, force map view centered
// on user's current location on user location updates
self.followUserLocation = showsUserLocation;
}
}
- (void)setJSONRegion:(NSDictionary *)region
{
if (region) {
MKCoordinateRegion coordinateRegion = self.region;
if ([region[@"latitude"] isKindOfClass:[NSNumber class]]) {
coordinateRegion.center.latitude = [region[@"latitude"] doubleValue];
} else {
RCTLogError(@"region must include numeric latitude, got: %@", region);
return;
}
if ([region[@"longitude"] isKindOfClass:[NSNumber class]]) {
coordinateRegion.center.longitude = [region[@"longitude"] doubleValue];
} else {
RCTLogError(@"region must include numeric longitude, got: %@", region);
return;
}
if ([region[@"latitudeDelta"] isKindOfClass:[NSNumber class]]) {
coordinateRegion.span.latitudeDelta = [region[@"latitudeDelta"] doubleValue];
}
if ([region[@"longitudeDelta"] isKindOfClass:[NSNumber class]]) {
coordinateRegion.span.longitudeDelta = [region[@"longitudeDelta"] doubleValue];
}
[self setRegion:coordinateRegion animated:YES];
}
}
- (NSDictionary *)JSONRegion
{
MKCoordinateRegion region = self.region;
if (!CLLocationCoordinate2DIsValid(region.center)) {
return nil;
}
return @{
@"latitude": @(FLUSH_NAN(region.center.latitude)),
@"longitude": @(FLUSH_NAN(region.center.longitude)),
@"latitudeDelta": @(FLUSH_NAN(region.span.latitudeDelta)),
@"longitudeDelta": @(FLUSH_NAN(region.span.longitudeDelta)),
};
}
@end

View File

@@ -0,0 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTViewManager.h"
@interface RCTMapManager : RCTViewManager
@end

View File

@@ -0,0 +1,119 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTMapManager.h"
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#import "RCTMap.h"
#import "UIView+ReactKit.h"
@interface RCTMapManager() <MKMapViewDelegate>
@end
@implementation RCTMapManager
- (UIView *)view
{
RCTMap *map = [[RCTMap alloc] init];
map.delegate = self;
return map;
}
RCT_EXPORT_VIEW_PROPERTY(showsUserLocation);
RCT_EXPORT_VIEW_PROPERTY(zoomEnabled);
RCT_EXPORT_VIEW_PROPERTY(rotateEnabled);
RCT_EXPORT_VIEW_PROPERTY(pitchEnabled);
RCT_EXPORT_VIEW_PROPERTY(scrollEnabled);
RCT_EXPORT_VIEW_PROPERTY(maxDelta);
RCT_EXPORT_VIEW_PROPERTY(minDelta);
RCT_EXPORT_VIEW_PROPERTY(legalLabelInsets);
RCT_REMAP_VIEW_PROPERTY(region, JSONRegion)
#pragma mark MKMapViewDelegate
- (void)mapView:(RCTMap *)mapView didUpdateUserLocation:(MKUserLocation *)location
{
if (mapView.followUserLocation) {
MKCoordinateRegion region;
region.span.latitudeDelta = RCTMapDefaultSpan;
region.span.longitudeDelta = RCTMapDefaultSpan;
region.center = location.coordinate;
[mapView setRegion:region animated:YES];
// Move to user location only for the first time it loads up.
mapView.followUserLocation = NO;
}
}
- (void)mapView:(RCTMap *)mapView regionWillChangeAnimated:(BOOL)animated
{
[self _regionChanged:mapView];
mapView.regionChangeObserveTimer = [NSTimer timerWithTimeInterval:RCTMapRegionChangeObserveInterval
target:self
selector:@selector(_onTick:)
userInfo:@{ @"mapView": mapView }
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:mapView.regionChangeObserveTimer forMode:NSRunLoopCommonModes];
}
- (void)mapView:(RCTMap *)mapView regionDidChangeAnimated:(BOOL)animated
{
[self _regionChanged:mapView];
[self _emitRegionChangeEvent:mapView continuous:NO];
[mapView.regionChangeObserveTimer invalidate];
mapView.regionChangeObserveTimer = nil;
}
#pragma mark Private
- (void)_onTick:(NSTimer *)timer
{
[self _regionChanged:timer.userInfo[@"mapView"]];
}
- (void)_regionChanged:(RCTMap *)mapView
{
BOOL needZoom = NO;
CGFloat newLongitudeDelta = 0.0f;
MKCoordinateRegion region = mapView.region;
// On iOS 7, it's possible that we observe invalid locations during initialization of the map.
// Filter those out.
if (!CLLocationCoordinate2DIsValid(region.center)) {
return;
}
// Calculation on float is not 100% accurate. If user zoom to max/min and then move, it's likely the map will auto zoom to max/min from time to time.
// So let's try to make map zoom back to 99% max or 101% min so that there are some buffer that moving the map won't constantly hitting the max/min bound.
if (mapView.maxDelta > FLT_EPSILON && region.span.longitudeDelta > mapView.maxDelta) {
needZoom = YES;
newLongitudeDelta = mapView.maxDelta * (1 - RCTMapZoomBoundBuffer);
} else if (mapView.minDelta > FLT_EPSILON && region.span.longitudeDelta < mapView.minDelta) {
needZoom = YES;
newLongitudeDelta = mapView.minDelta * (1 + RCTMapZoomBoundBuffer);
}
if (needZoom) {
region.span.latitudeDelta = region.span.latitudeDelta / region.span.longitudeDelta * newLongitudeDelta;
region.span.longitudeDelta = newLongitudeDelta;
mapView.region = region;
}
// Continously observe region changes
[self _emitRegionChangeEvent:mapView continuous:YES];
}
- (void)_emitRegionChangeEvent:(RCTMap *)mapView continuous:(BOOL)continuous
{
NSDictionary *region = mapView.JSONRegion;
if (region) {
NSDictionary *event = @{
@"target": [mapView reactTag],
@"continuous": @(continuous),
@"region": mapView.JSONRegion,
};
[self.bridge.eventDispatcher sendInputEventWithName:@"topChange" body:event];
}
}
@end

View File

@@ -0,0 +1,11 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
@interface RCTPicker : UIPickerView
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
@end

View File

@@ -0,0 +1,91 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTPicker.h"
#import "RCTConvert.h"
#import "RCTEventDispatcher.h"
#import "RCTUtils.h"
#import "UIView+ReactKit.h"
const NSInteger UNINITIALIZED_INDEX = -1;
@interface RCTPicker() <UIPickerViewDataSource, UIPickerViewDelegate>
{
RCTEventDispatcher *_eventDispatcher;
NSArray *_items;
NSInteger _selectedIndex;
}
@end
@implementation RCTPicker
- (id)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if (self = [super initWithFrame:CGRectZero]) {
_eventDispatcher = eventDispatcher;
_selectedIndex = UNINITIALIZED_INDEX;
self.delegate = self;
}
return self;
}
- (void)setItems:(NSArray *)items
{
if (_items != items) {
_items = [items copy];
[self setNeedsLayout];
}
}
- (void)setSelectedIndex:(NSInteger)selectedIndex
{
if (_selectedIndex != selectedIndex) {
BOOL animated = _selectedIndex != UNINITIALIZED_INDEX; // Don't animate the initial value
_selectedIndex = selectedIndex;
dispatch_async(dispatch_get_main_queue(), ^{
[self selectRow:selectedIndex inComponent:0 animated:animated];
});
}
}
#pragma mark - UIPickerViewDataSource protocol
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return [_items count];
}
#pragma mark - UIPickerViewDelegate methods
- (NSDictionary *)itemForRow:(NSInteger)row
{
return (NSDictionary*)[_items objectAtIndex:row];
}
- (id)valueForRow:(NSInteger)row
{
return [self itemForRow:row][@"value"];
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return [self itemForRow:row][@"label"];
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
_selectedIndex = row;
NSDictionary *event = @{
@"target": self.reactTag,
@"newIndex": @(row),
@"newValue": [self valueForRow:row]
};
[_eventDispatcher sendInputEventWithName:@"topChange" body:event];
}
@end

View File

@@ -0,0 +1,7 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTViewManager.h"
@interface RCTPickerManager : RCTViewManager
@end

View File

@@ -0,0 +1,28 @@
// Copyright 2004-present Facebook. All Rights Reserved.
#import "RCTPickerManager.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
#import "RCTPicker.h"
@implementation RCTPickerManager
- (UIView *)view
{
return [[RCTPicker alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
}
RCT_EXPORT_VIEW_PROPERTY(items)
RCT_EXPORT_VIEW_PROPERTY(selectedIndex)
- (NSDictionary *)constantsToExport
{
RCTPicker *pv = [[RCTPicker alloc] init];
return @{
@"ComponentHeight": @(CGRectGetHeight(pv.frame)),
@"ComponentWidth": @(CGRectGetWidth(pv.frame))
};
}
@end

View File

@@ -17,7 +17,7 @@
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
{
if ((self = [super initWithFrame:CGRectZero])) {
_eventDispatcher = eventDispatcher;
[self addTarget:self action:@selector(_textFieldDidChange) forControlEvents:UIControlEventEditingChanged];
[self addTarget:self action:@selector(_textFieldBeginEditing) forControlEvents:UIControlEventEditingDidBegin];
@@ -40,7 +40,7 @@
- (void)removeReactSubview:(UIView *)subview
{
// TODO: this is a bit broken - if the TextField inserts any of
// it's own views below or between React's, the indices won't match
// its own views below or between React's, the indices won't match
[_reactSubviews removeObject:subview];
[subview removeFromSuperview];
}
@@ -48,7 +48,7 @@
- (void)insertReactSubview:(UIView *)view atIndex:(NSInteger)atIndex
{
// TODO: this is a bit broken - if the TextField inserts any of
// it's own views below or between React's, the indices won't match
// its own views below or between React's, the indices won't match
[_reactSubviews insertObject:view atIndex:atIndex];
[super insertSubview:view atIndex:atIndex];
}
@@ -74,7 +74,7 @@
- (void)setAutoCorrect:(BOOL)autoCorrect
{
[super setAutocorrectionType:(autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo)];
self.autocorrectionType = (autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo);
}
- (BOOL)autoCorrect
@@ -117,7 +117,6 @@ RCT_TEXT_EVENT_HANDLER(_textFieldSubmitEditing, RCTTextEventTypeSubmit)
return result;
}
// Prevent native from becoming first responder (TODO: why?)
- (BOOL)canBecomeFirstResponder
{
return _jsRequestingFirstResponder;

View File

@@ -20,7 +20,6 @@ RCT_REMAP_VIEW_PROPERTY(autoCapitalize, autocapitalizationType)
RCT_EXPORT_VIEW_PROPERTY(enabled)
RCT_EXPORT_VIEW_PROPERTY(placeholder)
RCT_EXPORT_VIEW_PROPERTY(text)
RCT_EXPORT_VIEW_PROPERTY(font)
RCT_EXPORT_VIEW_PROPERTY(clearButtonMode)
RCT_EXPORT_VIEW_PROPERTY(keyboardType)
RCT_REMAP_VIEW_PROPERTY(color, textColor)