With the new multi-windowing design in iPadOS 26, the behavior of openWindow() has changed.
In iPadOS 18, if I called openWindow(), I would go from a full-screen window to two side-by-side windows. That allowed my app to meet the goal of the user; keep some information on the screen while being able to navigate through the app in the other window.
In iPadOS 26 (beta 3), this no longer works. Instead, a new Window opens ON top of the current window. I am looking for some mechanism to help the user see that two windows are now present and then easily move them on the screen (tiled, side-by-side) or whatever else they would prefer.
SwiftUI
RSS for tagProvide views, controls, and layout structures for declaring your app's user interface using SwiftUI.
Posts under SwiftUI tag
200 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Is there any way to change the color of default items like Back button or Search?
Even if I apply .tint() to a view these items in the .toolbar are always in primary color.
The bane of my existence has been designing interfaces where the whole view needs to scroll, but a portion is a List and the other portion is static.
I run into this problem time and again so I was hoping someone has a good solution because we all know that embedding a List view inside ScrollView is a no-go within SwiftUI. It simply doesn't work.
So what is a best practice when you need the whole screen to scroll, but a portion is a List? Use a navigation stack instead of a ScrollView? What if it's a child view of a navigation stack already?
I have a More button in my nav bar that contains a Delete action, and when you tap that I want to show a confirmation dialog before performing the deletion. In order words, I have a toolbar containing a toolbar item containing a menu containing a button that when tapped needs to show a confirmation dialog.
In iOS 26, you're supposed to add the confirmationDialog on the view that presents the action sheet so that it can point to the source view or morph out of it if it's liquid glass. But when I do that, the confirmation dialog does not appear. Is that a bug or am I doing something wrong?
struct ContentView: View {
@State private var showingDeleteConfirmation = false
var body: some View {
NavigationStack {
Text("👋, 🌎!")
.toolbar {
ToolbarItem {
Menu {
Button(role: .destructive) {
showingDeleteConfirmation.toggle()
} label: {
Label("Delete", systemImage: "trash")
}
.confirmationDialog("Are you sure?", isPresented: $showingDeleteConfirmation) {
Button(role: .destructive) {
print("Delete it")
} label: {
Text("Delete")
}
Button(role: .cancel, action: {})
}
} label: {
Label("More", systemImage: "ellipsis")
}
}
}
}
}
}
Is there a SwiftUI version of NSControl.allowsExpansionToolTips? That is, showing a tool tip with the full text when (and only when) a text item is truncated?
Or do I need to use a hosting view to get that behavior?
I'm developing a calculator app and working to ensure a great experience for both VoiceOver and Braille display users.
For expressions like (2+3)×5, I need two different accessibility outputs:
VoiceOver (spoken): A descriptive string like “left paren two plus three right paren times five,” provided via .accessibilityValue. I'm using a custom spellOut function since VoiceOver doesn't announce parentheses—which are kind of important when doing math!
Braille (symbolic): The literal math string (2+3)×5, provided using .accessibilityCustomContent("", ...), with an empty label so it’s not spoken aloud.
The issue: I don’t have access to a Braille display device and Xcode’s Accessibility Inspector doesn’t seem to show the custom content.
Is there any way to confirm that custom Braille content is being set correctly in Simulator or with other tools?
Or…is there a "math mode" in VoiceOver that forces it to announce parentheses?
Any advice or workarounds would be much appreciated!
Thanks,
Uhl
Topic:
Accessibility & Inclusion
SubTopic:
General
Tags:
External Accessory
iOS
Accessibility
SwiftUI
The Java Swing and AWT MVC model made it easy to develop complex UIs with data interactions that were not described readily in a nested layer that SwiftUI demands. The implicit update model of SwiftUI greatly complicates development of applications that often requires nested components to have to know too much about other components and other structures than their own, because button events and other user interactions cannot readily alter state across layers. A button push on one component then has to be knowledgable about state in other components which have to have that state represented as @State or @Binding etc. and this causes all kinds of wiring to be spread all over the place rather than have a more centralized "state management function" that would be able to look at the world and synchronize the UIs state across changes.
The fact that the compiler get's lost in the weeds when types and signatures don't match in deeper component structures doesn't help because it makes it doubly hard to do refactoring to raise and lower state management within the structure readily, because the compiler just cannot simply tell you that a function or constructor signature is no longer correct.
Again and and again, I reach the point in a new application where I need to make structural changes in components and my data model, and the SwiftUI compiler fails to compile and just reports "I'm lost in the weeds", with no indication of what it was last working on, aside from a particular level in a multi-layered nested UI.
This typically happens when a sub-views construction is not coded correctly because I changed that view and am looking for what broke, by just letting the compiler tell me what is not compatible. This is how refactoring has been done for ages and it's just amazingly frustrating that Apple engineers don't seem to understand nor care about this issue enough to fix it.
Why does this problem persist through version after version of SwiftUI? Is no-one actually using it for anything?
Hello,
I was doing some tasks, and then noticed a small lag/delay when tapping on a Secure field, I tried to investigate it, and noticed this was not my app issue, so I got it into a Playground and the issue is there (Is there in Physical devices, simulator, playground, iPad playground)
So I suppose this can be SwiftUI Issue:
import SwiftUI
struct ContentView: View {
@State var field1: String = ""
@State var field2: String = ""
@State var field3: String = ""
var body: some View {
VStack {
TextField("", text: $field1, prompt: Text("User"))
SecureField("", text: $field2, prompt: Text("pass"))
SecureField("", text: $field3, prompt: Text("uvv"))
}
}
}
So When the focus is set on Field1 TextField, and then you tap the second field, there is a small delay (Even in simulator, there is a small jump trying to show the keyboard, and in an iPad with physical keyboard, the on-screen keyboard is shown).
The console only shows this message:
Cannot show Automatic Strong Passwords for app bundleID: ... due to error: Cannot save passwords for this app. Make sure you have set up Associated Domains for your app and AutoFill Passwords is enabled in Settings
If you change the order of the elements, or some types, this lag disappears. (For example, adding first the SecureField : [SecureField, TextField, SecureField] the Issue disappears.)
(Even tried to add textContentType as password, newPassword and emailAddress without helping any bit.
I've been trying out the new .safeAreaBar modifier for iOS 26, but I cannot seem to notice any difference between that and .safeAreaInset?
The documentation says:
the bar modifier configures the content to support views to automatically extend the edge effect of any scroll view’s the bar adjusts safe area of.
But I can't seem to see that in action.
Hey there,
since SceneView has been marked as „deprecated“ for SwiftUI, I‘m wondering which alternatives should be considered for the following situation:
I have a SwiftUI app (for iOS and iPadOS) where users can view (with rotate, scale, move gestures) 3D models (USDZ) in a scene. The models will be downloaded from web backend and called via local URL paths.
What I tested:
I‘ve tried ARView in .nonAR mode, RealityView, however I didn‘t get the expected response -> User can rotate, scale the 3D models in a virtual space.
ARView in nonAR mode still shows the object like in normal AR mode without camera stream.
I tried to add Gestures to the RealityView on iOS - loading USDZ 3D models worked but the gestures didn’t).
Model3D is only available for visionOS (that would be amazing to have it for iOS)
I also checked QuickLook Preview however it works pretty strange via Filepicker etc, which is not the way how the user should load the 3D models in my app.
Maybe I missed something, I couldn’t find anything which can help me. I‘m pretty much stucked adopting the latest and greatest frameworks/APIs in my App and taking the next steps porting my app to visionOS.
Long story short 😃:
Does someone have an idea what is the alternative to SceneView for USDZ 3D models?
I appreciate your support!!
Thanks in advance!
Hello,
I have a simple example using StateObject and List. When I bind the List(selection:) to a property of the StateObject like this:
List(selection: $viewModel.selectedIndex) { ... }
I noticed that each time I push the view using a NavigationLink, a new instance of the StateObject is created. However, when I pop the view, the deinit of the StateObject is not called.
When is deinit actually expected to be called in this case?
Example code:
import SwiftUI
@main
struct NavigationViewDeinitSampleApp: App {
var body: some Scene {
WindowGroup {
NavigationStack {
ContentView()
}
}
}
}
struct Item: Hashable {
let text: String
}
@MainActor
fileprivate class ContentViewModel: ObservableObject {
@Published var selectedIndex: Int? = nil
init() {
NSLog("ContentViewModel.init")
}
deinit {
NSLog("ContentViewModel.deinit")
}
}
struct ContentView: View {
@StateObject private var model = ContentViewModel()
let items: [Item] = {
return (0...10).map { i in
Item(text: "\(i)")
}
}()
var body: some View {
List(selection: $model.selectedIndex) {
ForEach(items.indices, id: \.self) { idx in
let item = items[idx]
NavigationLink {
ContentView()
} label: {
Text(item.text)
}
}
}
}
}
Interestingly, if I instead use a plain @State variable inside the View:
@State private var selectedIndex: Int?
...
List(selection: $selectedIndex) { ... }
Then the deinit of the StateObject does get called when the view is popped.
Because there's no sign of deinit being triggered in the first pattern, I’m starting to suspect this might be a SwiftUI bug. Has anyone seen this behavior or have more information about it?
Thanks in advance.
Environment:
Xcode: 16.4(16F6)
iOS Simulator: iPhone SE3 iOS16.4(20E247),iPhone SE3 iOS 18.4(22E238)
My iphone 11 pro max in ios 26 is not a liquid glass like other
I have more than five tabs in a TabView, and on some screens, I'm seeing a navigation bar with the title "More". I understand that this is the default behavior of TabView when there are more than five tabs—iOS automatically creates a "More" screen.
I've set navigationBarHidden = true throughout my app, but the navigation bar still appears on some screens within the "More" section. Is there another way to hide the moreNavigationController or completely remove the navigation bar from the "More" screen?
I found the Table with Toggle will have performance issue when the data is large.
I can reproduce it in Apple demo:
https://developer.apple.com/documentation/swiftui/building_a_great_mac_app_with_swiftui
Replace with a large mock data, for example
database.json
Try to scroll the table, it's not smooth.
I found if I delete the Toggle, the performance be good.
TableColumn("Favorite", value: \.favorite, comparator: BoolComparator()) { plant in
Toggle("Favorite", isOn: $garden[plant.id].favorite)
.labelsHidden()
}
Is this bug in SwiftUI? Any workaround?
My Mac is Intel, not sure it can repro on Apple Silicon
How to remove the black background for both navigation bar and tab bar when scrolling.
When in light mode, it looks fine.
How do you create a toolbar item using the standard system close button or prominent done (✔️) button in SwiftUI?
In UIKit UIBarButtonItem provides .close and .done system items, and to get the tinted checkmark for done you set style = .prominent.
Cannot select individual view in selectable mode in Xcode preview, it only show the whole blue device border.
Hi,
In my apps, the recent iOS 16.0 beta 7 (20A5356a) broke the .timer DateStyle property of the Text view, in a SwiftUI widget.
In previous OS and beta, Text(Date(), style: .timer) was correctly displaying an increasing counter.
In iOS 6.0 beta 7, Text(Date(), style: .timer) does not update anymore, (and is offset to the left).
The other DateStyle (like .offset, .relative, ...) seems to update correctly.
Anyone noticed that (very specific) problem ?
I have a discrete scrubber implementation (range 0-100) using ScrollView in SwiftUI that fails on the end points. For instance, scrolling it all the way to bottom shows a value of 87 instead of 100. Or if scrolling down by tapping + button incrementally till it reaches the end, it will show the correct value of 100 when it reaches the end. But now, tapping minus button doesn't scrolls the scrubber back till minus button is clicked thrice.
I understand this has only to do with scroll target behaviour of .viewAligned but don't understand what exactly is the issue, or if its a bug in SwiftUI.
import SwiftUI
struct VerticalScrubber: View {
var config: ScrubberConfig
@Binding var value: CGFloat
@State private var scrollPosition: Int?
var body: some View {
GeometryReader { geometry in
let verticalPadding = geometry.size.height / 2 - 8
ZStack(alignment: .trailing) {
ScrollView(.vertical, showsIndicators: false) {
VStack(spacing: config.spacing) {
ForEach(0...(config.steps * config.count), id: \.self) { index in
horizontalTickMark(for: index)
.id(index)
}
}
.frame(width: 80)
.scrollTargetLayout()
.safeAreaPadding(.vertical, verticalPadding)
}
.scrollTargetBehavior(.viewAligned)
.scrollPosition(id: $scrollPosition, anchor: .top)
Capsule()
.frame(width: 32, height: 3)
.foregroundColor(.accentColor)
.shadow(color: .accentColor.opacity(0.3), radius: 3, x: 0, y: 1)
}
.frame(width: 100)
.onAppear {
DispatchQueue.main.async {
scrollPosition = Int(value * CGFloat(config.steps))
}
}
.onChange(of: value, { oldValue, newValue in
let newIndex = Int(newValue * CGFloat(config.steps))
print("New index \(newIndex)")
if scrollPosition != newIndex {
withAnimation {
scrollPosition = newIndex
print("\(scrollPosition)")
}
}
})
.onChange(of: scrollPosition, { oldIndex, newIndex in
guard let pos = newIndex else { return }
let newValue = CGFloat(pos) / CGFloat(config.steps)
if abs(value - newValue) > 0.001 {
value = newValue
}
})
}
}
private func horizontalTickMark(for index: Int) -> some View {
let isMajorTick = index % config.steps == 0
let tickValue = index / config.steps
return HStack(spacing: 8) {
Rectangle()
.fill(isMajorTick ? Color.accentColor : Color.gray.opacity(0.5))
.frame(width: isMajorTick ? 24 : 12, height: isMajorTick ? 2 : 1)
if isMajorTick {
Text("\(tickValue * 5)")
.font(.system(size: 12, weight: .medium))
.foregroundColor(.primary)
.fixedSize()
}
}
.frame(maxWidth: .infinity, alignment: .trailing)
.padding(.trailing, 8)
}
}
#Preview("Vertical Scrubber") {
struct VerticalScrubberPreview: View {
@State private var value: CGFloat = 0
private let config = ScrubberConfig(count: 20, steps: 5, spacing: 8)
var body: some View {
VStack {
Text("Vertical Scrubber (0–100 in steps of 5)")
.font(.title2)
.padding()
HStack(spacing: 30) {
VerticalScrubber(config: config, value: $value)
.frame(width: 120, height: 300)
.background(Color(.systemBackground))
.border(Color.gray.opacity(0.3))
VStack {
Text("Current Value:")
.font(.headline)
Text("\(value * 5, specifier: "%.0f")")
.font(.system(size: 36, weight: .bold))
.padding()
HStack {
Button("−5") {
let newValue = max(0, value - 1)
if value != newValue {
value = newValue
UISelectionFeedbackGenerator().selectionChanged()
}
print("Value \(newValue), \(value)")
}
.disabled(value <= 0)
Button("+5") {
let newValue = min(CGFloat(config.count), value + 1)
if value != newValue {
value = newValue
UISelectionFeedbackGenerator().selectionChanged()
}
print("Value \(newValue), \(value)")
}
.disabled(value >= CGFloat(config.count))
}
.buttonStyle(.bordered)
}
}
Spacer()
}
.padding()
}
}
return VerticalScrubberPreview()
}