mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-24 04:16:00 +08:00
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:
24
ReactKit/Views/RCTMap.h
Normal file
24
ReactKit/Views/RCTMap.h
Normal 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
130
ReactKit/Views/RCTMap.m
Normal 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
|
||||
7
ReactKit/Views/RCTMapManager.h
Normal file
7
ReactKit/Views/RCTMapManager.h
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTMapManager : RCTViewManager
|
||||
|
||||
@end
|
||||
119
ReactKit/Views/RCTMapManager.m
Normal file
119
ReactKit/Views/RCTMapManager.m
Normal 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
|
||||
11
ReactKit/Views/RCTPicker.h
Normal file
11
ReactKit/Views/RCTPicker.h
Normal 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
|
||||
91
ReactKit/Views/RCTPicker.m
Normal file
91
ReactKit/Views/RCTPicker.m
Normal 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
|
||||
7
ReactKit/Views/RCTPickerManager.h
Normal file
7
ReactKit/Views/RCTPickerManager.h
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTPickerManager : RCTViewManager
|
||||
|
||||
@end
|
||||
28
ReactKit/Views/RCTPickerManager.m
Normal file
28
ReactKit/Views/RCTPickerManager.m
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user