Explore the various UI frameworks available for building app interfaces. Discuss the use cases for different frameworks, share best practices, and get help with specific framework-related questions.

All subtopics
Posts under UI Frameworks topic

Post

Replies

Boosts

Views

Activity

AccessorySetupKit - Removing accessory inconsistencies
Hello everyone, I am using the new AccessorySetupKit, and whenever I try to remove an accessory I sometimes get an alert asking me to make sure if I want to remove the accessory from my device. The problem is that this alert sometimes shows up, and sometimes it does not. The removal of the accessory does work, it is just that the alert is not predictable. Is this expected behavior? How can we “predict” when it will show up and when it will not?
0
0
444
Sep ’24
10162: The SwiftUI cookbook for focus - does not work as expected with iOS 18
I try to create a sheet that shows a textfield and the textfield should gain the focus immediately after the sheet is presented. This use case is explained in Session 10162 at 11:21 and at 13:31 my desired behavior is shown. I could not get this to work in my own code and downloaded the sample code from here: https://developer.apple.com/documentation/swiftui/focus-cookbook-sample Then I opened the sample code and run it in the simulator. It does not focus when the sheet appears in a iOS 18 simulator using Xcode 16 installed via the AppStore. It does gain focus if I use the "add" Button. No changes made on the sample code. Just adjusted the Team setting to get a clean build. The behavior I get locally under iOS 18 is not what is shown in the session and I can't understand why. I tried the following Simulators (and platforms) iPhone 16, iPad Pro (M4, 11inch) and my Mac. On none of those the last item got focus just by presenting the sheet. Is it not possible to test this in a simulator? Could I have a configuration error? Given all the information I found yet, this seems like a Bug. It would be very helpful if someone could replicate my problem. Thank you for your help. Programm Versions: Xcode: Version 16.0 (16A242d) MacOS: 15.0 (24A335)
0
0
513
Sep ’24
CMMotionManager level meter inaccurate
I want to add a "bubble horizon" to a camera application to show if the user is keeping their phone level. For this, I'm using the Motion Attitude functionality of CMMotionManager. However, the output I'm getting is very inaccurate. I'm comparing it with Apple's own Measure app which is dead accurate, so the sensors are working fine. My own readings seem to be several degrees off. Am I missing some calibration step or something? - (void)processDeviceMotion:(CMDeviceMotion *)motion { // use quaternions to avoid Gimbal Lock CMQuaternion quat = motion.attitude.quaternion; // calculate roll in degrees double roll = atan2( 2 * ( quat.w * quat.x + quat.y * quat.z ), 1 - 2 * ( quat.x * quat.x + quat.y * quat.y ) ); roll = radiansToDegrees( roll ); NSLog( @"Roll: %f", roll ); }
Topic: UI Frameworks SubTopic: General
0
0
260
Nov ’24
Critical Rendering Bug in PencilKit on iPad with "More Space" Display Mode & Apple Pencil Hover Interaction
There is a significant rendering issue in PencilKit when using an iPad set to "More Space" display mode, combined with an Apple Pencil that supports hover functionality (e.g., Apple Pencil 2). This problem affects all applications that use PencilKit, including Apple's own Notes and Quick Note. The issue results in flickering black borders and subtle jittering while drawing, which is especially noticeable during tasks requiring precise handwriting, such as writing mathematical expressions. Due to the short strokes and frequent lifts and drops of the pencil, the jitter is much more pronounced, leading to visual discomfort and even dizziness after extended use. Steps to Reproduce: Open Settings on your iPad. Navigate to Display & Brightness > Display Zoom > More Space. Switch to the More Space display mode. Open the Notes app or trigger Quick Note from any application by swiping from the bottom-right corner. Start drawing or writing using the Apple Pencil (with hover functionality) in the writing area. Observe the display anomalies as you hover and write: Flickering black borders appear intermittently around the writing area. The strokes show subtle jittering whenever you lift and lower the pencil. This is particularly disruptive when writing short, precise strokes, such as those in mathematical expressions, where the frequent up-and-down motion makes the jitter more apparent. Expected Results: Smooth and stable drawing experience with no visual artifacts or jittering during interactions with the Apple Pencil, regardless of the display zoom settings. Actual Results: Flickering black borders intermittently appear during drawing. Jittering of strokes is noticeable, particularly when lifting and lowering the Apple Pencil for short strokes. This disrupts precise writing tasks, especially in cases like mathematical notation, where frequent pen lifts and short strokes make the jitter much more apparent. This can lead to discomfort (e.g., dizziness) after extended use. System-Wide Impact: This issue affects all apps that utilize PencilKit, including third-party apps such as Doodle Draw (link). Since PencilKit is a core framework, the rendering bug significantly degrades the user experience across multiple productivity and note-taking applications. Similar Reports: There are numerous discussions about this issue online, indicating that many users are experiencing the same problem. Reddit Discussion: https://www.reddit.com/r/ipad/comments/1f042ol/comment/ljvilv6/ Apple Support Thread Additionally, a feedback report (ID: FB15166022) related to this issue with Notes has been previously filed, but the bug remains unresolved. This bug severely impacts the usability of PencilKit-enabled apps, especially during tasks that involve frequent, precise pencil strokes, such as writing mathematical expressions. We kindly request the development team investigate this issue promptly to ensure a smooth, stable experience with the iPad's "More Space" display mode and Apple Pencil hover interaction.
1
0
623
Sep ’24
Page view in SwiftUI
I have an app for musicians that works with Songs and Setlists. The logical structure is as follows: A Setlist contains Songs. A Song has Sections, which include Lines (chords & lyrics). I want to view my Setlist in a "Page View," similar to a book where I can swipe through pages. In this view, the Song Sections are wrapped into columns to save screen space. I use a ColumnsLayout to calculate and render the columns, and then a SplitToPages modifier to divide these columns into pages. Problem: The TabView sometimes behaves unexpectedly when a song spans multiple pages during rendering. This results in a transition that is either not smooth or stops between songs. Is there a better way to implement this behavior? Any advice would be greatly appreciated. struct TestPageView: View { struct SongWithSections: Identifiable { var id = UUID() var title: String var section: [String] } var songSetlistSample: [SongWithSections] { var songs: [SongWithSections] = [] //songs for i in 0...3 { var sections: [String] = [] for _ in 0...20 { sections.append(randomSection() + "\n\n") } songs.append(SongWithSections(title: "Song \(i)", section: sections)) } return songs } func randomSection() -> String { var randomSection = "" for _ in 0...15 { randomSection.append(String((0..<Int.random(in: 3..<10)).map{ _ in "abcdefghijklmnopqrstuvwxyz".randomElement()! }) + " ") } return randomSection } var body: some View { GeometryReader {geo in TabView { ForEach(songSetlistSample, id:\.id) {song in let columnWidth = geo.size.width / 2 //song ColumnsLayout(columns: 2, columnWidth: columnWidth, height: geo.size.height) { Text(song.title) .font(.largeTitle) ForEach(song.section, id:\.self) {section in Text(section) } } .modifier(SplitToPages(pageWidth: geo.size.width, id: song.id)) } } .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) } } } public struct ColumnsLayout: Layout { var columns: Int let columnWidth: CGFloat let height: CGFloat let spacing: CGFloat = 10 public static var layoutProperties: LayoutProperties { var properties = LayoutProperties() properties.stackOrientation = .vertical return properties } struct Column { var elements: [(index: Int, size: CGSize, yOffset: CGFloat)] = [] var xOffset: CGFloat = .zero var height: CGFloat = .zero } public func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Cache) -> CGSize { let columns = arrangeColumns(proposal: proposal, subviews: subviews, cache: &cache) guard let maxHeight = columns.map({ $0.height}).max() else {return CGSize.zero} let width = Double(columns.count) * self.columnWidth return CGSize(width: width, height: maxHeight) } public func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Cache) { let columns = arrangeColumns(proposal: proposal, subviews: subviews, cache: &cache) for column in columns { for element in column.elements { let x: CGFloat = column.xOffset let y: CGFloat = element.yOffset let point = CGPoint(x: x + bounds.minX, y: y + bounds.minY) let proposal = ProposedViewSize(width: self.columnWidth, height: proposal.height ?? 100) subviews[element.index].place(at: point, anchor: .topLeading, proposal: proposal) } } } private func arrangeColumns(proposal: ProposedViewSize, subviews: Subviews, cache: inout Cache) -> [Column] { var currentColumn = Column() var columns = [Column]() var colNumber = 0 var currentY = 0.0 for index in subviews.indices { let proposal = ProposedViewSize(width: self.columnWidth, height: proposal.height ?? 100) let size = subviews[index].sizeThatFits(proposal) let spacing = size.height > 0 ? spacing : 0 if currentY + size.height > height { currentColumn.height = currentY columns.append(currentColumn) colNumber += 1 currentColumn = Column() currentColumn.xOffset = Double(colNumber) * (self.columnWidth) currentY = 0.0 } currentColumn.elements.append((index, size, currentY)) currentY += size.height + spacing } currentColumn.height = currentY columns.append(currentColumn) return columns } } struct SplitToPages: ViewModifier { let pageWidth: CGFloat let id: UUID @State private var pages = 1 func body(content: Content) -> some View { let contentWithGeometry = content .background( GeometryReader { geometryProxy in Color.clear .onChange(of: geometryProxy.size) {newSize in guard newSize.width > 0, pageWidth > 0 else {return} pages = Int(ceil(newSize.width / pageWidth)) } .onAppear { guard geometryProxy.size.width > 0, pageWidth > 0 else {return} pages = Int(ceil(geometryProxy.size.width / pageWidth)) } }) Group { ForEach(0..<pages, id:\.self) {p in ZStack(alignment: .topLeading) { contentWithGeometry .offset(x: -Double(p) * pageWidth, y: 0) .frame(width: pageWidth, alignment: .leading) VStack { Spacer() HStack { Spacer() Text("\(p + 1) of \(pages)") .padding([.leading, .trailing]) } } } .id(id.description + p.description) } } } }
0
0
362
Nov ’24
Terminating app due to uncaught exception 'NSInternalInconsistencyException
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Expected dequeued view to be returned to the collection view in preparation for display. When the collection view's data source is asked to provide a view for a given index path, ensure that a single view is dequeued and returned to the collection view. Avoid dequeuing views without a request from the collection view. For retrieving an existing view in the collection view, use -[UICollectionView cellForItemAtIndexPath:] or -[UICollectionView supplementaryViewForElementKind:atIndexPath:]. Dequeued view: <Pezzano_Enterprises.BannerImageCollectionViewCell: 0x103af8370; baseClass = UICollectionViewCell; frame = (880 0; 440 130); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x6000003980a0>>; Collection view: <UICollectionView: 0x106030c00; frame = (0 0; 440 130); clipsToBounds = YES; autoresize = RM+BM; gestureRecognizers = <NSArray: 0x600000de0240>; backgroundColor = <UIDynamicSystemColor: 0x6000017b0500; name = systemBackgroundColor>; layer = <CALayer: 0x600000337fe0>; contentOffset: {205.66666666666666, 0}; contentSize: {1320, 130}; adjustedContentInset: {0, 0, 0, 0}; layout: <UICollectionViewFlowLayout: 0x103b35720>; dataSource: <Pezzano_Enterprises.BannerTableViewCell: 0x10605d000; baseClass = UITableViewCell; frame = (0 0; 440 130); autoresize = W; layer = <CALayer: 0x600000336900>>>'
Topic: UI Frameworks SubTopic: UIKit
0
0
332
Oct ’24
NSMenuToolbarItem not showing first menu item when using NSMenuDelegate
I am using NSMenuToolbarItem to show a drop-down menu in my NSToolbar. This works as expected when creating an NSMenu beforehand and assign it to the menu property of NSMenuToolbarItem. However, my menu needs to be built dynamically when the user clicks the dropdown button. To do that, I am using NSMenuDelegate. When creating the menu in the menuNeedsUpdate of the delegate, the first menu item isn't shown to the user. Why? Menu when using delegate: Menu when pre-assigning menu: I also cannot just add a placeholder menu item at the start of the NSMenuToolbarItem as all menu items do show in the overflow menu. Example code: import Cocoa class AppDelegate: NSObject, NSApplicationDelegate, NSToolbarDelegate, NSMenuDelegate { var window: NSWindow! func applicationDidFinishLaunching(_ aNotification: Notification) { window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 400, height: 300), styleMask: [.titled, .closable, .resizable], backing: .buffered, defer: false) window.makeKeyAndOrderFront(nil) let toolbar = NSToolbar(identifier: "MainToolbar") toolbar.delegate = self window.toolbar = toolbar } func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { return [NSToolbarItem.Identifier("item1"), NSToolbarItem.Identifier("item2")] } func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] { return [NSToolbarItem.Identifier("item1"), NSToolbarItem.Identifier("item2")] } func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar: Bool) -> NSToolbarItem? { let item = NSMenuToolbarItem(itemIdentifier: itemIdentifier) if itemIdentifier == NSToolbarItem.Identifier("item1") { let menu = NSMenu() fillMenuWithItems(menu) item.menu = menu } else if itemIdentifier == NSToolbarItem.Identifier("item2") { item.menu = NSMenu() item.menu.delegate = self } return item } func menuNeedsUpdate(_ menu: NSMenu) { menu.removeAllItems() fillMenuWithItems(menu) } func fillMenuWithItems(_ menu: NSMenu) { menu.addItem(NSMenuItem(title: "Option 1", action: nil, keyEquivalent: "")) menu.addItem(NSMenuItem(title: "Option 2", action: nil, keyEquivalent: "")) } }
1
0
416
Sep ’24
Overzealous TextField Behavior
macOS 14.6.1, SwiftUI, Xcode 16.0 (developing for macOS) I have a TextField with a custom formatter and a custom binding (one created using Binding(get:set:). My understanding is that the TextField should only call on the formatter and update the binding when I commit the TextField by moving focus away from it, hitting return, etc. Unfortunately, that is not the case. I was able to verify that the formatter is being called (via NSLog added to the formatter code) every time I add a character to the field. Not only is this a waste of CPU resources, but in combination with the custom binding, it is causing a data entry issue for my application. I am writing a simple emulator for a computer system I concocted, and the formatter is formatting a memory location into its assembly language - the formatter's string method disassembles the instruction from the underlying memory value and the getObjectValue method assembles an input string back into the memory location. One example of an instruction would be the "set" instruction. All of these are valid forms for input to the assembler: set set 0 set x The "set" by itself is equivalent to "set 0", so what is happening is that I type "set" and the TextField immediately gets a valid response back from the formatter and updates the binding with the value of the "set 0" instruction. The custom binding updates the value in memory, which triggers the "get" method of the binding and updates the TextField with the disassembled value which helpfully adds the "0" to the field, setting it to "set 0" before I have a chance to type the "x". I need to manually delete the "0" from the line which I never typed there in the first place in order to finish typing the line of input. I either need a way to cause the TextField to behave the way it is supposed to and only update the binding when the value is committed, or a way to prevent the binding's get method from being retriggered by its own set method. Any ideas how to go about fixing this? Note that I set up a temporary TextField which was bound using a simple $binding to a state variable, and while the variable is updated whenever valid input is presented, it does not trigger the TextField to reread and thus I do not have the same input issue with that TextField. There is a reason why I need to use the custom get/set in context, however, so this is still an issue for me.
Topic: UI Frameworks SubTopic: SwiftUI
1
0
229
Sep ’24
Is there a way to opt a Catalyst app into supporting preferred text size?
As of macOS Sequoia 15.1 (and probably earlier), in System Settings under Accessibility -> Display, there's a Text Size option that looks an awful lot like Dynamic Type on iOS: I have an iOS app with robust support for Dynamic Type that I've brought to the Mac via Catalyst. Is there any way for me to opt this app into supporting this setting, maybe with some Info.plist key? Calendar's Info.plist has a CTIgnoreUserFonts value set to true, but the Info.plist for Notes has no such value.
0
0
544
Oct ’24
Is there a way to know what size of icons is enabled in the home page?
Hey, i have an app that uses some calculations to replicate a transparent background in widgets, however given that in ios 18 we can now change the icons size to large, I wonder if there is a way to know what the user has currently selected and react to it? The workaround I have is that user needs to select a new config to switch the widget to large so re-calculations are done, but it would be nice if we there was a way for us to catch the new size.
0
0
299
Sep ’24
Custom Container View with ForEach(subviews:content:)
Hi, I’m trying to implement a custom horiztonal Picker using the newly introduced API on ForEach like the following: struct ScopeBar<SelectionValue: Hashable, Content: View>: View { // MARK: Initializers @Binding private var selection: SelectionValue @ViewBuilder private var content: () -> Content public init(selection: Binding<SelectionValue>, @ViewBuilder content: @escaping () -> Content) { _selection = selection self.content = content } // MARK: Content var body: some View { ScrollView(.horizontal) { LazyHStack { ForEach(subviews: content()) { subview in Button { // selection = subview.id as! SelectionValue } label: { subview } .tag(subview.id) } } } } } The following implementation is what I’m trying to achieve for the custom container usage: struct MyOtherView: View { enum SomeScopes: String, CaseIterable, Hashable { case first case second case third case fourth case fifth } @State private var selection: SomeScopes = .first var body: some View { ScopeBar(selection: $selection) { ForEach(SomeScopes.allCases, id: \.rawValue) { scope in Text(scope.rawValue) } } } } I’m having some trouble figuring out two things: How can I make my SelectionValue equal to the Subview.ID so that my selection behaves internally like a Picker would? Say I wanted to add an Image(…) in addition to the Text(scope.rawValue), how would I do it in order for the Button { … } label: { … } to use both views, and not each separately…?
2
0
397
Oct ’24
External Display with SwiftUI in 2024
What is current best-practice for supporting an external display in a SwiftUI iOS app in 2024? (I'm only interested in iOS 17 and/or iOS 18) The only docs I found require abandoning the SwiftUI App structure and "switching back to a full UIKit App Delegate". Is this still the only option? Are there any complete examples on how to accomplish this? Also is testing external displays with Simulator at all reliable? All experiences I read about say that it never works. Thanks in advance.
0
0
338
Sep ’24
Defer system gestures in a WatchKit app
Hi, I'm making a WatchKit game app with SpriteKit and Objective-C, and I'm encountering an annoyance where system gestures, namely long-pressing the top and bottom edges to pull Notification/Control Center, interfere with the controls of the game. In iOS, this can be mitigated by using overriding preferredScreenEdgesDeferringSystemGestures in UIViewController, but I couldn't find any equivalent API in any WatchKit class, and searching for similar symbols only yielded a single private API (-[_UISystemAppearanceManager screenEdgesDeferringSystemGestures]) that isn't ever called on watchOS. Any idea how to achieve a similar effect on watchOS?
1
0
513
Nov ’24
NSPersistentCloudkitContainer setup - merge behavior - contexts
Is it ok to use automaticallyMergesChangesFromParent to true for a managed object context that pins its query generation to current? I have heard from a few sources that in most cases, it’s not recommended to set automaticallyMergesChangesFromParent to true for a managed object context that pins its query generation to current. A source I have seen says: When you pin a managed object context to a specific query generation, you’re explicitly telling the context to view the data as it existed at the time of that generation token (often referred to as a snapshot). This ensures that the context is isolated from any subsequent changes made by other contexts or background tasks. But this may Conflict with Automatic Merging because: The purpose of setting automaticallyMergesChangesFromParent to true is to have the context automatically merge changes from the parent (e.g., background or main context) into itself whenever the parent context saves changes. This behavior conflicts with the concept of a pinned query generation because: • If the context is pinned to current, it should not be concerned with changes that happen after that pinning. • Automatic merging would introduce updates from the parent context that occur after the pinning, thereby violating the “snapshot” nature of the query generation and potentially creating inconsistencies. However, in your own Apple Sample Code Titled "" does use both together. Inside the definition of lazy var persistentContainer: NSPersistentCloudkitContainer the following code is included: // Pin the viewContext to the current generation token, and set it to keep itself up to date with local changes. container.viewContext.automaticallyMergesChangesFromParent = true do { try container.viewContext.setQueryGenerationFrom(.current) } catch { fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)") }
Topic: UI Frameworks SubTopic: General
0
0
278
Oct ’24
Weird vibrancy effects on Mac Catalyst
Hello, my app uses vibrancy effects thanks to SwiftUI's materials and hierarchical shape style. While they work flawlessly on iOS and iPadOS, on Mac Catalyst they seem to be pretty broken. I'm attaching some screenshots. iPad Mac This is the same code, with the same background behind the material. The colors are already pretty different, but my concern is about the text, which clearly looks broken Here a sample code: HStack(spacing: 0) { VStack(alignment: .leading, spacing: 2) { Text(event.label) .font(.subheadline.weight(.semibold)) .foregroundStyle(.secondary) Text(event.info(for: currentDestination)) .font(.system(.title2, design: .rounded, weight: .semibold)) .foregroundStyle(.primary) } Spacer(minLength: 0) InfoIcon(isCustom: event.isCustomIcon, icon: event.icon, renderingMode: .palette, size: iconSize) .font(.body.weight(.semibold)) } .padding(.horizontal, 24) .padding(.vertical, 12) .background(.quaternary, in: .rect(cornerRadius: 16, style: .continuous)) .clipShape(.rect(cornerRadius: 16, style: .continuous)) .padding(.all, 16) .background(.ultraThinMaterial, in: .rect)
0
0
386
Oct ’24
Binding NSPopUpButton to an NSMutableArray
My view controller has this property: @property NSMutableArray *tableCities; Whenever I press a button a new city object is added to this array, it consists of a dictionary of NSStrings containing the name and state of the city being added. Now, I have an NSPopUpButton that I'd like to bind to the city name of all cities in this NSMutableArray, how exactly can I achieve this with the Bindings Inspector?
2
0
542
Oct ’24
Apple Maps Not responding to scrolling, zooming, or tap gestures on custom pins
Scrolling and zooming I have two apps that utilize the Apple Maps and since I updated my xcode to use IOS18, some of the functionalities have either been missing or glitching, and when I roll back to IOS17.5, everything seems to work fine. When I use MKMapView and use mapView.isZoomEnabled = true mapView.isScrollEnabled = true Scrolling and zooming is still not working on IOS18 but IOS17.5 works fine. Clicking on custom annotations When I press on a custom annotation I add to the MapView, the gesture is sometimes recognized and most of the times not recognized. If I have 20 annotations, there is a possibility only 2 of them respond to tap gestures. Below is how I define the annotation. Annotation("Pin", coordinate: CLLocationCoordinate2D(latitude: latitude, longitude: longitude), anchor: .bottom) { Button(action: {print ("Pressed annotation"){ CustomPin() } }
0
0
349
Oct ’24
request review bug
Previously, i create an app and i'm using a userdefault with app group to enable to connect it with extension. But a bug causing me very frustating and so long to solve this, I think it just my code bug, but it was causing by the swiftui itself. Where requestReview environment, causing my navigationlink that pointing to a view that include userdefault that connect to app group is freezing while tapping. Than it just caused in my ios 16 device, and work smoothly in my ios 18 device. struct SettingView: View { @Environment(\.requestReview) var requestReview var body: some View { NavigationStack { List { Section("Configuration") { NavigationLink(destination: WidgetConfigurationView()) { Label("Widget", systemImage: "paintpalette") } } } } } struct WidgetConfigurationView: View { @Environment(\.dismiss) var dismiss @AppStorage("widgetalignment", store: UserDefaults(suiteName: "group.com.my.app")) var alignment: Int = 0 } can anyone explain why this happened? is this my mistake or the swiftui bug?
0
0
325
Oct ’24
$FocusState and Lists with rows containing TextFields
While I've seen examples of using $FocusState with Lists containing "raw" TextFields, in my use case, the List rows are more complex than that and contain multiiple elements, including TextFields. I obviously don't understand something fundamental here, because I am completely unable to get TextField-to-TextField tabbing to work. Can someone set me straight? Sample code demonstrating the issues: // // ContentView.swift // ListElementFocus // // Created by Richard Aurbach on 10/12/24. // import SwiftUI /// NOTE: in my actual app, the data model is actually a set of SwiftData /// PresistentModel objects. Here, I'm simulating them with an Observable. @Observable final class TestModel: Identifiable { public var id: UUID public var checked: Bool = false public var title: String = "Test" public var subtitle: String = "Subtitle" init(checked: Bool = false, title: String, subtitle: String) { self.id = UUID() self.checked = checked self.title = title self.subtitle = subtitle } } struct ContentView: View { /// Instead of a @Query... @State var records: [TestModel] = [ TestModel(title: "First title", subtitle: "blah, blah, blah"), TestModel(title: "Second title", subtitle: "more nonsense"), TestModel(title: "Third title", subtitle: "even more nonsense"), ] @FocusState var focus: UUID? var body: some View { Form { Section { HStack(alignment: .top) { Text("Goal:").font(.headline) Text( "If a user taps in the TextField in any row, they should be able to tab from row to row using any keyboard which supports a tab key." ) } HStack(alignment: .top) { Text("#1:").font(.headline) Text( "While I will admit that this code is probaby total garbage, I haven't been able to find any way to make tabbing from row to row to work at all." ) } HStack(alignment: .top) { Text("#2:").font(.headline) Text( "Tapping the checkbox button causes the row to flash with the current accent color, and I can't find any way to turn that off." ) } } header: { Text("Problems").font(.title3).bold() }.headerProminence(.increased) Section { List(records) { record in ListRow(record: record, focus: focus) .onKeyPress(.tab) { focus = next(record) return .handled } } } header: { Text("Example: Selector of Editable Items").font(.title3).bold() }.headerProminence(.increased) } .padding() } private func next(_ record: TestModel) -> UUID { guard !records.isEmpty else { return UUID() } if record.id == records.last!.id { return records.first!.id } if let index = records.firstIndex(where: { $0.id == record.id }) { return records[index + 1].id } return UUID() } } struct ListRow: View { @Bindable var record: TestModel var focus: UUID? @FocusState var focusState: Bool init(record: TestModel, focus: UUID?) { self.record = record self.focus = focus self.focusState = focus == record.id } var body: some View { HStack(alignment: .top) { Button { record.checked.toggle() } label: { record.checked ? Image(systemName: "checkmark.square.fill") : Image(systemName: "square") }.font(.title2).focusable(false) VStack(alignment: .leading) { TextField("title", text: $record.title).font(.headline) .focused($focusState) Text("subtitle").italic() } } } } #Preview { ContentView() }
0
0
460
Oct ’24