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

Posts under SwiftUI tag

200 Posts

Post

Replies

Boosts

Views

Activity

Task cancellation behaviour
Hi everyone, I believe this should be a simple and expected default behavior in a real-world app, but I’m unable to make it work: 1. I have a View (a screen/page in this case) that calls an endpoint using async/await. 2. If the endpoint hasn’t finished, but I navigate forward to a DetailView, I want the endpoint to continue fetching data (i.e., inside the @StateObject ViewModel that the View owns). This way, when I go back, the View will have refreshed with the fetched data once it completes. 3. If the endpoint hasn’t finished and I navigate back to the previous screen, I want it to be canceled, and the @StateObject ViewModel should be deinitialized. I can achieve 1 and 3 using the .task modifier, since it automatically cancels the asynchronous task when the view disappears: view .task { await vm.getData() } I can achieve 1 and 2 using a structured Task in the View (or in the ViewModel, its the same behavior), for example: .onFirstAppearOnly { Task { away vm.getData() } } onFirstAppearOnly is a custom modifier that I have for calling onAppear only once in view lifecycle. Just to clarify, dont think that part is important for the purpose of the example But the question is: How can I achieve all three behaviors? Is this really such an unusual requirement? My minimum deployment target is iOS 15, and I’m using NavigationView + NavigationLink. However, I have also tried using NavigationStack + NavigationPath and still couldn’t get it to work. Any help would be much appreciated. Thank you, folks!
0
0
398
Feb ’25
[iOS, SwiftUI] UiRefreshControl.tintColor is not the same as given one
Hello! I'm trying to set a UiRefreshControl.tintColor: .onAppear { UIRefreshControl.appearance().tintColor = UIColor.systemBlue } But instead of I get The color in the second picture is a high contrast version of the first one. I can't understand why it works this way. I also tried the following. UIRefreshControl.appearance().tintColor = UIColor(red: 0, green: 0.478, blue: 1, alpha: 1) // doesn't work UIRefreshControl.appearance().tintColor = UIColor(named: "RefreshControlColor") // doesn't work, here set "High contrast" on and indicated Universal.systemBlueColor Perhaps I missed something?
0
0
192
Feb ’25
AsyncImage - Cancelled Loading before View is Visible
I have been playing around with the new AsyncImage Api in SwiftUI I am using the initialiser that passes in a closure with the AsyncImagePhase, to view why an image may not load, when I looked at the error that is passed in if the phase is failure, the localised description of the error is "Cancelled" but this is happening before the view is being displayed. I am loading these images in a list, I imagine I am probably doing something which is causing the system to decide to cancel the loading, but I cannot see what. Are there any tips to investigate this further?
15
8
13k
Feb ’25
SwiftUI Popover Crash During Resizing in Stage Manager
SwiftUI Popover Crash on iPad During Resizing in Stage Manager with Exception. *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Trying to layout popover in the delegate callback popoverPresentationController:willRepositionPopoverToRect:inView: will lead to recursion. Do not force the popover's container view or an ancestor to layout in this callback.' (Occurred from iPadOS 18.1) struct ContentView: View { @State var showPopover: Bool = false var body: some View { VStack { Text("Hello, world!") Button(action: { showPopover = true }, label: { Text("Open Popover") }) } .padding() .popover(isPresented: $showPopover, attachmentAnchor: .point(.trailing), content: { VStack { Text("Popover Content") } }) } }
10
9
1.2k
Feb ’25
LongPressGesture.updating(_:body:)
Hello, I'm using LongPressGesture to provide haptic & color change feedback on a long press action. This fails to call the .updating(_:body:) method on every iOS 18.0 beta. It works fine on iOS 17. The following code, taken directly from Apple's documentation illustrates the problem. The .updating() closure is never called, while the .onEnded() closure is called correctly. Instead of a gradual color transition the example code generates a delayed sharp switch from Red to Blue, preventing interactive user feedback. struct LongPressGestureView: View { @GestureState private var isDetectingLongPress = false @State private var completedLongPress = false var longPress: some Gesture { LongPressGesture(minimumDuration: 3) .updating($isDetectingLongPress) { currentState, gestureState, transaction in gestureState = currentState transaction.animation = Animation.easeIn(duration: 2.0) } .onEnded { finished in self.completedLongPress = finished } } var body: some View { Circle() .fill(self.isDetectingLongPress ? Color.red : (self.completedLongPress ? Color.green : Color.blue)) .frame(width: 100, height: 100, alignment: .center) .gesture(longPress) } } Can anyone share tips on a workaround or potential fix application-side?
4
4
1.1k
Feb ’25
Getting a file icon on iOS
Some time ago I read somewhere that one can get a file icon on iOS like this: UIDocumentInteractionController(url: url).icons.last!) but this always returns the following image for every file: Today I tried the following, which always returns nil: (try? url.resourceValues(forKeys: [.effectiveIconKey]))?.allValues[.effectiveIconKey] as? UIImage Is there any way to get a file icon on iOS? You can try the above methods in this sample app: struct ContentView: View { @State private var isPresentingFilePicker = false @State private var url: URL? var body: some View { VStack { Button("Open") { isPresentingFilePicker = true } if let url = url { Image(uiImage: UIDocumentInteractionController(url: url).icons.last!) if let image = (try? url.resourceValues(forKeys: [.effectiveIconKey]))?.allValues[.effectiveIconKey] as? UIImage { Image(uiImage: image) } else { Text("none") } } } .padding() .fileImporter(isPresented: $isPresentingFilePicker, allowedContentTypes: [.data]) { result in do { let url = try result.get() if url.startAccessingSecurityScopedResource() { self.url = url } } catch { preconditionFailure(error.localizedDescription) } } } }
2
0
463
Feb ’25
LinearGradient not completely filling shape
I am working through an Apple Developer SwiftUI Landmarks Tutorial using Xcode 16.2 on a M2 Pro Mac mini with OSX 14.7 (Sonoma). Under Drawing paths and Shapes, the step where they draw the background, the linearGradient is not filling the top and bottom of the hex shape. If I use a solid fill the shape is filled. I've attached a screenshot from the tutorial showing the entire shape filled with the linear gradient, a screen shot from Xcode showing the gradient not filling the top and bottom of the shape and a screen shot from Xcode showing that a solid fill does work. I found one online mention that implies that this started when the author upgraded to Xcode 16. Since the tutorial was written for Xcode 15, I assume that it worked there.
5
1
525
Feb ’25
Sheet + keyboard toolbar (18.3) - is anyone else still encountering this issue?
I'm pretty sure I'm writing this code correctly, and the keyboard toolbar shows up in the simulator. When I test on-device, though, the keyboard toolbar doesn't show up. I know this issue has been around intermittently since swiftui was released, but I figured it would have been fixed by now. I did submit another feedback request regarding this issue, but maybe I'm just writing the view in the wrong way to get this to function? I'm thinking that might be the case -- in another project I can get the toolbar to appear once, but after switching from one textfield to another it stops appearing. This is my attempt to get it to work in a simple demo project (excerpted from the original): // Content View NavigationStack { List { // ... } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { EditButton() } ToolbarItem { Button(action: addItem) { Image(systemName: "plus") } } } } .sheet(item: $presentedItem) { item in NavigationStack { ItemView(item: item) .toolbar { ToolbarItem(placement: .topBarLeading) { Button("Cancel") { self.modelContext.delete(item) self.presentedItem = nil } } ToolbarItem(placement: .topBarTrailing) { Button("Save") { self.presentedItem = nil } } } .navigationTitle("Item") } } // Item View ScrollView { VStack { TextField("Title", text: $title, axis: .vertical) .focused($focus, equals: TextFieldFocus.title) } } .toolbar { ToolbarItemGroup(placement: .keyboard) { HStack { Spacer() Button("Done") { self.focus = nil print("done tapped") } } } } .navigationTitle("ItemView")
3
1
360
Feb ’25
Is it possible to get selected text from a Text field?
Basically the title. If a Text field has textSelection enabled, is there anyway to get the text that the user has highlighted? Text("My text here") .textSelection(.enabled) Ultimately, my goal is to be able to highlight text and apply style to individual characters. Using a TextField would give my app the ability to know what's been highlighted, but then I can't style individual elements. I'd like to be able to do this in SwiftUI without having to drop into TextKit. struct MyTextEditorView: View { @State var myText: String = "" @State var textSelection: TextSelection? = nil var body: some View { TextField("Placehodler", text: $myText, selection: $textSelection) } }
3
0
735
Feb ’25
There seems to be a bug with digitalCrownAccessory
After reading the documentation on .digitalCrownAccessory, I am under the impression that the system should know to show your accessory and hide your accessory according to whether the crown is being rotated. I have noticed this is not the case. I have also tried a few work arounds with no luck. Here is a simplified version of my code. I have recorded a video of the issue I am seeing, but I can't upload it, so I have attached some screenshots instead. Expected result: After rotating the crown, the accessory image should disappear along with the vertical system scroll bar. Actual result: After rotating the crown, the vertical system scroll bar disappears but the accessory image stays behind. Versions: Sonoma 14.6.1 Xcode 16 Beta 5 (and Xcode 15.4) Apple Watch Ultra 2 watchOS 11 (device and simulator) Starting file (main): import SwiftUI @main struct DummyWatch_Watch_AppApp: App { var body: some Scene { WindowGroup { ContentView() } } } ContentView.swift: import SwiftUI struct ContentView: View { @State var crownValue: CGFloat = 0 var body: some View { VStack { Text("Hello, world!") } .focusable() .digitalCrownRotation($crownValue, from: 0, through: 10, by: 0.1, sensitivity: .low, isContinuous: false) .digitalCrownAccessory { Image(systemName: "speaker.fill") } .padding() } } Images: Scrolling: Speaker wont go away:
1
1
532
Feb ’25
Link in Swift Student Challenge
I intend to participate in the Swift Student Challenge. A link is provided within my application that directs users to an Internet HTML web page. Link(destination: URL(string: "https://url.com")!) { Label("Developer Website - .....com", systemImage: "arrow.right") .shadow(color: Color.white ,radius: 50) } This URL corresponds to my personal web page. Although it is not directly related to the experience interaction within the application, I have decided to include it as it serves as a logo and demonstrates my proficiency in HTML. However, the challenge’s rules stipulate that the evaluation environment is not permitted to connect to the Internet. Consequently, I am concerned that my work may be rejected due to its perceived incompleteness or errors. So should I keep it? Thanks!
1
0
481
Feb ’25
LongPressGesture does not work as expected in Xcode Version 16.0 (16A242) and iOS 18
When I copy and paste example code in apple developer documentation, LongPressGesture does not work as expected in Xcode Version 16.0 (16A242) and iOS 18. It seems updating(_:body:) method does not work when used with LongPressGesture. When I make a breakpoint in updating(_:body:) method and long press the blue circle on the screen of simulator(or device), it is expected to be caught in breakpoint or it is expected that color of circle turns from blue to red to green. However, it is not caught in breakpoint and never turns to red. Question of Stackoverflow is about same issue and I can not use onLongPressGesture method to implement required feature of my app. Development environment: Xcode Version 16.0 (16A242), macOS 14.5 Run-time configuration: iOS 18.0
15
18
2.5k
Feb ’25
Location Permission Popup Not Appearing in SwiftUI App
Hello everyone, I'm working on a SwiftUI app that requires location services, and I've implemented a LocationManager class to handle location updates and permissions. However, I'm facing an issue where the location permission popup does not appear when the app is launched. Here is my current implementation: LocationManager.swift: import CoreLocation import SwiftUI class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate { private let locationManager = CLLocationManager() @Published var userLocation: CLLocation? @Published var isAuthorized = false @Published var authorizationStatus: CLAuthorizationStatus = .notDetermined override init() { super.init() locationManager.delegate = self checkAuthorizationStatus() } func startLocationUpdates() { locationManager.startUpdatingLocation() } func stopLocationUpdates() { locationManager.stopUpdatingLocation() } func requestLocationAuthorization() { print("Requesting location authorization") DispatchQueue.main.async { self.locationManager.requestWhenInUseAuthorization() } } private func checkAuthorizationStatus() { print("Checking authorization status") authorizationStatus = locationManager.authorizationStatus print("Initial authorization status: \(authorizationStatus.rawValue)") handleAuthorizationStatus(authorizationStatus) } func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { print("Authorization status changed") authorizationStatus = manager.authorizationStatus print("New authorization status: \(authorizationStatus.rawValue)") handleAuthorizationStatus(authorizationStatus) } private func handleAuthorizationStatus(_ status: CLAuthorizationStatus) { switch status { case .authorizedAlways, .authorizedWhenInUse: DispatchQueue.main.async { self.isAuthorized = true self.startLocationUpdates() } case .notDetermined: requestLocationAuthorization() case .denied, .restricted: DispatchQueue.main.async { self.isAuthorized = false self.stopLocationUpdates() print("Location access denied or restricted") } @unknown default: DispatchQueue.main.async { self.isAuthorized = false self.stopLocationUpdates() } } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { DispatchQueue.main.async { self.userLocation = locations.last } } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { print("Location manager error: \(error.localizedDescription)") } } MapzinApp.swift: @main struct MapzinApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate @StateObject private var locationManager = LocationManager() var body: some Scene { WindowGroup { Group { if locationManager.authorizationStatus == .notDetermined { Text("Determining location authorization status...") } else if locationManager.isAuthorized { CoordinatorView() .environmentObject(locationManager) } else { Text("Location access is required to use this app. Please enable it in Settings.") } } } } } Log input: Checking authorization status Initial authorization status: 0 Requesting location authorization Authorization status changed New authorization status: 0 Requesting location authorization Despite calling requestWhenInUseAuthorization() when the authorization status is .notDetermined, the permission popup never appears. Here are the specific steps I have taken: Checked the Info.plist to ensure the necessary keys for location usage are present: NSLocationWhenInUseUsageDescription NSLocationAlwaysUsageDescription NSLocationAlwaysAndWhenInUseUsageDescription Verified that the app's target settings include location services capabilities. Tested on a real device to ensure it's not a simulator issue. I'm not sure what I might be missing. Any advice or suggestions to resolve this issue would be greatly appreciated. Thank you!
2
0
2.3k
Feb ’25
Reduce padding, spacing between list section header and search bar
Anyone know how to reduce the padding between list section header (plain style) and search bar? I have tried all available method on google but none work. The default list style does not have this big padding/space between the section header and the search bar. struct Demo: View { @State private var searchText: String = "" var body: some View { NavigationStack { List { Section { ForEach(0..<100) { index in Text("Sample value for \(index)") } } header: { Text("Header") .font(.headline) } } .listStyle(.plain) .navigationTitle("Demo") .navigationBarTitleDisplayMode(.inline) .searchable(text: $searchText) } } }
0
0
234
Feb ’25
Unexpected Frame Resizing Behavior During Animation of safeAreaInset content.
Hey everyone! I’m encountering an issue while attempting to animate height changes of the content inside safeAreaInset(edge:alignment:spacing:content:). When animating a reduction in the frame height, the container view (in my case, Map) also animates unexpectedly. However, when animating an increase in the frame height, the animation works smoothly, and the Map view remains still. How can I address this odd resizing behavior of the container? Code: struct MapView: View { var body: some View { Map() .safeAreaInset(edge: .bottom) { MapDetailView() } } } struct MapDetailView: View { @State private var oldHeightOffset: CGFloat = 0 @State private var newHeightOffset: CGFloat = 0 @State private var containerHeight: CGFloat = 0 private var drag: some Gesture { DragGesture(coordinateSpace: .global) .onChanged { value in withAnimation(.interactiveSpring) { newHeightOffset = oldHeightOffset + value.translation.height } } .onEnded { value in switch newHeightOffset { case containerHeight * 0.625...containerHeight: withAnimation(.spring) { newHeightOffset = containerHeight * 0.75 } case containerHeight * 0.25..<containerHeight * 0.625: withAnimation(.spring) { newHeightOffset = containerHeight * 0.5 } case 0..<containerHeight * 0.25: withAnimation(.spring) { newHeightOffset = 0 } default: break } oldHeightOffset = newHeightOffset } } var body: some View { NavigationStack { Rectangle() .fill(.clear) .containerBackground(.ultraThinMaterial, for: .navigation) } .gesture(drag) .containerRelativeFrame(.vertical) { length, _ in length - newHeightOffset } .onGeometryChange(for: CGFloat.self) { geometryProxy in let frame = geometryProxy.frame(in: .local) return frame.height + newHeightOffset } action: { containerHeight in self.containerHeight = containerHeight } } } Reducing safe area inset's content height (drag down): Increasing safe area inset's content height (drag up):
3
0
516
Feb ’25
ARMeshAnchor Data with RealityView
I want to use SwiftUI and RealityView to get AR scene understanding data (ARMeshAnchor) on iOS devices with LiDAR. The only way we can do that is by using ARSession (unless there is another way). However in previous iOS 18 builds there was this function: https://developer.apple.com/documentation/realitykit/spatialtrackingsession/run(_:session:arconfiguration:) , which worked with SpatialTrackingSession and a custom ARSession together. This function in the the latest iOS and Xcode has since been removed in the RealityKit framework but still there on documentation. I also wanted to get ARFaceAnchor data which I still cannot get without ARSession, the closest I can get is by using: let target = AnchoringComponent.Target.face let anchoringComponent = AnchoringComponent(target, trackingMode: .predicted) entity = Entity() entity!.components.set(anchoringComponent) But I still can't find a way to get the current frame (ARFrame) or the anchors ([ARAnchor]) in the view. Alternatively if I use if I use this function: https://developer.apple.com/documentation/realitykit/spatialtrackingsession/run(_:) and start the ARSession separately. The session (didUpdate and didAdd) only runs for a few frames before getting interrupted. And if I completely remove SpatialTrackingConfiguration and just run the ARSession. There still is a valid tracked entity for the AnchoringComponent.Target.face component. IF in the configuration for the ARSession I use the ARWorldTrackingConfiguration with face tracking. And I still get updated facial data each frame. But the ARSession didUpdate or didAdd functions don't get called passed the first few frames. Interestingly if I switch the RealityViewCameraContent.RealityViewCamera to .virtual. I get ARMeshAnchor and ARFaceAnchor data, but no camera feed (as expected). This with or without SpatialTrackingConfiguration. My overarching question is what is the proper way to access ARMeshAnchors and other ARAnchors created by the system and track them live while also using SwiftUI. GitHub Repo with sample project can be found here: https://github.com/bpate75/RealityViewTesting
1
1
448
Feb ’25
Correct way to label TextField inside Form in SwiftUI
Hello everyone. I'm building a simple Form in a Multiplatform App with SwiftUI. Originally I had something like this. import SwiftUI struct OnboardingForm: View { @State var firstName: String = "" @State var lastName: String = "" @State var email: String = "" @State var job: String = "" @State var role: String = "" var body: some View { Form { TextField("First Name", text: $firstName, prompt: Text("Required")) TextField("Last Name", text: $lastName, prompt: Text("Required")) TextField("Email", text: $email, prompt: Text("Required")) TextField("Job", text: $job, prompt: Text("Required")) TextField("Role", text: $role, prompt: Text("Required")) } } } #Preview { OnboardingForm() } In macOS it looks ok but then in iOS it looks like this: and it's impossible to know what each field is for if all the prompts are the same. I tried adding LabeledContent around each text field and that solves it for iOS but then on macOS it looks like this: The labels are shown twice and the columns are out of alignment. I think I could get around it by doing something like this: #if os(iOS) LabeledContent { TextField("First Name", text: $firstName, prompt: Text("Required")) } label: { Text("First Name") } #else TextField("First Name", text: $firstName, prompt: Text("Required")) #endif but it seems to me like reinventing the wheel. Is there a "correct" way to declare TextFields with labels that works for both iOS and macOS?
2
0
968
Feb ’25
SceneKit
Helle there Currently, I’m attempting to create an interactive learning application with a 3D view. I’ve discovered the framework SceneKit, but I lack the necessary knowledge to animate, load and moving objects. Could someone kindly suggest some good articles or tutorials on this topic?
2
0
595
Feb ’25
Swiftui Rendering issue: View not displayed when using almost full screen height
I plan to use the entire screen height minus 40 pixels approximately to not overlap with the time, batter and carrier data. However, I noticed that in the code shared below the vstack with pink background is not displayed at the top of the screen. The interesting part is that it's actually occupying an offset at the top of the screen. What's more, when I set an offset greater than 70 pixels, then the pink vstack displays on the view! Thus, I'm looking for an explanation to this swiftui rendering issue. Offset less than 70 pixels: Offset greater or equal than 70 pixels: GeometryReader { proxy in let offset = 40.0 let height = proxy.size.height - offset ZStack { VStack(spacing:0){ VStack{Text("heasdas")}.frame(width: 300,height: offset,alignment: .leading) .background(.pink) VStack { HStack(alignment:.center,spacing:10){ Text("Shapecloud") .font(.callout) .fontWeight(.semibold) .frame(alignment: .leading) SLine() } .frame(maxWidth: .infinity,alignment: .leading) Text("Digital Twin Solutions\nServices") .font(.largeTitle) .fontWeight(.medium) .frame(maxWidth: .infinity,alignment: .leading) } .frame(maxWidth: .infinity,maxHeight: 0.3*height,alignment: .top) .background(.red) VStack { VideoPlayer(player: player) .frame(maxWidth: .infinity,maxHeight: 300) } .frame(maxWidth: .infinity,maxHeight: 0.4*height) .background(.yellow) VStack{ Button { } label: { Text("Subscribe now").foregroundStyle(.black) .font(.headline) .fontWeight(.semibold) } .frame(maxWidth: .infinity,maxHeight: 50) .border(Color.black, width: 2) Button { } label: { Text("Sign In").foregroundStyle(.white) .font(.headline) } .frame(maxWidth: .infinity,maxHeight: 50) .background(Color.theme.primary) } .frame(maxWidth: .infinity,maxHeight: 0.3*height) .background(.green) } .padding(.horizontal, 32) .background(.cyan) .ignoresSafeArea(.all) } .ignoresSafeArea(.all) } .ignoresSafeArea(.all)
2
0
237
Feb ’25
Swift Form on iOS
I am new to swift (developer from long ago) and have not been able to resolve an issue with IOS - I am building a text entry box with save / cancel buttons at bottom and a texteditor above all within a fixed size frame. It's all OK on macOS but when I try iOS the space taken by the form weirdly expands (beyond the size of the text editor) and end up pushing the save /cancel buttons below outside view. An extract of the code is here. I have tried all sorts of things like setting frame sizes, line limits, spacers, scrolling disable, but I can't stop the behaviour. Any ideas. Apologies if this is well known or my issue due to inexperience I just can't resolve it. private func editSource(in geometry: GeometryProxy) -> some View { NavigationStack{ Form { Section(header: Text("Source Name")) { TextEditor(text: $newSourceName) .font(.title3) .padding(5) .background( RoundedRectangle(cornerRadius: 10) .fill(.background) ) .textFieldStyle(PlainTextFieldStyle()) .frame(height: 50) .foregroundColor(.primary) .focused($isSourceFocused) } } HStack { Button("Save") { events[eventIndex].source = newSourceName isEditingSource = false isSourceFocused = false disableEdit = false } Spacer() Button("Cancel", role: .cancel) { isSourceFocused = false isEditingSource = false disableEdit = false } } .padding() .layoutPriority(1) } #if os(macOS) .scrollDisabled(true) .padding() .frame(height: 180) .frame(width: 240) .background(.gray) #else .frame(width: 240) .frame(height: 240) #endif .cornerRadius(10) .clipShape(RoundedRectangle(cornerRadius: 10)) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color.blue, lineWidth: 1) ) .position(sourceLocation) .offset(x: 0, y: 100) .zIndex(1) .onAppear() { isSourceFocused = true newSourceName = events[eventIndex].source } }
4
0
240
Feb ’25