This enables infinite loop animations in Widget.
And this library is implemented with reference to brycebostwick/WidgetAnimation.
- Development with Xcode 16.4+
- Written in Swift 6.1
- Compatible with iOS 18.0+
import AnimationLimitBreaker
import SwiftUI
import WidgetKit
structSampleEntry:TimelineEntry{vardate:Date}structSampleProvider:TimelineProvider{func placeholder(in context:Context)->SampleEntry{SampleEntry(date:.now)}func getSnapshot(in context:Context, completion:@escaping(SampleEntry)->()){completion(SampleEntry(date:.now))}func getTimeline(in context:Context, completion:@escaping(Timeline<Entry>)->()){completion(Timeline(entries:[SampleEntry(date:.now)], policy:.never))}}structSampleWidgetEntryView:View{varentry:SampleProvider.Entryvarimages:[Image]init(entry:SampleProvider.Entry){self.entry = entry
self.images =[0,33,50,67,100,67,50,33].map{ i inImage(systemName:"gauge.with.dots.needle.\(i)percent")}}varbody:someView{GeometryReader{ proxy in
// Use ForceAnimatingView in the widget.
// The provided image will loop playback at 2-second intervals.
ForceAnimatingView(
size:min(proxy.size.width, proxy.size.height),
images: images
).frame(maxWidth:.infinity, alignment:.center)}}}structSampleWidget:Widget{letkind:String="SampleWidget"init(){
// As a preparatory step, you need to register the custom font.
AnimationLimitBreaker.registerFont()}varbody:someWidgetConfiguration{StaticConfiguration(kind: kind, provider:SampleProvider()){ entry inSampleWidgetEntryView(entry: entry).containerBackground(.fill.tertiary, for:.widget)}.configurationDisplayName("Sample Widget").description("This is a sample widget.")}}@mainstructSampleWidgetBundle:WidgetBundle{varbody:someWidget{SampleWidget()}}First, the only component in Widgets that can animate infinitely is a timer using Text (e.g., Text(date: .now, style: .timer)).
Therefore, we take full advantage of this Text-based timer to implement frame-by-frame animation.
Since Text allows custom fonts, we apply a specially designed font in which odd values render as transparent and even values render as black squares. This creates a view that blinks between transparent and black every second.
Next, by overlapping and slightly offsetting this view in time and using it as a mask, the black square will only appear during the intervals when all layers are non-transparent. By then masking any arbitrary view with this mechanism, it becomes possible to display that view only for the specified short duration.
Finally, stacking these layers with ZStack enables frame-by-frame animation.

