Commit 81af59d3 authored by edchin's avatar edchin Committed by Commit Bot

[ios] Add reusable drag and drop handlers

This CL adds 2 reusable handlers:
- TableViewURLDragDropHandler
- URLDragDropHandler

These are constructed similarly, so I put them into one CL
for contrast and comparison. I'll create separate CLs that
demonstrate how they are used.

The delegate and dataSource protocols draw inspiration
from UITableViewDropDelegate and UITableViewDataSource.

This is in a series of CLs to enable drag and/or drop of URLs
in Bookmarks, Reading List, History, Recent Tabs, Omnibox,
and tab strip.
Note, unit tests will be added in a fast followup CL.

Bug: 1087847
Change-Id: Ia2dca93d032054447b5305fe4ce7f8c86655e966
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2272117
Commit-Queue: edchin <edchin@chromium.org>
Reviewed-by: default avatarStepan Khapugin <stkhapugin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#784832}
parent 2d69944f
......@@ -12,6 +12,10 @@ source_set("drag_and_drop") {
"drop_and_navigate_delegate.h",
"drop_and_navigate_interaction.h",
"drop_and_navigate_interaction.mm",
"table_view_url_drag_drop_handler.h",
"table_view_url_drag_drop_handler.mm",
"url_drag_drop_handler.h",
"url_drag_drop_handler.mm",
]
deps = [
"//base",
......
......@@ -19,7 +19,7 @@ class WebState;
// whether to allow a drop.
@interface TabInfo : NSObject
// The unique identifier of the tab.
@property(nonatomic, strong, readonly) NSString* tabID;
@property(nonatomic, copy, readonly) NSString* tabID;
// If YES, the tab is currently in an incognito profile.
@property(nonatomic, assign, readonly) BOOL incognito;
// Default initializer.
......@@ -27,12 +27,23 @@ class WebState;
- (instancetype)init NS_UNAVAILABLE;
@end
// Wrapper object that includes URL and title.
@interface URLInfo : NSObject
// The URL.
@property(nonatomic, assign, readonly) GURL URL;
// Title of the page at the URL.
@property(nonatomic, copy, readonly) NSString* title;
// Default initializer.
- (instancetype)initWithURL:(const GURL&)URL title:(NSString*)title;
- (instancetype)init NS_UNAVAILABLE;
@end
// Creates a drag item that encapsulates a tab and a user activity to move the
// tab to a new window.
UIDragItem* CreateTabDragItem(web::WebState* web_state);
// Creates a drag item that encapsulates an URL and a user activity to open the
// URL in a new Chrome window.
UIDragItem* CreateURLDragItem(const GURL& url, WindowActivityOrigin origin);
UIDragItem* CreateURLDragItem(URLInfo* url_info, WindowActivityOrigin origin);
#endif // IOS_CHROME_BROWSER_DRAG_AND_DROP_DRAG_ITEM_UTIL_H_
......@@ -26,6 +26,17 @@
}
@end
@implementation URLInfo
- (instancetype)initWithURL:(const GURL&)URL title:(NSString*)title {
self = [super init];
if (self) {
_URL = URL;
_title = title;
}
return self;
}
@end
UIDragItem* CreateTabDragItem(web::WebState* web_state) {
DCHECK(web_state);
NSURL* url = net::NSURLWithGURL(web_state->GetVisibleURL());
......@@ -46,19 +57,18 @@ UIDragItem* CreateTabDragItem(web::WebState* web_state) {
return drag_item;
}
UIDragItem* CreateURLDragItem(const GURL& url, WindowActivityOrigin origin) {
DCHECK(url.is_valid());
NSURL* url_object = net::NSURLWithGURL(url);
UIDragItem* CreateURLDragItem(URLInfo* url_info, WindowActivityOrigin origin) {
DCHECK(url_info.URL.is_valid());
NSItemProvider* item_provider =
[[NSItemProvider alloc] initWithObject:url_object];
[[NSItemProvider alloc] initWithObject:net::NSURLWithGURL(url_info.URL)];
// Visibility "all" is required to allow the OS to recognize this activity for
// creating a new window.
[item_provider registerObject:ActivityToLoadURL(origin, url)
[item_provider registerObject:ActivityToLoadURL(origin, url_info.URL)
visibility:NSItemProviderRepresentationVisibilityAll];
UIDragItem* drag_item =
[[UIDragItem alloc] initWithItemProvider:item_provider];
// Local objects allow synchronous drops, whereas NSItemProvider only allows
// asynchronous drops.
drag_item.localObject = url_object;
drag_item.localObject = url_info;
return drag_item;
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_DRAG_AND_DROP_TABLE_VIEW_URL_DRAG_DROP_HANDLER_H_
#define IOS_CHROME_BROWSER_DRAG_AND_DROP_TABLE_VIEW_URL_DRAG_DROP_HANDLER_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/window_activities/window_activity_helpers.h"
class GURL;
@class URLInfo;
// The interface for providing draggable URLs from a table view.
@protocol TableViewURLDragDataSource
// Returns a wrapper object with URL and title to drag for the item at
// |indexPath| in |tableView|. Returns nil if item at |indexPath| is not
// draggable.
- (URLInfo*)tableView:(UITableView*)tableView
URLInfoAtIndexPath:(NSIndexPath*)indexPath;
@end
// The interface for handling URL drops in a table view.
@protocol TableViewURLDropDelegate
// Returns whether |tableView| is in a state to handle URL drops.
- (BOOL)canHandleURLDropInTableView:(UITableView*)tableView;
// Provides the receiver with the dropped |URL|, which was dropped at
// |indexPath| in |tableView|.
- (void)tableView:(UITableView*)tableView
didDropURL:(const GURL&)URL
atIndexPath:(NSIndexPath*)indexPath;
@end
// A delegate object that is configured to handle URL drag and drops from a
// table view.
@interface TableViewURLDragDropHandler
: NSObject <UITableViewDragDelegate, UITableViewDropDelegate>
// Origin used to configure drag items.
@property(nonatomic, assign) WindowActivityOrigin origin;
// The data source object that provides draggable URLs from a table view.
@property(nonatomic, weak) id<TableViewURLDragDataSource> dragDataSource;
// The delegate object that manages URL drops into a table view.
@property(nonatomic, weak) id<TableViewURLDropDelegate> dropDelegate;
@end
#endif // IOS_CHROME_BROWSER_DRAG_AND_DROP_TABLE_VIEW_URL_DRAG_DROP_HANDLER_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/drag_and_drop/table_view_url_drag_drop_handler.h"
#import <MobileCoreServices/UTCoreTypes.h>
#include "base/check_op.h"
#include "base/mac/foundation_util.h"
#import "ios/chrome/browser/drag_and_drop/drag_item_util.h"
#import "net/base/mac/url_conversions.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@implementation TableViewURLDragDropHandler
#pragma mark - UITableViewDragDelegate
- (NSArray<UIDragItem*>*)tableView:(UITableView*)tableView
itemsForBeginningDragSession:(id<UIDragSession>)session
atIndexPath:(NSIndexPath*)indexPath {
DCHECK(self.dragDataSource && self.origin);
URLInfo* info = [self.dragDataSource tableView:tableView
URLInfoAtIndexPath:indexPath];
// Returning nil indicates that the drag item has no content to be dropped.
// However, drag to reorder within the table view is still enabled/controlled
// by the UITableViewDataSource reordering methods.
return info ? @[ CreateURLDragItem(info, self.origin) ] : nil;
}
- (NSArray<UIDragItem*>*)tableView:(UITableView*)tableView
itemsForAddingToDragSession:(id<UIDragSession>)session
atIndexPath:(NSIndexPath*)indexPath
point:(CGPoint)point {
// TODO(crbug.com/1100940): Enable multi-select dragging.
return nil;
}
- (void)tableView:(UITableView*)tableView
dragSessionWillBegin:(id<UIDragSession>)session {
DCHECK_EQ(1U, session.items.count);
UIDragItem* item = session.items.firstObject;
URLInfo* info = base::mac::ObjCCastStrict<URLInfo>(item.localObject);
session.items.firstObject.previewProvider = ^{
return [UIDragPreview previewForURL:net::NSURLWithGURL(info.URL)
title:info.title];
};
}
#pragma mark - UITableViewDropDelegate
- (BOOL)tableView:(UITableView*)tableView
canHandleDropSession:(id<UIDropSession>)session {
DCHECK(self.dropDelegate);
// TODO(crbug.com/1100940): Enable multi-item drops.
return session.items.count == 1U &&
[self.dropDelegate canHandleURLDropInTableView:tableView] &&
[session hasItemsConformingToTypeIdentifiers:@[
(__bridge NSString*)kUTTypeURL
]];
}
- (UITableViewDropProposal*)tableView:(UITableView*)tableView
dropSessionDidUpdate:(id<UIDropSession>)session
withDestinationIndexPath:(NSIndexPath*)destinationIndexPath {
UIDropOperation operation =
tableView.hasActiveDrag ? UIDropOperationMove : UIDropOperationCopy;
return [[UITableViewDropProposal alloc]
initWithDropOperation:operation
intent:UITableViewDropIntentInsertAtDestinationIndexPath];
}
- (void)tableView:(UITableView*)tableView
performDropWithCoordinator:(id<UITableViewDropCoordinator>)coordinator {
DCHECK(self.dropDelegate);
if ([coordinator.session canLoadObjectsOfClass:[NSURL class]]) {
__weak TableViewURLDragDropHandler* weakSelf = self;
[coordinator.session
loadObjectsOfClass:[NSURL class]
completion:^(NSArray<NSURL*>* objects) {
// TODO(crbug.com/1100940): Enable multi-item drops.
DCHECK_EQ(1U, objects.count);
GURL URL = net::GURLWithNSURL(objects.firstObject);
if (URL.is_valid()) {
[weakSelf.dropDelegate
tableView:tableView
didDropURL:URL
atIndexPath:coordinator.destinationIndexPath];
}
}];
}
}
@end
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_DRAG_AND_DROP_URL_DRAG_DROP_HANDLER_H_
#define IOS_CHROME_BROWSER_DRAG_AND_DROP_URL_DRAG_DROP_HANDLER_H_
#import <UIKit/UIKit.h>
#import "ios/chrome/browser/window_activities/window_activity_helpers.h"
class GURL;
@class URLInfo;
// The interface for providing a draggable URL from a view.
@protocol URLDragDataSource
// Returns a wrapper object with URL and title for dragging from |view|. Returns
// nil if |view| is not currently draggable.
- (URLInfo*)URLInfoForView:(UIView*)view;
// Returns the visible path for the |view| used for the drag preview.
- (UIBezierPath*)visiblePathForView:(UIView*)view;
@end
// The interface for handling URL drops in a view.
@protocol URLDropDelegate
// Returns whether |view| is in a state to handle URL drops.
- (BOOL)canHandleURLDropInView:(UIView*)view;
// Provides the receiver with the dropped |URL|, which was dropped at |point| in
// the coordinate space of the |view|'s bounds.
- (void)view:(UIView*)view didDropURL:(const GURL&)URL atPoint:(CGPoint)point;
@end
// A delegate object that is configured to handle single URL drags and drops
// from a view.
@interface URLDragDropHandler
: NSObject <UIDragInteractionDelegate, UIDropInteractionDelegate>
// Origin used to configure drag items.
@property(nonatomic, assign) WindowActivityOrigin origin;
// The data source object that provides draggable URLs from a view.
@property(nonatomic, weak) id<URLDragDataSource> dragDataSource;
// The delegate object that manages URL drops onto a view.
@property(nonatomic, weak) id<URLDropDelegate> dropDelegate;
@end
#endif // IOS_CHROME_BROWSER_DRAG_AND_DROP_URL_DRAG_DROP_HANDLER_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/drag_and_drop/url_drag_drop_handler.h"
#import <MobileCoreServices/UTCoreTypes.h>
#include "base/check_op.h"
#include "base/mac/foundation_util.h"
#import "ios/chrome/browser/drag_and_drop/drag_item_util.h"
#import "net/base/mac/url_conversions.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@implementation URLDragDropHandler
#pragma mark - UIDragInteractionDelegate
- (NSArray<UIDragItem*>*)dragInteraction:(UIDragInteraction*)interaction
itemsForBeginningSession:(id<UIDragSession>)session {
DCHECK(self.dragDataSource);
URLInfo* info = [self.dragDataSource URLInfoForView:interaction.view];
// Returning nil indicates that the drag item has no content to be dropped.
// However, drag to reorder within the table view is still enabled/controlled
// by the UITableViewDataSource reordering methods.
return info ? @[ CreateURLDragItem(info, self.origin) ] : nil;
}
- (NSArray<UIDragItem*>*)dragInteraction:(UIDragInteraction*)interaction
itemsForAddingToSession:(id<UIDragSession>)session
withTouchAtPoint:(CGPoint)point {
return nil;
}
- (UITargetedDragPreview*)dragInteraction:(UIDragInteraction*)interaction
previewForLiftingItem:(UIDragItem*)item
session:(id<UIDragSession>)session {
return [self previewForDragInteraction:interaction];
}
- (UITargetedDragPreview*)dragInteraction:(UIDragInteraction*)interaction
previewForCancellingItem:(UIDragItem*)item
withDefault:
(UITargetedDragPreview*)defaultPreview {
return [self previewForDragInteraction:interaction];
}
- (void)dragInteraction:(UIDragInteraction*)interaction
item:(UIDragItem*)item
willAnimateCancelWithAnimator:(id<UIDragAnimating>)animator {
[animator addAnimations:^{
// It looks better to fade the interaction view as the translucent preview
// flocks back to its original position above the view during cancellation.
interaction.view.alpha = 0.1;
}];
[animator addCompletion:^(UIViewAnimatingPosition finalPosition) {
interaction.view.alpha = 1.0;
}];
}
- (void)dragInteraction:(UIDragInteraction*)interaction
sessionWillBegin:(id<UIDragSession>)session {
DCHECK_EQ(1U, session.items.count);
UIDragItem* item = session.items.firstObject;
URLInfo* info = base::mac::ObjCCastStrict<URLInfo>(item.localObject);
session.items.firstObject.previewProvider = ^{
return [UIDragPreview previewForURL:net::NSURLWithGURL(info.URL)
title:info.title];
};
}
#pragma mark - Private drag helper
- (UITargetedDragPreview*)previewForDragInteraction:
(UIDragInteraction*)interaction {
UIDragPreviewParameters* parameters = [[UIDragPreviewParameters alloc] init];
parameters.visiblePath =
[self.dragDataSource visiblePathForView:interaction.view];
return [[UITargetedDragPreview alloc] initWithView:interaction.view
parameters:parameters];
}
#pragma mark - UIDropInteractionDelegate
- (BOOL)dropInteraction:(UIDropInteraction*)interaction
canHandleSession:(id<UIDropSession>)session {
DCHECK(self.dropDelegate);
// TODO(crbug.com/1100940): Enable multi-item drops.
return session.items.count == 1U &&
[self.dropDelegate canHandleURLDropInView:interaction.view] &&
[session hasItemsConformingToTypeIdentifiers:@[
(__bridge NSString*)kUTTypeURL
]];
}
- (UIDropProposal*)dropInteraction:(UIDropInteraction*)interaction
sessionDidUpdate:(id<UIDropSession>)session {
return [[UIDropProposal alloc] initWithDropOperation:UIDropOperationCopy];
}
- (void)dropInteraction:(UIDropInteraction*)interaction
performDrop:(id<UIDropSession>)session {
DCHECK(self.dropDelegate);
if ([session canLoadObjectsOfClass:[NSURL class]]) {
__weak URLDragDropHandler* weakSelf = self;
[session
loadObjectsOfClass:[NSURL class]
completion:^(NSArray<NSURL*>* objects) {
// TODO(crbug.com/1100940): Enable multi-item drops.
DCHECK_EQ(1U, objects.count);
GURL URL = net::GURLWithNSURL(objects.firstObject);
if (URL.is_valid()) {
[weakSelf.dropDelegate
view:interaction.view
didDropURL:URL
atPoint:[session locationInView:interaction.view]];
}
}];
}
}
@end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment