iOS - 移动自定义UITableViewCell

这里写图片描述
1.长按即可触发移动cell,操作逻辑简单;
2.移动cell时越靠近屏幕边缘,速度越快;
3.被移动cell的样式可以自定义;
调研

如果只是实现移动UITableViewCell,系统自带的API即可搞定。
调用下面的方法[tableView setEditing:YES animated:YES];即可进入编辑模式。然后实现下面的方法即可开启移动cell。
//默认编辑模式下,每个cell左边有个红色的删除按钮,设置为None即可去掉

- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return UITableViewCellEditingStyleNone;
}
//是否允许indexPath的cell移动
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
    return YES;
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
    //更新数据源
}

用系统的方法有几个缺点:
1.需要用一个开关去控制编辑状态,不方便;
2.移动cell的时候cell右边有个指示图标,看着不爽;
3.被移动cell的样式不能自己定制。
综上所述,需要自己去写效果了。

实现原理
大概原理:为UITableView添加一个长按手势,然后给选中的cell截图;让截图随着手势移动,同时记录选中的indexPath,方便位置互换。

1.添加长按手势

longPressGesutre = UILongPressGestureRecognizer.init(target: self, action:#selector(HomePageViewController.handleLongpressGesture(_:)))
        longPressGesutre.minimumPressDuration = 0.5
        self.tableView.addGestureRecognizer(longPressGesutre)

2.处理手势开始

    func handleLongpressGesture(sender:UILongPressGestureRecognizer) {

        switch sender.state {
        case UIGestureRecognizerState.Began:
            gestureBegan(sender)
            break
        case UIGestureRecognizerState.Changed:
            gestureChanged(sender)
            break
        case UIGestureRecognizerState.Cancelled:
            gestureEnded(sender)
            break
        case UIGestureRecognizerState.Ended:
            gestureEnded(sender)
            break
        default:

            break
        }
    }

3 开始处理手势

private func gestureBegan(gesture:UILongPressGestureRecognizer) {
        let location:CGPoint = gesture.locationInView(self.tableView)
        let indexPath:NSIndexPath! = self.tableView.indexPathForRowAtPoint(location)

        if indexPath != nil {
            // 开启边缘滚动
            startEdgeScroll()
            sourceIndexPath = indexPath
            let cell:HomeSubTableViewCell = self.tableView.cellForRowAtIndexPath(indexPath) as! HomeSubTableViewCell
            snapshot = self.customSnapshotFromView(cell)
            var center:CGPoint = cell.center
            snapshot.center = center
            snapshot.alpha = 0
            self.tableView.addSubview(snapshot)
            UIView.animateWithDuration(0.25, animations: {
                center.y = location.y
                self.snapshot.center = center
                self.snapshot.transform = CGAffineTransformMakeScale(1, 1)
                self.snapshot.alpha = 1.0
                self.snapshotColor = cell.backgroundColor
                cell.backgroundColor = UIColor.clearColor()
                cell.hidden = true
                }, completion: nil)
        }
    }

4 处理手势状态变化,并交换数据源

private func gestureChanged(gesture:UILongPressGestureRecognizer) {
        let location:CGPoint = gesture.locationInView(self.tableView)
        let indexPath:NSIndexPath! = self.tableView.indexPathForRowAtPoint(location)
        var center:CGPoint = snapshot.center
        center.y = location.y
        snapshot.center = center
        if indexPath != nil && indexPath.isEqual(sourceIndexPath) == false {

            let cellA:HomeSubTableViewCell = tableView.cellForRowAtIndexPath(sourceIndexPath) as! HomeSubTableViewCell
            let cellB:HomeSubTableViewCell = tableView.cellForRowAtIndexPath(indexPath) as! HomeSubTableViewCell
            let modelToMove:HomePageCellModel = self.dataArr![sourceIndexPath.row]
            self.dataArr!.removeAtIndex(sourceIndexPath.row)
            self.dataArr!.insert(modelToMove, atIndex: indexPath.row)
            StoreDataModel.sharedInstance.changeChooseRate((cellA.model?.nameEn)!, codeB: (cellB.model?.nameEn)!)
            self.tableView.moveRowAtIndexPath(sourceIndexPath, toIndexPath: indexPath)
            sourceIndexPath = indexPath
        }
    }

5 手势结束或者取消,结束之后将视图还原

private func gestureEnded(gesture:UILongPressGestureRecognizer) {
        if edgeScrollTimer != nil {
            edgeScrollTimer.invalidate()
            edgeScrollTimer = nil
        }
        let cell:HomeSubTableViewCell = self.tableView.cellForRowAtIndexPath(sourceIndexPath) as! HomeSubTableViewCell
        UIView.animateWithDuration(0.25, animations: {
            self.snapshot.center = cell.center
            self.snapshot.transform = CGAffineTransformIdentity
            self.snapshot.alpha = 0
            cell.backgroundColor = self.snapshotColor
            cell.hidden = false
            }, completion: { (finished:Bool) in
                self.snapshot.removeFromSuperview()
                self.snapshot = nil
        })
        sourceIndexPath = nil
    }

6.处理边缘滚动

///开始边界翻滚
    private func startEdgeScroll() {
        edgeScrollTimer = CADisplayLink.init(target: self, selector: #selector(processEdgeScroll))
        edgeScrollTimer.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
    }

    @objc private func processEdgeScroll() {

        gestureChanged(longPressGesutre)

        let minOffsetY:CGFloat = self.tableView.contentOffset.y + 150
        let maxOffsetY:CGFloat = self.tableView.contentOffset.y + self.tableView.bounds.size.height - 150
        let touchPoint = snapshot.center

        //处理上下达到极限之后不再滚动tableView,其中处理了滚动到最边缘的时候,当前处于edgeScrollRange内,但是tableView还未显示完,需要显示完tableView才停止滚动
        if touchPoint.y < 150 {
            if self.tableView.contentOffset.y <= 0 {
                return
            } else {
                if self.tableView.contentOffset.y - 1 < 0 {
                    return;
                }
                self.tableView.setContentOffset(CGPointMake(self.tableView.contentOffset.x
                    , self.tableView.contentOffset.y), animated: false)
                snapshot.center = CGPointMake(snapshot.center.x, snapshot.center.y - 1)
            }

        }

        if touchPoint.y > self.tableView.contentSize.height - 150 {
            if self.tableView.contentOffset.y >= self.tableView.contentSize.height - self.tableView.bounds.size.height {
                return
            } else {
                if self.tableView.contentOffset.y + 1 > self.tableView.contentSize.height - self.tableView.bounds.size.height {
                    return;
                }
                self.tableView.setContentOffset(CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + 1), animated: false)
                snapshot.center = CGPointMake(snapshot.center.x, snapshot.center.y + 1)
            }
        }
        // 处理滚动
        let maxMoveDistance:CGFloat = 20

        if touchPoint.y < minOffsetY {
            // cell在向上滚动
            let moveDistance:CGFloat = (minOffsetY - touchPoint.y) / 150 * maxMoveDistance
            self.tableView.setContentOffset(CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y - moveDistance), animated: false)
            snapshot.center = CGPointMake(snapshot.center.x, snapshot.center.y - moveDistance)
        } else if touchPoint.y > maxOffsetY {
            // cell在向下滚动
            let moveDistance:CGFloat = (touchPoint.y - maxOffsetY) / 150 * maxMoveDistance
            self.tableView.setContentOffset(CGPointMake(self.tableView.contentOffset.x, self.tableView.contentOffset.y + moveDistance), animated: false)
            snapshot.center = CGPointMake(snapshot.center.x, snapshot.center.y + moveDistance)
        }
    }

总结
通过截图实现以假乱真,自由移动截图产生的”cell”;交换的时候,首先要交换数据源,再交换cell;处理边缘滚动的时候,需要小心各种边界条件。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
iOS-RATreeView是一个开源的第三方库,提供了多层级的UITableView展示功能。使用该库可以轻松实现多级列表的展开与收起。 首先,在项目中引入iOS-RATreeView库。可以使用CocoaPods引入,也可以手动下载并导入。 接下来,在需要使用多级列表的UIViewController中,创建一个RADataObject类型的数组,用来存储数据。RADataObject是iOS-RATreeView中的一个数据模型,用来表示一条记录。每个RADataObject可以包含多个子RADataObject,从而形成多级列表。 ``` // 创建RADataObject数组 NSMutableArray *data = [NSMutableArray array]; // 创建一级列表 RADataObject *level1_1 = [RADataObject dataObjectWithName:@"Level 1-1" children:nil]; RADataObject *level1_2 = [RADataObject dataObjectWithName:@"Level 1-2" children:nil]; RADataObject *level1_3 = [RADataObject dataObjectWithName:@"Level 1-3" children:nil]; // 创建二级列表 RADataObject *level2_1 = [RADataObject dataObjectWithName:@"Level 2-1" children:nil]; RADataObject *level2_2 = [RADataObject dataObjectWithName:@"Level 2-2" children:nil]; RADataObject *level2_3 = [RADataObject dataObjectWithName:@"Level 2-3" children:nil]; // 将二级列表添加到一级列表中 level1_1.children = @[level2_1, level2_2]; level1_2.children = @[level2_3]; // 将一级列表添加到RADataObject数组中 [data addObject:level1_1]; [data addObject:level1_2]; [data addObject:level1_3]; ``` 创建完数据源后,需要创建RATreeView对象,并设置代理和数据源。 ``` // 创建RATreeView对象 self.treeView = [[RATreeView alloc] initWithFrame:self.view.bounds]; // 设置代理和数据源 self.treeView.delegate = self; self.treeView.dataSource = self; ``` 接下来实现RATreeViewDataSource协议中的方法,用来返回列表的数据。具体实现可以参考下面的代码。 ``` - (UITableViewCell *)treeView:(RATreeView *)treeView cellForItem:(id)item { static NSString *identifier = @"Cell"; UITableViewCell *cell = [treeView dequeueReusableCellWithIdentifier:identifier]; if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; } // 获取RADataObject对象 RADataObject *dataObject = item; // 设置cell的文本 cell.textLabel.text = dataObject.name; return cell; } - (NSInteger)treeView:(RATreeView *)treeView numberOfChildrenOfItem:(id)item { if (item == nil) { // 如果item为nil,表示请求根节点的子节点数量 return self.data.count; } else { // 获取RADataObject对象 RADataObject *dataObject = item; // 返回子节点数量 return dataObject.children.count; } } - (id)treeView:(RATreeView *)treeView child:(NSInteger)index ofItem:(id)item { if (item == nil) { // 如果item为nil,表示请求根节点的子节点 return self.data[index]; } else { // 获取RADataObject对象 RADataObject *dataObject = item; // 返回子节点 return dataObject.children[index]; } } - (BOOL)treeView:(RATreeView *)treeView canEditRowForItem:(id)item { // 返回是否可以编辑 return YES; } - (void)treeView:(RATreeView *)treeView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowForItem:(id)item { if (editingStyle == UITableViewCellEditingStyleDelete) { // 删除节点 RADataObject *parentObject = [treeView parentForItem:item]; if (parentObject) { NSMutableArray *children = [NSMutableArray arrayWithArray:parentObject.children]; [children removeObject:item]; parentObject.children = children; } else { NSMutableArray *data = [NSMutableArray arrayWithArray:self.data]; [data removeObject:item]; self.data = data; } // 刷新列表 [treeView reloadData]; } } ``` 最后,在RATreeViewDelegate协议中实现展开与收起节点的方法。 ``` - (void)treeView:(RATreeView *)treeView willExpandRowForItem:(id)item { // 获取RADataObject对象 RADataObject *dataObject = item; // 设置节点的展开状态 dataObject.expanded = YES; } - (void)treeView:(RATreeView *)treeView willCollapseRowForItem:(id)item { // 获取RADataObject对象 RADataObject *dataObject = item; // 设置节点的展开状态 dataObject.expanded = NO; } ``` 至此,多级列表展开与收起的功能就实现了。在需要展示多级列表的地方,只需要将创建的RATreeView添加到视图中即可。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值