【问题标题】:SwiftUI - Sliding Text animation and positioningSwiftUI - 滑动文本动画和定位
【发布时间】:2020-09-03 15:06:47
【问题描述】:

在我进一步了解 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: { $0[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) 可以使文本正确匹配。我不明白SwiftUIUIKit 的系统字体是否有区别。它不应该..

在我的情况下使用解决方案进行最终编辑:

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)

    }
}

这是视觉上的结果。

【问题讨论】:

  • 使 GeometryReader 更大。它使动画具有更多的填充...

标签: swift swiftui uiviewanimation swiftui-animation


【解决方案1】:

这是一种可能的简单方法 - 这个想法就像更改容器中的文本对齐一样简单,其他任何东西都可以像往常一样进行调整。

使用 Xcode 12 / iOS 14 准备和测试的演示

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))
    }
}

【讨论】:

  • 哇,这让它变得容易多了!!谢谢!你知道为什么当你向左或向右旋转屏幕时,它会使整个文本框在前一个位置和新位置之间移动吗?
  • 好的,我通过调整您向我展示的内容和我自己的代码使其正常工作。我在原始帖子中分享了代码。非常感谢您的帮助@Asperi!真的很感激!
  • 开明的缩进。
【解决方案2】:

一种方法是使用“PreferenceKey”协议,结合“preference”和“onPreferenceChange”修饰符。

这是 SwiftUI 将数据从子 View 发送到父 View 的方式。

代码:

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()
    }
}

结果:

【讨论】:

  • 超级有趣的方法!!感谢你的分享! :D
【解决方案3】:

在你们的一些启发下,我最终通过仅使用动画函数/属性来做一些更简单的事情。

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)

再次感谢您的帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-13
    • 1970-01-01
    • 2017-06-30
    • 1970-01-01
    • 2020-01-09
    • 2022-07-21
    • 1970-01-01
    相关资源
    最近更新 更多