Is it possible at all to define a @Namespace var outside a View and pass it in to views. For example, to do this in the State
class?
I have included a baseline code that has matched animations. I have tried a few things (like this) without success, meaning the animations are not matched. I have seen some solutions but the @Namespace var is always a View property which I cannot do.
import SwiftUI
import PlaygroundSupport
enum Flow {
case initial
case searching
}
class State: ObservableObject {
@Published var flow: Flow = .initial
}
let state = State()
struct Container: View {
@Namespace var namespace
@ObservedObject var state: State
var body: some View {
VStack {
SubView1(state: state, namespace: namespace)
SubView2(state: state, namespace: namespace)
}
.onTapGesture {
withAnimation(.spring()) {
if state.flow == .initial {
state.flow = .searching
} else {
state.flow = .initial
}
}
}
.frame(width: 300, height: 300)
}
}
struct SubView1: View {
@ObservedObject var state: State
var namespace: Namespace.ID
var body: some View {
HStack {
Text("Lorem")
Spacer()
if state.flow == .initial {
Text("Ipsum")
.matchedGeometryEffect(id: "id", in: namespace)
Text("|")
}
Image(systemName: "person.circle.fill")
}
}
}
struct SubView2: View {
@ObservedObject var state: State
var namespace: Namespace.ID
var body: some View {
VStack {
Text("Headline")
if state.flow == .searching {
Text("Ipsum")
.matchedGeometryEffect(id: "id", in: namespace)
}
}
}
}
PlaygroundPage.current.setLiveView(Container(state: state))
Is it possible at all to define a @Namespace var outside a View and pass it in to views. For example, to do this in the State
class?
I have included a baseline code that has matched animations. I have tried a few things (like this) without success, meaning the animations are not matched. I have seen some solutions but the @Namespace var is always a View property which I cannot do.
import SwiftUI
import PlaygroundSupport
enum Flow {
case initial
case searching
}
class State: ObservableObject {
@Published var flow: Flow = .initial
}
let state = State()
struct Container: View {
@Namespace var namespace
@ObservedObject var state: State
var body: some View {
VStack {
SubView1(state: state, namespace: namespace)
SubView2(state: state, namespace: namespace)
}
.onTapGesture {
withAnimation(.spring()) {
if state.flow == .initial {
state.flow = .searching
} else {
state.flow = .initial
}
}
}
.frame(width: 300, height: 300)
}
}
struct SubView1: View {
@ObservedObject var state: State
var namespace: Namespace.ID
var body: some View {
HStack {
Text("Lorem")
Spacer()
if state.flow == .initial {
Text("Ipsum")
.matchedGeometryEffect(id: "id", in: namespace)
Text("|")
}
Image(systemName: "person.circle.fill")
}
}
}
struct SubView2: View {
@ObservedObject var state: State
var namespace: Namespace.ID
var body: some View {
VStack {
Text("Headline")
if state.flow == .searching {
Text("Ipsum")
.matchedGeometryEffect(id: "id", in: namespace)
}
}
}
}
PlaygroundPage.current.setLiveView(Container(state: state))
Share
Improve this question
edited Mar 13 at 20:59
fingia
asked Mar 13 at 20:46
fingiafingia
6471 gold badge9 silver badges22 bronze badges
2
|
2 Answers
Reset to default 0Namespace
is a DynamicProperty
and DynamicProperty
only works in a View
because they get their value when the body
updates.
They cannot be declared in an additional class
, struct
or globally.
One possible solution is to inject the View-owned namespace into your "state" class. Then views can pass in this state's namespace property in their matchedGeometryEffect
:
import SwiftUI
import PlaygroundSupport
enum Flow {
case initial
case searching
}
class AppState: ObservableObject {
@Published var flow: Flow = .initial
// We'll store SwiftUI's actual Namespace.ID here (reassigned later).
// Published is necessary to emit updates.
@Published var namespace: Namespace.ID
init(namespace: Namespace.ID) {
self.namespace = namespace
}
}
struct Container: View {
@Namespace private var realNamespace
@StateObject private var state: AppState
init() {
// SwiftUI hasn't injected the "real" namespace yet, so use a placeholder.
let placeholder = Namespace().wrappedValue
_state = StateObject(wrappedValue: AppState(namespace: placeholder))
}
var body: some View {
VStack {
SubView1(state: state, namespace: state.namespace)
SubView2(state: state, namespace: state.namespace)
}
.onTapGesture {
withAnimation(.spring()) {
if state.flow == .initial {
state.flow = .searching
} else {
state.flow = .initial
}
}
}
.onAppear {
if state.namespace != realNamespace {
print(#function, "injecting real namespace")
state.namespace = realNamespace
}
}
.frame(width: 300, height: 300)
}
}
// MARK: - SubView1
struct SubView1: View {
@ObservedObject var state: AppState
var namespace: Namespace.ID
var body: some View {
HStack {
Text("Lorem")
Spacer()
if state.flow == .initial {
Text("Ipsum")
.matchedGeometryEffect(id: "id", in: namespace)
Text("|")
}
Image(systemName: "person.circle.fill")
}
}
}
// MARK: - SubView2
struct SubView2: View {
@ObservedObject var state: AppState
var namespace: Namespace.ID
var body: some View {
VStack {
Text("Headline")
if state.flow == .searching {
Text("Ipsum")
.matchedGeometryEffect(id: "id", in: namespace)
}
}
}
}
// MARK: - Playground Live View
PlaygroundPage.current.setLiveView(Container())
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744685995a4587919.html
@State
and pass down let for read access.@Binding
for read/write. No class needed. – malhal Commented Mar 14 at 10:41