SwiftUI:使用 size classes 时通过 AnyView 实现类型擦除

SwiftUI让我们的每个视图都可以访问一个共享的信息池,称为环境(environment),我们在删除工作表时已经使用了它。如果您还记得,这意味着要创建这样的属性:

@Environment(\.presentationMode) var presentationMode

当我们准备好的时候,我们可以这样把Sheet 弹窗隐藏掉:

Text("Hello World")
    .onTapGesture {
        self.presentationMode.wrappedValue.dismiss()
    }

这种方法允许SwiftUI确保在视图被隐藏时更新正确的状态——例如,如果我们附加了@state属性来显示工作表,则在工作表被解除时,它将被设置为false。

这个环境实际上充满了有趣的东西,我们可以阅读,以帮助我们的应用程序更好地工作。在这个项目中,我们将使用环境处理Core Data,但在这里我将向您展示它的另一个重要用途:size classes。size classes 是苹果完全模糊的方式告诉我们的视图有多大的空间。

当我说“完全模糊”时,我的意思是:我们只有两个水平和垂直大小的类,称为“紧凑”和“常规”。就是这样,它覆盖了所有屏幕尺寸,从横向最大的iPad Pro到纵向最小的iPhone。这并不意味着它是无用的——远远不是!——只是它只让我们用最广泛的术语来解释我们的用户界面。

要演示实际的 size classes,我们可以创建一个具有跟踪当前 size classes 的属性的视图,并将其属性显示在文本视图中:

struct ContentView: View {
    @Environment(\.horizontalSizeClass) var sizeClass

    var body: some View {
        if sizeClass == .compact {
            return HStack {
                Text("Active size class:")
                Text("压缩")
            }
            .font(.largeTitle)
        } else {
            return HStack {
                Text("Active size class:")
                Text("常规")
            }
            .font(.largeTitle)
        }
    }
}

请试着在一个12.9英寸的iPad Pro模拟器上运行,这样你就能得到完整的效果。首先你应该看到“常规”显示,因为我们的应用程序将得到全屏。但如果你从模拟器屏幕底部轻轻地向上滑动,就会出现基座,你可以将Safari之类的东西拖到iPad的右侧,进入多任务模式。

即使我们的应用只有一半的屏幕,你仍然会看到我们的“常规”标签出现。但是如果你把拆分器拖到左边,也就是说,只给我们的应用四分之一左右的可用空间,那么现在它将变为“压缩”。

所以,在全屏宽的情况下,我们是一个普通尺寸的类,在半屏宽的情况下,我们仍然是一个普通尺寸的类,但是当我们变小的时候,我们最终是紧凑的。就像我说的:这是广义的。

更有趣的是,我们是否想根据环境改变布局。在这种情况下,当我们在一个紧凑型的类中时,使用VStackHStack更有意义,但是这比您想象的要复杂。

首先,更改代码,以便返回VStackHStack

if sizeClass == .compact {
    return VStack {
        Text("Active size class:")
        Text("压缩")
    }
    .font(.largeTitle)
} else {
    return HStack {
        Text("Active size class:")
        Text("常规")
    }
    .font(.largeTitle)
}

当您构建代码时,您将看到一个不祥的错误:“Function declares an opaque return type, but the return statements in its body do not have matching underlying types.”,bodysome View返回类型要求从代码中的所有路径返回一个单独的类型——我们有不能有时返回一个视图,而有时又返回一些其他视图。

你可能认为你会很聪明,把我们的整个情况包装在另一个视图中,比如VStack,但这也不起作用。相反,我们需要一个更高级的解决方案,称为类型擦除。我之所以说“高级”,是因为它在概念上非常聪明,而且它的实现可能并不琐碎,但从我们的角度来看——即,实际上使用它,类型擦除非常简单。

首先,让我们看看代码——用以下代码替换当前的body代码:

if sizeClass == .compact {
    return AnyView(VStack {
        Text("Active size class:")
        Text("压缩")
    }
    .font(.largeTitle))
} else {
    return AnyView(HStack {
        Text("Active size class:")
        Text("常规")
    }
    .font(.largeTitle))
}

我知道这读起来很费劲,所以让我来简化一下到底发生了什么变化:

return AnyView(HStack {
    // ...
}
.font(.largeTitle))

如果您再次构建代码,您将看到它编译得很干净,而且运行时看起来更好——应用程序现在可以根据大小类在HStackVStack之间平滑切换。

改变的是,我们将两个堆栈都包装在一个名为AnyView的新视图类型中,这就是所谓的类型擦除包装器

AnyView遵循与TextColorVStack等相同的视图协议,并且它内部还包含一个特定类型的视图。然而,外部AnyView并不公开它包含的内容——Swift将我们的条件视为返回一个AnyView或另一个AnyView,因此它被认为是同一类型的。这就是“类型擦除”这个名字的由来:AnyView有效地隐藏或擦除它所包含的视图的类型。

现在,这里的逻辑结论是,如果AnyView可以让我们避免某些视图的限制,那么我们为什么不一直使用它呢。答案很简单:性能。当SwiftUI确切知道视图层次结构中的内容时,它可以根据需要简单地添加和删除小部分,但是当我们使用AnyView时,我们会主动拒绝SwiftUI的这些信息。因此,当发生常规更改时,它可能需要做更多的工作来保持用户界面的更新,因此通常最好避免使用AnyView,除非您特别需要它。

译自 Using size classes with AnyView type erasure

使用 @Binding 创建自定义组件 Hacking with iOS: SwiftUI Edition 如何结合 Core Data 和 SwiftUI

赏我一个赞吧~~~

你可能感兴趣的:(SwiftUI:使用 size classes 时通过 AnyView 实现类型擦除)