You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS 13 Context Menu:点击预览时如何实现菜单平滑关闭?

实现上下文菜单预览点击后的反向关闭动画

我完全懂你说的那个生硬的全屏展开动画——确实很破坏体验。要实现和唤起菜单反向的关闭动画,核心是拦截默认的预览点击行为,自定义动画逻辑让预览视图缩回到原单元格的位置,而非全屏展开。下面是具体的实现步骤:


1. 拦截默认的预览提交动作

确保你的UIViewController遵循UIContextMenuInteractionDelegate(如果用UITableView内置上下文菜单,代理已自动关联,我们只需重写特定方法)。

实现willPerformPreviewActionForMenuWithConfiguration方法,这个方法会在用户点击预览视图时触发,我们可以在这里取消默认行为,替换成自定义动画:

func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
    // 取消系统默认的动画和跳转行为
    animator.cancel()
    
    // 获取预览视图控制器(如果用快照,可调整为操作快照视图)
    guard let previewVC = animator.previewViewController else {
        // 兜底:直接关闭菜单
        interaction.dismissMenu()
        return
    }
    
    // 从配置中拿到对应单元格索引,获取原单元格在窗口中的位置
    guard let indexPath = configuration.identifier as? IndexPath,
          let cell = tableView.cellForRow(at: indexPath),
          let targetFrame = tableView.convert(cell.frame, to: nil) else {
        interaction.dismissMenu()
        return
    }
    
    // 执行反向动画:把预览视图缩回到原单元格位置并淡出
    UIView.animate(withDuration: 0.3, curve: .easeInOut) {
        previewVC.view.frame = targetFrame
        previewVC.view.alpha = 0
    } completion: { _ in
        // 动画结束后关闭菜单并移除预览VC
        interaction.dismissMenu()
        previewVC.dismiss(animated: false)
    }
}

2. 匹配预览视图与单元格的初始大小

为了让反向动画更自然,需要确保预览视图的初始大小和原单元格一致。在创建上下文菜单配置时,设置预览控制器的preferredContentSize:

func tableView(_ tableView: UITableView, contextMenuConfigurationForRowAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? {
    return UIContextMenuConfiguration(identifier: indexPath, previewProvider: {
        let previewVC = YourCustomPreviewViewController()
        // 让预览VC的大小和对应单元格完全一致
        previewVC.preferredContentSize = tableView.cellForRow(at: indexPath)?.frame.size ?? CGSize(width: tableView.bounds.width, height: 60)
        return previewVC
    }) { _ in
        // 返回你的菜单动作列表
        return UIMenu(title: "", children: [
            UIAction(title: "编辑", handler: { _ in
                // 处理编辑逻辑
            }),
            UIAction(title: "删除", attributes: .destructive, handler: { _ in
                // 处理删除逻辑
            })
        ])
    }
}

3. 针对快照预览的调整(可选)

如果没有用单独的预览VC,而是直接返回单元格的快照视图,可以调整动画逻辑直接操作快照视图:

// 在previewProvider里返回快照
previewProvider: {
    guard let cell = tableView.cellForRow(at: indexPath) else { return nil }
    let snapshot = cell.snapshotView(afterScreenUpdates: true)!
    snapshot.frame = cell.frame
    return snapshot
}

然后在代理方法里把previewViewController换成animator.previewView来操作:

guard let previewView = animator.previewView else {
    interaction.dismissMenu()
    return
}

// 后续动画逻辑直接操作previewView即可

关键注意事项

  • 该方案仅适用于iOS 13及以上(上下文菜单最低支持版本),若需兼容更低版本,记得做版本判断。
  • 动画时长可根据需求调整,0.3秒是贴近系统默认动画的时长。
  • 若单元格为动态高度,确保获取的targetFrame是当前单元格在窗口中的真实位置,避免动画错位。

内容的提问来源于stack exchange,提问作者mmklug

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:07:22