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

SwiftUI Documentation

Posts under SwiftUI subtopic

Post

Replies

Boosts

Views

Activity

SwiftUI Menu with NavigationLinks inside overall NavigationStack
I've read all previous posts on this topic but none seem to address what I'm seeing for iOS 16 and using NavigationStack. I'm also using an overall @EnvironmentObject for navigation state. I have a split view app. In the detail section, I have a NavigationStack surrounding the detail view. Within the detail view (MyView), there is a base view with a "+" button in the toolbar to create a new entity. That opens NewEntityView where I show a grid of buttons for the user to select a type to create a new entity before moving to NewEntityView to fill in the details for the entity. The top row of the grid of buttons takes the user straight to the NewEntityView with a NavigationLink. These work fine. The next row of buttons present a menu of sub-types and then should take the user to the NewEntityView view. These buttons do not work. Code (simplified to not have clutter): SplitViewDetailView: struct SplitViewDetailView: View { @EnvironmentObject var navigationManager: NavigationStateManager @Binding var selectedCategory: Route? var body: some View { NavigationStack(path: $navigationManager.routes) { // other irrelevant stuff MyView() } .environmentObject(navigationManager) .navigationDestination(for: Route.self) { $0 } } } MyView: struct MyView: View { @EnvironmentObject var navigationManager: NavigationStateManager var body: some View { List { // other stuff } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button(action: {}, label: { NavigationLink(value: Route.newTypeSelect) { Image(systemName: "plus") .frame(width: 44, height: 44) } } ) } } .navigationDestination(for: Route.self) { $0 } } SelectTypeView: struct SelectTypeView: View { var body: some View { ZStack { VStack { // Top row with no subtypes HStack { ForEach (topRows, id: \.self) { type in NavigationLink(value: Route.newEntityDetails(type.rawValue)) { <-- these work Text(type) } } } HStack { ForEach (middleRow, id: \.self) { type in Menu { ForEach (subtype[type], id: \.self) { sub in NavigationLink(value: Route.newEntityDetails(sub.rawValue)) { <-- these go nowhere Text(sub) } } } label: { Text(type) } } } } } } } NavigationStateManager: class NavigationStateManager: ObservableObject { @Published var routes = [Route]() // other stuff } And Route: enum Route: Identifiable { var id: UUID { UUID() } case newTypeSelect case newEntityDetails(String) } extension Route: View { var body: some View { switch self { case .newTypeSelect: SelectTypeView() case .newEntityDetails(let type): NewEntityView(selectedType: type) } } } The menus show up fine but tapping on an item does nothing. I've attempted to wrap the menu in its own NavigationStack but that is rejected stating it is already in one defined by a parent view. I've tried making the links Buttons with destinations and those are also rejected. What is the newest/best way to present a menu with NavigationLinks? One doesn't simply wrap the menu in a NavigationView if one is using a NavigationStack?
1
0
1.2k
Aug ’23
MapProxy conversion from screen to coords is wrong on macOS
Try the following code on macOS, and you'll see the marker is added in the wrong place, as the conversion from screen coordinates to map coordinates doesn't work correctly. The screenCoord value is correct, but reader.convert(screenCoord, from: .local) offsets the resulting coordinate by the height of the content above the map, despite the .local parameter. struct TestMapView: View { @State var placeAPin = false @State var pinLocation :CLLocationCoordinate2D? = nil @State private var cameraProsition: MapCameraPosition = .camera( MapCamera( centerCoordinate: .denver, distance: 3729, heading: 92, pitch: 70 ) ) var body: some View { VStack { Text("This is a bug demo.") Text("If there are other views above the map, the MapProxy doesn't convert the coordinates correctly.") MapReader { reader in Map( position: $cameraProsition, interactionModes: .all ) { if let pl = pinLocation { Marker("(\(pl.latitude), \(pl.longitude))", coordinate: pl) } } .onTapGesture(perform: { screenCoord in pinLocation = reader.convert(screenCoord, from: .local) placeAPin = false if let pinLocation { print("tap: screen \(screenCoord), location \(pinLocation)") } }) .mapControls{ MapCompass() MapScaleView() MapPitchToggle() } .mapStyle(.standard(elevation: .automatic)) } } } } extension CLLocationCoordinate2D { static var denver = CLLocationCoordinate2D(latitude: 39.742043, longitude: -104.991531) } (FB13135770)
5
1
1.3k
Sep ’23
String catalogs in packages
We have separated much of our UI into different packages to reduce complexity and compile time. When we recently tested using new .xcstrings string catalogs, we hit an unexpected problem. Strings extracted from SwiftUI components like Text or Button are extracted into the Localizable.xcstrings in the same package, but the default behaviour of Text(_ key:tableName:bundle:comment:) is to use Bundle.main. When the default behaviour of the string extraction isn't to extract to the main app target, this introduces a very fragile system where it's easy to add code that looks localised, but ends up failing lookup at runtime. I don't feel comfortable that we will always remember to define the correct module every time we create a Text. Also, other components like Button doesn't have an init that takes a Bundle, so we would also have to remember that Button(_ titleKey:action:) can now only be used in a package if we make sure that the main bundle contains a matching key. Is there a way for us to make sure that strings are always extracted to the same place as they are resolved against by default? Either by having strings in packages extracted to an xcstrings file in the main app or having Text default to resolving against the module bundle by default?
4
4
2.2k
Sep ’23
How to support foregroundColor (deprecated) and foregroundStyle in watchOS 9/10?
In my Watch app on watchOS 9 I was using .foregroundColor(myColour) to colour the text in a widgetLabel on a corner complication like this: let myColour: Color = functionThatReturnsAColorObjectConstructedLike Color.init(...) // green .widgetLabel { Text(myText) .foregroundColor(myColour) } It worked fine; the widget label was green. Now, in watchOS 10, I see that foregroundColor() is being deprecated in favour of foregroundStyle(), and I can use .foregroundStyle(.green), and - importantly - foregroundStyle() is only available on watchOS 10 and newer. myColour is calculated depending on some other info, so I can't just write .green, and when I use .foregroundStyle(myColour) the widget label comes out as white every time, even if I set myColour = .green. I think I have to use some sort of extension to pick the right combination, something like: extension View { func foregroundType(colour: Colour, style: any ShapeStyle) -> some THING? { if #available(watchOS 10.0, *) { return foregroundStyle(style) } else { return foregroundColor(colour) } } } // Usage let myStyle: any ShapeStyle = SOMETHING? ... .widgetLabel { Text(myText) .foregroundType(colour: myColour, style: myStyle) It doesn't work. I just can't figure out what should be returned, nor how to return it. Any ideas?
3
2
3.4k
Sep ’23
WatchOS app crashes when using .topBarTrailing toolbar item placement
I'm running into an issue with using .topBarTrailing placement for a toolbar item. The app fails to launch (crashes) with this placement. The following view works fine with any other placement (other than .topBarLeading). What am I doing wrong? var body: some View { NavigationStack { Text("Overview") .toolbar { ToolbarItem(placement: .topBarTrailing) { Button { // noop } label: { Label("Add", systemImage: "plus") } } } .navigationTitle("Overview") .navigationBarTitleDisplayMode(.inline) } } I've opted to use .confirmationAction as a workaround, which works fine. It also positions the toolbar item in the same place on the view as the .topBarTrailing placement would. I'm using Xcode version 15.0 and targeting WatchOS 10. Verbose error output when trying to run the app in the simulator: *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Layout requested for visible navigation bar, <PUICStackedNavigationBar: 0x100e1e8d0; baseClass = UINavigationBar; frame = (0 0; 198 60); opaque = NO; autoresize = W; layer = <CALayer: 0x60000027c280>> delegate=0x101877800 standardAppearance=0x60000261cc60, when the top item belongs to a different navigation bar. topItem = <UINavigationItem: 0x100f11230> title='Overview' style=navigator, navigation bar = <PUICStackedNavigationBar: 0x100f22a80; baseClass = UINavigationBar; frame = (0 0; 198 60); opaque = NO; autoresize = W; layer = <CALayer: 0x6000002887c0>> delegate=0x101069600 standardAppearance=0x60000261f3c0, possibly from a client attempt to nest wrapped navigation controllers.'
3
1
1.2k
Sep ’23
Disabled button in SwiftUI .alert not working
I found an issue when implementing an alert with a TextField to input a name. I want the action button to be disabled until a name has been entered, but the action block is never executed when the button has become enabled and pressed. The problem seems to appear only when name is initially an empty string. Tested with iOS 17.0. struct MyView: View { @State private var name = "" var body: some View { SomeView() .alert(...) { TextField("Name", text: $name) Button("Action") { // Action }.disabled(name.isEmpty) Button("Cancel", role: .cancel) {} } } }
14
6
4.2k
Sep ’23
SwiftUI Commands and StrictConcurrency Warnings Issue
I have enabled “StrictConcurrency” warnings in my project that uses SwiftUI. I have a Commands struct. It has a Button, whose action is calling an async method via Task{}. This builds without warnings within Views, but not Commands. There the compiler reports “Main actor-isolated property 'body' cannot be used to satisfy nonisolated protocol requirement”. Looking at SwiftUI: In View, body is declared @MainActor: @ViewBuilder @MainActor var body: Self.Body { get } In Commands, body is not declared @MainActor: @CommandsBuilder var body: Self.Body { get } So the common practice of making a Button action asynchronous: Button { Task { await model.load() } } label:{ Text("Async Button") } will succeed without warnings in Views, but not in Commands. Is this intentional? I've filed FB13212559. Thank you.
3
0
1.2k
Sep ’23
Invalid Numeric Value (NaN) Error in SwiftUI's TextField on Long-Press
I'm experiencing a peculiar issue with SwiftUI's TextField. Whenever I long-press on the TextField, the console outputs an error about passing an invalid numeric value (NaN, or not-a-number) to the CoreGraphics API. This issue persists even in a new Xcode project with minimal code. Code Snippet: import SwiftUI struct ContentView: View { @State private var text: String = "" var body: some View { TextField("Placeholder", text: $text) } } Error: this application, or a library it uses, has passed an invalid numeric value (NaN, or not-a-number) to CoreGraphics API and this value is being ignored. Please fix this problem. Steps to Reproduce: Create a new SwiftUI project in Xcode. Add a TextField to the ContentView. Run the app on a device or simulator. Long-press inside the TextField. What I've Tried: Updating to the latest version of Xcode and iOS. Using UIViewRepresentable to wrap a UIKit UITextField. Creating a new Xcode project to isolate the issue. None of these steps have resolved the issue. Questions: Has anyone else encountered this problem? Are there any known workarounds for this issue? Is this a known bug, and if so, has it been addressed in any updates?
21
33
13k
Oct ’23
visionOS Window launch size
Before visionOS Beta 4 it was possible to define the launch size in the Info.plist using PreferredLaunchSize like so: <key>UILaunchPlacementParameters</key> <dict> <key>PreferredLaunchSize</key> <dict> <key>Height</key> <integer>750</integer> <key>Width</key> <integer>750</integer> </dict> </dict> In visionOS Beta 4 this now doesn't work anymore and the window opens in a 16:9 format and then will scale down to the .defaultSize of the WindowGroup with an animation. Settings, Notes, Safari still open with a different default size though, including the launch screen. How are we supposed to do this now?
2
1
1.1k
Oct ’23
NavigationLinks embedded in a List getting clipped in tvOS
I want to recreate an user experience like in the settings app in tvOS. Therefore I have a HStack with some content on the left side and a List of NavigationLinks on the right side. However a focused link in the list gets clipped on the left side. I tried paddings and spacers and what not, but nothing helped. Is this is a bug or am I missing something? Here is some example code to show the problem: struct ContentView: View { var body: some View { NavigationStack { HStack(spacing: 20) { VStack(alignment: .center) { Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) Text("Hello, world!") } List { ForEach(SomeViewEnum.allCases) { someView in NavigationLink(someView.rawValue, value: someView) } } .navigationDestination(for: SomeViewEnum.self) { someView in Text(someView.rawValue) } } } } } And a screenshot to show the problem:
1
1
797
Oct ’23
Problem with TextFields and decimals iOS17
After upgrading to iOS 17 I am struggling to get decimals working in my app - here is a sample code: for clarity I provide a complete "working" sample (I am pulling data from a back-end server using json, etc - all of that is working ok, it's the UI issue - see below): Model import Foundation struct TestFloat: Encodable { var testFloatNumber: Float = 0 } Observable Class import Foundation class TestFloatViewModel: ObservableObject { @Published var testFloat = TestFloat() } View struct TestFloatView: View { @ObservedObject var testFloatVM: TestFloatViewModel let formatter: NumberFormatter = { let formatter = NumberFormatter() formatter.numberStyle = .decimal return formatter }() var body: some View { VStack { HStack { Text("Enter Float Number") Spacer() } Spacer() .frame(height: 2.0) HStack { TextField("Enter Float Number", value: $testFloatVM.testFloat.testFloatNumber, formatter: formatter) .keyboardType(.decimalPad) .textFieldStyle(.roundedBorder) } } } } This is working on a device with iOS16; however when run on device with iOS17: you can enter maximum four digits? using backspace you can delete all numbers apart of the first one you cannot enter the decimal (.) at all even though decimal keyboard is provided Any help is greatly appreciated.
10
3
2.3k
Oct ’23
Infinite loop getting "_dismiss changed"
I'm working on a NavigationStack based app. Somewhere I'm using: @Environment(\.dismiss) private var dismiss and when trying to navigate to that view it gets stuck. I used Self._printChanges() and discovered the environment variable dismiss is changing repeatedly. Obviously I am not changing that variable explicitly. I wasn't able to reproduce this in a small project so far, but does anybody have any idea what kind of thing I could be doing that might be causing this issue? iOS 17.0.3
10
6
2.2k
Oct ’23
ShareLink not reliable, entitlement errors
I am using public struct ShareLink<Data, PreviewImage, PreviewIcon, Label> : View where Data : RandomAccessCollection, PreviewImage : Transferable, PreviewIcon : Transferable, Label : View, Data.Element : Transferable {} And it is generating entitlement errors and not working reliably ( will transfer 1-3 items, but not 4+ ; will not work a second time) Error is: Error acquiring assertion: <Error Domain=RBSServiceErrorDomain Code=1 "(originator doesn't have entitlement com.apple.runningboard.primitiveattribute AND originator doesn't have entitlement com.apple.runningboard.assertions.frontboard AND target is not running or doesn't have entitlement com.apple.runningboard.trustedtarget AND Target not hosted by originator)" UserInfo={NSLocalizedFailureReason=(originator doesn't have entitlement com.apple.runningboard.primitiveattribute AND originator doesn't have entitlement com.apple.runningboard.assertions.frontboard AND target is not running or doesn't have entitlement com.apple.runningboard.trustedtarget AND Target not hosted by originator)}> Received port for identifier response: <> with error:Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.process-state, NSLocalizedFailureReason=Client not entitled, RBSPermanent=false} elapsedCPUTimeForFrontBoard couldn't generate a task port Received port for identifier response: <> with error:Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.process-state, NSLocalizedFailureReason=Client not entitled, RBSPermanent=false} elapsedCPUTimeForFrontBoard couldn't generate a task port Received port for identifier response: <> with error:Error Domain=RBSServiceErrorDomain Code=1 "Client not entitled" UserInfo={RBSEntitlement=com.apple.runningboard.process-state, NSLocalizedFailureReason=Client not entitled, RBSPermanent=false} Code is import SwiftUI var images = Array<Image?>(repeating: nil , count: 200); struct ShareTestGContentView: View { let columns = [GridItem(.fixed(165)), GridItem(.fixed(165))] private let twoColumnGrid = [ GridItem(.flexible(minimum: 40), spacing: 2), GridItem(.flexible(minimum: 40), spacing: 2) ] @State var selected = Set() var body: some View { topSection() ScrollView { LazyVGrid(columns: twoColumnGrid, spacing: 2) { // columns: [GridItem(.flexible())]) { ForEach(1...100, id: \.self) { idx in var cr = ( selected.contains(idx) ? 16.0 : 0 ) var lw = ( selected.contains(idx) ? 8.0 : 0 ) VStack{ Button(action: { if selected.contains(idx) { selected.remove(idx); cr = 0.0; lw = 0.0 } else { selected.insert(idx); cr = 16.0; lw = 8.0 } } , label: { AsyncImage(url :URL(string: "https://picsum.photos/800/800?\(idx)")) { image in let _ = ( images[idx] = image.image ) image.image }.frame(width: boxHalf,height:boxHalf).drawingGroup() }) }.overlay( RoundedRectangle(cornerRadius: cr).stroke(Color.yellow, lineWidth: lw) ).drawingGroup() } } } } func topSection() -> some View { let imgarray = selected.compactMap { images[$0] } return HStack { ShareLink("",items: imgarray) { img in SharePreview("images", image: Image(systemName: "captions.bubble.fill")) } } } } #Preview { ShareTestGContentView() }
6
11
7.4k
Oct ’23
MapKit elevation information for a route
Hello all, I am playing with MapKit for SwiftUI, so far so good. There is one thing I have not seen any documentations, or sample codes around and that's elevation data, e.g. My questions are: Is there a way to get this information from an MKRoute? Is it possible to get the elevation gain/drop at a given point in the route? Many thank in advance for your help.
1
1
645
Oct ’23
Sheet will change to dark theme from toggle but won't change back to light mode
I have my ContentView which has a Sheet that will appear when a button is pressed. struct ContentView: View { @EnvironmentObject private var settings: SettingsHandler @State private var settingsView: Bool = false var body: some View { NavigationStack { Button(action: { settingsView.toggle() }, label: { Image(systemName: "gearshape.fill") }) } .preferredColorScheme(settings.darkTheme ? .dark : nil) .sheet(isPresented: $settingsView, content: { SettingsView() }) } } Let's say the app is in light mode based on the phones theme settings. You open the SettingsView and select the toggle that will switch to dark mode. Everything changes to dark mode, including the SettingsView sheet. Then you select the same toggle to switch back and ContentView in the background changes to light theme but the sheet doesn't until you close and reopen it. I would think it would change back considering it changed to dark mode without needing to be closed. I tried attaching an ID to the SettingsView and having it refresh when settings.darkTheme is changed, however, it doesn't seem to be doing anything. I also added the .preferredColorScheme() modifier into the SettingsView, but it did nothing. I also replaced the nil to .light, and the same issue occurred. Settings is an EnvironmentObject that I created to manage all the Settings I have. At the moment, I'm thinking I can have the sheet just close and reopen, however, I would like for it to update properly. Any ideas?
2
0
1.9k
Oct ’23
Why aren't changes to @Published variables automatically published on the main thread?
Given that SwiftUI and modern programming idioms promote asynchronous activity, and observing a data model and reacting to changes, I wonder why it's so cumbersome in Swift at this point. Like many, I have run up against the problem where you perform an asynchronous task (like fetching data from the network) and store the result in a published variable in an observed object. This would appear to be an extremely common scenario at this point, and indeed it's exactly the one posed in question after question you find online about this resulting error: Publishing changes from background threads is not allowed Then why is it done? Why aren't the changes simply published on the main thread automatically? Because it isn't, people suggest a bunch of workarounds, like making the enclosing object a MainActor. This just creates a cascade of errors in my application; but also (and I may not be interpreting the documentation correctly) I don't want the owning object to do everything on the main thread. So the go-to workaround appears to be wrapping every potentially problematic setting of a variable in a call to DispatchQueue.main. Talk about tedious and error-prone. Not to mention unmaintainable, since I or some future maintainer may be calling a function a level or two or three above where a published variable is actually set. And what if you decide to publish a variable that wasn't before, and now you have to run around checking every potential change to it? Is this not a mess?
9
0
3.6k
Nov ’23
MapKit in List Breaks Top/Bottom Bar FadeIn/Out Effect
I've encountered a weird issue with the new Map for iOS 17. In my list, which includes a MapView among other elements, I've observed that with the new initializer, the top and bottom bars are persistently displayed. They don't hide and only appear when scrolling, as they should. This problem doesn't occur with the old, now-deprecated initializer. To illustrate this, I have two screenshots: one with the map enabled and the other with the map disabled, demonstrating the issue. Here is also my new Map code: struct MapListRowView: View { @Binding internal var location: LocationModel @State internal var user: Bool = true private var position: CLLocationCoordinate2D { .init(latitude: location.latitude, longitude: location.longitude) } private var isPad: Bool { UIDevice.current.userInterfaceIdiom == .pad ? true : false } var body: some View { Map(bounds: .init(minimumDistance: 1250)) { if user { UserAnnotation() } Annotation("", coordinate: position) { ZStack { Circle().fill().foregroundColor(.white).padding(1) Image(systemName: "mappin.circle.fill") .resizable() .foregroundColor(.indigo) }.frame(width: 20, height: 20).opacity(user ? .zero : 1.0) } } .frame(height: isPad ? 200 : 100) .cornerRadius(8) .listRowInsets(.init(top: -5, leading: .zero, bottom: -5, trailing: .zero)) .padding(.vertical, 5) .disabled(true) } }
3
1
673
Nov ’23
EditMode & EditButton not working in a way I expect
I have something that looks like: NavigationStack { List(self.items, id: \.self, selection: self.$selectedItems) { item in NavigationLink { ItemView(item: item) .environment(\.managedObjectContext, self.viewContext) } label: { LabelWithMenuView(object: item) { ptr in self.labelHandler(item: item, newName: ptr) } } } if self.editMode?.wrappedValue == .active { editButtons } else { TextField("Add Item", text: self.$newItem) .onSubmit { self.addItem() self.newItem = "" } .padding() } } #if os(iOS) .toolbar { EditButton() } .onChange(of: self.editMode?.wrappedValue) { old, new in print("editMode \(old) -> \(new)") } #endif With that layout, the edit button doesn't show up at all; if I put it as part of the List, it does show up, but the first click doesn't do anything; after that, it works, but the onChange handler doesn't show it getting changed, and the editButtons don't go away.
8
2
2.3k
Nov ’23