SwiftUI - 滑动文本动画和定位
SwiftUI - Sliding Text animation and positioning
在学习更多 SwiftUI 的过程中,我仍然对将我的元素定位在 ZStack
中感到困惑。
目标是“简单”,我想要一个文本,如果文本太长,它会在定义的区域内滑动。
假设我有一个 50px 的区域,文本占用 100。我希望文本从右向左滑动,然后从左向右滑动。
目前,我的代码如下所示:
struct ContentView: View {
@State private var animateSliding: Bool = false
private let timer = Timer.publish(every: 1, on: .current, in: .common).autoconnect()
private let slideDuration: Double = 3
private let text: String = "Hello, World! My name is Oleg and I would like this text to slide!"
var body: some View {
GeometryReader(content: { geometry in
VStack(content: {
Text("Hello, World! My name is Oleg!")
.font(.system(size: 20))
.id("SlidingText-Animation")
.alignmentGuide(VerticalAlignment.center, computeValue: { [=10=][VerticalAlignment.center] })
.position(y: geometry.size.height / 2 + self.textSize(fromWidth: geometry.size.width).height / 2)
.fixedSize()
.background(Color.red)
.animation(Animation.easeInOut(duration: 2).repeatForever())
.position(x: self.animateSliding ? -self.textSize(fromWidth: geometry.size.width).width : self.textSize(fromWidth: geometry.size.width).width)
.onAppear(perform: {
self.animateSliding.toggle()
})
})
.background(Color.yellow)
.clipShape(Rectangle())
})
.frame(width: 200, height: 80)
}
func textSize(fromWidth width: CGFloat, fontName: String = "System Font", fontSize: CGFloat = UIFont.systemFontSize) -> CGSize {
let text: UILabel = .init()
text.text = self.text
text.numberOfLines = 0
text.font = UIFont.systemFont(ofSize: 20) // (name: fontName, size: fontSize)
text.lineBreakMode = .byWordWrapping
return text.sizeThatFits(CGSize.init(width: width, height: .infinity))
}
}
对于如何将文本在其父项中垂直居中并制作从正确位置开始的动画,您有什么建议吗?
感谢您以后的帮助,非常感谢!
编辑:
我重构了我的代码,并改变了一些我正在做的事情。
struct SlidingText: View {
let geometryProxy: GeometryProxy
@State private var animateSliding: Bool = false
private let timer = Timer.publish(every: 1, on: .current, in: .common).autoconnect()
private let slideDuration: Double = 3
private let text: String = "Hello, World! My name is Oleg and I would like this text to slide!"
var body: some View {
ZStack(alignment: .leading, content: {
Text(text)
.font(.system(size: 20))
// .lineLimit(1)
.id("SlidingText-Animation")
.fixedSize(horizontal: true, vertical: false)
.background(Color.red)
.animation(Animation.easeInOut(duration: slideDuration).repeatForever())
.offset(x: self.animateSliding ? -textSize().width : textSize().width)
.onAppear(perform: {
self.animateSliding.toggle()
})
})
.frame(width: self.geometryProxy.size.width, height: self.geometryProxy.size.height)
.clipShape(Rectangle())
.background(Color.yellow)
}
func textSize(fontName: String = "System Font", fontSize: CGFloat = 20) -> CGSize {
let text: UILabel = .init()
text.text = self.text
text.numberOfLines = 0
text.font = UIFont(name: fontName, size: fontSize)
text.lineBreakMode = .byWordWrapping
let textSize = text.sizeThatFits(CGSize(width: self.geometryProxy.size.width, height: .infinity))
print(textSize.width)
return textSize
}
}
struct ContentView: View {
var body: some View {
GeometryReader(content: { geometry in
SlidingText(geometryProxy: geometry)
})
.frame(width: 200, height: 40)
}
}
现在动画看起来很不错,除了左右两边都有填充,我不明白为什么。
Edit2:我还注意到通过将 text.font = UIFont.systemFont(ofSize: 20)
更改为 text.font = UIFont.systemFont(ofSize: 15)
可以使文本正确显示。我不明白 SwiftUI
或 UIKit
的系统字体是否有区别。不应该..
在我的案例中使用解决方案进行最终编辑:
struct SlidingText: View {
let geometryProxy: GeometryProxy
@Binding var text: String
@Binding var fontSize: CGFloat
@State private var animateSliding: Bool = false
private let timer = Timer.publish(every: 5, on: .current, in: .common).autoconnect()
private let slideDuration: Double = 3
var body: some View {
ZStack(alignment: .leading, content: {
VStack {
Text(text)
.font(.system(size: self.fontSize))
.background(Color.red)
}
.fixedSize()
.frame(width: geometryProxy.size.width, alignment: animateSliding ? .trailing : .leading)
.clipped()
.animation(Animation.linear(duration: slideDuration))
.onReceive(timer) { _ in
self.animateSliding.toggle()
}
})
.frame(width: self.geometryProxy.size.width, height: self.geometryProxy.size.height)
.clipShape(Rectangle())
.background(Color.yellow)
}
}
struct ContentView: View {
@State var text: String = "Hello, World! My name is Oleg and I would like this text to slide!"
@State var fontSize: CGFloat = 20
var body: some View {
GeometryReader(content: { geometry in
SlidingText(geometryProxy: geometry, text: self.$text, fontSize: self.$fontSize)
})
.frame(width: 400, height: 40)
.padding(0)
}
}
这是直观的结果。
这是一个可能的简单方法 - 这个想法就像更改容器中的文本对齐方式一样简单,其他任何内容都可以照常调整。
使用 Xcode 12 / iOS 14
准备和测试的演示
更新:用 Xcode 13.3 / iOS 15.4
重新测试
struct DemoSlideText: View {
let text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor"
@State private var go = false
var body: some View {
VStack {
Text(text)
}
.fixedSize()
.frame(width: 300, alignment: go ? .trailing : .leading)
.clipped()
.onAppear { self.go.toggle() }
.animation(Animation.linear(duration: 5).repeatForever(autoreverses: true), value: go)
}
}
一种方法是使用“PreferenceKey”协议,结合“preference”和“onPreferenceChange”修饰符。
这是 SwiftUI 将数据从子视图发送到父视图的方式。
代码:
struct ContentView: View {
@State private var offset: CGFloat = 0.0
private let text: String = "Hello, World! My name is Oleg and I would like this text to slide!"
var body: some View {
// Capturing the width of the screen
GeometryReader { screenGeo in
ZStack {
Color.yellow
.frame(height: 50)
Text(text)
.fixedSize()
.background(
// Capturing the width of the text
GeometryReader { geo in
Color.red
// Sending width difference to parent View
.preference(key: MyTextPreferenceKey.self, value: screenGeo.size.width - geo.frame(in: .local).width
)
}
)
}
.offset(x: self.offset)
// Receiving width from child view
.onPreferenceChange(MyTextPreferenceKey.self, perform: { width in
withAnimation(Animation.easeInOut(duration: 1).repeatForever()) {
self.offset = width
}
})
}
}
}
// MARK: PreferenceKey
struct MyTextPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0.0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
结果:
在你们的启发下,我最终只使用动画 functions/properties。
import SwiftUI
struct SlidingText: View {
let geometryProxy: GeometryProxy
@Binding var text: String
let font: Font
@State private var animateSliding: Bool = false
private let slideDelay: Double = 3
private let slideDuration: Double = 6
private var isTextLargerThanView: Bool {
if text.size(forWidth: geometryProxy.size.width, andFont: font).width < geometryProxy.size.width {
return false
}
return true
}
var body: some View {
ZStack(alignment: .leading, content: {
VStack(content: {
Text(text)
.font(self.font)
.foregroundColor(.white)
})
.id("SlidingText-Animation")
.fixedSize()
.animation(isTextLargerThanView ? Animation.linear(duration: slideDuration).delay(slideDelay).repeatForever(autoreverses: true) : nil)
.frame(width: geometryProxy.size.width,
alignment: isTextLargerThanView ? (animateSliding ? .trailing : .leading) : .center)
.onAppear(perform: {
self.animateSliding.toggle()
})
})
.clipped()
}
}
我称 SlidingView
GeometryReader(content: { geometry in
SlidingText(geometryProxy: geometry,
text: self.$playerViewModel.seasonByline,
font: .custom("FoundersGrotesk-RegularRegular", size: 15))
})
.frame(height: 20)
.fixedSize(horizontal: false, vertical: true)
再次感谢您的帮助!
在学习更多 SwiftUI 的过程中,我仍然对将我的元素定位在 ZStack
中感到困惑。
目标是“简单”,我想要一个文本,如果文本太长,它会在定义的区域内滑动。 假设我有一个 50px 的区域,文本占用 100。我希望文本从右向左滑动,然后从左向右滑动。
目前,我的代码如下所示:
struct ContentView: View {
@State private var animateSliding: Bool = false
private let timer = Timer.publish(every: 1, on: .current, in: .common).autoconnect()
private let slideDuration: Double = 3
private let text: String = "Hello, World! My name is Oleg and I would like this text to slide!"
var body: some View {
GeometryReader(content: { geometry in
VStack(content: {
Text("Hello, World! My name is Oleg!")
.font(.system(size: 20))
.id("SlidingText-Animation")
.alignmentGuide(VerticalAlignment.center, computeValue: { [=10=][VerticalAlignment.center] })
.position(y: geometry.size.height / 2 + self.textSize(fromWidth: geometry.size.width).height / 2)
.fixedSize()
.background(Color.red)
.animation(Animation.easeInOut(duration: 2).repeatForever())
.position(x: self.animateSliding ? -self.textSize(fromWidth: geometry.size.width).width : self.textSize(fromWidth: geometry.size.width).width)
.onAppear(perform: {
self.animateSliding.toggle()
})
})
.background(Color.yellow)
.clipShape(Rectangle())
})
.frame(width: 200, height: 80)
}
func textSize(fromWidth width: CGFloat, fontName: String = "System Font", fontSize: CGFloat = UIFont.systemFontSize) -> CGSize {
let text: UILabel = .init()
text.text = self.text
text.numberOfLines = 0
text.font = UIFont.systemFont(ofSize: 20) // (name: fontName, size: fontSize)
text.lineBreakMode = .byWordWrapping
return text.sizeThatFits(CGSize.init(width: width, height: .infinity))
}
}
对于如何将文本在其父项中垂直居中并制作从正确位置开始的动画,您有什么建议吗?
感谢您以后的帮助,非常感谢!
编辑:
我重构了我的代码,并改变了一些我正在做的事情。
struct SlidingText: View {
let geometryProxy: GeometryProxy
@State private var animateSliding: Bool = false
private let timer = Timer.publish(every: 1, on: .current, in: .common).autoconnect()
private let slideDuration: Double = 3
private let text: String = "Hello, World! My name is Oleg and I would like this text to slide!"
var body: some View {
ZStack(alignment: .leading, content: {
Text(text)
.font(.system(size: 20))
// .lineLimit(1)
.id("SlidingText-Animation")
.fixedSize(horizontal: true, vertical: false)
.background(Color.red)
.animation(Animation.easeInOut(duration: slideDuration).repeatForever())
.offset(x: self.animateSliding ? -textSize().width : textSize().width)
.onAppear(perform: {
self.animateSliding.toggle()
})
})
.frame(width: self.geometryProxy.size.width, height: self.geometryProxy.size.height)
.clipShape(Rectangle())
.background(Color.yellow)
}
func textSize(fontName: String = "System Font", fontSize: CGFloat = 20) -> CGSize {
let text: UILabel = .init()
text.text = self.text
text.numberOfLines = 0
text.font = UIFont(name: fontName, size: fontSize)
text.lineBreakMode = .byWordWrapping
let textSize = text.sizeThatFits(CGSize(width: self.geometryProxy.size.width, height: .infinity))
print(textSize.width)
return textSize
}
}
struct ContentView: View {
var body: some View {
GeometryReader(content: { geometry in
SlidingText(geometryProxy: geometry)
})
.frame(width: 200, height: 40)
}
}
现在动画看起来很不错,除了左右两边都有填充,我不明白为什么。
Edit2:我还注意到通过将 text.font = UIFont.systemFont(ofSize: 20)
更改为 text.font = UIFont.systemFont(ofSize: 15)
可以使文本正确显示。我不明白 SwiftUI
或 UIKit
的系统字体是否有区别。不应该..
在我的案例中使用解决方案进行最终编辑:
struct SlidingText: View {
let geometryProxy: GeometryProxy
@Binding var text: String
@Binding var fontSize: CGFloat
@State private var animateSliding: Bool = false
private let timer = Timer.publish(every: 5, on: .current, in: .common).autoconnect()
private let slideDuration: Double = 3
var body: some View {
ZStack(alignment: .leading, content: {
VStack {
Text(text)
.font(.system(size: self.fontSize))
.background(Color.red)
}
.fixedSize()
.frame(width: geometryProxy.size.width, alignment: animateSliding ? .trailing : .leading)
.clipped()
.animation(Animation.linear(duration: slideDuration))
.onReceive(timer) { _ in
self.animateSliding.toggle()
}
})
.frame(width: self.geometryProxy.size.width, height: self.geometryProxy.size.height)
.clipShape(Rectangle())
.background(Color.yellow)
}
}
struct ContentView: View {
@State var text: String = "Hello, World! My name is Oleg and I would like this text to slide!"
@State var fontSize: CGFloat = 20
var body: some View {
GeometryReader(content: { geometry in
SlidingText(geometryProxy: geometry, text: self.$text, fontSize: self.$fontSize)
})
.frame(width: 400, height: 40)
.padding(0)
}
}
这是直观的结果。
这是一个可能的简单方法 - 这个想法就像更改容器中的文本对齐方式一样简单,其他任何内容都可以照常调整。
使用 Xcode 12 / iOS 14
准备和测试的演示更新:用 Xcode 13.3 / iOS 15.4
重新测试struct DemoSlideText: View {
let text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor"
@State private var go = false
var body: some View {
VStack {
Text(text)
}
.fixedSize()
.frame(width: 300, alignment: go ? .trailing : .leading)
.clipped()
.onAppear { self.go.toggle() }
.animation(Animation.linear(duration: 5).repeatForever(autoreverses: true), value: go)
}
}
一种方法是使用“PreferenceKey”协议,结合“preference”和“onPreferenceChange”修饰符。
这是 SwiftUI 将数据从子视图发送到父视图的方式。
代码:
struct ContentView: View {
@State private var offset: CGFloat = 0.0
private let text: String = "Hello, World! My name is Oleg and I would like this text to slide!"
var body: some View {
// Capturing the width of the screen
GeometryReader { screenGeo in
ZStack {
Color.yellow
.frame(height: 50)
Text(text)
.fixedSize()
.background(
// Capturing the width of the text
GeometryReader { geo in
Color.red
// Sending width difference to parent View
.preference(key: MyTextPreferenceKey.self, value: screenGeo.size.width - geo.frame(in: .local).width
)
}
)
}
.offset(x: self.offset)
// Receiving width from child view
.onPreferenceChange(MyTextPreferenceKey.self, perform: { width in
withAnimation(Animation.easeInOut(duration: 1).repeatForever()) {
self.offset = width
}
})
}
}
}
// MARK: PreferenceKey
struct MyTextPreferenceKey: PreferenceKey {
static var defaultValue: CGFloat = 0.0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
结果:
在你们的启发下,我最终只使用动画 functions/properties。
import SwiftUI
struct SlidingText: View {
let geometryProxy: GeometryProxy
@Binding var text: String
let font: Font
@State private var animateSliding: Bool = false
private let slideDelay: Double = 3
private let slideDuration: Double = 6
private var isTextLargerThanView: Bool {
if text.size(forWidth: geometryProxy.size.width, andFont: font).width < geometryProxy.size.width {
return false
}
return true
}
var body: some View {
ZStack(alignment: .leading, content: {
VStack(content: {
Text(text)
.font(self.font)
.foregroundColor(.white)
})
.id("SlidingText-Animation")
.fixedSize()
.animation(isTextLargerThanView ? Animation.linear(duration: slideDuration).delay(slideDelay).repeatForever(autoreverses: true) : nil)
.frame(width: geometryProxy.size.width,
alignment: isTextLargerThanView ? (animateSliding ? .trailing : .leading) : .center)
.onAppear(perform: {
self.animateSliding.toggle()
})
})
.clipped()
}
}
我称 SlidingView
GeometryReader(content: { geometry in
SlidingText(geometryProxy: geometry,
text: self.$playerViewModel.seasonByline,
font: .custom("FoundersGrotesk-RegularRegular", size: 15))
})
.frame(height: 20)
.fixedSize(horizontal: false, vertical: true)
再次感谢您的帮助!