A first look at matchedGeometryEffect

栏目: IT技术 · 发布时间: 5年前

内容简介:This article covers beta technology (iOS 14 and Xcode 12) which might subject to change in the future.After watchingSwiftUI already interpolates view's property for us without the need for

This article covers beta technology (iOS 14 and Xcode 12) which might subject to change in the future.

After watching What's new in SwiftUI , one feature the caught my attention is matchedGeometryEffect . It is a new SwiftUI effect which can interpolate position and size between two views. Let's see what we can do with it.

A first look at matchedGeometryEffect
matchedGeometryEffect is WWDC20's session

SwiftUI already interpolates view's property for us without the need for matchedGeometryEffect . In the following example, we can animate a rectangle size with a tap gesture.

struct ContentView: View {
    @State private var isExpanded = false
    
    var body: some View {        
        RoundedRectangle(cornerRadius: 10)
            .foregroundColor(Color.pink)
            .frame(width: isExpanded ? 100: 60, height: isExpanded ? 100: 60)
            .onTapGesture {
            withAnimation {
                isExpanded.toggle()
            }
        }
        
    }
}

Here is the result:

A first look at matchedGeometryEffect
Basic SwiftUI animation

What is matchedGeometryEffect for?

SwiftUI interpolates a value changes in view when an animation happens. In our previous example, we change frame value based on isExpanded state. This is great most of the time, but there would be a time when just value change is not enough or not possible to serve your layout. Let's say you want to change your layout from HStack to VStack .

struct ContentView: View {
    @State private var isExpanded = false
    
    var body: some View {
        Group() {
            if isExpanded {
                VStack {
                    RoundedRectangle(cornerRadius: 10)
                        .foregroundColor(Color.pink)
                        .frame(width: 60, height: 60)
                    Text("Hello SwiftUI!").fontWeight(.semibold)
                }
            } else {
                HStack {
                    Text("Hello SwiftUI!").fontWeight(.semibold)
                    RoundedRectangle(cornerRadius: 10)
                        .foregroundColor(Color.pink)
                        .frame(width: 60, height: 60)
                }
            }
        }.onTapGesture {
            withAnimation {
                isExpanded.toggle()
            }
        }
    }
}

Because what SwiftUI sees here is just showing and hiding of different views. The best SwiftUI can do is fade in and out between two views.

A first look at matchedGeometryEffect
SwiftUI use fade in and out for a show and hide

This kind of inter-view animation is where matchedGeometryEffect comes into play. matchedGeometryEffect can animate position and size between two views. What we need to do is link two views together. Here is how we do it.

struct ContentView: View {
    @State private var isExpanded = false
    @Namespace private var namespace // <1>
    
    var body: some View {
        Group() {
            if isExpanded {
                VStack {
                    RoundedRectangle(cornerRadius: 10)
                        .foregroundColor(Color.pink)
                        .frame(width: 60, height: 60)
                        .matchedGeometryEffect(id: "rect", in: namespace) // <2>
                    Text("Hello SwiftUI!").fontWeight(.semibold)
                        .matchedGeometryEffect(id: "text", in: namespace) // <3>
                }
            } else {
                HStack {
                    Text("Hello SwiftUI!").fontWeight(.semibold)
                        .matchedGeometryEffect(id: "text", in: namespace) // <4>
                    RoundedRectangle(cornerRadius: 10)
                        .foregroundColor(Color.pink)
                        .frame(width: 60, height: 60)
                        .matchedGeometryEffect(id: "rect", in: namespace) // <5>
                }
            }
        }.onTapGesture {
            withAnimation {
                isExpanded.toggle()
            }
        }
    }
}

<1> First, we need to define a namespace, a new property wrapper.

<2>, <5> We link two rectangle views together by specified the same id to .matchedGeometryEffect(id: "rect", in: namespace) .

<3>, <4> We link two texts together by specified the same id to .matchedGeometryEffect(id: "text", in: namespace) .

With these extra lines of code, our animation can interpolate nicely. It no longer treats our animation as a show and hides of views anymore, but a change of position and size of the same view.

A first look at matchedGeometryEffect
matchedGeometryEffect map animation between two views

Since it is still in beta, I'm not sure whether this is a limitation or a bug. I will update once we have a final release. If it is my misunderstanding on the API, please let me know on Twitter (DM is open).

Size is not interpolating nicely

Not like basic size change animation, matchedGeometryEffect can't animate size change properly.

A first look at matchedGeometryEffect
Basic SwiftUI size change animation

Instead of interpolating size from source to destination, it just uses fades in and out (I also change color to make it more visible).

A first look at matchedGeometryEffect
Size not interpolate nicely with matchedgeometryeffect

Work only on the same namespace

Since @Namespace is a required parameter for matchedGeometryEffect and I can't find a way to pass this around, that's mean everything must happen on the same struct . So, we can't extract any views to their own struct.

We can't extract our animated views to VerticalView and HorizontalView because we can't pass @Namespace into those views.

struct ContentViewNameSpace: View {
    @State private var isExpanded = false
    @Namespace private var namespace
    
    var body: some View {
        Group() {
            if isExpanded {
                VerticalView()
            } else {
                HorizontalView()
            }
        }.onTapGesture {
            withAnimation {
                isExpanded.toggle()
            }
        }
    }
}

struct VerticalView: View {
    var body: some View {
        VStack {
            RoundedRectangle(cornerRadius: 10)
                .foregroundColor(Color.pink)
                .frame(width: 60, height: 60)
            Text("Hello SwiftUI!").fontWeight(.semibold)
        }
    }
}

struct HorizontalView: View {
    var body: some View {
        HStack {
            Text("Hello SwiftUI!").fontWeight(.semibold)
            RoundedRectangle(cornerRadius: 10)
                .foregroundColor(Color.pink)
                .frame(width: 60, height: 60)
        }
    }
}

Declare a property wrapper at the top-level also not supported. So, something like this won't work.

@Namespace private var namespace

struct ContentViewNameSpace: View {
    @State private var isExpanded = false
    
    ...
}

struct VerticalView: View {
    ...
}

struct HorizontalView: View {
    ...
}

Not work with modal

I expected this to work, but no luck.

struct ContentView: View {
    @Namespace private var namespace
    @State private var isExpanded = false
    
    var body: some View {

        HStack {
            Text("Hello SwiftUI!").fontWeight(.semibold)
                .matchedGeometryEffect(id: "text", in: namespace)
            RoundedRectangle(cornerRadius: 10)
                .foregroundColor(Color.blue)
                .frame(width: 60, height: 60)
                .matchedGeometryEffect(id: "rect", in: namespace, properties: .frame)
        }.onTapGesture {
            withAnimation {
              isExpanded.toggle()
            }
        }.sheet(isPresented: $isExpanded) {
            VStack {
                RoundedRectangle(cornerRadius: 10)
                    .foregroundColor(Color.pink)
                    .frame(width: 100, height: 100)
                    .matchedGeometryEffect(id: "rect", in: namespace, properties: .frame)
                Text("Hello SwiftUI!").fontWeight(.semibold)
                    .matchedGeometryEffect(id: "text", in: namespace)
            }
        }

    }
}

This doesn't work, and the destination view gets pushed with the default animation.

struct ContentView: View {
    @Namespace private var namespace
    @State private var isExpanded = false
    
    var body: some View {        
        NavigationView {
            List {
                NavigationLink(destination: VStack {
                    RoundedRectangle(cornerRadius: 10)
                        .foregroundColor(Color.pink)
                        .frame(width: 100, height: 100)
                        .matchedGeometryEffect(id: "rect", in: namespace, properties: .frame)
                    Text("Hello SwiftUI!").fontWeight(.semibold)
                        .matchedGeometryEffect(id: "text", in: namespace)
                }) {
                    HStack {
                        Text("Hello SwiftUI!").fontWeight(.semibold)
                            .matchedGeometryEffect(id: "text", in: namespace)
                        RoundedRectangle(cornerRadius: 10)
                            .foregroundColor(Color.blue)
                            .frame(width: 60, height: 60)
                            .matchedGeometryEffect(id: "rect", in: namespace, properties: .frame)
                    }
                }
                
            }
        }
        
    }
}

My first thought is this matchedGeometryEffect is going to help me do all hero transition effects for modal and navigation view, but it turned out it is not that powerful (at least in this beta version). But this is still in beta, so I hope the animation is getting better when it release (at least for size transition). If they can do this right, a transition between modal and navigation shouldn't be that far.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

自品牌

自品牌

[美] 丹·斯柯伯尔(Dan Schawbel) / 佘卓桓 / 湖南文艺出版社 / 2016-1-1 / 39.80元

什么是自品牌?如何利用新媒体推广自己?如何放大自己的职业优势? 细化到如何巩固“弱联系”人脉?如何在团队里合作与生存?如何开创自己的事业?这些都是职场人不得不面临的问题,但少有人告诉你答案,你需要利用书里分享的高效方法独辟蹊径,把自己变成职场里高性价比的人才。这是一本教你利用新型社交媒体开发职业潜能的自我管理读本,不管你是新人还是老鸟,都可以通过打造自品牌在职场中脱颖而出。如果不甘平庸,就亮......一起来看看 《自品牌》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具