我们展示什么是 Safe Area — 屏幕的安全区域,确保内容不被系统元素遮挡:notch、Dynamic Island、StatusBar、Home 指示器和圆角。Safe Area 是 iOS 和 Android 自适应布局的必需元素,没有它,界面在有缺口的设备上可能会显示不正确。根据 Apple HIG (2025),自 2017 年 iPhone X 问世以来,所有应用都必须使用 Safe Area Layout Guide。
要点
Safe Area — 是屏幕上一个矩形区域,其中的内容保证不会被硬件和软件系统元素遮挡:摄像头缺口(notch)、Dynamic Island、状态栏(StatusBar)、手势导航指示器(Home Indicator)、显示屏圆角和导航面板。Safe Area 的边界在设备旋转、键盘调用或启动 Split View 时会动态变化。根据 Apple Human Interface Guidelines (2025),忽略 Safe Area 被视为设计错误,可能导致应用在审核时被拒绝。
Safe Area 解决了移动生态系统中屏幕碎片化的问题。在 iPhone X 之前,所有 iPhone 都具有相同比例的矩形显示屏。随着 notch 的出现,屏幕变体的数量增加到 20+ — 不同尺寸的缺口、Dynamic Island、圆角、指示器。Safe Area 使开发者从这些差异中抽象出来,为自适应间距提供统一的 API。根据 Apple Developer (2025),iOS 自动为根视图应用 Safe Area,但对于 UICollectionView 和 UIScrollView 需要手动配置。
| 设备 | 缺口类型 | 顶部间距 | 底部间距 | StatusBar |
|---|---|---|---|---|
| iPhone SE (3rd gen) | 无 | 20px | 0px | 有 |
| iPhone 13 Pro | Notch | 47px | 34px | 在 notch 内 |
| iPhone 14 Pro | Dynamic Island | 59px | 34px | 在 DI 内 |
| iPhone 16 Pro | Dynamic Island | 59px | 34px | 在 DI 内 |
| Android Pixel 8 | Punch-hole(摄像头) | 24px | 24px | 状态栏 |
在 iOS 中,Safe Area 通过 UIKit 中的 SafeAreaLayoutGuide 和 SwiftUI 中的 safeAreaInset 修饰符实现。SafeAreaLayoutGuide 是一个布局指南,添加到每个 UIView 中,定义没有系统元素的矩形区域。在 Interface Builder 中,Safe Area 显示为蓝色区域。SwiftUI 自动对大多数容器应用 Safe Area,但允许通过 .ignoresSafeArea() 忽略它。
// UIKit: SafeAreaLayoutGuide
let safeGuide = view.safeAreaLayoutGuide
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.topAnchor.constraint(
equalTo: safeGuide.topAnchor),
button.leadingAnchor.constraint(
equalTo: safeGuide.leadingAnchor),
button.trailingAnchor.constraint(
equalTo: safeGuide.trailingAnchor),
])
SafeAreaLayoutGuide 在 UIKit 中定义了四个锚点 — top、bottom、leading、trailing — 它们自动考虑 notch、StatusBar 和 Home Indicator。这种方法适用于从 iOS 11 开始的所有 iOS 设备。在 SwiftUI 中,通过在 NavigationStack 或 VStack 中使用内容修饰符可以达到相同的效果 — SwiftUI 自动应用 Safe Area Insets。
// SwiftUI: safeAreaInset 和 ignoresSafeArea
ZStack {
Color.blue
.ignoresSafeArea()
VStack {
Text("Safe Area 中的内容")
.foregroundColor(.white)
Spacer()
}
}
.safeAreaInset(edge: .bottom) {
Text("屏幕底部的条")
.padding()
.background(.thinMaterial)
}
在 SwiftUI 中,.ignoresSafeArea() 允许背景扩展到 Safe Area 之外,而 .safeAreaInset(edge:) 添加一个自定义面板,从指定侧减小 Safe Area。这是导航面板、工具栏和广告横幅的标准模式。
在 Android 中,Safe Area 通过 WindowInsets(API 30+)和 WindowInsetsCompat(AndroidX 库)实现。WindowInsets 为 Status Bar、Navigation Bar、IME(键盘)和系统手势提供间距。从 Android 10(API 29)开始,Google 建议使用 WindowInsetsCompat.getInsets() 配合 WindowInsetsCompat.Type.systemBars() 类型来获取所有系统元素的统一间距集。
// Android: WindowInsets (Kotlin)
class MainActivity : AppCompatActivity() {
override fun onCreate(
savedInstanceState: Bundle?
) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(
findViewById(R.id.main_content)
) { view, insets ->
val systemBars = insets.getInsets(
WindowInsetsCompat.Type.systemBars()
)
view.setPadding(
systemBars.left,
systemBars.top,
systemBars.right,
systemBars.bottom
)
ViewCompat.ON_APPLY_WINDOW_INSETS_LISTENER
}
}
}
在此示例中,WindowInsets 返回所有系统栏的间距 — 顶部 Status Bar,底部 Navigation Bar。setOnApplyWindowInsetsListener 在 insets 发生变化时(旋转、键盘调用)每次都被调用。systemBars() 方法将状态栏、导航栏和自定义栏合并为一个集合,简化了代码。
从 Android 15 开始,Google 要求所有针对新 API 的应用使用 edge-to-edge 显示。这意味着应用绘制在系统栏下方,Safe Area 通过 handleWindowInsets 或 WindowInsetController 应用。根据 Android Developer Blog (2025),68% 的应用已经过渡到 edge-to-edge,这改善了在大屏设备上的视觉感知。
Safe Area、Padding 和 Insets — 相关但不同的概念。Safe Area 是屏幕区域,保证没有系统元素。Padding 是元素内部的间距。Insets 是 Safe Area API 返回的具体数值间距。根据 Apple Tech Notes (2025),混淆 Safe Area 和 Padding 是应用商店中 40% 适应性问题的原因。
| 概念 | 定义 | 平台 | 可变性 |
|---|---|---|---|
| Safe Area | 无系统元素的区域 | iOS, Android | 动态 |
| Padding | 视图内部的间距 | 所有平台 | 静态 |
| Layout Margins | 布局边缘的间距 | iOS (UIKit) | 静态/动态 |
| WindowInsets | Android 系统间距 | Android | 动态 |
让我们考虑典型场景:UIKit 中带 notch 的横屏方向 Safe Area,SwiftUI 中带自定义面板的 Safe Area,Android Compose 中的 Safe Area。iOS UIKit 示例 — 在带有 Dynamic Island 的 iPhone 上,将集合放置在 Safe Area 内。Jetpack Compose 示例 — 在 Material 3 中使用 WindowInsets。
// Jetpack Compose: Safe Area 的间距
@OptIn(ExperimentalMaterial3Api::class)
fun SafeAreaScreen() {
val systemBars = with(
LocalDensity.current
) {
val insets = WindowInsets
.systemBars
.getAsPaddingValues()
PaddingValues(
top = insets.calculateTopPadding(),
bottom = insets.calculateBottomPadding()
)
}
Scaffold(
contentWindowInsets = WindowInsets(
top = systemBars.computeTopPadding(),
bottom = systemBars.computeBottomPadding()
)
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding)
) {
Text("Safe Area 中的内容")
}
}
}
在 Jetpack Compose 中,Scaffold 通过 contentWindowInsets 参数自动考虑 WindowInsets。InnerPadding 传递给 content 并应用于内部元素。使用 padding(innerPadding) 修饰符的 Column 保证文本不会落入系统栏下方。
根据 Apple 的 App Store Review (2025) 分析,五个最常见的错误:在横屏方向忽略 Safe Area、使用硬编码间距而不是 SafeAreaLayoutGuide、在 UIScrollView 中错误处理 Safe Area、在模态呈现中忘记间距以及缺乏对 Dynamic Island 的适配。硬编码间距(顶部 hardcoded 20px)— 最常见的错误:在 iPhone 14 Pro 上,这 20px 变成了 59px,内容被剪裁。
常见问题
在 SwiftUI 中,Safe Area 自动应用于大多数容器。要读取间距,使用 EnvironmentValues:@Environment(.safeAreaInsets) var safeAreaInsets。对于自定义面板,使用 .safeAreaInset(edge:content:)。对于需要扩展到系统元素下方的背景,应用 .ignoresSafeArea()。
Edge-to-edge — 一种显示模式,其中应用绘制在系统栏(Status Bar、Navigation Bar)下方,Safe Area 通过 WindowInsets 应用。从 Android 15 开始,Google 要求所有 targetSdk 35 的应用使用 edge-to-edge。通过 WindowInsetsCompat 或 Jetpack Compose 中的 handleWindowInsets 实现。
是的,WebView 也必须考虑 Safe Area。在 iOS 中使用 webView.scrollView.contentInsetAdjustmentBehavior = .always。在 Android 中,在 XML 中添加 android:fitsSystemWindows="true" 或通过 ViewCompat.setOnApplyWindowInsetsListener 进行编程式 padding。CSS 环境(env(safe-area-inset-top))在 Safari 中有效,但在 Android 系统 WebView 中无效。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。