16

我正在使用新的 Xcode 12 beta 和 SwiftUi 2.0。.matchedGeometryEffect()修改器非常适合做英雄动画。@NamespaceSwiftUI 中引入了一个新属性。它超级酷。工作真棒。

我只是想知道是否有可能将命名空间变量传递给多个视图?

这是我正在研究的一个例子,

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

有没有办法跨多个视图传递命名空间?

4

4 回答 4

24

是的@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()
            }
        }
    }
}
于 2020-07-28T09:57:31.500 回答
1

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)
    }
}
于 2022-02-07T23:24:50.283 回答
0

虽然接受的答案有效,但在多个嵌套子视图之间共享命名空间有点烦人,特别是如果你希望你的初始化程序简洁明了。在这种情况下,使用环境值可能会更好:

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)
        }
    }
}
于 2021-12-27T18:16:39.240 回答
0

@mickben 的回答略有变化。

我们将使用 Namespaces 对象来保存多个 Namespace.ID 实例。我们将把它作为环境对象注入 - 并提供一种配置预览的简单方法。

首先 - 命名空间包装器

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    
}

我为不同的对象使用不同的命名空间。

所以,一个带有图像和标题的项目......

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 作为图像和标题动画的“自然”ID。

请注意这里的预览是如何非常容易构建的.environmentObject(Namespaces())

如果我们使用 Namespaces() 在实际应用程序中创建命名空间,那么我们会收到警告。

在 View.body 之外读取命名空间属性。这将导致标识符永远不会匹配任何其他标识符。

根据您的设置 - 这可能不是真的,但我们可以通过使用显式初始化程序来解决

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 中,因为它将属性包装器和初始化器保持在一起。根据需要添加新的命名空间很容易。

于 2022-02-23T12:07:44.500 回答