【问题标题】:SWIFTUI Marquee when text not fit文本不适合时 SWIFTUI 选取框
【发布时间】:2020-07-02 09:49:36
【问题描述】:

我有文字,但不合适。当文本不适合我的默认框架时,我想使用选取框。

Text(self.viewModel.soundTrack.title)
    .font(.custom("Avenir Next Regular", size: 24))
    .multilineTextAlignment(.trailing)
    .lineLimit(1)
    .foregroundColor(.white)
    .fixedSize(horizontal: false, vertical: true)
    //.frame(width: 200.0, height: 30.0)

【问题讨论】:

标签: swiftui


【解决方案1】:

试试下面的代码......

在 MarqueeText.swift 中

import SwiftUI

struct MarqueeText: View {

    @State private var leftMost = false

    @State private var w: CGFloat = 0

    @State private var previousText: String = ""

    @State private var contentViewWidth: CGFloat = 0

    @State private var animationDuration: Double = 5

    @Binding var text : String

    var body: some View {
        let baseAnimation = Animation.linear(duration: self.animationDuration)//Animation duration
        let repeated = baseAnimation.repeatForever(autoreverses: false)
        return VStack(alignment:.center, spacing: 0) {
            GeometryReader { geometry in//geometry.size.width will provide container/superView width
                Text(self.text).font(.system(size: 24)).lineLimit(1).foregroundColor(.clear).fixedSize(horizontal: true, vertical: false).background(TextGeometry()).onPreferenceChange(WidthPreferenceKey.self, perform: {
                    self.w = $0
                    print("textWidth:\(self.w)")
                    print("geometry:\(geometry.size.width)")
                    self.contentViewWidth = geometry.size.width
                    if self.text.count != self.previousText.count && self.contentViewWidth < self.w {
                        let duration = self.w/50
                        print("duration:\(duration)")
                        self.animationDuration = Double(duration)
                        self.leftMost = true
                    } else {
                        self.animationDuration = 0.0
                    }
                    self.previousText = self.text
                    }).fixedSize(horizontal: false, vertical: true)// This Text is temp, will not be displayed in UI. Used to identify the width of the text.
                if self.animationDuration > 0.0 {
                    Text(self.text).font(.system(size: 24)).lineLimit(nil).foregroundColor(.green).fixedSize(horizontal: true, vertical: false).background(TextGeometry()).onPreferenceChange(WidthPreferenceKey.self, perform: { _ in
                                    if self.text.count != self.previousText.count && self.contentViewWidth < self.w {

                                    } else {
                                        self.leftMost = false
                                    }
                                    self.previousText = self.text
                        }).modifier(self.makeSlidingEffect().ignoredByLayout()).animation(repeated, value: self.leftMost).clipped(antialiased: true).offset(y: -8)//Text with animation
                }
                else {
                    Text(self.text).font(.system(size: 24)).lineLimit(1).foregroundColor(.blue).fixedSize(horizontal: true, vertical: false).background(TextGeometry()).fixedSize(horizontal: false, vertical: true).frame(maxWidth: .infinity, alignment: .center).offset(y: -8)//Text without animation
                }
            }
            }.fixedSize(horizontal: false, vertical: true).layoutPriority(1).frame(maxHeight: 50, alignment: .center).clipped()

    }


    func makeSlidingEffect() -> some GeometryEffect {
      return SlidingEffect(
        xPosition: self.leftMost ? -self.w : self.w,
        yPosition: 0).ignoredByLayout()
    }
}

struct MarqueeText_Previews: PreviewProvider {
    @State static var myCoolText = "myCoolText"
    static var previews: some View {
        MarqueeText(text: $myCoolText)
    }
}

struct SlidingEffect: GeometryEffect {
    var xPosition: CGFloat = 0
    var yPosition: CGFloat = 0

  var animatableData: CGFloat {
    get { return xPosition }
    set { xPosition = newValue }
  }

  func effectValue(size: CGSize) -> ProjectionTransform {
    let pt = CGPoint(
      x: xPosition,
      y: yPosition)
    return ProjectionTransform(CGAffineTransform(translationX: pt.x, y: pt.y)).inverted()
  }
}

struct TextGeometry: View {
    var body: some View {
        GeometryReader { geometry in
            return Rectangle().fill(Color.clear).preference(key: WidthPreferenceKey.self, value: geometry.size.width)
        }
    }
}

struct WidthPreferenceKey: PreferenceKey {
    static var defaultValue = CGFloat(0)

    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }

    typealias Value = CGFloat
}

struct MagicStuff: ViewModifier {
    func body(content: Content) -> some View {
        Group {
            content.alignmentGuide(.underlineLeading) { d in
                return d[.leading]
            }
        }
    }
}

extension HorizontalAlignment {
    private enum UnderlineLeading: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[.leading]
        }
    }
    static let underlineLeading = HorizontalAlignment(UnderlineLeading.self)
}

在您现有的 SwiftUI 结构中。 (下面的示例代码将检查3种情况1.空字符串,2.不需要选框的短字符串,3.长选框字符串)

@State var value = ""
@State var counter = 0

var body: some View {
    VStack {
        Spacer(minLength: 0)
        Text("Monday").background(Color.yellow)
        HStack {
            Spacer()
            VStack {
                Text("One").background(Color.blue)
            }
            VStack {
            MarqueeText(text: $value).background(Color.red).padding(.horizontal, 8).clipped()
            }
            VStack {
            Text("Two").background(Color.green)
            }
            Spacer()

        }
        Text("Tuesday").background(Color.gray)
        Spacer(minLength: 0)
        Button(action: {
            self.counter = self.counter + 1
            if (self.counter % 2 == 0) {
                self.value = "1Hello World! Hello World! Hello World! Hello World! Hello World!"
            } else {
                self.value = "1Hello World! Hello"
            }
        }) {
            Text("Button")
        }
        Spacer()
    }
}

【讨论】:

  • 我看到一个问题,在应用程序运行时更改文本时不要更改文本
  • @Vladikug 编辑了我的答案,请尝试一下。
  • 现在文字是改变它!但是动画消失了。文本是静态的
  • @Vladikug 仅限互联网。我有 iOS 原生应用开发经验(swift&objectiveC)。
  • 我认为 self.text.count != self.previousText.count 这行是不需要的,不是吗?
【解决方案2】:

安装https://github.com/SwiftUIKit/Marquee0.2.0以上 使用 Swift 包管理器并尝试下面的代码......

struct ContentView: View {
    var body: some View {
        Marquee {
            Text("Hello World!")
                .font(.system(size: 40))
        }
        // This is the key point.
        .marqueeWhenNotFit(true)
    }
}

当你不断增加文本的长度直到超过选取框的宽度时,选取框动画会自动开始。

【讨论】:

    【解决方案3】:

    我一直在寻找同样的东西,但我尝试的每个解决方案要么不符合我的规范,要么导致布局/渲染问题,尤其是在文本更改或刷新父视图时。我最终只是从头开始写一些东西。这是相当hack-y,但它现在似乎正在工作。我欢迎任何关于如何改进它的建议!

    import SwiftUI
    
    struct Marquee: View {
        
        @ObservedObject var controller:MarqueeController
        
        var body: some View {
            VStack {
                
                if controller.changing  {
                    
                    Text("")
                        .font(Font(controller.font))
                    
                } else {
                    
                    if !controller.shouldAnimate {
                        
                        Text(controller.text)
                            .font(Font(controller.font))
                        
                    } else {
                        
                        AnimatedText(controller: controller)
                        
                    }
                }
                
            }
            .onAppear() {
                
                self.controller.checkForAnimation()
                
            }
            .onReceive(controller.$text) {_ in
                
                self.controller.checkForAnimation()
                
            }
        }
    }
    
    struct AnimatedText: View {
        
        @ObservedObject var controller:MarqueeController
        
        var body: some View {
            
            Text(controller.text)
                .font(Font(controller.font))
                .lineLimit(1)
                .fixedSize()
                .offset(x: controller.animate ? controller.initialOffset - controller.offset : controller.initialOffset)
                .frame(width:controller.maxWidth)
                .mask(Rectangle())
            
            
        }
    }
    
    
    class MarqueeController:ObservableObject {
        
        @Published var text:String
        @Published var animate = false
        @Published var changing = true
        @Published var offset:CGFloat = 0
        @Published var initialOffset:CGFloat = 0
        
        var shouldAnimate:Bool {text.widthOfString(usingFont: font) > maxWidth}
        let font:UIFont
        var maxWidth:CGFloat
        var textDoubled = false
        let delay:Double
        let duration:Double
        
        
        init(text:String, maxWidth:CGFloat, font:UIFont = UIFont.systemFont(ofSize: 12), delay:Double = 1, duration:Double = 3) {
            
            self.text = text
            self.maxWidth = maxWidth
            self.font = font
            self.delay = delay
            self.duration = duration
            
            
        }
        
        func checkForAnimation() {
            
            if shouldAnimate  {
                
                let spacer = "    "
                
                if !textDoubled {
                    self.text += (spacer + self.text)
                    self.textDoubled = true
                }
                
                let textWidth = self.text.widthOfString(usingFont: font)
                
                self.initialOffset = (textWidth - maxWidth) / 2
                
                self.offset = (textWidth + spacer.widthOfString(usingFont: font)) / 2
                
            }
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                
                self.changing = false
                
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                    
                    withAnimation(Animation.linear(duration:self.duration).delay(self.delay).repeatForever(autoreverses: false)) {
                        
                        self.animate = self.shouldAnimate
                        
                    }
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多