From 6498126090108d3fe0ae50848ae64142eaeee1f8 Mon Sep 17 00:00:00 2001 From: nyura123 Date: Sun, 7 Feb 2016 15:44:23 -0600 Subject: [PATCH 01/12] feature to render react components into reusable tableviewcells - RNReactModuleCell; NOTE: needs 2 lines changed in RCTRootView.m - see comments in RNReactModuleCell.h --- RNTableView.xcodeproj/project.pbxproj | 6 + RNTableView/RNReactModuleCell.h | 27 +++ RNTableView/RNReactModuleCell.m | 44 +++++ RNTableView/RNTableView.h | 4 +- RNTableView/RNTableView.m | 35 +++- RNTableView/RNTableViewManager.m | 7 +- examples/TableViewDemo/index.ios.js | 252 ++++++++++++++++++++++++-- examples/TableViewDemo/package.json | 8 +- 8 files changed, 359 insertions(+), 24 deletions(-) create mode 100644 RNTableView/RNReactModuleCell.h create mode 100644 RNTableView/RNReactModuleCell.m diff --git a/RNTableView.xcodeproj/project.pbxproj b/RNTableView.xcodeproj/project.pbxproj index 83dfa03..c78beb8 100644 --- a/RNTableView.xcodeproj/project.pbxproj +++ b/RNTableView.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0808A0331C67E9A60038993A /* RNReactModuleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0808A0321C67E9A60038993A /* RNReactModuleCell.m */; }; 872545E11BAAC85D00889249 /* JSONDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 872545D61BAAC85D00889249 /* JSONDataSource.m */; }; 872545E21BAAC85D00889249 /* RNCellView.m in Sources */ = {isa = PBXBuildFile; fileRef = 872545D81BAAC85D00889249 /* RNCellView.m */; }; 872545E31BAAC85D00889249 /* RNCellViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 872545DA1BAAC85D00889249 /* RNCellViewManager.m */; }; @@ -32,6 +33,8 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0808A0311C67E9A60038993A /* RNReactModuleCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNReactModuleCell.h; sourceTree = ""; }; + 0808A0321C67E9A60038993A /* RNReactModuleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNReactModuleCell.m; sourceTree = ""; }; 872545D51BAAC85D00889249 /* JSONDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONDataSource.h; sourceTree = ""; }; 872545D61BAAC85D00889249 /* JSONDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONDataSource.m; sourceTree = ""; }; 872545D71BAAC85D00889249 /* RNCellView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCellView.h; sourceTree = ""; }; @@ -69,6 +72,8 @@ 872545D41BAAC85D00889249 /* RNTableView */ = { isa = PBXGroup; children = ( + 0808A0311C67E9A60038993A /* RNReactModuleCell.h */, + 0808A0321C67E9A60038993A /* RNReactModuleCell.m */, 872545D51BAAC85D00889249 /* JSONDataSource.h */, 872545D61BAAC85D00889249 /* JSONDataSource.m */, 872545D71BAAC85D00889249 /* RNCellView.h */, @@ -177,6 +182,7 @@ 872545E31BAAC85D00889249 /* RNCellViewManager.m in Sources */, 8799E1E01BF1152400AF9A67 /* RNTableFooterViewManager.m in Sources */, 872545E51BAAC85D00889249 /* RNTableViewCell.m in Sources */, + 0808A0331C67E9A60038993A /* RNReactModuleCell.m in Sources */, 8799E1DD1BF114F900AF9A67 /* RNTableFooterView.m in Sources */, 8799E1E31BF117F600AF9A67 /* RNTableHeaderView.m in Sources */, 8799E1E61BF1181400AF9A67 /* RNTableHeaderViewManager.m in Sources */, diff --git a/RNTableView/RNReactModuleCell.h b/RNTableView/RNReactModuleCell.h new file mode 100644 index 0000000..dc0bc89 --- /dev/null +++ b/RNTableView/RNReactModuleCell.h @@ -0,0 +1,27 @@ + +#import + +//Use react-native root views as reusable cells returned from cellForRowAtIndexPath. + +/* + Two react-native changes in RCTRootView.m are needed to allow re-rendering into the same view: + + 1. In initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties: + if (!_bridge.loading) { + [self bundleFinishedLoading:_bridge]; //instead of _bridge.batchedBridge + } + + 2. In setAppProperties:(NSDictionary *)appProperties: + if (_contentView && _bridge.valid && !_bridge.loading) { + [self runApplication:_bridge]; //instead of _bridge.batchedBridge + } + */ + +@interface RNReactModuleCell : UITableViewCell { +} + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier bridge:(RCTBridge*) bridge data:(NSDictionary*)data indexPath:(NSIndexPath*)indexPath reactModule:(NSString*)reactModule; + +-(void)setUpAndConfigure:(NSDictionary*)data bridge:(RCTBridge*)bridge indexPath:(NSIndexPath*)indexPath reactModule:(NSString*)reactModule; + +@end \ No newline at end of file diff --git a/RNTableView/RNReactModuleCell.m b/RNTableView/RNReactModuleCell.m new file mode 100644 index 0000000..deed205 --- /dev/null +++ b/RNTableView/RNReactModuleCell.m @@ -0,0 +1,44 @@ +// +// RNReactModuleCell.m +// RNTableView +// +// Created by Anna Berman on 2/6/16. +// Copyright © 2016 Pavlo Aksonov. All rights reserved. +// + +#import +#import "RNReactModuleCell.h" + +@implementation RNReactModuleCell { + RCTRootView *_rootView; +} + +- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier bridge:(RCTBridge*) bridge data:(NSDictionary*)data indexPath:(NSIndexPath*)indexPath reactModule:(NSString*)reactModule +{ + self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; + if (self) { + [self setUpAndConfigure:data bridge:bridge indexPath:indexPath reactModule:reactModule]; + } + return self; +} + +-(NSDictionary*) toProps:(NSDictionary *)data indexPath:(NSIndexPath*)indexPath { + return @{@"data":data, @"section":[[NSNumber alloc] initWithLong:indexPath.section], @"row":[[NSNumber alloc] initWithLong:indexPath.row]}; +} + +-(void)setUpAndConfigure:(NSDictionary*)data bridge:(RCTBridge*)bridge indexPath:(NSIndexPath*)indexPath reactModule:(NSString*)reactModule{ + NSDictionary *props = [self toProps:data indexPath:indexPath]; + if (_rootView == nil) { + //Create the mini react app that will populate our cell. This will be called from cellForRowAtIndexPath + _rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:reactModule initialProperties:props]; + [self.contentView addSubview:_rootView]; + _rootView.frame = self.contentView.frame; + } else { + //Ask react to re-render us with new data + _rootView.appProperties = props; + } + + //The application will be unmounted in javascript when the cell/rootview is destroyed +} + +@end diff --git a/RNTableView/RNTableView.h b/RNTableView/RNTableView.h index f8cd9d9..3be4fe9 100644 --- a/RNTableView/RNTableView.h +++ b/RNTableView/RNTableView.h @@ -8,6 +8,7 @@ #import @class RCTEventDispatcher; +@class RCTBridge; @protocol RNTableViewDatasource @@ -21,7 +22,7 @@ @interface RNTableView : UIView -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher bridge:(RCTBridge*) bridge NS_DESIGNATED_INITIALIZER; @property (nonatomic, copy) NSMutableArray *sections; @property (nonatomic, copy) NSArray *additionalItems; @@ -58,5 +59,6 @@ @property (nonatomic) BOOL autoFocus; @property (nonatomic) BOOL allowsToggle; @property (nonatomic) BOOL allowsMultipleSelection; +@property (nonatomic) NSString *reactModuleForCell; @end diff --git a/RNTableView/RNTableView.m b/RNTableView/RNTableView.m index 987b516..ba0d31c 100644 --- a/RNTableView/RNTableView.m +++ b/RNTableView/RNTableView.m @@ -15,6 +15,7 @@ #import "RNCellView.h" #import "RNTableFooterView.h" #import "RNTableHeaderView.h" +#import "RNReactModuleCell.h" @interface RNTableView() { id datasource; @@ -25,9 +26,11 @@ @interface RNTableView() { @end @implementation RNTableView { + RCTBridge *_bridge; RCTEventDispatcher *_eventDispatcher; NSArray *_items; NSMutableArray *_cells; + NSString *_reactModuleCellReuseIndentifier; } -(void)setEditing:(BOOL)editing { @@ -66,11 +69,12 @@ - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex } } -- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher bridge:(RCTBridge*)bridge { RCTAssertParam(eventDispatcher); if ((self = [super initWithFrame:CGRectZero])) { + _bridge = bridge; _eventDispatcher = eventDispatcher; _cellHeight = 44; _cells = [NSMutableArray array]; @@ -148,6 +152,8 @@ - (void)createTableView { _tableView.tableHeaderView = view; _tableView.tableFooterView = view; _tableView.separatorStyle = self.separatorStyle; + _reactModuleCellReuseIndentifier = @"ReactModuleCell"; + [_tableView registerClass:[RNReactModuleCell class] forCellReuseIdentifier:_reactModuleCellReuseIndentifier]; [self addSubview:_tableView]; } - (void)tableView:(UITableView *)tableView willDisplayFooterView:(nonnull UIView *)view forSection:(NSInteger)section { @@ -279,6 +285,7 @@ - (void)setSections:(NSArray *)sections if(selectedIndex == -1) selectedIndex = [items count]; itemData[@"selected"] = @YES; + found = YES; } [items addObject:itemData]; @@ -312,20 +319,35 @@ -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger) } return count; } + +-(UITableViewCell*)setupReactModuleCell:(UITableView *)tableView data:(NSDictionary*)data indexPath:(NSIndexPath *)indexPath { + RNReactModuleCell *cell = [tableView dequeueReusableCellWithIdentifier:_reactModuleCellReuseIndentifier]; + if (cell == nil) { + cell = [[RNReactModuleCell alloc] initWithStyle:self.tableViewCellStyle reuseIdentifier:_reactModuleCellReuseIndentifier bridge: _bridge data:data indexPath:indexPath reactModule:_reactModuleForCell]; + } else { + [cell setUpAndConfigure:data bridge:_bridge indexPath:indexPath reactModule:_reactModuleForCell]; + } + return cell; +} + -(UITableViewCell* )tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { - UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; + UITableViewCell *cell = nil; NSDictionary *item = [self dataForRow:indexPath.item section:indexPath.section]; // check if it is standard cell or user-defined UI - if (![self hasCustomCells:indexPath.section]){ + if ([self hasCustomCells:indexPath.section]){ + cell = ((RNCellView *)_cells[indexPath.section][indexPath.row]).tableViewCell; + } else if (self.reactModuleForCell != nil && ![self.reactModuleForCell isEqualToString:@""]) { + cell = [self setupReactModuleCell:tableView data:item indexPath:indexPath]; + } else { + cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:self.tableViewCellStyle reuseIdentifier:@"Cell"]; } cell.textLabel.text = item[@"label"]; cell.detailTextLabel.text = item[@"detail"]; - } else { - cell = ((RNCellView *)_cells[indexPath.section][indexPath.row]).tableViewCell; } + if (item[@"selected"] && [item[@"selected"] intValue]){ cell.accessoryType = UITableViewCellAccessoryCheckmark; } else if ([item[@"arrow"] intValue]) { @@ -354,7 +376,8 @@ -(NSMutableDictionary *)dataForRow:(NSInteger)row section:(NSInteger)section { -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if (![self hasCustomCells:indexPath.section]){ - return _cellHeight; + NSNumber *styleHeight = _sections[indexPath.section][@"items"][indexPath.row][@"height"]; + return styleHeight.floatValue ?: _cellHeight; } else { RNCellView *cell = (RNCellView *)_cells[indexPath.section][indexPath.row]; CGFloat height = cell.componentHeight; diff --git a/RNTableView/RNTableViewManager.m b/RNTableView/RNTableViewManager.m index 245be24..413622f 100644 --- a/RNTableView/RNTableViewManager.m +++ b/RNTableView/RNTableViewManager.m @@ -16,7 +16,7 @@ @implementation RNTableViewManager RCT_EXPORT_MODULE() - (UIView *)view { - return [[RNTableView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; + return [[RNTableView alloc] initWithEventDispatcher:self.bridge.eventDispatcher bridge:self.bridge]; } RCT_EXPORT_VIEW_PROPERTY(sections, NSArray) @@ -56,6 +56,11 @@ - (UIView *)view [view setTableViewCellEditingStyle:[RCTConvert NSInteger:json]]; } +/*Each cell is a separate app, multiple cells share the app/module name*/ +RCT_CUSTOM_VIEW_PROPERTY(reactModuleForCell, NSString*, RNTableView) { + [view setReactModuleForCell:[RCTConvert NSString:json]]; +} + RCT_CUSTOM_VIEW_PROPERTY(contentInset, UIEdgeInsets, RNTableView) { [view setContentInset:[RCTConvert UIEdgeInsets:json]]; } diff --git a/examples/TableViewDemo/index.ios.js b/examples/TableViewDemo/index.ios.js index 094d95d..21d3f1c 100755 --- a/examples/TableViewDemo/index.ios.js +++ b/examples/TableViewDemo/index.ios.js @@ -8,6 +8,7 @@ var Item = TableView.Item; var Cell = TableView.Cell; var {Actions, Router, Route, Schema, Animations} = require('react-native-router-flux'); var NavigationBar = require('react-native-navbar'); +var Firebase = require('firebase'); class NavBar extends React.Component { render(){ @@ -74,8 +75,6 @@ class Example3 extends React.Component { render(){ return ( alert(JSON.stringify(event))}> @@ -112,6 +111,127 @@ class Example3 extends React.Component { } } +//Similar to example 2 but use "TableViewExampleCell" reusable cells +class ReusableCellExample1 extends React.Component { + // list spanish provinces and add 'All states' item at the beginning + render() { + var country = "ES"; + return ( + alert(JSON.stringify(event))}> + All states + + ); + } +} + +class ReusableCellExample2 extends React.Component { + render(){ + var numAdditionaItems = 1000; + var moreItems = []; + for (var i = 0; i < numAdditionaItems; ++i) { + moreItems.push(i); + } + return ( + alert(JSON.stringify(event))}> +
+ Item 1 + Item 2 + Item 3 + Item 4 + Item 5 + Item 6 + Item 7 + Item 8 + Item 9 + Item 10 + Item 11 + Item 12 + Item 13 + Item 14 + Item 15 + Item 16 +
+
+ Item 1 + Item 2 + Item 3 +
+
+ Item 1 + Item 2 + Item 3 +
+
+ {moreItems.map((i)=>{i+1})} +
+
+ ); + } +} + +class FirebaseExample extends React.Component { + constructor(props) { + super(props); + this.state = {data:null}; + this.reactCellModule = "DinosaurCellExample"; + this.firebaseLocation = "https://dinosaur-facts.firebaseio.com/dinosaurs"; + this.propPrefix = "dinosaur"; + } + componentDidMount() { + var dinData = null; + var self = this; + this.ref = new Firebase(this.firebaseLocation); + this.ref.on('value', function(snapshot) { + self.setState({data:snapshot.val()}); + }); + } + componentWillUnmount() { + this.ref.off(); + } + renderItem(itemData, key, index) { + //TODO passing itemData={itemData} doesn't seem to work... so pass all data props with a prefix to make sure they don't clash + //with other props + var item = {}; + Object.keys(itemData||{}).forEach(k => { + item[this.propPrefix+k] = itemData[k]; + }); + item[this.propPrefix+"key"] = key; + + //concat all key-val's into one string + var label = Object.keys(itemData).map((k)=>k+":"+itemData[k]).join(","); + + return (); + } + render() { + var data = this.state.data; + if (!data) { + return NO DATA + } + + var self = this; + var items = Object.keys(data).map((key,index)=>self.renderItem(data[key], key, index)); + + return ( + + All Items + alert(JSON.stringify(event))}> +
+ {items} +
+
+
+ ); + } +} + class Edit extends React.Component { constructor(props){ super(props); @@ -124,23 +244,67 @@ class Edit extends React.Component { self.setState({editing: !self.state.editing})}/> alert(JSON.stringify(event))} onChange={(event) => alert("CHANGED:"+JSON.stringify(event))}> -
- Item 1 - Item 2 - Item 3 - Item 4 - Item 5 - Item 6 - Item 7 - Item 8 + onPress={(event) => alert(JSON.stringify(event))} onChange={(event) => alert("CHANGED:"+JSON.stringify(event))}> +
+ Item 1 + Item 2 + Item 3 + Item 4 + Item 5 + Item 6 + Item 7 + Item 8 +
+ + + ); + } +} +class ListViewExample extends React.Component { + constructor(props){ + super(props); + this.numAdditionaItems = 1000; + this.data = {}; + for (var i = 0; i < this.numAdditionaItems; ++i) { + this.data[i] = i; + } + this.state = {dataSource: new React.ListView.DataSource({ + rowHasChanged: (r1, r2) => r1 !== r2 + })}; + } + render() { + const data = this.data; + return ( + alert("item:"+k+", "+data[k])}> data: {data[k]}} + /> + ); + } +} + +class LargeTableExample extends React.Component { + render() { + var numAdditionaItems = 1000; + var items = []; + for (var i = 0; i < numAdditionaItems; ++i) { + items.push(i); + } + return ( + alert(JSON.stringify(event))}> +
+ {items.map((i)=>{i+1})}
- ); } } + class Launch extends React.Component { constructor(props) { super(props); @@ -158,6 +322,11 @@ class Launch extends React.Component { Example with app bundle JSON data Example with multiple sections Example with editing mode + Reusable Cell Example 1 + Reusable Custom Cells + Firebase Example + Large ListView (scroll memory growth) + Reusable Large TableView Example
); @@ -174,10 +343,67 @@ class TableViewExample extends React.Component { + + + + + ); } } +//Should be pure... setState on top-level component doesn't seem to work +class TableViewExampleCell extends React.Component { + render(){ + var style = {}; + //cell height is passed from child of tableview and native code passes it back up to javascript in "app params" for the cell. + //This way our component will fill the full native table cell height. + if (this.props.data.height !== undefined) { + style.height = this.props.data.height; + } + if (this.props.data.backgroundColor !== undefined) { + style.backgroundColor = this.props.data.backgroundColor; + } + return (section:{this.props.section},row:{this.props.row},label:{this.props.data.label}); + } +} + +//Should be pure... setState on top-level component doesn't seem to work +class DinosaurCellExample extends React.Component { + yearsAgoInMil(num) { + return ((-1 * num)/1000000)+" million years ago"; + } + render(){ + var style = {}; + //cell height is passed from child of tableview and native code passes it back up to javascript in "app params" for the cell. + //This way our component will fill the full native table cell height. + if (this.props.data.height !== undefined) { + style.height = this.props.data.height; + } + if (this.props.data.backgroundColor !== undefined) { + style.backgroundColor = this.props.data.backgroundColor; + } + style.borderColor = "grey"; + style.borderRadius = 0.02; + + var appeared = this.yearsAgoInMil(this.props.data.dinosaurappeared); + var vanished = this.yearsAgoInMil(this.props.data.dinosaurvanished); + return ( + Name: {this.props.data.dinosaurkey} + Order:{this.props.data.dinosaurorder} + Appeared: {appeared} + Vanished: {vanished} + Height: {this.props.data.dinosaurheight} + Length: {this.props.data.dinosaurlength} + Weight: {this.props.data.dinosaurweight} + ); + } +} + + + AppRegistry.registerComponent('TableViewExample', () => TableViewExample); +AppRegistry.registerComponent('TableViewExampleCell', () => TableViewExampleCell); +AppRegistry.registerComponent('DinosaurCellExample', () => DinosaurCellExample); diff --git a/examples/TableViewDemo/package.json b/examples/TableViewDemo/package.json index 66d6612..f213d20 100644 --- a/examples/TableViewDemo/package.json +++ b/examples/TableViewDemo/package.json @@ -6,9 +6,11 @@ "start": "node_modules/react-native/packager/packager.sh" }, "dependencies": { - "react-native": "^0.14.2", + "firebase": "^2.4.0", + "react-native": "^0.19.0", "react-native-navbar": "^0.8.2", - "react-native-router-flux": "^0.3.0", - "react-native-tableview": "1.4.1" + "react-native-router-flux": "^2.1.8", + "react-native-tableview": "1.4.2", + "react-native-tabs": "^0.1.10" } } From 148e0f986e38e5f0d2274813677de232377a1921 Mon Sep 17 00:00:00 2001 From: nyura123 Date: Sun, 7 Feb 2016 18:11:53 -0600 Subject: [PATCH 02/12] restore lost lines in example3 --- examples/TableViewDemo/index.ios.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/TableViewDemo/index.ios.js b/examples/TableViewDemo/index.ios.js index 21d3f1c..1f7304a 100755 --- a/examples/TableViewDemo/index.ios.js +++ b/examples/TableViewDemo/index.ios.js @@ -75,6 +75,8 @@ class Example3 extends React.Component { render(){ return ( alert(JSON.stringify(event))}> From e92187cd414ba37651de316d7eb1c35d1370de4a Mon Sep 17 00:00:00 2001 From: nyura123 Date: Sun, 7 Feb 2016 19:28:07 -0600 Subject: [PATCH 03/12] rounded radius in a few demo examples --- examples/TableViewDemo/index.ios.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/TableViewDemo/index.ios.js b/examples/TableViewDemo/index.ios.js index 1f7304a..bf5ec0e 100755 --- a/examples/TableViewDemo/index.ios.js +++ b/examples/TableViewDemo/index.ios.js @@ -359,11 +359,13 @@ class TableViewExample extends React.Component { //Should be pure... setState on top-level component doesn't seem to work class TableViewExampleCell extends React.Component { render(){ - var style = {}; + var style = {borderColor:"#aaaaaa", borderWidth:1, borderRadius:3}; //cell height is passed from child of tableview and native code passes it back up to javascript in "app params" for the cell. //This way our component will fill the full native table cell height. if (this.props.data.height !== undefined) { style.height = this.props.data.height; + } else { + style.flex = 1; } if (this.props.data.backgroundColor !== undefined) { style.backgroundColor = this.props.data.backgroundColor; From 2d342f3b7b19997a38524e6a0d2220879d3899e9 Mon Sep 17 00:00:00 2001 From: nyura123 Date: Mon, 8 Feb 2016 23:22:22 -0600 Subject: [PATCH 04/12] advanced firebase editable table example --- examples/TableViewDemo/index.ios.js | 139 +++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 5 deletions(-) diff --git a/examples/TableViewDemo/index.ios.js b/examples/TableViewDemo/index.ios.js index bf5ec0e..fae6fd8 100755 --- a/examples/TableViewDemo/index.ios.js +++ b/examples/TableViewDemo/index.ios.js @@ -1,7 +1,7 @@ 'use strict'; var React = require('react-native'); -var { AppRegistry, Text, Dimensions,View } = React; +var { AppRegistry, Text, Dimensions, View, TouchableHighlight } = React; var TableView = require('react-native-tableview'); var Section = TableView.Section; var Item = TableView.Item; @@ -205,10 +205,7 @@ class FirebaseExample extends React.Component { }); item[this.propPrefix+"key"] = key; - //concat all key-val's into one string - var label = Object.keys(itemData).map((k)=>k+":"+itemData[k]).join(","); - - return (); + return (); } render() { var data = this.state.data; @@ -234,6 +231,136 @@ class FirebaseExample extends React.Component { } } + +class FirebaseEditableExample extends React.Component { + constructor(props) { + super(props); + this.state = {data:null,editing:false}; + this.reactCellModule = "TableViewExampleCell"; + //TODO replace this with your test location - warning, this example will overwrite data! + this.firebaseLocation = "https://dinosaur-facts.firebaseio.com/dinosaurs"; + } + componentDidMount() { + var self = this; + this.ref = new Firebase(this.firebaseLocation); + this.ref.on('value', function(snapshot) { + if (self.state.editing) { + console.warn("Ignoring update from firebase while editing data locally"); + self.dataToSetAfterCancelling = snapshot.val(); + } else { + self.setState({data:snapshot.val()}); + } + }); + } + componentWillUnmount() { + this.ref.off(); + } + editOrSave() { + if (this.state.editing) { + //Save edited data + + this.dataToSetAfterCancelling = null; + + var self = this; + var newData = (this.dataItemKeysBeingEdited || []).map(itemKey=>self.state.data[itemKey]); + this.dataItemKeysBeingEdited = null; + + this.setState({editing: false}, function() { + //Save to firebase and override any remote changes that happened while we were editing. + //Do this in setState(editing=false) callback to make sure it's set by the time we get the 'value' + //callback. + + //NOTE: this changes the data into an array! + self.ref.set(newData); + }); + } else { + //Start editing - save snapshot of data + this.dataToSetAfterCancelling = this.state.data; + //Must be same ordering as used in rendering items + this.dataItemKeysBeingEdited = Object.keys(this.state.data); + this.setState({editing: true}); + } + } + cancelEditing() { + var data = this.dataToSetAfterCancelling; + this.dataToSetAfterCancelling = null; + this.dataItemKeysBeingEdited = null; + var self = this; + + //The last data we rendered with hasn't changed, but native side *displayed* data has changed + //due to local editing. Need to force to re-render with javascript data. + this.setState({editing: false, data: {'___fake___':true,...data}}, function() { + self.setState({editing: false, data: data}); + }) + } + moveItem(info) { + if (info.sourceIndex >= this.dataItemKeysBeingEdited.length + || info.destinationIndex >= this.dataItemKeysBeingEdited.length) { + console.error("moved row source/destination indices are out of range"); + return; + } + var itemKey = this.dataItemKeysBeingEdited[info.sourceIndex]; + this.dataItemKeysBeingEdited.splice(info.sourceIndex, 1); + this.dataItemKeysBeingEdited.splice(info.destinationIndex, 0, itemKey); + } + deleteItem(info) { + this.dataItemKeysBeingEdited.splice(info.selectedIndex, 1); + } + onChange(info) { + if (info.mode == 'move') { + this.moveItem(info); + } else if (info.mode == 'delete') { + this.deleteItem(info); + } else { + console.error("unknown change mode: "+info.mode); + } + } + renderItem(itemData, key, index) { + return ( + + ); + } + render() { + var {data, editing} = this.state; + if (!data) { + return NO DATA + } + + var self = this; + var items = Object.keys(data).map((key,index)=>self.renderItem(data[key], key, index)); + + return ( + + + + + {this.editOrSave()}} + style={{borderRadius:5, width:100,backgroundColor:"lightblue",alignItems:"center",justifyContent:"center"}}> + {editing?"Save":"Edit"} + + + {editing && + {this.cancelEditing()}} + style={{borderRadius:5, width:100,backgroundColor:"red",alignItems:"center",justifyContent:"center"}}> + Cancel + } + + + alert(JSON.stringify(event))} + onChange={this.onChange.bind(this)} + > +
+ {items} +
+
+
+ ); + } +} + class Edit extends React.Component { constructor(props){ super(props); @@ -329,6 +456,7 @@ class Launch extends React.Component { Firebase Example Large ListView (scroll memory growth) Reusable Large TableView Example + Firebase Editing Example
); @@ -350,6 +478,7 @@ class TableViewExample extends React.Component { + ); From f2be433becdf4dcd783eb25fad648b59aefc6a14 Mon Sep 17 00:00:00 2001 From: nyura123 Date: Tue, 9 Feb 2016 02:48:06 -0600 Subject: [PATCH 05/12] example of editable table with reusable cells --- RNTableView/RNReactModuleCell.m | 6 +++ examples/TableViewDemo/index.ios.js | 73 +++++++++++++++++------------ 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/RNTableView/RNReactModuleCell.m b/RNTableView/RNReactModuleCell.m index deed205..d248350 100644 --- a/RNTableView/RNReactModuleCell.m +++ b/RNTableView/RNReactModuleCell.m @@ -31,6 +31,12 @@ -(void)setUpAndConfigure:(NSDictionary*)data bridge:(RCTBridge*)bridge indexPath if (_rootView == nil) { //Create the mini react app that will populate our cell. This will be called from cellForRowAtIndexPath _rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:reactModule initialProperties:props]; + NSLog(@"width: %@",data[@"width"]); + if (data[@"width"]) { + CGRect contentViewFrame = self.contentView.frame; + contentViewFrame.size.width = ((NSNumber*)data[@"width"]).floatValue; + self.contentView.frame = contentViewFrame; + } [self.contentView addSubview:_rootView]; _rootView.frame = self.contentView.frame; } else { diff --git a/examples/TableViewDemo/index.ios.js b/examples/TableViewDemo/index.ios.js index fae6fd8..4f5643e 100755 --- a/examples/TableViewDemo/index.ios.js +++ b/examples/TableViewDemo/index.ios.js @@ -1,7 +1,7 @@ 'use strict'; var React = require('react-native'); -var { AppRegistry, Text, Dimensions, View, TouchableHighlight } = React; +var { AppRegistry, Text, Dimensions, View, TouchableHighlight, TextInput } = React; var TableView = require('react-native-tableview'); var Section = TableView.Section; var Item = TableView.Item; @@ -232,28 +232,20 @@ class FirebaseExample extends React.Component { } -class FirebaseEditableExample extends React.Component { +class CustomEditableExample extends React.Component { constructor(props) { super(props); - this.state = {data:null,editing:false}; + this.state = {data:null,editing:false,text:""}; this.reactCellModule = "TableViewExampleCell"; - //TODO replace this with your test location - warning, this example will overwrite data! - this.firebaseLocation = "https://dinosaur-facts.firebaseio.com/dinosaurs"; } - componentDidMount() { + onExternalData(data) { var self = this; - this.ref = new Firebase(this.firebaseLocation); - this.ref.on('value', function(snapshot) { - if (self.state.editing) { - console.warn("Ignoring update from firebase while editing data locally"); - self.dataToSetAfterCancelling = snapshot.val(); - } else { - self.setState({data:snapshot.val()}); - } - }); - } - componentWillUnmount() { - this.ref.off(); + if (self.state.editing) { + console.warn("Ignoring update from firebase while editing data locally"); + self.dataToSetAfterCancelling = data; + } else { + self.setState({data:data}); + } } editOrSave() { if (this.state.editing) { @@ -266,12 +258,8 @@ class FirebaseEditableExample extends React.Component { this.dataItemKeysBeingEdited = null; this.setState({editing: false}, function() { - //Save to firebase and override any remote changes that happened while we were editing. - //Do this in setState(editing=false) callback to make sure it's set by the time we get the 'value' - //callback. - - //NOTE: this changes the data into an array! - self.ref.set(newData); + //Simulate saving data remotely and getting a data-changed callback + setTimeout(()=>self.onExternalData(newData), 2); }); } else { //Start editing - save snapshot of data @@ -306,6 +294,18 @@ class FirebaseEditableExample extends React.Component { deleteItem(info) { this.dataItemKeysBeingEdited.splice(info.selectedIndex, 1); } + addItem() { + var {text} = this.state; + if (!text) return; + var self = this; + + //Simulate saving data remotely and getting a data-changed callback + setTimeout(()=>self.onExternalData(!this.state.data?[text]:[...(this.state.data), text]), 2); + + //clear text & hide keyboard + this.setState({text:""}); + this.refs.addTextInput.blur(); + } onChange(info) { if (info.mode == 'move') { this.moveItem(info); @@ -317,14 +317,14 @@ class FirebaseEditableExample extends React.Component { } renderItem(itemData, key, index) { return ( - ); } render() { var {data, editing} = this.state; if (!data) { - return NO DATA + data = {}; } var self = this; @@ -347,12 +347,27 @@ class FirebaseEditableExample extends React.Component { } + {!editing && + + this.setState({text:text})} + value={this.state.text} + /> + + {this.addItem()}} + style={{borderRadius:5, width:100,backgroundColor:"red",alignItems:"center",justifyContent:"center"}}> + Add + + + } + alert(JSON.stringify(event))} onChange={this.onChange.bind(this)} > -
+
{items}
@@ -456,7 +471,7 @@ class Launch extends React.Component { Firebase Example Large ListView (scroll memory growth) Reusable Large TableView Example - Firebase Editing Example + Custom Editing Example
); @@ -478,7 +493,7 @@ class TableViewExample extends React.Component { - + ); From 015f7fe6819ec5267a504fae472d098178a8ce2b Mon Sep 17 00:00:00 2001 From: nyura123 Date: Tue, 9 Feb 2016 04:06:16 -0600 Subject: [PATCH 06/12] more editable example changes --- RNTableView/RNReactModuleCell.m | 6 +- examples/TableViewDemo/index.ios.js | 115 ++++++++++++++++++---------- examples/TableViewDemo/package.json | 2 +- 3 files changed, 81 insertions(+), 42 deletions(-) diff --git a/RNTableView/RNReactModuleCell.m b/RNTableView/RNReactModuleCell.m index d248350..49860e9 100644 --- a/RNTableView/RNReactModuleCell.m +++ b/RNTableView/RNReactModuleCell.m @@ -31,7 +31,6 @@ -(void)setUpAndConfigure:(NSDictionary*)data bridge:(RCTBridge*)bridge indexPath if (_rootView == nil) { //Create the mini react app that will populate our cell. This will be called from cellForRowAtIndexPath _rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:reactModule initialProperties:props]; - NSLog(@"width: %@",data[@"width"]); if (data[@"width"]) { CGRect contentViewFrame = self.contentView.frame; contentViewFrame.size.width = ((NSNumber*)data[@"width"]).floatValue; @@ -47,4 +46,9 @@ -(void)setUpAndConfigure:(NSDictionary*)data bridge:(RCTBridge*)bridge indexPath //The application will be unmounted in javascript when the cell/rootview is destroyed } +-(void)prepareForReuse { + [super prepareForReuse]; + //TODO prevent stale data flickering +} + @end diff --git a/examples/TableViewDemo/index.ios.js b/examples/TableViewDemo/index.ios.js index 4f5643e..26169cc 100755 --- a/examples/TableViewDemo/index.ios.js +++ b/examples/TableViewDemo/index.ios.js @@ -15,7 +15,7 @@ class NavBar extends React.Component { return + {...this.props} /> } } class Example1 extends React.Component { @@ -236,7 +236,7 @@ class CustomEditableExample extends React.Component { constructor(props) { super(props); this.state = {data:null,editing:false,text:""}; - this.reactCellModule = "TableViewExampleCell"; + this.reactCellModule = "TableViewExampleCell2"; } onExternalData(data) { var self = this; @@ -265,7 +265,7 @@ class CustomEditableExample extends React.Component { //Start editing - save snapshot of data this.dataToSetAfterCancelling = this.state.data; //Must be same ordering as used in rendering items - this.dataItemKeysBeingEdited = Object.keys(this.state.data); + this.dataItemKeysBeingEdited = Object.keys(this.state.data || {}); this.setState({editing: true}); } } @@ -277,12 +277,12 @@ class CustomEditableExample extends React.Component { //The last data we rendered with hasn't changed, but native side *displayed* data has changed //due to local editing. Need to force to re-render with javascript data. - this.setState({editing: false, data: {'___fake___':true,...data}}, function() { + this.setState({editing: false, data: {...data,'___fake___':true}}, function() { self.setState({editing: false, data: data}); }) } moveItem(info) { - if (info.sourceIndex >= this.dataItemKeysBeingEdited.length + if (!this.dataItemKeysBeingEdited || info.sourceIndex >= this.dataItemKeysBeingEdited.length || info.destinationIndex >= this.dataItemKeysBeingEdited.length) { console.error("moved row source/destination indices are out of range"); return; @@ -292,6 +292,10 @@ class CustomEditableExample extends React.Component { this.dataItemKeysBeingEdited.splice(info.destinationIndex, 0, itemKey); } deleteItem(info) { + if (!this.dataItemKeysBeingEdited || info.selectedIndex >= this.dataItemKeysBeingEdited.length) { + console.error("deleted row index is out of range"); + return; + } this.dataItemKeysBeingEdited.splice(info.selectedIndex, 1); } addItem() { @@ -317,10 +321,48 @@ class CustomEditableExample extends React.Component { } renderItem(itemData, key, index) { return ( - + ); } + getNavProps() { + var self = this; + var navProps = { + title:{title:"Custom Editable"}, + rightButton: { + title: (this.state.editing? 'Save':'Edit'), + handler: function onNext() { + self.editOrSave(); + } + } + }; + navProps.leftButton = { + title: (this.state.editing?'Cancel':'Back'), + handler: function onNext() { + if (self.state.editing) + self.cancelEditing(); + else { + Actions.pop(); + } + } + }; + return navProps; + } + getAddItemRow() { + return ( + + this.setState({text:text})} + value={this.state.text} + /> + + {this.addItem()}} + style={{borderRadius:5, width:100,backgroundColor:"red",alignItems:"center",justifyContent:"center"}}> + Add + + + ); + } render() { var {data, editing} = this.state; if (!data) { @@ -331,40 +373,14 @@ class CustomEditableExample extends React.Component { var items = Object.keys(data).map((key,index)=>self.renderItem(data[key], key, index)); return ( - - - - - {this.editOrSave()}} - style={{borderRadius:5, width:100,backgroundColor:"lightblue",alignItems:"center",justifyContent:"center"}}> - {editing?"Save":"Edit"} - - - {editing && - {this.cancelEditing()}} - style={{borderRadius:5, width:100,backgroundColor:"red",alignItems:"center",justifyContent:"center"}}> - Cancel - } - - - {!editing && - - this.setState({text:text})} - value={this.state.text} - /> - - {this.addItem()}} - style={{borderRadius:5, width:100,backgroundColor:"red",alignItems:"center",justifyContent:"center"}}> - Add - - - } + + + + + {!editing && this.getAddItemRow()} alert(JSON.stringify(event))} onChange={this.onChange.bind(this)} >
@@ -487,13 +503,13 @@ class TableViewExample extends React.Component { - + - + ); @@ -518,6 +534,24 @@ class TableViewExampleCell extends React.Component { } } +//Should be pure... setState on top-level component doesn't seem to work +class TableViewExampleCell2 extends React.Component { + render(){ + var style = {}; + //cell height is passed from child of tableview and native code passes it back up to javascript in "app params" for the cell. + //This way our component will fill the full native table cell height. + if (this.props.data.height !== undefined) { + style.height = this.props.data.height; + } else { + style.flex = 1; + } + if (this.props.data.backgroundColor !== undefined) { + style.backgroundColor = this.props.data.backgroundColor; + } + return ({this.props.data.label}); + } +} + //Should be pure... setState on top-level component doesn't seem to work class DinosaurCellExample extends React.Component { yearsAgoInMil(num) { @@ -554,4 +588,5 @@ class DinosaurCellExample extends React.Component { AppRegistry.registerComponent('TableViewExample', () => TableViewExample); AppRegistry.registerComponent('TableViewExampleCell', () => TableViewExampleCell); +AppRegistry.registerComponent('TableViewExampleCell2', () => TableViewExampleCell2); AppRegistry.registerComponent('DinosaurCellExample', () => DinosaurCellExample); diff --git a/examples/TableViewDemo/package.json b/examples/TableViewDemo/package.json index f213d20..d0f5d2c 100644 --- a/examples/TableViewDemo/package.json +++ b/examples/TableViewDemo/package.json @@ -8,7 +8,7 @@ "dependencies": { "firebase": "^2.4.0", "react-native": "^0.19.0", - "react-native-navbar": "^0.8.2", + "react-native-navbar": "^1.2.0", "react-native-router-flux": "^2.1.8", "react-native-tableview": "1.4.2", "react-native-tabs": "^0.1.10" From befad1211be561807232bc3a6874b00d11233695 Mon Sep 17 00:00:00 2001 From: nyura123 Date: Tue, 9 Feb 2016 04:35:08 -0600 Subject: [PATCH 07/12] . --- examples/TableViewDemo/index.ios.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/TableViewDemo/index.ios.js b/examples/TableViewDemo/index.ios.js index 26169cc..550c3a4 100755 --- a/examples/TableViewDemo/index.ios.js +++ b/examples/TableViewDemo/index.ios.js @@ -257,9 +257,12 @@ class CustomEditableExample extends React.Component { var newData = (this.dataItemKeysBeingEdited || []).map(itemKey=>self.state.data[itemKey]); this.dataItemKeysBeingEdited = null; - this.setState({editing: false}, function() { + this.setState({editing: false, data: newData}, function() { //Simulate saving data remotely and getting a data-changed callback - setTimeout(()=>self.onExternalData(newData), 2); + setTimeout(()=> { + self.onExternalData(newData); + this.setState({editing: false}); + }); }); } else { //Start editing - save snapshot of data @@ -277,7 +280,7 @@ class CustomEditableExample extends React.Component { //The last data we rendered with hasn't changed, but native side *displayed* data has changed //due to local editing. Need to force to re-render with javascript data. - this.setState({editing: false, data: {...data,'___fake___':true}}, function() { + this.setState({editing: false, data: {...data,'___fake___':"1"}}, function() { self.setState({editing: false, data: data}); }) } From 4cf2d302c1a8a533f835fa111ae6b9762f7cd9e4 Mon Sep 17 00:00:00 2001 From: nyura123 Date: Tue, 9 Feb 2016 04:43:32 -0600 Subject: [PATCH 08/12] . --- examples/TableViewDemo/index.ios.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/TableViewDemo/index.ios.js b/examples/TableViewDemo/index.ios.js index 550c3a4..a2171ae 100755 --- a/examples/TableViewDemo/index.ios.js +++ b/examples/TableViewDemo/index.ios.js @@ -259,10 +259,7 @@ class CustomEditableExample extends React.Component { this.setState({editing: false, data: newData}, function() { //Simulate saving data remotely and getting a data-changed callback - setTimeout(()=> { - self.onExternalData(newData); - this.setState({editing: false}); - }); + setTimeout(()=> self.onExternalData(newData), 2); }); } else { //Start editing - save snapshot of data From f476b66895c0e37b3adda42ffc78e7fb357e1ee5 Mon Sep 17 00:00:00 2001 From: nyura123 Date: Tue, 9 Feb 2016 16:00:40 -0600 Subject: [PATCH 09/12] remove the need for react-native lib changes - create one bridge per table instead of attempting to share the original app bridge --- RNTableView/RNReactModuleCell.h | 14 -------------- RNTableView/RNTableView.m | 8 +++++++- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/RNTableView/RNReactModuleCell.h b/RNTableView/RNReactModuleCell.h index dc0bc89..81886ca 100644 --- a/RNTableView/RNReactModuleCell.h +++ b/RNTableView/RNReactModuleCell.h @@ -3,20 +3,6 @@ //Use react-native root views as reusable cells returned from cellForRowAtIndexPath. -/* - Two react-native changes in RCTRootView.m are needed to allow re-rendering into the same view: - - 1. In initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties: - if (!_bridge.loading) { - [self bundleFinishedLoading:_bridge]; //instead of _bridge.batchedBridge - } - - 2. In setAppProperties:(NSDictionary *)appProperties: - if (_contentView && _bridge.valid && !_bridge.loading) { - [self runApplication:_bridge]; //instead of _bridge.batchedBridge - } - */ - @interface RNReactModuleCell : UITableViewCell { } diff --git a/RNTableView/RNTableView.m b/RNTableView/RNTableView.m index ba0d31c..524ad3e 100644 --- a/RNTableView/RNTableView.m +++ b/RNTableView/RNTableView.m @@ -74,7 +74,13 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher br RCTAssertParam(eventDispatcher); if ((self = [super initWithFrame:CGRectZero])) { - _bridge = bridge; + + //RCTRootView setAppProperties and initWithBridge call "_bridge.batchedBridge" which will return nil because the bridge that gets passed to this constructor is *already* the batched bridge. + //So we have to create a separate (parent) _bridge here ourselves. + _bridge = [[RCTBridge alloc] initWithBundleURL:bridge.bundleURL + moduleProvider:nil + launchOptions:nil]; + _eventDispatcher = eventDispatcher; _cellHeight = 44; _cells = [NSMutableArray array]; From 7c8fe1bf20aae5e2c26cfa0ea28c9eeddc58d7c9 Mon Sep 17 00:00:00 2001 From: nyura123 Date: Tue, 9 Feb 2016 16:00:40 -0600 Subject: [PATCH 10/12] remove the need for react-native lib changes - create one bridge per table instead of attempting to share the original app bridge --- RNTableView/RNReactModuleCell.h | 14 -------------- RNTableView/RNTableView.m | 8 +++++++- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/RNTableView/RNReactModuleCell.h b/RNTableView/RNReactModuleCell.h index dc0bc89..81886ca 100644 --- a/RNTableView/RNReactModuleCell.h +++ b/RNTableView/RNReactModuleCell.h @@ -3,20 +3,6 @@ //Use react-native root views as reusable cells returned from cellForRowAtIndexPath. -/* - Two react-native changes in RCTRootView.m are needed to allow re-rendering into the same view: - - 1. In initWithBridge:(RCTBridge *)bridge moduleName:(NSString *)moduleName initialProperties:(NSDictionary *)initialProperties: - if (!_bridge.loading) { - [self bundleFinishedLoading:_bridge]; //instead of _bridge.batchedBridge - } - - 2. In setAppProperties:(NSDictionary *)appProperties: - if (_contentView && _bridge.valid && !_bridge.loading) { - [self runApplication:_bridge]; //instead of _bridge.batchedBridge - } - */ - @interface RNReactModuleCell : UITableViewCell { } diff --git a/RNTableView/RNTableView.m b/RNTableView/RNTableView.m index ba0d31c..524ad3e 100644 --- a/RNTableView/RNTableView.m +++ b/RNTableView/RNTableView.m @@ -74,7 +74,13 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher br RCTAssertParam(eventDispatcher); if ((self = [super initWithFrame:CGRectZero])) { - _bridge = bridge; + + //RCTRootView setAppProperties and initWithBridge call "_bridge.batchedBridge" which will return nil because the bridge that gets passed to this constructor is *already* the batched bridge. + //So we have to create a separate (parent) _bridge here ourselves. + _bridge = [[RCTBridge alloc] initWithBundleURL:bridge.bundleURL + moduleProvider:nil + launchOptions:nil]; + _eventDispatcher = eventDispatcher; _cellHeight = 44; _cells = [NSMutableArray array]; From 01bd6bbe358643c283737c3e9db92a56b73119b8 Mon Sep 17 00:00:00 2001 From: nyura123 Date: Tue, 9 Feb 2016 17:05:26 -0600 Subject: [PATCH 11/12] fix editing example --- examples/TableViewDemo/index.ios.js | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/examples/TableViewDemo/index.ios.js b/examples/TableViewDemo/index.ios.js index a2171ae..5d3544e 100755 --- a/examples/TableViewDemo/index.ios.js +++ b/examples/TableViewDemo/index.ios.js @@ -242,7 +242,6 @@ class CustomEditableExample extends React.Component { var self = this; if (self.state.editing) { console.warn("Ignoring update from firebase while editing data locally"); - self.dataToSetAfterCancelling = data; } else { self.setState({data:data}); } @@ -250,36 +249,30 @@ class CustomEditableExample extends React.Component { editOrSave() { if (this.state.editing) { //Save edited data - - this.dataToSetAfterCancelling = null; - + var self = this; - var newData = (this.dataItemKeysBeingEdited || []).map(itemKey=>self.state.data[itemKey]); + var newData = (this.dataItemKeysBeingEdited || []).map(itemKey=>self.preEditData[itemKey]); this.dataItemKeysBeingEdited = null; + this.preEditData = null; this.setState({editing: false, data: newData}, function() { //Simulate saving data remotely and getting a data-changed callback setTimeout(()=> self.onExternalData(newData), 2); }); } else { - //Start editing - save snapshot of data - this.dataToSetAfterCancelling = this.state.data; + this.preEditData = Object.assign({}, this.state.data); //Must be same ordering as used in rendering items this.dataItemKeysBeingEdited = Object.keys(this.state.data || {}); this.setState({editing: true}); } } cancelEditing() { - var data = this.dataToSetAfterCancelling; - this.dataToSetAfterCancelling = null; + var data = this.preEditData; this.dataItemKeysBeingEdited = null; + this.preEditData = null; var self = this; - //The last data we rendered with hasn't changed, but native side *displayed* data has changed - //due to local editing. Need to force to re-render with javascript data. - this.setState({editing: false, data: {...data,'___fake___':"1"}}, function() { - self.setState({editing: false, data: data}); - }) + self.setState({editing: false, data: data}); } moveItem(info) { if (!this.dataItemKeysBeingEdited || info.sourceIndex >= this.dataItemKeysBeingEdited.length @@ -290,6 +283,10 @@ class CustomEditableExample extends React.Component { var itemKey = this.dataItemKeysBeingEdited[info.sourceIndex]; this.dataItemKeysBeingEdited.splice(info.sourceIndex, 1); this.dataItemKeysBeingEdited.splice(info.destinationIndex, 0, itemKey); + + var self = this; + var newData = (this.dataItemKeysBeingEdited || []).map(itemKey=>self.preEditData[itemKey]); + this.setState({data: newData}); } deleteItem(info) { if (!this.dataItemKeysBeingEdited || info.selectedIndex >= this.dataItemKeysBeingEdited.length) { @@ -297,6 +294,10 @@ class CustomEditableExample extends React.Component { return; } this.dataItemKeysBeingEdited.splice(info.selectedIndex, 1); + + var self = this; + var newData = (this.dataItemKeysBeingEdited || []).map(itemKey=>self.preEditData[itemKey]); + this.setState({data: newData}); } addItem() { var {text} = this.state; From 950b85251e1394ca879a5bd372fcba6f2f232976 Mon Sep 17 00:00:00 2001 From: Moses Gunesch Date: Tue, 9 Feb 2016 16:10:20 -0800 Subject: [PATCH 12/12] Adds showsDragIconWhenEditing prop --- RNTableView/RNTableView.h | 1 + RNTableView/RNTableView.m | 1 + RNTableView/RNTableViewCell.h | 1 + RNTableView/RNTableViewCell.m | 16 ++++++++++++++++ RNTableView/RNTableViewManager.m | 1 + index.js | 9 ++++++++- 6 files changed, 28 insertions(+), 1 deletion(-) diff --git a/RNTableView/RNTableView.h b/RNTableView/RNTableView.h index 3be4fe9..ddcc478 100644 --- a/RNTableView/RNTableView.h +++ b/RNTableView/RNTableView.h @@ -44,6 +44,7 @@ @property (nonatomic, assign) UITableViewStyle tableViewStyle; @property (nonatomic, assign) UITableViewCellStyle tableViewCellStyle; @property (nonatomic, assign) UITableViewCellEditingStyle tableViewCellEditingStyle; +@property (nonatomic, assign) BOOL showsDragIconWhenEditing; @property (nonatomic, assign) UITableViewCellSeparatorStyle separatorStyle; @property (nonatomic, strong) UIFont *font; @property (nonatomic, strong) UIFont *headerFont; diff --git a/RNTableView/RNTableView.m b/RNTableView/RNTableView.m index 524ad3e..405ad5e 100644 --- a/RNTableView/RNTableView.m +++ b/RNTableView/RNTableView.m @@ -343,6 +343,7 @@ -(UITableViewCell* )tableView:(UITableView *)tableView cellForRowAtIndexPath:(NS // check if it is standard cell or user-defined UI if ([self hasCustomCells:indexPath.section]){ cell = ((RNCellView *)_cells[indexPath.section][indexPath.row]).tableViewCell; + ((RNTableViewCell *)cell).showsDragIconWhenEditing = self.showsDragIconWhenEditing; } else if (self.reactModuleForCell != nil && ![self.reactModuleForCell isEqualToString:@""]) { cell = [self setupReactModuleCell:tableView data:item indexPath:indexPath]; } else { diff --git a/RNTableView/RNTableViewCell.h b/RNTableView/RNTableViewCell.h index 85e4e21..8246547 100644 --- a/RNTableView/RNTableViewCell.h +++ b/RNTableView/RNTableViewCell.h @@ -13,5 +13,6 @@ @interface RNTableViewCell : UITableViewCell @property (nonatomic, weak) RNCellView *cellView; +@property (nonatomic, assign) BOOL showsDragIconWhenEditing; @end diff --git a/RNTableView/RNTableViewCell.m b/RNTableView/RNTableViewCell.m index 26b957b..4c81857 100644 --- a/RNTableView/RNTableViewCell.m +++ b/RNTableView/RNTableViewCell.m @@ -18,6 +18,22 @@ -(void)setCellView:(RNCellView *)cellView { -(void)setFrame:(CGRect)frame { [super setFrame:frame]; [_cellView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)]; + + if (!self.showsDragIconWhenEditing && self.editing) { + static NSString *reorderControlClass = @"UITableViewCellReorderControl"; + for (UIView* view in self.subviews) { + if ([[[view class] description] isEqualToString:reorderControlClass]) { + view.transform = CGAffineTransformScale(CGAffineTransformIdentity, 2.0, 2.0); // expands hit area somewhat + for (UIImageView* cellGrip in view.subviews) + { + if ([cellGrip isKindOfClass:[UIImageView class]]) { + [cellGrip setImage:nil]; + } + } + break; + } + } + } } @end diff --git a/RNTableView/RNTableViewManager.m b/RNTableView/RNTableViewManager.m index 413622f..292d7c4 100644 --- a/RNTableView/RNTableViewManager.m +++ b/RNTableView/RNTableViewManager.m @@ -41,6 +41,7 @@ - (UIView *)view RCT_EXPORT_VIEW_PROPERTY(moveWithinSectionOnly, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowsToggle, BOOL) RCT_EXPORT_VIEW_PROPERTY(allowsMultipleSelection, BOOL) +RCT_EXPORT_VIEW_PROPERTY(showsDragIconWhenEditing, BOOL) RCT_CUSTOM_VIEW_PROPERTY(tableViewStyle, UITableViewStyle, RNTableView) { diff --git a/index.js b/index.js index e3216f4..2047c20 100644 --- a/index.js +++ b/index.js @@ -50,6 +50,13 @@ var TableView = React.createClass({ * @platform ios */ scrollIndicatorInsets: React.EdgeInsetsPropType, + showsDragIconWhenEditing: React.PropTypes.bool, + }, + + getDefaultProps() { + return { + showsDragIconWhenEditing: true, + }; }, getInitialState: function() { @@ -125,7 +132,7 @@ var TableView = React.createClass({ additionalItems={this.state.additionalItems} tableViewStyle={TableView.Consts.Style.Plain} tableViewCellStyle={TableView.Consts.CellStyle.Subtitle} - tableViewCellEditingStyle={TableView.Consts.CellEditingStyle.Delete} + showsDragIconWhenEditing={this.props.showsDragIconWhenEditing} separatorStyle={TableView.Consts.SeparatorStyle.Line} scrollIndicatorInsets={this.props.contentInset} {...this.props}