How can I create a GUI and react to Cocoa events programmatically? - objective-c

I found out how to create a window in Cocoa programmatically but can't figure out how to react to events. The window is not reacting to a Quit request or button click.
I tried adding the following controller and used setDelegate/setTarget without luck:
#interface AppController : NSObject {
}
- (IBAction)doSomething:(id)sender;
#end
#implementation AppController
- (IBAction)doSomething:(id)sender;
{
printf("Button clicked!\n");
}
#end
int main(int argc, char **args){
NSRect frame = NSMakeRect(0, 0, 200, 200);
AppController *controller = [[AppController alloc] init];
> [[NSApplication sharedApplication] setDelegate:controller];
NSWindow* window = [[NSWindow alloc] initWithContentRect:frame
styleMask:NSBorderlessWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask
backing:NSBackingStoreBuffered
defer:NO];
[window setBackgroundColor:[NSColor blueColor]];
NSButton *button = [ [ NSButton alloc ] initWithFrame: NSMakeRect( 30.0, 20.0, 80.0, 50.0 ) ];
[ button setBezelStyle:NSRoundedBezelStyle];
[ button setTitle: #"Click" ];
> [ button setAction:#selector(doSomething:)];
> [ button setTarget:controller];
[ [ window contentView ] addSubview: button ];
[window makeKeyAndOrderFront:NSApp];
[[NSRunLoop currentRunLoop] run];
return 0;
}

You need to invoke -[NSApplication run] instead of -[[NSRunLoop currentRunLoop] run]. The reason should be clear if you look at the basic structure of the method:
- (void)run
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self finishLaunching];
shouldKeepRunning = YES;
do
{
[pool release];
pool = [[NSAutoreleasePool alloc] init];
NSEvent *event =
[self
nextEventMatchingMask:NSAnyEventMask
untilDate:[NSDate distantFuture]
inMode:NSDefaultRunLoopMode
dequeue:YES];
[self sendEvent:event];
[self updateWindows];
} while (shouldKeepRunning);
[pool release];
}
NSApplication encapsulates a lot about how to get an event, how to dispatch them and how to update windows.

I found out how to create a window in Cocoa programmatically …
Why? Why not just make a nib?
The window is not reacting to a Quit request or button click.
How would you quit a window? This isn't Windows 3; applications can have multiple windows on Mac OS X. As such, closing a window and quitting an application are separate actions.
[[NSRunLoop currentRunLoop] run];
Except in rare circumstances, running the run loop is NSApplication's job, and you should leave that to it. Use NSApplicationMain or -[NSApplication run] to tell the application to run.

Excellent question. I think Matt Gallagher answered it already, but if you want to go further with this, you'll have to delve into Apple's event-handling documentation. Bear in mind that doing everything programmatically will require a solid understanding of cocoa fundamentals.

I spent an entire day looking for answers to the GUI and Menu portion of this question. There are not that many current, concise answers out there to the question. So after solving it for myself, I posted an answer which addresses this on Stack here: Cocoa GUI Programmatically. I add a referral to it here to help community members who are digging around for the same answers.

Related

NSApplication Assert failure when exiting fullscreen

I've written a corevideo application which has one window with a single content view.
The window resizes as expected. I've added the code to make it accept the fullscreen event, which the window does and works as expected, the dock and menu autohide and appear when the mouse hovers in the expected places.
However when I come out of fullscreen mode, I get an assertion failure in the AppKit's NSWindow_FullScreen.m which I cannot find mentioned anywhere in the fullscreen documentation nor can I find the error message searching google.
I've tried adding an observer for the NSWindowDidExitFullScreen Notification but the assertion remains.
I'm hoping someone can help.
2020-05-10 10:01:16.812 a.out[45616:2858300] *** Assertion failure in -[NSWindow _didExitFullScreen], /BuildRoot/Library/Caches/com.apple.xbs/Sources/AppKit/AppKit-1561.61.100/FullScreen.subproj/NSWindow_FullScreen.m:469
2020-05-10 10:01:16.812 a.out[45616:2858300] content controller was not cleaned up properly
I'm not sure what it's referring to as the Content Controller, I've tried adding a window controller but it still fails, I'm not sure what needs to be cleaned up, as my application is still running and rendering in the window.
Here's my minimal app which exhibits the issue. Compile with: gcc -framework AppKit example.m
#import <AppKit/AppKit.h>
int main (int argc, char **argv)
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[NSApplication sharedApplication];
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
NSUInteger windowStyle = NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable;
NSRect wr = NSMakeRect(0,0,640,480);
NSWindow * window = [[NSWindow alloc] initWithContentRect:wr
styleMask:windowStyle
backing:NSBackingStoreBuffered
defer:NO];
[window autorelease];
NSWindowCollectionBehavior behavior = [window collectionBehavior];
behavior |= NSWindowCollectionBehaviorFullScreenPrimary;
[window setCollectionBehavior:behavior];
[[NSNotificationCenter defaultCenter]
addObserver:NSApp
selector:#selector(terminate:)
name:NSWindowWillCloseNotification
object:nil];
[window orderFrontRegardless];
[NSApp run];
[pool drain];
return 0;
}
The cause of this issue is that a NSWindowWillCloseNotification is sent when the window exits fullscreen, removing the lines:
[[NSNotificationCenter defaultCenter]
addObserver:NSApp
selector:#selector(terminate:)
name:NSWindowWillCloseNotification
object:nil];
And handling quit events differently resolved this issue.

Cocoa WebView doesn't respond to keyboard events

I'm making super-simple one-page browser in pure Objective-C, and everything is fine except the keyboard events. I just can't enter any data in webforms using keyboard! Mouse operations like pasting in webforms and clicking on links works, but typing in webforms doesn't!
code:
#import <WebKit/WebKit.h>
#interface MyWebView : WebView
{
}
#end
#implementation MyWebView
-(void)windowWillClose:(NSNotification *)notification
{
[NSApp terminate:self];
}
-(BOOL)becomeFirstResponder
{
printf("becomeFirstResponder\n"); // this message always appears
return YES;
}
-(void)keyDown:(NSEvent *)event
{
printf("keydown\n"); // this message never appears
// should I use this code to make keyboard work?
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
}
#end
int main(void) {
NSRect contentRect;
NSWindow *window = nil;
WebView *webView = nil;
NSString *urlForAuthentication = nil;
NSApp = [NSApplication sharedApplication];
contentRect = NSMakeRect(100, 100, 700, 700);
window = [[NSWindow alloc] initWithContentRect:contentRect styleMask:NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask backing:NSBackingStoreBuffered defer:NO];
if (!window) {
printf("!window\n");
goto end;
}
webView = [[MyWebView alloc] initWithFrame:window.contentLayoutRect frameName:nil groupName:nil];
if (!webView) {
printf("!webView\n");
goto end;
}
urlForAuthentication = [[NSString alloc] initWithCString:(const char *)"https://yahoo.com" encoding:NSUTF8StringEncoding];
[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:urlForAuthentication]]];
window.contentView = webView;
window.delegate = (id)webView;
[window makeFirstResponder:webView];
[window makeKeyAndOrderFront:nil];
[window makeMainWindow];
if (!window.keyWindow)
printf("not keyWindow\n"); // this message always appears
if (!window.mainWindow)
printf("not mainWindow\n"); // this message always appears
[NSApp run];
end:
[urlForAuthentication release];
[webView release];
[window release];
[NSApp release];
return 0;
}
makefile:
CC=clang
FRAMEWORKS:=/System/Library/Frameworks/WebKit.framework/WebKit /System/Library/Frameworks/AppKit.framework/AppKit
LIBRARIES:=
WARNINGS=-Wall -Werror
SOURCE=app.m
CFLAGS=-g -v $(WARNINGS) $(SOURCE)
LDFLAGS=$(LIBRARIES) $(FRAMEWORKS) $(LINK_WITH)
OUT=-o app
all:
$(CC) $(CFLAGS) $(LDFLAGS) $(OUT)
What I'm doing wrong? I've read documentation and searched on SO for similar questions, but I couldn't find the solution. I tried to capture keyDown event, but it doesn't happens. I tried to make my window key and main, but seemingly unsuccessful.
To whom it may concern: place your executable 'abc' in folder 'abc.app/Contents/MacOS/', and it will finally work properly!
Edit: my colleague found out that this simple measure affects NSApplicationActivationPolicy. With this path, app's activationPolicy is 0 (NSApplicationActivationPolicyRegular). Without this path, app's activationPolicy is 2 (NSApplicationActivationPolicyProhibited).
Binary with some name should be placed inside "<Insert Name Here>.app" folder, which also should contain "Contents" folder. This way your executable pretends to be a bundled application for macOS.

programmatically create initial window of cocoa app (OS X)

Usually I am making iOS app but now I am trying to make an OS X app, and I am lost at the very beginning. Say the style I make the iOS apps are totally programmatic, there's no xib files or whatsoever just because that I have a lot more control by typing than dragging. However in OS X programming, it starts with some xib files with the menu items and a default window. There are quite a lot of items in the menu items so that's probably not something I want to mess around, but I want to programmatically create my first window myself.
So I did this:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSUInteger windowStyleMask = NSTitledWindowMask|NSResizableWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask;
NSWindow* appWindow = [[NSWindow alloc] initWithContentRect:NSMakeRect(200, 200, 1280, 720) styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:NO];
appWindow.backgroundColor = [NSColor lightGrayColor];
appWindow.minSize = NSMakeSize(1280, 720);
appWindow.title = #"Sample Window";
[appWindow makeKeyAndOrderFront:self];
_appWindowController = [[AppWindowController alloc] initWithWindow:appWindow];
[_appWindowController showWindow:self];
}
So here, I have created a window first, and use that windowController to init this window. The window does show up in this way, but I can only specify the inner elements, like buttons and labels here, but not in the windowController. It makes me feel bad so I tried another way.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
_appWindowController = [[AppWindowController alloc] init];
[_appWindowController showWindow:self];
}
and after this I want to set the other elements in the loadWindow: function in the windowController like this:
- (void)loadWindow
{
[self.window setFrame:NSMakeRect(200, 200, 1280, 720) display:YES];
self.window.title = #"Sample window";
self.window.backgroundColor = [NSColor lightGrayColor];
NSButton* sampleButton = [[NSButton alloc] initWithFrame:NSRectFromCGRect(CGRectMake(100, 100, 200, 23))];
sampleButton.title = #"Sample Button!";
[sampleButton setButtonType:NSMomentaryLightButton];
[sampleButton setBezelStyle:NSRoundedBezelStyle];
[self.window.contentView addSubview:sampleButton];
NSLog(#"Loaded window!");
[self.window makeKeyAndOrderFront:nil];
}
Unfortunately, this never works. the loadWindow: never gets called, nor windowDidLoad:. Where did they go?
And please don't ask why I don't use nibs. I wish to make some highly customized views inside, possibly OpenGL, so I don't think nibs can handle it. I am greatly appreciated if anyone could help. Thanks.
And also, who knows how to even start the menu items from scratch, programmatically?
I am using the latest Xcode.
I spent an entire Sunday digging into this problem myself. Like the person asking the question, I prefer coding iOS and OSX without nib files (mostly) or Interface Builder and to go bare metal. I DO use NSConstraints though. It is probably NOT WORTH avoiding IB if you're doing simpler UIs, however when you get into a more complex UI it gets harder.
It turns out to be fairly simple to do, and for the benefit of the "Community" I thought I'd post a concise up to date answer here. There ARE some older Blog Posts out there and the one I found most useful were the ones from Lap Cat Software. 'Tip O The Hat' to you sir!
This Assumes ARC. Modify your main() to look something like this:
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
int main(int argc, const char *argv[])
{
NSArray *tl;
NSApplication *application = [NSApplication sharedApplication];
[[NSBundle mainBundle] loadNibNamed:#"MainMenu" owner:application topLevelObjects:&tl];
AppDelegate *applicationDelegate = [[AppDelegate alloc] init]; // Instantiate App delegate
[application setDelegate:applicationDelegate]; // Assign delegate to the NSApplication
[application run]; // Call the Apps Run method
return 0; // App Never gets here.
}
You'll note that there is still a Nib (xib) in there. This is for the main menu only. As it turns out even today (2014) apparently no way to easily set the position 0 menu item. That's the one with the title = to your App name. You can set everything to the right of it using [NSApplication setMainMenu] but not that one. So I opted to keep the MainMenu Nib created by Xcode in new projects, and strip it down to just the position 0 item. I think that is a fair compromise and something I can live with. One brief plug for UI Sanity... when you're creating Menus please follow the same basic pattern as other Mac OSX Apps.
Next modify the AppDelegate to look something like this:
-(id)init
{
if(self = [super init]) {
NSRect contentSize = NSMakeRect(500.0, 500.0, 1000.0, 1000.0);
NSUInteger windowStyleMask = NSTitledWindowMask | NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask;
window = [[NSWindow alloc] initWithContentRect:contentSize styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:YES];
window.backgroundColor = [NSColor whiteColor];
window.title = #"MyBareMetalApp";
// Setup Preference Menu Action/Target on MainMenu
NSMenu *mm = [NSApp mainMenu];
NSMenuItem *myBareMetalAppItem = [mm itemAtIndex:0];
NSMenu *subMenu = [myBareMetalAppItem submenu];
NSMenuItem *prefMenu = [subMenu itemWithTag:100];
prefMenu.target = self;
prefMenu.action = #selector(showPreferencesMenu:);
// Create a view
view = [[NSTabView alloc] initWithFrame:CGRectMake(0, 0, 700, 700)];
}
return self;
}
-(IBAction)showPreferencesMenu:(id)sender
{
[NSApp runModalForWindow:[[PreferencesWindow alloc] initWithAppFrame:window.frame]];
}
-(void)applicationWillFinishLaunching:(NSNotification *)notification
{
[window setContentView:view]; // Hook the view up to the window
}
-(void)applicationDidFinishLaunching:(NSNotification *)notification
{
[window makeKeyAndOrderFront:self]; // Show the window
}
And Bingo... you're good to go! You can start working from there in the AppDelegate pretty much like you're familiar with. Hope that helps!
UPDATE: I don't create menus in code anymore as I've shown above. I've discovered you can edit MainMenu.xib source in Xcode 6.1. Works nice, very flexible and all it takes is a little experimentation to see how it works. Faster than messing around in code and easy to localize! See the picture to understand what I am on about:
See https://github.com/sindresorhus/touch-bar-simulator/blob/master/Touch%20Bar%20Simulator/main.swift
In main.swift
let app = NSApplication.shared()
let delegate = AppDelegate()
app.delegate = delegate
app.run()
Swift 4:
// File main.swift
autoreleasepool {
// Even if we loading application manually we need to setup `Info.plist` key:
// <key>NSPrincipalClass</key>
// <string>NSApplication</string>
// Otherwise Application will be loaded in `low resolution` mode.
let app = Application.shared
app.setActivationPolicy(.regular)
app.run()
}
// File Application.swift
public class Application: NSApplication {
private lazy var mainWindowController = MainWindowController()
private lazy var mainAppMenu = MainMenu()
override init() {
super.init()
delegate = self
mainMenu = mainAppMenu
}
public required init?(coder: NSCoder) {
super.init(coder: coder) // This will newer called.
}
}
extension Application: NSApplicationDelegate {
public func applicationDidFinishLaunching(_ aNotification: Notification) {
mainWindowController.showWindow(nil)
}
}
Override the -init method in your AppWindowController class to create the window and then call super's -initWithWindow: method (which is NSWindowController's designated initializer) with that window.
But I generally agree with the comments that there's little reason to avoid NIBs.

My modal dialog crashes (Cocoa)

My code below crashes if I have the code in windowWillClose: that releases
my MyWindowController, otherwise it works fine.
I test it on Mac OS 10.6.8.
I am using XCode 3.1.3.
What have I done wrong?
It seems like the window is not disposed of before I release MyWindowController,
because it crashes in a NSTableView method.
My button handler calls [NSApp stopModalWithCode:0];
MyDialog()
{
MyWindowController* controller = [[MyWindowController alloc] init];
[controller showWindow:controller];
NSWindow* window = [controller window];
[NSApp runModalForWindow:window];
[window close];
}
In my MyWindowController:
- (void)windowWillClose:(NSNotification*)notification
{
[self autorelease];
}
You are releasing 'self' in windowWillClose - that seems wrong.
Surely anything like that should be done in dealloc?
-(void)dealloc
{
[super dealloc];
}
Also, you might be better autoreleasing controller when it is initially alloc'd?

Memory Crash in UIPopoverController

I've now invested days in trying to figure out what is going on and for the life of me I can't see what I am doing wrong. I am popping up a UIPopover when the user touches a point on the screen. The popover has a tab controller and table view that displays information about that point. But when the popover is dismissed, it crashes claiming that:
-[UIAnimator removeAnimationsForTarget:]: message sent to deallocated instance
Here is the code that loads the view controller:
MyViewController *popView = [[MyViewController alloc] init];
myPop = [[UIPopoverController alloc] initWithContentViewController:pop];
[popView release];
myPop.delegate = self;
[airportPop setPopoverContentSize:popView.view.frame.size];
[airportPop presentPopoverFromRect:CGRectMake(location.x,location.y,1,1) inView:self.mainView permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
- (void)dismissPopover {
if( myPop != nil ) {
[myPop dismissPopoverAnimated:YES];
[myPop.delegate popoverControllerDidDismissPopover:airportPop];
}
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
[myPop release];
myPop = nil;
}
The actual MyViewController is just a UIViewController that with (abridged for brevity) init:
- (id)init
{
self = [super init];
//create a newview
self.view = popView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, POPUP_WIDTH, POPUP_HEIGHT)];
[popView release];
topBar = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, POPUP_WIDTH, 30)];
....
[popView addSubview:topBar];
[topBar release];
//create a table view
self.table = [[UITableView alloc] initWithFrame:CGRectMake(0, 30, POPUP_WIDTH, POPUP_HEIGHT-30-49)];
table.delegate = table.dataSource = self;
....
//create a tab bar
tabBar = [[UITabBar alloc] initWithFrame:CGRectMake(0, POPUP_HEIGHT-49, POPUP_WIDTH, 49)];
tabBar.delegate = self;
[popView addSubview:tabBar];
[popView addSubview:table];
[tabBar release];
[table release];
return( self );
}
Dealloc is nothing more than [super dealloc] since everything is essentially owned by the view and the view controller will take care of it. When the myPop is released, in DidDismissPopover, the view is also released, so that seems to work okay. But very soon thereafter, I get the crash.
Do I need to do something special to discard the tab view or table view when the popup dismisses?
I am using an autorelease on the cells in the table, should I stop doing that?
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
Thank you in advance for any help!!! Any ideas at all are greatly appreciated!!
-Kevin
[myPop dismissPopoverAnimated:YES] will continue to access you object even after the method call because you set YES for the animation (there is a timer and other stuff going under the hood to perform the animation for that)
So, instead of immediately releasing the object, you could mark it as autorelease to postpone this action, which actually might solved it or not.
Or postpone the release to a time after that makes tyou sure thta the animation will be finished. You could use GCD for that (if you are using iOS 4+) and as the default time for animation in UIKit is 0.3s, the code bellow should do the trick.
double delayInSeconds = 0.3;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[myPop.delegate popoverControllerDidDismissPopover:airportPop];
});
EDIT: You should use this time only for the test propose as it is far from being the right way to release an object.
You should store a pointer to your UIPopover and release it in your class dealloc method.
Add following keys in yor Exectables info->Arguments tab-> enviroment variables
NSZombieEnabled = YES
CFZombie = 5
MallocStackLoggingNoCompact = 1
then when you get crash automatically you get a message
some thing like this
(gdb) continue
2011-06-09 11:46:08.404 test [6842:40b] * -[_NSArrayI
release]:message sent to deallocated instance 0X64a4900
then type
(gdb) info malloc-history 0x64a4900
it will give you complete history.
May be it helps you to find the place.
also you can use where command when you got crash.
The fastest way to avoid waiting for animation to end is to set popoverController.delegate = nil as soon as you dismiss the popup or the Popover Delegate method
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
is called.