Provide views, controls, and layout structures for declaring your app's user interface using SwiftUI.

Posts under SwiftUI tag

200 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

SwiftData updates in the background are not merged in the main UI context
Hello, SwiftData is not working correctly with Swift Concurrency. And it’s sad after all this time. I personally found a regression. The attached code works perfectly fine on iOS 17.5 but doesn’t work correctly on iOS 18 or iOS 18.1. A model can be updated from the background (Task, Task.detached or ModelActor) and refreshes the UI, but as soon as the same item is updated from the View (fetched via a Query), the next background updates are not reflected anymore in the UI, the UI is not refreshed, the updates are not merged into the main. How to reproduce: Launch the app Tap the plus button in the navigation bar to create a new item Tap on the “Update from Task”, “Update from Detached Task”, “Update from ModelActor” many times Notice the time is updated Tap on the “Update from View” (once or many times) Notice the time is updated Tap again on “Update from Task”, “Update from Detached Task”, “Update from ModelActor” many times Notice that the time is not update anymore Am I doing something wrong? Or is this a bug in iOS 18/18.1? Many other posts talk about issues where updates from background thread are not merged into the main thread. I don’t know if they all are related but it would be nice to have 1/ bug fixed, meaning that if I update an item from a background, it’s reflected in the UI, and 2/ proper documentation on how to use SwiftData with Swift Concurrency (ModelActor). I don’t know if what I’m doing in my buttons is correct or not. Thanks, Axel import SwiftData import SwiftUI @main struct FB_SwiftData_BackgroundApp: App { var body: some Scene { WindowGroup { ContentView() .modelContainer(for: Item.self) } } } struct ContentView: View { @Environment(\.modelContext) private var modelContext @State private var simpleModelActor: SimpleModelActor! @Query private var items: [Item] var body: some View { NavigationView { VStack { if let firstItem: Item = items.first { Text(firstItem.timestamp, format: Date.FormatStyle(date: .omitted, time: .standard)) .font(.largeTitle) .fontWeight(.heavy) Button("Update from Task") { let modelContainer: ModelContainer = modelContext.container let itemID: Item.ID = firstItem.persistentModelID Task { let context: ModelContext = ModelContext(modelContainer) guard let itemInContext: Item = context.model(for: itemID) as? Item else { return } itemInContext.timestamp = Date.now.addingTimeInterval(.random(in: 0...2000)) try context.save() } } .buttonStyle(.bordered) Button("Update from Detached Task") { let container: ModelContainer = modelContext.container let itemID: Item.ID = firstItem.persistentModelID Task.detached { let context: ModelContext = ModelContext(container) guard let itemInContext: Item = context.model(for: itemID) as? Item else { return } itemInContext.timestamp = Date.now.addingTimeInterval(.random(in: 0...2000)) try context.save() } } .buttonStyle(.bordered) Button("Update from ModelActor") { let container: ModelContainer = modelContext.container let persistentModelID: Item.ID = firstItem.persistentModelID Task.detached { let actor: SimpleModelActor = SimpleModelActor(modelContainer: container) await actor.updateItem(identifier: persistentModelID) } } .buttonStyle(.bordered) Button("Update from ModelActor in State") { let container: ModelContainer = modelContext.container let persistentModelID: Item.ID = firstItem.persistentModelID Task.detached { let actor: SimpleModelActor = SimpleModelActor(modelContainer: container) await MainActor.run { simpleModelActor = actor } await actor.updateItem(identifier: persistentModelID) } } .buttonStyle(.bordered) Divider() .padding(.vertical) Button("Update from View") { firstItem.timestamp = Date.now.addingTimeInterval(.random(in: 0...2000)) } .buttonStyle(.bordered) } else { ContentUnavailableView( "No Data", systemImage: "slash.circle", // 􀕧 description: Text("Tap the plus button in the toolbar") ) } } .toolbar { ToolbarItem(placement: .primaryAction) { Button(action: addItem) { Label("Add Item", systemImage: "plus") } } } } } private func addItem() { modelContext.insert(Item(timestamp: Date.now)) try? modelContext.save() } } @ModelActor final actor SimpleModelActor { var context: String = "" func updateItem(identifier: Item.ID) { guard let item = self[identifier, as: Item.self] else { return } item.timestamp = Date.now.addingTimeInterval(.random(in: 0...2000)) try! modelContext.save() } } @Model final class Item: Identifiable { var timestamp: Date init(timestamp: Date) { self.timestamp = timestamp } }
0
1
336
Oct ’24
Question about .presentationDetents and interactiveDismissDisabled modifiers
I have an app that I'm working on where I'd like to use the .presentationDetents modifier and I need the view to not be dismissed by the user. I'm therefore using the .interactiveDismissDisabled modifier. Unfortunately, that means that the Map on the ContentView can no longer respond to touch gestures. How can I have a similar experience to the Maps app where the underlying Map (or other views) can be interacted with and the user can still be prevented from dismissing the view controlled by the .presentationDetents modifier? SomeView(searchResults: $searchResults, selectedItem: $selectedItem) .presentationDetents([.height(100), .medium]) .presentationDragIndicator(.visible) .interactiveDismissDisabled(true)
2
0
216
Oct ’24
Failed to preview SwiftUI app for macOS in Xcode 12.(0, 1, 3, 4).
Hello! SwiftUI preview does not work. I tried different versions of Xcode, deleting the "Developer" folders, clearing the "Build" folder from the project in Xcode, reinstalling Command Line Tools and Developer Tools. I think that the error appeared after I cleaned Xcode with CleanMyMacX, before that the preview worked. -MacBook Pro (15-inch, Mid 2012) -macOS Catalina 10.15.7 -Xcode 12.3 Thanks for the feedback!
1
0
202
Oct ’24
VoiceOver issues with LazyVStack in ScrollView
I have a ScrollView which has inside a LazyVStack and multiple rows. On the appearing of the ScrollView, I scroll to row 250. When having the VoiceOver running, after opening the app, the focus is on the navigation title as it should be, than, after swiping right to enter the ScrollView, one would expect the focus to be placed on the first visible row, but this is not the case, it scrolls back to some row and focus on that, than reads it. Here is the code to reproduce it, and if you ask me, this is pretty standard stuff I don't do anything special. import SwiftUI struct ContentView: View { var body: some View { NavigationView { ScrollViewReader { scrollProxy in ScrollView { LazyVStack { ForEach(1...500, id: \.self) { index in NavigationLink(destination: DetailView(row: index)) { Text("Row \(index)") .padding() .frame(maxWidth: .infinity) .background(Color.gray.opacity(0.2)) .cornerRadius(8) .padding(.horizontal) } } } } .onAppear { // Scroll to row 250 when the view appears withAnimation { scrollProxy.scrollTo(250, anchor: .center) } } } .navigationTitle("Rows") } } } struct DetailView: View { let row: Int var body: some View { VStack { Text("Detail for Row \(row)") .font(.largeTitle) .padding() Spacer() } .navigationTitle("Row \(row) Details") } } @main struct ScrollViewExampleApp: App { var body: some Scene { WindowGroup { ContentView() } } }
0
0
145
Oct ’24
Are SwiftUI Text views with empty strings optimized out in iOS 18?
A UI test case that checks for the existence of a SwiftUI Text element initialized with an empty string previously reliably passed in iOS 17. In iOS 18 the assertion reliably fails. I searched the iOS 18 developer release notes for anything possibly related to this but didn't find much. I'd like to point to a conclusive change in iOS handling before changing the test permantently. Can anyone confirm that this is indeed a change in iOS 18?
0
3
175
Oct ’24
Displaying to-many relationships in CoreData
Any CoreData experts know what could be going on here when trying to display to-many entity type relationship data? On initialisation my model does an fetch request for an entity called Expense, and some prefetching for related entities (to-many type) like this: request.relationshipKeyPathsForPrefetching = ["splits"] I was under the impression that calling a property on one of the related entities should fire the fault (if there is one) and give me the value. Here’s where it starts to go wrong. I have a property in my extension that I use in my detailed view: public var viewExpenseSplits: [ExpenseSplit] { return splits?.allObjects as? [ExpenseSplit] ?? [] } and then a list in my detail view: List(expense.viewExpenseSplits, id: \.self) { split in Text(split.amount ?? "") } Result: What's even more baffling is that I have no problem viewing these splits in the parent view. I thought perhaps I would have more luck passing the viewExpenseSplits through to the detail view instead of trying to access them from the Expense entity itself. Whilst I can print the data out when the view loads, my list still has the same problem. I thought this would be trivial. Can anyone point to me where I'm going wrong? Thanks!
1
0
167
Oct ’24
TimelineView corrupts independent animations
Hello. I am here to report a problem. I was investigating the strange behaviour of my text animation, and I've found out that unrelated TimelineView is causing the problem. text is animating on change and there are colors on background (in ZStack) that change using TimelineView. As a result, when the text changes, it twitches, until it fills the whole screen width. If I pause TimelineView animation, everything works smoothly The problem occurs only on a real device. I've tested on iOS 17 and 18 Example code struct ContentView: View { @State var open = false let colors: [Color] = [.cyan, .red, .green, .gray, .blue, .orange, .pink, .purple] @State var i: Int = 0 @State var text: String = chunks[0] @State var pause = false var body: some View { ZStack { TimelineView(.animation(minimumInterval: 1.2, paused: pause)) { context in let color = colors[Int(context.date.timeIntervalSinceReferenceDate) % colors.count] ZStack { color .animation(.easeInOut(duration: 1.2)) Rectangle().fill(Material.regular).edgesIgnoringSafeArea(.all) } } VStack { Spacer() Text(text) .font(.system(size: 32)) .multilineTextAlignment(.center) .padding() .animation(.linear(duration: 0.1), value: text) Spacer() Button("Add text") { if i < (chunks.count - 1) { i += 1 } else { i = 0 text = "" } text.append(" " + chunks[i]) } Button("Toggle background animation") { pause.toggle() } } } } } let lyrics = "I'm born to run down rocky cliffs Give me grace, bury my sins Shattered glass and black holes Can't hold me back from where I need to go" let chunks = lyrics.components(separatedBy: " ")
0
1
118
Oct ’24
Equal width buttons in VStack
I can't wrap my head around how to make two buttons the same width, without resorting to ugly hacks like GeometryReader. What is otherwise easy to implement using UIStackView or AutoLayout, becomes a nightmare in SwiftUI. I tried the following, plus some other variations, but nothing works. What is the "official way" to make two vertically aligned buttons of the same width? 1) VStack(alignment: .leading) { Button("Short", action: {}) .frame(maxWidth: .infinity) Button("Long title", action: {}) .frame(maxWidth: .infinity) } .frame(maxWidth: .infinity) 2) VStack(alignment: .leading) { Button(action: {}) { Text("Short") .frame(maxWidth: .infinity) } Button(action: {}) { Text("Long title") .frame(maxWidth: .infinity) } } .frame(maxWidth: .infinity) 3) VStack(alignment: .leading) { Button(action: {}) { Text("Short") }.frame(maxWidth: .infinity) Button(action: {}) { Text("Long title") }.frame(maxWidth: .infinity) } .frame(maxWidth: .infinity)
3
0
232
Oct ’24
AreaMark Always alignsMarkStylesWithPlotArea for linear gradients
I'm trying to make a Swift Chart where 24 AreaMarks an hour apart on X axis over a day display a vertical gradient. The gradient is vertical and is essentially [Color.opacity(0.1),Colour,Color.opacity(0.1] The idea here is where the upper and lower points of each AreaMark are the same or close to each other in the Y axis, the chart essentially displays a line, where they are far apart you get a nice fading vertical gradient. However, it seems that the .alignsMarkStylesWithPlotArea modifier is always set for AreaMarks even if manually applying it false. Investigating further, I've learnt that with AreaMarks in a series, Swift Charts seems to only listen to the first foreground style set in. I've created some sample code to demonstrate this. struct DemoChartView: View { var body: some View { Chart { AreaMark(x: .value("Time", Date().addingTimeInterval(0)), yStart: .value("1", 40), yEnd: .value("2", 60)) .foregroundStyle(LinearGradient(colors: [.pink, .teal], startPoint: .top, endPoint: .bottom)) .alignsMarkStylesWithPlotArea(false) AreaMark(x: .value("Time", Date().addingTimeInterval(3600)), yStart: .value("1", 44), yEnd: .value("2", 58)) .foregroundStyle(LinearGradient(colors: [.orange, .yellow], startPoint: .top, endPoint: .bottom)) .alignsMarkStylesWithPlotArea(false) AreaMark(x: .value("Time", Date().addingTimeInterval(03600*2)), yStart: .value("1", 50), yEnd: .value("2", 90)) .foregroundStyle(LinearGradient(colors: [.green, .blue], startPoint: .top, endPoint: .bottom)) .alignsMarkStylesWithPlotArea(false) } } } Which produces this: So here, all the different .foregroundStyle LinearGradients are being ignored AND the .alignsMarkStylesWithPlotArea(false) is also ignored - the amount of pink on the first mark is different to the second and third 🤷‍♂️ Has anyone encountered this. Are AreaMarks the correct choice or are they just not setup to create this type of data display. Thanks
2
0
210
Oct ’24
How can I get a tab-bar styled ornament on the trailing edge of a view controller?
I've got a UIKit app with a collapsible trailing-edge child view controller, implemented sort of like UISplitViewController but it's got a bunch of custom behavior - moving to the bottom edge in portrait orientation, etc. It exposes a couple of different app functions via a UITabBar on the bottom edge on iOS. When I run the app on visionOS, that tab bar transforms to a leading-edge ornament. This would be great, but that means it tries to overlap the trailing-edge content of its parent view controller, which isn't ideal. Is there a way to get the tab bar to lay out on the trailling edge of the child view controller? Or can I create a custom ornament that has the same auto-expand behavior as the tab bar, where it shows a vertical column of icons that expands to show titles when you're gazing at it?
1
0
201
Oct ’24
SwiftUI warning for "Publishing changes from within view updates" on macOS
I have a simple example of a List with multiple selection. When I run it on macOS, and select an item from the list, it works fine but I get a warning in the console: Publishing changes from within view updates is not allowed, this will cause undefined behavior Interestingly, it doesn't produce a purple 'issue' in the Issues navigator, but as I change selection, I keep getting this warning. Also, the warning doesn't show when running the same code on iOS. Here is code to reproduce it: struct TestListSelection: View { let testArray = [TestItem(itemValue: 1), TestItem(itemValue: 2), TestItem(itemValue: 3), TestItem(itemValue: 4)] @ObservedObject var listOptions: TestListViewModel var body: some View { List (selection: $listOptions.multipleSelection) { Section("Header") { ForEach (testArray, id: \.self) { item in Button { print("row tapped - \(item.itemValue)") } label: { VStack { HStack { Text(item.itemString) } } } .buttonStyle(.plain) } } } .listStyle(.plain) } } public struct TestItem: Identifiable, Hashable { public let id = UUID() let itemValue: Int var itemString: String { get { return "test \(itemValue)" } } } @MainActor public class TestListViewModel: NSObject, ObservableObject { @Published public var multipleSelection = Set<TestItem>() } I annotated the view model with @MainActor as suggested in other threads, but it doesn't silence the warning. If I move the multipleSelection into the view itself, and make it a @State variable (bypassing the viewModel completely), it works and doesn't produce a warning. But I need it work so I can pass in selection from the UIKit part of the app as well. I also can't migrate to @Observable because my main project has to support iOS15 and above. Any clue why this is happening on macOS specifically, and what I can do to avoid it?
0
0
162
Oct ’24
List: Why does the destination value seem to be 0-based in one direction and 1-based in another with .onMove?
I ran into this with a more complex project, but was able to recreate with a very simple example below. I have a List with five items in it. I use a ForEach on the items with .onMove. The onMove function has a destination value. If my list is: A, B, C, D, E, and I drag B so it is below C, I would expect to get A, C, B, D, E. Further, I would expect that destination would be 2, since I am moving B to the 3rd index, assuming that A is at index 0. Weirdly, destination is always 3. Conversely, if I do the opposite, and drag C so it is above B, and I get A, C, B, D, E, destination is now 1! This makes sense, since it's the second position. So why is it that moving down the list results in what appears to be 1-based indexing, but moving it up the list seems to be 0-based indexing? How am I supposed to reliably get the correct destination value? @State var items = ["A", "B", "C", "D", "E"] var body: some View { NavigationStack { List { ForEach(items, id: \.self) { item in Text(item) } .onMove(perform: moveItem) } .toolbar { ToolbarItem { EditButton() } } } } private func moveItem(from source: IndexSet, to destination: Int) { print("destination is \(destination)") } } Output from above. If I drag B below C: destination is 3 If I drag C above B: destination is 1
0
0
155
Oct ’24
How do I make a toolbar-style window ornament from UIKit?
I've got a UIKit app and I want to add some buttons in a top-edge window ornament. I'm looking at the WWDC23 talk Meet UIKit for Spatial Computing, and it does exactly what I think I want to do: extension EditorViewController { func showEditingControlsOrnament() { let ornament = UIHostingOrnament(sceneAlignment: .bottom, contentAlignment: .center) { EditingControlsView(model: controlsViewModel) .glassBackgroundEffect() } self.ornaments = [ornament] editorView.style = .edgeToEdge } } But the thing I really want to know is what is in EditingControlsView. Is it a toolbar? How do you make a toolbar in SwiftUI without something to attach the .toolbar modifier to?
1
0
171
Oct ’24
iOS 18 update causes toolbar display regression
Hi there, I'm having an app developed in the last weeks, and recently I'm testing it on iOS 18. This following piece of UI code has diff between iOS 18 and iOS version lower than 18. I have a NavigationStack in my homeView, and the display difference is for its toolbar in one destination. I've tried both approaches in code, with header variable or ToolbarItemGroup used directly in the toolbar modifier, both would result in there being a spacer between the body VStack and toolbar, which is unexpected. Here's the code and a demo screenshot. var body: some View { // header VStack(alignment: .leading) { notificationView( iconKey: "ErrorCircle", contentKey: "receivedFile.notification.noNetwork.content" ) fileListView } .toolbar { // header ToolbarItemGroup(placement: .principal) { Button { dismiss() } label: { Image(systemName: "chevron.backward") } .background(Color.yellow) Spacer() Text(LocalizedStringKey("title")) .font( .system(size: 17) .weight(.semibold) ) .background(Color.yellow) Spacer() Button { print("click") } label: { Text("Click") } .background(Color.yellow) } } .navigationBarBackButtonHidden() .onAppear { refreshAllFiles() } } @ToolbarContentBuilder private var header: some ToolbarContent { ToolbarItem(placement: .topBarLeading) { Button { dismiss() } label: { Image(systemName: "chevron.backward") } .background(Color.yellow) } ToolbarItem(placement: .principal) { Text(LocalizedStringKey("receivedFileList.title")) .font( .system(size: 17) .weight(.semibold) ) .background(Color.yellow) } ToolbarItem(placement: .topBarTrailing) { Button { print("click for jumping") } label: { Text("Click for jumping") } .background(Color.yellow) } } Hope I can get some help from the forum on the usage. If not fixable, may I know if this is a known issue that would be fixed in the next upgrades.
3
0
260
Oct ’24