NSNotification addObserver selector could not open a NSBeginAlertSheet sheet on window - objective-c

I have two different window Controllers. First is a custom panel window controller and another is main window controller. In panel window there is a panel window and there are button on that panel. On click of those buttons I am posting notification like :
In PanelWindowController:
-(IBAction)okAndCancelButtonClicked:(id)sender
{
[self postNotification:sender];
}
-(void)postNotification:(id)sender
{
if([sender tag]!=2){
[[self window] endSheet:self.panel returnCode:NSModalResponseCancel];
[self.panel orderOut:self];
}
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInteger:[sender tag]],#"value",nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"PanelButtonClickedNotification" object:self userInfo:dict];
}
Now, in my main window controller, I am trying to open a NSBeginAlertSheet in the selector of addObserver of NSNotificationCenter. Following is the addObserver selector declaration in init method of my main window controller:
MainWindowController
-(id) init{
..// some code here
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(okButtonClicked:) name:#"PanelButtonClickedNotification" object:[self panelClass]];
return self;
}
the implementation of okButtonClicked is the following :
- (void) okButtonClicked:(NSNotification*)notification
{
if ([[notification object] isEqualTo:[self panelClass]])
{
if([[[notification userInfo] objectForKey:#"value"] integerValue] == 1)
{
// Yes Button in the Panel is clicked
}
else if([[[notification userInfo] objectForKey:#"value"] integerValue] == 0)
{
// No Button in the Panel is clicked
NSBeginAlertSheet(#"Alert", #"Ok", nil, nil, [[self view] window], self,nil, nil,nil,#"Alert is being shown on window.");
}
}
}
When user clicks No button on Panel, an alert on the window should be displayed. But the alert is never shown. I also tried [NSApp keyWindow] and [NSApp mainWindow] instead of [[self view] window].
And, If I run the alert independently of window, it is displayed:
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:#"Alert"];
[alert addButtonWithTitle:#"OK"];
NSImage *icon=[NSImage imageNamed:#"warning.png"];
[alert setIcon:icon];
[alert runModal];
Please let me know if I am missing anything here.
The alert is not displayed in any of the methods called after the notification is received.
PFA my sample project : https://www.dropbox.com/s/0xfe4bk17v9girj/PanelApplication.zip?dl=0

It is a little difficult to tell, but my best guess would be, that the object that reacts to the notification, in the okButtonClicked: function, does not have a (valid) reference to your window. And then, the alert don't know on what window to display.
If you can send a reference to your window in the notification object, then you should be able to get the alert shown on the window you want.
example code:
[[NSNotificationCenter defaultCenter] postNotificationName:#"PanelButtonClickedNotification" object:[self window]];
And:
- (void) okButtonClicked:(NSNotification*)notification
{
NSBeginAlertSheet(#"Alert", #"Ok", nil, nil, [notification object], self,nil, nil,nil,#"Alert is being shown on window.");
}
this works in my test-project.

The problem was not with the notifications, it was the panel which was causing problems:
I was opening the panel in my main window controller:
[[self window] beginSheet:self.panelClass.panel completionHandler:nil];
And the closing action of the panel was written in the Panel window controller. Because of this reason no NSBeginAlertSheet was not being shown once the custom panel was loaded/ unloaded on the main window.
Therefore moving the following piece of code from panel window controller to main window controller solved the problem:
[[self window] endSheet:self.panelClass.panel returnCode:NSModalResponseCancel];
[self.panelClass.panel orderOut:self];

Related

Unable to focus on NSTextFields from Modal window

I currently have a window open which was opened via:
// FirstWindowController
[self showWindow:self];
[[self window] makeKeyAndOrderFront:self];
[NSApp runModalForWindow:[self window]];
And upon click of a button I'd like to hide FirstWindowController via:
// FirstWindowController
[self.window orderOut:self];
And then show my second window:
// SecondWindowController
[self showWindow:self];
[[self window] makeKeyAndOrderFront:self];
[NSApp runModalForWindow:[self window]];
The first window disappears correctly, and the second window appears. But I can't actually use the NSTextFields in the input. But I can click the cancel button to hide SecondWindowController and give focus back to FirstWindowController.
Why can't I click any of the NSTextField elements?
I had the same problem. It worked when the window had a title bar and otherwise not. It seems like that a window needs to have a title to become a keyWindow.
The workaround for this is to make a subclass of NSWindow and override -canBecomeKeyWindow:
(BOOL)canBecomeKeyWindow {
return YES;
}

Trying to change button text in a view from an appDelegate method

I'm a little new to iOS development and am running into a little problem.
I've implemented the FB SDK for iOS login into my app, and I can login and out no problem.
However, what I'm trying to do is change the single button text from 'Log in' to 'Log out', depending on the state of the FB session.
In my appDelegate (which handles the FB session state changes), I'm calling two methods from my main view controller like this:
helloappViewController * vc = [[helloappViewController alloc]init];
[vc showLogInButton];
...and...
helloappViewController * vc = [[helloappViewController alloc]init];
[vc showLogOutButton];
The methods in helloappViewController that are being called are these:
- (void) showLogInButton {
NSLog(#"Changing button text to 'Login'.");
[self.buttonLogInLogOut setTitle:#"Login" forState:UIControlStateNormal];
}
- (void) showLogOutButton {
NSLog(#"Changing button text to 'Logout'.");
[self.buttonLogInLogOut setTitle:#"Logout" forState:UIControlStateNormal];
}
I know these methods is being called properly because I can see the console log output fine, and I know I'm logged in and out via FB because of other console log outputs I've set.
However, the button title text is not being changed.
Anyone have any idea where I might be going wrong?
Thx.
Try to move code
[self showLogInButton];
and
[self showLogOutButton];
to viewDidLoad method of the helloappViewController view controller
something like this:
helloappViewController * vc = [[helloappViewController alloc]init];
vc.fbIsConnected = YES;
in viewDidLoad:
if (self.fbIsConnect)
{
[self showLogOutButton];
}
else
{
[self showLogInButton];
}
In vc:
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(fbDidChange)
name: FBDidChangeNotification
object: nil];
- (void) fbDidChange
{
// change button title
}
in fb controller:
[[NSNotificationCenter defaultCenter] postNotificationName:FBDidChangeNotification object:self];

NSNotification sent once, but is received multiple times

I am communicating between two classes with NSNotificationCenter. My problem is that although I tap a button once (and that button only fires off once) I am unintentionally producing increasing numbers of notifications from only one call to the NSNotificationCenter.
Here is a better explanation of the problem, with code:
My two classes are the mainView class and the Menu class.
When a view in the mainView class is tapped, it launches a view created and governed by the Menu class. This code is called when the mainView is initialized:
menu=[[MyMenu alloc] init];
UITapGestureRecognizer * tap=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onTapped:)];
[tap setNumberOfTapsRequired:1];
[container addGestureRecognizer:tap];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onChangeItem:) name:#"ItemChange" object:nil];
This Gesture Recognizer fires off this method, also in the mainView class:
- (void) onTapped: (UIGestureRecognizer*) recognizer {
NSLog(#"tap");
[menu displayMenu];
}
This is how the Menu class initializes:
- (MyMenu*) init {
self=[super init];
UICollectionViewFlowLayout * layout=[[UICollectionViewFlowLayout alloc] init];
menuView=[[UICollectionView alloc] initWithFrame:CGRectMake(0, 0, 200, 200) collectionViewLayout:layout];
[menuView setDataSource:self];
[menuView setDelegate:self];
[menuView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:#"cell"];
[menuView setAutoresizesSubviews:YES];
[menuView setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth];
[menuView setBackgroundColor:[UIColor clearColor]];
[menuView setIndicatorStyle:UIScrollViewIndicatorStyleWhite];
return self;
}
And this is the displayMenu method inside the Menu class:
- (void) displayMenu {
[viewForMenu addSubview:menuView];
}
The Menu class also has a clearMenu method:
- (void) clearMenu {
[menuView removeFromSuperview];
}
This is the code for each cell in the UICollectionView, contained within my Menu class:
- (UICollectionViewCell*) collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell * cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cell" forIndexPath:indexPath];
[cell setTag:indexPath.row];
UITapGestureRecognizer * tap=[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onButtonTapped:)];
[tap setNumberOfTapsRequired:1];
[cell addGestureRecognizer:tap];
NSLog(#"button tapped : %d",indexPath.row);
return cell;
}
This calls the onButtonTapped: method, also within my Menu class:
- (void) onButtonTapped:(UIGestureRecognizer*) recognizer {
NSInteger buttonTapped=[[recognizer view] tag];
[[NSNotificationCenter defaultCenter] postNotificationName:#"ItemChange" object:nil userInfo:#{#"selected":#(buttonTapped)}];
[self clearMenu];
}
This notification is picked up by my mainView class with this code:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onChangeItem:) name:#"ItemChange" object:nil];
This calls the onChangeItem: method, inside my mainView class:
- (void) onChangeItem: (NSNotification*) notification {
NSLog(#"change item to %d",[[[notification userInfo] objectForKey:#"clock"] intValue]);
}
So that's the code.
OK, here's the problem: the first time the menu displays I get this in my log:
...[43023:11f03] tap
...[43023:11f03] button tapped : 1
...[43023:11f03] change item to 1
And this is fine, this is what I expect. However second time around I get this:
...[43023:11f03] tap
...[43023:11f03] button tapped : 1
...[43023:11f03] change item to 1
...[43023:11f03] change item to 1
Third time around I get this:
...[43023:11f03] tap
...[43023:11f03] button tapped : 1
...[43023:11f03] change item to 1
...[43023:11f03] change item to 1
...[43023:11f03] change item to 1
...[43023:11f03] change item to 1
And so on. Each successive tap on a menu item doubles the amount of notification calls.
To begin with I thought I was adding multiple views, and thus resulting in multiple button taps, and therefore multiple notifications calls.
However as you can see from my logs, this is not the case. The buttons are only receiving 1 tap event - this is firing off only 1 notification - but receiving class gets sent multiple notifications.
Can anyone explain this to me?
Sorry for the lengthy post!
Well, I am assuming that [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onChangeItem:) name:#"ItemChange" object:nil]; are being added more than once.
I like to remove any potential observer before adding an observer, like so:
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"ItemChange" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(onChangeItem:) name:#"ItemChange" object:nil];
That way there will ever only be one observer callback.
Issue: I faced same problem, observer was calling twice, sometimes thrice.
Scenario
A user taps logout button
HomeViewController was dismissed and LoginViewController screen was presented
When user signed in again for second time
Observer was called twice (sometimes thrice)
The issue was [[NSNotificationCenter defaultCenter] removeObserver:self]; in dealloc method of my HomeViewController was not called at all, which actually removes an object from the notification center all together.
ℹ️ dealloc is an Objective-C selector that is sent by the
Objective-C runtime to an object when the object is no longer owned by
any part of the application.
Solution: Made your own method dispose and call it when user tap logout.
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
// ....
}
- (void)dispose {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)logoutTapped {
[self dispose];
// ....
}

UINavigationController Title Overlap

I am using a UINavigationController and pushing/popping UIViewControllers onto it. In some instances I am attempting to pop to the root view controller and then push a view controller after a short delay (0.1f).
My push code for the Message View Controller is as follows. My app fires two notifications. The first to select a tab and the second to push the correct view controller onto the stack of that tab.
//user taps a button and the app needs to switch tab and push the correct viewController
//onto the tab. I have tried setting pop == NO to avoid a 'double pop' but I still get
//overlapped titles
-(IBAction)messages:(id)sender {
NSDictionary* dictionary = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:[NSNumber numberWithInt:4], [NSNumber numberWithBool:YES] , nil] forKeys:[NSArray arrayWithObjects:#"tab",#"pop", nil]];
[[NSNotificationCenter defaultCenter] postNotificationName:kAutoSelectTab object:dictionary];
[[NSNotificationCenter defaultCenter] performSelector:#selector(postNotificationName:object:) withObject:kMessages afterDelay:0.1f];
}
//responds to the first notification
-(void)autoSelectTab:(NSNotification*)notification {
NSDictionary* dictionary = (NSDictionary*)[notification object];
int tab = [[dictionary objectForKey:#"tab"] intValue];
BOOL pop = [[dictionary objectForKey:#"pop"] boolValue];
[self.tabBarController setSelectedIndex:tab];
UIViewController* vc = [[self.tabBarController childViewControllers] objectAtIndex:tab];
PSLogDebug(#"Selecting tab:%#",[vc class]);
[self tabBarController:self.tabBarController didSelectViewController:vc];
if (pop == YES) {
if ([vc isKindOfClass:[UINavigationController class]]) {
[(UINavigationController*)vc popToRootViewControllerAnimated:YES];
}
}
}
//responds to the second notification
-(IBAction)messages:(id)sender {
[self.navigationController popToRootViewControllerAnimated:NO];
MessagesViewController* vc = [[MessagesViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
[vc release];
}
Functionally the views appear to pop and push correctly BUT the titles do not pop and each new title is overlaid atop the old one.
I set the titles for each of the view controllers in viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.navigationItem.title = #"More";
}
When I don't attempt the pop to root followed by the delay followed by the push - the titles and views behave as expected with no overlapping occurring.
Example Images from screenshots
I've had a good dig around stack overflow but I can't see any questions which describe the same issue as the one I am having.
Qn.1: Is there something fundamentally incorrect with the popToRoot, Delay, push View approach?
Qn.2: If anyone out there has seen this kind of behaviour before, how did you resolve it?
Increasing the delay from 0.1f to 0.5f fixed the problem
Change
[[NSNotificationCenter defaultCenter] performSelector:#selector(postNotificationName:object:) withObject:kMessages afterDelay:0.1f];
to
[[NSNotificationCenter defaultCenter] performSelector:#selector(postNotificationName:object:) withObject:kMessages afterDelay:0.5f];

NSWindow windowDidResignKey not getting invoked after redisplaying window

I have a custom NSWindow subclass that the user can toggle the display of with the click of a button. I'd also like the window to disappear when the window resigns key status (e.g. by the user clicking outside the window).
I have a delegate that implements windowDidResignKey: but I find that this delegate method is only invoked the first time the window resigns key.
Here's how I toggle the display of the window (via user action or windowDidResignKey):
- (void) toggleWindowAtPoint:(NSPoint)point
{
// Attach/detach window.
if (!attachedWindow)
{
attachedWindow = [[CustomWindow alloc] attachedToPoint:point];
attachedWindow.delegate = self;
[attachedWindow setLevel:NSMainMenuWindowLevel+1]; // show window in front of all other apps on desktop
[attachedWindow makeKeyAndOrderFront:self];
}
else
{
attachedWindow.delegate = nil;
[attachedWindow orderOut:self];
[attachedWindow release];
attachedWindow = nil;
}
}
Here's my implementation of windowDidResignKey:
- (void) windowDidResignKey:(NSNotification *)note
{
[self toggleWindowAtPoint:NSMakePoint(0, 0)];
}
I'm finding that the first time the custom window is displayed, windowDidResignKey: gets called. Every time the custom window is re-displayed after that, windowDidResignKey: is not getting invoked.
The issue was that in some cases, the custom window was not actually becoming the key window after calling [attachedWindow makeKeyAndOrderFront:self].
I fixed this by adding the following line before re-creating the window:
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
In the context of the code snippet above:
- (void) toggleWindowAtPoint:(NSPoint)point
{
// Attach/detach window.
if (!attachedWindow)
{
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
attachedWindow = [[CustomWindow alloc] attachedToPoint:point];
....
Have you tried calling [attachedWindow makeFirstResponder:attachedWindow] in your toggle method?
If you want to activate a window without using activateIgnoringOtherApps: you should use a NSPanel with a NSNonactivatingPanelMask:
[[CustomPanel alloc]
initWithContentRect: NSZeroRect
styleMask: NSNonactivatingPanelMask
backing: NSBackingStoreBuffered
defer: NO];