如何将@namespace 传递给 SwiftUI 中的多个视图?
How to pass @namespace to multiple Views in SwiftUI?
我正在玩新的 Xcode 12 beta 和 SwiftUi 2.0。 .matchedGeometryEffect()
修改器非常适合制作英雄动画。 SwiftUI 中引入了一个新的 属性 @Namespace
。它超级酷。工作很棒。
我只是想知道是否有可能将命名空间变量传递给多个视图?
这是我正在处理的示例,
struct HomeView: View {
@Namespace var namespace
@State var isDisplay = true
var body: some View {
ZStack {
if isDisplay {
VStack {
Image("share sheet")
.resizable()
.frame(width: 150, height: 100)
.matchedGeometryEffect(id: "img", in: namespace)
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.onTapGesture {
withAnimation {
self.isDisplay.toggle()
}
}
} else {
VStack {
Spacer()
Image("share sheet")
.resizable()
.frame(width: 300, height: 200)
.matchedGeometryEffect(id: "img", in: namespace)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
.onTapGesture {
withAnimation {
self.isDisplay.toggle()
}
}
}
}
}
}
它工作正常。
但是如果我想将 Vstack
提取为子视图,下图显示我已将第一个 VStack 提取到子视图中。
我得到了赞美Cannot find 'namespace' in scope
有没有办法跨多个视图传递命名空间?
@Namespace
是 Namespace.ID
的包装器,您可以将 Namespace.ID
参数传递给子视图。
这是一个可能的解决方案演示。用 Xcode 12 / iOS 14
测试
struct HomeView: View {
@Namespace var namespace
@State var isDisplay = true
var body: some View {
ZStack {
if isDisplay {
View1(namespace: namespace, isDisplay: $isDisplay)
} else {
View2(namespace: namespace, isDisplay: $isDisplay)
}
}
}
}
struct View1: View {
let namespace: Namespace.ID
@Binding var isDisplay: Bool
var body: some View {
VStack {
Image("plant")
.resizable()
.frame(width: 150, height: 100)
.matchedGeometryEffect(id: "img", in: namespace)
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.onTapGesture {
withAnimation {
self.isDisplay.toggle()
}
}
}
}
struct View2: View {
let namespace: Namespace.ID
@Binding var isDisplay: Bool
var body: some View {
VStack {
Spacer()
Image("plant")
.resizable()
.frame(width: 300, height: 200)
.matchedGeometryEffect(id: "img", in: namespace)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
.onTapGesture {
withAnimation {
self.isDisplay.toggle()
}
}
}
}
虽然已接受的答案有效,但在多个嵌套子视图之间共享名称空间会有点烦人,尤其是如果您希望初始化程序简洁明了。在这种情况下使用环境值可能更好:
struct NamespaceEnvironmentKey: EnvironmentKey {
static var defaultValue: Namespace.ID = Namespace().wrappedValue
}
extension EnvironmentValues {
var namespace: Namespace.ID {
get { self[NamespaceEnvironmentKey.self] }
set { self[NamespaceEnvironmentKey.self] = newValue }
}
}
extension View {
func namespace(_ value: Namespace.ID) -> some View {
environment(\.namespace, value)
}
}
现在您可以在任何视图中创建命名空间并允许其所有后代使用它:
/// Main View
struct PlaygroundView: View {
@Namespace private var namespace
var body: some View {
ZStack {
SplashView()
...
}
.namespace(namespace)
}
}
/// Subview
struct SplashView: View {
@Environment(\.namespace) var namespace
var body: some View {
ZStack(alignment: .center) {
Image("logo", bundle: .module)
.matchedGeometryEffect(id: "logo", in: namespace)
}
}
}
将 Namespace
注入 Environment
的无警告方法是创建一个 ObservableObject
,名称类似于 NamespaceWrapper
,以保存 Namespace
一旦它被创建。这可能看起来像:
class NamespaceWrapper: ObservableObject {
var namespace: Namespace.ID
init(_ namespace: Namespace.ID) {
self.namespace = namespace
}
}
然后您可以像这样创建并传递 Namespace
:
struct ContentView: View {
@Namespace var someNamespace
var body: some View {
Foo()
.environmentObject(NamespaceWrapper(someNamespace))
}
}
struct Foo: View {
@EnvironmentObject var namespaceWrapper: NamespaceWrapper
var body: some View {
Text("Hey you guys!")
.matchedGeometryEffect(id: "textView", in: namespaceWrapper.namespace)
}
}
@mickben 的回答略有改进。
我们将使用命名空间 object 来保存多个 Namespace.ID 实例。
我们会将其作为环境注入 object - 并提供一种配置预览的简单方法。
首先 - 命名空间包装器
class Namespaces:ObservableObject {
internal init(image: Namespace.ID = Namespace().wrappedValue,
title: Namespace.ID = Namespace().wrappedValue) {
self.image = image
self.title = title
}
let image:Namespace.ID
let title:Namespace.ID
}
我为不同的 object 使用不同的命名空间。
那么,带有图像和标题的项目...
struct Item:Identifiable {
let id = UUID()
var image:UIImage = UIImage(named:"dummy")!
var title = "Dummy Title"
}
struct ItemView: View {
@EnvironmentObject var namespaces:Namespaces
var item:Item
var body: some View {
VStack {
Image(uiImage: item.image)
.matchedGeometryEffect(id: item.id, in: namespaces.image)
Text(item.title)
.matchedGeometryEffect(id: item.id, in: namespaces.title)
}
}
}
struct ItemView_Previews: PreviewProvider {
static var previews: some View {
ItemView(item:Item())
.environmentObject(Namespaces())
}
}
您可以在这里看到多个命名空间的优势。我可以使用 asme item.id 作为图像和标题动画的 'natural' id。
注意这里使用 .environmentObject(Namespaces())
构建预览真的很容易
如果我们在实际应用中使用 Namespaces() 创建我们的命名空间,那么我们会收到警告。
Reading a Namespace property outside View.body. This will result in
identifiers that never match any other identifier.
根据您的设置 - 这可能不是真的,但我们可以通过使用显式初始化程序来解决
struct ContentView: View {
var body: some View {
ItemView(item: Item())
.environmentObject(namespaces)
}
@Namespace var image
@Namespace var title
var namespaces:Namespaces {
Namespaces(image: image, title: title)
}
}
我喜欢将我的命名空间创建保存在一个 var 中,因为它将 属性 包装器和初始化器放在一起。很容易根据需要添加新的命名空间。
我正在玩新的 Xcode 12 beta 和 SwiftUi 2.0。 .matchedGeometryEffect()
修改器非常适合制作英雄动画。 SwiftUI 中引入了一个新的 属性 @Namespace
。它超级酷。工作很棒。
我只是想知道是否有可能将命名空间变量传递给多个视图?
这是我正在处理的示例,
struct HomeView: View {
@Namespace var namespace
@State var isDisplay = true
var body: some View {
ZStack {
if isDisplay {
VStack {
Image("share sheet")
.resizable()
.frame(width: 150, height: 100)
.matchedGeometryEffect(id: "img", in: namespace)
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.onTapGesture {
withAnimation {
self.isDisplay.toggle()
}
}
} else {
VStack {
Spacer()
Image("share sheet")
.resizable()
.frame(width: 300, height: 200)
.matchedGeometryEffect(id: "img", in: namespace)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
.onTapGesture {
withAnimation {
self.isDisplay.toggle()
}
}
}
}
}
}
它工作正常。
但是如果我想将 Vstack
提取为子视图,下图显示我已将第一个 VStack 提取到子视图中。
我得到了赞美Cannot find 'namespace' in scope
有没有办法跨多个视图传递命名空间?
@Namespace
是 Namespace.ID
的包装器,您可以将 Namespace.ID
参数传递给子视图。
这是一个可能的解决方案演示。用 Xcode 12 / iOS 14
测试struct HomeView: View {
@Namespace var namespace
@State var isDisplay = true
var body: some View {
ZStack {
if isDisplay {
View1(namespace: namespace, isDisplay: $isDisplay)
} else {
View2(namespace: namespace, isDisplay: $isDisplay)
}
}
}
}
struct View1: View {
let namespace: Namespace.ID
@Binding var isDisplay: Bool
var body: some View {
VStack {
Image("plant")
.resizable()
.frame(width: 150, height: 100)
.matchedGeometryEffect(id: "img", in: namespace)
Spacer()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.blue)
.onTapGesture {
withAnimation {
self.isDisplay.toggle()
}
}
}
}
struct View2: View {
let namespace: Namespace.ID
@Binding var isDisplay: Bool
var body: some View {
VStack {
Spacer()
Image("plant")
.resizable()
.frame(width: 300, height: 200)
.matchedGeometryEffect(id: "img", in: namespace)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.red)
.onTapGesture {
withAnimation {
self.isDisplay.toggle()
}
}
}
}
虽然已接受的答案有效,但在多个嵌套子视图之间共享名称空间会有点烦人,尤其是如果您希望初始化程序简洁明了。在这种情况下使用环境值可能更好:
struct NamespaceEnvironmentKey: EnvironmentKey {
static var defaultValue: Namespace.ID = Namespace().wrappedValue
}
extension EnvironmentValues {
var namespace: Namespace.ID {
get { self[NamespaceEnvironmentKey.self] }
set { self[NamespaceEnvironmentKey.self] = newValue }
}
}
extension View {
func namespace(_ value: Namespace.ID) -> some View {
environment(\.namespace, value)
}
}
现在您可以在任何视图中创建命名空间并允许其所有后代使用它:
/// Main View
struct PlaygroundView: View {
@Namespace private var namespace
var body: some View {
ZStack {
SplashView()
...
}
.namespace(namespace)
}
}
/// Subview
struct SplashView: View {
@Environment(\.namespace) var namespace
var body: some View {
ZStack(alignment: .center) {
Image("logo", bundle: .module)
.matchedGeometryEffect(id: "logo", in: namespace)
}
}
}
将 Namespace
注入 Environment
的无警告方法是创建一个 ObservableObject
,名称类似于 NamespaceWrapper
,以保存 Namespace
一旦它被创建。这可能看起来像:
class NamespaceWrapper: ObservableObject {
var namespace: Namespace.ID
init(_ namespace: Namespace.ID) {
self.namespace = namespace
}
}
然后您可以像这样创建并传递 Namespace
:
struct ContentView: View {
@Namespace var someNamespace
var body: some View {
Foo()
.environmentObject(NamespaceWrapper(someNamespace))
}
}
struct Foo: View {
@EnvironmentObject var namespaceWrapper: NamespaceWrapper
var body: some View {
Text("Hey you guys!")
.matchedGeometryEffect(id: "textView", in: namespaceWrapper.namespace)
}
}
@mickben 的回答略有改进。
我们将使用命名空间 object 来保存多个 Namespace.ID 实例。 我们会将其作为环境注入 object - 并提供一种配置预览的简单方法。
首先 - 命名空间包装器
class Namespaces:ObservableObject {
internal init(image: Namespace.ID = Namespace().wrappedValue,
title: Namespace.ID = Namespace().wrappedValue) {
self.image = image
self.title = title
}
let image:Namespace.ID
let title:Namespace.ID
}
我为不同的 object 使用不同的命名空间。
那么,带有图像和标题的项目...
struct Item:Identifiable {
let id = UUID()
var image:UIImage = UIImage(named:"dummy")!
var title = "Dummy Title"
}
struct ItemView: View {
@EnvironmentObject var namespaces:Namespaces
var item:Item
var body: some View {
VStack {
Image(uiImage: item.image)
.matchedGeometryEffect(id: item.id, in: namespaces.image)
Text(item.title)
.matchedGeometryEffect(id: item.id, in: namespaces.title)
}
}
}
struct ItemView_Previews: PreviewProvider {
static var previews: some View {
ItemView(item:Item())
.environmentObject(Namespaces())
}
}
您可以在这里看到多个命名空间的优势。我可以使用 asme item.id 作为图像和标题动画的 'natural' id。
注意这里使用 .environmentObject(Namespaces())
如果我们在实际应用中使用 Namespaces() 创建我们的命名空间,那么我们会收到警告。
Reading a Namespace property outside View.body. This will result in identifiers that never match any other identifier.
根据您的设置 - 这可能不是真的,但我们可以通过使用显式初始化程序来解决
struct ContentView: View {
var body: some View {
ItemView(item: Item())
.environmentObject(namespaces)
}
@Namespace var image
@Namespace var title
var namespaces:Namespaces {
Namespaces(image: image, title: title)
}
}
我喜欢将我的命名空间创建保存在一个 var 中,因为它将 属性 包装器和初始化器放在一起。很容易根据需要添加新的命名空间。