This is a macOS related thing.
I have chased this forever in vain.
This is akin to NSCell.isHighlighted or NSCell.backgroundStyle
I for sure want to use some special colors when not in highlight mode.
A cell will be first highlighted in mouse down and than eventually selected on mouse up.
So apple has to provide an API or an environment value to indicate to our row view that is highlighted or not.
Maybe @eskimo can look up the source for this and shed some deep insight.
Post
Replies
Boosts
Views
Activity
I had the same vexing problem and remembered that often this is due to macOS caching. Since for example it will work for new projects.
Removed all Xcode caches ~/Library/Developer/Xcode/DerivedData/
Rebooted, I also suspect a log out/log in will do.
Rebuild and profiled my app and the source is there, tada.
This is a known bug and has been fixed on Xcode 14, beta 5 and macOS 13.
If you need this feature on older systems, such as macOS 11 + you will need to go back and use a MainMenu.xib and an AppDelegate
struct SwiftUIView: View {
var body: some View {
Text("Hello, SwiftUI!")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
@main
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 270),
styleMask: [.miniaturizable, .closable, .resizable, .titled],
backing: .buffered, defer: false)
window.center()
window.title = "No Storyboard Window"
window.contentView = NSHostingView(rootView: SwiftUIView())
window.makeKeyAndOrderFront(nil)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
Small world.
I have been banging my head for hours on this.
I have a real SwiftUI app, with one Window.
When this Window (mainWindow/keyWindow) is minimized an icon is added on the dock.
Now if i click on the app icon, i expect this minimized window to expand again.
But it does not.
If i switch to another app and than click on the app icon on the doc, the minimized window is expanded as expected.
It appears the app thinks it is still active applicationDidResignActive: is not called.
@eskimo What magic is going on pls ?
These are special links, indeed, I knew a few under /Volume/ were mounts, but forgot that some others are also links such as /Applications
[macos-bigsur]/usr/local> ls -ail /
12886009979 drwxrwxr-x 7 root admin 224 Feb 4 11:00 Applications/
[macos-bigsur]/usr/local> ls -ail /System/Volumes/Data/
12886009979 drwxrwxr-x 7 root admin 224 Feb 4 11:00 Applications/
/Applications to /System/Volumes/Data/Applications
My problem comes from inconsistent results of stat() vs getattrlistbulk()
Now if I could just figure that out :-)
After spending a few more hours debuging this, it is possible I have a bad pointer arithmetic somewhere.
However this code fails on about a dozen paths in my giant file system. (more than 4m inodes)
- (IDDNode*)_fetchChild:(char**)entry_start :(NSString*)parentPath :(uint64)pnode {
IDDNodeAttribute attribute;
char* field = *entry_start;
attribute.length = *(uint32_t *)field;
field += sizeof(uint32_t);
*entry_start += attribute.length;
attribute.returned = *(attribute_set_t *)field;
field += sizeof(attribute_set_t);
if (attribute.returned.commonattr & ATTR_CMN_NAME) {
attribute.name = field;
attribute.name_info = *(attrreference_t *)field;
field += sizeof(attrreference_t);
// DEBUG
NSString* childPath = [parentPath stringByAppendingPathComponent:[NSString stringWithUTF8String:(attribute.name + attribute.name_info.attr_dataoffset)]];
_logInfo(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@'", childPath]);
}
if (attribute.returned.commonattr & ATTR_CMN_FSID) {
attribute.fsid = *(fsid_t *)field;
field += sizeof(fsid_t);
int32_t fileSystemID = (int32_t)attribute.fsid.val[0];
// DEBUG
NSString* childPath = [parentPath stringByAppendingPathComponent:[NSString stringWithUTF8String:(attribute.name + attribute.name_info.attr_dataoffset)]];
_logInfo(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@' fileSystemID: '%d'", childPath, fileSystemID]);
}
if (attribute.returned.commonattr & ATTR_CMN_OBJTYPE) {
attribute.obj_type = *(fsobj_type_t *)field;
field += sizeof(fsobj_type_t);
// DEBUG
NSString* childPath = [parentPath stringByAppendingPathComponent:[NSString stringWithUTF8String:(attribute.name + attribute.name_info.attr_dataoffset)]];
_logInfo(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@' type: '%@'", childPath, [self _fileType:attribute.obj_type]]);
}
if (attribute.returned.commonattr & ATTR_CMN_MODTIME) {
struct timespec time = *(struct timespec*)field;
field += sizeof(struct timespec);
NSDate* modificationDate = [NSDate dateWithTimeIntervalSince1970:time.tv_sec];
// DEBUG
NSString* childPath = [parentPath stringByAppendingPathComponent:[NSString stringWithUTF8String:(attribute.name + attribute.name_info.attr_dataoffset)]];
_logInfo(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@' modificationDate: '%@'", childPath, modificationDate]);
}
if (attribute.returned.commonattr & ATTR_CMN_FILEID) {
attribute.inode = *(uint64_t *)field;
// DEDA DEBUG
struct stat file_status;
NSString* childPath = [parentPath stringByAppendingPathComponent:[NSString stringWithUTF8String:(attribute.name + attribute.name_info.attr_dataoffset)]];
_logInfo(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@' inode: '%lld'", childPath, attribute.inode]);
if (lstat((const char *)[childPath fileSystemRepresentation], &file_status) == 0) {
int64_t inode2 = file_status.st_ino;
if (attribute.inode != inode2) {
_logError(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@' inode mismatch: '%lld'", childPath, inode2]);
} else {
_logInfo(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"filePath: '%@' inode match: '%lld'", childPath, inode2]);
}
}
field += sizeof(uint64_t);
}
return nil;
}
typedef struct IDDNodeAttribute {
uint32_t length; // ATTR_BIT_MAP_COUNT
attribute_set_t returned; // ATTR_CMN_RETURNED_ATTRS
char *name;
attrreference_t name_info; // ATTR_CMN_NAME
fsid_t fsid; // ATTR_CMN_FSID
fsobj_type_t obj_type; // ATTR_CMN_OBJTYPE
struct timespec modtime; // ATTR_CMN_MODTIME
uint64_t inode; // ATTR_CMN_FILEID
struct {
u_int32_t link_count; // ATTR_FILE_LINKCOUNT
off_t alloc_size; // ATTR_FILE_ALLOCSIZE
off_t total_size; // ATTR_FILE_TOTALSIZE
};
} __attribute__((aligned(4), packed)) IDDNodeAttribute;
// buffer to place our results
const int IDDNodeAttributeBufferSize = 32 * sizeof(IDDNodeAttribute);
struct attrlist attrList = {
.bitmapcount = ATTR_BIT_MAP_COUNT,
.commonattr = ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_NAME | ATTR_CMN_FSID | ATTR_CMN_OBJTYPE | ATTR_CMN_MODTIME | ATTR_CMN_FILEID,
.dirattr = ATTR_DIR_ENTRYCOUNT,
.fileattr = ATTR_FILE_LINKCOUNT | ATTR_FILE_ALLOCSIZE | ATTR_FILE_TOTALSIZE
};
int itemCount = getattrlistbulk(dirfd, &attrList, attrBuf, IDDNodeAttributeBufferSize, 1);
if (itemCount == -1) {
_logError(NSStringFromSelector(_cmd), [NSString stringWithFormat:@"error: '%ld = %s' path: '%s'", (long)errno, strerror(errno), path]);
break;
} else if (itemCount == 0) {
break;
} else {
char* entry_start = attrBuf;
for (int index = 0; index < itemCount; index++) {
IDDNode* child = [self _fetchChild:&entry_start :filePath :pnode];
if (child) {
[rv addObject:child];
}
}
}
Interesting ...
Here are some examples
inode mismatch: '/Applications ' ' 1152921500311879699' vs ' 78133671'
inode mismatch: '/Library ' ' 1152921500311879700' vs ' 78110879'
inode mismatch: '/System/Library/Assets ' ' 1152921500312008804' vs ' 78132729'
inode mismatch: '/System/Library/AssetsV2 ' ' 1152921500312008805' vs ' 78131512'
inode mismatch: '/System/Library/Caches ' ' 1152921500312115767' vs ' 78132730'
inode mismatch: '/System/Library/Speech ' ' 1152921500312658038' vs ' 78131497'
inode mismatch: '/System/Volumes/Data ' ' 1152921500312709177' vs ' 1152921500311879682'
inode mismatch: '/System/Volumes/Preboot ' ' 1152921500312709197' vs ' 2'
inode mismatch: '/System/Volumes/Update ' ' 1152921500312709199' vs ' 2'
inode mismatch: '/System/Volumes/VM ' ' 1152921500312709200' vs ' 2'
inode mismatch: '/Users ' ' 1152921500312764772' vs ' 319'
inode mismatch: '/Volumes ' ' 1152921500312764773' vs ' 320'
inode mismatch: '/Volumes/970Raid ' ' 78720565' vs ' 2'
inode mismatch: '/Volumes/Macintosh HD ' ' 78716885' vs ' 2'
inode mismatch: '/Volumes/Macintosh HD - Data ' ' 78716879' vs ' 2'
inode mismatch: '/Volumes/TimeMachine ' ' 78716884' vs ' 2'
inode mismatch: '/Volumes/Vault ' ' 78716876' vs ' 2'
inode mismatch: '/cores ' ' 1152921500312764847' vs ' 321'
inode mismatch: '/dev ' ' 1152921500312764848' vs ' 388'
inode mismatch: '/opt ' ' 1152921500312764850' vs ' 324'
inode mismatch: '/private ' ' 1152921500312764851' vs ' 78132742'
inode mismatch: '/usr/libexec/cups ' ' 1152921500312768002' vs ' 78110424'
inode mismatch: '/usr/local ' ' 1152921500312773577' vs ' 78110530'
inode mismatch: '/usr/share/snmp ' ' 1152921500312787193' vs ' 78110532'
/Volumes/$* are mounts.
The left column is the 'inode' from getattrlistbulk
The right is the 'inode' as I fetch using lstat
Really ?
Why did apple make this so hard.
I remember there used to be a menu on Xcode to switch this feature on and off.
I guess we need to use 3rd party markdown editors.
This magic is frustrating when it does not work.
I have a SwiftUI view (PhysicalSize) wrapped in NSHostingView that is placed inside a NSTableCellView.
I have set a custom NSTableRowView on the NSTableView and AppKit is setting the proper isEmphasized status when the row is selected.
Good.
Then I'm setting the backgroundStyle of the cells to .emphasized
Good.
However the swift UI is absolutely unaware, that we are being drawn in highlighted mode and the Text color does invert. Not good.
Can some apple genius chime in here.
struct PhysicalSize: View {
var file: File
var body: some View {
HStack {
Spacer()
Text(file.physicalSize.compactFormatted)
.font(.subheadline)
}
}
}
@Quinn, I did open ticket #787511160 and can provide the main class that calls the getattrlistbulk
Thanks.
The ATTR_CMN_ERROR is not triggered.
As as soon as we hit this particular folder boom, we manage to collect ATTR_CMN_NAME, ATTR_CMN_FSID and ATTR_CMN_OBJTYPE and than as soon as we attempt to fetch the ATTR_CMN_MODTIME, boom.
So we are crashing on the same darn portion.
Maybe I should revert this module back to the c version.
let modTimeInSeconds: Int32 = {
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_MODTIME)) != 0 {
aligningBuffer.copyMemory(from: UnsafeRawBufferPointer(start: field, count: MemoryLayout<timespec>.size))
let time = aligningBuffer.baseAddress!.load(as: timespec.self)
// the following will fails cause of memory misalignment
// let time = field.load(as: timespec.self)
field += MemoryLayout<timespec>.size
return Int32(time.tv_sec)
}
return 0
}()
I could share some more code on a direct message basis, it you are up for a challenge.
My pain is that I can't reproduce it.
It just happens on a few 'lucky' customer machines. And these machines are recent. No old software or old file systems.
I will add the 'ATTR_CMN_ERROR' and see if that will prevent me from digging deeper into trouble.
It is possible these troubled folders are returning some error and instead of bailing out early I'm continuing to read the rest of the pointer.
This class, the one that uses the getattrlistbulk was my last one to move to swift.
The previous version using Objective-C was working.
Mixing both obj-c and swift requires much more mental effort.
Imagine speaking English and French in the same conversation randomly.
Maybe I should eat it and revert this back the C base implementation.
Again thank you very much for the tips.
Klajd Deda
let myEmail = "kdeda" + "@" + "mac.com"
Went through the entire process of fully symbolicating a crash.
And I was rewarded with some more gold nuggets.
NodeFactory.appendChildren(_:_:_:_:_:_:) (in com.id-design.v7.whatsizehelper) (NodeFactory.swift:256)
let modTimeInSeconds: Int32 = {}() is obviously crashing.
Also another clue, it crashes always on the same folder.
So the meta data for this folder is 'wrong' ?
let modTimeInSeconds: Int32 = {
if (returned.commonattr & attrgroup_t(bitPattern: ATTR_CMN_MODTIME)) != 0 {
aligningBuffer.copyMemory(from: UnsafeRawBufferPointer(start: field, count: MemoryLayout<timespec>.size))
let time = aligningBuffer.baseAddress!.load(as: timespec.self)
// the following will fails cause of memory misalignment
// let time = field.load(as: timespec.self)
field += MemoryLayout<timespec>.size
return Int32(time.tv_sec)
}
return 0
}()