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!
Post
Replies
Boosts
Views
Activity
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.
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()
}
}
Every error in the editor is duplicated. At the same time in the Issue Navigator, everything is fine and errors are shown only once. This does not happen on Xcode 12, only 13.
Please see the screenshot. Would appreciate some help here, thank you.