从 SwiftUI 到 ArkUI:迁移一款真实 App 时需要改变的 7 个思维习惯

迁移不是翻译语法

“心晴手记”最初已经有 iOS 版本,随后使用 ArkTS + ArkUI 实现 HarmonyOS 原生版。

两个版本的产品目标一致:

  • 每天记录一种心情;
  • 增加情境标签和一句话日记;
  • 完成或取消习惯打卡;
  • 通过月历和 7/30 天统计回顾;
  • 本地保存,不要求账号;
  • 支持应用锁和数据导出。

但平台实现并不是一一对应的语法替换:

iOS 思路HarmonyOS 实现
Swift / SwiftUIArkTS / ArkUI
SwiftDataPreferences JSON
Face ID / Touch ID 场景UserAuthenticationKit 的 PIN/人脸/指纹
Share Sheet / 文件导出CoreFileKit DocumentViewPicker
App 生命周期Stage 模型 UIAbility + 页面生命周期
Localizable 资源AppScope 与模块区域资源

真正需要迁移的是业务语义,而不是 API 名字。

一、先列功能对照表,不要先重画首页

迁移前,我先建立功能对照:

iOS 功能 → HarmonyOS 目标 → 状态 → 验收路径

例如:

每日记录去重
→ dayIdentifier 更新同日记录
→ 已完成
→ 当天保存两次,确认只有一条记录

这张表的价值是把“页面看起来差不多”转换成可验证的产品行为。

如果直接从 UI 开始,最容易漏掉的是:

  • 删除习惯时是否级联删除打卡;
  • 归档后历史是否保留;
  • 没有心情但有打卡的日期是否出现在历史;
  • 语言切换后默认习惯是否变化;
  • 撤回隐私同意后应用锁是否关闭。

这些行为往往比圆角和间距更决定迁移质量。

二、从“视图对象”转向明确的状态边界

SwiftUI 和 ArkUI 都是声明式 UI,但状态观察机制、对象更新方式和组件复用细节并不完全相同。

HarmonyOS 版本没有把所有状态藏在界面内部,而是先定义持久化根对象:

export interface PersistedState {
  exportVersion: number;
  settings: AppSettings;
  moodEntries: MoodEntry[];
  habits: Habit[];
  habitCompletions: HabitCompletion[];
}

页面编辑的仍然是 ArkUI 状态,但保存时会生成完整快照交给 Repository。

这种做法可以清楚回答:

  • 哪些值只是临时输入;
  • 哪些值必须持久化;
  • 哪些值可以从其他数据派生;
  • 导出文件应该包含什么;
  • 新版本如何给旧字段补默认值。

迁移时先建立状态边界,比寻找“Swift 某个属性包装器对应 ArkTS 哪个装饰器”更稳妥。

三、不要假设同为声明式 UI,Key 的行为就完全相同

项目早期出现过:习惯已经编辑,ArkUI 列表仍显示旧图标;切换 Tab 后又恢复正确。

数据没有错,问题在节点复用。最终通过不可变更新和包含修订信息的 Key 解决:

ForEach(this.activeHabits(), (habit: Habit) => {
  // 构建习惯行
}, (habit: Habit) =>
  `${habit.id}-${habit.updatedAtMillis}-${this.uiRevision}`
)

迁移经验是:

  • 不要把另一个框架的刷新直觉直接套过来;
  • 先确认数组和对象是否真正替换;
  • 再检查 ArkUI Key 是否表达了显示身份;
  • 不要用随机 Key 粗暴关闭全部复用。

相似的语法会让人降低警惕,但真正需要理解的是目标框架的状态与渲染模型。

四、平台能力要做“等价设计”,不是寻找同名 API

iOS 上的 Face ID 应用锁,迁移目标不应该写成“在 HarmonyOS 找 Face ID”。

真正的用户需求是:

使用设备已有的可信认证,保护本机日记入口。

因此 HarmonyOS 版本使用 UserAuthenticationKit,允许系统根据设备能力提供 PIN、人脸或指纹:

authType: [
  userAuth.UserAuthType.PIN,
  userAuth.UserAuthType.FACE,
  userAuth.UserAuthType.FINGERPRINT
]

同样,iOS 分享/导出能力在 HarmonyOS 上通过系统文件 Picker 实现。用户需求仍然是“把数据保存到自己选择的位置”,不需要强行复制另一平台的弹窗样式。

迁移时应当写两列:

原平台 API 做了什么
用户真正需要什么

然后用目标平台最自然的系统能力满足第二列。

五、日期模型不能沿用“时间戳万能”的习惯

日记中的“一天”是本地日历概念。HarmonyOS 版本保存:

dayIdentifier: '2026-08-21'

而不是用 UTC 日期字符串充当业务日期。

export function dayIdentifier(date: Date): string {
  const year = date.getFullYear().toString();
  const month = (date.getMonth() + 1)
    .toString().padStart(2, '0');
  const day = date.getDate()
    .toString().padStart(2, '0');
  return `${year}-${month}-${day}`;
}

这条规则必须在迁移初期确定,否则今日页、月历、历史和统计可能各自采用不同日期口径。

跨平台一致不等于两个平台保存完全相同的底层时间对象,而是用户在相同本地日期看到相同记录。

六、多语言要迁移稳定身份,不要迁移当前翻译

iOS 版本已有中英日文案,HarmonyOS 版本仍需要重新处理资源目录和应用偏好语言。

更重要的是,默认习惯不能把“早睡”这个中文字符串当作永久身份。

{
  resourceName: 'starter_sleep',
  storedName: 'sleep-early',
  legacyNames: ['早睡', 'Sleep early', '早寝']
}
  • storedName 是跨语言稳定身份;
  • resourceName 决定当前显示;
  • legacyNames 识别早期版本保存过的翻译文本。

迁移时,如果只复制用户当前看到的字符串,后续语言切换和旧数据兼容会非常困难。

七、迁移完成的标准是验收闭环,不是编译通过

项目最终建立了多层验证:

  1. ArkTS 编译通过;
  2. HAP/APP 构建成功;
  3. 模拟器完成首次隐私、引导和五个 Tab 抽查;
  4. 中英日切换及冷启动持久化通过;
  5. 今日、历史、习惯和统计按清单回归;
  6. 应用锁和文件保存器留给真机重点验证;
  7. AppGallery 发布资料单独检查。

特别要区分:

代码路径已实现 ≠ 系统能力已真机验收

设备认证、文件 URI、宽屏布局等能力,即使模拟器中可编译,也不能跳过真机验证。

一套可复用的迁移顺序

如果重新做一次,我仍会使用下面的顺序:

第 1 步:冻结源版本功能范围

不要一边迁移一边无限增加新功能。

第 2 步:提取业务不变量

日期唯一性、关联删除、归档语义、隐私边界先写清楚。

第 3 步:建立目标平台骨架

完成 Stage 模型、资源、模块配置和持久化入口。

第 4 步:按垂直切片实现

先走通“记录 → 保存 → 历史查看”,再扩展统计和设置。

第 5 步:替换为目标平台能力

系统认证、Picker、语言偏好使用 HarmonyOS 原生方案。

第 6 步:建立差异清单

明确哪些功能等价完成、哪些暂不提供、哪些需要真机验证。

第 7 步:以用户任务验收

不要只按文件和 API 测试,要按真实操作路径回归。

总结

从 SwiftUI 到 ArkUI,最重要的不是学会另一套组件语法,而是改变迁移思路:

  1. 迁移业务语义,不逐行翻译;
  2. 先定义状态和数据不变量;
  3. 尊重 ArkUI 的状态与复用模型;
  4. 为系统能力做等价设计;
  5. 日期和语言保存稳定身份;
  6. 用功能对照和 QA 清单闭环;
  7. 把“待真机验证”明确记录下来。

做到这些,跨平台重写才不会变成一个外观相似、行为却逐渐偏离的复制品。

本文案例来自“心晴手记(MoodMemoir)”iOS 与 HarmonyOS 双平台实现。

App Store 下载华为应用市场下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值