# 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 依赖,打开会报一大堆找不到符号的错误。 ```bash open XuXuCheck.xcworkspace ``` 图形界面:选中 XuXuCheck scheme 和目标设备 → ⌘R。 命令行(先用 `xcrun devicectl list devices` 查设备 id): ```bash # 真机 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), 跑单个用例: ```bash 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 = "" ``` - 工程里**没有** `FRAMEWORK_SEARCH_PATHS` / `HEADER_SEARCH_PATHS`:构建时 Xcode 的 `ProcessXCFramework` 会把对应切片拷进 DerivedData,编译器与链接器通过自动的 `-F$(BUILT_PRODUCTS_DIR)` 找到它 - `#include ` 走的是 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` 是**正确**写法, 不要"顺手"升级。**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**: ```bash 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 ← 309 MB,自带的 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 仓库,没有历史可回溯,改动前请自行备份 - 更多架构说明见 `CLAUDE.md`;WebView 顶部/底部边距的处理与实测数据见 `docs/webview-safe-area.md`