iOS SwiftUI Text跑馬燈(Marquee)

執行效果

Text基礎設定

Text("PPPPPPPP OOOOOOOOOO IIIIIIIIII UUUUUUUUUU YYYYYYYYYY")
     //縮放最小的大小
     .minimumScaleFactor(0.5)
     //最大兩行
     .lineLimit(2)
     //allowsTightening設定成true的時,適應文本大小
     //空間太小直接變...
     .allowsTightening(false)
     //多行皆以.center為對齊方式
     .multilineTextAlignment(.center)
     .foregroundColor(Color.white)
     .padding(.leading, 10)
     .padding(.trailing, 10)

Text文字大小讀取

//1.繼承String新增方式
 extension String {
     func widthOfString(usingFont font: UIFont) -> CGFloat {
         let fontAttributes = [NSAttributedString.Key.font: font]
         let size = self.size(withAttributes: fontAttributes)
         return size.width
     }

     func heightOfString(usingFont font: UIFont) -> CGFloat {
         let fontAttributes = [NSAttributedString.Key.font: font]
         let size = self.size(withAttributes: fontAttributes)
         return size.height
     }

     func sizeOfString(usingFont font: UIFont) -> CGSize {
         let fontAttributes = [NSAttributedString.Key.font: font]
         return self.size(withAttributes: fontAttributes)
     }
 }
 //2.讀取Text大小方式
 let width: CGFloat = String("1234567890abc")
     .widthOfString(usingFont: UIFont.systemFont(ofSize: 18))
 let height: CGFloat = String("1234567890abc")
     .heightOfString(usingFont: UIFont.systemFont(ofSize: 18))
 let size: CGSize = String("1234567890abc")
     .sizeOfString(usingFont: UIFont.systemFont(ofSize: 18))

Text跑馬燈

struct paramModel {
    var animation: Int = 0
    var textSize: CGFloat = 0
    var opacity: Double = 1
    var offsetXCurrent: CGFloat = 0
    var offsetXMax: CGFloat = 0
}

struct AMainView: View {
    @State var param = paramModel()
    let moveTime = 2.0//移動時間
    let lastStopTime = 1.0//移動到最後,停止時間
    let opacityTime = 0.1//停止後,隱藏文字動畫時間
    let hideTime = 0.6//動畫結束後,至初始化等待時間
    let firstStopTime = 1.0//初始化後,等待移動文字時間
    
    func getFirstStopTime() -> DispatchTime {
        return DispatchTime.now()+firstStopTime
    }
    
    func getMoveAndStopTime() -> DispatchTime {
        return DispatchTime.now()+moveTime+lastStopTime
    }
    
    func getMoveToEndTime() -> DispatchTime {
        return DispatchTime.now()+moveTime+lastStopTime+hideTime+opacityTime
    }
    var body: some View {
        return GeometryReader { geometry in
            let maxSize = geometry.size.width//最大文字範圍(避免變...)
            let limitSize = geometry.size.width / 2//要顯示文字範圍
            Group {
                Text("1234567890abcdefghiABCDEFGHIJKLMNOP")
                    .foregroundColor(.red)
                    .font(.system(size: 18))
                    .lineLimit(1)
                    .allowsTightening(true)
                    .background(ListenerView())
                    .onPreferenceChange(ViewSizeKey.self) { _ in
                        param.textSize = CGFloat(
                            String("1234567890abcdefghiABCDEFGHIJKLMNOP")
                                .widthOfString(usingFont: UIFont.systemFont(ofSize: 18))
                        )
                        withAnimation(Animation.linear) {
                            param.offsetXCurrent = limitSize-param.textSize
                            param.offsetXMax = limitSize-param.textSize
                        }
                    }
                    .offset(x:
                               (param.animation != 0 && param.textSize != 0) ?
                             param.offsetXCurrent : 0 ,
                            y: 0
                    )
                    .opacity(param.opacity)
                    .animation(
                     param.animation == 1 ?
                         Animation.linear(duration: moveTime) : param.animation == 2 ?
                         Animation.linear(duration: opacityTime) : nil
                    )
                    .onAnimationDataChange(for: param.offsetXCurrent) {
                        if param.offsetXMax == 0 {
                            return
                        }
                        if param.offsetXCurrent == 0 {
                            DispatchQueue.main.asyncAfter(
                                deadline:
                                    getFirstStopTime(),
                                execute: {
                                    withAnimation(Animation.default, {
                                    param.offsetXCurrent = param.offsetXMax
                                   })
                               })
                        } else if param.offsetXCurrent != 0  {
                            param.animation = 1
                            DispatchQueue.main.asyncAfter(
                                deadline:
                                    getMoveAndStopTime(),
                                execute: {
                                    param.animation = 2
                                    param.opacity = 0
                                })

                            DispatchQueue.main.asyncAfter(
                            deadline:
                                getMoveToEndTime(),
                            execute: {
                                param.animation = 0
                                withAnimation(Animation.default, {
                                    param.offsetXCurrent = 0
                                })
                                param.opacity = 1
                           })
                       }
                    }
                    .frame(width: maxSize, alignment: .leading)//這不設定,過長文字會變成...
           }.frame(width: limitSize, alignment: .leading)
           .mask (
            HStack(spacing: 0) {
                LinearGradient(
                    gradient: Gradient(
                        colors: [Color.black, Color.black]
                    ),
                    startPoint: .leading,
                    endPoint: .trailing
                )
            }
           ).frame(width: limitSize)
       }
    }
}

訂閱Codeilin的旅程,若有最新消息會通知。

廣告

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

WordPress.com.

向上 ↑

%d 位部落客按了讚: