Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions RNTableView.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand All @@ -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 = "<group>"; };
0808A0321C67E9A60038993A /* RNReactModuleCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNReactModuleCell.m; sourceTree = "<group>"; };
872545D51BAAC85D00889249 /* JSONDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONDataSource.h; sourceTree = "<group>"; };
872545D61BAAC85D00889249 /* JSONDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONDataSource.m; sourceTree = "<group>"; };
872545D71BAAC85D00889249 /* RNCellView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCellView.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -69,6 +72,8 @@
872545D41BAAC85D00889249 /* RNTableView */ = {
isa = PBXGroup;
children = (
0808A0311C67E9A60038993A /* RNReactModuleCell.h */,
0808A0321C67E9A60038993A /* RNReactModuleCell.m */,
872545D51BAAC85D00889249 /* JSONDataSource.h */,
872545D61BAAC85D00889249 /* JSONDataSource.m */,
872545D71BAAC85D00889249 /* RNCellView.h */,
Expand Down Expand Up @@ -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 */,
Expand Down
13 changes: 13 additions & 0 deletions RNTableView/RNReactModuleCell.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

#import <UIKit/UIKit.h>

//Use react-native root views as reusable cells returned from cellForRowAtIndexPath.

@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
54 changes: 54 additions & 0 deletions RNTableView/RNReactModuleCell.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// RNReactModuleCell.m
// RNTableView
//
// Created by Anna Berman on 2/6/16.
// Copyright © 2016 Pavlo Aksonov. All rights reserved.
//

#import <RCTRootView.h>
#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];
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 {
//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
}

-(void)prepareForReuse {
[super prepareForReuse];
//TODO prevent stale data flickering
}

@end
5 changes: 4 additions & 1 deletion RNTableView/RNTableView.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#import <UIKit/UIKit.h>
@class RCTEventDispatcher;
@class RCTBridge;

@protocol RNTableViewDatasource <NSObject>

Expand All @@ -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;
Expand All @@ -43,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;
Expand All @@ -58,5 +60,6 @@
@property (nonatomic) BOOL autoFocus;
@property (nonatomic) BOOL allowsToggle;
@property (nonatomic) BOOL allowsMultipleSelection;
@property (nonatomic) NSString *reactModuleForCell;

@end
42 changes: 36 additions & 6 deletions RNTableView/RNTableView.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import "RNCellView.h"
#import "RNTableFooterView.h"
#import "RNTableHeaderView.h"
#import "RNReactModuleCell.h"

@interface RNTableView()<UITableViewDataSource, UITableViewDelegate> {
id<RNTableViewDatasource> datasource;
Expand All @@ -25,9 +26,11 @@ @interface RNTableView()<UITableViewDataSource, UITableViewDelegate> {
@end

@implementation RNTableView {
RCTBridge *_bridge;
RCTEventDispatcher *_eventDispatcher;
NSArray *_items;
NSMutableArray *_cells;
NSString *_reactModuleCellReuseIndentifier;
}

-(void)setEditing:(BOOL)editing {
Expand Down Expand Up @@ -66,11 +69,18 @@ - (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])) {

//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];
Expand Down Expand Up @@ -148,6 +158,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 {
Expand Down Expand Up @@ -279,6 +291,7 @@ - (void)setSections:(NSArray *)sections
if(selectedIndex == -1)
selectedIndex = [items count];
itemData[@"selected"] = @YES;

found = YES;
}
[items addObject:itemData];
Expand Down Expand Up @@ -312,20 +325,36 @@ -(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;
((RNTableViewCell *)cell).showsDragIconWhenEditing = self.showsDragIconWhenEditing;
} 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]) {
Expand Down Expand Up @@ -354,7 +383,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;
Expand Down
1 change: 1 addition & 0 deletions RNTableView/RNTableViewCell.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@
@interface RNTableViewCell : UITableViewCell

@property (nonatomic, weak) RNCellView *cellView;
@property (nonatomic, assign) BOOL showsDragIconWhenEditing;

@end
16 changes: 16 additions & 0 deletions RNTableView/RNTableViewCell.m
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 7 additions & 1 deletion RNTableView/RNTableViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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) {
Expand All @@ -56,6 +57,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]];
}
Expand Down
Loading