uikit-app-modernization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

UIKit App Modernization Skill

UIKit应用现代化技能

Purpose

目标

Modernize UIKit apps to behave correctly on modern iOS by:
  • Eliminating references to legacy shared-state APIs
  • Migrating from application lifecycle to scene lifecycle
  • Supporting dynamic scene sizing and multi-window environments
通过以下方式使UIKit应用适配现代iOS系统,确保运行正常:
  • 移除对旧版共享状态API的引用
  • 从应用生命周期迁移至场景生命周期
  • 支持动态场景尺寸与多窗口环境

Scope

适用范围

This skill performs specific, targeted modernizations in both Swift and Objective-C codebases:
  • Replace legacy shared-state APIs with context-appropriate modern APIs
  • Migrate to scene-based lifecycle
  • Update apps to support a resizable user interface by removing usage of:
    • main screen (
      UIScreen.mainScreen
      ,
      UIScreen.main
      )
    • interface orientation (
      interfaceOrientation
      )
    • assumptions of symmetric safe areas (
      safeAreaLayoutGuide
      ,
      safeAreaInsets
      )
本技能针对Swift和Objective-C代码库执行特定、有针对性的现代化改造
  • 用适合上下文的现代API替换旧版共享状态API
  • 迁移至基于场景的生命周期
  • 更新应用以支持可调整大小的用户界面,移除以下内容的使用:
    • 主屏幕(
      UIScreen.mainScreen
      UIScreen.main
    • 界面方向(
      interfaceOrientation
    • 对称安全区域的假设(
      safeAreaLayoutGuide
      safeAreaInsets

Core Principles

核心原则

  1. Closest to consumer — Prefer information nearest the point of use (e.g., view's trait collection over window's).
  2. Always apply a replacement when the target API is present. A TODO alone is a failure. An empty diff for a file containing the target API is also a failure. If the file contains the target deprecated API and a concrete replacement is feasible under any pattern in the active task's reference file, apply it. Only skip when the target API appears exclusively inside dead code (
    #if 0
    /
    #endif
    ). When uncertain between two valid replacements, pick the one that best fits the user's request rather than producing an empty diff. Never silently skip a file: if you are unwilling to apply a change, talk to the user about possible options — never produce no output for it. Do not get stuck weighing edge cases on simple files; when the substitution is obvious, apply it and move on.
  3. TODOs must be actionable. Every TODO you do leave must state (a) why the change is needed, (b) what the correct replacement would look like, and (c) any lifecycle or threading concerns. Place the TODO on its own line above the unchanged code — never inline. A vague TODO ("fix this later") is worse than no TODO; it consumes review attention without telling the next reader anything they couldn't infer.
  4. Don't add a redundant TODO when an existing annotation already covers the migration. If the call site already has a
    #pragma clang diagnostic ignored
    paired with a bug-report reference, an existing
    // TODO
    , or a deprecation comment that points at the migration, do not add another one. Only add a new TODO when it provides additional migration guidance not present in the existing annotation.
  5. Ask the user before making a risky code change; fall back to a TODO only when interactive guidance is unavailable. When a replacement risks breaking callers or changing observable behavior (e.g., changing a method signature in a header that other modules import; substituting
    width > height
    for orientation when left-vs-right matters), the first move is to ask the user how to proceed. Only when the skill is running non-interactively, or when the user explicitly declines to provide guidance, drop a TODO and move on. This does not apply to standard, drop-in safe replacements specified by the active task's reference file — those must be applied per Core Principle 2.
  6. Honor explicit user instructions; otherwise apply the defaults from the task reference file. When the user asks for a specific approach — a particular attribute, parameter name, parameter position, trait source, or fallback behavior — use that exactly. Don't silently substitute what you consider the modern equivalent. When the user is general ("modernize this app", "fix
    UIScreen.main
    usages"), apply the defaults from the active task's reference file.
  7. Never replace dynamic values with literals — Always keep replacements dynamic.
  8. Preserve control flow — Prefer drop-in replacements that maintain the original code structure. Only add guard/early-return patterns when a direct substitution does not work. When editing code around control flow (
    if
    /
    else
    ,
    switch
    /
    case
    /
    default
    ,
    do
    /
    catch
    ), verify that the branching structure is preserved after your edit. Never remove a branch (
    } else {
    ,
    default:
    ,
    catch
    ) unless the user explicitly asks for it. A diff that collapses an
    if
    /
    else
    into sequential execution is a critical bug — both branches will execute unconditionally.
  9. Stay in scope — no opportunistic cleanup. Only modify lines containing the target deprecated API for the active task. Do NOT also fix other deprecation that happens to live nearby. Do NOT trim trailing whitespace, reformat blank lines, or "clean up" surrounding formatting. Even if you see an obvious modernization opportunity on an adjacent line, leave it alone — each task is independent and out-of-scope edits convert a successful in-scope change into a warning.
  10. Extract repeated expressions — When the same replacement value is used multiple times in a scope, extract it into a named local variable.
  11. Never walk global scene/window state — Never use
    UIApplication.shared
    ,
    UIDevice.current
    ,
    UIScreen.main
    , or other shared objects as a replacement. If no local object is available, modify the method to accept a new parameter and deprecate the old method.
  12. Complete patterns — atomic, never partial — Every multi-part pattern requires ALL parts applied together as a single atomic unit. Deprecate-and-forward requires deprecation + new overload + forwarding — never just an inline replacement when the pattern calls for method extraction. When the active task requires both an API replacement AND a reactive update (e.g., trait change observation), these form a single atomic change — never apply one without the other. Downgrading the deprecate-and-forward pattern to an inline reference to a shared object is an error — it silently breaks the migration story by removing the deprecated bridge that callers rely on to find the new API. If you cannot complete all four parts (new overload with the appropriate parameter name/type/position, old method delegates with shared state (e.g.
    UITraitCollection.current
    ,
    UIScreen.main
    ), old method marked deprecated with the appropriate attribute, deprecated wrapper kept in place), do not apply a partial change — either complete the full pattern or skip with an explicit reason.
  13. Never remove the old method when adding a new overload. When applying deprecate-and-forward, the old method must remain in the file as the deprecated wrapper that forwards to the new overload via
    .current
    . Deleting the old method (even if it appears unused in the diff) removes the deprecation signal from the codebase and silently drops the migration bridge. This applies to ObjC methods, Swift methods, Swift initializers, computed properties, and protocol-extension methods. If you find yourself removing a method as part of adding a new overload, STOP — you should be keeping it with a deprecation attribute, not deleting it.
  14. Preserve unrelated guards and fallbacks. When removing a
    UIScreen.mainScreen
    reference, change ONLY that reference. Do not simultaneously delete
    respondsToSelector:
    checks, nil-screen guards,
    if (screen != nil)
    defenses, version checks (
    #available
    ,
    @available
    ), or any other defensive logic that wraps the call site — unless the user explicitly asks for it. Each guard exists for an independent reason (selector availability across SDK versions, nil-window safety, feature flags); the modernization touches only the screen-derived value, not the surrounding control flow.
  15. Apply the deprecation at the lowest method that touches the deprecated API. When several callers funnel into one helper that actually reads the deprecated shared state, put the deprecate-and-forward on the helper, not on every public caller. Forcing every public caller to grow a
    traitCollection:
    parameter when the helper is the only site that needs it produces over-broad churn and a wider blast radius than the migration requires. Conversely, when the deprecated state is read directly inside each public caller (no helper), the deprecation belongs on the public callers — there is nothing lower to deprecate. Rule of thumb: identify which method contains the line you would otherwise need to change; deprecate that method. The deprecation chain should grow only as wide as the actual surface that touches the deprecated API.
  16. Off-target replacement guard. Before editing any line, verify two things: (a) the line contains the target deprecated API for the active task, and (b) you're editing the deprecation the user asked about — not a nearby line that "looks similar."

  1. 贴近使用者 —— 优先使用离使用点最近的信息(例如,视图的trait collection而非窗口的)。
  2. 目标API存在时必须应用替代方案。仅留下TODO是失败的。包含目标API的文件若生成空差异也是失败的。如果文件包含目标废弃API,且根据当前任务参考文件中的任何模式都能实现具体替代方案,则应用该方案。仅当目标API完全出现在死代码(
    #if 0
    /
    #endif
    )中时才跳过。若在两个有效替代方案间不确定,选择最符合用户需求的方案,而非生成空差异。切勿静默跳过文件:若不愿应用更改,请与用户讨论可能的选项——切勿无输出。不要在简单文件上纠结边缘情况;当替换方案明显时,直接应用并继续。
  3. TODO必须可执行。留下的每个TODO必须说明(a) 为何需要更改,(b) 正确的替代方案是什么,以及(c) 任何生命周期或线程相关注意事项。将TODO放在未修改代码上方单独一行——切勿内联。模糊的TODO(“稍后修复”)比没有TODO更糟;它会占用评审注意力,却未给后续读者提供任何他们无法自行推断的信息。
  4. 现有注释已涵盖迁移内容时,请勿添加冗余TODO。若调用点已有
    #pragma clang diagnostic ignored
    搭配错误报告引用、现有
    // TODO
    或指向迁移的废弃注释,则无需添加新的TODO。仅当现有注释未提供额外迁移指导时,才添加新的TODO。
  5. 进行有风险的代码更改前先询问用户;仅当无法获得交互式指导时才使用TODO替代。当替代方案可能破坏调用者或改变可观察行为(例如,更改其他模块导入的头文件中的方法签名;当左右方向重要时用
    width > height
    替代方向判断),首先应询问用户如何处理。仅当技能以非交互式运行,或用户明确拒绝提供指导时,才添加TODO并继续。这不适用于当前任务参考文件中指定的标准、安全的直接替换方案——这些必须根据核心原则2应用。
  6. 遵循用户明确指示;否则应用任务参考文件中的默认规则。当用户要求特定方法——特定属性、参数名称、参数位置、trait来源或回退行为时,严格按照要求执行。切勿静默替换为你认为的现代等效方案。当用户要求较为笼统(“现代化此应用”、“修复
    UIScreen.main
    用法”)时,应用当前任务参考文件中的默认规则。
  7. 切勿用字面量替换动态值 —— 始终保持替代方案的动态性。
  8. 保留控制流 —— 优先选择能保持原始代码结构的直接替换方案。仅当直接替换不可行时才添加guard/提前返回模式。编辑控制流(
    if
    /
    else
    switch
    /
    case
    /
    default
    do
    /
    catch
    )周围的代码时,验证编辑后的分支结构是否保留。除非用户明确要求,否则切勿移除分支(
    } else {
    default:
    catch
    )。将
    if
    /
    else
    合并为顺序执行的差异是严重错误——两个分支将无条件执行。
  9. 坚守范围——不进行机会性清理。仅修改包含当前任务目标废弃API的行。请勿同时修复附近的其他废弃内容。请勿修剪尾随空格、重新格式化空行或“清理”周围格式。即使在相邻行看到明显的现代化机会,也请忽略——每个任务都是独立的,超出范围的编辑会将成功的范围内变更转化为警告。
  10. 提取重复表达式 —— 当同一替换值在某个作用域中多次使用时,将其提取为命名局部变量。
  11. 切勿遍历全局场景/窗口状态 —— 切勿使用
    UIApplication.shared
    UIDevice.current
    UIScreen.main
    或其他共享对象作为替代方案。若没有可用的局部对象,请修改方法以接受新参数并废弃旧方法。
  12. 完整模式——原子性,绝不部分应用 —— 每个多部分模式要求所有部分作为单个原子单元一起应用。废弃并转发模式要求废弃+新重载+转发——当模式要求提取方法时,切勿仅进行内联替换。当当前任务需要API替换和响应式更新(例如,trait变化观察)时,这些构成单个原子变更——切勿仅应用其中一个。 将废弃并转发模式降级为对共享对象的内联引用是错误的——它会通过移除调用者依赖的废弃桥接来静默破坏迁移流程。若无法完成所有四个部分(具有适当参数名称/类型/位置的新重载、旧方法通过共享状态(如
    UITraitCollection.current
    UIScreen.main
    )委托、旧方法标记适当的废弃属性、保留废弃包装器),请勿应用部分变更——要么完成完整模式,要么明确说明原因后跳过。
  13. 添加新重载时切勿移除旧方法。应用废弃并转发模式时,旧方法必须保留在文件中,作为通过
    .current
    转发到新重载的废弃包装器。删除旧方法(即使在差异中显示未使用)会从代码库中移除废弃信号,并静默删除迁移桥接。这适用于ObjC方法、Swift方法、Swift初始化器、计算属性和协议扩展方法。若在添加新重载时发现自己正在移除方法,请停止——你应该保留它并添加废弃属性,而非删除。
  14. 保留无关的守卫和回退逻辑。移除
    UIScreen.mainScreen
    引用时,仅修改该引用。请勿同时删除
    respondsToSelector:
    检查、空屏幕守卫、
    if (screen != nil)
    防御、版本检查(
    #available
    @available
    )或任何其他包裹调用点的防御逻辑——除非用户明确要求。每个守卫都有独立的存在理由(跨SDK版本的选择器可用性、空窗口安全性、功能标志);现代化仅涉及屏幕派生值,不涉及周围的控制流。
  15. 在接触废弃API的最低层级方法上应用废弃标记。当多个调用者集中到一个实际读取废弃共享状态的辅助方法时,将废弃并转发模式应用于该辅助方法,而非每个公共调用者。当辅助方法是唯一需要该参数的位置时,强制每个公共调用者添加
    traitCollection:
    参数会导致过度变更,超出迁移所需的影响范围。相反,当废弃状态直接在每个公共调用者内部读取(无辅助方法)时,废弃标记应放在公共调用者上——没有更低层级的方法可废弃。经验法则:确定你需要更改的代码所在的方法;废弃该方法。废弃链的范围应仅扩展到实际接触废弃API的表面。
  16. 非目标替换守卫。编辑任何行之前,验证两件事:(a) 该行包含当前任务的目标废弃API,且(b) 你正在编辑用户要求处理的废弃内容——而非“看起来相似”的附近行。

Workflow

工作流程

Phase 0: Fast Path for Simple Cases

阶段0:简单场景的快速路径

Before reaching for the decision tree, check if the occurrence matches the simple case. A large fraction of
UIScreen.main
/
UIScreen.mainScreen
occurrences are simple substitutions inside a UIView/UIViewController instance method where the value is consumed fresh. These cases need no analysis — just substitute and move on:
OriginalReplacement
UIScreen.main.scale
(Swift) inside a UIView/UIViewController instance method, used inline (not stored)
self.traitCollection.displayScale
[UIScreen mainScreen].scale
(ObjC) inside a UIView/UIViewController instance method, used inline (not stored)
self.traitCollection.displayScale
UIScreen.main.scale
inside
layoutSubviews
,
drawRect:
,
updateConstraints
, or
viewIsAppearing:
self.traitCollection.displayScale
(no registration needed — UIKit auto-calls these on trait change)
Do not over-think simple substitutions. If the enclosing class is
UIView
/
UIViewController
and the value isn't being assigned to an ivar, layer property, constraint, or stored image, just substitute. Empty diffs on simple files are the most common mistake — apply the substitution and move on. Reach for the decision tree only when the simple case doesn't fit (non-view class, cached value, class/static method, special user instructions).
在使用决策树之前,先检查是否符合简单场景
UIScreen.main
/
UIScreen.mainScreen
的大部分使用场景是UIView/UIViewController实例方法内的简单替换,且值是即时使用的。这些场景无需分析——直接替换即可:
原始代码替代代码
UIView/UIViewController实例方法内的
UIScreen.main.scale
(Swift),内联使用(未存储)
self.traitCollection.displayScale
UIView/UIViewController实例方法内的
[UIScreen mainScreen].scale
(ObjC),内联使用(未存储)
self.traitCollection.displayScale
layoutSubviews
drawRect:
updateConstraints
viewIsAppearing:
内的
UIScreen.main.scale
self.traitCollection.displayScale
(无需注册——UIKit会在trait变化时自动调用这些方法)
不要过度思考简单替换。若所属类是
UIView
/
UIViewController
且值未分配给实例变量、图层属性、约束或存储的图像,直接替换即可。简单文件生成空差异是最常见的错误——应用替换并继续。仅当不符合简单场景(非视图类、缓存值、类/静态方法、特殊用户指令)时才使用决策树。

Phase 1: Detection

阶段1:检测

Identify patterns to modernize using each relevant task file's detection patterns. Run detection for every task in the Task Registry that applies to this codebase, not just one — see Task Registry below.
使用每个相关任务文件的检测模式识别需要现代化的模式。对适用于此代码库的任务注册表中的每个任务运行检测,而非仅一个——见下文的任务注册表

Phase 2: Analysis

阶段2:分析

For each occurrence, read surrounding context to understand:
  • Class hierarchy (UIView/UIViewController subclass vs plain NSObject vs non-view class)
  • Method type (instance, static, free function, cached
    dispatch_once
    helper)
  • Lifecycle phase (init, viewDidLoad, viewWillAppear, layoutSubviews)
  • Code intent (layout, rendering, display scale, full screen dimensions)
The active task's reference file may add task-specific bullets to this list.
Use subagents to identify code that needs to be updated to keep your context window small.
针对每个匹配项,阅读上下文以了解:
  • 类层次结构(UIView/UIViewController子类 vs 普通NSObject vs 非视图类)
  • 方法类型(实例方法、静态方法、自由函数、缓存的
    dispatch_once
    辅助方法)
  • 生命周期阶段(init、viewDidLoad、viewWillAppear、layoutSubviews)
  • 代码意图(布局、渲染、显示比例、全屏尺寸)
当前任务的参考文件可能会为此列表添加任务特定的要点。
使用子代理识别需要更新的代码,以缩小上下文窗口。

Phase 3: Decision & Validation

阶段3:决策与验证

ConditionAction
Safe 1:1 replacement existsApply it. No added commentary (no
// TODO: FIXME
, no
// TODO
, no
// FIXME
— just the replacement). Use the replacement specified by the active task's reference file.
Multiple valid approaches or code relocation >10 linesAsk the user.
No safe replacement possible (extremely rare)Add todo with an explicit task outlined for the user. Never produce a silent empty diff. Re-check every pattern with a subagent before concluding nothing applies.
Use subagents to validate against the active task's Post-file Checklist before any code change.
条件操作
存在安全的1:1替换方案应用该方案。无需添加注释(无
// TODO: FIXME
、无
// TODO
、无
// FIXME
——仅替换)。使用当前任务参考文件中指定的替代方案。
存在多种有效方法或代码迁移超过10行询问用户
无安全替换方案(极为罕见)添加TODO,为用户列出明确的任务。切勿生成静默空差异。在得出无适用方案的结论前,与子代理重新检查所有模式。
在进行任何代码更改前,使用子代理根据当前任务的文件后检查表进行验证。

Phase 3b: File Processing Completeness

阶段3b:文件处理完整性

Process EVERY file that contains the target deprecated API. Do not stop early, skip files, or silently drop files from the work queue. A file that was identified in Phase 1 but produces no diff and no skip explanation is a processing failure.
Explicit file tracking: At the start of processing, write out the complete list of files to be modified using available task / todo tools or a markdown file. As you process each file, mark it done. Before finishing, compare this list against your output — any file without a diff or an explicit skip reason is a failure that must be addressed before completing.
Context size: If you are concerned about context size, use subagents to process individual files or tasks.
Silent-drop prevention: Before finishing, use subagents to compare the list of files you were given against the list of files you produced output for. If any file is missing from your output, go back and process it. Common causes of silent drops:
  • File size: Large files (1000+ lines) are not exempt. Process them with the same approach.
  • Complexity: Files with preprocessor macros, complex class hierarchies, or unusual code patterns still need changes.
  • Project grouping: Do not skip all files from a specific project or directory. If you notice you've dropped multiple files from the same project, that indicates a systematic issue — investigate and fix.
  • Ambiguity: If you're unsure how to fix a file, ask the user — do not silently produce an empty diff.
Large or complex files: Files with heavy preprocessor usage (
#if
/
#ifdef
nesting), 1000+ lines, or less common patterns (C++ interop,
dispatch_once
caching, deeply nested macros) are not exempt from processing. If the target API appears in such a file, apply the same decision tree. If the file is too large to edit in one pass, process the deprecated API usages one at a time. Use subagents if helpful. If you genuinely cannot determine a safe replacement due to macro expansion or preprocessor complexity, ask the user — never silently skip it.
Batch processing discipline: When processing a list of files, do NOT attempt to analyze all files first and then produce all diffs at once. Instead, process files one at a time or in small batches (3–5 files): read context, decide, produce the diff, then move to the next batch. This prevents the tail end of the file list from being silently dropped due to output limits or context exhaustion. If you notice you have produced output for fewer files than you were given, STOP and process the remaining files before finishing.
If you find empty diffs for files that should have straightforward replacements, go back and process them — straightforward files are fast to handle and should never be dropped.
处理所有包含目标废弃API的文件。请勿提前停止、跳过文件或从工作队列中静默丢弃文件。若阶段1识别的文件未生成差异且未说明跳过原因,则处理失败。
明确的文件跟踪:处理开始时,使用可用的任务/TODO工具或markdown文件写出要修改的完整文件列表。处理每个文件后,标记为已完成。完成前,将此列表与输出进行比较——任何无差异或无明确跳过原因的文件都是必须解决的失败项。
上下文大小:若担心上下文过大,使用子代理处理单个文件或任务。
静默丢弃预防:完成前,使用子代理比较给定的文件列表与生成输出的文件列表。若任何文件未出现在输出中,返回并处理该文件。静默丢弃的常见原因:
  • 文件大小:大文件(1000+行)不例外。使用相同方法处理。
  • 复杂度:包含预处理器宏、复杂类层次结构或不常见代码模式的文件仍需更改。
  • 项目分组:请勿跳过特定项目或目录中的所有文件。若发现丢弃了同一项目中的多个文件,表明存在系统性问题——调查并修复。
  • 模糊性:若不确定如何修复文件,询问用户——切勿静默生成空差异。
大型或复杂文件:大量使用预处理器(
#if
/
#ifdef
嵌套)、1000+行或不常见模式(C++互操作、
dispatch_once
缓存、深度嵌套宏)的文件不例外。若目标API出现在此类文件中,应用相同的决策树。若文件过大无法一次性编辑,逐个处理废弃API的使用情况。必要时使用子代理。若因宏展开或预处理器复杂度确实无法确定安全替换方案,询问用户——切勿静默跳过。
批量处理规则:处理文件列表时,切勿先分析所有文件再生成所有差异。相反,逐个或小批量(3–5个文件)处理:读取上下文、决策、生成差异,然后处理下一批。这可防止因输出限制或上下文耗尽而静默丢弃文件列表的尾部。若发现生成输出的文件数量少于给定数量,请停止并处理剩余文件。
若本应直接替换的文件生成了空差异,返回并处理——简单文件处理速度快,绝不应丢弃。

Phase 4: Implementation

阶段4:实现

Apply the active task's implementation gates, rules, and post-file checklist from its reference file. The pattern-specific decision tree, gate questions, and validation rules live alongside the patterns they govern in each task file. Use subagents for verification.
应用当前任务参考文件中的实现门限、规则和文件后检查表。特定模式的决策树、门限问题和验证规则与它们所管理的模式一同存在于每个任务文件中。使用子代理进行验证。

Phase 5: Final Verification

阶段5:最终验证

File coverage audit: Use subagents to compare the list of files you were given (or detected in Phase 1) against the files you actually produced diffs for. Every input file must have a non-empty diff. If any file is missing changes, go back and process it now.
The active task's reference file may add task-specific verification steps.

文件覆盖审计:使用子代理比较给定的文件列表(或阶段1检测到的文件)与实际生成差异的文件列表。每个输入文件必须有非空差异。若任何文件缺少更改,立即返回并处理。
当前任务的参考文件可能会添加任务特定的验证步骤。

Task Registry

任务注册表

Apply every task in this registry to the codebase unless the developer's request explicitly scopes to a subset. Each task is independent and has its own detection patterns, decision tree, and verification rules in its reference file. Run them in order from top to bottom.
TaskFileDescription
UIScreen.main modernizationuiscreen-task.mdReplace
UIScreen.main
with context-appropriate APIs
userInterfaceOrientation modernizationorientation-task.mdReplace layout-related orientation checks with size classes or window bounds
Scene lifecycle migrationscene-lifecycle-task.mdMigrate AppDelegate to SceneDelegate
Safe Area Insetssafe-area-task.mdReplace hard coded values for insets with safe area references and ensure that existing references work with asymetric safe areas
除非开发者的请求明确限定了子集,否则将此注册表中的每个任务应用于代码库。每个任务都是独立的,其参考文件中包含各自的检测模式、决策树和验证规则。按从上到下的顺序运行。
任务文件描述
UIScreen.main现代化uiscreen-task.md用适合上下文的API替换
UIScreen.main
userInterfaceOrientation现代化orientation-task.md用尺寸类或窗口边界替换与布局相关的方向检查
场景生命周期迁移scene-lifecycle-task.md将AppDelegate迁移至SceneDelegate
安全区域内边距safe-area-task.md用安全区域引用替换内边距的硬编码值,并确保现有引用适用于非对称安全区域