Files
ios/Xuxucheck

XuXuCheck

干化学尿液试纸分析 App(iOS / SwiftUI)。用户用相机拍摄试纸,App 通过 C++/OpenCV 定位试纸、 跨 16 帧采样 16 个试剂垫,再把 HSV/Lab 测量值上传服务端。除相机与扫描流程外,其余界面 (产品介绍、记录、成员、我的)都是服务端渲染的网页,由 WKWebView 承载。

Bundle ID com.niaodaifu.xuxu.XuXuCheck
Deployment Target iOS 18.4
开发语言 Swift 5.0 + Objective-C++(OpenCV 桥接)
界面语言 英文(App 内置)/ 中文(业务域)
版本管理 https://git.china-planet.com/admin/ios.git 的 Xuxucheck/ 子目录

本目录是 git 仓库里的副本,由桌面版工程复制而来,为控制仓库体积做了两处删减: 删掉了 Frameworks/opencv2.xcframework/ 里的两个 visionOS 切片(xros-arm64、 xros-arm64-simulator,合计约 106 MB)以及所有 .DS_Store / xcuserdata/。 切片是连同 xcframework 内的 Info.plist 一起删的,只保留 ios-arm64 与 ios-arm64_x86_64-simulator —— 本工程只构建 iOS,不受影响(已编译验证)。 桌面上的原始工程仍是完整四切片版本,两者内容一致,只有这一处差异。

一、环境要求

项 要求
macOS 任意较新版本
Xcode 16.3 或更高(deployment target 为 iOS 18.4,需要 iOS 18.4 SDK)已在本机 Xcode 27.0(Build 27A266a)验证
CocoaPods 不需要安装 —— 依赖已随项目提供,见第三节
iOS 真机 相机 / 扫描流程必须真机;模拟器只会显示空白相机分支
Apple ID 需要能用于签名的账号,见第四节

二、编译方式

必须打开 XuXuCheck.xcworkspace,不是 XuXuCheck.xcodeproj。 后者不带 Pods 依赖,打开会报一大堆找不到符号的错误。

open XuXuCheck.xcworkspace

图形界面:选中 XuXuCheck scheme 和目标设备 → ⌘R。

命令行(先用 xcrun devicectl list devices 查设备 id):

# 真机
xcodebuild -workspace XuXuCheck.xcworkspace -scheme XuXuCheck \
           -configuration Debug -destination 'id=<设备 id>' \
           -allowProvisioningUpdates build

# 只验证能否编译,不依赖具体设备
xcodebuild -workspace XuXuCheck.xcworkspace -scheme XuXuCheck \
           -destination 'generic/platform=iOS' build

第一次编译会比较久(数分钟):7 个 pod 全部从源码编译,加上大量 OpenCV 头文件。 这是正常的,不是卡住。

测试 target 目前是空壳(XuXuCheckTests 用 Swift Testing,XuXuCheckUITests 用 XCTest), 跑单个用例:

xcodebuild -workspace XuXuCheck.xcworkspace -scheme XuXuCheck \
           -only-testing:XuXuCheckTests/XuXuCheckTests/example test

三、依赖已随仓库提供,无需恢复

OpenCV

  • 位置:Frameworks/opencv2.xcframework(203 MB)

  • 切片:ios-arm64、ios-arm64_x86_64-simulator(桌面版工程另有 xros-arm64、 xros-arm64-simulator 两个 visionOS 切片,本仓库为控制体积已删除,见开头的说明); 两个都是 静态库,因此没有 Embed 阶段,运行时不依赖任何 opencv 文件(代码已编进 App)

  • 工程中只有一处引用,在 XuXuCheck.xcodeproj/project.pbxproj:

    path = Frameworks/opencv2.xcframework; sourceTree = "<group>"
    
  • 工程里没有 FRAMEWORK_SEARCH_PATHS / HEADER_SEARCH_PATHS:构建时 Xcode 的 ProcessXCFramework 会把对应切片拷进 DerivedData,编译器与链接器通过自动的 -F$(BUILT_PRODUCTS_DIR) 找到它

  • #include <opencv2/opencv.hpp> 走的是 framework 式查找 → opencv2.framework/Headers/opencv.hpp, 所以不需要额外的头文件搜索路径

  • ⚠️ 不要把它改成指向 ~/Downloads/opencv2.framework。那是 OpenCV 4.11.0 官方 fat 包 (含 i386/armv7,没有独立的模拟器切片),与本项目使用的自建 xcframework 不是同一个东西

  • 框架包内是 macOS 经典的版本化布局(opencv2、Headers、Resources 都是指向 Versions/Current/* 的符号链接,Versions/Current 再指向 A),终点在包内,不指向任何外部目录

CocoaPods

Pods/ 目录已完整随项目提供(pod 源码、Pods.xcodeproj、Target Support Files 齐全), 所以:

  • 不需要执行 pod install,新机器连 CocoaPods 都不用装。工程里三个 [CP] 脚本阶段 都是纯 shell(diff 比对 lock + 运行生成的 .sh),构建时不调用 pod 命令

  • 依赖清单(共 3 个):Alamofire 4.x(不是 5.x)、AlamofireObjectMapper 5.2, 以及它带进来的 ObjectMapper 3.5

  • 因为 Alamofire 是 4.x,代码里的 Alamofire.request(...) 和 DataResponse<T> 是正确写法, 不要"顺手"升级。Alamofire 也升不动:AlamofireObjectMapper 5.2.1 要求 Alamofire ~> 4.7, 要上 5.x 就必须先把它换成 Codable,牵连 6 个 Mappable 类、32 处 <- 和 6 处 .responseObject

  • 原先还依赖 ProgressHUD、SDWebImage + SDWebImageSwiftUI、WrappingHStack,但 App 代码 从未 import 过任何一个(全仓零引用)。use_frameworks! 会把每个 pod 都做成动态 framework 并嵌进 .app,所以它们每次构建都要编译、启动时都要加载 —— 已移除,嵌入框架由 7 个/6.4 MB 降到 3 个/2.8 MB

  • ⚠️ 改 Podfile 后跑 pod install 必须用 1.16.2:

    gem install cocoapods -v 1.16.2
    pod install
    

    用其它版本会重写 Pods.xcodeproj,可能引入不必要的差异

  • ⚠️ pod install 会重新生成 Pods.xcodeproj,这里曾经埋着一个雷:podspec 自己声明的最低 部署目标是 8.0,而 Xcode 27 只接受 15.0~27.0,低于 15.0 会在编译任何代码之前就报错 (The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 8.0, but the range of supported deployment target versions is 15.0 to 27.0.x)。原来的工程是手工把这 22 处改成 15.6 才能编译的,而手工改动每次 pod install 都会被覆盖 —— 也就是说以前任何人跑一次 pod install,这个项目就编译不过了,只是没人跑过。

    现在改由 Podfile 末尾的 post_install 钩子统一设置,重新生成也不会丢。不要把这段挪回 手工改工程文件,那样等于把这个坑重新埋上

四、签名

当前配置:自动签名(CODE_SIGN_STYLE = Automatic),Team BK4A926286。

在另一台 Mac 或另一个账号上编译时,二选一:

  1. 沿用同一团队 —— 把新机器的 Apple ID 加入团队成员,并在该 Mac 的 Xcode 里登录, 自动签名会签发新证书。
  2. 换成自己的账号 —— 修改 DEVELOPMENT_TEAM,但必须同时修改 Bundle ID (com.niaodaifu.xuxu.XuXuCheck 属于原团队),例如改成 com.yourname.XuXuCheck。 免费个人账号也可以跑起来。

常见报错与对策:

报错 原因与处理
No profiles for 'com.niaodaifu.xuxu.XuXuCheck' were found 并伴随 PLA Update available ... Account Holder ... must agree 团队协议未接受,会锁住该团队的证书/描述文件资源。只有 Account Holder 能在 developer.apple.com 后台处理
Your account already has an Apple Development signing certificate ... but its private key is not installed 指的是历史(常为已过期)证书的私钥不在本机钥匙串。通常新证书已可用;确认无用后可在钥匙串里删掉那张旧证书
编译到 Embed Pods Frameworks 时报 errSecInternalComponent 钥匙串拒绝了 codesign 使用私钥。用 Xcode 图形界面编译一次并在弹窗点「始终允许」,或检查该证书私钥的「访问控制」

五、复制到另一台 Mac

整包复制即可编译,不需要 pod install。 因为 Pods/、opencv、工程文件里 没有任何 /Users/... 绝对路径(已逐文件核查),整个目录也不依赖项目之外的任何东西。

注意事项:

  1. 不要放在 Desktop / Documents / Downloads —— macOS 隐私保护会拦截 Xcode 和命令行对其的访问 (表现为 Operation not permitted)。建议放 ~/Developer/ 之类不设防的位置
  2. 拷贝前建议删掉本机残留:各处的 xcuserdata/(工程里共 4 处,含个人窗口/断点状态)与 .DS_Store
  3. 在新机器上打开 XuXuCheck.xcworkspace,签名按第四节调整
  4. 首次编译同样需要数分钟
  5. 目录体积约 206 MB(其中 opencv 203 MB、Pods 0.9 MB),传输较慢

六、目录结构

XuXuCheck/
├── XuXuCheck.xcworkspace     ← 打开这个
├── XuXuCheck.xcodeproj
├── Frameworks/
│   └── opencv2.xcframework   ← 203 MB(仅 iOS 两个切片),自带的 OpenCV
├── Pods/                     ← 已随项目提供,无需 pod install
├── Podfile / Podfile.lock    ← CocoaPods 1.16.2
├── XuXuCheck/                ← App 源码(SwiftUI + opencv/ 下的 C++ 管线)
├── XuXuCheckTests/           ← 空壳
├── XuXuCheckUITests/         ← 空壳
├── docs/                     ← 专题说明(含实测数据)
├── CLAUDE.md                 ← 架构说明与非显而易见的坑
└── README.md

七、已知坑

  • Debug 产物是分裂的:Xcode 16+ 的 debug-dylib 机制下,真实代码在 XuXuCheck.debug.dylib(约 10 MB),主可执行文件只有约 90 KB、只是个壳。 判断"OpenCV 有没有链进去"要看前者,别看错文件
  • 模拟器没有相机,扫描流程必须真机
  • 这一份在 git 仓库里(admin/ios 的 Xuxucheck/ 子目录),有历史可回溯,但仓库 体积大(203 MB 大部分是 OpenCV),克隆较慢;桌面上的原始工程不在仓库里,改动前自行备份
  • 更多架构说明见 CLAUDE.md;WebView 顶部/底部边距的处理与实测数据见 docs/webview-safe-area.md