NSTokenField not firing action - objective-c

I have an NSTokenField to add tags to an object (a document). I would like to update the object with new tags the moment a token is added to the token field (when a tokenising character is typed). Unfortunately this does not seem to work.
The NSTokenField is connected to an action in my controller but this action method is never called.
I also have a NSTextField connected in the same way to the controller and its action method in the controller is called.
I've also tried this with key value observing:
- (void) awakeFromNib {
[tokenField addObserver:self forKeyPath:#"objectValue" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if([object isEqual:tokenField]){
NSLog(#"Tokens changed");
}
}
but this action is only called when I programatically change the tokens.
How can I be notified when the tokens in the tokenField are changed?

The NSTokenField action selector isn't called the moment a new tag is created. Depending on the setting you've gone with in Interface Builder, it's called either when you hit enter to end editing (Send On Enter Only) , or when you end editing some other way (Send On End Editing). To get the fine control you're after you'll need another approach.
The blue tags that appear when a tokenising character is added to the token field are called text attachments (instances of NSTextAttachment). One way of working out when tags are being added/removed from your token field is to track changes to the number of these objects contained in the token field's underlying attributed string.
To get access to the relevant attributed string you need to get hold of the fieldEditor's layoutManager - the object which ultimately supplies the string that appears in the text-view. Once you've got it, each time you get a controlTextDidChange: message, count up the number of text attachments in the string representation of its attributedString. If the number this time around is greater than the number recorded in the previous count, a tag has just been added.
#import "AppDelegate.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (weak) NSLayoutManager *lm;
#property (nonatomic) NSUInteger tokenCount;
#end
#implementation AppDelegate
// The text in the fieldEditor has changed. If the number of attachments in the
// layoutManager's attributedString has changed, either a new tag has been added,
// or an existing tag has been deleted.
-(void)controlTextDidChange:(NSNotification *)obj {
NSUInteger updatedCount = [self countAttachmentsInAttributedString:self.lm.attributedString];
if (updatedCount > self.tokenCount) {
NSLog(#"ADDED");
self.tokenCount = updatedCount;
} else if (updatedCount < self.tokenCount) {
NSLog(#"REMOVED");
self.tokenCount = updatedCount;
}
}
// About to start editing - get access to the fieldEditor's layoutManager
-(BOOL)control:(NSControl *)control textShouldBeginEditing:(NSText *)fieldEditor {
self.lm = [(NSTextView *)fieldEditor layoutManager];
return YES;
}
// Iterate through the characters in an attributed string looking for occurrences of
// the NSAttachmentCharacter.
- (NSInteger)countAttachmentsInAttributedString:(NSAttributedString *)attributedString {
NSString *string = [attributedString string];
NSUInteger maxIndex = string.length - 1;
NSUInteger counter = 0;
for (int i = 0; i < maxIndex + 1; i++) {
if ([string characterAtIndex:i] == NSAttachmentCharacter) {
counter++;
}
}
return counter;
}
#end

A port of #paul-patterson's code to Swift 3:
override func controlTextDidChange(_ obj: Notification) {
guard let fieldEditor = self.tokenField.currentEditor() as? NSTextView,
let layoutManager = fieldEditor.layoutManager
else { return }
func countAttachments(attributedString: NSAttributedString) -> Int {
let string = attributedString.string as NSString
let maxIndex = string.length - 1
var counter = 0
for i in 0..<maxIndex {
if string.character(at: i) == unichar(NSAttachmentCharacter) {
counter += 1
}
}
return counter
}
let currentCount = countAttachments(attributedString: layoutManager.attributedString())
// cache count or act on it directly
}
Oddly enough, the following does not produce the expected outcome in Swift:
layoutManager.attributedString().string
.split(by: Character(UnicodeScalar(NSAttachmentCharacter)!)).count
Instead, it returns 0 when the user is not typing and 1 when a token is being edited.
let isEditing = layoutManager.attributedString().string
.split(by: Character(UnicodeScalar(NSAttachmentCharacter)!)).count == 1
With the combination of both approaches, you could write a custom "did add/remove token" callback using a state machine. (I don't think this is a very safe way to implement that, though.)
Track the count of tokens with countAttachments(attributedString:).
Use isEditing to check ...
if the user started adding a new note (new count > old count && isEditing == true)
if the user started editing an existing note (new count == old count && isEditing == true)
if the user finished a token (oldIsEditing == true && newIsEditing == false)

Related

How to set up a pipeline for dynamically created RACSignals - dynamic Form application - Reactive Cocoa

I'm working on a Form which consists of dynamically created Fields, and I'd like each Field to have a method that returns a RACSignal which sends a nextEvent each time the Field's value changes.
The payload of the nextEvent should include BOOL that indicates weather the Field is complete (which wraps up some validation logic along the way).
The best I can illustrate my intent so far is with the pseudo-code below:
// Inside Form.m
// Create an array of signals from all the fields in the form
#property(nonatomic) BOOL *formComplete;
-(void)setUpSignals {
NSMutableArray *fieldCompleteSignals = [NSMutableArray arrayWithCapacity:0];
for (Field *field in self.fields)
{
RACSignal *completeSignal = [field complete];
[fieldCompleteSignals addObject:completeSignal];
}
// ? S.O. Help here
[RACSignal combineLatest:self.fieldCompleteSignals reduce:^id {
self.formComplete = ?? // combined BOOL values, if any are NO, then returns NO- if all are YES, returns YES
return ??; // when all the BOOL values returned from the fields are YES, then the form is complete
}];
}
// Inside Field.m
#property(nonatomic, copy) NSString *value;
-(RACSignal *)fieldComplete
{
return [RACObserve(self, value) map:^id(id value) {
return #([self validateCurrentValue]);
}];
}
-(BOOL)validateCurrentValue
{
// snazzy validation logic here
return YES | NO;
}
This Form is dynamically created from an XML document, and I'd like to set up a pipeline that handles dynamically created RACSignals.
What I'd like to end up with is a stream of BOOL values, collected from each Field, and reduced to a single formIsComplete BOOL- I'm wondering if I'm on the right track to setting up this pipeline if there's a better way (or other examples) of handling this?
Fo your first question, you can use map along with RACTuple:
#property (assign, nonatomic) BOOL formComplete;
- (void)setUpSignals {
NSMutableArray *fieldCompleteSignals =
[NSMutableArray arrayWithCapacity:self.fields.count];
for (Field *field in self.fields) {
RACSignal *completeSignal = [field complete];
[fieldCompleteSignals addObject:completeSignal];
}
RAC(self, formComplete) =
[[RACSignal
combineLatest:self.fieldCompleteSignals]
map:^id (RACTuple *allValues) {
return #([allValues.rac_sequence all:^BOOL(id value) {
return [value boolValue];
}]);
}];
}
RACTuple is an ordered collection and conforms to NSFastEnumeration protocol (To learn more about RACTuple see RACTuple reference).
For your second question, yes boxing primitive types is appropriate. Say that you would like to enable some button only when some other BOOL is YES, you can do it like this:
RAC(self.button, enabled) = RACObserve(self, formComplete);

Updating UILabel and clearing it?

I have a UILabel that I start at "0" and then as my game progresses I call the below code to add "1" to the score each time a button is pressed. This all works fine.
playerOneScore.text = [NSString stringWithFormat:#"%d",[playerOneScore.text intValue]+1];
My problem is that when the game ends and the user presses the "Replay" button. This replay button sets the label back to "0" by calling this.
playerOneScore.text = #"0";
But then when the game progresses my label jumps from "0" to where it had initially left off + 1. What am I doing wrong here? Not making sense.
Any help would be very grateful! Thank you!
More code requested (simplified):
- (void)viewDidLoad
{
playerOneScore.text = #"0";
}
-(IBAction)touchDown {
if (playerOneTurn == YES) {
if (button1.touchInside == YES) {
playerOneScore.text = [NSString stringWithFormat:#"%d",[playerOneScore.text intValue]+1];
}
}
}
-(void) replayGame {
playerOneScore.text = #"0";
}
Not sure what's going on, but I would suggest using a separate NSInteger property to track the score. Then override the property setter to update the label text.
Add the property to your interface or a private category:
#property (nonatomic, assign) NSInteger score;
Then override the setter:
- (void)setScore:(NSInteger)value {
_score = value;
playerOneScore.text = [NSString stringWithFormat:#"%d", value];
}
Then in your action, update the score and label using the new property:
-(IBAction)touchDown {
if (playerOneTurn == YES) {
if (button1.touchInside == YES) {
self.score += 1;
}
}
}
Note that I haven't compiled or tested this code, but it should be close.
EDIT: fixed typo in setter

Dynamic Accessibility Label for CALayer

How do I make a CALayer accessible? Specifically, I want the layer to be able to change its label on the fly, since it can change at any time. The official documentation's sample code does not really allow for this.
The following assumes that you have a superview whose layers are all of class AccessableLayer, but if you have a more complex layout this scheme can be modified to handle that.
In order to make a CALayer accessible, you need a parent view that implements the UIAccessibilityContainer methods. Here is one suggested way to do this.
First, have each layer own its UIAccessibilityElement
#interface AccessableLayer : CALayer
#property (nonatomic) UIAccessibilityElement *accessibilityElement;
#end
now in its implementation, you modify the element whenever it changes:
#implementation AccessableLayer
... self.accessibilityElement.accessibilityLabel = text;
#end
The AccessableLayer never creates the UIAccessibilityElement, because the constructor requires a UIAccessibilityContainer. So have the super view create and assign it:
#pragma mark - accessibility
// The container itself is not accessible, so return NO
- (BOOL)isAccessibilityElement
{
return NO;
}
// The following methods are implementations of UIAccessibilityContainer protocol methods.
- (NSInteger)accessibilityElementCount
{
return [self.layer.sublayers count];
}
- (id)accessibilityElementAtIndex:(NSInteger)index
{
AccessableLayer *panel = [self.layer.sublayers objectAtIndex:index];
UIAccessibilityElement *element = panel.accessibilityElement;
if (element == nil) {
element = [[UIAccessibilityElement alloc] initWithAccessibilityContainer:self];
element.accessibilityFrame = [self convertRect:panel.frame toView:[UIApplication sharedApplication].keyWindow];
element.accessibilityTraits = UIAccessibilityTraitButton;
element.accessibilityHint = #"some hint";
element.accessibilityLabel = #"some text";
panel.accessibilityElement = element;
}
return element;
}
- (NSInteger)indexOfAccessibilityElement:(id)element
{
int numElements = [self accessibilityElementCount];
for (int i = 0; i < numElements; i++) {
if (element == [self accessibilityElementAtIndex:i]) {
return i;
}
}
return NSNotFound;
}

OBJ C - How to get the textfield name from a sender

I have several textfields, each with tags, that I want to individually add to an array. I need to figure out which field it is coming from before I add it. I would like to use the same method for all of them rather than have a method for each textfield.
Is it possible to get the variable name of the textfield from the sender? If they were button I could use [sender currentTitle], but I don't know how to get an identifier from the textfield.
I am thinking of something like this:
- (void)makeItSo:(id)sender
{
NSString * senderName = (UITextField*)[sender stringValue] ;
if ([senderName isEqual: #"name"] )
-- add name to array
else if ([senderName isEqual: #"address"] )
-- add address to array
}
If you give each text field a tag, then use the tag:
- (void)makeItSo:(UITextField *)sender {
if (sender.tag == 1) {
// the name text field
} else if (sender.tag == 2) {
// the address text field
}
}
This assumes you have set the tag property for each text field either in IB or in code.
It could be useful to define constants for each tag so you end up with something that is easier to read:
#define kNameTextField 1
#define kAddressTextField 2
- (void)makeItSo:(UITextField *)sender {
if (sender.tag == kNameTextField) {
// the name text field
} else if (sender.tag == kAddressTextField) {
// the address text field
}
}
If you have outlets or instance variables then you can do:
- (void)makeItSo:(UITextField *)sender {
if (sender == _nameTextField) {
// the name text field
} else if (sender == _addressTextField) {
// the address text field
}
}
where _nameTextField and _addressTextFields are the ivars for the text fields.
Is it possible to get the variable name of the textfield from the sender?
No, unless it's an instance variable, in which case you can, but you better don't.
I don't know how to get an identifier from the textfield
As always, it's enough to read the documentation as use the tag property of UIView:
if ([sender tag] == SOME_CUSTOM_PRESET_VALUE) {
// do stuff
}
For example you may have these text fields as ivars:
#property (weak) UITextField* textField1; // tag=1
#property (weak) UITextField* textField2; // tag=2
...
#property (weak) UITextField* textFieldN; // tag=N
When you receive an action you simply do:
- (void)makeItSo:(id)sender
{
// This is the searched text field
UITextField* textField= [self valueForKey: [NSString stringWithFormat: #"textField%d",sender.tag] ];
}
But at this point why not using a single property which is an array with N text fields, instead of N properties?

How can I force NSTextField to only allow numbers?

In Interface Builder I’ve created a textfield and stuck an NSNumberFormatter into the cell. However, the user can still type text into the textfield. Is there any way to restrict the input to numbers only using interface builder? I thought that was the point of the NSNumberFormatter.
Every formatter has this method:
- (BOOL) getObjectValue: (id*) object forString: (NSString*) string
errorDescription: (NSError**) error;
This is valid for every formatter.
If the string is not valid it returns false and the object passed as argument (dereferenced) will be nil.
So instead of dropping a number formatter to the text field, use your own formatter as instance variable.Observe the text field implementing the delegate protocol so that at every change of the text you can be notified.
Then invoke this method:
NSNumber* number;
BOOL success=[formatter getObjectValue: &number forString: [myTextField stringValue] errorDescription:nil];
At this point if success is false (or check if number is nil), there is an invalid string in the text field, so do the action that is more appropriate for you (maybe delete the entire string, or display 0.0 as value).
There is also another method:
- (BOOL) isPartialStringValid : (NSString*) partialString: (NSString*) partialString
errorDescription: (NSString*) error;
This way you can know if a partial string is valid.For example with the scientific notation "1e" is not valid, but is partially valid because the user may add 1 and it will become "1e1".
Create an NSNumberFormatter subclass and put this in the implementation. In your code, set the YKKNumberFormatter as the formatter for the NSTextField/UITextField.
#implementation YKKNumberFormatter
- (BOOL)isPartialStringValid:(NSString *)partialString newEditingString:(NSString **)newString errorDescription:(NSString **)error {
// Make sure we clear newString and error to ensure old values aren't being used
if (newString) { *newString = nil;}
if (error) {*error = nil;}
static NSCharacterSet *nonDecimalCharacters = nil;
if (nonDecimalCharacters == nil) {
nonDecimalCharacters = [[[NSCharacterSet decimalDigitCharacterSet] invertedSet] retain];
}
if ([partialString length] == 0) {
return YES; // The empty string is okay (the user might just be deleting everything and starting over)
} else if ([partialString rangeOfCharacterFromSet:nonDecimalCharacters].location != NSNotFound) {
return NO; // Non-decimal characters aren't cool!
}
return YES;
}
#end