在云端 Mac 上验收 iOS 拖放数据交换

CI/CD 实践 ·约 7 分钟阅读

在云端 Mac 上验收 iOS 拖放数据交换

发版前,团队把一段任务编号从列表拖到编辑区。拖动预览正常,放下后也没有报错,保存的却是空字符串。这个问题不在动画层:拖放源声明的数据类型、接收端接受的类型,以及异步读取完成的时机,只要有一处没对上,界面就可能给出误导性的成功反馈。远程使用云端 Mac 时,可以先让 XCTest 检查数据契约,再打开模拟器验收实际手势;两者不能互相替代。

先把拖放协议写成可检查的约定

为每一种可拖动内容列出三项:提供者声明的 UTType、接收端允许的类型、成功读取后应得到的内容。以任务编号为例,如果它只是要粘贴进文本框的字符串,双方可以约定 UTType.utf8PlainText。不要让发送端注册纯文本、接收端却只查询图片类型,再用“放置动作已触发”判断成功。

还要写明错误路径:不支持的类型不应高亮为可接收;读取失败时不应插入空任务;取消拖动不应修改原数据。若业务需要保留任务编号以外的字段,应定义自己的数据格式并让两端共同解析,而不是把结构化内容偷偷塞进显示文本。

拖放完成是一次界面事件,不是一次数据提交。只有类型匹配、异步读取成功并通过内容校验,才能更新业务状态。

用 XCTest 验证类型和实际载荷

在应用的测试 target 新增一个测试文件。下面的提供者注册 UTF-8 文本,测试先检查类型,再等待读取回调,并验证读到的字节。它不依赖鼠标或图形桌面,适合先作为构建流水线中的快速检查。

import XCTest
import UniformTypeIdentifiers

final class DragPayloadTests: XCTestCase {
    func testPlainTextPayload() {
        let provider = NSItemProvider()
        provider.registerDataRepresentation(
            forTypeIdentifier: UTType.utf8PlainText.identifier,
            visibility: .all
        ) { completion in
            completion(Data("ticket-42".utf8), nil)
            return nil
        }

        XCTAssertTrue(provider.hasItemConformingToTypeIdentifier(
            UTType.plainText.identifier
        ))
        XCTAssertFalse(provider.hasItemConformingToTypeIdentifier(
            UTType.image.identifier
        ))

        let loaded = expectation(description: "Load drag payload")
        provider.loadDataRepresentation(
            forTypeIdentifier: UTType.utf8PlainText.identifier
        ) { data, error in
            XCTAssertNil(error)
            XCTAssertEqual(data.flatMap { String(data: $0, encoding: .utf8) },
                           "ticket-42")
            loaded.fulfill()
        }
        wait(for: [loaded], timeout: 5)
    }
}

这里用 plainText 检查兼容类型,用 utf8PlainText 读取注册的具体表示。测试通过后,再把同样的类型判断和解码逻辑用于产品代码,避免测试里接受一种类型、界面里又使用另一套白名单。生产代码还应在回调中处理 error、空数据和 UTF-8 解码失败,不要仅凭 hasItemConformingToTypeIdentifier 就修改界面。

固定模拟器目标再运行

先在 VMCommit 的控制台确认当前可选配置,交付后通过 SSH 进入 Mac,查看已安装的模拟器。将下面的工程、Scheme 和设备标识替换成自己的值;设备标识来自第一条命令,不要把示例字符串当作真实设备。

xcrun simctl list devices available
xcodebuild -list -project App.xcodeproj
xcodebuild test \
  -project App.xcodeproj \
  -scheme App \
  -destination 'platform=iOS Simulator,id=YOUR_DEVICE_ID' \
  -only-testing:AppTests/DragPayloadTests

-only-testing 的路径必须与工程中的测试 target 和类名一致。若命令提示 Scheme 不存在,先检查它是否共享;若目标设备不可用,重新读取本机列表,而不是沿用另一台机器的标识。测试失败时先保留原始输出,区分“模拟器没有启动”和“载荷断言失败”。

在图形会话里验收真实放置

契约测试绕过了手势处理,不能证明 UIDragInteraction 或 UIDropInteraction 已正确接线。在图形桌面打开模拟器,安装测试应用,按以下顺序做一次完整操作:

  1. 长按有效源项目并开始拖动,确认预览内容与所选项目一致。
  2. 移到允许的目标,检查目标是否给出可放置反馈;移到不接受该类型的区域,确认反馈消失。
  3. 放下后等待读取结束,再检查编辑区出现的任务编号是否准确、是否只插入一次。
  4. 再做一次拖动并在放下前取消,确认列表和编辑区都没有变化。

如果载荷较大,把“读取中”作为独立状态:禁用重复提交,完成后再更新内容;失败时恢复可操作状态并显示明确提示。不要为了让动画更顺畅,就在异步回调返回前先显示“已导入”。图形验收关注的是用户看到的状态转换,XCTest 关注的是底层数据,两份结果应分别记录。

排查失败时沿着数据流走

“目标不亮”先查类型:打印开发环境中源端注册的标识、接收端允许的标识,确认用的是类型兼容判断,而非只比较展示名称。“目标亮了却没有内容”再查加载回调是否执行,以及错误、数据长度和解码结果。不要把整个载荷写进共享日志;任务内容可能包含业务数据,记录类型与长度通常已足够定位边界。

“偶尔插入两次”则检查是否同时在放置回调和加载完成回调更新了模型,或同一个提供者被重复发起读取。给每次放置分配一次操作标识,只在验证成功后提交一次;取消或失败路径丢弃待提交结果。切换到图片等其他载荷时,为每种接受的类型分别写测试,不能因为纯文本通过就假定所有拖放路径成立。

交付前核对两张结果单

自动化结果单应包含:类型匹配、拒绝不支持的类型、字节内容一致、空值与读取错误有处理、超时会使测试失败。图形结果单应包含:源预览正确、目标反馈正确、成功后仅更新一次、取消后不修改内容、失败提示不会误报成功。前者适合每次提交运行,后者在交互实现或系统版本变化后重点复查。

最后从一台干净的模拟器重复操作,确认结果不依赖上一次运行留下的编辑区内容。把工程中实际接受的类型和这两张检查单一起保留,下一次改动拖放格式时,就能判断问题出在数据契约、异步读取,还是手势反馈。

常见问题

只测 NSItemProvider,能证明拖放界面正常吗?

不能。它能验证类型和载荷读取;拖动预览、放置目标高亮及取消反馈仍需在图形界面验收。

接收端何时可以读取拖放数据?

先检查提供者是否声明了可接受的 UTType,再异步加载表示;加载完成前不要把数据当作已导入。

按需要提交租期

用独享物理 Mac mini 验证下一步

VMCommit 提供 M4 独享物理 Mac mini,支持按天、周、月或季租用。选择机型与节点后,可在控制台查看实时可用性。

立即租用 Mac mini