how to know which NSTextField has been modified its value - objective-c

I have several NSTextFields declared on my NSWindowController, all of them has as delegate the File's Owner, and respond fine to this method:
-(void)controlTextDidEndEditing:(NSNotification *)obj{
}
but I also want to know the control's value for this I used next code
-(void)controlTextDidEndEditing:(NSNotification *)obj{
if ((NSTextField *)obj == self.nombreCuentaActivoTextField) {
NSLog(#"you just edited nombreCuentaActivoTextField");
}
}
but it doesn't work, how to do that

obj is an NSNotification. You can't just cast it to an NSTextField and assume you've achieved anything useful.
The control which posted that notification and thus triggered that delegate method is the "object" of the notification. You can use [obj object] to obtain that. So, you might implement the method like so (I've renamed obj to notification for clarity):
-(void)controlTextDidEndEditing:(NSNotification *)notification{
if ([notification object] == self.nombreCuentaActivoTextField) {
NSLog(#"you just edited nombreCuentaActivoTextField");
}
}

Related

NSComboBox - how can I implement a delegate for 2 different comboBoxes?

I've implemented the NSComboBoxDelegate:
-(void)comboBoxSelectionDidChange:(NSNotification *)notification{
}
- (void)controlTextDidEndEditing:(NSNotification *)aNotification{
}
- (void)comboBoxWillPopUp:(NSNotification *)notification{
}
- (void)comboBoxWillDismiss:(NSNotification *)notification{
}
but I have 2 comboBoxes - with 2 different functionalities.
is there a way to know which comboBox is no on the run, and act accordingly?
or do I have to implement 2 different delegates outside? and if so - is there an easy way to transfer information back to my viewcontroller?
is there a way to get info about the sender of the notification?
For text change (controlTextDidEndEditing, controlTextDidChange...), see the following example.
- (void)controlTextDidEndEditing:(NSNotification *)obj {
if ([obj object] == combobox1) {
}
else if ([obj object] == combobox2) {
}
}
As for selection change, you need to create IBAction connections for respective objects.
The notification object ([notification object] or notification.object) will be the combo box that sent the notification.
Why don't you use tags? you can assign a tag 101 to one of them and a 102 to the other, then when they fire the delegate you just need to have an if clause to check the object's tag.

textFieldDidBeginEditing using tags

I'm running into a little issue with my textFieldDidBeginEditing method..
I'm trying to figure out which textfield is being called on to edit so I can decide if I want the view to move up or not to make the field visible.
Here is my method, I have commented some things out to try to find out where the error is:
- (void)textFieldDidBeginEditing:(UITextField *)sender
{
NSLog(#"This method is called");
//[self.view setFrame:CGRectMake(0,-120,320,568)];
if(sender.tag == _nameF.tag)
{
NSLog(#"This if is called");
//[self.view setFrame:CGRectMake(0,-120,320,568)];
}
else
{
NSLog(#"Else called instead");
}
}
I see "This method is called" in the log, so I know the method is being called in the first place, but after that, I see this:
2013-07-23 12:27:18.654 SidebarDemo[2110:60b] -[NSConcreteNotification tag]: unrecognized selector sent to instance 0x15d7b8c0
2013-07-23 12:27:18.655 SidebarDemo[2110:60b] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSConcreteNotification tag]: unrecognized selector sent to instance 0x15d7b8c0'
This leads me to believe that it is something with sender.tag, but I don't see anything wrong with my code, to my knowledge.
What could the issue be here? Is there another method I can use to find out what textfield is being edited?
Thanks.
Since you are setting up the UITextFieldTextDidBeginEditingNotification notification to call your textFieldDidBeginEditing: method, you need to change the method parameter. And to avoid confusion with the corresponding UITextFieldDelegate method, you should rename this method as well (which means you need to update the line of code that register the notification handler).
- (void)textFieldDidBeginEditingHandler:(NSNotification *)notification {
UITextField *textField = (UITextField *)notification.object;
// It's OK to use == here since we really do want to compare pointer values
if(textField == _nameF) {
NSLog(#"This if is called");
//[self.view setFrame:CGRectMake(0,-120,320,568)];
} else {
NSLog(#"Else called instead");
}
}
There is no need for tags since you have ivars for each text field.
BTW - why are you using notifications for this? Why not use the UITextFieldDelegate methods?

How to check if a view controller can perform a segue

This might be a very simple question but didn't yield any results when searching for it so here it is...
I am trying to work out a way to check if a certain view controller can perform a segue with identifier XYZ before calling the performSegueWithIdentifier: method.
Something along the lines of:
if ([self canPerformSegueWithIdentifier:#"SegueID"])
[self performSegueWithIdentifier:#"SegueID"];
Possible?
To check whether the segue existed or not, I simply surrounded the call with a try-and-catch block. Please see the code example below:
#try {
[self performSegueWithIdentifier:[dictionary valueForKey:#"segue"] sender:self];
}
#catch (NSException *exception) {
NSLog(#"Segue not found: %#", exception);
}
Hope this helps.
- (BOOL)canPerformSegueWithIdentifier:(NSString *)identifier
{
NSArray *segueTemplates = [self valueForKey:#"storyboardSegueTemplates"];
NSArray *filteredArray = [segueTemplates filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"identifier = %#", identifier]];
return filteredArray.count>0;
}
This post has been updated for Swift 4.
Here is a more correct Swift way to check if a segue exists:
extension UIViewController {
func canPerformSegue(withIdentifier id: String) -> Bool {
guard let segues = self.value(forKey: "storyboardSegueTemplates") as? [NSObject] else { return false }
return segues.first { $0.value(forKey: "identifier") as? String == id } != nil
}
/// Performs segue with passed identifier, if self can perform it.
func performSegueIfPossible(id: String?, sender: AnyObject? = nil) {
guard let id = id, canPerformSegue(withIdentifier: id) else { return }
self.performSegue(withIdentifier: id, sender: sender)
}
}
// 1
if canPerformSegue("test") {
performSegueIfPossible(id: "test") // or with sender: , sender: ...)
}
// 2
performSegueIfPossible(id: "test") // or with sender: , sender: ...)
As stated in the documentation:
Apps normally do not need to trigger segues directly.
Instead, you configure an object in Interface Builder associated with
the view controller, such as a control embedded in its view hierarchy,
to trigger the segue. However, you can call this method to trigger a
segue programmatically, perhaps in response to some action that cannot
be specified in the storyboard resource file. For example, you might
call it from a custom action handler used to process shake or
accelerometer events.
The view controller that receives this message must have been loaded
from a storyboard. If the view controller does not have an associated
storyboard, perhaps because you allocated and initialized it yourself,
this method throws an exception.
That being said, when you trigger the segue, normally it's because it's assumed that the UIViewController will be able to respond to it with a specific segue's identifier. I also agree with Dan F, you should try to avoid situations where an exception could be thrown. As the reason for you not to be able to do something like this:
if ([self canPerformSegueWithIdentifier:#"SegueID"])
[self performSegueWithIdentifier:#"SegueID"];
I am guessing that:
respondsToSelector: only checks if you are able to handle that message in runtime. In this case you can, because the class UIViewController is able to respond to performSegueWithIdentifier:sender:. To actually check if a method is able to handle a message with certain parameters, I guess it would be impossible, because in order to determine if it's possible it has to actually run it and when doing that the NSInvalidArgumentException will rise.
To actually create what you suggested, it would be helpful to receive a list of segue's id that the UIViewController is associated with. From the UIViewController documentation, I wasn't able to find anything that looks like that
As for now, I am guessing your best bet it's to keep going with the #try #catch #finally.
You can override the -(BOOL)shouldPerformSegueWithIdentifier:sender: method and do your logic there.
- (BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([identifier isEqualToString:#"someSegue"]) {
if (!canIPerformSegue) {
return NO;
}
}
return YES;
}
Hope this helps.
Reference CanPerformSegue.swift
import UIKit
extension UIViewController{
func canPerformSegue(identifier: String) -> Bool {
guard let identifiers = value(forKey: "storyboardSegueTemplates") as? [NSObject] else {
return false
}
let canPerform = identifiers.contains { (object) -> Bool in
if let id = object.value(forKey: "_identifier") as? String {
return id == identifier
}else{
return false
}
}
return canPerform
}
}
Swift version of Evgeny Mikhaylov's answer, which worked for me:
I reuse a controller for two views. This helps me reuse code.
if(canPerformSegueWithIdentifier("segueFoo")) {
self.performSegueWithIdentifier("segueFoo", sender: nil)
}
else {
self.performSegueWithIdentifier("segueBar", sender: nil)
}
func canPerformSegueWithIdentifier(identifier: NSString) -> Bool {
let templates:NSArray = self.valueForKey("storyboardSegueTemplates") as! NSArray
let predicate:NSPredicate = NSPredicate(format: "identifier=%#", identifier)
let filteredtemplates = templates.filteredArrayUsingPredicate(predicate)
return (filteredtemplates.count>0)
}
It will be useful, before call performSegue, check native storyboard property on base UIViewController (for example screen was from StoryBoard or Manual Instance)
extension UIViewController {
func performSegueWithValidate(withIdentifier identifier: String, sender: Any?) {
if storyboard != nil {
performSegue(withIdentifier: identifier, sender: sender)
}
}
}
enter image description here
There is no way to check that using the standard functions, what you can do is subclass UIStoryboardSegue and store the information in the source view controller (which is passed to the constructor). In interface builder select "Custom" as the segue type as type the class name of your segue, then your constructor will be called for every segue instantiated and you can query the stored data if it exists.
You must also override the perform method to call [source presentModalViewController:destination animated:YES] or [source pushViewController:destination animated:YES] depending on what transition type you want.

EXC_BAD_ACCESS when messaging a valid object

My main app controller invokes a subcontroller to handle a certain sequence of screens. The main controller sets itself as a delegate in the subcontroller. When the subcontroller is done doing its stuff, it notifies the delegate. Every now and then, this notification fails with EXC_BAD_ACCESS.
0)Based on gdb, the problem occurs in objc_msgSend. Both registers have a non-zero value.
gdb: 0x3367cc98 <+0016> ldr r5, [r4, #8]
1)I've tried NSZombiesEnabled to track the problem, but I couldn't reproduce it then.
2)I've tried setting a breakpoint just before the problematic command, but again I can't reproduce the issue.
I have no clue what's going on.
This is the delegate property declaration (the parent controller outlives the child):
#property (assign) id<ParentControllerDelegate> delegate
This is the problematic code:
- (void) doStuff {
if(mode == Done) {
NSLog(#"Done. Handling back control");//this is the last log displayed by the console
[self.delegate done: self];
} else {
// some controller code
}
This is the the code on the delegate side (the delegate has been retained by the App_Delegate, as it is the main controller).
- (void) done: (UIViewController *) caller {
NSLog(#"Taken back control");// this never displays
[caller.view removeFromSuperview];
[caller release];
}
Some extra info:
The main controller retains the subcontroller.
I've also modified the deallocs in both the main and sub controllers to log when it is called. Based on the visible logs, neither is ever called during the course of the application. Hence both the receiver and the sender of the message are valid objects.
I'm really at loss here. Looking forward to your help.
If the NSLog call in done: is never performed, that can only mean that you did not call the main controller's done:. That can mean that self.delegate is not valid. The objects may be valid and alive, but not the link (self.delegate) between them. Check that, please. In doStuff, in the "Done" branch, show the address of self.delegate with
NSLog(#"%p", self.delegate);
before you call done: and compare that with the address of the main controller.
Just a wild guess, but if it's "now and then" it's probably viewDidLoad or viewDidUnload causing the EXC_BAD_ACCESS after receiving memory warning. Check your released/retained/created instance variables in your parent/child controller especially in aforementioned view loading methods.
Try to perform check protocol and method before call as in the code:
- (void) doStuff
{
if(mode == Done)
{
NSLog(#"Done. Handling back control");//this is the last log displayed by the console
if ([delegate conformsToProtocol: #protocol(ParentControllerDelegate)])
{
if ([delegate respondsToSelector: #selector(done:)] == YES)
{
[delegate performSelector: #selector(done:) withObject: self];
}
}
}
else
{
// some controller code

EXC_BAD_ACCESS invoking a block

UPDATE | I've uploaded a sample project using the panel and crashing here: http://w3style.co.uk/~d11wtq/BlocksCrash.tar.gz (I know the "Choose..." button does nothing, I've not implemented it yet).
UPDATE 2 | Just discovered I don't even have to invoke anything on newFilePanel in order to cause a crash, I merely need to use it in a statement.
This also causes a crash:
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) {
newFilePanel; // Do nothing, just use the variable in an expression
}];
It appears the last thing dumped to the console is sometimes this: "Unable to disassemble dyld_stub_objc_msgSend_stret.", and sometimes this: "Cannot access memory at address 0xa".
I've created my own sheet (an NSPanel subclass), that tries to provide an API similar to NSOpenPanel/NSSavePanel, in that it presents itself as a sheet and invokes a block when done.
Here's the interface:
//
// EDNewFilePanel.h
// MojiBaker
//
// Created by Chris Corbyn on 29/12/10.
// Copyright 2010 Chris Corbyn. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#class EDNewFilePanel;
#interface EDNewFilePanel : NSPanel <NSTextFieldDelegate> {
BOOL allowsRelativePaths;
NSTextField *filenameInput;
NSButton *relativePathSwitch;
NSTextField *localPathLabel;
NSTextField *localPathInput;
NSButton *chooseButton;
NSButton *createButton;
NSButton *cancelButton;
}
#property (nonatomic) BOOL allowsRelativePaths;
+(EDNewFilePanel *)newFilePanel;
-(void)beginSheetModalForWindow:(NSWindow *)aWindow completionHandler:(void (^)(NSInteger result))handler;
-(void)setFileName:(NSString *)fileName;
-(NSString *)fileName;
-(void)setLocalPath:(NSString *)localPath;
-(NSString *)localPath;
-(BOOL)isRelative;
#end
And the key methods inside the implementation:
-(void)beginSheetModalForWindow:(NSWindow *)aWindow completionHandler:(void (^)(NSInteger result))handler {
[NSApp beginSheet:self
modalForWindow:aWindow
modalDelegate:self
didEndSelector:#selector(sheetDidEnd:returnCode:contextInfo:)
contextInfo:(void *)[handler retain]];
}
-(void)dismissSheet:(id)sender {
[NSApp endSheet:self returnCode:([sender tag] == 1) ? NSOKButton : NSCancelButton];
}
-(void)sheetDidEnd:(NSWindow *)aSheet returnCode:(NSInteger)result contextInfo:(void *)contextInfo {
((void (^)(NSUInteger result))contextInfo)(result);
[self orderOut:self];
[(void (^)(NSUInteger result))contextInfo release];
}
This all works provided my block is just a no-op with an empty body. My block in invoked when the sheet is dismissed.
EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
[newFilePanel setAllowsRelativePaths:[self hasSelectedItems]];
[newFilePanel setLocalPath:#"~/"];
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) {
NSLog(#"I got invoked!");
}];
But as soon as I try to access the panel from inside the block, I crash with EXC_BAD_ACCESS. For example, this crashes:
EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
[newFilePanel setAllowsRelativePaths:[self hasSelectedItems]];
[newFilePanel setLocalPath:#"~/"];
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) {
NSLog(#"I got invoked and the panel is %#!", newFilePanel);
}];
It's not clear from the debugger with the cause is. The first item (zero 0) on the stack just says "??" and there's nothing listed.
The next items (1 and 2) in the stack are the calls to -endSheet:returnCode: and -dismissSheet: respectively. Looking through the variables in the debugger, nothing seems amiss/out of scope.
I had thought that maybe the panel had been released (since it's autoreleased), yet even calling -retain on it right after creating it doesn't help.
Am I implementing this wrong?
It's a little odd for you to retain a parameter in one method and release it in another, when that object is not an instance variable.
I would recommend making the completionHandler bit of your beginSheet stuff an instance variable. It's not like you'd be able to display the sheet more than once at a time anyway, and it would be cleaner this way.
Also, your EXC_BAD_ACCESS is most likely coming from the [handler retain] call in your beginSheet: method. You're probably invoking this method with something like (for brevity):
[myObject doThingWithCompletionHandler:^{ NSLog(#"done!"); }];
If that's the case, you must -copy the block instead of retaining it. The block, as typed above, lives on the stack. However, if that stack frame is popped off the execution stack, then that block is gone. poof Any attempt to access the block later will result in a crash, because you're trying to execute code that no longer exists and has been replaced by garbage. As such, you must invoke copy on the block to move it to the heap, where it can live beyond the lifetime of the stack frame in which it was created.
Try defining your EDNewFilePanel with the __block modifier:
__block EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
This should retain the object when the block is called, which may be after the Panel object is released. As an unrelated side-effect, this will make also make it mutable within the block scope.