Post

Replies

Boosts

Views

Activity

iOS 18 seems to have broken animations in Widgets
In iOS 18 the same animations inside the main app and a widget behave very differently. This was not the case in iOS 17 and it seems like a regression. Bellow I described the full source code for an example application to reproduce the issue. But I'll try to describe it as well. I'm using a simple bounce animation .animation(.bouncy(duration: 0.25, extraBounce: 0.3).delay(delay), value: toggle) on multiple views with a variable delay to create an effect of staggered motion. In the main app, the bounce parameters and delays behave exactly as specified, but on the widget they seem to be largely ignored and the timing functions for the animations look very different. It's also visible that on some elements the delay is ignored completely and animation starts right away. I double-checked that the whole animation duration never exceeds 2 seconds as I saw that that's a hard cutoff for animations in widgets. In iOS 17 the same code works identically inside the main app and the widget. The issue is only present in iOS 18. Another point is that in Xcode previews for widgets animations behave as expected. The issue is only present on the device or in the simulator. Maybe there is some additional limitation introduced for widget animations? I could not find anything relevant in the documentation, unfortunately, so I'd appreciate any help here. Please let me know if I can provide any additional information that would help. Thank you! Example app source code: ContentView.swift // ContentView.swift import SwiftUI struct ContentView: View { @State private var toggle: Bool = true var body: some View { VStack { BounceView(toggle: toggle) Button("Toggle Bounce") { toggle.toggle() } } .padding() } } BounceView.swift import SwiftUI let ROWS: Int = 5 let COLS: Int = 5 let MAX_DISTANCE: Double = sqrt(pow(Double(COLS - 1), 2) + pow(Double(ROWS - 1), 2)) struct BounceView: View { let toggle: Bool var body: some View { VStack(spacing: 3) { ForEach(0..<ROWS, id: \.self) { y in HStack(spacing: 3) { ForEach(0..<COLS, id: \.self) { i in let distance: Double = sqrt(Double(y) * Double(y) + Double(i) * Double(i)) let influence = distance / MAX_DISTANCE let delay: Double = 0.4 * influence RoundedRectangle(cornerRadius: 4) .fill(.blue) .frame(width: 14, height: 14) .scaleEffect(toggle ? 1 : 0.1) .animation(.bouncy(duration: 0.25, extraBounce: 0.3).delay(delay), value: toggle) } } } } } } DemoWidget.swift import WidgetKit import SwiftUI struct Provider: TimelineProvider { func placeholder(in context: Context) -> SimpleEntry { SimpleEntry(date: Date(), toggle: false) } func getSnapshot(in context: Context, completion: @escaping (SimpleEntry) -> ()) { let entry = SimpleEntry(date: Date(), toggle: false) completion(entry) } func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) { let currentToggle = UserDefaults.standard.bool(forKey: "animationToggle") let entries: [SimpleEntry] = [SimpleEntry(date: Date(), toggle: currentToggle)] let timeline = Timeline(entries: entries, policy: .atEnd) completion(timeline) } } struct SimpleEntry: TimelineEntry { let date: Date let toggle: Bool } struct DemoWidget: Widget { let kind: String = "DemoWidget" var body: some WidgetConfiguration { StaticConfiguration(kind: kind, provider: Provider()) { entry in VStack { BounceView(toggle: entry.toggle) Button( intent: ToggleAppIntent(), label: { Text("Toggle Bounce") } ) } .containerBackground(.fill.tertiary, for: .widget) } .configurationDisplayName("My Widget") .description("This is an example widget.") } } #Preview(as: .systemMedium) { DemoWidget() } timeline: { SimpleEntry(date: .now, toggle: true) SimpleEntry(date: .now, toggle: false) } ToggleAppIntent.swift import Foundation import WidgetKit import AppIntents struct ToggleAppIntent: AppIntent { static var title: LocalizedStringResource = "Toggle App Intent" func perform() async throws -> some IntentResult { let currentToggle = UserDefaults.standard.bool(forKey: "animationToggle") UserDefaults.standard.set(!currentToggle, forKey: "animationToggle") WidgetCenter.shared.reloadTimelines(ofKind: "DemoWidget") return .result() } }
4
2
1k
Aug ’24
[iOS 18] Colors from asset catalog render as pure white on tinted widgets
When a widget uses a custom color from Assets catalog. Those colors seem to be ignored and rendered as pure white when used in a widget and the Home Screen is set into the Tinted mode. This can be re-produced both in a Simulator and on a device and on the latest 18.1 beta (as of writing this post). Occasionally, I was able to catch the widget to be rendered correctly after it had been on the Home Screen for some time, but I could not identify what caused it, just re-rendering the widget by pushing a new entry into the timeline or changing the configuration, does not help the issue, colors are still rendered white. I've attached the screenshots with my widget rendered in tree different modes. Has anyone also encountered this? I'm not sure if I need to do something extra to properly support tinted mode.
1
1
968
Sep ’24
[iOS 18.2 Beta] LazyVGrid within NavigationStack breaks animations
Hello. There seems to be a bug specifically in the iOS 18.2 (both Beta 1 and 2) and not seen in the previous versions. The bug is: when LazyVGrid is nested inside NavigationStack and some elements of the LazyVGrid have animations, navigating into any nested view and then going back to the initial view with the LazyVGrid causes all animations to stop working. Here is the example code inline: struct ContentView: View { @State private var count: Int = 0 var body: some View { NavigationStack { LazyVGrid( columns: Array( repeating: GridItem(spacing: 0), count: 1 ), alignment: .center, spacing: 0 ) { VStack { Text(String(count)) .font(.system(size: 100, weight: .black)) .contentTransition(.numericText()) .animation(.bouncy(duration: 1), value: count) Button("Increment") { count += 1 } NavigationLink(destination: { Text("Test") }, label: { Text("Navigate") }) } } } .padding() } } Once you run the application on iOS 18.2 Beta (I've tried on a real device only), the steps to reproduce are: Tap on the "Increment button" You should see the number change with an animation Tap on the "Navigate" button Tap "Back" to go to the initial screen Tap "Increment" again The number changes without an animation I can confirm that this affects not only .contentTransition() animation but any animation within the LazyVGrid, I've tested this in my real app. Let me know if I can provide more details. Thank you!
3
0
131
5d