I have the following code (compile as a standalone file):
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (strong) NSWindow *window;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
// Create main window
self.window = [[NSWindow alloc] initWithContentRect:NSMakeRect(100, 100, 800, 600)
styleMask:(NSWindowStyleMaskTitled |
NSWindowStyleMaskClosable |
NSWindowStyleMaskResizable |
NSWindowStyleMaskMiniaturizable)
backing:NSBackingStoreBuffered
defer:NO];
[self.window setTitle:@"Nested ScrollView Demo"];
[self.window makeKeyAndOrderFront:nil];
// Split view controller
NSSplitViewController *splitVC = [[NSSplitViewController alloc] init];
// Sidebar
NSViewController *sidebarVC = [[NSViewController alloc] init];
sidebarVC.view = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 200, 600)];
sidebarVC.view.wantsLayer = YES;
NSSplitViewItem *sidebarItem = [NSSplitViewItem sidebarWithViewController:sidebarVC];
sidebarItem.minimumThickness = 150;
sidebarItem.maximumThickness = 400;
[splitVC addSplitViewItem:sidebarItem];
// Content view controller
NSViewController *contentVC = [[NSViewController alloc] init];
// Vertical scroll view (outer)
NSScrollView *verticalScrollView = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, 600, 600)];
verticalScrollView.automaticallyAdjustsContentInsets = YES;
verticalScrollView.hasVerticalScroller = YES;
verticalScrollView.hasHorizontalScroller = NO;
verticalScrollView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
NSView *verticalContent = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 600, 1200)];
verticalContent.wantsLayer = YES;
verticalContent.layer.backgroundColor = [[NSColor blueColor] CGColor];
[verticalScrollView setDocumentView:verticalContent];
// Add several horizontal scroll sections
CGFloat sectionHeight = 150;
for (int i = 0; i < 5; i++) {
// Horizontal scroll view inside section
NSScrollView *horizontalScroll = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, verticalContent.frame.size.height - (i+1)*sectionHeight - i*20, 600, sectionHeight)];
horizontalScroll.hasHorizontalScroller = YES;
horizontalScroll.hasVerticalScroller = NO;
horizontalScroll.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
NSView *horizontalContent = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 1200, sectionHeight)];
horizontalContent.wantsLayer = YES;
// Add labels horizontally
for (int j = 0; j < 6; j++) {
NSTextField *label = [NSTextField labelWithString:[NSString stringWithFormat:@"Item %d-%d", i+1, j+1]];
label.frame = NSMakeRect(20 + j*200, sectionHeight/2 - 15, 180, 30);
[horizontalContent addSubview:label];
}
horizontalScroll.documentView = horizontalContent;
[verticalContent addSubview:horizontalScroll];
}
contentVC.view = verticalScrollView;
NSSplitViewItem *contentItem = [NSSplitViewItem splitViewItemWithViewController:contentVC];
contentItem.automaticallyAdjustsSafeAreaInsets = YES;
contentItem.minimumThickness = 300;
[splitVC addSplitViewItem:contentItem];
self.window.contentViewController = splitVC;
// Sidebar label
NSTextField *label = [NSTextField labelWithString:@"Sidebar"];
label.translatesAutoresizingMaskIntoConstraints = NO;
[sidebarVC.view addSubview:label];
[NSLayoutConstraint activateConstraints:@[
[label.centerXAnchor constraintEqualToAnchor:sidebarVC.view.centerXAnchor],
[label.centerYAnchor constraintEqualToAnchor:sidebarVC.view.centerYAnchor]
]];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSApplication *app = [NSApplication sharedApplication];
AppDelegate *delegate = [[AppDelegate alloc] init];
[app setDelegate:delegate];
[app run];
}
return 0;
}
Obviously, since the contents of the right part (the content of the sidebar) has its content inset, the horizontal scrolling views cannot extend to the left beneath the sidebar. I can change line 39 to say verticalScrollView.automaticallyAdjustsContentInsets = NO; which will make the inner horizontal scroll views able to extend below the sidebar, but then I'd lose out on the automatic inset management to not have other content overlap beneath the sidebar. So what can I do here? I've tried manually changing the frame of the inner scroll views to start on a negative x, but that does not allow me to scroll all the way to the leftmost content. I'm hoping I won't have to adjust for safe areas manually for the outer scroll view.
I'd also appreciate tips on how to fix the fact that veritical scrolling doesn't work when your mouse is in a horizontal scroll view.
Thanks!
P.S. same thing also exists on UIKit.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I have this MWE right here -- it has a toolbar with a random action on it, in addition to a scroll view as the content of the window, with random labels attached inside. Since the redeisgn of the NSToolbar stuff in Tahoe, I expect the share button be able to "float" on top of the scrolled out content as shown as the first image at https://developer.apple.com/documentation/TechnologyOverviews/adopting-liquid-glass.
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate, NSToolbarDelegate>
@property (strong) NSWindow *window;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
NSRect frame = NSMakeRect(100, 100, 600, 400);
self.window = [[NSWindow alloc] initWithContentRect:frame
styleMask:(NSWindowStyleMaskTitled |
NSWindowStyleMaskClosable |
NSWindowStyleMaskResizable |
NSWindowStyleMaskMiniaturizable)
backing:NSBackingStoreBuffered
defer:NO];
[self.window setTitle:@"Scroll View + Toolbar Demo"];
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"MainToolbar"];
toolbar.displayMode = NSToolbarDisplayModeIconAndLabel;
toolbar.delegate = self;
[self.window setToolbar:toolbar];
NSScrollView *scrollView = [[NSScrollView alloc] initWithFrame:self.window.contentView.bounds];
[scrollView setAutoresizingMask:(NSViewWidthSizable | NSViewHeightSizable)];
[scrollView setHasVerticalScroller:YES];
[scrollView setHasHorizontalScroller:YES];
NSView *documentView = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 1000, 1000)];
for (int i = 0; i < 10; i++) {
NSTextField *label = [[NSTextField alloc] initWithFrame:NSMakeRect(50, 950 - i*80, 400, 40)];
[label setStringValue:[NSString stringWithFormat:@"Sample Label #%d", i + 1]];
[label setBezeled:NO];
[label setDrawsBackground:NO];
[label setEditable:NO];
[label setSelectable:NO];
[documentView addSubview:label];
}
[scrollView setDocumentView:documentView];
[self.window setContentView:scrollView];
[self.window makeKeyAndOrderFront:nil];
}
- (NSArray<NSToolbarItemIdentifier> *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar {
return @[NSToolbarFlexibleSpaceItemIdentifier, @"ShareItem"];
}
- (NSArray<NSToolbarItemIdentifier> *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar {
return @[@"ShareItem"];
}
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSToolbarItemIdentifier)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag {
if ([itemIdentifier isEqualToString:@"ShareItem"]) {
NSToolbarItem *shareItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
shareItem.toolTip = @"Share this content";
shareItem.image = [NSImage imageNamed:NSImageNameShareTemplate];
shareItem.target = self;
shareItem.action = @selector(shareAction:);
return shareItem;
}
return nil;
}
- (void)shareAction:(id)sender {
NSLog(@"Share button clicked!");
// Here you could present a sharing service picker
NSSharingServicePicker *picker = [[NSSharingServicePicker alloc] initWithItems:@[@"Hello, world!"]];
[picker showRelativeToRect:[sender view].bounds ofView:[sender view] preferredEdge:NSRectEdgeMinY];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSApplication *app = [NSApplication sharedApplication];
AppDelegate *delegate = [[AppDelegate alloc] init];
[app setDelegate:delegate];
[app run];
}
return EXIT_SUCCESS;
}
But it doesn't and produces this image:
https://imgur.com/a/kA7MzIe
I've tried to set various settings to make the top bar transparent, but all it does is that it makes it completely opaque instead. How can I make the share button float on top of the content?
P.S. the app is a single-file app, compile it with
clang -fobjc-arc -framework Cocoa -o ScrollApp toolbar.m
Topic:
UI Frameworks
SubTopic:
AppKit
Here's the result of a blank app from Xcode:
https://imgur.com/a/1hMmwbO
now there's only 3 items in the storyboard configuration:
https://imgur.com/a/iGWWQE7
So I try to replicate that in code (some of this reproducer was generated by ChatGPT however the same issue I'm descrbing has been hit when using Python to objc bridges to construct the GUI) by specifying these 3 actions appropriately and see if the rest pops up. The code below changes the activation policy so that when I run ./a.out from the terminal it doesn't show as a window of Terminal but a separate app
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
// Build main menu
NSMenu *mainMenu = [[NSMenu alloc] initWithTitle:@"MainMenu"];
// --- App menu with Quit ---
NSMenuItem *appMenuItem = [[NSMenuItem alloc] init];
NSMenu *appMenu = [[NSMenu alloc] initWithTitle:@"App"];
NSMenuItem *quitItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
action:@selector(terminate:)
keyEquivalent:@"q"];
[appMenu addItem:quitItem];
[appMenuItem setSubmenu:appMenu];
[mainMenu addItem:appMenuItem];
// --- Window menu with only Minimize, Zoom, Bring All to Front ---
NSMenuItem *windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:NULL keyEquivalent:@""];
NSMenu *windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
[windowMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Minimize"
action:@selector(performMiniaturize:)
keyEquivalent:@"m"]];
[windowMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Zoom"
action:@selector(performZoom:)
keyEquivalent:@""]];
[windowMenu addItem:[NSMenuItem separatorItem]];
[windowMenu addItem:[[NSMenuItem alloc] initWithTitle:@"Bring All to Front"
action:@selector(arrangeInFront:)
keyEquivalent:@""]];
[windowMenuItem setSubmenu:windowMenu];
[mainMenu addItem:windowMenuItem];
[NSApp setMainMenu:mainMenu];
// Optional demo window (remove if you want zero windows)
NSWindow *w = [[NSWindow alloc] initWithContentRect:NSMakeRect(200,200,400,200)
styleMask:(NSWindowStyleMaskTitled |
NSWindowStyleMaskClosable |
NSWindowStyleMaskResizable |
NSWindowStyleMaskMiniaturizable)
backing:NSBackingStoreBuffered
defer:NO];
[w setTitle:@"Demo"];
[w makeKeyAndOrderFront:nil];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return YES;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
AppDelegate *delegate = [AppDelegate new];
[NSApplication sharedApplication];
[NSApp setDelegate:delegate];
return NSApplicationMain(argc, argv);
}
}
Now, I only see 3 items that's literally specified
https://imgur.com/a/LylRsaJ
So, what allows interface builder to auto-add these extra items as opposed by creating it in code? Is there something in this reproducer of the Window menu that is missing that needs to make it happen programatically?
Thanks!
All tests done on macOS Tahoe
Topic:
UI Frameworks
SubTopic:
AppKit
I can compile this
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (strong) NSWindow *window;
@property (strong) NSSlider *slider;
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
// Window size
NSRect frame = NSMakeRect(0, 0, 400, 300);
NSUInteger style = NSWindowStyleMaskTitled |
NSWindowStyleMaskClosable |
NSWindowStyleMaskResizable;
self.window = [[NSWindow alloc] initWithContentRect:frame
styleMask:style
backing:NSBackingStoreBuffered
defer:NO];
[self.window setTitle:@"Centered Slider Example"];
[self.window makeKeyAndOrderFront:nil];
// Slider size
CGFloat sliderWidth = 200;
CGFloat sliderHeight = 32;
CGFloat windowWidth = self.window.frame.size.width;
CGFloat windowHeight = self.window.frame.size.height;
CGFloat sliderX = (windowWidth - sliderWidth) / 2;
CGFloat sliderY = (windowHeight - sliderHeight) / 2;
self.slider = [[NSSlider alloc] initWithFrame:NSMakeRect(sliderX, sliderY, sliderWidth, sliderHeight)];
[self.slider setMinValue:0];
[self.slider setMaxValue:100];
[self.slider setDoubleValue:50];
[self.window.contentView addSubview:self.slider];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSApplication *app = [NSApplication sharedApplication];
AppDelegate *delegate = [[AppDelegate alloc] init];
[app setDelegate:delegate];
[app run];
}
return 0;
}
with
(base) johnzhou@Johns-MacBook-Pro liquidglasstest % clang -framework Foundation -framework AppKit testobjc.m
and get this neat liquid glass effect:
https://github.com/user-attachments/assets/4199493b-6011-4ad0-9c9f-25db8585e547
However if I use pyobjc to make an equivalent
import sys
from Cocoa import (
NSApplication, NSApp, NSWindow, NSSlider, NSMakeRect,
NSWindowStyleMaskTitled, NSWindowStyleMaskClosable,
NSWindowStyleMaskResizable, NSBackingStoreBuffered,
NSObject
)
class AppDelegate(NSObject):
def applicationDidFinishLaunching_(self, notification):
# Create the main window
window_size = NSMakeRect(0, 0, 400, 300)
style = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable
self.window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_(
window_size, style, NSBackingStoreBuffered, False
)
self.window.setTitle_("Centered Slider Example")
self.window.makeKeyAndOrderFront_(None)
# Slider size and positioning
slider_width = 200
slider_height = 32
window_width = self.window.frame().size.width
window_height = self.window.frame().size.height
slider_x = (window_width - slider_width) / 2
slider_y = (window_height - slider_height) / 2
self.slider = NSSlider.alloc().initWithFrame_(NSMakeRect(slider_x, slider_y, slider_width, slider_height))
self.slider.setMinValue_(0)
self.slider.setMaxValue_(100)
self.slider.setDoubleValue_(50)
self.window.contentView().addSubview_(self.slider)
if __name__ == "__main__":
app = NSApplication.sharedApplication()
delegate = AppDelegate.alloc().init()
app.setDelegate_(delegate)
app.run()
I get a result shown at
https://github.com/user-attachments/assets/7da022bc-122b-491d-9e08-030dcb9337c3
which does not have the new liquid glass effect.
Why is this? Is this perhaps related to the requirement that you must compile on latest Xcode as indicated in the docs? Why, is the compiler doing some magic?
How does one know the fitting width of a UIDatePicker in a selector hooked up with UIControlEventValueChanged? By fitting width, I mean the width of the grey background rounded box displayed with the date -- I need to get the width of that whenever the date is changed.
How can I get the macOS version from the Mac Catalyst version? We're building Info.plist files ourselves but we need a way to programatically (using shell scripts) derive the LSMinimumSystemVersion key needed from the iOS deployment target.
Is it possible to have some additional content at
Versions/A/
in a macOS Framework bundle that is not in any of the standard folders? Will there be any side-effects during signing and notarization? The reason is it'd be a lot easier in my use case to be able to put content here instead of the Resources folder.
Topic:
Code Signing
SubTopic:
General
How does one check if a file descriptor is guarded? Is there any guarded FD numbers that are determinate? I've seen 12 being NPOLICY in a few things -- is there documentation for which FDs might be guarded? Thanks. The platform is Mac Catalyst.
Is it theoretically possible to:
Build an app with Mac Catalyst without the App Sandbox entitlement and
Distribute it outside the Mac App Store (w/ notarization)?
Thank you!
Topic:
App Store Distribution & Marketing
SubTopic:
General
Tags:
App Store
Mac Catalyst
Notarization
App Sandbox
How can I disable Hardened Runtime in Xcode only when signing ad hoc?
If I make a new project, Xcode will say
Disabling hardened runtime with ad-hoc codesigning.
at the beginning of the build logs.
However, somehow my project isn't doing this -- it's still hardening the runtime when ad-hoc signing.
What should I do to debug this?
Topic:
Code Signing
SubTopic:
Entitlements