Construct and manage a graphical, event-driven user interface for your macOS app using AppKit.

AppKit Documentation

Posts under AppKit subtopic

Post

Replies

Boosts

Views

Activity

Remove bottom border in a row in AppKit's NSTableView
I just made a simple AppKit app, but don't know how to remove borders of rows when they're swiped. SwiftUI's list does not have this problem though. Attaching gif demo and code: import SwiftUI struct NSTableViewWrapper: NSViewRepresentable { @State var data: [String] class Coordinator: NSObject, NSTableViewDataSource, NSTableViewDelegate { var parent: NSTableViewWrapper weak var tableView: NSTableView? init(parent: NSTableViewWrapper) { self.parent = parent } func numberOfRows(in tableView: NSTableView) -> Int { self.tableView = tableView return parent.data.count } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("Cell"), owner: nil) as? NSTextField ?? NSTextField(labelWithString: "") cell.identifier = NSUserInterfaceItemIdentifier("Cell") cell.stringValue = parent.data[row] cell.isBordered = false return cell } func tableView(_ tableView: NSTableView, rowActionsForRow row: Int, edge: NSTableView.RowActionEdge) -> [NSTableViewRowAction] { guard edge == .trailing else { return [] } let deleteAction = NSTableViewRowAction(style: .destructive, title: "Delete") { action, index in self.deleteRow(at: index, in: tableView) } return [deleteAction] } private func deleteRow(at index: Int, in tableView: NSTableView) { guard index < parent.data.count else { return } NSAnimationContext.runAnimationGroup({ context in context.duration = 0.3 tableView.removeRows(at: IndexSet(integer: index), withAnimation: .slideUp) }, completionHandler: { DispatchQueue.main.async { self.parent.data.remove(at: index) tableView.reloadData() } }) } } func makeCoordinator() -> Coordinator { return Coordinator(parent: self) } func makeNSView(context: Context) -> NSScrollView { let scrollView = NSScrollView() let tableView = NSTableView() let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("Column")) column.width = 200 tableView.addTableColumn(column) tableView.delegate = context.coordinator tableView.dataSource = context.coordinator tableView.backgroundColor = .clear tableView.headerView = nil tableView.rowHeight = 50 tableView.style = .inset scrollView.documentView = tableView scrollView.hasVerticalScroller = true scrollView.additionalSafeAreaInsets = .init(top: 0, left: 0, bottom: 6, right: 0) return scrollView } func updateNSView(_ nsView: NSScrollView, context: Context) { (nsView.documentView as? NSTableView)?.reloadData() } } struct ContentView: View { @State private var itemsString = Array(0..<40).map(\.description) var body: some View { NSTableViewWrapper(data: itemsString) } } func createAppWindow() { let window = NSWindow( contentRect: .zero, styleMask: [.titled], backing: .buffered, defer: false ) window.title = "NSTableView from AppKit" window.contentViewController = NSHostingController(rootView: ContentView()) window.setContentSize(NSSize(width: 759, height: 300)) window.center() window.makeKeyAndOrderFront(nil) } class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { createAppWindow() } } let delegate = AppDelegate() NSApplication.shared.delegate = delegate NSApplication.shared.run()
Topic: UI Frameworks SubTopic: AppKit Tags:
1
0
171
Mar ’25
AppKit: presentAsModalWindow doesn't center the presented window on macOS 15
When I present a view controller, whose view is a SwiftUI View, via presentAsModalWindow(_:) the presented window is no longer centered horizontally to the screen, but rather its origin is there. I know this issue occurs for macOS 15.2+, but can't tell if it is from 15.0+. I couldn't find any documentation on why was this changed. Here's an example code that represents my architecture: class RootViewController: NSViewController { private lazy var button: NSButton = NSButton( title: "Present", target: self, action: #selector(presentView)) override func viewDidLoad() { super.viewDidLoad() // Add button to tree } @objc func presentView() { presentAsModalWindow(PresentedViewController()) } } class PresentedViewController: NSViewController { override loadView() { view = NSHostingView(rootView: MyView()) } } struct MyView: View { /* impl */ }
Topic: UI Frameworks SubTopic: AppKit Tags:
0
0
137
Mar ’25
How do we retrieve UnknownFSObjectIcon.icns these days?
In the good old days, it was possible to retrieve dynamically the UnknownFSObjectIcon.icns icon using: [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUnknownFSObjectIcon)]; Now, this solution is considered to be deprecated (but is still working) by recent macOS SDKs. [Q] What is the modern equivalent of this solution? Notes: Yes, reading the file directly works but is more fragile than using a System API. Yes, Xcode suggests to use the iconForContentType: method but I haven't found which UTType should be used.
1
0
289
Mar ’25
Avoiding logoff when installing new/modified InputMethodKit input source
It appears that on all recent versions of macOS when adding a new InputSource in /Library/Input Methods (or modifying an existing one there) the user needs to logoff and log back in in order for Keyboard/Input Sources in System Settings and Input Menu in menu bar to pick up the changes. Is there a way to avoid this? That is, some notification to send or API to call to tell both of these "hey, things might have changed on disk, please re-read the info, and update the UI". 🙂
1
0
207
Feb ’25
NSWritingToolsCoordinator can't complete proofreading.
I am revising my app to support NSWritingToolsCoordinator/ NSWritingToolsCoordinatorDelegate. When proofreading some paragraphs, it works well. But, when proofreading many paragraphs (for example, 56 paragraphs), it can't complete the proofreading. I am not sure which is wrong with my app or macOS API. I think I implemented all NSWritingToolsCoordinatorDelegate methods. Is there any information for such an issue? Phenomenon For paragraphs 1-9, text animation completed. But, for paragraphs 10-56, text animation does not complete. It shows 5 corrected items, but I can't jump to items 3, 4, 5, when I click the ">" button in the "Proofread" window. Items 3, 4, 5 were not corrected actually. Log For each NSWritingToolsCoordinatorDelegate method, the method name and main arguments are output by NSLog. requestsContextsForScope willChangeToState newState:2 requestsPreviewForTextAnimation range:(0, 18233) prepareForTextAnimation range:(0, 18233) willChangeToState newState:3 requestsPreviewForTextAnimation range:(0, 18233) finishTextAnimation range:(0, 18233) requestsPreviewForRect requestsPreviewForTextAnimation range:(0, 1837) replaceRange proposedText:an range:(208, 2) replaceRange proposedText:you range:(443, 4) prepareForTextAnimation range:(1836, 16396) requestsPreviewForTextAnimation range:(0, 1836) requestsBoundingBezierPathsForRange range:(208, 2) requestsBoundingBezierPathsForRange range:(443, 3) requestsPreviewForRect prepareForTextAnimation range:(0, 1836) prepareForTextAnimation range:(1836, 0) finishTextAnimation range:(1836, 16396) requestsPreviewForTextAnimation range:(1836, 16396) requestsBoundingBezierPathsForRange range:(208, 2) requestsBoundingBezierPathsForRange range:(443, 3) prepareForTextAnimation range:(1836, 16396) finishTextAnimation range:(0, 1836) finishTextAnimation range:(0, 1836) replaceRange proposedText:an range:(208, 2) requestsUnderlinePathsForRange range:(208, 2) requestsUnderlinePathsForRange range:(443, 3) selectRanges ranges.count:1 requestsBoundingBezierPathsForRange range:(208, 2) replaceRange proposedText:an range:(208, 2) requestsUnderlinePathsForRange range:(208, 2) requestsUnderlinePathsForRange range:(443, 3) selectRanges ranges.count:1 replaceRange proposedText:you range:(443, 3) requestsUnderlinePathsForRange range:(208, 2) requestsUnderlinePathsForRange range:(443, 3) selectRanges ranges.count:1 requestsBoundingBezierPathsForRange range:(443, 3) replaceRange proposedText:you range:(443, 3) requestsUnderlinePathsForRange range:(208, 2) requestsUnderlinePathsForRange range:(443, 3) selectRanges ranges.count:1 macOS version is 15.3.1 (24D70)
1
0
272
Feb ’25
NSDocumentController subclass with remembered document options
Hi all, I am trying to allow users of my app to select extra options when opening documents, and to remember those options when re-opening documents at launch. So far best idea I have is: Subclass NSDocumentController to provide an NSOpenPanel.accessoryView with the options Create a URL bookmark for each opened file and keep a mapping of bookmarks to options On launch and when the recent documents list changes, prune the stored mappings to match only the recent items Has anyone done this before, or know of a better approach? Thank you.
Topic: UI Frameworks SubTopic: AppKit
0
0
265
Feb ’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
415
Feb ’25
Crash on Sequoia 15.2
Starting from Sequoia release 15.2 apps crash with following call stack, when adding static text controls. First call to [NSTextField setStringValue] causes following crash 0 libobjc.A.dylib 0x19f2f5820 objc_msgSend + 32 1 AppKit 0x1a3355460 -[NSCell _objectValue:forString:errorDescription:] + 144 2 AppKit 0x1a3355348 -[NSCell setStringValue:] + 48 3 AppKit 0x1a33af9fc -[NSControl setStringValue:] + 104 4 AppKit 0x1a3d1f190 -[NSTextField setStringValue:] + 52 It happens on specific MacBook Pro models(16 in MacBook Pro). Crash analysis found that isa pointer of the object was corrupted for the object NSCell. Enabled Zombies in debugging, not found any issue. Also tried address sanitizer. Since the issue started with a recent release of macOS, any changes in the Appkit in the recent releases trigger the crash? Any help/suggestions would be appreciated.
Topic: UI Frameworks SubTopic: AppKit
4
0
326
Feb ’25
macos 15, savepanel return the wrong path when saving files occasionally
macOS: 15.0 macFUSE: 4.8.3 I am using rclone + macFUSE and mount my netdisk where it has created three subdirectories in its root directory: /user, /share, and /group. When I save a file to /[root]/user using NSSavePanel and name it test.txt, I expect the file to be saved as: /[root]/user/test.txt However, occasionally, the delegate method: - (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError **)outError { } returns an incorrect path: /[root]/test.txt This issue only occurs when selecting /user. The same operation works correctly for /share and /group. Is there any logs I could provide to help solving this issue? Many thanks!
Topic: UI Frameworks SubTopic: AppKit Tags:
0
0
319
Feb ’25
Will [NSEvent setMouseCoalescingEnabled:NO] even work in Swift?
In a macOS Swift app I'm trying to get mouse events to not coalesce. This used to work in an Obj-C app by calling [NSEvent setMouseCoalescingEnabled:NO]. setMouseCoalescingEnabled is not exposed in the Swift version of NSEvent. I built a bridge over to a .mm file that calls [NSEvent setMouseCoalescingEnabled:NO] and checked NSEvent.isMouseCoalescingEnabled in Swift after calling and it returns false saying that coalescing is disabled. The mouse is still skipping points (func mouseDragged(with theEvent: NSEvent)) when it is dragged across the window fast. I'm calling [NSEvent setMouseCoalescingEnabled:NO] in applicationDidFinishLaunching().
Topic: UI Frameworks SubTopic: AppKit
1
0
306
Feb ’25
macOS Menu disappears when converting from NSApplicationActivationPolicyAccessory to NSApplicationActivationPolicyRegular if display disconnected and reconnected
I'm not quite sure where the problem is, but I will describe what I am doing to recreate the issue, and am happy to provide whatever information I can to be more useful. I am changing the ActivationPolicy for my app in order to make it unobtrusive when in the background (e.g. hiding it from the dock and using only a menu bar status item). When the user activates the app with a hotkey, it changes from NSApplicationActivationPolicyAccessory back to NSApplicationActivationPolicyRegular. This allows normal usage (dock icon, menu bar, etc.) This works fine, except in a rare situation which I finally just tracked down. If there is a window open in the app and I use the hotkey to convert back to an accessory, and then disconnect and reconnect the display on which the app was previously displayed, when I convert the app back to "regular mode", the menu bar has disappeared (and I am left with an empty space at the top of the screen). I can also trigger this bug by having the display in question briefly mirror the other display (effectively "orphaning" the hidden app), and then restoring the original side-by-side configuration before activating the app again. The app otherwise works, but the menu bar is missing. Switching back and forth with other apps does not fix the problem. Quitting and restarting the app resolves the issue. As does disabling the accessory only mode and forcing the app to always remain in "regular mode" with a dock icon (there is a preference for this in my app). Once fixed, I can then re-enable the "accessory mode" and all is well until the bug is triggered again. The bug would normally occur quite sporadically, presumably requiring a particular combination of changing Spaces or displays, or having the computer go to sleep while this app was in accessory mode. Thus far, the above is the only way I have found that can replicate this issue on demand. If I close all windows before hiding the app, then it works fine when I revert to "regular mode". It only happens if there is a window open at the time. Using applicationDidChangeScreenParameters: on my AppDelegate indicates that there is a change in screen, and logging window.screen.frame for each open window in [NSApp orderedWindows] shows that the size changes from e.g. 1920x1080 to 0x0 and back while the display is disconnected or mirrored. There is also an error in the console in Xcode when this happens -- invalid display identifier <some UUID>. I have tried various options for window collectionBehavior, as well as various settings for Spaces (which I normally use). None of these changes has fixed the behavior thus far. I use [NSApp hide:self]; from my AppDelegate to hide the app, and [[NSRunningApplication currentApplication] activateWithOptions:NSApplicationActivateAllWindows];[NSApp unhide:self]; to bring it back to the front. I welcome any ideas for things to chase down, or requests for more specific information that would be useful. Thank you! Fletcher
1
0
370
Feb ’25
Can't join current Group Activity on macOS (maybe some launch services thing?)
I'm testing using Group Activities and having no trouble iOS<->iOS or starting an activity on macOS and joining via iOS. However, when I start an activity and then try to join it from another macOS client, the starting side joins the session just fine, but the receiving side acts like I don't have the required app, even when it is already running. I see the active SharePlay icon in the menu bar, and the Current Activity is shown, but instead of an "Open" button there is a "MyApp Required" string and a "View" button that goes to the App Store. (Where the app is not available yet, as expected, since I'm still working on it.) There is no GroupSession started on that Mac yet, obviously. I'm looking for any hints to help debug what is going on. How does Group Activities find the app for the activity on macOS and how can I figure out why it isn't finding mine? Thanks!
0
0
292
Feb ’25
AppKit document project reports an error when opening a new document in Swift 6
In an AppKit document-based project created by Xcode, setting canConcurrentlyReadDocuments to true allows new documents to open normally in Swift 5, but switching to Swift 6 causes an error. Judging from the error message, it seems to be a threading issue, but I’m not sure how to adjust the code to support the Swift 6 environment. The project is the most basic code from an Xcode-created document-based project without any modifications, except for changing the Swift version to 6 and setting canConcurrentlyReadDocuments to true. Source code: https://drive.google.com/file/d/1ryb2TaU6IX884q0h5joJqqZwSX95Q335/view?usp=sharing AppDelegate.swift import Cocoa @main class AppDelegate: NSObject, NSApplicationDelegate { func applicationDidFinishLaunching(_ aNotification: Notification) { // Insert code here to initialize your application } func applicationWillTerminate(_ aNotification: Notification) { // Insert code here to tear down your application } func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { return true } } Document.swift import Cocoa class Document: NSDocument { override init() { super.init() // Add your subclass-specific initialization here. } override class var autosavesInPlace: Bool { return true } override class func canConcurrentlyReadDocuments(ofType typeName: String) -> Bool { true } override func canAsynchronouslyWrite(to url: URL, ofType typeName: String, for saveOperation: NSDocument.SaveOperationType) -> Bool { true } override func makeWindowControllers() { // Returns the Storyboard that contains your Document window. let storyboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil) let windowController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("Document Window Controller")) as! NSWindowController self.addWindowController(windowController) } override func data(ofType typeName: String) throws -> Data { // Insert code here to write your document to data of the specified type, throwing an error in case of failure. // Alternatively, you could remove this method and override fileWrapper(ofType:), write(to:ofType:), or write(to:ofType:for:originalContentsURL:) instead. // throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) return Data() } override func read(from data: Data, ofType typeName: String) throws { // Insert code here to read your document from the given data of the specified type, throwing an error in case of failure. // Alternatively, you could remove this method and override read(from:ofType:) instead. // If you do, you should also override isEntireFileLoaded to return false if the contents are lazily loaded. // throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) } } ViewController.swift import Cocoa class ViewController: NSViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } override var representedObject: Any? { didSet { // Update the view, if already loaded. } } }
1
1
515
Feb ’25
Custom app icon on macOS
I would like to provide a way to choose an app icon as a reward for using the referral feature of the app. I found that setting the image via NSApp.applicationIconImage works, but only when the app is running, and the app icon is reset on the next launch. I also found NSDockTile plugin APIs, and that seems to be the way, but it won't be allowed on the Mac App Store. Is there really no way to do it legit way and pass the Mac App Store review? Seems to be another really strange limitation imposed.
Topic: UI Frameworks SubTopic: AppKit Tags:
1
0
332
Jan ’25
NSLayoutManager laying out overlapping text into the same NSTextContainer even when there are more containers available.
In summation: I have a nasty bug where my layout manager is laying out text visually overlapping on top of other text, i.e., into a container that it should have left in the rear view as it continues to lay out into ensuing containers. Details below... I'm coding a word processing app with some custom pagination that involves multiple pages, within which there can be multiple NSTextView/NSTextContainer pairs that represent single column or dual column runs of text. I generate pagination data by using a measuring NSLayoutManager. This process ensures that no containers overlap, and that they are sized correctly for their associated ranges of text (i.e., non-overlapping, continuous ranges from a single NSTextStorage). I determine frame sizes by a series of checks, most importantly, by finding the last glyph in a column. Prior to the code below, remainingColumnRange represents the remaining range of my textStorage that is of a consistent column type (i.e., single, left column, or right column). My measuring passes consist of my measuringLayoutManager laying out text into its textContainers, the final of which is an extra overflowContainer (i.e., == measuringLayoutManager.textContainers.last!) which I only use to find the last glyph in the second to last container (measuringContainer, which is thus == measuringLayoutManager.textContainers[count - 2]) let glyphRangeOfLastColumnChar = measuringLayoutManager.glyphRange(forCharacterRange: remainingColumnRange, actualCharacterRange: nil) let lastGlyphIndex = NSMaxRange(glyphRangeOfLastColumnChar) - 1 measuringLayoutManager.ensureLayout(for: measuringContainer) // Not sure if this is necessary, but I've added it to insure I'm getting accurate measurements. if measuringLayoutManager.textContainer(forGlyphAt: lastGlyphOfColumnIndex, effectiveRange: &actualGlyphRangeInContainer) == overflowContainer { actualCharRangeInContainer = measuringLayoutManager.characterRange(forGlyphRange: actualGlyphRangeInContainer, actualGlyphRange: nil) let overflowLoc = actualCharRangeInContainer.location remainingColumnRange = NSRange(location: overflowLoc, length: remainingColumnRange.length - overflowLoc) currentPage += 1 } else { lineFragmentRectForLastChar = measuringLayoutManager.lineFragmentRect(forGlyphAt: lastGlyphIndex, effectiveRange: nil) // Resize measuring container if needed. let usedHeight = lineFragmentRectForLastChar.maxY if usedHeight < measuringContainer.size.height { measuringContainer.size = CGSize(width: measuringContainer.size.width, height: usedHeight) } else if usedHeight == measuringContainer.size.height { currentPage += 1 // we perfectly filled the page } else { // This would be an error case, because all cases should have been handled prior to arriving here. I throw an error. I have never fallen through here. throw MyClass.anError } } // I use the above data to create a PageLayoutItem, which is a struct that has frame data (CGRect/x,y,w,h), a containerIndex (Int), pageNumber (Int), textRange (NSRange), columnType (custom enum). // After this I remove the overflowContainer, and continue to iterate through. This is inefficient but I'm simplifying my code to identify the root issue. I don't explicitly use these containers when done with my pagination process. Rather, I use the PageLayoutItems I have created to generate/resize/remove textContainers/textViews for the UI as needed. My UI-interfacing/generating NSLayoutManager, which is of course assigned to the same NSTextStorage as the measuring layout manager, then iterates through my paginator model class' pageLayoutItems array to generate/resize/remove. I have verified my pagination data. None of my frames overlap. They are sized exactly the same as they should be per my measurement passes. The number of containers/views needed is correct. But here's the issue: My views render the text that SHOULD appear in my final textContainer/textView as visually overlapping the text in my second to last textContainer/textView. I see a garble of text. When I iterate through my UI textContainers, I get this debug print: TextContainer 0 glyphRange: {0, 172} TextContainer 1 glyphRange: {172, 55} TextContainer 2 glyphRange: {227, 100} // this is wrong, final 31 chars should be in container 3 TextContainer 3 glyphRange: {327, 0} // empty range here, odd I have tried setting textContainers for glyph ranges explicitly, via: // Variable names just for clarity here layoutManager.setTextContainer(correctTextView.textContainer!, forGlyphRange: correctGlyphRangeForThisContainer) Debug prints show that I'm setting the right ranges there. But they don't retain. I have tried resizing my final text container to be much larger in case that was the issue. No dice. My final range of text/glyphs still lays out in the wrong container and overlaps the other content laid out there. Any help here?? I've scoured the forums and have been dealing with this bug for two weeks straight with no hope in sight.
4
0
869
Jan ’25
Genmoji is there but doesn't display in NSTextView
I have discovered an odd issue with NSTextView, NSAdaptiveImageGlyph, and NSLayoutManager. (Forgive the Objective-C... I'm old-school.) I can easily display an attributed string containing text and Genmoji NSAdaptiveImageGlyphs with something very basic like this: textView = [[NSTextView alloc] initWithFrame:NSMakeRect(100.0, 100.0, 500.0, 100.0)]; [textView.textStorage setAttributedString:sampleText]; [self addSubview:textView]; //NSLayoutManager *layoutManager = textView.layoutManager; I can even insert or paste new Genmoji into there as well. However, if I uncomment out that last line to retrieve the layoutManager of the text view it breaks the rendering of Genmoji, even if I do nothing with the layoutManager! Just getting it causes the NSTextView to skip past Genmoji glyphs when spacing out glyphs and when rendering. I thought perhaps getting the layoutManager caused an internal cache to break so I tried ensureLayoutForTextContainer but that didn't help. Interestingly if I paste a new Genmoji into the NSTextView it doesn't display either. Yet if I select all, copy, and paste into TextEdit I see all the Genmoji just fine so they are there, just invisible and zero size. Using the arrow keys I have to skip past the invisible Genmoji. But if I comment out that line again I see all Genmoji with no issues. I do need to use a layoutManager to measure things like text bounds and heights. Elsewhere in the code, my existing drawing routines would like to draw the resulting attributed string not with drawAtPoint (which works fine) but with drawGlyphsForGlyphRange but that doesn't work either as it uses NSLayoutManager. Is there a trick to getting NSAdaptiveImageGlyphs working with NSLayoutManager? Thanks for any assistance!
3
0
874
Jan ’25