Last active
          September 30, 2023 05:58 
        
      - 
      
 - 
        
Save jamesrochabrun/523a0ee19bfd2eb4854bb9748a697053 to your computer and use it in GitHub Desktop.  
    An animated Trailing text
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) | |
| struct CenteredLoadingText: View { | |
| private let mainText: String | |
| private let dots = [".", ".", "."] | |
| private let repeatInterval: TimeInterval | |
| private let accessibilityLabel: String | |
| private let loadingAnimation: Animation | |
| private let spacing: CGFloat | |
| /// https://www.hackingwithswift.com/books/ios-swiftui/triggering-events-repeatedly-using-a-timer | |
| private let timer: Publishers.Autoconnect<Timer.TimerPublisher> | |
| @State private var trailingText = "" | |
| @State private var currentIndex = 0 | |
| init( | |
| centerText: String, | |
| accessibilityLabel: String, | |
| repeatInterval: Double = 0.5, | |
| loadingAnimation: Animation = Animation.default, | |
| spacing: CGFloat = 0) | |
| { | |
| self.mainText = centerText | |
| self.accessibilityLabel = accessibilityLabel | |
| self.repeatInterval = repeatInterval | |
| self.loadingAnimation = loadingAnimation | |
| self.spacing = spacing | |
| timer = Timer.publish(every: repeatInterval, on: .main, in: .default).autoconnect() | |
| } | |
| var body: some View { | |
| FirstChildrenCenteredLayout(spacing: spacing) { | |
| Text(mainText) | |
| .border(.black) | |
| Text(trailingText) | |
| .border(.red) | |
| } | |
| .onReceive(timer) { _ in | |
| withAnimation(loadingAnimation) { | |
| if currentIndex < dots.count { | |
| trailingText += dots[currentIndex] | |
| currentIndex += 1 | |
| } else { | |
| currentIndex = 0 | |
| trailingText = "" | |
| } | |
| } | |
| } | |
| .onDisappear { | |
| timer.upstream.connect().cancel() | |
| } | |
| .accessibilityElement(children: .ignore) | |
| .accessibilityLabel(accessibilityLabel) | |
| } | |
| private struct FirstChildrenCenteredLayout: Layout { | |
| // MARK: Lifecycle | |
| init(spacing: CGFloat) { | |
| self.spacing = spacing | |
| } | |
| // MARK: Public | |
| public func sizeThatFits( | |
| proposal: ProposedViewSize, | |
| subviews: Subviews, | |
| cache _: inout Void) | |
| -> CGSize | |
| { | |
| // Return a size. | |
| assert(subviews.count == 2) | |
| let width = proposal.width ?? 0 | |
| // We assume texts has the same Font and number of lines is 1, so the first height should be enough. | |
| let height = subviews.first?.sizeThatFits(proposal).height ?? 0 | |
| return CGSize(width: width, height: height) | |
| } | |
| public func placeSubviews( | |
| in bounds: CGRect, | |
| proposal: ProposedViewSize, | |
| subviews: Subviews, | |
| cache _: inout Void) | |
| { | |
| // Place child views. | |
| assert(subviews.count == 2) | |
| let height = subviews.first?.sizeThatFits(proposal).height ?? 0 | |
| let combinedWidth = subviews.map { $0.sizeThatFits(proposal) }.reduce(0.0) { (result, size) in | |
| return result + size.width | |
| } | |
| let placementProposal = ProposedViewSize(width: combinedWidth, height: height) | |
| let firstSubView = subviews[0] | |
| let firstSubViewSize = firstSubView.sizeThatFits(proposal) | |
| let firstSubViewPoint = CGPoint(x: bounds.midX, y: bounds.midY) | |
| firstSubView.place(at: firstSubViewPoint, anchor: .center, proposal: placementProposal) | |
| let secondSubView = subviews[1] | |
| let secondSubViewPoint = CGPoint(x: firstSubViewPoint.x + firstSubViewSize.width / 2 + spacing, y: bounds.midY - firstSubView.sizeThatFits(proposal).height / 2) | |
| secondSubView.place(at: secondSubViewPoint, proposal: placementProposal) | |
| } | |
| // MARK: Private | |
| private let spacing: CGFloat | |
| } | |
| } | |
| struct ContentView: View { | |
| var body: some View { | |
| CenteredLoadingText( | |
| centerText: "Loading", | |
| accessibilityLabel: "Loading") | |
| } | |
| } | |
| #Preview { | |
| ContentView() | |
| } | 
      
      
  Author
  
  
      
          
      
      
            jamesrochabrun
  
      
      
      commented 
        Sep 30, 2023 
      
    
  

  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment