How to enable an UISwitch while disabling other two simultaneously - objective-c

I have 3 UISwitches in my View Controller and each one identifies gender ("Woman, Male, No Gender") that user has to choose.
The behavior I would like to implement is that, when the user taps on a switch to select the gender, the others two have to be disabled simultaneously.
And once the switch is selected, the "Create Profile" button is activated. (screenshots attached)
I'm not able to implement the if condition (or alternatively a switch condition)to do that.
Please help.
This is the code I implemented:
- (IBAction)checkGenderSwitch:(id)sender {
if ([self.femaleGenderSwitch isOn] && (![self.maleGenderSwitch isOn] && ![self.noGenderSwitch isOn])) {
[self enableCreateProfileButton];
} else if ([self.maleGenderSwitch isOn] && (![self.femaleGenderSwitch isOn] && ![self.noGenderSwitch isOn])) {
[self enableCreateProfileButton];
} else if ([self.noGenderSwitch isOn] && (![self.femaleGenderSwitch isOn] && ![self.maleGenderSwitch isOn])) {
[self enableCreateProfileButton];
}else{
[self disableCreateProfileButton];
}
}
Thanks.

Let's reformulate your need:
Every time you change a switch value, you do (in pseudo code):
if (male isOn && female isOff && noGender isOff) { enableButton }
else if (male isOff && female isOn && noGender isOff) { enableButton}
else if (male isOff && female isOff && noGender isOn)) { enableButton }
else { disableButton }
Which could be translated in:
if (onlyMale isOn) { enableButton }
else if (onlyFemale isOn) { enableButton}
else if (onlyNoGender isOn) { enableButton }
else { disableButton }
only... isOn? What about putting them into a NSArray, and count the number of isOn? That should do the trick no? only... isOn is for count = 0 where isOn == true.
Which could be then coded:
NSArray *switches = #[self.femaleGenderSwitch, self.maleGenderSwitch, self.noGenderSwitch];
NSUInteger numberOfSwitchesOn = 0;
for (UISwitch *aSwitch in switches) {
if ([aSwitch isOn]) {
numberOfSwitchesOn += 1;
}
}
if (numberOfSwitchesOn == 1) {
[self enableCreateProfileButton];
} else {
[self disableCreateProfileButton];
}
Now, if you want that when you switchOn a Switch, you switchOff the other two (if needed), which in our case would only be one, it can be done with:
NSArray *switches = #[self.femaleGenderSwitch, self.maleGenderSwitch, self.noGenderSwitch];
BOOL hasASwitchOn = NO;
//We iterate over the switches
for (UISwitch *aSwitch in switches) {
//If the newly set is set to on, we need to disable the other (the one which aren't the same as the sender)
if ((UISwitch *)[sender isOn] && ![sender isEqual:aSwitch]) {
[aSwitch setOn:NO];
hasASwitchOn = YES;
} else if ([aSwitch isOn]) { //Else, we still count the number of isOn
hasASwitchOn = YES;
}
}
if (hasASwitchOn) {
[self enableCreateProfileButton];
} else {
[self disableCreateProfileButton];
}

As has been said in a comment, the disablement requirement is wrong. All three switches need to be enabled at all times. The rule should be simply that only one switch at a time can be on.
The key here is that when you tap a switch, and the IBAction method is called, the sender is that switch.
So just keep a list references to all three switches and if the sender is now on, cycle thru the list and turn off each switch that is not the sender.
(Or use a three part UISegmentedControl which does all this for you automatically; it lets the user choose exactly one of three.)

I would use the tag property of UIView for your three switches, giving each of the three its own, unique tag value: for instance, 0, 1, 2. (You can set that up in Interface Builder, or in code — whichever you prefer.) This makes it trivial to distinguish the three switches.
Then, create one extra property, genders, an array of your three switches:
#interface ViewController ()
#property (nonatomic, weak) IBOutlet UISwitch* femaleGenderSwitch;
#property (nonatomic, weak) IBOutlet UISwitch* maleGenderSwitch;
#property (nonatomic, weak) IBOutlet UISwitch* noGenderSwitch;
#property (nonatomic, strong) NSArray* genders;
#end
In -viewDidLoad set that up:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.genders =
#[[self femaleGenderSwitch], [self maleGenderSwitch], [self noGenderSwitch]];
}
Finally, you can simplify your logic in your action method like this:
- (IBAction)checkGenderSwitch:(id)sender
{
if ([sender isOn]) {
for (id aSwitch in [self genders]) {
if ([aSwitch tag] != [sender tag]) {
[aSwitch setOn:NO animated:YES];
}
}
[self enableCreateProfileButton];
}
else {
[self disableCreateProfileButton];
}
}

Related

Bindings does not update NSTextField

I use bindings to NSObjectController within XIB. When I set new content object of NSObjectController the only textfield value which doesn't change is the one that has first responder. Model changes without an issue.
If I don't use custom getter/setter the textfield that has firstResponder (isBeingEdited) changes without an issue.
What's wrong with my KVC, KVO?
My custom getter/setter is below pic.
PS: I don't want to make window a first responder before I change content object to make it work.
static const CGFloat MMsInOneInch = 25.4;
static const CGFloat inchesInOneMM = 0.0393700787402;
- (void)setPaperWidth:(CGFloat)paperWidth
{
[self willChange];
CGFloat newWidth = paperWidth * [self conversionKoeficientToDefaultUnitType];
if (!_isChangingPaperSize) {
if (self.paperSize == PPPaperSizeA4 && fabs(newWidth - widthSizeOfA4) > 0.001) {
[self setPaperSize:PPPaperSizeCustom];
}
if (self.paperSize == PPPaperSizeUSLetter && fabs(newWidth - widthSizeOfUSLetter) > 0.001 ) {
[self setPaperSize:PPPaperSizeCustom];
}
}
[self willChangeValueForKey:#"paperWidth"];
_paperWidth = newWidth;
[self didChangeValueForKey:#"paperWidth"];
[self didChange];
}
- (CGFloat)conversionKoeficientToDefaultUnitType
{
if ([self defaultUnitType] == [self unitType]) {
return 1;
}
if ([self defaultUnitType] == PPPrintSettingsUnitTypeMM) {
return MMsInOneInch;
}
if ([self defaultUnitType] == PPPrintSettingsUnitTypeInch) {
return inchesInOneMM;
}
return 1;
}
- (CGFloat)paperWidth
{
return _paperWidth / [self conversionKoeficientToDefaultUnitType];
}
I forgot that I use NSNumberFormatter with min/max value which where blocking NSTextField to update.

disabling rows on condition on xlform

I have a section set up using xlform where there is a bool field and then a text field under it. I want to disable the text field if the bool is chosen. I tried with the following, but it did not work
if ([[self.form valueForKey:#"pay"] isEqualToValue:#(1)]){
row.disabled = YES;
} else {
row.required = YES;
}
[section addFormRow:row];
Any suggestions? Here is the documentation, spent a bunch of time search without being able to find the answer.
Edit: I'm starting to think that the values of the dictionary don't get updated dynamically. Which is odd because the dictionary can be accessed in other parts of the view controller at any time.
Edit
- (void)formRowDescriptorValueHasChangedTwo:(XLFormRowDescriptor *)formRow value:(id)value
{
[value isEqual:[self.formValues valueForKey:#"pay"]];
if([formRow.tag isEqualToString:#"pay"] && [value isEqual:[NSNumber numberWithBool:YES]])
{
self.price.disabled = NO;
self.price.required = YES;
[self.tableView reloadRowsAtIndexPaths:#[[self.form indexPathOfFormRow:self.price]]
withRowAnimation:UITableViewRowAnimationNone];
}
}
I got it working, but I have a new problem.
Here is the code
- (void)formRowDescriptorValueHasChanged:(XLFormRowDescriptor *)formRow oldValue:(id)oldValue newValue:(id)newValue
{
if([formRow.tag isEqualToString:#"now"] && [newValue isEqual:[NSNumber numberWithBool:YES]])
{
self.time.disabled = YES;
[self.tableView reloadRowsAtIndexPaths:#[[self.form indexPathOfFormRow:self.time]]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
if([formRow.tag isEqualToString:#"pay"] && [newValue isEqual:[NSNumber numberWithBool:YES]])
{
self.price.disabled = NO;
self.price.required = YES;
[self.tableView reloadRowsAtIndexPaths:#[[self.form indexPathOfFormRow:self.price]]
withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
When I click on the first row (now), it disables the corresponding row fine, but when I click on the second row (pay), it makes the row that now disables reappear AND it makes the row that I want to appear under pay to disappear.
Edit Got it to work by changing the animation to UITableViewRowAnimationNone
Here's how you can do it using XLFormDescriptorDelegate. This will change the state as the field is toggled on the form.
#interface SomeClass() <XLFormDescriptorDelegate>
#end
#implementation SomeClass
- (void)formRowDescriptorValueHasChanged:(XLFormRowDescriptor *)formRow oldValue:(id)oldValue newValue:(id)newValue
{
if([formRow.tag isEqualToString:#"boolFieldTag"] && [newValue boolValue])
{
self.otherRow.disabled = YES;
[self.tableView reloadRowsAtIndexPaths:#[[self.form indexPathOfFormRow:self.otherRow]]
withRowAnimation:UITableViewRowAnimationNone];
}
}
#end
In this example I set the row as a property when I originally added it to the form. Alternatively you can just use: [self.form formRowWithTag:#"someTag"]

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.

Anyway to make a (wrapping) NSTextField write a carriage return upon pressing return key?

I want to use a wrapping text field that can potentially contain carriage returns in my app. Is there any way to force the NSTextField object to write a carriage return into the text area instead of sending its action to its target when the Return key is pressed?
This is covered in Technical Q&A QA1454, which also enumerates reasons why one would use NSTextField instead of NSTextView in this case.
You can implement the following method in the text field delegate:
- (BOOL)control:(NSControl*)control
textView:(NSTextView*)textView
doCommandBySelector:(SEL)commandSelector
{
BOOL result = NO;
if (commandSelector == #selector(insertNewline:))
{
// new line action:
// always insert a line-break character and don’t cause the receiver
// to end editing
[textView insertNewlineIgnoringFieldEditor:self];
result = YES;
}
return result;
}
Okay, I figured out one way to do it, but this very well may not be the best (or even a good) way. I subclassed NSTextField, and overrode -textShouldEndEditing: like so:
-(BOOL)textShouldEndEditing:(NSText *)textObject {
NSEvent * event = [[NSApplication sharedApplication] currentEvent];
if ([event type] == NSKeyDown && [event keyCode] == 36) {
[self setStringValue:[[self stringValue] stringByAppendingString:#"\n"]];
return NO;
}
else {
return [super textShouldEndEditing:textObject];
}
}
I found a combination of Sean and Bevarious worked best for me. Sean's answer assumes that the new line is always wanted to be added to the end (instead of for instance where the user's cursor is placed).
-(BOOL)textShouldEndEditing:(NSText *)textObject
{
NSEvent * event = [[NSApplication sharedApplication] currentEvent];
if ([event type] == NSKeyDown && [event keyCode] == 36)
{
[textObject insertNewlineIgnoringFieldEditor:nil];
return NO;
}
else
{
return [super textShouldEndEditing:textObject];
}
}
Swift version:
override func textShouldEndEditing(textObject: NSText) -> Bool {
let event = NSApplication.sharedApplication().currentEvent
if event?.type == NSEventType.KeyDown && event?.keyCode == 36 {
self.stringValue = self.stringValue.stringByAppendingString("\n")
return false
} else {
return super.textShouldEndEditing(textObject)
}
}