iphone SDK: Not sure why I am not receiving UITextField events? - objective-c

I have defined the controller to receive the events.
#interface salesViewController : UIViewController
<UITextFieldDelegate>{
However, none of my events are not firing.
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
//this is not getting called
}
In Interface Builder I assigned the TextField delegate to the salesView.
What am I missing?

You have to set the delegate properly. You observe the protocol, but you need to do this:
#interface YourController : UIViewController<UITextFieldDelegate> {
IBOutlet UITextField* field;
}
#end
#implementation YourController
-(void)viewDidLoad
{
[field setDelegate:self];
}
And you will receive the events. Alternatively, you can set the delegate in Interface Builder as well, along with doing it programmatically in loadView, allocating the field and setting the delegate.
Additionally, try to use NSNotificationCenter as little as possible. Notifications are somewhat obsolete unless there isn't really a direct path between you and the object in question. Just a small comment on the answer above.

what are you trying to accomplish? textFieldDidBeginEditing is messaged whenever the user selects the text field. If you are trying to update a label or something as the user makes edits, you need to setup an observer w/ NSNotificationCenter and watch for the notification that is fired whenever this happens.If you take this approach, make sure to remove the observer once you are done with it
for example:
#pragma mark
#pragma mark -
#pragma mark Notification Observers
- (void)addObservers {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textFieldDidChange:) name:#"UITextFieldTextDidChangeNotification" object:nil];
}
- (void)removeObservers {
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"UITextFieldTextDidChangeNotification" object:nil];
}
if you need to keep tabs on multiple text fields, do something like this for your selector:
- (void)textFieldDidChange:(NSNotification*)aNotification {
UITextField *textField = (UITextField *)[aNotification object];
if([textField isEqual:usernameTextField])
{
[user setUsername:usernameTextField.text];
}
else if([textField isEqual:phoneNumberTextField])
{
[user setPhoneNumber:phoneNumberTextField.text];
}
}

Related

NSViewController -- dismiss with memory cleanup

My environment is Yosemite 10.10.5 with Xcode 7.2 using ARC.
In a simple test program, I am attempting various ways to dismiss a NSViewController and all of them are showing problems with memory handling.
In my primary view controller, I have the following code. (The notification pieces are there to test various ways of dismissing the presented controller.)
- (IBAction)showFirstReplacement:(id)sender {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dismissWithNotification:) name:#"removeFirst" object:nil];
NSStoryboard *sb = [self storyboard];
FirstReplacement *controller = [sb instantiateControllerWithIdentifier:#"first_replacement"];
[self presentViewControllerAsSheet:controller];
}
- (void)dismissWithNotification:(NSNotification *)notification {
NSViewController *controller = [notification object];
[self dismissViewController:controller];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Inside FirstReplacement, I have:
- (IBAction)dismiss:(id)sender {
[self dismissViewController:self];
// [[NSNotificationCenter defaultCenter] postNotificationName:#"removeFirst" object:self];
// [[self presentingViewController] dismissViewController:self];
}
Uncommenting any one of the three lines in this method produces the correct visual results but.... Depending on which of the calls I enable inside dismiss:, I get different results when profiling. Using self dismissViewController:, I see no leaks but FirstReplacement objects are not deallocated. Using either of the other two approaches gets rid of the dismissed FirstReplacement but leaks one 16-byte malloc block and one NSMutableArray every time a view controller is dismissed.
According to Instruments, the leaks are related to a method called [NSViewController _addPresentedViewController:].
Are there other clean-up steps necessary to prevent these leaks (or memory bloat in the non-leak case)?
The view controller that presents another view controller is also responsible for dismissing it. So none of the lines in FirstReplacement's dismiss method are correct. Instead, you should be creating a delegate in FirstReplacement so it can notify its delegate (the primary view controller) that it should be dismissed.
FirstReplacement.h
#class FirstReplacement;
#protocol FirstReplacementDelegate <NSObject>
- (void)firstReplacementShouldDismiss:(FirstReplacement *)controller;
#end
#interface FirstReplacement : NSViewController
#property (nonatomic, weak) id<FirstReplacementDelegate> delegate;
#end
FirstReplacement.m
- (IBAction)dismiss:(id)sender {
[self.delegate firstReplacementShouldDismiss:self];
}
Then in your primary view controller:
- (IBAction)showFirstReplacement:(id)sender {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dismissWithNotification:) name:#"removeFirst" object:nil];
NSStoryboard *sb = [self storyboard];
FirstReplacement *controller = [sb instantiateControllerWithIdentifier:#"first_replacement"];
controller.delegate = self;
[self presentViewControllerAsSheet:controller];
}
- (void)firstReplacementShouldDismiss:(FirstReplacement *)controller {
[self dismissViewController:controller];
}
While it may seem like posting a notification is the same as a delegate, it is not. The difference is that when dismissWithNotification fires, you are still executing the code from FirstReplacement::dismiss. NSNotificationCenter::postNotificationName does not finish executing until all observers have finished executing their selectors. So even though the dismissal code is executing in the primary view controller, it still being run from the dismiss method.
If you are still not convinced, override FirstReplacement::dealloc to print a log statement. You will see that dealloc is not called using any of your methods, but will be called using delegation.

Cocoa Posting an NSNotification from a subclass within a subclass

I am having an issue with receiving an NSNotification that is being sent from an NSButton that has been subclassed to detect double clicks. Which itself is used within a subclass of NSView.
When I post the notification to the default notification center it never arrives in my appDelegate where I am listening for it.
Here is my NSButton subclass:
#import "DoubleClickButton.h"
#import "AppDelegate.h"
#implementation DoubleClickButton
- (void)mouseDown:(NSEvent *)theEvent
{
NSInteger clickCount = [theEvent clickCount];
if (2 == clickCount)
{
[self performSelectorOnMainThread:#selector(handleDoubleClickEvent:) withObject:nil waitUntilDone:NO];
}
}
-(void)handleDoubleClickEvent:(NSEvent *)event
{
NSLog(#"DoubleClick");
[[NSNotificationCenter defaultCenter] postNotificationName:#"doubleClickButtonNotification" object:nil];
}
#end
Listening in my AppDelegate applicationDidFinishLaunching method:
//Notification for Double tap notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doubleTapAction:)
name:#"doubleClickButtonNotification"
object:self];
The notification never arrives and doubleTapAction: is never called.
Please could someone point me in the correct direction as it is melting my brain...
Many Thanks in advance
Ben
First of all: User events (as a mouse down) are always delivered on the main thread. Therefore you do not need the -performSelectorOnMainThread:….
To your Q: Likely you misunderstood the object parameter. Why do you set it to nil when posting a notification and set it to self for adding the observer? Since nil is not $anySelf that does not match. You can set this parameter to nil when you add the observer to get notifications of all senders. So simply do it the other way round. (Setting object to something useful when posting the notification and to nilwhen adding the observer.)

Detecting a change to text in UITextfield

I would like to be able to detect if some text is changed in a UITextField so that I can then enable a UIButton to save the changes.
Instead of observing notifications or implementing textField:shouldChangeCharacterInRange:replacementString:, it's easier to just add an event target:
[textField addTarget:self
action:#selector(myTextFieldDidChange:)
forControlEvents:UIControlEventEditingChanged];
- (void)myTextFieldDidChange:(id)sender {
// Handle change.
}
Note that the event is UIControlEventEditingChanged and not UIControlEventValueChanged!
The advantages over the other two suggested solutions are:
You don't need to remember to unregister your controller with the NSNotificationCenter.
The event handler is called after the change has been made which means textField.text contains the text the user actually entered. The textField:shouldChangeCharacterInRange:replacementString: delegate method is called before the changes have been applied, so textField.text does not yet give you the text the user just entered – you'd have to apply the change yourself first.
Take advantage of the UITextFieldTextDidChange notification or set a delegate on the text field and watch for textField:shouldChangeCharactersInRange:replacementString.
If you want to watch for changes with a notification, you'll need something like this in your code to register for the notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(textFieldDidChange:) name:UITextFieldTextDidChangeNotification object:theTextField];
Here theTextField is the instance of UITextField that you want to watch. The class of which self is an instance in the code above must then implement textFieldDidChange, like so:
- (void)textFieldDidChange:(NSNotification *)notification {
// Do whatever you like to respond to text changes here.
}
If the text field is going to outlive the observer, then you must deregister for notifications in the observer's dealloc method. Actually it's a good idea to do this even if the text field does not outlive the observer.
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Other dealloc work
}
For that, first you need to have your textfield have it delegate reference assigned. And the delgate, should preferably be, the vew controller which is the files owner of the view.
Which goes like
myTextField.delegate = myViewControllerReferenceVariable
And in your viewController interface, tell you will be implementing UITextFieldDelegate by
#interface MyViewController:UIViewController<UITextFieldDelegate>
And in your view controller implementation override
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
So the code will look like
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
text = [textfield.text stringByReplacingCharactersInRange:range withString:string];
if (textfield == refToTextFieldYouWantToCheck) {
if ( ! [textToCheck isEqualToString:text] ) {
[theButtonRef setEnabled:YES];
}
}
return YES; //If you don't your textfield won't get any text in it
}
You can also subscribe to notification which is sort of messy IMHO
You can find how to do it here.
Swift 3.0
Process 1
Create IBOutlet of UITextfiled and Add Target to text field.
m_lblTxt.addTarget(self, action: #selector(self.textFieldDidChange), for: UIControlEvents.editingChanged)
func textFieldDidChange(textField:UITextField)
{
NSLog(textField.text!)
}
Process 2
m_lblTxt.delegate = self
//MARK: - TextField Delegates
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
{
print(textField.text!)
return true
}
This can be accomplished in Interface Builder on the Editing Changed event of UITextField. Drag from it to your code and create an IBAction.
For example:
#IBAction func textFieldChanged(_ sender: UITextField) {
print(sender.text)
}
This event is the same as described in other answers here in that the .text property contains the updated text input when it gets triggered. This can help clean up code clutter by not having to programmatically add the event to every UITextField in the view.
You could create a variable to store the original string, then register with the notification center to receive UITextFieldTextDidChangeNotification event:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateButton:) name:UITextFieldTextDidChangeNotification object:nil];
Then, create a method to receive the notification, and compare the current value of the text field with the original value
-(void) updateButton:(NSNotification *)notification {
self.myButton.enabled = ![self.myTextField.text isEqualToString:originalString];
}
Don't forget to de-register the notification when the view controller is deallocated.
[[NSNotificationCenter defaultCenter] removeObserver:self name:UITextFieldTextDidChangeNotification object:nil];
You can add a class member like this NSString *changeTemp,then
changetemp = textfield;
if( change temp != textfild ){
changetemp=textfild;
NSLog(#" text is changed"
} else {
NSLog(#" text isn't changed"):
}

Hide UISearchBar Cancel Button

I have a UISearchDisplayController and UISearchBar hooked up to my ViewController via Outlets from my nib.
I'd like to hide the cancel button so that the user never sees it. The problem is that the following code hides the button, but only after displaying it to the user for a millisecond (e.g., it flashes on the simulator and device and then disappears out of view).
- (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller
{
controller.searchBar.showsCancelButton = NO;
}
Is there a better way to hide it?
I managed to hide the "Cancel" button by subclassing UISearchBar and override this method:
-(void)layoutSubviews{
[super layoutSubviews];
[self setShowsCancelButton:NO animated:NO];
}
I had the same issue, but fixed it a different way.
For those who can't or don't want to subclass UISearchDisplayController, I fixed the issue by adding a listener on UIKeyboardWillShowNotification, and setting [self setShowsCancelButton:NO animated:NO] there.
In viewWillAppear::
// Add keyboard observer:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillAppear:)
name:UIKeyboardWillShowNotification
object:nil];
Then you create:
- (void)keyboardWillAppear:(NSNotification *)notification
{
[YOUR-SEARCHBAR-HERE setShowsCancelButton:NO animated:NO];
}
Don't forget to add,
[[NSNotificationCenter defaultCenter] removeObserver:self];
in viewWillDisappear:!
Hope this helps!
Similar to Nimrod's answer, you can also subclass UISearchDisplayController and implement the setActive:animated: method:
- (void)setActive:(BOOL)visible animated:(BOOL)animated {
[super setActive:visible animated:animated];
self.searchBar.showsCancelButton = NO;
}
This seems to be a bug within Xcode. I submitted this error to Apple's bug reporting site, and they've followed up asking for more sample code and use-cases.
Thanks everyone for your attempt at solving this problem.
class CustomSearchBar: UISearchBar {
override func setShowsCancelButton(showsCancelButton: Bool, animated: Bool) {
super.setShowsCancelButton(false, animated: false)
}
}
class CustomSearchController: UISearchController, UISearchBarDelegate {
lazy var _searchBar: CustomSearchBar = {
[unowned self] in
let customSearchBar = CustomSearchBar(frame: CGRectZero)
customSearchBar.delegate = self
return customSearchBar
}()
override var searchBar: UISearchBar {
get {
return _searchBar
}
}
}
Had this problem when using the UISearchBar with UISearchController. I'm using my own cancel button, as the cancel button wasn't showing on iPad with showsCancelButton = YES, now it won't hide on iPhone with showsCancelButton = NO!
The following worked for me.
Set the delegate, and initial value:
- (void)viewDidLoad
{
// ...
self.searchController.searchBar.showsCancelButton = NO;
self.searchController.searchBar.delegate = self;
}
Reset showsCancelButton to NO 0.1s after the text bar begins editing.
#pragma mark - UISearchBarDelegate
- (void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar
{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
self.searchController.searchBar.showsCancelButton = NO;
});
}
If you want to avoid the subclassing, implement
searchController.searchBar.showsCancelButton = false;
in these two delegate methods (Do not forget to assign delegates):
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController
- (void)didPresentSearchController:(UISearchController *)searchController
The first one is called everytime you update the searchBar (Cancel button is visible by default) and the second one is for the first searchBar activation.
Just based on issues I've had before have you tried setting it in:
- (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller
I don't know how to ask this question in your question sorry if this is out of place.
If the cancel button shows up when editing the search field of the search bar you could do the following; subclass the search bar and have it implement the UITextFieldDelegateprotocol:
#interface CustomAlignedSearchBar : UISearchBar<UITextFieldDelegate>
Then implement textFieldDidBeginEditing: and do something like:
- (void)textFieldDidBeginEditing:(UITextField *)textField{
[self setShowsCancelButton:self.cancelButtonShown animated:NO];
}
This will make sure that the cancel button will not show up.
After UISearchDisplayController deprecated in iOS8, Apple give handle search presentation to UISearchControllerDelegate.
so you can override searchBar to hide the Cancel button, like below :
- (void)didPresentSearchController:(UISearchController *)searchController {
[searchController.searchBar setShowsCancelButton:NO];
}
if you need hidden Cancel button from inactive state, you need set searchBar on init :
search = [[UISearchController alloc] initWithSearchResultsController:nil];
[search.searchBar setShowsCancelButton:NO];
On iOS 13.0 and later, UISearchController has this property you can use:
#property (nonatomic) BOOL automaticallyShowsCancelButton API_AVAILABLE(ios(13.0)); // Default YES

How do i correct this warning?

I was searching for a Done button for the Number Pad,then i saw this question:
How to show "Done" button on iPhone number pad
I copied Archie's answer code into mine,and i get 2 warnings in this area:
- (void)textFieldDidBeginEditing:(NSNotification *)note {
[self updateKeyboardButtonFor:[note object]];
}
- (void)keyboardWillShow:(NSNotification *)note {
[self updateKeyboardButtonFor:[self findFirstResponderTextField]];
}
- (void)keyboardDidShow:(NSNotification *)note {
[self updateKeyboardButtonFor:[self findFirstResponderTextField]];
}
The warnings are:
Incompatible Objective-C types initializing 'struct NSNotification *', expected 'struct UITextField *'
How can i correct that? I tried to switch with a UITextField but it all messed up
As BoltClock suggested, it does seem a bit strange that Archie use a delegate method's name as a notification handler. The problem might be stemming from the fact that you must be adopting the UITextFieldDelegate protocol. If you've done so, remove the line observing the notification,
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(textFieldDidBeginEditing:)
name:UITextFieldTextDidBeginEditingNotification
object:nil];
and then edit make the textFieldDidBeginEditing: method while becoming the delegate of the text fields,
- (void)textFieldDidBeginEditing:(UITextField *)textField {
[self updateKeyboardButtonFor:textField];
}
Or alternatively, rename the occurrences of textFieldDidBeginEditing: with some other suitable method name
textFieldDidBeginEditing is not a notification, it is a delegate method. The expected signature is - (void)textFieldDidBeginEditing:(UITextField *)aTextField