Consider the following example of a List view containing sections of rows.
import SwiftUI
struct ListSectionView: View {
@State private var sectionHeaders = ["section1", "section2", "section3"]
var body: some View {
List {
ForEach(sectionHeaders, id: \.self) { sectionHeader in
Section(header: Text(sectionHeader)) {
Text("1")
Text("2")
Text("3")
}
}
.onMove { indices, newOffset in
// ...
}
}
}
}
I would like to reorder the sections within the list by dragging the respective section header to its new position in the list - similar to moving individual rows via onMove-dragging but with the sections instead of the rows.
The above approach does not work. It "activates" moving the rows and then the .onMove code acts on those. The sections themselves are not moved.
How can I move/reorder the sections within the list?
Thanks.
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
I have been working on a feature, where I have a List in SwiftUI with previous and next data loading, user can scroll up and down to load previous/next page data.
Recently, I faced one accessibility issue while testing voice-over, when user lands on the listing screen and swipe across the screen from navigation and when focus comes on list it should highlight the first item visible.
But when user swipes back:
Should it load the previous data and announce the previous item or it should go back to the navigation items?
If it loads the previous item, what if the user wants to go to the navigation to switch to other actions and vice-versa?
Did anyone come across this kind of issue? What can be the standard expected behavior in this case if list has both previous and next page scroll?
I different tried gestures https://support.apple.com/en-in/guide/iphone/iph3e2e2281/ios, but it isn't working
Hello,
given this following simple SwiftUI setup:
struct ContentView: View {
var body: some View {
CustomFocusView()
}
}
struct CustomFocusView: View {
@FocusState private var isFocused: Bool
var body: some View {
color
.frame(width: 128, height: 128)
.focusable(true)
.focused($isFocused)
.onTapGesture {
isFocused.toggle()
}
.onKeyPress("a") {
print("A pressed")
return .handled
}
}
var color: Color {
isFocused ? .blue : .red
}
}
If I run this via Mac – Designed for iPad, the CustomFocusView toggles focus as expected and cycles through red and blue.
Now if I run this same exact code via Mac Catalyst absolutely nothing happens and so far I wasn't able to ever get this view to accept focused state. Is this expected? I would appreciate if anyone could hint me on how to get this working.
Thank and best regards!
Take a look at this simple code:
import Cocoa
import SwiftUI
struct DemoView: View {
var body: some View {
Text("Click me!")
.onTapGesture {
print("Clicked")
}
}
}
class FlippedView: NSView {
override var isFlipped: Bool {
return true
}
}
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let stackView = NSStackView()
stackView.orientation = .vertical
stackView.alignment = .leading
stackView.spacing = 0
stackView.translatesAutoresizingMaskIntoConstraints = false
let hostView = NSHostingView(rootView: DemoView())
stackView.addArrangedSubview(hostView)
let scrollView = NSScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
let flippedView = FlippedView()
flippedView.addSubview(stackView)
scrollView.documentView = flippedView
view.addSubview(scrollView)
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
}
I need my scroll view to start at the very top, so i put it inside a flipped document view.
But now .onTapGesture does not fire.
Hi, I'm trying to make a weather menu bar app, and I want to have it so that the icon of the app in the menu changes with the actual weather, but the icon isn't showing up. There is still a space in the menu bar where I can click and open the app, it's just that the icon has disappeared. Any ideas to fix it?
Hi, everyone
I have an app already in production that uses SwiftUI's lifecycle (paired with an AppDelegate). Due to some specific behaviour of the app, we decided to migrate the app to use UIKit's lifecycle, adding the corresponding SceneDelegate to the app, as well as modifying the Info.plist file accordingly to accommodate to these new changes.
Although everything seems to work when installing the app from zero, when installing it on top of another version, the screen goes black and the user cannot interact with the app at all unless they reinstall it completely. As I've read online, iOS is reusing the window configuration from the previous execution of the app. I know this because the AppDelegate's application(application:connectingSceneSession:options) is not being called when coming from a previous version of the app.
I would love to know what can I do to make this work because, as you may understand, we cannot ask our user base to reinstall the application.
Thank you very much.
Hi, Im new to SwiftUI and Im trying to implement some drag and drop functionality for some tabs in my application.
Im using .draggable(_) and .dropDestination for this and the issue I have is that as I drag the view, the mouse cursor changes to the copy cursor with the green plus sign and I don't like it but I can't figure out how to avoid it.
Any help would be appreciated.
In the attached code snippet:
struct ContentView: View {
@State private var vText: String = ""
var body: some View {
TextField("Enter text", text: Binding(
get: { vText },
set: { newValue in
print("Text will change to: \(newValue)")
vText = newValue
}
))
}
}
I have access to the newValue of the text-field whenever the text-field content changes, but how do I detect which key was pressed? I can manually get the diff between previous state and the new value to get the last pressed char but is there a simpler way? Also this approach won't let me detect any modifier keys (such as Alt, Ctrl etc) that the user may have pressed.
Is there a pure swift-ui approach to detect these key presses?
I'm using Xcode 14.3.1 on macOS 13.5, and I've managed to reproduce my issue in a trivial application. All the project settings are left at the defaults for a macOS project.
It looks like using a GroupBox breaks the ability of XCTest to find popovers connected to buttons (I suspect any UI element) inside the GroupBox.
The debug console output from the code below lists 15 descendants from my window with the outside-the-GroupBox popover open, and one of them is definitely a popover. With the inside-the-GroupBox popover open, my window only shows nine descendants, and no popover (the rest of the difference is the popover's contents). It's simple enough I don't see what I could be doing wrong:
import SwiftUI
@main
struct GroupBox_Popover_DemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State var outsidePopoverPresented: Bool = false
@State var insidePopoverPresented: Bool = false
var body: some View {
VStack {
Button("Outside GroupBox") {
outsidePopoverPresented = true
}
.popover(isPresented: $outsidePopoverPresented,
attachmentAnchor: .point(.leading),
arrowEdge: .leading) {
Popover(selected: .constant("Item A"), isPresented: $outsidePopoverPresented)
}
.padding()
GroupBox {
Button("Inside GroupBox") {
insidePopoverPresented = true
}
.popover(isPresented: $insidePopoverPresented,
attachmentAnchor: .point(.leading),
arrowEdge: .leading) {
Popover(selected: .constant("Item B"), isPresented: $insidePopoverPresented)
}
.padding()
}
}
.padding()
}
}
struct Popover: View {
@Binding var selected: String
@Binding var isPresented: Bool
var body: some View {
VStack(alignment: .leading) {
Picker("", selection: $selected) {
Text("Item A").tag("Item A")
Text("Item B").tag("Item B")
Text("Item C").tag("Item C")
}
.pickerStyle(.radioGroup)
HStack {
Spacer()
Button("Cancel") {
isPresented = false
}
}
}
.padding()
.frame(width: 200)
}
}
Then in my UI tests:
import XCTest
final class GroupBox_Popover_DemoUITests: XCTestCase {
let mainWindow = XCUIApplication().windows
override func setUpWithError() throws {
continueAfterFailure = false
XCUIApplication().launch()
}
func testPopovers() {
let myDescendants = mainWindow.descendants(matching: .any)
mainWindow.buttons["Outside GroupBox"].click()
print("Window descendants with outside popover open:")
print(myDescendants.debugDescription)
mainWindow.popovers.buttons["Cancel"].click()
mainWindow.buttons["Inside GroupBox"].click()
print("Window descendants with inside popover open:")
print(myDescendants.debugDescription)
mainWindow.popovers.buttons["Cancel"].click()
XCTAssert(true, "Test was able to hit cancel on both popovers.")
}
}
Any ideas? Have I missed unchecking some "Ignore anything in a GroupBox" checkbox somewhere?
Hey,
We're loading data from Core Data, and for some reason an error is thrown. This is happening extremely rarely and we haven't been able to reproduce it.
The error thrown has the following description:
Åtgärden kunde inte slutföras. (ScreenGenieCore.EnrolledView.(unknown context at $10087af4c).EnrolledError fel 0.)
It is occurring in an app written in SwiftUI when the user taps a button. The managed object context is initiated in app init and provided to the view using the @environment modifier. So the viewContext should always exist. Still it throws an error saying unknown context ....
Any guidance or possible things to investigate would be much appreciated.
I’ve submitted the following feedback:
FB13820942 (List Outline View Not Using Accent Color on Disclosure Caret for visionOS)
I’d appreciate help on this to see if I’m doing something wrong or indeed it’s the way visionOS currently works and it’s a suggested feedback.
I have a DocumentGroup working with a FileDocument, and that's fine.
However, when someone creates a new document I want them to have to immediately save it. This is the behavior on ipadOS and iOS from what I can understand (you select where before the file is created).
There seems to be no way to do this on macOS?
I basically want to have someone:
create a new document
enter some basic data
hit "create" which saves the file
then lets the user start editing it
(1), (2), and (4) are done and fairly trivial.
(3) seems impossible, though...?
This really only needs to support macOS but any pointers would be appreciated.
In iOS 16, VoiceOver no longer speaks numbers such as “¥1,230" in Japanese, which worked correctly in iOS 15 and earlier. This is a very important bug in Accessibility support.
Steps to reproduce
Create iOS App Project on Xcode. (I used Xcode 14)
Set the Development Localization to "Japanese" on the project settings. (to make VoiceOver speak Japanese)
For example, write the following code.
Build and run the App targeting the actual device, not the simulator.
Actual devices running iOS 15 or earlier will correctly read out loud with VoiceOver, while iOS 16 will have some reading problems.
import SwiftUI
@main
struct VoiceOverProblemApp: App {
var body: some Scene {
WindowGroup {
// on project settings, set the development localization to Japanese.
VStack {
// ✅ said "Hello, world!"
Text("Hello, world!")
// ✅ said "残高 (ざんだか, zan-daka)"
Text("残高")
// ❌ said nothing (until iOS 15 or earlier, said "千二百三十円 (せんにひゃくさんじゅうえん, sen-nihyaku-san-ju-yen)"
Text("¥1,230")
// ❌ said wrong
// correct (iOS 15 or earlier): "残高は千二百三十円です (zan-daka wa sen-nihyaku-san-ju-yen desu)"
// incorrect (iOS 16) : "残高はです (zan-daka wa desu)"
Text("残高は ¥1,230 です")
}
}
}
}
The above sample code uses SwiftUI's Text but the same problem occurs with UIKit's UILabel.
I made an animation in Blender using geometry nodes that I exported to USDC file (then I used Reality Converter to convert to USDZ) and I can see the animation when viewing from the finder but does not play after importing to RCP. Any idea how I can play the animation? Or can the animation be accessed through Xcode?
Thanks!
I have a SwiftUI based app. For lots of reasons I was forced to use NSDocument instead of using DocumentGroup.
I configure the main menu in my AppDelegate.
It has the following code:
let fileMenuItem = NSMenuItem()
let fileMenu = NSMenu(title: "File")
fileMenu.addItem(withTitle: "New", action: #selector(NSDocumentController.newDocument(_:)), keyEquivalent: "n")
fileMenu.addItem(withTitle: "Open...", action: #selector(NSDocumentController.openDocument(_:)), keyEquivalent: "o")
The New and Open work as expected.
It is my understanding that the NSDocumentController should automatically add the "Open Recent" menu when it sees the Open action based on the NSDocumentController.
It is not appearing.
When I print the state of the recent documents using
print("recent documents \(NSDocumentController.shared.recentDocumentURLs), maximum \(NSDocumentController.shared.maximumRecentDocumentCount)")
I see the recent document urls and a count of 10.
What can I do to make the menu appear?
Thanks for the help.
Hi,
I'm trying to fix tvOS view for VoiceOver accessibility feature:
TabView { // 5 tabs
Text(title)
Button(play)
ScrollView { // Live
LazyHStack { 200 items }
}
ScrollView { // Continue watching
LazyHStack { 500 items }
}
}
When the view shows up VoiceOver reads:
"Home tab 1 of 5, Item 2" - not sure why it reads Item 2 of the first cell in scroll view, maybe beacause it just got loaded by LazyHStack.
VocieOver should only read "Home tab 1 of 5"
When moving focus to scroll view it reads:
"Live, Item 1" and after slight delay "Item 1, Item 2, Item 3, Item 4"
When moving focus to second item it reads:
"Item 2" and after slight delay "Item 1, Item 2, Item 3, Item 4"
When moving focus to third item it reads:
"Item 3" and after slight delay "Item 1, Item 2, Item 3, Item 4"
It should be just reading what is focused, idealy just
"Live, Item 1, 1 of 200"
then after moving focus on item 2
"Item 2, 2 of 200"
this time without the word "Live" because we are on the same scroll view (the same horizontal list)
Currently the app is unusable, we have visually impaired testers and this rotor reading everything on the screen is totaly confusing, because users don't know where they are and what is actually focused.
This is a video streaming app and we are streaming all the time, even on home page in background, binge plays one item after another, usually there is never ending Live stream playing, user can switch TV channel, but we continue to play. Voice over should only read what's focused after user interaction.
Original Apple TV app does not do that, so it cannot be caused by some verbose accessibility settings. It reads correctly only focused item in scrolling lists.
How do I disable reading content that is not focused?
I tried:
.accessibilityLabel(isFocused ? title : "")
.accessibilityHidden(!isFocused)
.accessibilityHidden(true) - tried on various levels in view hierarchy
.accessiblityElement(children: .ignore) - even focused item is not read back by voice over
.accessiblityElement(children: .ignore) - even focused item is not read back by voice over
.accessiblityElement(children: .contain) - tried on various levels in view hierarchy
.accessiblityElement(children: .combine) - tried on various levels in view hierarchy
.accessibilityAddTraits(.isHeader) - tried on various levels in view hierarchy
.accessibilityRemoveTraits(.isHeader) - tried on various levels in view hierarchy
// the last 2 was basically an attempt to hack it
.accessibilityRotor("", ranges []) - another hack that I tried on ScrollView, LazyHStack, also on top level view.
50+ other attempts at configuring accessibility tags attached to views.
I have seen all the accessibility videos, tried all sample code projects, I haven't found a solution anywhere, internet search didn't find anything, AI didn't help as it can only provide code that someone else wrote before.
Any idea how to fix this?
Thanks.
There's an easily reproducible SwiftUI bug on macOS where an app's UI state no longer updates/re-renders for "Designed for iPad" apps (i.e. ProcessInfo.processInfo.isiOSAppOnMac == true). The bug occurs in Xcode and also if the app is running independent of Xcode.
The bug occurs when:
the user Hides the app (i.e. it goes into the background)
the user puts the Mac to sleep (e.g. Apple menu > Sleep)
a total of ~60 seconds transpires (i.e. macOS puts the app into the "suspended state")
when the app is brought back into the foreground the UI no longer updates properly
The only way I have found to fix this is to manually open a new actual full app window via File > New, in which case the app works fine again in the new window.
The following extremely simple code in a default Xcode project illustrates the issue:
import SwiftUI
@main
struct staleApp: App {
@State private var isBright = true
var body: some Scene {
WindowGroup() {
ZStack {
(isBright ? Color.white : Color.black).ignoresSafeArea()
Button("TOGGLE") { isBright.toggle(); print("TAPPED") }
}
.onAppear { print("\(isBright ? "light" : "dark") view appeared") }
}
}
}
For the code above, after Hiding the app and putting the computer to sleep for 60 seconds or more, the button no longer swaps views, although the print statements still appear in the console upon tapping the button. Also, while in this buggy state, i can get the view to update to the current state (i.e. the view triggered by the last tap) by manually dragging the corner of the app window to resize the window. But after resizing, the view again does not update upon button tapping until I resize the window again.
so it appears the diff engine is mucked or that the Scene or WindowGroup are no longer correctly running on the main thread
I have tried rebuilding the entire view hierarchy by updating .id() on views but this approach does NOT work. I have tried many other options/hacks but have not been able to reset the 'view engine' other than opening a new window manually or by using: @Environment(.openWindow) private var openWindow
openWindow could be a viable solution except there's no way to programmatically close the old window for isiOSAppOnMac (@Environment(.dismissWindow) private var dismissWindow doesn't work for iOS)
Seeing weird sequences of changes when locking the screen when view is visable.
.onChange(of: scenePhase) { phase in
if phase == .active {
if UIApplication.shared.applicationState == .active {
print("KDEBUG: App genuinely became active")
} else {
print("KDEBUG: False active signal detected")
}
} else if phase == .inactive {
print("KDEBUG: App became inactive")
// Handle inactive state if needed
} else if phase == .background {
print("KDEBUG: App went to background")
// Handle background state if needed
}
}
seen:
(locks screen)
KDEBUG: App became inactive
KDEBUG: App genuinely became active
KDEBUG: App went to background
expected
(locks screen)
KDEBUG: App became inactive
KDEBUG: App went to background
According to the docs tvOS 18+ supports the new NavigationTransition and the matchedTransitionSource and navigationTransition(.zoom(sourceID: id, in: namespace)) modifiers, however they don't seems to work.
Taking the DestinationVideo project example from the latest WWDC the matchedTransitionSourceis marked with #if os(iOS)
Is it supported by tvOS or is it for iOS only?
https://developer.apple.com/documentation/swiftui/view/navigationtransition(_:)
https://developer.apple.com/documentation/swiftui/view/matchedtransitionsource(id:in:configuration:)
When using FileImporter in SwiftUI, the following error is always returned when closed; even if the user taps "Cancel"
The view service did terminate with error: Error Domain=_UIViewServiceErrorDomain Code=1 "(null)" UserInfo={Terminated=disconnect method}
Recreation rate is 10/10. It feels like a threading issue, but in SwiftUI we are leveraging the .fileImporter modifier, so we cannot hold on to the reference like we would in a class.
Is there a different approach we should be using for this?
Code for recreation
import SwiftUI
struct ContentView: View {
@State private var fileURL: URL?
@State private var showFileImporter: Bool = false
var body: some View {
VStack {
if let fileURL {
Text(fileURL.absoluteString)
}
Button {
showFileImporter = true
} label: {
Text("Select PDF")
}
.fileImporter(
isPresented: $showFileImporter,
allowedContentTypes: [.pdf],
allowsMultipleSelection: true
) { result in
switch result {
case .success(let files):
files.forEach { file in
let gotAccess = file.startAccessingSecurityScopedResource()
if !gotAccess { return }
fileURL = file
file.stopAccessingSecurityScopedResource()
}
case .failure(let error):
print(error)
}
}
}
}
}