From 40808cd5ffed82abaeb7e7523100f901f336298f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hans=20Kn=C3=B6chel?= Date: Tue, 25 Jun 2024 04:58:01 +0200 Subject: [PATCH] feat: add swipe actions support for Ti.UI.TableView (#14065) * 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 --- apidoc/Titanium/UI/ListItem.yml | 29 +++++++++- apidoc/Titanium/UI/TableView.yml | 34 ++++++++++++ apidoc/Titanium/UI/TableViewRow.yml | 8 +++ iphone/Classes/TiUIListView.m | 86 +++++++++++++++-------------- iphone/Classes/TiUITableView.m | 67 ++++++++++++++++++++++ iphone/Classes/TiUIiOSProxy.h | 2 +- iphone/Classes/TiUIiOSProxy.m | 17 ++---- 7 files changed, 186 insertions(+), 57 deletions(-) diff --git a/apidoc/Titanium/UI/ListItem.yml b/apidoc/Titanium/UI/ListItem.yml index 30065848130..d29225625cb 100644 --- a/apidoc/Titanium/UI/ListItem.yml +++ b/apidoc/Titanium/UI/ListItem.yml @@ -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 ) and table view rows (if you use . + 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] + + - 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. diff --git a/apidoc/Titanium/UI/TableViewRow.yml b/apidoc/Titanium/UI/TableViewRow.yml index a5fb76598bc..a5e511827b9 100644 --- a/apidoc/Titanium/UI/TableViewRow.yml +++ b/apidoc/Titanium/UI/TableViewRow.yml @@ -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 . + type: Array + since: 12.4.0 + platforms: [iphone, ipad, macos] + - name: filterAlwaysInclude summary: | This row will always be visible when you filter your content. diff --git a/iphone/Classes/TiUIListView.m b/iphone/Classes/TiUIListView.m index f1ca5250794..ae821bb0393 100644 --- a/iphone/Classes/TiUIListView.m +++ b/iphone/Classes/TiUIListView.m @@ -1146,23 +1146,49 @@ - (BOOL)tableView:(UITableView *)tableView canHandleDropSession:(id *editActions = [editActionProxies filteredArrayUsingPredicate:predicate]; + NSMutableArray *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]; @@ -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 @@ -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]; diff --git a/iphone/Classes/TiUITableView.m b/iphone/Classes/TiUITableView.m index 31fba206cbb..bb417a072b6 100644 --- a/iphone/Classes/TiUITableView.m +++ b/iphone/Classes/TiUITableView.m @@ -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 *editActionProxies = (NSArray *)[row valueForKey:@"editActions"]; + NSPredicate *predicate = [NSPredicate predicateWithFormat:isDefault ? @"state == nil OR state == %@" : @"state == %@", state]; + NSArray *editActions = [editActionProxies filteredArrayUsingPredicate:predicate]; + NSMutableArray *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 diff --git a/iphone/Classes/TiUIiOSProxy.h b/iphone/Classes/TiUIiOSProxy.h index 0350a3dea11..ef246632734 100644 --- a/iphone/Classes/TiUIiOSProxy.h +++ b/iphone/Classes/TiUIiOSProxy.h @@ -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; diff --git a/iphone/Classes/TiUIiOSProxy.m b/iphone/Classes/TiUIiOSProxy.m index ceb94efd132..e06f49302e4 100644 --- a/iphone/Classes/TiUIiOSProxy.m +++ b/iphone/Classes/TiUIiOSProxy.m @@ -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