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

Toggling UITextView attributes for spellchecking, smart quotes, etc stops working
I've added some menu actions to toggle various text view attributes named in the subject. The default for Simulator and my devices is to have these features turned on. But I'm finding that toggling them in my menu actions doesn't actually work. Toggling spellchecking or smart quotes (I haven't yet bothered to add more actions and test them) to .off does indeed set the correct value on the UITextView, but the features are still happening when I type (soft or hard keyboards behave the same). What's wrong? Is it simply broken and is caching the initial value or something? 18.4 is being used on my Simulator and my devices. I also tried 18.3.1 in Simulator with the same results.
Topic: UI Frameworks SubTopic: UIKit
3
0
121
Apr ’25
cannot save event
iOS 18.4.1 When I change a Google type event to an iCloud type, a "Cannot Save Event" prompt box pops up. We have also received user feedback that recurring events also fail to save. After updating to iOS 18.4 when trying to save changes to an existing repeating event, the message "Cannot Save Event" will appear. EventKitUI
Topic: UI Frameworks SubTopic: UIKit
0
0
68
May ’25
How to detect iPad trackpad touch-down (indirectPointer) to immediately stop coasting animation
Hello, I have a custom 3D object viewer on iOS that lets users spin the model using the touchscreen or a trackpad and supports coasting (momentum spinning). I need to stop the coasting animation as soon as the user touches down, but I can only immediately detect touches on the screen itself - on the trackpad I can't get an immediate notification of the touches. So far I’ve tried: State.began on my UIPanGestureRecognizer. It only fires after a small movement on both touchscreen and trackpad. .possible on the pan gesture; this state never occurs during the gesture cycle. UIApplicationSupportsIndirectInputEvents = YES in Info.plist; it didn’t make touchesBegan fire for indirectPointer touches. Since UITableView (and other UIScrollView subclasses) clearly detect trackpad “touch-down” to cancel scrolling, there must be a way to receive that event. Does anyone know how to catch the initial trackpad contact—before any movement—on an indirect input device? Below is a minimal code snippet demonstrating the issue. On the touchscreen you'll see a message the moment you touch the view, but the trackpad doesn't trigger any messages until your fingers move. Any advice would be greatly appreciated. Thanks in advance, John import UIKit class ViewController: UIViewController { private let debugView = DebugView() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white // Fill the screen with our debug view debugView.frame = view.bounds debugView.autoresizingMask = [.flexibleWidth, .flexibleHeight] debugView.backgroundColor = UIColor(white: 0.95, alpha: 1) view.addSubview(debugView) // Attach a pan recognizer that logs its state let panGR = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) panGR.allowedScrollTypesMask = .all debugView.addGestureRecognizer(panGR) } @objc private func handlePan(_ gr: UIPanGestureRecognizer) { switch gr.state { case .possible: print("Pan state: possible") case .began: print("Pan state: began") case .changed: print("Pan state: changed – translation = \(gr.translation(in: debugView))") case .ended: print("Pan state: ended – velocity = \(gr.velocity(in: debugView))") case .cancelled: print("Pan state: cancelled") case .failed: print("Pan state: failed") @unknown default: print("Pan state: unknown") } } } class DebugView: UIView { override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) for t in touches { let typeDesc: String switch t.type { case .direct: typeDesc = "direct (finger)" case .indirectPointer: typeDesc = "indirectPointer (trackpad/mouse)" case .indirect: typeDesc = "indirect (Apple TV remote)" case .pencil: typeDesc = "pencil (Apple Pencil)" @unknown default: typeDesc = "unknown" } print("touchesBegan on DebugView – touch type: \(typeDesc), location: \(t.location(in: self))") } } }
Topic: UI Frameworks SubTopic: UIKit Tags:
2
0
116
Apr ’25
Lists, Generics, Views, Navigation Link, SwiftData - ForEach can't pass a binding anymore.
I'm trying out putting most of my business logic in a Protocol that my @Model can conform to, but I'm running into a SwiftUI problem with a Binding that does not get magically offered up like it does when it the subview is not generic. I have a pretty basic List with a ForEach that now can't properly pass to a generic view based on a protocol. When I try to make a binding manually in the row it says that "item is immutable"... but that also doesn't help me with the NavigationLink? Which is seeing the Binding not the ? But before when the subview was concrete to Thing, it took in the and made its own Binding once it hit the view. I'm unclear on precisely where the change happens and what I can do to work around it. Before I go rearchitecting everything... is there a fix to get the NavigationLink to take on the object like before? What needs to be different? I've tried a number of crazy inits on the subview and they all seem to come back to saying either it can't figure out how to pass the type or I'm trying to use the value before it's been initialized. Have I characterized the problem correctly? Thanks! (let me know if I forgot a piece of code, but this should be the List, the Model/Protocol and the subview) import SwiftUI import SwiftData struct ThingsView: View {     @Environment(\.modelContext) var modelContext     @Query var items: [Thing]          var body: some View {         NavigationStack {             List {                 ForEach(items) { item in                     NavigationLink(value: item) {                         VStack(alignment: .leading) {                             Text(item.textInfo)                                 .font(.headline)                                                          Text(item.timestamp.formatted(date: .long, time: .shortened))                         }                     }                 }.onDelete(perform: deleteItems)             }             .navigationTitle("Fliiiing!") //PROBLEM HERE: Cannot convert value of type '(Binding<Thing>) -> EditThingableView<Thing>' to expected argument type '(Thing) -> EditThingableView<Thing>'             .navigationDestination(for: Thing.self, destination: EditThingableView<Thing>.init) #if os(macOS)             .navigationSplitViewColumnWidth(min: 180, ideal: 200) #endif             .toolbar { #if os(iOS)                 ToolbarItem(placement: .navigationBarTrailing) {                     EditButton()                                      } #endif                 ToolbarItem {                     Button(action: addItem) {                         Label("Add Item", systemImage: "plus")                     }                 }                 ToolbarItem {                     Button("Add Samples", action: addSamples)                 }             }         }     }          func addSamples() {         withAnimation {             ItemSDMC.addSamples(context: modelContext)         }     }          private func addItem() {         withAnimation {             let newItem = ItemSDMC("I did a thing!")             modelContext.insert(newItem)         }     }          func deleteItems(_ indexSet:IndexSet) {         withAnimation {             for index in indexSet {                 items[index].delete(from: modelContext)             }         }     } } #Preview {     ThingsView().modelContainer(for: ItemSDMC.self, inMemory: true) } import Foundation import SwiftData protocol Thingable:Identifiable {     var textInfo:String { get set }     var timestamp:Date { get set } } extension Thingable {     var thingDisplay:String {         "\(textInfo) with \(id) at \(timestamp.formatted(date: .long, time: .shortened))"     } } extension Thingable where Self:PersistentModel {     var thingDisplayWithID:String {         "\(textInfo) with modelID \(self.persistentModelID.id) in \(String(describing: self.persistentModelID.storeIdentifier)) at \(timestamp.formatted(date: .long, time: .shortened))"     } } struct ThingLite:Thingable, Codable, Sendable {     var textInfo: String     var timestamp: Date     var id: Int } @Model final class Thing:Thingable {     //using this default value requires writng some clean up logic looking for empty text info.     var textInfo:String = ""     //using this default value would require writing some data clean up functions looking for out of bound dates.     var timestamp:Date = Date.distantPast          init(textInfo: String, timestamp: Date) {         self.textInfo = textInfo         self.timestamp = timestamp     } } extension Thing {     var LiteThing:ThingLite {         ThingLite(textInfo: textInfo, timestamp: timestamp, id: persistentModelID.hashValue)     } } import SwiftUI struct EditThingableView<DisplayItemType:Thingable>: View {     @Binding var thingHolder: DisplayItemType          var body: some View {                  VStack {             Text(thingHolder.thingDisplay)             Form {                 TextField("text", text:$thingHolder.textInfo)                 DatePicker("Date", selection: $thingHolder.timestamp)             }                      } #if os(iOS)         .navigationTitle("Edit Item")         .navigationBarTitleDisplayMode(.inline) #endif     } } //NOTE: First sign of trouble //#Preview { //    @Previewable var myItem = Thing(textInfo: "Example Item for Preview", timestamp:Date()) //    EditThingableView<Thing>(thingHolder: myItem) //}
4
0
209
May ’25
Printing NSTextStorage over multiple UITextView produces weird results
I would like to print a NSTextStorage on multiple pages and add annotations to the side margins corresponding to certain text ranges. For example, for all occurrences of # at the start of a line, the side margin should show an automatically increasing number. My idea was to create a NSLayoutManager and dynamically add NSTextContainer instances to it until all text is laid out. The layoutManager would then allow me to get the bounding rectangle of the interesting text ranges so that I can draw the corresponding numbers at the same height inside the side margin. This approach works well on macOS, but I'm having some issues on iOS. When running the code below in an iPad Simulator, I would expect that the print preview shows 3 pages, the first with the numbers 0-1, the second with the numbers 2-3, and the last one with the number 4. Instead the first page shows the number 4, the second one the numbers 2-4, and the last one the numbers 0-4. It's as if the pages are inverted, and each page shows the text starting at the correct location but always ending at the end of the complete text (and not the range assigned to the relative textContainer). I've created FB17026419. class ViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { let printController = UIPrintInteractionController.shared let printPageRenderer = PrintPageRenderer() printPageRenderer.pageSize = CGSize(width: 100, height: 100) printPageRenderer.textStorage = NSTextStorage(string: (0..<5).map({ "\($0)" }).joined(separator: "\n"), attributes: [.font: UIFont.systemFont(ofSize: 30)]) printController.printPageRenderer = printPageRenderer printController.present(animated: true) { _, _, error in if let error = error { print(error.localizedDescription) } } } } class PrintPageRenderer: UIPrintPageRenderer, NSLayoutManagerDelegate { var pageSize: CGSize! var textStorage: NSTextStorage! private let layoutManager = NSLayoutManager() private var textViews = [UITextView]() override var numberOfPages: Int { if !Thread.isMainThread { return DispatchQueue.main.sync { [self] in numberOfPages } } printFormatters = nil layoutManager.delegate = self textStorage.addLayoutManager(layoutManager) if textStorage.length > 0 { let glyphRange = layoutManager.glyphRange(forCharacterRange: NSRange(location: textStorage.length - 1, length: 0), actualCharacterRange: nil) layoutManager.textContainer(forGlyphAt: glyphRange.location, effectiveRange: nil) } var page = 0 for textView in textViews { let printFormatter = textView.viewPrintFormatter() addPrintFormatter(printFormatter, startingAtPageAt: page) page += printFormatter.pageCount } return page } func layoutManager(_ layoutManager: NSLayoutManager, didCompleteLayoutFor textContainer: NSTextContainer?, atEnd layoutFinishedFlag: Bool) { if textContainer == nil { addPage() } } private func addPage() { let textContainer = NSTextContainer(size: pageSize) layoutManager.addTextContainer(textContainer) let textView = UITextView(frame: CGRect(origin: .zero, size: pageSize), textContainer: textContainer) textViews.append(textView) } }
Topic: UI Frameworks SubTopic: UIKit Tags:
4
0
138
Apr ’25
UITextView crash on iOS 18.4 beta
UITextView crash when setting attributed text that contains substring ffi and attributedText contains NSFontAttributeName, NSForegroundColorAttributeName Reproducible case: UITextView *textView = [[UITextView alloc] init]; textView.attributedText = [[NSAttributedString alloc] initWithString:@"ffi" attributes:@{ NSParagraphStyleAttributeName: [self createParagraphOfLineHeight:20], NSFontAttributeName: [UIFont systemFontOfSize:fontSize weight:UIFontWeightRegular], NSForegroundColorAttributeName: UIColor.black }];
Topic: UI Frameworks SubTopic: UIKit Tags:
9
2
2.2k
May ’25
Setting UserDefaults in Preview
Hello, I've got a View that loads data from UserDefaults. I want to set the value of the UserDefault in the preview so i can see how it looks while developing. However when i am trying to set it in preview, i get the following error when i try set it in preview. 'buildExpression' is unavailable: this expression does not conform to 'View' What is the correct way to set the user defaults in preview? import Foundation class PreferencesViewModel: ObservableObject { @Published var maximumDistance: Double = UserDefaults.standard.value(for: .maximumDistance) as? Double ?? PreferencesViewModel.maximumDistanceOptions[0] { didSet { UserDefaults.standard.set(maximumDistance, for: .maximumDistance) } } #Preview { let preferencesViewModel = PreferencesViewModel() preferencesViewModel.maximumDistance = 5.0 PreferencesView() .environmentObject(PreferencesViewModel()) }
2
0
138
Apr ’25
Extra spacing above scrollview
I have a fairly simple view that consists of a tile and below it a horizontal scrollview. This display with a large gap between the title and the scrollview and I want the title sitting on top of the scrollview -- maybe 2 or 4 point gap. The current gap looks like 80 points or so. Code: let section: Section let userLevel: Int let stringsViewModel: StringsViewModel let onItemSelected: (AudioItem) -> Void var body: some View { VStack(alignment: .leading, spacing: 0) { Text(section.title) .font(.system(size: 18, weight: .medium)) .foregroundColor(.white) .padding(.leading, 16) ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 16) { ForEach(section.items, id: \.titleKey) { item in MeditationItemView(audioItem: item, userLevel: userLevel, stringsViewModel: stringsViewModel) .onTapGesture { onItemSelected(item) } } } .padding(.leading, 0) .padding(.trailing, 16) } } .padding(.leading, 16) } }
Topic: UI Frameworks SubTopic: SwiftUI
3
0
82
May ’25
Hover effect in Custom UIKit Views
I am adapting my custom UI Framework for visionOS, and I'm wondering if it is going to be possible to detect hover over different UI elements within my view. The UI Framework draws to a Metal layer in a UIView. I don't currently support uihovergesturerecognizer on the view but I guess this wouldn't help, since you don't get coordinates. I can imagine an unpleasant solution might be to add invisible UIControls for each of my custom controls that are drawn in my own framework.
1
0
67
Apr ’25
iOS 18.4 Beta: On iPhones which support Dynamic Island, the AppDelegate method applicationDidBecomeActive() takes longer to be called.
When testing with iOS 18.4 Beta on iPhones which support Dynamic Island, after doing a Face ID authentication, the amount of time it takes before the AppDelegate method applicationDidBecomeActive() is called takes longer than iPhones that do not support Dynamic Island. The time it takes is about double, 1.2 seconds vs 2.5 seconds on average. This does not occur with versions before 18.4 Beta. Anyone else seeing this?
Topic: UI Frameworks SubTopic: UIKit
2
3
381
May ’25
RealityView in UIHostingController/UIKit transparency
I'm experimenting with RealityView in the UI of an AUv3 plug-in. The plug-in UI is implemented in a UIKitViewController with a UIHostingController hosting a RealityView. When i run the standalone app on visionOS I want the background to be transparent, and the reality view content. how can i achieve that? I've tried turning off opaque in many views and and setting background colors to .clear.
1
0
77
Apr ’25
Apple Pay
I'm implementing Apple Pay in a Flutter iOS app using the pay plugin and Braintree as the payment processor. I have followed all necessary steps as outlined by Apple and community resources (e.g., Medium articles, official Apple Developer documentation), but the Apple Pay button does not appear on a real device. Here's what I've completed: Created an Apple Pay Merchant ID Created and downloaded the Apple Pay Payment Processing Certificate, then uploaded it to Braintree Downloaded the Braintree-signed certificate and confirmed it's active in the Apple Developer portal Added the Merchant ID under Signing & Capabilities in Xcode Enabled Apple Pay capability in Xcode Added the Merchant ID to Info.plist Installed required Flutter packages (e.g., pay) Using a valid Apple Pay payment configuration file in Flutter (see below) Tested on a real iOS device with a valid Apple Pay test card added to Wallet Flutter Payment Configuration (in Dart JSON): json Copy Edit { "provider": "apple_pay", "data": { "merchantIdentifier": "merchant.com.example", "displayName": "My Store", "merchantCapabilities": ["3DS", "debit", "credit"], "supportedNetworks": ["visa", "masterCard", "amex"], "countryCode": "US", "currencyCode": "USD" } } Despite this complete setup, the ApplePayButton widget remains invisible There are no errors in the console. Can you help identify what may be missing or misconfigured at the code or configuration level?
2
0
211
May ’25
TipViewStyle layout broken in iOS 18.4 – Tip message gets truncated
Hi everyone, I’m using a custom TipViewStyle to modify the background and slightly adjust the layout of the Tips in my app. Everything looked great until iOS 18.4. Since updating, the layout is being compressed, and the message inside the Tip is getting truncated. Here’s a screenshot of how it looks on iOS 18.4 (truncated message) and another showing how it used to look before iOS 18.4 (correct layout). Here is the relevant code for the custom style: struct CustomTipViewStyle: TipViewStyle { func makeBody(configuration: Configuration) -> some View { VStack(alignment: .leading, spacing: 4) { HStack { configuration.title? .font(.headline) .foregroundColor(.daBackground) Spacer() Button(action: { configuration.tip.invalidate(reason: .tipClosed) }) { Image(systemName: "xmark") .foregroundColor(.daBackground.opacity(0.3)) } } VStack(alignment: .leading, spacing: 8.0) { configuration.message? .font(.subheadline) .foregroundColor(.daBackground.opacity(0.8)) Divider().background(.daBackground.opacity(0.3)) ForEach(configuration.actions) { action in HStack { Spacer() Button(action: action.handler) { action.label() .foregroundStyle(.accent) .font(.system(size: 18, weight: .bold)) } } } } } .padding() .background(Color.daBlack) } } Has anyone else experienced this issue with TipViewStyle in iOS 18.4? Any workarounds or solutions would be appreciated! Thanks in advance!
1
1
210
Apr ’25
Compiler stuck::considering giving up on SwiftUI
Apologies if this comes off as a bit of a rant, and TLDR: Compiler hangs or gives an unhelpful error with any level of complexity in SwiftUI - which makes me want to back to IB Porting a project over to iPhone. Made great progress the first week. However, lately I have been encountering compiler bugs where either the build never finishes, or the build take a really long time and ends with just the error : The compiler is unable to type-check this expression in reasonable time Sometimes I can get it to go away by playing divide and conquer and finding some small error, but other times with my nested scrollview it just seems unhappy with any level of complexity (and I don't think my views are all that complex compared to lots of other apps out there) I've read several posts on these forums for things to try and do to fix it but the bottom line is its impacting my ability to produce a working solution in a reasonable amount of time. So considering going back to Interface Builder though it means starting over with some of the code. If there was another independent way to syntax check my swiftUI code other than line by line myself I'd welcome something like that. The compiler going away and never coming back is very discouraging -- what if I have to make some kind of emergency fix in the future with limited time? Open for any suggestions. Xcode : 16.2
3
0
167
May ’25
NSTableView.clickedRow sometimes is greater than number of rows
Xcode has been downloading many similar crash reports for my app for some time now, related to an index out of range runtime exception when accessing a Swift array. The crashes always happen in methods triggered by user input or during menu item validation when I try to access the data source array by using the following code to determine the indexes of the relevant table rows: let indexes = clickedRow == -1 || selectedRowIndexes.contains(clickedRow) ? selectedRowIndexes : IndexSet(integer: clickedRow) I was never able to reproduce the crash until today. When the app crashed in the Xcode debugger, I examined the variables clickedRow and selectedRowIndexes.first, which were 1 and 0 respectively. What's interesting: the table view only contained one row, so clickedRow was effectively invalid. I tried to reproduce the issue several times afterwards, but it never happened again. What could cause this issue? What are the circumstances where it is invalid? Do I always have to explicitly check if clickedRow is within the data source range?
Topic: UI Frameworks SubTopic: AppKit Tags:
4
0
108
Apr ’25
List Layout Breaks in NavigationStack When a View Exceeds Screen Width
This is a bug report. FB17433985 The layout of the following ContentView appears correctly when it is outside a NavigationStack. However, when the same view is placed inside a NavigationStack, the layout breaks. It seems that the width of the List is being affected by the width of the buttonsView, which exceeds the screen width. In my testing, this issue occurs on iOS 18.4 and later, but does not occur on iOS 18.2 or iOS 17.5. Workaround I found: Remove the fixed width modifier from the Button If anyone knows of other ways to resolve this issue without affecting the layout, I would appreciate it if you could share them. import SwiftUI let values = (1...100).map { $0 } let buttonTitles = (1...9).map { "Button\($0)" } struct ContentView: View { var body: some View { VStack { List { Section { ForEach(values.indices, id: \.self) { val in HStack { Text("Index: \(val)") } } } } buttonsView } } private var buttonsView: some View { HStack { ForEach(0..<buttonTitles.count, id: \.self) { index in Button() { } label: { Image(systemName: "square.and.arrow.up") .resizable() .frame(width: 48, height: 48) } } } } } @main struct ButtonShapeBugApp: App { var body: some Scene { WindowGroup { if true { NavigationStack { ContentView() } } else { ContentView() } } } } Environment: Xcode Version 16.3 (16E140) iPhone 18.4.1 real device iPhone SE3rd 18.4 simulator Expect layout Broken layout(9 buttons) Broken layout(10 buttons)
2
0
95
May ’25
NSLayoutManager returning inconsistent values for a glyph's text container and its line fragment rect
TLDR: NSLayoutManager's textContainer(forGlyphAt:effectiveRange:) and lineFragmentRect(forGlyphRange:effectiveRange:) are returning inconsistent results. Context: I'm developing a word processing app that paginates from an NSTextStorage using NSLayoutManager. My app uses a text attribute (.columnType) to paginate sub-ranges of the text at a time, ensuring that each columnRange gets a container (or series of containers across page breaks) to fit. This is to support both multi-column and standard full-page-width content. After any user edit, I update pagination data in my Paginator model class. I calcuate frames/sizes for the views/containers, along with what superview they belong to (page). The UI updates accordingly. In order to determine whether the columnRange has overflowed from a container due to a page break OR whether the range of text hasn't overflowed its container and is actually using less space than available and should be sized down, I call both: layoutManager.textContainer(forGlyphAt: lastGlyphOfColumn, effectiveRange: &actualGlyphRangeInContainer)` // and `layoutManager.lineFragmentRect(forGlyphAt: lastGlyphOfColumn, effectiveRange: nil) Apple Documentation notes that both these calls force glyph generation and layout. As I'm in early development, I have not set non-contiguous layout. So these should be causing full layout, assuring accurate return values. Or so I'd hoped. This does work fine in many cases. I edit. Pagination works. But then I'll encounter UI-breaking inconsistent returns from these two calls. By inconsistent, I mean that the second call returns a line fragment rect that is in the container coordinates of A DIFFERENT container than the container returned by the first call. To be specific, the line fragment rect seems to be in the coordinates of the container that comes next in layoutManager.textContainers. Example Code: if !layoutManager.textContainers.indices.contains(i) { containerToUse = createTextContainer(with: availableSize) layoutManager.addTextContainer(containerToUse) } else { // We have a container already but it may be // the wrong size. containerToUse = layoutManager.textContainers[i] if containerToUse.size.width != availableSize.width { // Mandatory that we resize if we don't have // a matching width. Height resizing is not // mandatory and requires a layout check below. containerToUse.size = availableSize } } let glyphRange = layoutManager.glyphRange(forCharacterRange: remainingColumnRange, actualCharacterRange: nil) let lastGlyphOfColumn = NSMaxRange(glyphRange) - 1 var containerForLastGlyphOfColumn = layoutManager.textContainer(forGlyphAt: lastGlyphOfColumn, effectiveRange: &actualGlyphRangeInContainer) if containerForLastGlyphOfColumn != containerToUse && containerToUse.size.height < availableSize.height { // If we are here, we overflowed the container, // BUT the container we overflowed didn't use // the maximum remaining page space (this // means it was a pre-existing container that // needs to be sized up and checked once more). // NOTE RE: THE BUG: // at this point, prints show... // containerToUse.size.height // =628 // availableSize.height // =648 containerToUse.size = availableSize containerForLastGlyphOfColumn = layoutManager.textContainer(forGlyphAt: lastGlyphOfColumn, effectiveRange: &actualGlyphRangeInContainer) } // We now check again, knowing that the container we // are testing flow into is the max size it can be. if containerForLastGlyphOfColumn != containerToUse { // If we are here, we have overflowed the // container, so containerToUse size SHOULD be // final/accurate, since it is fully used. actualCharRangeInContainer = layoutManager.characterRange(forGlyphRange: actualGlyphRangeInContainer, actualGlyphRange: nil) // Start of overflow range is the first character // in the container that was overflowed into. let overflowLoc = actualCharRangeInContainer.location remainingColumnRange = NSRange(location: overflowLoc, length: remainingColumnRange.length - overflowLoc) // Update page count as we have broken to a new page currentPage += 1 } else { // If we are here, we have NOT overflowed // from the container. BUT... // THE BUG: // ***** HERE IS THE BUG! ***** lineFragmentRectForLastChar = layoutManager.lineFragmentRect(forGlyphAt: lastGlyphOfColumn, effectiveRange: nil) let usedHeight = lineFragmentRectForLastChar.maxY // BUG: ^The lines of code above return a // fragment rect that is in the coordinates // of the WRONG text container. Prints show: // usedHeight // =14 // usedHeight shouldn't be just 14 if this is // the SAME container that, when it was 628 // high, resulted in text overflowing. // Therefore, the line fragment here seems // to be in the coordinates of the ENSUING // container that we overflowed INTO, but // that shouldn't be possible, since we're in // a closure for which we know: // // containerForLastGlyphOfColumn == containerToUse // // If the last glyph container is the container // we just had to size UP, why does the final // glyph line fragment rect have a maxY of 14!? // Including ensuing code below only for context. if usedHeight < containerToUse.size.height { // Adjust container size down to usedRect containerToUse.size = CGSize(width: containerToUse.size.width, height: usedHeight) } else if usedHeight == availableSize.height { // We didn't force break to a new page BUT // we've used exactly the height of our page // to layout this column range, so need to // break to a new page for any ensuing text // columns. currentPage += 1 } else if usedHeight > containerToUse.size.height { // We should have caught this earlier. Text // has overflowed, but this should've been // caught when we checked // containerForLastGlyphOfColumn != // containerToUse. // // Note: this error has never thrown. throw PaginationError.unknownError("Oops.") } } Per my comments in the code block above, I don't understand why the very same text container that just overflowed and so had to be sized up from 628 to 648 in order to try to fit a glyph would now report that same glyph as both being IN that same container and having a line fragment rect with a maxY of just 14. A glyph couldn't fit in a container when it was 628 high, but if I size it up to 648, it only needs 14? There's something very weird going on here. Working with NSLayoutManager is a bit of a nightmare given the unclear documentation. Any help or insight here would be massively, massively appreciated.
2
0
554
Apr ’25
NavigationStack within NavigationSplitView's detail column clears the path when disappearing
I'd like to persist the path on a sidebar selection, so when user comes back to the sidebar selection, they land where they were before. Unexpectedly, the path gets cleared when sidebarSelection is changed from the NavigationStack that uses the path to something else. Is this an intended behavior? How to workaround it? Using TabView is one way, but TabView has its own problems, so I'm wondering if there's a solution within NavigationSplitView first. Here is a minimal reproduce of the issue: struct Home2: View { private enum SidebarSelection: CaseIterable, Identifiable { var id: Self { self } case files, tags } @State private var sidebarSelection: SidebarSelection? = .files @State private var path: [Int] = [] var body: some View { NavigationSplitView { List(SidebarSelection.allCases, selection: $sidebarSelection) { selection in switch selection { case .files: Label("Files", image: "custom.square.stack") case .tags: Label("Tags", systemImage: "grid") } } } detail: { switch sidebarSelection { case .files: NavigationStack(path: $path) { Subview(depth: 1) .navigationDestination(for: Int.self) { Subview(depth: $0) } } case .tags: Text("Tags") default: EmptyView() } } .onChange(of: path) { print("\(path.count)") } } } struct Subview: View { let depth: Int var body: some View { List { NavigationLink("Next: \(depth + 1)", value: depth + 1) } .navigationTitle("Depth \(depth)") } } #Preview("Home2") { Home2() }
4
0
165
May ’25
onReceive(_:perform:) on Frontmost Window Only?
I have a simple document-based application for macOS. struct ContentView: View { @Binding var document: TextDocument var body: some View { .onReceive(NotificationCenter.default.publisher(for: .notificationTextWillAppendSomeTextt), perform: { _ in }) VStack { TextEditor(text: $document.text) } } } extension Notification.Name { static let notificationTextWillAppendSomeTextt = Notification.Name("TextWillAppendSomeText") } Suppose that my application currently has three tabs. If I call a menu command through post(name:object:) this menu command call will affect all three of them. This stackoverflow topic talks about it, too. So how could I tell which window should get a call and others don't? Thanks.
3
0
113
May ’25
Toggling UITextView attributes for spellchecking, smart quotes, etc stops working
I've added some menu actions to toggle various text view attributes named in the subject. The default for Simulator and my devices is to have these features turned on. But I'm finding that toggling them in my menu actions doesn't actually work. Toggling spellchecking or smart quotes (I haven't yet bothered to add more actions and test them) to .off does indeed set the correct value on the UITextView, but the features are still happening when I type (soft or hard keyboards behave the same). What's wrong? Is it simply broken and is caching the initial value or something? 18.4 is being used on my Simulator and my devices. I also tried 18.3.1 in Simulator with the same results.
Topic: UI Frameworks SubTopic: UIKit
Replies
3
Boosts
0
Views
121
Activity
Apr ’25
cannot save event
iOS 18.4.1 When I change a Google type event to an iCloud type, a "Cannot Save Event" prompt box pops up. We have also received user feedback that recurring events also fail to save. After updating to iOS 18.4 when trying to save changes to an existing repeating event, the message "Cannot Save Event" will appear. EventKitUI
Topic: UI Frameworks SubTopic: UIKit
Replies
0
Boosts
0
Views
68
Activity
May ’25
How to detect iPad trackpad touch-down (indirectPointer) to immediately stop coasting animation
Hello, I have a custom 3D object viewer on iOS that lets users spin the model using the touchscreen or a trackpad and supports coasting (momentum spinning). I need to stop the coasting animation as soon as the user touches down, but I can only immediately detect touches on the screen itself - on the trackpad I can't get an immediate notification of the touches. So far I’ve tried: State.began on my UIPanGestureRecognizer. It only fires after a small movement on both touchscreen and trackpad. .possible on the pan gesture; this state never occurs during the gesture cycle. UIApplicationSupportsIndirectInputEvents = YES in Info.plist; it didn’t make touchesBegan fire for indirectPointer touches. Since UITableView (and other UIScrollView subclasses) clearly detect trackpad “touch-down” to cancel scrolling, there must be a way to receive that event. Does anyone know how to catch the initial trackpad contact—before any movement—on an indirect input device? Below is a minimal code snippet demonstrating the issue. On the touchscreen you'll see a message the moment you touch the view, but the trackpad doesn't trigger any messages until your fingers move. Any advice would be greatly appreciated. Thanks in advance, John import UIKit class ViewController: UIViewController { private let debugView = DebugView() override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white // Fill the screen with our debug view debugView.frame = view.bounds debugView.autoresizingMask = [.flexibleWidth, .flexibleHeight] debugView.backgroundColor = UIColor(white: 0.95, alpha: 1) view.addSubview(debugView) // Attach a pan recognizer that logs its state let panGR = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) panGR.allowedScrollTypesMask = .all debugView.addGestureRecognizer(panGR) } @objc private func handlePan(_ gr: UIPanGestureRecognizer) { switch gr.state { case .possible: print("Pan state: possible") case .began: print("Pan state: began") case .changed: print("Pan state: changed – translation = \(gr.translation(in: debugView))") case .ended: print("Pan state: ended – velocity = \(gr.velocity(in: debugView))") case .cancelled: print("Pan state: cancelled") case .failed: print("Pan state: failed") @unknown default: print("Pan state: unknown") } } } class DebugView: UIView { override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { super.touchesBegan(touches, with: event) for t in touches { let typeDesc: String switch t.type { case .direct: typeDesc = "direct (finger)" case .indirectPointer: typeDesc = "indirectPointer (trackpad/mouse)" case .indirect: typeDesc = "indirect (Apple TV remote)" case .pencil: typeDesc = "pencil (Apple Pencil)" @unknown default: typeDesc = "unknown" } print("touchesBegan on DebugView – touch type: \(typeDesc), location: \(t.location(in: self))") } } }
Topic: UI Frameworks SubTopic: UIKit Tags:
Replies
2
Boosts
0
Views
116
Activity
Apr ’25
Lists, Generics, Views, Navigation Link, SwiftData - ForEach can't pass a binding anymore.
I'm trying out putting most of my business logic in a Protocol that my @Model can conform to, but I'm running into a SwiftUI problem with a Binding that does not get magically offered up like it does when it the subview is not generic. I have a pretty basic List with a ForEach that now can't properly pass to a generic view based on a protocol. When I try to make a binding manually in the row it says that "item is immutable"... but that also doesn't help me with the NavigationLink? Which is seeing the Binding not the ? But before when the subview was concrete to Thing, it took in the and made its own Binding once it hit the view. I'm unclear on precisely where the change happens and what I can do to work around it. Before I go rearchitecting everything... is there a fix to get the NavigationLink to take on the object like before? What needs to be different? I've tried a number of crazy inits on the subview and they all seem to come back to saying either it can't figure out how to pass the type or I'm trying to use the value before it's been initialized. Have I characterized the problem correctly? Thanks! (let me know if I forgot a piece of code, but this should be the List, the Model/Protocol and the subview) import SwiftUI import SwiftData struct ThingsView: View {     @Environment(\.modelContext) var modelContext     @Query var items: [Thing]          var body: some View {         NavigationStack {             List {                 ForEach(items) { item in                     NavigationLink(value: item) {                         VStack(alignment: .leading) {                             Text(item.textInfo)                                 .font(.headline)                                                          Text(item.timestamp.formatted(date: .long, time: .shortened))                         }                     }                 }.onDelete(perform: deleteItems)             }             .navigationTitle("Fliiiing!") //PROBLEM HERE: Cannot convert value of type '(Binding<Thing>) -> EditThingableView<Thing>' to expected argument type '(Thing) -> EditThingableView<Thing>'             .navigationDestination(for: Thing.self, destination: EditThingableView<Thing>.init) #if os(macOS)             .navigationSplitViewColumnWidth(min: 180, ideal: 200) #endif             .toolbar { #if os(iOS)                 ToolbarItem(placement: .navigationBarTrailing) {                     EditButton()                                      } #endif                 ToolbarItem {                     Button(action: addItem) {                         Label("Add Item", systemImage: "plus")                     }                 }                 ToolbarItem {                     Button("Add Samples", action: addSamples)                 }             }         }     }          func addSamples() {         withAnimation {             ItemSDMC.addSamples(context: modelContext)         }     }          private func addItem() {         withAnimation {             let newItem = ItemSDMC("I did a thing!")             modelContext.insert(newItem)         }     }          func deleteItems(_ indexSet:IndexSet) {         withAnimation {             for index in indexSet {                 items[index].delete(from: modelContext)             }         }     } } #Preview {     ThingsView().modelContainer(for: ItemSDMC.self, inMemory: true) } import Foundation import SwiftData protocol Thingable:Identifiable {     var textInfo:String { get set }     var timestamp:Date { get set } } extension Thingable {     var thingDisplay:String {         "\(textInfo) with \(id) at \(timestamp.formatted(date: .long, time: .shortened))"     } } extension Thingable where Self:PersistentModel {     var thingDisplayWithID:String {         "\(textInfo) with modelID \(self.persistentModelID.id) in \(String(describing: self.persistentModelID.storeIdentifier)) at \(timestamp.formatted(date: .long, time: .shortened))"     } } struct ThingLite:Thingable, Codable, Sendable {     var textInfo: String     var timestamp: Date     var id: Int } @Model final class Thing:Thingable {     //using this default value requires writng some clean up logic looking for empty text info.     var textInfo:String = ""     //using this default value would require writing some data clean up functions looking for out of bound dates.     var timestamp:Date = Date.distantPast          init(textInfo: String, timestamp: Date) {         self.textInfo = textInfo         self.timestamp = timestamp     } } extension Thing {     var LiteThing:ThingLite {         ThingLite(textInfo: textInfo, timestamp: timestamp, id: persistentModelID.hashValue)     } } import SwiftUI struct EditThingableView<DisplayItemType:Thingable>: View {     @Binding var thingHolder: DisplayItemType          var body: some View {                  VStack {             Text(thingHolder.thingDisplay)             Form {                 TextField("text", text:$thingHolder.textInfo)                 DatePicker("Date", selection: $thingHolder.timestamp)             }                      } #if os(iOS)         .navigationTitle("Edit Item")         .navigationBarTitleDisplayMode(.inline) #endif     } } //NOTE: First sign of trouble //#Preview { //    @Previewable var myItem = Thing(textInfo: "Example Item for Preview", timestamp:Date()) //    EditThingableView<Thing>(thingHolder: myItem) //}
Replies
4
Boosts
0
Views
209
Activity
May ’25
Printing NSTextStorage over multiple UITextView produces weird results
I would like to print a NSTextStorage on multiple pages and add annotations to the side margins corresponding to certain text ranges. For example, for all occurrences of # at the start of a line, the side margin should show an automatically increasing number. My idea was to create a NSLayoutManager and dynamically add NSTextContainer instances to it until all text is laid out. The layoutManager would then allow me to get the bounding rectangle of the interesting text ranges so that I can draw the corresponding numbers at the same height inside the side margin. This approach works well on macOS, but I'm having some issues on iOS. When running the code below in an iPad Simulator, I would expect that the print preview shows 3 pages, the first with the numbers 0-1, the second with the numbers 2-3, and the last one with the number 4. Instead the first page shows the number 4, the second one the numbers 2-4, and the last one the numbers 0-4. It's as if the pages are inverted, and each page shows the text starting at the correct location but always ending at the end of the complete text (and not the range assigned to the relative textContainer). I've created FB17026419. class ViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { let printController = UIPrintInteractionController.shared let printPageRenderer = PrintPageRenderer() printPageRenderer.pageSize = CGSize(width: 100, height: 100) printPageRenderer.textStorage = NSTextStorage(string: (0..<5).map({ "\($0)" }).joined(separator: "\n"), attributes: [.font: UIFont.systemFont(ofSize: 30)]) printController.printPageRenderer = printPageRenderer printController.present(animated: true) { _, _, error in if let error = error { print(error.localizedDescription) } } } } class PrintPageRenderer: UIPrintPageRenderer, NSLayoutManagerDelegate { var pageSize: CGSize! var textStorage: NSTextStorage! private let layoutManager = NSLayoutManager() private var textViews = [UITextView]() override var numberOfPages: Int { if !Thread.isMainThread { return DispatchQueue.main.sync { [self] in numberOfPages } } printFormatters = nil layoutManager.delegate = self textStorage.addLayoutManager(layoutManager) if textStorage.length > 0 { let glyphRange = layoutManager.glyphRange(forCharacterRange: NSRange(location: textStorage.length - 1, length: 0), actualCharacterRange: nil) layoutManager.textContainer(forGlyphAt: glyphRange.location, effectiveRange: nil) } var page = 0 for textView in textViews { let printFormatter = textView.viewPrintFormatter() addPrintFormatter(printFormatter, startingAtPageAt: page) page += printFormatter.pageCount } return page } func layoutManager(_ layoutManager: NSLayoutManager, didCompleteLayoutFor textContainer: NSTextContainer?, atEnd layoutFinishedFlag: Bool) { if textContainer == nil { addPage() } } private func addPage() { let textContainer = NSTextContainer(size: pageSize) layoutManager.addTextContainer(textContainer) let textView = UITextView(frame: CGRect(origin: .zero, size: pageSize), textContainer: textContainer) textViews.append(textView) } }
Topic: UI Frameworks SubTopic: UIKit Tags:
Replies
4
Boosts
0
Views
138
Activity
Apr ’25
UITextView crash on iOS 18.4 beta
UITextView crash when setting attributed text that contains substring ffi and attributedText contains NSFontAttributeName, NSForegroundColorAttributeName Reproducible case: UITextView *textView = [[UITextView alloc] init]; textView.attributedText = [[NSAttributedString alloc] initWithString:@"ffi" attributes:@{ NSParagraphStyleAttributeName: [self createParagraphOfLineHeight:20], NSFontAttributeName: [UIFont systemFontOfSize:fontSize weight:UIFontWeightRegular], NSForegroundColorAttributeName: UIColor.black }];
Topic: UI Frameworks SubTopic: UIKit Tags:
Replies
9
Boosts
2
Views
2.2k
Activity
May ’25
Setting UserDefaults in Preview
Hello, I've got a View that loads data from UserDefaults. I want to set the value of the UserDefault in the preview so i can see how it looks while developing. However when i am trying to set it in preview, i get the following error when i try set it in preview. 'buildExpression' is unavailable: this expression does not conform to 'View' What is the correct way to set the user defaults in preview? import Foundation class PreferencesViewModel: ObservableObject { @Published var maximumDistance: Double = UserDefaults.standard.value(for: .maximumDistance) as? Double ?? PreferencesViewModel.maximumDistanceOptions[0] { didSet { UserDefaults.standard.set(maximumDistance, for: .maximumDistance) } } #Preview { let preferencesViewModel = PreferencesViewModel() preferencesViewModel.maximumDistance = 5.0 PreferencesView() .environmentObject(PreferencesViewModel()) }
Replies
2
Boosts
0
Views
138
Activity
Apr ’25
Extra spacing above scrollview
I have a fairly simple view that consists of a tile and below it a horizontal scrollview. This display with a large gap between the title and the scrollview and I want the title sitting on top of the scrollview -- maybe 2 or 4 point gap. The current gap looks like 80 points or so. Code: let section: Section let userLevel: Int let stringsViewModel: StringsViewModel let onItemSelected: (AudioItem) -> Void var body: some View { VStack(alignment: .leading, spacing: 0) { Text(section.title) .font(.system(size: 18, weight: .medium)) .foregroundColor(.white) .padding(.leading, 16) ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: 16) { ForEach(section.items, id: \.titleKey) { item in MeditationItemView(audioItem: item, userLevel: userLevel, stringsViewModel: stringsViewModel) .onTapGesture { onItemSelected(item) } } } .padding(.leading, 0) .padding(.trailing, 16) } } .padding(.leading, 16) } }
Topic: UI Frameworks SubTopic: SwiftUI
Replies
3
Boosts
0
Views
82
Activity
May ’25
Hover effect in Custom UIKit Views
I am adapting my custom UI Framework for visionOS, and I'm wondering if it is going to be possible to detect hover over different UI elements within my view. The UI Framework draws to a Metal layer in a UIView. I don't currently support uihovergesturerecognizer on the view but I guess this wouldn't help, since you don't get coordinates. I can imagine an unpleasant solution might be to add invisible UIControls for each of my custom controls that are drawn in my own framework.
Replies
1
Boosts
0
Views
67
Activity
Apr ’25
iOS 18.4 Beta: On iPhones which support Dynamic Island, the AppDelegate method applicationDidBecomeActive() takes longer to be called.
When testing with iOS 18.4 Beta on iPhones which support Dynamic Island, after doing a Face ID authentication, the amount of time it takes before the AppDelegate method applicationDidBecomeActive() is called takes longer than iPhones that do not support Dynamic Island. The time it takes is about double, 1.2 seconds vs 2.5 seconds on average. This does not occur with versions before 18.4 Beta. Anyone else seeing this?
Topic: UI Frameworks SubTopic: UIKit
Replies
2
Boosts
3
Views
381
Activity
May ’25
RealityView in UIHostingController/UIKit transparency
I'm experimenting with RealityView in the UI of an AUv3 plug-in. The plug-in UI is implemented in a UIKitViewController with a UIHostingController hosting a RealityView. When i run the standalone app on visionOS I want the background to be transparent, and the reality view content. how can i achieve that? I've tried turning off opaque in many views and and setting background colors to .clear.
Replies
1
Boosts
0
Views
77
Activity
Apr ’25
Apple Pay
I'm implementing Apple Pay in a Flutter iOS app using the pay plugin and Braintree as the payment processor. I have followed all necessary steps as outlined by Apple and community resources (e.g., Medium articles, official Apple Developer documentation), but the Apple Pay button does not appear on a real device. Here's what I've completed: Created an Apple Pay Merchant ID Created and downloaded the Apple Pay Payment Processing Certificate, then uploaded it to Braintree Downloaded the Braintree-signed certificate and confirmed it's active in the Apple Developer portal Added the Merchant ID under Signing & Capabilities in Xcode Enabled Apple Pay capability in Xcode Added the Merchant ID to Info.plist Installed required Flutter packages (e.g., pay) Using a valid Apple Pay payment configuration file in Flutter (see below) Tested on a real iOS device with a valid Apple Pay test card added to Wallet Flutter Payment Configuration (in Dart JSON): json Copy Edit { "provider": "apple_pay", "data": { "merchantIdentifier": "merchant.com.example", "displayName": "My Store", "merchantCapabilities": ["3DS", "debit", "credit"], "supportedNetworks": ["visa", "masterCard", "amex"], "countryCode": "US", "currencyCode": "USD" } } Despite this complete setup, the ApplePayButton widget remains invisible There are no errors in the console. Can you help identify what may be missing or misconfigured at the code or configuration level?
Replies
2
Boosts
0
Views
211
Activity
May ’25
TipViewStyle layout broken in iOS 18.4 – Tip message gets truncated
Hi everyone, I’m using a custom TipViewStyle to modify the background and slightly adjust the layout of the Tips in my app. Everything looked great until iOS 18.4. Since updating, the layout is being compressed, and the message inside the Tip is getting truncated. Here’s a screenshot of how it looks on iOS 18.4 (truncated message) and another showing how it used to look before iOS 18.4 (correct layout). Here is the relevant code for the custom style: struct CustomTipViewStyle: TipViewStyle { func makeBody(configuration: Configuration) -> some View { VStack(alignment: .leading, spacing: 4) { HStack { configuration.title? .font(.headline) .foregroundColor(.daBackground) Spacer() Button(action: { configuration.tip.invalidate(reason: .tipClosed) }) { Image(systemName: "xmark") .foregroundColor(.daBackground.opacity(0.3)) } } VStack(alignment: .leading, spacing: 8.0) { configuration.message? .font(.subheadline) .foregroundColor(.daBackground.opacity(0.8)) Divider().background(.daBackground.opacity(0.3)) ForEach(configuration.actions) { action in HStack { Spacer() Button(action: action.handler) { action.label() .foregroundStyle(.accent) .font(.system(size: 18, weight: .bold)) } } } } } .padding() .background(Color.daBlack) } } Has anyone else experienced this issue with TipViewStyle in iOS 18.4? Any workarounds or solutions would be appreciated! Thanks in advance!
Replies
1
Boosts
1
Views
210
Activity
Apr ’25
Compiler stuck::considering giving up on SwiftUI
Apologies if this comes off as a bit of a rant, and TLDR: Compiler hangs or gives an unhelpful error with any level of complexity in SwiftUI - which makes me want to back to IB Porting a project over to iPhone. Made great progress the first week. However, lately I have been encountering compiler bugs where either the build never finishes, or the build take a really long time and ends with just the error : The compiler is unable to type-check this expression in reasonable time Sometimes I can get it to go away by playing divide and conquer and finding some small error, but other times with my nested scrollview it just seems unhappy with any level of complexity (and I don't think my views are all that complex compared to lots of other apps out there) I've read several posts on these forums for things to try and do to fix it but the bottom line is its impacting my ability to produce a working solution in a reasonable amount of time. So considering going back to Interface Builder though it means starting over with some of the code. If there was another independent way to syntax check my swiftUI code other than line by line myself I'd welcome something like that. The compiler going away and never coming back is very discouraging -- what if I have to make some kind of emergency fix in the future with limited time? Open for any suggestions. Xcode : 16.2
Replies
3
Boosts
0
Views
167
Activity
May ’25
NSTableView.clickedRow sometimes is greater than number of rows
Xcode has been downloading many similar crash reports for my app for some time now, related to an index out of range runtime exception when accessing a Swift array. The crashes always happen in methods triggered by user input or during menu item validation when I try to access the data source array by using the following code to determine the indexes of the relevant table rows: let indexes = clickedRow == -1 || selectedRowIndexes.contains(clickedRow) ? selectedRowIndexes : IndexSet(integer: clickedRow) I was never able to reproduce the crash until today. When the app crashed in the Xcode debugger, I examined the variables clickedRow and selectedRowIndexes.first, which were 1 and 0 respectively. What's interesting: the table view only contained one row, so clickedRow was effectively invalid. I tried to reproduce the issue several times afterwards, but it never happened again. What could cause this issue? What are the circumstances where it is invalid? Do I always have to explicitly check if clickedRow is within the data source range?
Topic: UI Frameworks SubTopic: AppKit Tags:
Replies
4
Boosts
0
Views
108
Activity
Apr ’25
List Layout Breaks in NavigationStack When a View Exceeds Screen Width
This is a bug report. FB17433985 The layout of the following ContentView appears correctly when it is outside a NavigationStack. However, when the same view is placed inside a NavigationStack, the layout breaks. It seems that the width of the List is being affected by the width of the buttonsView, which exceeds the screen width. In my testing, this issue occurs on iOS 18.4 and later, but does not occur on iOS 18.2 or iOS 17.5. Workaround I found: Remove the fixed width modifier from the Button If anyone knows of other ways to resolve this issue without affecting the layout, I would appreciate it if you could share them. import SwiftUI let values = (1...100).map { $0 } let buttonTitles = (1...9).map { "Button\($0)" } struct ContentView: View { var body: some View { VStack { List { Section { ForEach(values.indices, id: \.self) { val in HStack { Text("Index: \(val)") } } } } buttonsView } } private var buttonsView: some View { HStack { ForEach(0..<buttonTitles.count, id: \.self) { index in Button() { } label: { Image(systemName: "square.and.arrow.up") .resizable() .frame(width: 48, height: 48) } } } } } @main struct ButtonShapeBugApp: App { var body: some Scene { WindowGroup { if true { NavigationStack { ContentView() } } else { ContentView() } } } } Environment: Xcode Version 16.3 (16E140) iPhone 18.4.1 real device iPhone SE3rd 18.4 simulator Expect layout Broken layout(9 buttons) Broken layout(10 buttons)
Replies
2
Boosts
0
Views
95
Activity
May ’25
NSLayoutManager returning inconsistent values for a glyph's text container and its line fragment rect
TLDR: NSLayoutManager's textContainer(forGlyphAt:effectiveRange:) and lineFragmentRect(forGlyphRange:effectiveRange:) are returning inconsistent results. Context: I'm developing a word processing app that paginates from an NSTextStorage using NSLayoutManager. My app uses a text attribute (.columnType) to paginate sub-ranges of the text at a time, ensuring that each columnRange gets a container (or series of containers across page breaks) to fit. This is to support both multi-column and standard full-page-width content. After any user edit, I update pagination data in my Paginator model class. I calcuate frames/sizes for the views/containers, along with what superview they belong to (page). The UI updates accordingly. In order to determine whether the columnRange has overflowed from a container due to a page break OR whether the range of text hasn't overflowed its container and is actually using less space than available and should be sized down, I call both: layoutManager.textContainer(forGlyphAt: lastGlyphOfColumn, effectiveRange: &actualGlyphRangeInContainer)` // and `layoutManager.lineFragmentRect(forGlyphAt: lastGlyphOfColumn, effectiveRange: nil) Apple Documentation notes that both these calls force glyph generation and layout. As I'm in early development, I have not set non-contiguous layout. So these should be causing full layout, assuring accurate return values. Or so I'd hoped. This does work fine in many cases. I edit. Pagination works. But then I'll encounter UI-breaking inconsistent returns from these two calls. By inconsistent, I mean that the second call returns a line fragment rect that is in the container coordinates of A DIFFERENT container than the container returned by the first call. To be specific, the line fragment rect seems to be in the coordinates of the container that comes next in layoutManager.textContainers. Example Code: if !layoutManager.textContainers.indices.contains(i) { containerToUse = createTextContainer(with: availableSize) layoutManager.addTextContainer(containerToUse) } else { // We have a container already but it may be // the wrong size. containerToUse = layoutManager.textContainers[i] if containerToUse.size.width != availableSize.width { // Mandatory that we resize if we don't have // a matching width. Height resizing is not // mandatory and requires a layout check below. containerToUse.size = availableSize } } let glyphRange = layoutManager.glyphRange(forCharacterRange: remainingColumnRange, actualCharacterRange: nil) let lastGlyphOfColumn = NSMaxRange(glyphRange) - 1 var containerForLastGlyphOfColumn = layoutManager.textContainer(forGlyphAt: lastGlyphOfColumn, effectiveRange: &actualGlyphRangeInContainer) if containerForLastGlyphOfColumn != containerToUse && containerToUse.size.height < availableSize.height { // If we are here, we overflowed the container, // BUT the container we overflowed didn't use // the maximum remaining page space (this // means it was a pre-existing container that // needs to be sized up and checked once more). // NOTE RE: THE BUG: // at this point, prints show... // containerToUse.size.height // =628 // availableSize.height // =648 containerToUse.size = availableSize containerForLastGlyphOfColumn = layoutManager.textContainer(forGlyphAt: lastGlyphOfColumn, effectiveRange: &actualGlyphRangeInContainer) } // We now check again, knowing that the container we // are testing flow into is the max size it can be. if containerForLastGlyphOfColumn != containerToUse { // If we are here, we have overflowed the // container, so containerToUse size SHOULD be // final/accurate, since it is fully used. actualCharRangeInContainer = layoutManager.characterRange(forGlyphRange: actualGlyphRangeInContainer, actualGlyphRange: nil) // Start of overflow range is the first character // in the container that was overflowed into. let overflowLoc = actualCharRangeInContainer.location remainingColumnRange = NSRange(location: overflowLoc, length: remainingColumnRange.length - overflowLoc) // Update page count as we have broken to a new page currentPage += 1 } else { // If we are here, we have NOT overflowed // from the container. BUT... // THE BUG: // ***** HERE IS THE BUG! ***** lineFragmentRectForLastChar = layoutManager.lineFragmentRect(forGlyphAt: lastGlyphOfColumn, effectiveRange: nil) let usedHeight = lineFragmentRectForLastChar.maxY // BUG: ^The lines of code above return a // fragment rect that is in the coordinates // of the WRONG text container. Prints show: // usedHeight // =14 // usedHeight shouldn't be just 14 if this is // the SAME container that, when it was 628 // high, resulted in text overflowing. // Therefore, the line fragment here seems // to be in the coordinates of the ENSUING // container that we overflowed INTO, but // that shouldn't be possible, since we're in // a closure for which we know: // // containerForLastGlyphOfColumn == containerToUse // // If the last glyph container is the container // we just had to size UP, why does the final // glyph line fragment rect have a maxY of 14!? // Including ensuing code below only for context. if usedHeight < containerToUse.size.height { // Adjust container size down to usedRect containerToUse.size = CGSize(width: containerToUse.size.width, height: usedHeight) } else if usedHeight == availableSize.height { // We didn't force break to a new page BUT // we've used exactly the height of our page // to layout this column range, so need to // break to a new page for any ensuing text // columns. currentPage += 1 } else if usedHeight > containerToUse.size.height { // We should have caught this earlier. Text // has overflowed, but this should've been // caught when we checked // containerForLastGlyphOfColumn != // containerToUse. // // Note: this error has never thrown. throw PaginationError.unknownError("Oops.") } } Per my comments in the code block above, I don't understand why the very same text container that just overflowed and so had to be sized up from 628 to 648 in order to try to fit a glyph would now report that same glyph as both being IN that same container and having a line fragment rect with a maxY of just 14. A glyph couldn't fit in a container when it was 628 high, but if I size it up to 648, it only needs 14? There's something very weird going on here. Working with NSLayoutManager is a bit of a nightmare given the unclear documentation. Any help or insight here would be massively, massively appreciated.
Replies
2
Boosts
0
Views
554
Activity
Apr ’25
NavigationStack within NavigationSplitView's detail column clears the path when disappearing
I'd like to persist the path on a sidebar selection, so when user comes back to the sidebar selection, they land where they were before. Unexpectedly, the path gets cleared when sidebarSelection is changed from the NavigationStack that uses the path to something else. Is this an intended behavior? How to workaround it? Using TabView is one way, but TabView has its own problems, so I'm wondering if there's a solution within NavigationSplitView first. Here is a minimal reproduce of the issue: struct Home2: View { private enum SidebarSelection: CaseIterable, Identifiable { var id: Self { self } case files, tags } @State private var sidebarSelection: SidebarSelection? = .files @State private var path: [Int] = [] var body: some View { NavigationSplitView { List(SidebarSelection.allCases, selection: $sidebarSelection) { selection in switch selection { case .files: Label("Files", image: "custom.square.stack") case .tags: Label("Tags", systemImage: "grid") } } } detail: { switch sidebarSelection { case .files: NavigationStack(path: $path) { Subview(depth: 1) .navigationDestination(for: Int.self) { Subview(depth: $0) } } case .tags: Text("Tags") default: EmptyView() } } .onChange(of: path) { print("\(path.count)") } } } struct Subview: View { let depth: Int var body: some View { List { NavigationLink("Next: \(depth + 1)", value: depth + 1) } .navigationTitle("Depth \(depth)") } } #Preview("Home2") { Home2() }
Replies
4
Boosts
0
Views
165
Activity
May ’25
CarPlay application inaccessible when the mobile app is terminated
Hello, When my mobile app is terminated, say 30 secs later the CarPlay app stops working. I don't get the access token that is saved in the KeyChain. The same happens when my mobile app is in background for more than 20 secs or so. Please suggest the way forward. Or is this the expected behavior?
Replies
1
Boosts
0
Views
118
Activity
Apr ’25
onReceive(_:perform:) on Frontmost Window Only?
I have a simple document-based application for macOS. struct ContentView: View { @Binding var document: TextDocument var body: some View { .onReceive(NotificationCenter.default.publisher(for: .notificationTextWillAppendSomeTextt), perform: { _ in }) VStack { TextEditor(text: $document.text) } } } extension Notification.Name { static let notificationTextWillAppendSomeTextt = Notification.Name("TextWillAppendSomeText") } Suppose that my application currently has three tabs. If I call a menu command through post(name:object:) this menu command call will affect all three of them. This stackoverflow topic talks about it, too. So how could I tell which window should get a call and others don't? Thanks.
Replies
3
Boosts
0
Views
113
Activity
May ’25