Post

Replies

Boosts

Views

Activity

Scroll offset incorrectly resets when animating insertion of ScrollView using .geometryGroup()
Hey, I've been having a problem with scroll views in combination with the .geometryGroup() modifier. I have filed a Feedback (FB17698293) but I also wanted to post this here in case someone maybe has a better workaround for the problem. Problem Whenever you conditionally insert a ScrollView inside a VStack that is modified with a .geometryGroup() modifier, the scroll view content offset resets itself after the insertion animation is done, even if you started scrolling inside the scroll view during the animation and haven't let go of the screen. This happens consistently and is fully reproducible (see below), both using a simulator and a real device. Unfortunately, this is a very annoying glitch that ruins a lot of cool UX components that rely on .geometryGroup(). The weird thing is that the glitch entirely disappears, if you add a simple, non-zero (but greater than 1) .padding() modifier to the VStack (.padding().geometryGroup()). I have no idea why this fixes the glitch, but it does. However, adding a padding is not feasible in many situations, so this workaround is not ideal. Steps to reproduce Launch the code below (using a simulator or a real device) and tap "Toggle Expansion" to insert the scroll view. As the view is animating in, drag the scroll content and hold it scrolled away from the top. Wait for the animation to complete. The scroll view will reset the content offset, even though the drag gesture is still active (i.e. you haven't lifted your finger to release the scroll view) On a real device, this sometimes even leads to an even worse visual artifact where the scroll view is rendered twice for a few frames; once with the correct offset, and once with the reset offset. I wanted to include a link to a gif/video showing the glitch, but it tells me that imgur is not allowed on the forums. Expected Behavior I want the scroll view to respect the content offset, even if I started changing it mid-animation. Xcode Version I am using Xcode 16.4 (16F6) but this problem has been occurring since the .geometryGroup() modifier has been release. I was only now able to pinpoint this problem exactly, so I'm filing this feedback. Code The entire code that reproduces the problem: import SwiftUI struct ContentView: View { @State private var isExpanded: Bool = false var body: some View { VStack { if isExpanded { ScrollView { Text(loremIpsum) } } Button("Toggle Expansion") { isExpanded.toggle() } } // .padding(10) // Adding a non-zero padding makes the glitch disappear .frame(maxWidth: .infinity) .geometryGroup() .animation(.default, value: isExpanded) } } #Preview { ContentView().preferredColorScheme(.dark) } // MARK: - Mock Data let loremIpsum = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt \ ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco \ laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla \ pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt \ mollit anim id est laborum. """
Topic: UI Frameworks SubTopic: SwiftUI
0
0
58
May ’25
Xcode does not see code changes in local Swift packages (autocomplete wrong, errors shown, but still compiles)
Hey, I've been having a lot of problems with Xcode 16 not seeing changes made to code in local Swift packages (the packages are inside the root directory of the project). Whenever I make any change like renaming a variable or type, or adding new methods or whatever, autocomplete doesn't see those changes and when I type in that new type/variable manually it gives me an error. However, building the project still works fine, even with the errors never going away. The only way for it to notice the code changes is to clean the project and build it again which takes a long time. At first, I thought this was connected to the new "Explicitly built modules" feature in Xcode 16, but I turned it off and it still happens. Any ideas what I can try? I'm on the latest Xcode version, but this problem has been happening since Xcode 16 originally came out. Thanks! Dennis
0
0
92
May ’25
SwiftUI gets stuck in a view update cycle when I pass down a Binding to a NavigationPath
Hey, I have a situation where the app freezes (because it's stuck in an endless update cycle) when I pass down a binding to a NavigationPath. I have a simple example to reproduce the behavior. Copy the following code and run it in previews, a simulator or a real device. Just tap "Show Middle View" and then "Show Inner View", which will cause the app to freeze, because SwiftUI gets stuck in an update cycle: import SwiftUI // This is literally empty @MainActor final class SomeEnvironmentObject: ObservableObject {} @MainActor final class Router: ObservableObject { @Published var path: NavigationPath = .init() } struct ContentView: View { @StateObject var router = Router() var body: some View { NavigationStack(path: $router.path) { Button("Show Middle View") { router.path.append(0) } .navigationDestination(for: Int.self) { destination in MiddleView(path: $router.path) } } } } struct MiddleView: View { @EnvironmentObject var someEnvironmentObject: SomeEnvironmentObject @Binding var path: NavigationPath var body: some View { Button("Show Inner View") { path.append("0") } .navigationDestination(for: String.self) { destination in InnerView(path: $path) } } } struct InnerView: View { @Binding var path: NavigationPath var body: some View { Text("Inner View") } } #Preview { ContentView() .environmentObject(SomeEnvironmentObject()) } The strange thing is, that the app freezes only, when MiddleView has the environment object: struct MiddleView: View { @EnvironmentObject var someEnvironmentObject: SomeEnvironmentObject // ... } Removing that line makes everything work. I'm pretty sure this is some kind of navigation dependency problem, where I'm capturing too much in the navigationDestination modifier, causing SwiftUI to repeatedly update the views in a cycle. I've read about something similar here: https://hachyderm.io/@teissler/112533860374716961 However, I've tried a variety of combinations of only capturing stuff that's needed in the navigationDestination closure, but nothing works. It still freezes up. Does anyone have an idea? I assume it's an error on my side, but maybe it could be a bug in SwiftUI? I have no idea how to solve this. This problem occurs on Xcode 16.0 Beta 5, 16.1 Beta 1 and 15.4, as well as on iOS 17 and iOS 18
4
1
1.1k
Aug ’24
.alignmentGuide modifier causes consistent app crash when Swift 6 language mode is enabled
I've been experiencing random crashes in my app and today, I could finally narrow it down enough to start building an example project that consistently crashes. The error is this: "com.apple.SwiftUI.AsyncRenderer (19): EXC_BREAKPOINT (code=1, subcode=0x102f103f8)" It's a very strange bug because the SwiftUI view hierarchy has to be in a very specific shape to cause the crash, but if it is, then it crashes 100% of the time: It's a button inside a List and a simple "@State var number: Int?" that is being animated when the view appears. You can simply paste the following code into a project and run it (device, simulator or even in a preview): // Must be in Swift 6 Language Mode! // iOS 18 Beta 4 struct ContentView: View { @State var number: Int? var body: some View { List { // Must be in a List // It also crashes if button is here inside the List } .overlay { button } .task { // Has to be in a task modifier (or .onAppear + Task {}, so it has to be async) number = 0 } } // Has to be in a separate view property private var button: some View { Button {} label: { // Has to be this initializer and not Button(number == nil ? "" : "") {} if number == nil { // There has to be a conditional with "number" in here Color.clear } } .animation(.default, value: number) // Animation must be inside the List .alignmentGuide(VerticalAlignment.center) { dimension in dimension[VerticalAlignment.center] } } } The crash only happens under the following conditions, as far as I can tell: This only happens in the Swift 6 language mode (which I have enabled in the example project) It's related to an .alignmentGuide modifier attached to a button that also has an .animation modifier The button has to have a conditional view inside its label that checks for the same variable that is being animated The button must be inside a separate property that is put either inside a List or inside an overlay of a List The crash is then triggered when the animated value is asynchronously changed (in a .task modifier for example) The crash happens at least with Beta 3 and 4 of Xcode 16.0 I have also opened a feedback: FB14510236 but I thought I'd post it here as well because this crash was driving me crazy and maybe others might find it helpful for their debugging. Took me hours to find out what's been causing it 😅 Edit: Interestingly, when I run this project on an iOS 17 device, it does not crash, but rather print a runtime warning: "warning: data race detected: @MainActor function at AlignmentGuideCrash/ContentView.swift:27 was not called on the main thread" Line 27 is: .alignmentGuide(VerticalAlignment.center) { dimension in
6
1
974
Jul ’24
Asset Symbol Generation - color and image accessors for Asset Catalogs
Hey, in Xcode 16's release notes there is this: Asset catalogs now provide an inspector property for enabling system color and image accessors for generated asset symbols, which allows Swift packages to opt-in to generating these accessors. (113704993) What exactly does this mean? Can we make the generated asset symbols public, so that we can share assets between local swift packages in the project? Unfortunately, I don't see this option in the inspector. I only see "Asset Symbols > Extensions On/Off". Not sure what that means, either. Thanks
1
4
945
Jul ’24
Confusing SwiftUI error log: "Mutating observable property after view is torn down has no effect"
Hey, I have a setup in my app that I am currently building, that uses @Observable router objects that hold the app's entire navigation state, so that I can easily set it globally and let SwiftUI show the appropriate views accordingly. Each view gets passed in such a router object and there is a global "app" router that the app's root view can access as an entry point: @Observable @MainActor final class AppRouter { static let shared = AppRouter() // Entry point used by the app's root view var isShowingSheet = false // Navigation state let sheetRouter = SheetRouter() // Router that's passed to the sheet view. This router could contain other routers for sheets it will show, and so on } @Observable @MainActor final class SheetRouter { // Example of a "nested" router for a sheet view var path = NavigationPath() var isShowingOtherSheet = false func reset() { path = .init() isShowingOtherSheet = false } } To open a sheet, I have a button like this: @Bindable var appRouter = AppRouter.shared // ... Button("Present") { appRouter.sheetRouter.reset() // Reset sheet router appRouter.isShowingSheet = true // show sheet } This seems to work perfectly fine. However, this produces tons of "error" logs in the console, whenever I open the sheet for a second time: Mutating observable property \SheetRouter.path after view is torn down has no effect. Mutating observable property \SheetRouter.path after view is torn down has no effect. Mutating observable property \SheetRouter.path after view is torn down has no effect. Mutating observable property \SheetRouter.path after view is torn down has no effect. Mutating observable property \SheetRouter.isShowingOtherSheet after view is torn down has no effect. These errors appear when calling the reset() of the sheet view's router before opening the sheet. That method simply resets all navigation states in a router back to their defaults. It's as if the sheetRouter is still connected to the dismissed view from the previous sheet, causing a mutation to trigger these error logs. Am I misusing SwiftUI here or is that a bug? It's also worth mentioning that these error logs do not appear on iOS 17. Only on iOS 18. So it might be a bug but I just want to make sure my usage of these router objects is okay and not a misuse of the SwiftUI API that the runtime previously simply did not catch/notice. Then I'd have to rewrite my entire navigation logic. I do have an example project to demonstrate the issue. You can find it here: https://github.com/SwiftedMind/PresentationBugDemo. I have also filed a feedback for this: FB14162780 STEPS TO REPRODUCE Open the example project. Open the sheet in the ContentView twice by tapping "Present" Close that sheet Open it again. Then the console will show these error logs from above. I'd appreciate any help with this. Cheers
2
2
952
Jul ’24
Why is the `TipViewStyle` protocol not isolated to the main actor?
Hey, the TipViewStyle protocol seems to still be nonisolated, which makes using it in the Swift 6 language mode impossible, I believe (at least without using something like MainActor.assumeIsolated): @available(macOS 14.0, iOS 17.0, tvOS 17.0, visionOS 1.0, watchOS 10.0, *) public protocol TipViewStyle { // ... } I cannot use any views inside the makeBody method. Other style protocols, like ButtonStyle are correctly isolated to the main actor: @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) @MainActor @preconcurrency public protocol ButtonStyle { // ... } Is this just an oversight or am I missing something and this is actually intentional? Thanks!
Topic: UI Frameworks SubTopic: SwiftUI Tags:
2
0
470
Jun ’24
"TranslationUnit" - Xcode Previews constantly crash in Xcode 16 Beta 1
Hey, I've opened an existing project with Xcode 16 Beta 1 (I'm also running macOS Sequoia). None of my previews work anymore. They simply crash with an error I don't quite understand: JITError ================================== | NoBuiltTargetDescriptionCouldBeFound | | translationUnit: PreviewTranslationUnit(moduleNamePrefix: "Previews_ErrorView", sourceIdentifier: file:///Users/dennis/Code/Project/Project/Components/ErrorView.swift -> ErrorView.swift, parseTree: ParseTree(version: 1848, statements: 5, providers: 0), update: nil, changesContextMemoizer: PreviewsPipeline.PreviewTranslationUnit.(unknown context at $3561fdc4c).ChangesContextMemoizer(parseTree: ParseTree(version: 1848, statements: 5, providers: 0), sourceIdentifier: file:///Users/dennis/Code/Project/Project/Components/ErrorView.swift -> ErrorView.swift, cachedValue: os.OSAllocatedUnfairLock<Swift.Optional<PreviewsModel.ParseTree.PreviewChangesContext>>(__lock: Swift.ManagedBuffer<Swift.Optional<PreviewsModel.ParseTree.PreviewChangesContext>, __C.os_unfair_lock_s>)), registryDeclarationMemoizer: PreviewsPipeline.PreviewTranslationUnit.(unknown context at $3561fdbec).RegistryDeclarationMemoizer) | | builtTargetDescriptions: The app builds and runs fun on a simulator and on device. Any ideas what this error means? Is this just an early Beta 1 bug or might there be anything I can do in my project to fix this? The WWDC24 demo projects all work fine and their previews are working as well. Thanks!
4
1
941
Jun ’24
CKSyncEngine - Are the "change batches" we send to iCloud guaranteed to be identical to the batches iCloud sends back to other devices?
Hey, I'm currently working on adding CloudKit support to the GRDB SQLite database in my app. CKSyncEngine, though still a bit tricky to wrap your head around, is amazing! I have most of the basic setup implemented and some very easy cases already work, which is really exciting! However, I do have some questions regarding data consistency across devices. I'm not sure though, that these questions are actually "correct" to ask. Maybe my entire approach is inherently flawed. Say we add two records to the pending changes of the sync engine: // I'm simplifying CKRecord.ID to be a String here syncEngine.state.add(pendingRecordZoneChanges: [.saveRecord("1"), .saveRecord("2")] Let's also say that both records are tightly connected. For example, they could be in a one-to-one relationship and must always be added to the database together because the app relies on the existence of either none or both. After that call, at some later point, the system will call the sync engine's delegate nextRecordZoneChangeBatch(_:syncEngine:) for us to batch the changes together to send to iCloud. First question: Can we guarantee that records "1" and "2" always land in the exact same batch, and are never separated? Looking at the example code, there are two line that worry me a bit: // (Sample project: `SyncedDatabase.swift, lines 132-133`) let scope = context.options.scope let changes = syncEngine.state.pendingRecordZoneChanges.filter { scope.contains($0) } The scope could lead to one of the two records being filtered out. However, my assumption is that the scope will always be .all when the system calls it from an automatically managed schedule, and only differs when you manually specify a different value through calling syncEngine.sendChanges(_:). Is that correct? Now back to the example. Say we successfully batched records "1" and "2" together and the changes have been sent to iCloud. Awesome! What happens next? Other connected devices will, at some point, fetch those changes and apply them to their respective local databases. Second question: Can we guarantee that iCloud sends the exact same batches from earlier to other devices and does not create new batches from all the changes? I'm worried that iCloud might take all stored changes and "re-batch" them for whatever reason (efficiency, for example). Because that could cause records "1" and "2" to be separated into different batches. A second device could end up receiving "1" and, for at least some period of time, will not receive "2", which would be an "illegal" state of the data. I'd appreciate any help or clarification on this :) Thanks a lot!
2
0
898
Apr ’24