Formatting UITextField as a time hh:mm - objective-c

I want a UITextField to correctly format an inputted number as a time, adding minutes using "shouldChangeCharactersInRange" to obtain this result every time a new number is pressed:
i.e.:
00:00
00:08 = 8 pressed
01:23 = 8 -> 3 pressed
08:30 = 8 -> 3 -> 0 pressed
The UITextField is a subview into a Custom UITableViewCell.
I can't figure out, thanks in advance.

Simple
in the H declare:
#interface ViewController : UIViewController
{
int hh;
int mm;
int ss;
}
#property (strong, nonatomic) IBOutlet UILabel *outputLabel;
#end
In the M
-(void)generateText
{
NSString *hhString;
NSString *mmString;
NSString *ssString;
if (hh < 10){
hhString = [NSString stringWithFormat:#"0%d",hh];
} else {
hhString = [NSString stringWithFormat:#"%d",hh];
}
//
if (mm < 10){
mmString = [NSString stringWithFormat:#"0%d",mm];
} else {
mmString = [NSString stringWithFormat:#"%d",mm];
}
if (ss < 10){
ssString = [NSString stringWithFormat:#"0%d",ss];
} else {
ssString = [NSString stringWithFormat:#"%d",ss];
}
NSString *outputText = [NSString stringWithFormat:#"%#:%#:%#",hhString,mmString,ssString];
NSLog(#"output string = %#",outputText);
outputLabel.text = outputText;
}
-(IBAction)addHH:(id)sender
{
hh = hh +1;
[self generateText];
}
-(IBAction)addMM:(id)sender
{
mm = mm +1;
[self generateText];
}
-(IBAction)addSS:(id)sender
{
ss = ss +1;
[self generateText];
}
In Interface builder use 3 buttons to activate the appropriate IBActions

http://developer.apple.com/library/ios/#documentation/uikit/reference/UITextFieldDelegate_Protocol/UITextFieldDelegate/UITextFieldDelegate.html
Use this delegate method to change the text field accordingly
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
// You should already have set some string in the text field
if (/*user input is between 0-9*/) {
NSString *text = /* Format your text */
textFiled.text = text;
// Return NO to disable editing done by the user
// Not required to return here if you always return NO;
return NO;
}
// Return NO if you do not want to let user make any changes, otherwise YES
return YES;
}
I have tested it, but changing the textField.text inside this method may call this recursively and fail. If this is the case, use a flag to keep track of what changes are made by you and what by the user. Return YES if the flag is ON and NO otherwise.

Related

NSTokenField not firing action

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)

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

After xcode crash some buttons have either empty titles or are partially truncated

This seems to be a partial repost of XCode 4: some button titles not visible in iOS simulation but their question wasn't answered.
Please be gentle, I'm very new to this.
I've implemented a stack-based calculator program as per the stamford lecture series, but some of my buttons show as either empty or half truncated in the simulator. The same buttons will do it every time I run the sim, but if I move any buttons around it changes which ones are affected. It's usually the bottom ones.
First example here: http://i.imgur.com/YpC1f.png - see how the bottom row of buttons don't display correctly? If I make any one of those buttons taller, it will show with no title but all the other four buttons will then show correctly.
I thought it might be too close to the bottom, or those buttons were broken, or similar. So I deleted the whole bottom row, made everything smaller, and then recreated those buttons and now I get four buttons with blank titles and two truncated: http://i.imgur.com/kM1Rb.png
Note that the buttons all still work as expected, it's just the display that isn't right.
Am I doing something wrong? Any advice appreciated.
EDIT: Full code from the controller:
#import "CalculatorViewController.h"
#import "CalculatorBrain.h"
#interface CalculatorViewController()
#property (nonatomic) BOOL userIsInTheMiddleOfEnteringANumber;
#property (nonatomic, strong) CalculatorBrain *brain;
- (void)updateStackDisplay:(NSString *)value;
#end
#implementation CalculatorViewController
#synthesize display = _display;
#synthesize stackDisplay = _stackDisplay;
#synthesize userIsInTheMiddleOfEnteringANumber = _userIsInTheMiddleOfEnteringANumber;
#synthesize brain = _brain;
- (CalculatorBrain *)brain {
if (!_brain) _brain = [[CalculatorBrain alloc] init];
return _brain;
}
- (void)updateStackDisplay:(NSString *)value {
// if there's nothing sent so far, just initialise it
if (self.stackDisplay.text.length == 0) {
self.stackDisplay.text = value;
return;
}
// This part is a little confusing. Extra assignment asked for = to be added to the end of the stack label if an operation was pressed.
// Here I check for = at the end of the label and remove it so it's only displayed once. If "=" is being passed (done by the operation itself), it will be added back on right at the end of this function.
if ([self.stackDisplay.text rangeOfString:#"="].location == (self.stackDisplay.text.length - 1)) {
// .location starts at zero, .length doesn't.
self.stackDisplay.text = [self.stackDisplay.text substringToIndex:[self.stackDisplay.text length]-1];
}
// If we add a space after remove the = we'll end up with double space. Hence the else if, not if.
else if (!self.userIsInTheMiddleOfEnteringANumber) {
self.stackDisplay.text = [self.stackDisplay.text stringByAppendingString:#" "];
}
self.stackDisplay.text = [self.stackDisplay.text stringByAppendingString:value];
}
- (IBAction)digitPressed:(UIButton *)sender {
NSString *digit = sender.currentTitle;
if ([digit isEqualToString:#"."]) {
if ([self.display.text rangeOfString:#"."].location != NSNotFound) {
return;
}
else {
if (!self.userIsInTheMiddleOfEnteringANumber) digit = #"0.";
}
}
[self updateStackDisplay:digit];
if (self.userIsInTheMiddleOfEnteringANumber) {
self.display.text = [self.display.text stringByAppendingString:digit];
}
else {
self.display.text = digit;
self.userIsInTheMiddleOfEnteringANumber = YES;
}
}
- (IBAction)operationPressed:(UIButton *)sender {
if (self.userIsInTheMiddleOfEnteringANumber) [self enterPressed];
[self updateStackDisplay:sender.currentTitle];
[self updateStackDisplay:#"="];
double result = [self.brain performOperation:sender.currentTitle];
NSString *resultString = [NSString stringWithFormat:#"%g", result];
self.display.text = resultString;
}
- (IBAction)enterPressed {
if (self.userIsInTheMiddleOfEnteringANumber) {
self.userIsInTheMiddleOfEnteringANumber = NO;
}
[self.brain pushOperand:[self.display.text doubleValue]];
}
- (IBAction)clearPressed {
self.stackDisplay.text = #"";
[self.brain clearStack];
self.userIsInTheMiddleOfEnteringANumber = NO;
self.display.text = #"0";
}
#end
I got a maybe similar error, where some UIButtons had empty text, although by outputting titleLabel.text got the correct text value. I deleted the Buttons and added them identically to what it has before been in the storyboard. Then it worked.

Trouble with NSString: using a controller to set an NSTextField?

I am using Xcode 4 writing a simple program to generate text based on user input. Im reading from two text fields successfully, but I am unable to send the result to a separate NSTextField. I'm confident I've made all the right connections in IB, and I'm sending the NSTextField the setStringValue method. I have created a ScaleGenerator object that I believe may be improperly creating the string itself. My Controller header looks like this:
#import <Cocoa/Cocoa.h>
#interface Controller : NSObject
{
IBOutlet NSTextField * notesField;
IBOutlet NSTextField * midiField;
IBOutlet NSTextField * resultField;
IBOutlet id scalegenerator;
}
- (IBAction) generate: (id) sender;
#end // Controller
The Controller implementation looks like this:
#import "Controller.h"
#import "ScaleGenerator.h"
#import <Foundation/foundation.h>
#implementation Controller
- (IBAction) generate: (id) sender
{
int midinote;
int notes;
NSString * scale;
midinote = [midiField intValue];
if(midinote < 0 || midinote > 127) {
NSRunAlertPanel(#"Bad MIDI", #"That MIDI note is out of range! (0 - 127)", #"OK", nil, nil);
return;
}
notes = [notesField intValue];
if(notes < 2 || notes > 24) {
NSRunAlertPanel(#"Bad Scale Size", #"You must have at least two notes in your scale, and no more than 25 notes!", #"OK", nil, nil);
return;
}
scale = [scalegenerator generateScale: midinote and: notes];
[resultField setStringValue:scale];
}
#end
My ScaleGenerator code looks like this:
#import "ScaleGenerator.h"
#import <math.h>
#implementation ScaleGenerator
- (NSMutableString *) generateScale: (int) midinote and: (int) numberOfNotes
{
double frequency, ratio;
double c0, c5;
double intervals[24];
int i;
NSMutableString * result;
/* calculate standard semitone ratio in order to map the midinotes */
ratio = pow(2.0, 1/12.0); // Frequency Mulitplier for one half-step
c5 = 220.0 * pow(ratio, 3); // Middle C is three semitones above A220
c0 = c5 * pow(0.5, 5); // Lowest MIDI note is 5 octaves below middle C
frequency = c0 * pow(ratio, midinote); // the first frequency is based off of my midinote
/* calculate ratio from notes and fill the frequency array */
ratio = pow(2.0, 1/numberOfNotes);
for(i = 0; i < numberOfNotes; i++) {
intervals[i] = frequency;
frequency *= ratio;
}
for(i = 0; i < n; i++){
[result appendFormat: #"#%d: %f", numberOfNotes + 1, intervals[i]];
}
return (result);
}
#end // ScaleGenerator
My ScaleGenerator object has one function, which is where I believe I may be messing things up. What kind of string can I iteratively append formatted text to?? And what method do I call??
You have not allocated/initialized your NSMutableString. Therefore the message appendFormat: goes to nil. Replace the line
NSMutableString * result;
with
NSMutableString * result = [[NSMutableString alloc] init];

NSString from a class returning null after assignment?

I have a problem with assigning values to a string and then getting them back. The string is a property of a class, because I need it to carry over to other view controllers. (I have tried Core Data, but it didn't work either and this seemed simpler.)
The class for the strings is this:
.h
#interface GameInformation : NSObject
#property (nonatomic, retain) NSString *word;
#property (nonatomic, retain) NSString *mode;
#end
.m
#implementation GameInformation
#synthesize mode = _mode;
#synthesize word = _word;
#end
Pretty simple. The relevant code in app delegate:
GameInformation *gameInfo = [GameInformation alloc].init;
optionsView.gameInfo = gameInfo;
oneBox.gameInfo = gameInfo;
twoBox.gameInfo = gameInfo;
And so on, until I've got all the controllers covered. Options view is where I set the value for the strings, and test those values.
GameInformation *info = [self gameInfo];
UISegmentedControl *selectMode = [self mode];
UISegmentedControl *gameWord = [self word];
if (selectMode.selectedSegmentIndex == 0)
{
info.mode = #"regular";
} else if (selectMode.selectedSegmentIndex == 1) {
info.mode = #"wordTap";
}
if (gameWord.selectedSegmentIndex == 0)
{
info.word = #"regular";
} else if (gameWord.selectedSegmentIndex == 1) {
info.word = #"hat";
} else if (gameWord.selectedSegmentIndex == 2) {
info.word = #"dog";
} else if (gameWord.selectedSegmentIndex == 3) {
info.word = #"book";
} else if (gameWord.selectedSegmentIndex == 4) {
info.word = #"bus";
} else if (gameWord.selectedSegmentIndex == 5) {
info.word = #"cup";
} else if (gameWord.selectedSegmentIndex == 6) {
info.word = #"pig";
}
NSLog(#"%#", info.mode);
NSLog(#"%#", info.word);
And the log comes out as null when those are passed over. I have tried [info setWord:#"regular"]; and [info setMode#"regular"]; but those didn't work either.
I haven't tried using the strings in the one box, two box, etc. controllers yet, because the test return null.
So. What am I doing wrong? Or am I barking up the wrong tree? And, like I said earlier, trying to use core data for this didn't work and this seemed like a simpler approach.
Thanks in advance!
EDIT:
Thank you all for the quick comments! I did a NSLog on info and it is indeed null. I also changed the declarations to copy instead of retain, and changed the dot notation in the alloc statement. The alloc statement is in the app delegate's didFinsihLaunchingWithOptions. Is that the wrong place?
Thanks again for the help!