I used matchedGeometryEffect to "link" the two view's size and position together.
But I tried to use Textfield and changed the timing of triggering the animation to focus. When the keyboard pops up, the animation does not execute smoothly.
How to make the elements in the view slide smoothly? (position1 moves smoothly to position2)
struct ContentView: View {
@State private var text = ""
@Namespace var ns
@FocusState private var isFocused: Bool
var body: some View {
ZStack {
Color.clear
.contentShape(Rectangle())
.onTapGesture {
isFocused = false
}
VStack {
HStack {
TextField("placeholder", text: $text, axis: .vertical)
.focused($isFocused)
.frame(minHeight: 28)
.background(Color.red)
if !isFocused {
Text("xxxxxx") // position1
.background(Color.blue)
.matchedGeometryEffect(id: "xxxxxx", in: ns)
}
}
if isFocused {
HStack {
Spacer()
Text("xxxxxx") // position2
.background(Color.blue)
.matchedGeometryEffect(id: "xxxxxx", in: ns)
}
}
}
.background(Color.yellow)
.animation(.easeInOut(duration: 1), value: isFocused)
}
.padding()
}
}
The way you are using .matchedGeometryEffect
is to switch between two separate versions of the blue view. This means there are also transitions involved.
It works more smoothly if you have a single view for the blue view, which is shown as an overlay. Then use hidden placeholders as the source for the .matchedGeometryEffect
.
The (single) blue view then moves between the two locations, so no transitions are involved.
It seems that the overlay needs its own .animation
modifier too.
Here is the updated example with the changes applied:
VStack {
HStack {
TextField("placeholder", text: $text, axis: .vertical)
.focused($isFocused)
.frame(minHeight: 28)
.background(Color.red)
if !isFocused {
Text("xxxxxx") // position1
.hidden() // 👈 added
// .background(Color.blue) // 👈 not needed
.matchedGeometryEffect(id: "xxxxxx", in: ns)
}
}
if isFocused {
HStack {
Spacer()
Text("xxxxxx") // position2
.hidden() // 👈 added
// .background(Color.blue) // 👈 not needed
.matchedGeometryEffect(id: "xxxxxx", in: ns)
}
}
}
.overlay { // 👈 Show the visible version of the blue view as an overlay
Text("xxxxxx")
.background(Color.blue)
.matchedGeometryEffect(id: "xxxxxx", in: ns, isSource: false)
.animation(.easeInOut(duration: 1), value: isFocused)
}
.background(Color.yellow)
.animation(.easeInOut(duration: 1), value: isFocused)
EDIT You were saying in a comment that the animation of the blue text sometimes lags the yellow rectangle when tapping very quickly between Textfield
and background.
I tried adding alignment: .top
to the ZStack
to see if this would help to reduce the vertical movement. It actually made the problem worse, the blue text then jumps between positions.
Applying the animation to the overlay via .transaction
modifier seems to work better in this case:
ZStack(alignment: .top) { // 👈 alignment added
Color.clear
// ... modifiers as before
VStack {
// ... content as before
}
.overlay {
Text("xxxxxx")
.background(Color.blue)
.matchedGeometryEffect(id: "xxxxxx", in: ns, isSource: false)
.transaction { trans in // 👈 .animation replaced with .transaction
trans.animation = .easeInOut
}
}
.background(Color.yellow)
.animation(.easeInOut, value: isFocused) // 👈 duration removed
}
.padding()