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

SwiftUI Documentation

Post

Replies

Boosts

Views

Activity

Text with relative date - sizing bug when used in widget
When using a SwiftUI Text view with a relative date, with .minimumScaleFactor(), in a widget, the Text will always be shown at the minimum scale rather than the largest size possible. This started happening in iOS17, and is still the case in iOS18. Therefore, if for example, you wanted to have a Text, showing a relative date in a countdown, with a "minimumScaleFactor" of 0.5, it will always show at 0.5 scale, no matter how much space there is available. Text(.now, style: .relative) .font(.system(size: 80, weight: .bold, design: .rounded)) .minimumScaleFactor(0.2) ^^ will always show at 0.2 scale (in a widget) even if there's space for much more. This is only the case in widgets, not in the main app. Similarly, if you try to use "ViewThatFits" instead of "minimumScaleFactor", it will choose the smallest view, even if larger ones would fit. Screenshot demonstrates the issue - all the countdown text views should be similarly large. This is not an issue with lack of vertical spacing causing the more flexible views to shrink - it behaves the same even if there's loads of vertical space too. Maybe related, but trying to use ".fixedSize()" with a relative date Text view in a widget results in a completely blank widget. Seems like a system bug rather than anything wrong in my code - I've logged it as Feedback (#15151577) with a demo project. Has anybody else come up against these issues - any solutions?
4
0
143
2w
Open an specific view or sheet of the app from a control widget button
Hi everyone. I'm trying to use the new ControlWidget API introduced on iOS 18 to open a sheet that contains a form when the user taps on the button on the control center. This is my current code. It opens the app, but I haven't found how to do an action inside the app when the app is opened. @available(iOS 18, *) struct AddButtonWidgetControl: ControlWidget { var body: some ControlWidgetConfiguration { StaticControlConfiguration(kind: "com.example.myapp.ButtonWidget") { ControlWidgetButton(action: LaunchAppIntent()) { Label("Add a link", systemImage: "plus") } } .displayName("Add a link") .description("Creates a link.") } } @available(iOS 18, *) struct LaunchAppIntent: AppIntent { static var title: LocalizedStringResource { "Launch app" } static var openAppWhenRun: Bool = true func perform() async throws -> some IntentResult { return .result() } }
2
0
203
2w
Xcode 16 SwiftUI List Fast Scrolling Issue
Hi! When building my app using Xcode 16, fast scrolling (using scrollViewProxy.scrollTo) a list would result in items not appearing even when scrolling stopped. This does not happen when the app is built with Xcode 15, even on iOS 18. I'm also getting this error in the logs: List failed to visit cell content, returning an empty cell. - SwiftUICore/Logging.swift:84 - please file a bug report.
2
1
237
2w
How do you let SwiftUI View know that ViewModel's computed property has changed?
Imagine you have ViewModel which computed property may change from time to time: import Foundation class ViewModel: ObservableObject { private var val = 42; var compProp: Int { return val } func maybeChange() { if (Int.random(in: 0..<2) == 1) { val += 1 ??? //heyViewThePropertyIsChanged(nameof(compProp)) } } } How could you force the View: import SwiftUI struct ContentView: View { @StateObject var viewModel: ViewModel var body: some View { VStack { Text("\(viewModel.compProp)").font(.title) } .frame(minWidth: 320) .toolbar { Button(action: viewModel.maybeChange) { Label("Maybe", systemImage: "gear.badge.questionmark") } }.padding() } } to reflect such a change?
3
0
136
2w
Swift Charts - weak scrolling performance
Hello there! I wanted to give a native scrolling mechanism for the Swift Charts Graph a try and experiment a bit if the scenario that we try to achieve might be possible, but it seems that the Swift Charts scrolling performance is very poor. The graph was created as follows: X-axis is created based on a date range, Y-axis is created based on an integer values between moreless 0-320 value. the graph is scrollable horizontally only (x-axis), The time range (x-axis) for the scrolling content was set to one year from now date (so the user can scroll one year into the past as a minimum visible date (.chartXScale). The X-axis shows 3 hours of data per screen width (.chartXVisibleDomain). The data points for the graph are generated once when screen is about to appear so that the Charts engine can use it (no lazy loading implemented yet). The line data points (LineMark views) consist of 2880 data points distributed every 5 minutes which simulates - two days of continuous data stream that we want to present. The rest of the graph displays no data at all. The performance result: The graph on the initial loading phase is frozen for about 10-15 seconds until the data appears on the graph. Scrolling is very laggy - the CPU usage is 100% and is unacceptable for the end users. If we show no data at all on the graph (so no LineMark views are created at all) - the result is similar - the empty graph scrolling is also very laggy. Below I am sharing a test code: @main struct ChartsTestApp: App { var body: some Scene { WindowGroup { ContentView() Spacer() } } } struct LineDataPoint: Identifiable, Equatable { var id: Int let date: Date let value: Int } actor TestData { func generate(startDate: Date) async -> [LineDataPoint] { var values: [LineDataPoint] = [] for i in 0..<(1440 * 2) { values.append( LineDataPoint( id: i, date: startDate.addingTimeInterval( TimeInterval(60 * 5 * i) // Every 5 minutes ), value: Int.random(in: 1...100) ) ) } return values } } struct ContentView: View { var startDate: Date { return endDate.addingTimeInterval(-3600*24*30*12) // one year into the past from now } let endDate = Date() @State var dataPoints: [LineDataPoint] = [] var body: some View { Chart { ForEach(dataPoints) { item in LineMark( x: .value("Date", item.date), y: .value("Value", item.value), series: .value("Series", "Test") ) } } .frame(height: 200) .chartScrollableAxes(.horizontal) .chartYAxis(.hidden) .chartXScale(domain: startDate...endDate) // one year possibility to scroll back .chartXVisibleDomain(length: 3600 * 3) // 3 hours visible on screen .onAppear { Task { dataPoints = await TestData().generate(startDate: startDate) } } } } I would be grateful for any insights or suggestions on how to improve it or if it's planned to be improved in the future. Currently, I use UIKit CollectionView where we split the graph into smaller chunks of the graph and we present the SwiftUI Chart content in the cells, so we use the scrolling offered there. I wonder if it's possible to use native SwiftUI for such a scenario so that later on we could also implement some kind of lazy loading of the data as the user scrolls into the past.
0
0
141
2w
App freezes when built with Xcode 16 on iOS 18, but not on iOS 17 or lower, or with Xcode 15 on iOS 18
I'm experiencing an issue where my app freezes when built with Xcode 16 on iOS 18. Additional Information: The issue does not occur when building the app with Xcode 16 on iOS 17 or lower. The issue does not occur when building the app with Xcode 15 on iOS 18. The CPU usage spikes to 100% when the app freezes. The app specifically freezes after the code runs into .sink(receiveValue:). Here is the relevant code snippet: @Published var selectedCardData: CardData? @Published var selectedRootTab: RootViewTab = .statement override func load() { state = .loading $selectedCardData.ignoreNil() .removeDuplicates() .map { [unowned self] cardData in $selectedRootTab.filter { $0 == .statement } .first() .map { _ in cardData } } .switchToLatest() .sink(receiveValue: { value in print(value) // value not nil print("Execution reaches this point and the app freezes (CPU 100%).") }) .store(in: &cancellables) } Are there any known changes in iOS 18 or Xcode 16 that might affect this code?
2
2
237
2w
.matchedGeomtryEffect from View to Sheet.
I'm trying to accomplish something like this: https://x.com/mlaithv/status/1835041850236838265 But it seems like Apple uses a Private API to pull it off. Has anyone managed to create something similar? I know there is a package named Transmission that can do it but it seems hacky & I'm unfamiliar with UIKit. The source uses a "custom modal" but I'm not sure how.
0
0
142
2w
When using a SwiftData value for the customizationID of a TabView, that ID is not unique for a particular behavior.
In the customizationID modifier of the TabView, if I use an ID with a SwiftData value, it returns the following error when I open a new window on iPadOS: “Not unique” even when using a UUID. I can't do anything about it, so I'm posting it here. Any advice that could help me on the way to solving this problem would be appreciated. Thread 1: "Fatal: supplied item identifiers are not unique. Duplicate identifiers: {(\n "Tab.Custom.1C350509-C28A-4C41-85A0-3EA57779DB1B"\n)}" Below is a portion of the code that reproduces this phenomenon. I believe it is sufficient for your needs, but if you need more code, we can accommodate. struct ContentView: View { @Environment(\.modelContext) var modelContext @Query private var tags: [Tag] @AppStorage("Customization") private var customization: TabViewCustomization @State private var isAdding = false @State private var tagName = "" var body: some View { TabView { Tab("Home", systemImage: "house") { NavigationStack { Label("Home", systemImage: "house") .toolbar { ToolbarItem(placement: .primaryAction) { Button { isAdding.toggle() } label: { Label("Add Tag", systemImage: "plus") } } } } } .customizationID("Tab.Home") Tab("Workplace", systemImage: "building.2") { NavigationStack { Label("Workplace", systemImage: "building.2") .toolbar { ToolbarItem(placement: .primaryAction) { Button { isAdding.toggle() } label: { Label("Add Tag", systemImage: "plus") } } } } } .customizationID("Tab.Workplace") Tab("Apple Store", systemImage: "apple.logo") { NavigationStack { Label("Apple Store", systemImage: "apple.logo") .toolbar { ToolbarItem(placement: .primaryAction) { Button { isAdding.toggle() } label: { Label("Add Tag", systemImage: "plus") } } } } } .customizationID("Tab.AppleStore") TabSection { ForEach(tags) { tag in Tab(tag.name, systemImage: "tag") { NavigationStack { Label(tag.name, systemImage: "tag") .toolbar { ToolbarItem(placement: .primaryAction) { Button { isAdding.toggle() } label: { Label("Add Tag", systemImage: "plus") } } } } } .customizationID("Tab.Custom.\(tag.id.uuidString)") } } header: { Label("Custom", systemImage: "tag") } .customizationID("TabSection.AppleStore") .sectionActions { Button { isAdding.toggle() } label: { Label("Add Tag", systemImage: "plus") } } } .tabViewCustomization($customization) .tabViewStyle(.sidebarAdaptable) .alert("Create Tag", isPresented: $isAdding) { TextField("Tag Name", text: $tagName) Button("Cancel") { tagName = "" isAdding = false } Button("Done") { let tagList = tagName.components(separatedBy: .whitespaces) for tagItem in tagList { if tagItem != "" && tags.filter({return $0.name == tagItem}).isEmpty { let newTag = Tag(name: tagItem) modelContext.insert(newTag) } } try? modelContext.save() tagName = "" isAdding = false } } } } import SwiftData import Foundation @Model final class Tag: Identifiable { var id: UUID = UUID() var name: String = "" init(name: String) { self.id = UUID() self.name = name } } How to reproduce Implement the above code so that it works. Build the application on the actual iPad device using Xcode. Add tags (data) Open a new window and check the UI behavior. Correct Behavior The ID is not actually unique. Or, if it is unique, it should not freeze without generating an error. Actual Behavior The ID should be set to be unique, but it freezes with an error that it is not unique. Environment iPadOS 18 RC (22A3354) Xcode 16.0 (16A242)
0
0
179
2w
onScrollVisibilityChange for Lists
I cannot make the new onScrollVisibilityChange work within Lists. Is this an expected limitation or am I doing something wrong? Please see the sample code below. Thank you! struct TestView: View { @State var isTitleVisible: Bool = true var body: some View { List { Section { Text("Title") } .onScrollVisibilityChange { isVisible in self.isTitleVisible = isVisible } Section { ForEach((0..<100), id: \.self) { i in Text(i.formatted()) } /// Changes the background color from green to red once the title is no more visible .listRowBackground(isTitleVisible ? Color.green : .red) } } } }
2
0
134
2w
Issue with Keyboard Avoidance for Sheet View in SwiftUI
I am currently working on a comments section modeled after TikTok's/Instagram's comment sections for a media app. The view is a sheet view that is presented as follows: .sheet(isPresented: $showChat) { TakesChatView(viewModel: viewModel) .presentationDetents([.medium, .large]) .presentationDragIndicator(.hidden) .overlay( VStack { RoundedRectangle(cornerRadius: 2) .fill(Color.gray) .frame(width: 40, height: 5) .padding(.top, 15) .opacity(0.8) Label("Chat", systemImage: "message.badge") .lineLimit(nil) .padding(.top, 5) .padding([.leading, .trailing], 16) Divider() .padding(.top, 5) .padding([.leading, .trailing], 16) Spacer() } .frame(maxWidth: .infinity, alignment: .top) ) }.ignoresSafeArea(.keyboard, edges: .bottom) However, some issues arise regarding keyboard avoidance. Currently, when the user taps on the TextField to type a comment, the keyboard shifts the entire view upwards as it pops up. Instead, I need it where I can still view the comments without the keyboard affecting their placement when it pop up. Below is the associated code for the comment view: struct TakesChatView: View { @ObservedObject var viewModel: TakeCommentViewModel @FocusState var focus: Bool @State private var selectedMedia: [PhotosPickerItem] = [] @State private var selectedImageData: [Data] = [] @State private var selectedGIFData: [Data] = [] @State private var selectedVideoData: [Data] = [] var textFieldNotEmpty: Bool { !viewModel.textToPost.isEmpty || !selectedImageData.isEmpty || !selectedGIFData.isEmpty || !selectedVideoData.isEmpty } var body: some View { GeometryReader { _ in ZStack { VStack { contentView commentTextField .padding(.top,5) } } } .ignoresSafeArea(.keyboard, edges: .bottom) .background(Color.containerBackground) .onChange(of: viewModel.focus) { focus in self.focus = focus } .onChange(of: selectedMedia) { _ in loadMedia() } } var contentView: some View { VStack { Spacer().frame(height: 105) ScrollViewReader { scroll in ScrollView(showsIndicators: true) { ForEach(viewModel.comments, id: \.self) { comment in TakeCommentView( comment: comment, viewModel: self.viewModel ) .padding(.horizontal, 10) .id(comment.documentID) } .onChange(of: viewModel.commentAdded) { id in scroll.scrollTo(id, anchor: .bottom) viewModel.commentAdded = nil } } .scrollDismissesKeyboard(.immediately) } } } } extension TakesChatView { var commentTextField: some View { VStack { mediaPreview HStack { TextField("Type your comment", text: $viewModel.textToPost, axis: .vertical) .keyboardType(.twitter) .padding([.leading, .vertical], 6) .focused($focus) PhotosPicker(selection: $selectedMedia, matching: .any(of: [.images, .videos]), photoLibrary: .shared()) { ComposeType.media.image .frame(width: 40, height: 40, alignment: .center) } .onAppear { selectedMedia.removeAll() selectedImageData.removeAll() selectedGIFData.removeAll() selectedVideoData.removeAll() } postButton } .border(.lightGray, width: 1, cornerRadius: 10) .padding([.bottom, .horizontal], 10) } } } I have tried using .ignoresSafeAres(), .safeAreaInset(), and custom keyboard observer functions but to no avail. How do I fix this issue?
0
0
166
2w
Text Max Character Number SwiftUI
Hi, Some times long text damage the design of views it might go to a second row and increase view height and damage the overall design, so how to solve this issue, is there a way to set a Max Characters number for Text and TextField views in SwiftUI ? and maybe show few dots as used in some designs ? Kind Regards
1
0
156
2w
AppIntent - Widget & ControlWidget
Hey all, iOS 18 - RC I have an app that supports both Widgets and ControlWidget, which resides on the same AppIntent. The following sync works fine when any action is taken on any of the three players - App - Widget - both directions - works as expected App - Control Widget - both directions - works as expected However - Widget - ControlWidget - the UI not always sync in real time (the values are ok) So if for instance I increase a counter on the widget from 1 to 2, the comtrolwidget will still show 1, but if I tap it, it will increase to 3. For any update/action taken on the AppInten, I call - WidgetCenter.shared.reloadAllTimelines() ControlCenter.shared.reloadAllControls() Any idea how to ensure this sync? Thanks a lot! Dudi
1
0
237
2w
Is @ObservationIgnored necessary for private var?
In SwiftUI's ViewModel class that are @Observable, is it necessary to annotate private fields as @ObservationIgnored? I'm not sure if adding @ObservationIgnored to these fields will get performance gains, since there are no SwiftUI structs referencing these fields because they're private. However, I'd like to know what's the recommended approach here? While this might not seem obvious for the example below, however, sometimes I have private fields that are changing pretty frequently. For these frequently changed fields, I think the performance gains will be larger. Example: @Observable class UserProfileViewModel { var userName: String? var userPhoneNumber: String? private var isFetchingData = false } vs @Observable class UserProfileViewModel { var userName: String? var userPhoneNumber: String? @ObservationIgnored private var isFetchingData = false }
1
0
220
2w
Tab button's ax identifier is missing when using `.sidebarAdaptable` TabViewStyle
Hello, I found that if you apply the new .sidebarAdaptable tab view style, the accessibility identifiers of tab bar buttons are missing. import SwiftUI struct ContentView: View { var body: some View { TabView { Tab("Received", systemImage: "tray.and.arrow.down.fill") { Text("Received") } .accessibilityIdentifier("tab.received") // 👀 Tab("Sent", systemImage: "tray.and.arrow.up.fill") { Text("Sent") } .accessibilityIdentifier("tab.sent") // 👀 Tab("Account", systemImage: "person.crop.circle.fill") { Text("Account") } .accessibilityIdentifier("tab.account") // 👀 } .tabViewStyle(.sidebarAdaptable) // 👈 if remove this, ax identifiers are ok } } #Preview { ContentView() } The identifiers automatically appear after a few seconds. But this behaviour breaks a lot of the UI test cases.
2
0
152
2w
Lag When Dismissing Keyboard in ScrollView Inside NavigationStack in SwiftUI
I’m experiencing a lag in SwiftUI when dismissing the keyboard inside a ScrollView that’s within a NavigationStack. When the keyboard is opened and then dismissed, the view seems to lag as it resizes back to its original state. This issue occurs when the content is in a ScrollView. I’m using iOS 17, and the resizing of the content feels choppy after the keyboard interaction. Here’s a simplified version of my code: VStack { NavigationStack { ScrollView { createAllForm } .onAppear { if #available(iOS 17.0, *) { Self.addTripOpen.sendDonation() } } .toolbar { ToolbarItem(placement: .topBarLeading) { Button(action: { presentationMode.wrappedValue.dismiss() }, label: { Image(systemName: IconsEnum.closeIcon).foregroundColor(.gray) }) } } .toolbar { ToolbarItem(placement: .topBarTrailing) { Button(LocalizedText.create, action: { focusedField = nil withAnimation { addTripViewModel.creatingTrip = true addTripViewModel.addTripToFirebase(presentationMode: presentationMode) } }).disabled(addTripViewModel.disableCreate).foregroundColor(Color(addTripViewModel.disableCreate ? ColorsEnum.greyColor : ColorsEnum.tripBlue)) } } .navigationTitle(LocalizedText.createTrip) .navigationBarTitleDisplayMode(.inline) .isLoadingView(isLoading: addTripViewModel.creatingTrip) .alert(addTripViewModel.alertMessage, isPresented: $addTripViewModel.showingAlert) { Button(LocalizedText.acceptLabel, role: .cancel) { } } .onDisappear { if addTripViewModel.isCreated { processCompletedCount += 1 } if let currentAppVersion = Bundle.currentAppVersion, processCompletedCount >= 2, currentAppVersion != lastVersionPromptedForReview, addTripViewModel.isCreated { presentReview() lastVersionPromptedForReview = currentAppVersion } onDismiss(addTripViewModel.isCreated) } .sheet(isPresented: $addTripViewModel.showingAddUsers) { AddUsers().environmentObject(addTripViewModel) } } }
2
0
111
2w
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
6
8
342
2w
Struggling with Swift Gesture/Observation
I'm trying to create an equivalent to TabView, but with the difference that the 2nd View slides in over the top of the primary view. Maybe there's a more elegant way of coding this (suggestions appreciated), but I've almost succeeded using the dragGesture. When a user swipes right to left the observed variable showTab2 is set to true, and the 2nd tab glides in over the top of tab 1 and displays 🥳. The only problem is, that when a user happens to start the swipe over a button, the observed status (showTab2) does change as expected but the main view does not catch this change and does not display tab2. And that despite the showTab2 being an @Observable. Any clues what I've missed? Or how to capture that the start of a swipe gesture starts over the top of a button and should be ignored. According to the code in SwipeTabView this screenshot 👆 should never occur. Here's the code: @Observable class myclass { var showTab2 = false } struct SwipeTabView: View { @State var myClass = myclass() @State var dragAmount: CGSize = CGSize.zero var body: some View { VStack { ZStack { GeometryReader { geometryProxy in VStack { tab(tabID: 1, selectedTab: myClass.showTab2) .zIndex(/*@START_MENU_TOKEN@*/1.0/*@END_MENU_TOKEN@*/) .background(.black) .transition(.identity) .swipeable(stateOfViewAdded: $myClass.showTab2, dragAmount: $dragAmount, geometryProxy: geometryProxy, insertion: true) } if myClass.showTab2 || dragAmount.width != 0 { tab(tabID: 2, selectedTab: myClass.showTab2) .zIndex(2.0) .drawingGroup() .transition(.move(edge: .trailing)) .offset(x: dragAmount.width ) .swipeable(stateOfViewAdded: $myClass.showTab2, dragAmount: $dragAmount, geometryProxy: geometryProxy, insertion: false) } } } } } } extension View { func swipeable(stateOfViewAdded: Binding<Bool>, dragAmount: Binding<CGSize>, geometryProxy: GeometryProxy, insertion: Bool) -> some View { self.gesture( DragGesture() .onChanged { gesture in // inserting must be minus, but removing must be positive - hence the multiplication. if gesture.translation.width * (insertion ? 1 : -1 ) < 0 { if insertion { dragAmount.wrappedValue.width = geometryProxy.size.width + gesture.translation.width } else { dragAmount.wrappedValue.width = gesture.translation.width } } } .onEnded { gesture in if abs(gesture.translation.width) > 100.0 && gesture.translation.width * (insertion ? 1 : -1 ) < 0 { withAnimation(.easeOut.speed(Double(gesture.velocity.width))) { stateOfViewAdded.wrappedValue = insertion } } else { withAnimation(.easeOut.speed(Double(gesture.velocity.width))) { stateOfViewAdded.wrappedValue = !insertion } } withAnimation(.smooth) { dragAmount.wrappedValue = CGSize.zero } } ) } } struct tab: View { var tabID: Int var selectedTab: Bool var body: some View { ZStack { Color(tabID == 1 ? .yellow : .orange) VStack { Text("Tab \(tabID) ").foregroundColor(.black) Button(action: { print("Tab2 should display - \(selectedTab.description)") }, label: { ZStack { circle label } }) Text("Tab2 should display - \(selectedTab.description)") } } } var circle: some View { Circle() .frame(width: 100, height: 100) .foregroundColor(.red) } var label: some View { Text("\(tabID == 1 ? ">>" : "<<")").font(.title).foregroundColor(.black) } }
4
0
233
2w
SwiftUI Document-based apps crash in iOS 18
I am trying to integrate the new iOS 18 document launching experience with my app, but opening or saving files fails with the RC version of Xcode 16. The error I receive is "Publishing changes from background threads is not allowed." I downloaded Apple's new sample code ("Writing app"), and it fails with the same error. Is there an easy fix for this?
3
0
239
2w