Skip to content

Commit

Permalink
feat: add swipe actions support for Ti.UI.TableView (#14065)
Browse files Browse the repository at this point in the history
* feat: add swipe actions support for Ti.UI.TableView

* fix: fix linting issue

* feat: support both leading and trailing state

* feat: support leading edit actions in list view as well
  • Loading branch information
hansemannn authored Jun 25, 2024
1 parent 7b82317 commit 40808cd
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 57 deletions.
29 changes: 27 additions & 2 deletions apidoc/Titanium/UI/ListItem.yml
Original file line number Diff line number Diff line change
Expand Up @@ -674,12 +674,27 @@ properties:
---
name: RowActionType
summary: Represents the custom edit action for a ListItem.
summary: Represents the custom edit action for a ListItem or TableViewRow.
description: |
By default when a listItem has [canEdit](Titanium.UI.ListItem.canEdit) set to true, a left swipe on the the row presens the 'Delete' button.
Edit actions can be used to add contextual buttons to your list items / table view rows. The configuration of
this API is the same for list items (if you use <Titanium.UI.ListView>) and table view rows (if you use <Titanium.UI.TableView).
But please note that the trigger to activate these edit actions can defer based on the API you're integrating the edit actions:
*List Views*:
By default when a ListItem has [canEdit](Titanium.UI.ListItem.canEdit) set to `true`, a left swipe on the the row presens the localized 'Delete' button.
This object lets developers define custom titles for editing actions supported on the row.
This object is used in conjunction with the [editActions](Titanium.UI.ListItem.editActions) property and
[editaction](Titanium.UI.ListView.editaction) event.
*Table Views*:
By default when a TableViewRow has [editable](Titanium.UI.TableViewRow.editable) set to `true`, a left swipe on the the row presens the localized 'Delete' button.
This object lets developers define custom titles for editing actions supported on the row.
This object is used in conjunction with the [editActions](Titanium.UI.TableViewRow.editActions) property and
[editaction](Titanium.UI.TableView.editaction) event. For table views, this property was added in Titanium SDK 12.4.0.
In addition, the new property "state" was added in 12.4.0 and can either equal "leading" or "trailing" to show them on a right swipe (leading)
or left swipe (trailing). If the "state" property is not set, it defaults to "trailing" for backwards compatibility.
platforms: [ipad, iphone, macos]
since: '4.1.0'
properties:
Expand Down Expand Up @@ -719,3 +734,13 @@ properties:
optional: true
since: 12.1.0
platforms: [iphone, ipad, macos]

- name: state
type: String
summary: The state to show this edit action. Either "trailing" (default) or "leading".
description: |
In a LTR layout (e.g English, German), the "leading" state is the left area of the screen and the "trailing" state in the right.
In a RTL layout (e.g. Hebrew, Arabic), the "leading" state is the right area of the screen and the "trailing" state in the left.
optional: true
since: 12.4.0
platforms: [iphone, ipad, macos]
34 changes: 34 additions & 0 deletions apidoc/Titanium/UI/TableView.yml
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,40 @@ events:
type: Boolean
since: '3.0.0'

- name: editaction
summary: Fired when the user interacts with one of the custom edit actions defined by <Titanium.UI.TableViewRow.editActions>.
description: |
Do not rely on the `source` property to determine which item fired the event. Use the
`row` and `section`, or the `index` to determine the table row that generated
the event.
Note that the `index` property of this event correspond to the list view state
before the user action.
platforms: [iphone, ipad, macos]
since: 12.4.0
properties:
- name: action
summary: The [title](RowActionType.title) as defined in the row action object.
type: String

- name: identifier
summary: |
The [identifier](RowActionType. identifier) of the row action. Only included in the event
if previously defined.
type: String

- name: row
summary: The row that fired this event.
type: [Titanium.UI.TableViewRow, Dictionary<Titanium.UI.TableViewRow>]

- name: section
summary: The section that fired this event.
type: Titanium.UI.TableViewSection

- name: index
summary: The index of the row that fired this event.
type: Number

methods:
- name: appendRow
summary: Appends a single row or an array of rows to the end of the table.
Expand Down
8 changes: 8 additions & 0 deletions apidoc/Titanium/UI/TableViewRow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,14 @@ properties:
platforms: [android, iphone, ipad, macos]
since: {android: "9.3.0", iphone: "3.2.0", ipad: "3.2.0", macos: "9.2.0"}

- name: editActions
summary: Specifies custom action items to be shown when when a list item is edited.
description: |
For more information see the "Editing Support" section of <Titanium.UI.TableView>.
type: Array<RowActionType>
since: 12.4.0
platforms: [iphone, ipad, macos]

- name: filterAlwaysInclude
summary: |
This row will always be visible when you filter your content.
Expand Down
86 changes: 45 additions & 41 deletions iphone/Classes/TiUIListView.m
Original file line number Diff line number Diff line change
Expand Up @@ -1146,23 +1146,49 @@ - (BOOL)tableView:(UITableView *)tableView canHandleDropSession:(id<UIDropSessio
return [session canLoadObjectsOfClass:[NSString class]];
}

- (NSArray *)editActionsFromValue:(id)value
- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
{
ENSURE_ARRAY(value);
NSArray *propArray = (NSArray *)value;
NSMutableArray *returnArray = nil;
return [self swipeConfigurationForState:@"leading" withIndexPath:indexPath isDefault:NO];
}

- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [self swipeConfigurationForState:@"trailing" withIndexPath:indexPath isDefault:YES];
}

- (UISwipeActionsConfiguration *)swipeConfigurationForState:(NSString *)state withIndexPath:(NSIndexPath *)indexPath isDefault:(BOOL)isDefault
{
NSIndexPath *realIndexPath = [self pathForSearchPath:indexPath];

if (![self canEditRowAtIndexPath:realIndexPath]) {
return nil;
}

id editActionProxies = [self valueWithKey:@"editActions" atIndexPath:realIndexPath];

if (IS_NULL_OR_NIL(editActionProxies)) {
return nil;
}

NSPredicate *predicate = [NSPredicate predicateWithFormat:isDefault ? @"state == nil OR state == %@" : @"state == %@", state];
NSArray<NSDictionary *> *editActions = [editActionProxies filteredArrayUsingPredicate:predicate];
NSMutableArray<UIContextualAction *> *nativeEditActions = [NSMutableArray arrayWithCapacity:editActions.count];

if (IS_NULL_OR_NIL(editActions) || editActions.count == 0) {
return nil;
}

for (id prop in propArray) {
ENSURE_DICT(prop);
for (id prop in editActions) {
NSString *title = [TiUtils stringValue:@"title" properties:prop];
NSString *identifier = [TiUtils stringValue:@"identifier" properties:prop];
int actionStyle = [TiUtils intValue:@"style" properties:prop def:UITableViewRowActionStyleDefault];
UIContextualActionStyle style = [TiUtils intValue:@"style" properties:prop def:UIContextualActionStyleNormal];
TiColor *color = [TiUtils colorValue:@"color" properties:prop];
id image = [prop objectForKey:@"image"];

UITableViewRowAction *theAction = [UITableViewRowAction rowActionWithStyle:actionStyle
UIContextualAction *action = [UIContextualAction contextualActionWithStyle:style
title:title
handler:^(UITableViewRowAction *action, NSIndexPath *indexPath) {
handler:^(UIContextualAction *_Nonnull action, __kindof UIView *_Nonnull sourceView, void (^_Nonnull completionHandler)(BOOL)) {
completionHandler(YES);
NSString *eventName = @"editaction";

NSIndexPath *realIndexPath = [self pathForSearchPath:indexPath];
Expand Down Expand Up @@ -1192,27 +1218,22 @@ - (NSArray *)editActionsFromValue:(id)value
}

// Hide editActions after selection
[[self tableView] setEditing:NO];
// [[self tableView] setEditing:NO];
}];
if (color) {
theAction.backgroundColor = [color color];

if (color != nil) {
action.backgroundColor = color.color;
}
if (image) {

if (image != nil) {
NSURL *url = [TiUtils toURL:image proxy:(TiProxy *)self.proxy];
UIImage *nativeImage = [[ImageLoader sharedLoader] loadImmediateImage:url];
if (color) {
nativeImage = [self generateImage:nativeImage withBackgroundColor:[color color]];
}
theAction.backgroundColor = [UIColor colorWithPatternImage:nativeImage];
}
if (!returnArray) {
returnArray = [NSMutableArray arrayWithObject:theAction];
} else {
[returnArray addObject:theAction];
action.image = [[ImageLoader sharedLoader] loadImmediateImage:url];
}

[nativeEditActions addObject:action];
}

return returnArray;
return [UISwipeActionsConfiguration configurationWithActions:nativeEditActions];
}

- (UIImage *)generateImage:(UIImage *)image withBackgroundColor:(UIColor *)bgColor
Expand Down Expand Up @@ -1388,23 +1409,6 @@ - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleFo
return UITableViewCellEditingStyleNone;
}

- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *realIndexPath = [self pathForSearchPath:indexPath];

if (![self canEditRowAtIndexPath:realIndexPath]) {
return nil;
}

id editValue = [self valueWithKey:@"editActions" atIndexPath:realIndexPath];

if (IS_NULL_OR_NIL(editValue)) {
return nil;
}

return [self editActionsFromValue:editValue];
}

- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
NSIndexPath *realIndexPath = [self pathForSearchPath:indexPath];
Expand Down
67 changes: 67 additions & 0 deletions iphone/Classes/TiUITableView.m
Original file line number Diff line number Diff line change
Expand Up @@ -2338,6 +2338,73 @@ - (void)tableViewDidEndMultipleSelectionInteraction:(UITableView *)tableView
[self fireRowsSelectedEvent];
}

- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [self swipeConfigurationForState:@"leading" withIndexPath:indexPath isDefault:NO];
}

- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [self swipeConfigurationForState:@"trailing" withIndexPath:indexPath isDefault:YES];
}

- (UISwipeActionsConfiguration *)swipeConfigurationForState:(NSString *)state withIndexPath:(NSIndexPath *)indexPath isDefault:(BOOL)isDefault
{
TiUITableViewRowProxy *row = [self rowForIndexPath:indexPath];
TiUITableViewSectionProxy *section = [self sectionForIndex:indexPath.section];

BOOL canEdit = [TiUtils boolValue:[row valueForKey:@"editable"] def:NO];

if (!canEdit) {
return nil;
}

NSArray<NSDictionary *> *editActionProxies = (NSArray *)[row valueForKey:@"editActions"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:isDefault ? @"state == nil OR state == %@" : @"state == %@", state];
NSArray<NSDictionary *> *editActions = [editActionProxies filteredArrayUsingPredicate:predicate];
NSMutableArray<UIContextualAction *> *nativeEditActions = [NSMutableArray arrayWithCapacity:editActions.count];

if (IS_NULL_OR_NIL(editActions) || editActions.count == 0) {
return nil;
}

for (id prop in editActions) {
NSString *title = [TiUtils stringValue:@"title" properties:prop];
NSString *identifier = [TiUtils stringValue:@"identifier" properties:prop];
UIContextualActionStyle style = [TiUtils intValue:@"style" properties:prop def:UIContextualActionStyleNormal];
TiColor *color = [TiUtils colorValue:@"color" properties:prop];
id image = [prop objectForKey:@"image"];

UIContextualAction *action = [UIContextualAction contextualActionWithStyle:style
title:title
handler:^(UIContextualAction *_Nonnull action, __kindof UIView *_Nonnull sourceView, void (^_Nonnull completionHandler)(BOOL)) {
completionHandler(YES);

[[self proxy] fireEvent:@"editaction"
withObject:@{
@"index" : @(indexPath.row),
@"row" : row,
@"section" : section,
@"action" : NULL_IF_NIL(action.title),
@"identifier" : NULL_IF_NIL(identifier)
}];
}];

if (color != nil) {
action.backgroundColor = color.color;
}

if (image != nil) {
NSURL *url = [TiUtils toURL:image proxy:(TiProxy *)self.proxy];
action.image = [[ImageLoader sharedLoader] loadImmediateImage:url];
}

[nativeEditActions addObject:action];
}

return [UISwipeActionsConfiguration configurationWithActions:nativeEditActions];
}

#pragma mark Collation

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)ourTableView
Expand Down
2 changes: 1 addition & 1 deletion iphone/Classes/TiUIiOSProxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
@property (nonatomic, readonly) NSNumber *CLIP_MODE_ENABLED;
@property (nonatomic, readonly) NSNumber *CLIP_MODE_DISABLED;

#ifdef USE_TI_UILISTVIEW
#if defined(USE_TI_UILISTVIEW) || defined(USE_TI_UITABLEVIEW)
@property (nonatomic, readonly) NSNumber *ROW_ACTION_STYLE_DEFAULT;
@property (nonatomic, readonly) NSNumber *ROW_ACTION_STYLE_DESTRUCTIVE;
@property (nonatomic, readonly) NSNumber *ROW_ACTION_STYLE_NORMAL;
Expand Down
17 changes: 4 additions & 13 deletions iphone/Classes/TiUIiOSProxy.m
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,10 @@ - (NSNumber *)CLIP_MODE_DISABLED
return NUMINT(-1);
}

#ifdef USE_TI_UILISTVIEW
- (NSNumber *)ROW_ACTION_STYLE_DEFAULT
{
return @(UIContextualActionStyleNormal);
}
- (NSNumber *)ROW_ACTION_STYLE_DESTRUCTIVE
{
return @(UIContextualActionStyleDestructive);
}
- (NSNumber *)ROW_ACTION_STYLE_NORMAL
{
return @(UIContextualActionStyleNormal);
}
#if defined(USE_TI_UILISTVIEW) || defined(USE_TI_UITABLEVIEW)
MAKE_SYSTEM_PROP(ROW_ACTION_STYLE_DEFAULT, UIContextualActionStyleNormal);
MAKE_SYSTEM_PROP(ROW_ACTION_STYLE_DESTRUCTIVE, UIContextualActionStyleDestructive);
MAKE_SYSTEM_PROP(ROW_ACTION_STYLE_NORMAL, UIContextualActionStyleNormal);
#endif

#ifdef USE_TI_UIPICKER
Expand Down

0 comments on commit 40808cd

Please sign in to comment.