浅谈iOS 11.0中UITableView 都更改了什么? (三)

版本记录

版本号 时间
V1.0 2018.01.25

前言

2017年iOS版本更新到了11.0的系统,新机器比如iPhone X都是预装11.0的系统,而我们的UIKit框架中的UITableView类都做了哪些更改?接下来我们就看一下iOS11.0中UITableView类的改变,共22处新增,改动的还是很大的,下面我们就详细的看一下。感兴趣的可以看上面几篇。
1. 浅谈iOS 11.0中UITableView 都更改了什么? (一)
2. 浅谈iOS 11.0中UITableView 都更改了什么? (二)

协议 UITableViewDropItem

与item相关联的数据被放入tableview。还是先看一下这个API

API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos, watchos)
@protocol UITableViewDropItem 

// Retrieve drop data from the dragItem's itemProvider.
@property (nonatomic, readonly) UIDragItem *dragItem;

// If this drop item is also from this table view this index path will specify the location of the row it came from.
// If the dragItem comes from some other source (e.g. another source inside or outside of the app), or if the source
// table view is updated or reloaded after the drag begins, this index path will be nil.
// This is useful for directly accessing the model object in your data source instead of using the item provider
// to retrieve the data.
@property (nonatomic, readonly, nullable) NSIndexPath *sourceIndexPath;

// May be useful for computing the UIDragPreviewTarget transform for UITableViewDropCoordinator dropItem:toTarget:
// Returns CGSizeZero if the dragItem does not have a visible drop preview.
@property (nonatomic, readonly) CGSize previewSize;

@end

处理drop时,您从 UITableViewDropCoordinator对象的items属性中获取此类的实例。 使用它们检索正在拖动的项目的数据,并计划与drop项目相关的任何动画。 你不要自己创建这个类的实例。

Topics

1. Getting the Drag Item

  • dragItem
    • 要拖动的item,Required

2. Getting the Item Information

  • previewSize

    • 拖动项目预览的大小。Required
  • sourceIndexPath

    • tableview中项目的索引路径(如果有的话),Required

协议 UITableViewDropPlaceholderContext

在等待接收您计划显示的实际数据时,您临时插入到tableview中的对象。下面先看一下该协议的API

API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos, watchos)
@protocol UITableViewDropPlaceholderContext 

// The drag item this placeholder was created for.
@property (nonatomic, readonly) UIDragItem *dragItem;

// Exchange the placeholder for the final cell.
// You are only responsible for updating your data source inside the block using the provided insertionIndexPath.
// If the placeholder is no longer available (e.g. -reloadData has been called) the dataSourceUpdates block
// will not be executed and this will return NO.
- (BOOL)commitInsertionWithDataSourceUpdates:(void(NS_NOESCAPE ^)(NSIndexPath *insertionIndexPath))dataSourceUpdates;

// If the placeholder is no longer needed or you wish to manually insert a cell for the drop data, you can
// remove the placeholder via this method.
// If the placeholder is no longer available (e.g. -reloadData has been called) this will return NO.
- (BOOL)deletePlaceholder;

@end

这里,可以看见这个协议父协议是UIDragAnimating,它存在于新加入的类和文件UIDragInteraction.h

//
//  UIDragInteraction.h
//  UIKit
//
//  Copyright © 2017 Apple Inc. All rights reserved.
//

#import 
#import 
#import 

NS_ASSUME_NONNULL_BEGIN

@protocol UIDragInteractionDelegate, UIDragSession;
@class UIDragItem, UITargetedDragPreview;


API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(watchos, tvos) @protocol UIDragAnimating 

/* Instances of UIDragAnimating provide you a way to animate your own changes
 * alongside the drag and drop system's animations.
 *
 * Animatable view properties that are set by the animation block will be
 * animated to their new values. 
 */
- (void)addAnimations:(void (^)(void))animations;
- (void)addCompletion:(void (^)(UIViewAnimatingPosition finalPosition))completion;

@end


UIKIT_EXTERN API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(watchos, tvos) @interface UIDragInteraction : NSObject 

- (instancetype)initWithDelegate:(id)delegate NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

@property (nonatomic, nullable, readonly, weak) id delegate;

/* Determines whether this interaction allows recognition of other gesture recognizers during the lift.
 * If true, the interaction will be cancelled during the lift if another gesture recognizer recognizes.
 * If false (the default value), all competing gesture recognizers will be failed when the lift begins.
 * Note: UILongPressGestureRecognizers are always delayed and simultaneous during the lift.
 */
@property (nonatomic) BOOL allowsSimultaneousRecognitionDuringLift;

/* Whether this interaction is allowed to drag.
 * If true, the interaction will use touches to begin drags and/or add items to drags.
 * If false, it will ignore touches.
 */
@property (nonatomic, getter=isEnabled) BOOL enabled;

/* The default value of `enabled` in newly created UIDragInteractions.
 * The value depends on the device.
 */
@property (class, nonatomic, readonly, getter=isEnabledByDefault) BOOL enabledByDefault;

@end


API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(watchos, tvos) @protocol UIDragInteractionDelegate 

@required

/* Provide items to begin a drag.
 *
 * If these items represent things that are displayed in a linear order
 * (for example, rows in a table), provide them in the same order, first
 * to last.
 *
 * If an empty array is returned, then no drag will begin.
 */
- (NSArray *)dragInteraction:(UIDragInteraction *)interaction itemsForBeginningSession:(id)session;

@optional

/* Provide a preview to display while lifting the drag item.
 * Return nil to indicate that this item is not visible and should have no lift animation.
 * If not implemented, a UITargetedDragPreview initialized with interaction.view will be used.
 */
- (nullable UITargetedDragPreview *)dragInteraction:(UIDragInteraction *)interaction previewForLiftingItem:(UIDragItem *)item session:(id)session;

/* Called when the lift animation is about to start.
 * Use the animator to animate your own changes alongside the system animation,
 * or to be called when the lift animation completes.
 */
- (void)dragInteraction:(UIDragInteraction *)interaction willAnimateLiftWithAnimator:(id)animator session:(id)session;

/* Drag session lifecycle. */

/* Called when the the items are in their fully lifted appearance,
 * and the user has started to drag the items away.
 */
- (void)dragInteraction:(UIDragInteraction *)interaction sessionWillBegin:(id)session;

/* Return whether this drag allows the "move" drop operation to happen.
 * This only applies to drops inside the same app. Drops in other apps are always copies.
 *
 * If true, then a UIDropInteraction's delegate's -dropInteraction:sessionDidUpdate:
 * may choose to return UIDropOperationMove, and that operation will be provided to
 * -dragInteraction:session:willEndWithOperation: and -dragInteraction:session:didEndWithOperation:.
 *
 * If not implemented, defaults to true.
 */
- (BOOL)dragInteraction:(UIDragInteraction *)interaction sessionAllowsMoveOperation:(id)session;

/* Return whether this drag is restricted to only this application.
 *
 * If true, then the drag will be restricted. Only this application will be
 * able to see the drag, and other applications will not.
 * If the user drops it on another application, the drag will be cancelled.
 *
 * If false, then the drag is not restricted. Other applications may see the drag,
 * and the user may drop it onto them.
 *
 * If not implemented, defaults to false.
 *
 * Note that this method is called only on devices that support dragging across applications.
 */
- (BOOL)dragInteraction:(UIDragInteraction *)interaction sessionIsRestrictedToDraggingApplication:(id)session;

/* Return whether this drag's items' previews should be shown in their full
 * original size while over the source view. For instance, if you are reordering
 * items, you may want them not to shrink like they otherwise would.
 *
 * If not implemented, defaults to false.
 */
- (BOOL)dragInteraction:(UIDragInteraction *)interaction prefersFullSizePreviewsForSession:(id)session;

/* Called when the drag has moved (because the user's touch moved).
 * Use -[UIDragSession locationInView:] to get its new location.
 */
- (void)dragInteraction:(UIDragInteraction *)interaction sessionDidMove:(id)session;

/* Called when the user is done dragging, and the drag will finish.
 *
 * If the operation is UIDropOperationCancel or UIDropOperationForbidden,
 * the delegate should prepare its views to show an appropriate appearance 
 * before the cancel animation starts.
 */
- (void)dragInteraction:(UIDragInteraction *)interaction session:(id)session willEndWithOperation:(UIDropOperation)operation;

/* Called when the user is done dragging and all related animations are
 * completed. The app should now return to its normal appearance.
 *
 * If the operation is UIDropOperationCopy or UIDropOperationMove, 
 * then data transfer will begin, and -dragInteraction:sessionDidTransferItems: will be called later.
 */
- (void)dragInteraction:(UIDragInteraction *)interaction session:(id)session didEndWithOperation:(UIDropOperation)operation;

/* Called after a drop happened and the handler of the drop has received
 * all of the data that it requested. You may now clean up any extra information
 * relating to those items or their item providers.
 */
- (void)dragInteraction:(UIDragInteraction *)interaction sessionDidTransferItems:(id)session;

/* Adding items to an existing drag. */

/* To allow touches on this view to add items to an existing drag,
 * implement `-dragInteraction:itemsForAddingToSession:withTouchAtPoint:`.
 *
 * If the provided session is an appropriate one to use, then return an array of items,
 * just like in `-dragInteraction:itemsForBeginningSession:`. They will be added to the 
 * drag session.
 * Afterwards, `-dragInteraction:session:willAddItems:forInteraction:` will be sent
 * to each interaction which has contributed items to the session, including this one.
 *
 * Otherwise, return an empty array. No items will be added, and the touch
 * will be handled as if the other drag session did not exist.
 *
 * If this method is not implemented, acts as though an an empty array was returned.
 *
 * Use the point, in the view's coordinate space, to determine what part of your view
 * has been touched.
 */
- (NSArray *)dragInteraction:(UIDragInteraction *)interaction itemsForAddingToSession:(id)session withTouchAtPoint:(CGPoint)point;

/* If there are two or more existing drag sessions, it may not be clear to the user
 * which session items will be added to. Therefore, by default, we do not add to any session.
 *
 * If you *do* want to add the item to a session, implement
 * `-dragInteraction:sessionForAddingItems:withTouchAtPoint:` and return the
 * appropriate session. This should be rare.
 *
 * To continue without adding items, return nil.
 *
 * If this method is not implemented, defaults to nil.
 */
- (nullable id)dragInteraction:(UIDragInteraction *)interaction sessionForAddingItems:(NSArray> *)sessions withTouchAtPoint:(CGPoint)point;

/* Similar to -dragInteraction:sessionWillBegin:, but for
 * items added to an session after it has already begun.
 *
 * Note that the interaction that is causing the items to be added
 * may be different than the interaction that started the drag.
 * This method is called on the delegates of all interactions that ever added items
 * to this session. `addingInteraction` is the interaction that is causing
 * these new items to be dragged.
 */
- (void)dragInteraction:(UIDragInteraction *)interaction session:(id)session willAddItems:(NSArray *)items forInteraction:(UIDragInteraction *)addingInteraction;

/* Drag cancellation animation. */

/* Called when the drag is cancelled, once for each visible item.
 * Provide a preview to animate the item back to where it belongs.
 * We provide `defaultPreview` which would move the current preview back to where it came from.
 * You may return:
 * - defaultPreview, to use it as-is
 * - nil, to fade the drag item in place
 * - [defaultPreview retargetedPreviewWithTarget:] to move the preview to a different target
 * - a UITargetedDragPreview that you create however you like
 */
- (nullable UITargetedDragPreview *)dragInteraction:(UIDragInteraction *)interaction previewForCancellingItem:(UIDragItem *)item withDefault:(UITargetedDragPreview *)defaultPreview;

/* Called when the cancel animation is about to start, once for each item,
 * whether it is visible or not.
 * Use the animator to animate your own changes alongside the system animation.
 */
- (void)dragInteraction:(UIDragInteraction *)interaction item:(UIDragItem *)item willAnimateCancelWithAnimator:(id)animator;

@end

NS_ASSUME_NONNULL_END

可见,新增了很多的东西。对于协议UITableViewDropPlaceholderContext,你不要自己创建这个类的实例。 对于插入到tableview中的每个占位符单元格,drop coordinator 将为您提供此类的一个实例。 稍后使用此上下文对象通过将实际数据提交给数据源对象或通过删除占位符单元来删除占位符单元格。

Topics

1. Updating the Placeholder Cell

  • commitInsertionWithDataSourceUpdates:
    • 交换placeholder cell和具有最终内容的cell,Required

2. Removing the Placeholder Cell

  • deletePlaceholder
    • 从tableview中一并删除不需要的placeholder cell,Required

3. Getting the Drag Item

  • dragItem
    • 拖动item由placeholder cell表示,Required

4. Inherits From

  • UIDragAnimating

类 UITableViewPlaceholder

我们先看一下该来的API

UIKIT_EXTERN API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos, watchos)
@interface UITableViewPlaceholder : NSObject

// A placeholder cell will be dequeued for the reuse identifier and inserted at the specified index path without requiring a data source update.
// You may use UITableViewAutomaticDimension for the rowHeight to have the placeholder cell self-size if the table view is using estimated row heights.

占位符单元格将根据重用标识符被重新取出,并插入指定的索引路径而不需要更新数据源。 如果tableview使
用估计的行高,则可以使用rowHeight的UITableViewAutomaticDimension使占位符单元自己大小。

- (instancetype)initWithInsertionIndexPath:(NSIndexPath *)insertionIndexPath reuseIdentifier:(NSString *)reuseIdentifier rowHeight:(CGFloat)rowHeight NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

// Called whenever the placeholder cell is visible to update the contents of the cell.
@property (nonatomic, nullable, copy) void(^cellUpdateHandler)(__kindof UITableViewCell *);

@end

类 UITableViewDropPlaceholder

它继承自UITableViewPlaceholder,这个也是新增的类,看一下它的API文档。

UIKIT_EXTERN API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos, watchos)
@interface UITableViewDropPlaceholder : UITableViewPlaceholder

// Allows customization of the preview used when dropping to a placeholder.
// If no block is set, or if nil is returned, the entire cell will be used for the preview.
  允许定制在放置到占位符时使用的预览。 如果没有设置block,或者返回nil,则整个单元格将用于预览。
@property (nonatomic, nullable, copy) UIDragPreviewParameters * _Nullable (^previewParametersProvider)(__kindof UITableViewCell *);

@end

UITableViewDelegate新增方法三处

  • 1. - (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);

  • 2. - (nullable UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos);

前面这两个方法就是关于leading或者trailing拖动cell时的自定义操作,可以看一下下面这个示例,先看代码。

#import "ViewController.h"

@interface ViewController () 

@property (nonatomic, strong) UITableView *tableView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
    self.tableView.backgroundColor = [UIColor lightGrayColor];
    self.tableView.delegate = self;
    self.tableView.dataSource = self;
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
    [self.view addSubview:self.tableView];
}

#pragma mark -  UITableViewDelegate, UITableViewDataSource

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 15;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return 80.0;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    return cell;
}

- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView leadingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UIContextualAction *cancelAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:@"取消" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
        NSLog(@"取消");
        NSLog(@"%@", sourceView);
    }];
    UIContextualAction *ensureAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:@"删除" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
        NSLog(@"删除");
        NSLog(@"%@", sourceView);
    }];
    UISwipeActionsConfiguration *actions = [UISwipeActionsConfiguration configurationWithActions:@[cancelAction, ensureAction]];
    return actions;
}

- (UISwipeActionsConfiguration *)tableView:(UITableView *)tableView trailingSwipeActionsConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UIContextualAction *topAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleDestructive title:@"置顶" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
        NSLog(@"置顶");
        NSLog(@"%@", sourceView);
    }];
    UIContextualAction *hasReadAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:@"已读" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
        NSLog(@"已读");
        NSLog(@"%@", sourceView);
    }];
    hasReadAction.backgroundColor = [UIColor yellowColor];
    UIContextualAction *shareAction = [UIContextualAction contextualActionWithStyle:UIContextualActionStyleNormal title:@"分享" handler:^(UIContextualAction * _Nonnull action, __kindof UIView * _Nonnull sourceView, void (^ _Nonnull completionHandler)(BOOL)) {
        NSLog(@"分享");
        NSLog(@"%@", sourceView);
    }];
    shareAction.backgroundColor = [UIColor blueColor];
    UISwipeActionsConfiguration *actions = [UISwipeActionsConfiguration configurationWithActions:@[topAction, hasReadAction, shareAction]];
    return actions;
}

@end

下面看一下输出及效果示意图。

2018-01-25 12:06:49.601950+0800 JJTableView_demo2[1741:405733] 取消
2018-01-25 12:06:49.603755+0800 JJTableView_demo2[1741:405733] >
2018-01-25 12:06:50.350196+0800 JJTableView_demo2[1741:405733] 删除
2018-01-25 12:06:50.350781+0800 JJTableView_demo2[1741:405733] >
2018-01-25 12:06:59.116568+0800 JJTableView_demo2[1741:405733] 分享
2018-01-25 12:06:59.117134+0800 JJTableView_demo2[1741:405733] >
2018-01-25 12:06:59.899444+0800 JJTableView_demo2[1741:405733] 已读
2018-01-25 12:06:59.900211+0800 JJTableView_demo2[1741:405733] >
2018-01-25 12:07:01.647648+0800 JJTableView_demo2[1741:405733] 置顶
2018-01-25 12:07:01.648073+0800 JJTableView_demo2[1741:405733] >
浅谈iOS 11.0中UITableView 都更改了什么? (三)_第1张图片
浅谈iOS 11.0中UITableView 都更改了什么? (三)_第2张图片

这个和以前的rowAction那些方法的用法是一样的,iOS11.0开放了这两个代理方法,使用起来更方便,但是要注意系统适配。

  • 3. - (BOOL)tableView:(UITableView *)tableView shouldSpringLoadRowAtIndexPath:(NSIndexPath *)indexPath withContext:(id)context API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos, watchos);

调用让你调整tableview中的行的弹簧加载行为,先看一下API文档

// Spring Loading

// Allows opting-out of spring loading for an particular row.
// If you want the interaction effect on a different subview of the spring loaded cell, modify the context.targetView property. The default is the cell.
// If this method is not implemented, the default is YES except when the row is part of a drag session.

允许选择某一行的弹簧加载。 如果你想在弹簧加载的单元格的不同子视图上的交互效果,修改context.targetView属性。 默认
是单元格cell。 如果此方法未实现,则默认值为YES,除非该行是拖动会话drag session的一部分。

- (BOOL)tableView:(UITableView *)tableView shouldSpringLoadRowAtIndexPath:(NSIndexPath *)indexPath withContext:(id)context API_AVAILABLE(ios(11.0)) API_UNAVAILABLE(tvos, watchos);

先看一下参数和返回值:

  • tableView:交互发生的tableview
  • indexPath :spring-loading行为考虑的行的indexPath
  • context:一个上下文对象,您可以使用它来修改spring-loading行为。 使用此对象来指定行中与spring-loading动画相关的位置。
  • return:如果该行允许spring-loading的交互,则为YES,否则为NO

如果要选择性地禁用与表格的行进行弹簧加载的交互,请重写此方法。 例如,对于表示叶子内容的行而不是内容文件夹,您可能会返回NO。 如果你没有实现这个方法,tableview在当前没有被拖动的时候会在行上执行弹簧加载动画。默认情况下,在整行上执行弹簧加载动画。 要修改这些动画,请修改提供的上下文对象。 例如,您可以使用上下文对象将弹簧加载动画应用到行的单个子视图,而不是整个行。

后记

本篇已结束,后面更精彩~~~

浅谈iOS 11.0中UITableView 都更改了什么? (三)_第3张图片

你可能感兴趣的:(浅谈iOS 11.0中UITableView 都更改了什么? (三))