NSToolbar created programmatically starts empty and won't save - objective-c

I'm currently trying to write a Mac application. In doing so, I'm having some peculiar problems when trying to setup an NSToolbar.
Although I have setup all the components as per the API documentation, when the application loads, the toolbar always starts empty. When I open the customisation pane, the toolbar items are there, and I can drag them into the toolbar, but when I quit the application and restart the changes are gone.
Note: I am aware that many of you will be of the opinion that the best way to solve this is to use interface builder rather than doing it in code. That is not the answer I am looking for - I chose to make this application without IB in order to better understand the internals of a Cocoa app.
I have verified (with NSLogs) that neither the toolbarAllowedItemIdentifiers nor the toolbarDefaultItemIdentifiers delegate methods get called when the toolbar is first initialised, but they do get called when you enter the customisation pane.
Below, please find a minimal, verifiable and complete example version of a basic app that demonstrates this bug. Anyone that can shed some light on this matter would be hugely appreciated!
Thanks
Defines.h
#define UNUSED(x) (void)(x)
#define TOOLBAR_ONE #"ONE"
#define TOOLBAR_TWO #"TWO"
#define TOOLBAR_IDENT #"TOOLBAR"
#define WINDOW_MASK NSTitledWindowMask | \
NSClosableWindowMask | \
NSResizableWindowMask | \
NSMiniaturizableWindowMask
main.m
#import "BWAppDelegate.h"
#import <AppKit/AppKit.h>
int main(void) {
#autoreleasepool {
NSApplication* application = [NSApplication sharedApplication];
BWAppDelegate* delegate = [[BWAppDelegate alloc] init];
application.delegate = delegate;
[application run];
return EXIT_SUCCESS;
}
}
BWAppDelegate.h
#import <Cocoa/Cocoa.h>
#import "BWMainToolbarDelegate.h"
#interface BWAppDelegate : NSObject<NSApplicationDelegate>
#property (atomic, strong) NSWindow* window;
#property (atomic, strong) BWMainToolbarDelegate* toolbarDelegate;
#end
BWAppDelegate.m
#import <AVFoundation/AVFoundation.h>
#import <AppKit/AppKit.h>
#import <Cocoa/Cocoa.h>
#import "BWAppDelegate.h"
#import "Defines.h"
#implementation BWAppDelegate
#synthesize window, toolbarDelegate;
- (void) applicationDidFinishLaunching: (NSNotification*) aNotification
{
UNUSED(aNotification);
NSRect dims = NSMakeRect(0, 0, 300, 300);
self.window = [[NSWindow alloc] initWithContentRect:dims
styleMask:WINDOW_MASK
backing:NSBackingStoreBuffered
defer:NO];
[self.window makeKeyAndOrderFront:nil];
self.window.toolbar = [[NSToolbar alloc] initWithIdentifier:TOOLBAR_IDENT];
toolbarDelegate = [[BWMainToolbarDelegate alloc] initWithToolbar:self.window.toolbar];
}
#end
BWMainToolbarDelegate.h
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#interface BWMainToolbarDelegate : NSObject<NSToolbarDelegate>
- (instancetype) initWithToolbar: (NSToolbar*) theToolbar;
- (NSArray*) toolbarAllowedItemIdentifiers: (NSToolbar*) toolbar;
- (NSArray*) toolbarDefaultItemIdentifiers: (NSToolbar*) toolbar;
- (NSToolbarItem*) toolbar: (NSToolbar*) toolbar
itemForItemIdentifier: (NSString*) identifier
willBeInsertedIntoToolbar: (BOOL) flag;
#end
BWMainToolbarDelegate.m
#import "BWMainToolbarDelegate.h"
#import "Defines.h"
#implementation BWMainToolbarDelegate {
NSToolbar* toolbar;
}
- (instancetype) initWithToolbar: (NSToolbar*) theToolbar
{
self = [super init];
if(self) {
toolbar = theToolbar;
toolbar.displayMode = NSToolbarDisplayModeIconAndLabel;
toolbar.allowsUserCustomization = YES;
toolbar.autosavesConfiguration = YES;
toolbar.delegate = self;
}
return self;
}
- (NSArray*) toolbarAllowedItemIdentifiers: (NSToolbar*) theToolbar
{
UNUSED(theToolbar);
return #[TOOLBAR_ONE, TOOLBAR_TWO];
}
- (NSArray*) toolbarDefaultItemIdentifiers: (NSToolbar*) theToolbar
{
UNUSED(theToolbar);
return #[TOOLBAR_ONE, TOOLBAR_TWO];
}
- (NSToolbarItem*) toolbar: (NSToolbar*) theToolbar
itemForItemIdentifier: (NSString*) identifier
willBeInsertedIntoToolbar: (BOOL) flag
{
UNUSED(flag);
NSToolbarItem* returnVal = nil;
NSString* label;
if([theToolbar.identifier isEqualToString:TOOLBAR_IDENT]) {
if([identifier isEqualToString:TOOLBAR_ONE]) {
returnVal = [[NSToolbarItem alloc] initWithItemIdentifier:TOOLBAR_ONE];
label = #"Toolbar One";
} else if([identifier isEqualToString:TOOLBAR_TWO]) {
returnVal = [[NSToolbarItem alloc] initWithItemIdentifier:TOOLBAR_TWO];
label = #"Toolbar TWO";
}
}
returnVal.label = label;
returnVal.paletteLabel = label;
return returnVal;
}
#end

Set the delegate of the toolbar before adding the toolbar to the window.

Related

Apple Metal without Interface Builder

I am racking my brain on this one. I've been trying to duplicate the initial metal project provided by Apple but without using interface builder at all. I'm able to create a window, but it's blank, nothing is rendering and I can't figure out why.
main.m
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSApplication* app = [NSApplication sharedApplication];
AppDelegate* appDelegate = [[AppDelegate alloc] init];
[app setDelegate:appDelegate];
[app run];
}
return EXIT_SUCCESS;
}
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
}
#end
AppDelegate.m
#import <Metal/Metal.h>
#import "AppDelegate.h"
#import "GameViewController.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (id)init {
if (self = [super init]) {
NSScreen* mainScreen = [NSScreen mainScreen];
NSRect frame = NSMakeRect(0, 0, mainScreen.frame.size.width / 2, mainScreen.frame.size.height / 2);
NSUInteger styleMask =
NSWindowStyleMaskTitled |
NSWindowStyleMaskResizable |
NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable;
NSBackingStoreType backing = NSBackingStoreBuffered;
window = [[NSWindow alloc] initWithContentRect:frame styleMask:styleMask backing:backing defer:YES];
[window center];
GameViewController* gvc = [[GameViewController alloc] init];
MTKView* metalView = [[MTKView alloc] initWithFrame:frame device:MTLCreateSystemDefaultDevice()];
[metalView setClearColor:MTLClearColorMake(0, 0, 0, 1)];
[metalView setColorPixelFormat:MTLPixelFormatBGRA8Unorm];
[metalView setDepthStencilPixelFormat:MTLPixelFormatDepth32Float];
[metalView setDelegate:gvc];
[gvc setView:metalView];
[window setContentView:metalView];
//[window setContentViewController: gvc];
}
return self;
}
- (void)applicationWillFinishLaunching:(NSNotification *)notification {
[window makeKeyAndOrderFront:self];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return YES;
}
#end
The other files; GameViewController.h, GameViewController.m, Shaders.metal, SharedStructures.h; are the same as what XCode 8.2.1 (8C1002) auto generates when you create a Game project that uses Metal with Objective-c.
You'll notice the line [window setContentViewController: gvc]; is commented out. When this is uncommented I get an EXEC_BAD_ACCESS on the first line of GameViewController.m's render function dispatch_semaphore_wait(_inflight_semaphore, DISPATCH_TIME_FOREVER);
Clearly there's something that I'm missing, but I've been googling all day and I can't seem to figure it out. Any help is much appreciated.
The issue is that, when an NSViewController such as the GameViewController is not loaded from a NIB, it doesn't call the -viewDidLoad method. The GameViewController does important work in that method.
You can move the creation of the view into an override of -loadView in GameViewController. Create the view and assign it to self.view. You don't need to set its delegate. The existing GameViewController code does that already. Setting the view's properties should be moved to -_setupView where the existing code already does that sort of thing.
Don't create the view in -[AppDelegate init]. Obviously, you also won't be able to set the view controller's view there, either. Just set the view controller as the window's content view controller (uncomment that line) and the rest will be taken care of automatically. When the window requests the content view controller's view property, it will call your override of -loadView along with the existing -viewDidLoad, etc.

OS X Delegate set label from other window (Xcode)

I'm quite new to Mac programming (not to Objective C).
I'm developing a small application, that shows some data and opens a second window on button press.
In the second window is a textfield and a submit button. If the submit button is pressed, the window should close + the value of the textfield needs to be passed to the first window.
I think the best method for that is a simple Delegate. I tried that but i can't change the label in the first window using the second window..
The delegate however seems to work as i can call methods from the other class and send data to it. It just won't change the label.
As this is my first try on Delegates, im pretty sure I've done something stupid here^^
or is there a better solution? Can't be to complicated to change a label from an second window.. right?
ViewController.h (FirstController)
#import <Cocoa/Cocoa.h>
#class ViewController;
#protocol ViewControllerDelegate
-(void)sayHello:(ViewController *)ViewController;
#end
#interface ViewController : NSViewController
{
IBOutlet NSTextField *txtlabel;
}
#property (nonatomic, assign) id delegate;
-(void)helloDelegate;
-(void)reciveVar:(NSString*)strvar;
#end
ViewController.m (FirstController)
#import "ViewController.h"
#implementation ViewController
#synthesize delegate;
-(id)init {
self = [super init];
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
txtlabel.stringValue=#"TEST";
}
-(void)helloDelegate
{
[delegate sayHello:self];
}
-(void)reciveVar:(NSString*)strvar
{
NSLog(#"recived: %#", strvar);
txtlabel.stringValue=strvar; // DOSENT WORK!!
}
#end
secondController.h
#import <Cocoa/Cocoa.h>
#import "ViewController.h"
#interface secondController : NSViewController <ViewControllerDelegate>
{
IBOutlet NSTextField *txtfield;
}
-(IBAction)submit:(id)sender;
#end
secondController.m
#import "firstController.h"
#implementation secondController
-(void)viewDidLoad
{
[super viewDidLoad];
ViewController *custom = [[ViewController alloc] init];
// assign delegate
custom.delegate = self;
[custom helloDelegate];
}
-(void)sayHello:(ViewController *)ViewController
{
NSLog(#"Hiya!");
}
-(IBAction)submit:(id)sender
{
NSString *txtval= txtfield.stringValue;
NSLog(#"submit: %#", txtval);
ViewController *custom = [[ViewController alloc] init];
// assign delegate
custom.delegate = self;
[custom reciveVar:txtval];
}
#end
LOG Output:
Hiya!
submit: test
recived: test
(so i guess the delegate works..)
SOLVED. (Thanks to Phillip Mills)
NSNotification is way simpler and efficient than Delegates in this case.
ViewController.m
[...]
- (void)viewDidLoad
{
[super viewDidLoad];
txtlabel.stringValue=#"TEST";
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleUpdatedData:)
name:#"DataUpdated"
object:nil];
}
-(void)handleUpdatedData:(NSNotification *)notification
{
NSLog(#"recieved %#", notification);
txtlabel.stringValue=[notification object];
}
secondController.m
-(IBAction)submit:(id)sender
{
NSString *txtval= txtfield.stringValue;
NSLog(#"submit: %#", txtval);
[[NSNotificationCenter defaultCenter] postNotificationName:#"DataUpdated"
object:txtval];
}

ABPersonViewControllerDelegate shouldPerformDefaultActionForPerson not working

For some reason, my shouldPerformDefaultActionForPerson function never gets called, in the simulator and the device. Could someone look at this and tell me what I'm missing? From all the examples I've found, I can't see what I'm doing wrong.
Here is my header:
#import <Foundation/Foundation.h>
#import <AddressBookUI/AddressBookUI.h>
#import <MessageUI/MessageUI.h>
#import "SWO.h"
#interface CustomABPersonViewController : ABPersonViewController
<MFMessageComposeViewControllerDelegate, ABPersonViewControllerDelegate>
{
SWO *theSWO;
}
- (id)initWithSWO:(SWO *)aSWO;
#end
And here is my implementation file:
#import "CustomABPersonViewController.h"
#import <MessageUI/MessageUI.h>
#import "SWO.h"
#implementation CustomABPersonViewController
- (id)initWithSWO:(SWO *)aSWO
{
if (self = [super init])
{
//Various init code
}
return self;
}
- (BOOL)personViewController:(ABPersonViewController *)personView shouldPerformDefaultActionForPerson:(ABRecordRef)person property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)IdentifierForValue
{
NSLog(#"In here!");
return YES;
}
#end
And here is when I call in the view:
CustomABPersonViewController *view = [[CustomABPersonViewController alloc] initWithSWO:aSWO];
view.personViewDelegate = self;
view.displayedPerson = aRecord;
[self.navigationController setToolbarHidden:NO animated:NO];
[self.navigationController pushViewController:view animated:YES];
Many default actions do not work on the Simulator. You have to try them on an actual device.

SubClassing UILabel

I read in this same site how to inset and UILabel (subclass UILabel and override the required methods). Before adding it to my app I decided to test it out in a standalone test app. Code is shown below.
Here's MyUILabel.h
#import <UIKit/UIKit.h>
#interface MyUILabel : UILabel
#end
Here's MyUILabel.m
#import "MyUILabel.h"
#import <QuartzCore/QuartzCore.h>
#implementation MyUILabel
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
// for border and rounding
-(void) drawRect:(CGRect)rect
{
self.layer.cornerRadius = 4.0;
self.layer.borderWidth = 2;
[super drawRect:rect];
}
// for inset
-(void) drawTextInRect:(CGRect)rect
{
UIEdgeInsets insets = {0, 5, 0, 5};
[super drawTextInRect: UIEdgeInsetsInsetRect(rect, insets)];
}
Here's my ViewController.h
#import <UIKit/UIKit.h>
#import "MyUILabel.h"
#interface ViewController : UIViewController
{
MyUILabel *myDisplay;
}
#property (strong, nonatomic) IBOutlet MyUILabel *myDisplay;
#end
Here's ViewController.m:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize myDisplay;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
myDisplay.text = #"Hello World!";
}
- (void)viewDidUnload
{
[self setMyDisplay:nil];
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
#end
None of the methods in MyUILabel.m (that Im overriding) get called.
Insights into why are greatly appreciated.
Regards,
Ramon.
Ok. I did some further digging and in Xcode there is a field visible when looking at the nib file. Its the 'Identity Inspector' (3rd icon from left). This needed to be changed from UILabel to MyUILabel.
Now it works!

setNeedsDisplay not updating interface

When calling [polygonShapeView setNeedsDisplay]; my polygonShapeView drawRect method is NOT called. I am able to do polygonShapeView.hidden = YES, which works fine so I have a good reference to the view and have hooked up my outlet. Any ideas?
Controller.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "PolygonShape.h"
#import "PolygonShapeView.h"
#interface Controller : NSObject {
IBOutlet UIButton *decreaseButton;
IBOutlet UIButton *increaseButton;
IBOutlet UILabel *numberOfSidesLabel;
IBOutlet PolygonShape *polygonShape;
IBOutlet PolygonShapeView *polygonShapeView;
IBOutlet UILabel *polygonLabel;
}
- (IBAction)decrease:(id)sender;
- (IBAction)increase:(id)sender;
- (void)awakeFromNib;
- (void)updateInterface;
#end
Controller.m
//
// Controller.m
//
// Created by Chris Muench on 6/24/11.
// Copyright 2011 N/A. All rights reserved.
//
#import "Controller.h"
#implementation Controller
- (IBAction)decrease:(id)sender
{
[polygonShape setNumberOfSides:numberOfSidesLabel.text.integerValue - 1];
[self updateInterface];
}
- (IBAction)increase:(id)sender
{
[polygonShape setNumberOfSides:numberOfSidesLabel.text.integerValue + 1];
[self updateInterface];
}
- (void)awakeFromNib
{
polygonShape = [[PolygonShape alloc] initWithNumberOfSides:numberOfSidesLabel.text.integerValue minimumNumberOfSides:3 maximumNumberOfSides:12];
[self updateInterface];
}
- (void)updateInterface
{
[polygonShapeView setNeedsDisplay];
numberOfSidesLabel.text = [NSString stringWithFormat:#"%d",polygonShape.numberOfSides];
polygonLabel.text = polygonShape.name;
if (polygonShape.numberOfSides == polygonShape.maximumNumberOfSides)
{
increaseButton.enabled = NO;
}
else
{
increaseButton.enabled = YES;
}
if(polygonShape.numberOfSides == polygonShape.minimumNumberOfSides)
{
decreaseButton.enabled = NO;
}
else
{
decreaseButton.enabled = YES;
}
}
#end
I can only guess, but it seems your polygonShapeView is not linked in any way to the polygonShape. So it might draw, but not according to the data you expect.
I think there should be something like polygonShapeView.shape = polygonShape; in awakeFromNib, or in updateInterface.