I'm playing with the new Xcode 12 beta & SwiftUi 2.0. .matchedGeometryEffect() modifier is great to do Hero animations. There is a new property @Namespace is introduced in SwiftUI. Its super cool. working awesome.
I was just wondering if there is any possibility to pass a Namespace variable to multiple Views?
Here is an example I'm working on,
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()
}
}
}
}
}
}
It is working fine.
But if I want to extract the Vstack as a SubView, Below picture shows that I have extracted the first VStack into a subview.

I'm getting a compliment Cannot find 'namespace' in scope
Is there a way to pass namespace across multiple Views?
The @Namespace is a wrapper for Namespace.ID, and you can pass Namespace.ID in argument to subviews.
Here is a demo of possible solution. Tested with 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()
}
}
}
}
A warning free approach to inject the Namespace into the Environment is to create an ObservableObject, named something like NamespaceWrapper, to hold the Namespace once it's been created. This could look something like:
class NamespaceWrapper: ObservableObject {
var namespace: Namespace.ID
init(_ namespace: Namespace.ID) {
self.namespace = namespace
}
}
You would then create and pass the Namespace like so:
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)
}
}
While the accepted answer works, it gets a bit annoying to share the namespace across multiple nested subviews, especially if you'd like your initialisers clean and to the point. Using environment values might be better in this case:
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)
}
}
Now you can create a namespace in any view and allow all its descendants to use it:
/// 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)
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With