返回博客首页
← 所有文章

NativeScript 5.0 iOS 安全区域支持

2018年11月29日 — 作者:Martin Yankov

随着 iPhone X 和 iOS 11 的发布,Apple 为用户带来了全新的沉浸式用户体验,应用程序可以明显地延伸到手机的整个表面。NativeScript 4.0 通过其核心导航组件提供了对此功能的支持。PageActionBarTabView 组件延伸到设备的边缘。在 NativeScript 5.0 中,我们决定通过添加一个新的属性来控制此行为,从而将此行为启用到所有组件。

让我用一个简单的例子来演示一下变化

<GridLayout rows="*, *">
   <GridLayout row="0" backgroundColor="Coral">
       <Label verticalAlignment="top" text="Lorem ipsum..." textWrap="true"></Label>
   </GridLayout>
   <GridLayout row="1" backgroundColor="SkyBlue">
       <Label verticalAlignment="top" text="Lorem ipsum..." textWrap="true"></Label>
   </GridLayout>
</GridLayout>

NativeScript 4 上的结果

nativescript 4 without safe area support

看起来不太对,尤其是在横屏模式下。到目前为止,您唯一可用的自定义选项是将 backgroundColor 设置为 Page 组件。即使这样,它也限制您在所有侧面使用相同的颜色。为了解决这个问题并为开发者提供更多功能,在 NativeScript 5.0 中,所有布局组件现在默认自动延伸到屏幕边缘。

这是 NativeScript 5.0 上的结果

nativescript 5 with safe area support

很酷,对吧?请注意 Label 组件实际上是内缩了一点。这是因为它默认情况下受限于所谓的“安全区域”。

在我们深入了解新 API 的细节之前,让我们先提供一些背景信息。

什么是 iOS 安全区域?

iOS 安全区域是 Apple 在 iOS 11 中引入的一个术语。它是屏幕上可以自由使用的区域,不会被系统的硬件和软件部分遮挡。安全区域不是一个常数。它受凹口、屏幕圆角、状态栏和主屏幕指示器影响,但也受应用程序的某些部分影响,如操作栏和标签栏。

为了适应这一点,iOS 11 附带了一套新的 API 和一个安全区域布局指南。此布局指南的目的是帮助开发者以一种方式定位其应用程序内容,以便应用程序提供沉浸式用户体验,同时正确显示其内容。您可以查看 Apple 在其文档中提供的信息

apple ios safe area

NativeScript 4.0 以一种兼容模式处理了这个问题,其中只有重要的覆盖组件 ActionBarTabView 遵守了新的指南。应用程序看起来总体上还不错,但我们很快意识到一些非常基本的需求和设计模式很难实现。例如,在显示模态视图时控制状态栏的颜色特别困难。

技术挑战

如果我们先花点时间解释一下为什么 iOS 中的这种新行为对 NativeScript 来说并不自然,将会很有帮助。Apple 文档中的一个主要要点是 UIKit 组件完美地处理了这个安全区域,并且只要您正确使用它们,您就可以获得最佳的用户体验。NativeScript 提供的大多数组件都具有底层的原生 UIKit 视图,因此我们可以让它们自己处理安全区域。但还有一些组件是 NativeScript 独有的,在 iOS 世界中不存在。这些是位于布局系统基础上的布局组件(GridLayoutStacklayout 等)。

iOS 自动布局系统纯粹基于坐标和约束(以方程的形式),表示视图彼此之间的相对边界。在这种系统中,添加表示安全区域的全局布局指南非常自然。

NativeScript 的 更传统的布局系统 就不那么幸运了。基本上,每个布局组件都需要自己处理安全区域布局指南,并决定如何测量和布局自身及其子项。这需要对布局系统进行软重构,使其组件能够进行测量和布局。但请放心,只要您正确使用它们,您将获得更好的用户体验 😀。

新 API

注意:以下说明至少需要对 NativeScript 布局系统的工作原理有一定的了解。如果您不熟悉它,请快速查看文档中的 布局过程文章

新布局行为的基础是以下想法

  1. 所有组件都在安全区域内测量自身及其子项 - 这个想法是,当您定义行或列或弹性项目时,您希望可用的安全区域(而不是全屏)被正确地划分。
  2. 所有组件都以全屏布局,但内缩到安全区域边界 - 这是因为我们希望布局的坐标系原点为全屏区域。它现在是您布局的一部分。
  3. 如果组件的 iosOverflowSafeArea 属性设置为 true 并且该组件接触到安全区域的任何一侧,它将溢出到全屏 - 这是使布局“沉浸式”的部分。它在安全区域内工作,但其背景扩展到全屏。

iosOverflowSafeArea 属性是一个新的布尔属性,随 NativeScript 5.0 提供。它适用于所有具有可见表示形式的组件。掌握安全区域的关键在于该属性对于不同的组件集具有不同的默认值。

对于所有可以包含子项的组件,iosOverflowSafeArea 属性默认为 true。我们可以称它们为容器。这包括六种布局、ScrollViewListViewRepeater。该列表还包括 WebView 组件,它不能包含其他 NativeScript 组件作为子项,而是包含 Web 组件。所有这些默认情况下都会溢出到屏幕边缘,以适应为 iPhone X 及类似设备设计的用户体验。

对于所有其他组件,iosOverflowSafeArea 属性默认为 false - LabelButtonImage 等。它们被认为是应用程序内容,默认情况下应在安全区域内布局,以便用户可以无障碍地访问它们。自行承担风险,将这些控件的属性值设置为 true :)

让我们做一些快速示例,展示您可以使用此新 API 执行的操作。需要注意的是,以下示例故意在没有边距和填充的情况下创建,以便您可以在不进行样式设置的情况下查看默认的组件约束。

嵌套布局与 iosOverflowSafeArea 结合使用

<GridLayout rows="*, *" columns="*, *" backgroundColor="DarkSlateBlue">
   <GridLayout row="0" col="0" backgroundColor="Coral">
       <Label verticalAlignment="top" text="Lorem ipsum..." textWrap="true"></Label>
   </GridLayout>
   <GridLayout row="1" col="0" backgroundColor="SkyBlue" iosOverflowSafeArea="false">
       <Label verticalAlignment="top" text="Lorem ipsum..." textWrap="true"></Label>
   </GridLayout>
   <GridLayout row="0" col="1" backgroundColor="SkyBlue" iosOverflowSafeArea="false">
       <Label verticalAlignment="top" text="Lorem ipsum..." textWrap="true"></Label>
   </GridLayout>
   <GridLayout row="1" col="1" backgroundColor="Coral">
       <Label verticalAlignment="top" text="Lorem ipsum..." textWrap="true"></Label>
   </GridLayout>
</GridLayout>

nested layouts

嵌套布局也会溢出安全区域。将子布局的 iosOverflowSafeArea 设置为 false 可以让您看到下面的父级。这可以实现一些奇特的视觉效果。

沉浸式 ListView

<ListView items="{{ source }}" backgroundColor="#003B46">
   <ListView.itemTemplate>
       <GridLayout columns="2*, 3*" backgroundColor="#07575B" margin="10">
           <Image col="0" stretch="aspectFill" src="~/images/ocean.jpg"></Image>
           <Label col="1" verticalAlignment="top" color="#C4DFE6" text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc id sem ex. Aenean at ultricies metus, ut tincidunt nunc. Vivamus dictum sem sed ante fermentum, id congue lacus lacinia. " textWrap="true"></Label>
       </GridLayout>
   </ListView.itemTemplate>
</ListView>

immersive listview

ListView 也是一个容器,现在可以很好地溢出安全区域。但是,这里有一个重要的限制 - 项目模板不能溢出。这是因为 NativeScript 目前仅将原生内容视图作为模板公开,并且它严格限制在安全区域内。下面的原生控件公开了另一个称为背景视图的模板,该模板延伸到全屏。

带有溢出图像的 ScrollView

<ScrollView backgroundColor="DarkSlateGray">
   <StackLayout>
       <Image stretch="aspectFill" height="200" src="~/images/forest1.jpg"></Image>
       <StackLayout backgroundColor="DarkSalmon">
           <Label verticalAlignment="top" text="Lorem ipsum..." textWrap="true"></Label>
       </StackLayout>
       <Image stretch="aspectFill" height="200" src="~/images/forest2.jpg"></Image>
       <StackLayout backgroundColor="YellowGreen">
           <Label verticalAlignment="top" text="Lorem ipsum..." textWrap="true"></Label>
       </StackLayout>
       <Image stretch="aspectFill" height="200" src="~/images/forest3.jpg"></Image>
       <StackLayout backgroundColor="DimGray">
           <Label verticalAlignment="top" text="Lorem ipsum..." textWrap="true"></Label>
       </StackLayout>
   </StackLayout>
</ScrollView>

scrollview overflowing images

ScrollView 本身及其中的布局会溢出安全区域。您可以在背景颜色中看到这一点。Image 组件被认为是内容,默认情况下受限于安全区域。不知何故,在这种特定情况下,它看起来不太对。让我们看看如果我们通过将 iosOverflowSafeArea 属性设置为 true 来强制它溢出会发生什么。

<ScrollView backgroundColor="DarkSlateGray">
   <StackLayout>
       <Image stretch="aspectFill" iosOverflowSafeArea="true" height="200" src="~/images/forest1.jpg"></Image>
       <StackLayout backgroundColor="DarkSalmon">
           <Label verticalAlignment="top" text="Lorem ipsum..." textWrap="true"></Label>
       </StackLayout>
       <Image stretch="aspectFill" iosOverflowSafeArea="true" height="200" src="~/images/forest2.jpg"></Image>
       <StackLayout backgroundColor="YellowGreen">
           <Label verticalAlignment="top" text="Lorem ipsum..." textWrap="true"></Label>
       </StackLayout>
       <Image stretch="aspectFill" iosOverflowSafeArea="true" height="200" src="~/images/forest3.jpg"></Image>
       <StackLayout backgroundColor="DimGray">
           <Label verticalAlignment="top" text="Lorem ipsum..." textWrap="true"></Label>
       </StackLayout>
   </StackLayout>
</ScrollView>

scrollview immersive images

这样好多了。但是现在又出现了另一个问题。应用程序非常沉浸式,以至于我们无法完全看到状态栏。要解决此问题,我们将不得不在其下方放置一些对比层,该层在滚动时不会移动。同时,我们不希望此层在横屏模式下出现,因为那里没有状态栏。过去,我们会使用一个控制状态栏颜色的插件,这很容易。但是从 iOS 11 开始,状态栏实际上是您布局的一部分。看到应用程序的内容在滚动时如何穿过状态栏?这是因为它们在那里布局。那么我们如何处理这种情况呢?

我将与您分享一个小秘密。如果布局与安全区域接壤,即使它高度为 0,它也会溢出。让我们将 ScrollView 包裹在一个 GridLayout 中,并在与 ScrollView 相同的行上但位于其上方的位置放置第二个布局。

<GridLayout rows="*">
   <ScrollView row="0" backgroundColor="DarkSlateGray">
      ...
   </ScrollView>

   <GridLayout row="0" backgroundColor="White" opacity="0.4" height="0" verticalAlignment="top"></GridLayout>
</GridLayout>

scrollview with status bar

现在无论我们滚动到哪里,都可以看到纵向模式下的状态栏。您可以使用此技巧在所有侧面将布局定位到安全区域之外。

关于转换和动画的一句话

这个新的 iOS 安全区域功能位于 NativeScript 布局过程的核心,因此(希望是更好)它可能会影响您的 UI 动画。如果您正在为溢出安全区域的组件设置动画或简单地应用变换,则需要考虑一件重要的事情

转换在安全区域溢出后应用!

这意味着如果元素接触到安全区域的边缘,它将有效地被扩展,并且这个新的更大元素将被动画化。这在大多数情况下应该可以正常工作。

对于需要动画组件具有严格大小的场景,只需将其 iosOverflowSafeArea 属性设置为 false,它就不会被扩展。

NativeScript Pro UI 插件怎么样?

如果您不知道,NativeScript Pro UI 是一组由 NativeScript 团队提供的免费丰富的 UI 插件。所有插件的最新版本都应该与 NativeScript 5.0 兼容,并有一些重要的说明。

RadSideDrawer 插件获得了新的主要版本 5.0,该版本实现了 NativeScript 5.0 中的 iOS 安全区域支持。由于重大的内部更改,它与 NativeScript 4.x 不兼容。这非常重要,因为通常插件将其主要内容容器中的整个应用程序 UI 包裹起来。如果它没有正确处理安全区域,则您的整个应用程序将无法处理它。您可以通过从侧抽屉模板创建新应用程序立即看到这一点。

RadListView 插件目前通过在安全区域内布局与 NativeScript 5.0 兼容,就像以前一样。我们目前正在开发一个新版本,它将允许它扩展并滚动到屏幕边缘,敬请期待。