SwiftUI 中的动画文本
Animating Text in SwiftUI
SwiftUI 具有出色的动画功能,但它处理 Text
视图内容变化的方式存在问题。它为文本框的变化设置动画,但在没有动画的情况下立即更改文本。因此,当 Text
视图的内容变长时,动画过渡会导致出现省略号 (...),直到文本框达到其全宽。例如,在这个小应用程序中,按下 Toggle 按钮可在较短和较长文本之间切换:
代码如下:
import SwiftUI
struct ContentView: View {
@State var shortString = true
var body: some View {
VStack {
Text(shortString ? "This is short." : "This is considerably longer.").font(.title)
.animation(.easeInOut(duration:1.0))
Button(action: {self.shortString.toggle()}) {
Text("Toggle").padding()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
问题是:如何避免省略号?当将一个字符的字符串动画化为两个字符的字符串时,情况更糟,因为短字符串在动画化为较长的字符串时完全被省略号取代。
一种可能性是通过添加修饰符(例如,.id(self.shortString ? 0 : 1)
,然后添加 .transition()
修饰符,为处于一种或另一种状态的视图分配一个单独的 ID。这会将文本视为前后两个不同的视图。不幸的是,在我的例子中,我需要在更改期间移动文本位置,而不同的 id 使动画无法实现。
我想解决方案是创造性地使用 AnimatableData
。有什么想法吗?
这是一个可能的方法的演示(有问题 - 您可以将其重新设计为扩展、修改器或单独的视图)
测试 Xcode 11.4 / iOS 13.4
struct ContentView: View {
@State var shortString = true
var body: some View {
VStack {
if shortString {
Text("This is short.").font(.title).fixedSize()
.transition(AnyTransition.opacity.animation(.easeInOut(duration:1.0)))
}
if !shortString {
Text("This is considerably longer.").font(.title).fixedSize()
.transition(AnyTransition.opacity.animation(.easeInOut(duration:1.0)))
}
Button(action: {self.shortString.toggle()}) {
Text("Toggle").padding()
}
}
}
}
Any suggestions for shrinking an animated gif's dimensions?
I use this way:
- decrease zoom of Preview to 75% (or resize window of Simulator)
- use QuickTimePlayer region-based Screen Recording
- use https://ezgif.com/video-to-gif for converting to GIF
如果将 .animation(nil)
添加到文本对象定义中,则内容将在值之间直接更改,避免省略号。
但是,这可能会阻止文本位置的动画,您也提到想同时这样做。
您可以在0.1秒后将一个字符一个一个地添加到带有动画的字符串中,但请记住在添加字符时禁用按钮切换,如下所示:
代码:
public struct TextAnimation: View {
public init(){ }
@State var text: String = ""
@State var toggle = false
public var body: some View {
VStack{
Text(text).animation(.spring())
HStack {
Button {
toggle.toggle()
} label: {
Text("Toggle")
}
}.padding()
}.onChange(of: toggle) { toggle in
if toggle {
text = ""
"This is considerably longer.".enumerated().forEach { index, character in
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.1) {
text += String(character)
}
}
} else {
text = "This is short."
}
}
}
}
SwiftUI 具有出色的动画功能,但它处理 Text
视图内容变化的方式存在问题。它为文本框的变化设置动画,但在没有动画的情况下立即更改文本。因此,当 Text
视图的内容变长时,动画过渡会导致出现省略号 (...),直到文本框达到其全宽。例如,在这个小应用程序中,按下 Toggle 按钮可在较短和较长文本之间切换:
代码如下:
import SwiftUI
struct ContentView: View {
@State var shortString = true
var body: some View {
VStack {
Text(shortString ? "This is short." : "This is considerably longer.").font(.title)
.animation(.easeInOut(duration:1.0))
Button(action: {self.shortString.toggle()}) {
Text("Toggle").padding()
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
问题是:如何避免省略号?当将一个字符的字符串动画化为两个字符的字符串时,情况更糟,因为短字符串在动画化为较长的字符串时完全被省略号取代。
一种可能性是通过添加修饰符(例如,.id(self.shortString ? 0 : 1)
,然后添加 .transition()
修饰符,为处于一种或另一种状态的视图分配一个单独的 ID。这会将文本视为前后两个不同的视图。不幸的是,在我的例子中,我需要在更改期间移动文本位置,而不同的 id 使动画无法实现。
我想解决方案是创造性地使用 AnimatableData
。有什么想法吗?
这是一个可能的方法的演示(有问题 - 您可以将其重新设计为扩展、修改器或单独的视图)
测试 Xcode 11.4 / iOS 13.4
struct ContentView: View {
@State var shortString = true
var body: some View {
VStack {
if shortString {
Text("This is short.").font(.title).fixedSize()
.transition(AnyTransition.opacity.animation(.easeInOut(duration:1.0)))
}
if !shortString {
Text("This is considerably longer.").font(.title).fixedSize()
.transition(AnyTransition.opacity.animation(.easeInOut(duration:1.0)))
}
Button(action: {self.shortString.toggle()}) {
Text("Toggle").padding()
}
}
}
}
Any suggestions for shrinking an animated gif's dimensions?
I use this way:
- decrease zoom of Preview to 75% (or resize window of Simulator)
- use QuickTimePlayer region-based Screen Recording
- use https://ezgif.com/video-to-gif for converting to GIF
如果将 .animation(nil)
添加到文本对象定义中,则内容将在值之间直接更改,避免省略号。
但是,这可能会阻止文本位置的动画,您也提到想同时这样做。
您可以在0.1秒后将一个字符一个一个地添加到带有动画的字符串中,但请记住在添加字符时禁用按钮切换,如下所示:
代码:
public struct TextAnimation: View {
public init(){ }
@State var text: String = ""
@State var toggle = false
public var body: some View {
VStack{
Text(text).animation(.spring())
HStack {
Button {
toggle.toggle()
} label: {
Text("Toggle")
}
}.padding()
}.onChange(of: toggle) { toggle in
if toggle {
text = ""
"This is considerably longer.".enumerated().forEach { index, character in
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.1) {
text += String(character)
}
}
} else {
text = "This is short."
}
}
}
}