How does a UIButton store the different UI states - ios

I was looking into adding a UILabel property into a UIButton as a subview, matching the functionality of the titleLabel property currently present, and giving it all the different states of the button (UIControlStateNormal, UIControlStateHighlighted, etc.).
I started playing around with storing the states in a NSDictionary using the state (wrapped in a NSNumber) as the key to the dictionary with the values being the text, text colour and shadow colour. But I'm not sure this would work correctly based on the various different button state combinations.
So far I've got a the following methods:
-(void)setSelected:(BOOL)selected
{
super.selected = selected;
[self stateUpdated];
}
-(void)setHighlighted:(BOOL)highlighted
{
super.highlighted = highlighted;
[self stateUpdated];
}
-(void)setEnabled:(BOOL)enabled
{
super.enabled = enabled;
[self stateUpdated];
}
-(void)stateUpdated
{
[self updateValueLabel];
}
-(void)setValueText:(NSString *)text forState:(UIControlState)state
{
NSMutableDictionary *stateValues = self.customStates[#state];
// If we haven't set the state before create a new place to store them
if (!stateValues) {
stateValue = [NSMutableDictionary dictionary];
self.customStates[#state] = stateValues;
}
if (text) {
stateValues[#"text"] = text;
} else {
[stateValues removeObjectForKey:#"text"];
}
[self updateValueLabel];
}
-(void)setValueTextColor:(UIColor *)textColor forState:(UIControlState)state
{
NSMutableDictionary *stateValues = self.customStates[#state];
// If we haven't set the state before create a new place to store them
if (!stateValues) {
stateValues = [NSMutableDictionary dictionary];
self.customStates[#state] = stateValues;
}
if (text) {
stateValues[#"textColor"] = textColor;
} else {
[stateValues removeObjectForKey:#"textColor"];
}
[self updateValueLabel];
}
-(void)updateValueLabel
{
NSMutableDictionary *currentStateValues = self.customStates[#self.state];
// Set the new values from the current state
self.valueLabel.text = currentStateValues[#"text"];
self.valueLabel.textColor = currentStateValues[#"textColor"];
}
I have the valueLabel as a property on the UIButton subclass.
The different attributes set for the different states don't seem to be taking effect. If I set them all manually with different bitmasks its fine, but I want to emulate the default fallback states as UIButton does for its properties.
So instead of having to set each one manually:
[button setValueText:#"Normal" forState:UIControlStateNormal];
[button setValueText:#"Normal" forState:UIControlStateHighighted];
I want to be able just to set the title once:
[button setValueText:#"Normal" forState:UIControlStateNormal];
And it show for all states.
Thanks in advance!

Create a getter valueTextForState:, like Apple does with titleForState:. The documentation for this method's return value reads:
The title for the specified state. If no title has been set for the specific state, this method returns the title associated with the UIControlStateNormal state.
So, let's do this as well for your property:
- (NSString *)valueTextForState:(UIControlState)state
{
NSString *result;
NSDictionary *currentStateValues;
currentStateValues = self.customStates[#(state)];
result = currentStateValues[#"text"];
if (!result && state != UIControlStateNormal) {
// Fall back to normal state.
currentStateValues = self.customStates[#(UIControlStateNormal)];
result = currentStateValues[#"text"];
}
return result;
}
You would do the same for your color. Then your updateValueLabel would look like this:
-(void)updateValueLabel
{
// Set the new values from the current state
self.valueLabel.text = [self valueTextForState:self.state];
self.valueLabel.textColor = [self valueColorForState:self.state];
}

Related

Expected Identifier

Please can anyone tell me why this piece of code isn't working? I've got a dictionary which contains UIViews with tables inside associated with the keys which are the names of the corresponding buttons (there are a lot of them). So what I actually want to do is to change the view visibility on the corresponding button click. But the issue is that the expression to do that is not accepted by Xcode and I get the Expected Identifier error.
- (IBAction)choosingButtonClicked:(id)sender {
if ([sender currentTitle]) {
[(UIView *)[self.selectionTables objectForKey:[sender currentTitle]]].hidden = ![(UIView *)[self.selectionTables objectForKey:[sender currentTitle]]].isHidden;
}
}
First of all, with all due respect, I agree with trojanfoe comments. Its not working because its not properly written.
Now, lets try to streamline it with below code:
- (IBAction)choosingButtonClicked:(id)sender {
NSString *title = [sender currentTitle];
if (title) {
UIView *selectionView = (UIView *)self.selectionTables[title];
selectionView.hidden = !selectionView.isHidden;
}
}
Your code is too complex, because of that even the author can't understand it. If we re-write your code using local variables, it will look like:
- (IBAction)choosingButtonClicked:(id)sender
{
NSString *title = [sender currentTitle];
if (title)
{
UIView *tempView = (UIView *)[self.selectionTables objectForKey:title];
[tempView].hidden = ![tempView].isHidden;
}
}
If you check the code now, you can see that the following code is causing the issues:
[tempView].hidden = ![tempView].isHidden;
Change your method like:
- (IBAction)choosingButtonClicked:(id)sender
{
NSString *title = [sender currentTitle];
if (title)
{
UIView *tempView = (UIView *)[self.selectionTables objectForKey:title];
tempView.hidden = !(tempView.isHidden);
}
}

Change attributes on view controller on alert selection (objective c)

I'm new to objective c and need some help. I've only one view controller which should show when as default an array called factbook. However I'd like to change the array by an alert validation. So that if the user would like to see sport he could just click on sport on the alert view and the current view controller would change the theme name to sport, the background color to the color scheme i'd like for the sport theme and the text to the sport facts array. Every help is welcome!
- (void)viewDidLoad {
[super viewDidLoad];
// init for displaying facts
self.factBook = [[FactBook alloc] init];
self.colorWheel = [[ColorWheel alloc] init];
self.view.backgroundColor = [self.colorWheel randomColor];
self.funFactLabel.text = [self.factBook randomFact];
}
- (IBAction)showFunFact:(UIButton *)sender {
self.view.backgroundColor = [self.colorWheel randomColor];
funFactLabel.text = [self.factBook randomFact];
}
- (IBAction)changeTheme:(UIButton *)sender {
// to sport
[changeThemeNotification addButton:#"change to sport" validationBlock:^BOOL{
BOOL passedValidation = true;
return passedValidation;
} actionBlock:^{
// segue to sport
self.themeName.text = #"Sport";
self.sportFactBook = [[SportFactBook alloc] init];
self.sportColorWheel = [[SportColorWheel alloc] init];
// not working
// self.funFactLabel = [self.sportFactBook randomSportFact];
self.view.backgroundColor = [self.sportColorWheel randomColor];
}];
//...
}
Assuming that this comment
// not working
// self.funFactLabel = [self.sportFactBook randomSportFact];
is the part you want to fix. If you are updating a label, just change its text
self.funFactLabel.text = [self.sportFactBook randomSportFactText];
randomSportFactText should return an NSString*.
If you set the output to a new label object, that doesn't update the view -- it just disassociates the outlet from the label in the view.
If you want to change just the backgroundColor, you can do that too:
self.funFactLabel.backgroundColor = [UIColor redColor];

My UILabel is not displaying when my if statement is false

I have a story board that has a UITextField, UIButton, UIImage, and UILabel to display the images in an array. If you type the correct name for the image file into a text field. So, the problem is that once the text field input does not match, it should update the UILabel to display "Result not found", but it doesn't.
#import "ViewController.h"
#interface ViewController ()
{
myClass *myNewClass;
NSMutableArray *picArray;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
picArray = [#[#"Button_Red",#"Button_Green"]mutableCopy];
}
- (IBAction)displayImageAction:(id)sender
{
NSString *titleSearched = self.textSearchField.text;
NSString *titleNotHere = self.notFoundLabel.text;
//Declare a bool variable here and set
BOOL variable1;
for (int i = 0; i < picArray.count; i++)
{
NSString *currentPic = picArray[i];
if ([titleSearched isEqualToString:currentPic])
{
variable1 = YES;
}
}
if (variable1 == YES) {
//this works fine displays the image
self.outputImage.image = [UIImage imageNamed: titleSearched];
[self.textSearchField resignFirstResponder];
} else {
//problem is here its not showing when input for the array is not equal it should display a message label "Result Not Found" but it remains blank on the IOS simulator
titleNotHere = [NSString stringWithFormat:#"Result Not found"];
[self.textSearchField resignFirstResponder];
}
}
//Get rid of the texfield when done typing
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Retract keyboard if up
[self.textSearchField resignFirstResponder];
}
Your problem is that
titleNotHere = [NSString stringWithFormat:#"Result Not found"];
simply sets the method variable titleNotHere.
What you want is
self.notFoundLabel.text=#"Result Not found";
You will also want
self.notFoundLabel.text=#"";
when the result is found.
I think you will have to SET the value to the variable
self.notFoundLabel.text = [NSString stringWithFormat:#"Result Not found"]
or
self.notFoundLabel setText:[NSString stringWithFormat:#"Result Not found"]
UILabel.text = #"whatever..." is actually converted into [UILable setText:#"whatever..."].
NSString *labelText = UILabel.text has to be thought as NSString *labelText = [UILabel text];
This:
NSString *titleNotHere = self.notFoundLabel.text;
stores the text from the label into a variable, but updating that variable again will not change the label text - it only changes what that variable points to.
You need to explicitly update the label text:
self.notFoundLabel.text = #"Result Not found";
note also that this uses a string literal - you don't need to use a format string as you aren't adding any parameters to it.
Also, when checking booleans, don't use if (variable1 == YES) {, just use if (variable1) { (it's safer).

Change Text Of A UILabel To A Random Array Object By Pressing UIButton

As the title says... I need to change the contents of my UILabel to a random object from NSArray by pressing a UIButton... Here is the code i have in the button:
- (IBAction)moodButton:(UILongPressGestureRecognizer *)sender {
UILabel *moodLabel = [[UILabel alloc] init];
if (sender.state == UIGestureRecognizerStateEnded) {
NSArray *moodArray = [[NSArray alloc] initWithObjects:#"Happy", #"Angry", #"Sad", #"Bored",
#"Tired", #"Stressed", #"Busy", nil];
id randomObject = [moodArray objectAtIndex:arc4random_uniform([moodArray count])];
if (randomObject == moodArray[0]) {
moodLabel.text = #"Happy";
}
else if (randomObject == moodArray[1]) {
moodLabel.text = #"Angry";
}
else if (randomObject == moodArray[2]) {
moodLabel.text = #"Sad";
}
else if (randomObject == moodArray[3]) {
moodLabel.text = #"Bored";
}
else if (randomObject == moodArray[4]) {
moodLabel.text = #"Tired";
}
else if (randomObject == moodArray[5]) {
moodLabel.text = #"Stressed";
}
else if (randomObject == moodArray[6]) {
moodLabel.text = #"Busy";
}
}
}
What am I doing wrong?
Thanks in advance.
You have a number of issues with your code.
You're creating a new label and then doing nothing with it other than adding some text (not even adding it to another view). Do you have an existing label you want to use instead? I'd recommend you keep a property pointing to a label in your view, which you can use to just update the text.
Your entire if statement is completely redundant. randomObject already contains a random string from your array, so you don't need to manually check which value it contains. Just remove your whole if statement, and do:
moodLabel.text = (NSString *)randomObject;
The answer by James covers the issues but I thought I would clarify by showing some code.
- (IBAction)moodButton:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateEnded) {
NSArray *moodArray = #[ #"Happy", #"Angry", #"Sad", #"Bored",
#"Tired", #"Stressed", #"Busy" ];
NSString *randomString = moodArray[arc4random_uniform([moodArray count])];
self.moodLabel.text = randomString;
}
}
Note how the label is needs to be obtained from the existing label. Don't create a new one. Also note the use of modern Objective-C syntax for the array and its access.

Deselect all UIButtons when one is selected

I have eight (8) UIButtons setup in my game. When one is selected it shows that it is selected and if you click it again it will show as unselected. However, I want to make it so that when you select a button and any of the other seven (7) are selected, they become unselected.
I know how to do this through the use of [buttonName setSelected:NO] but the problem is I can't pass buttonOne to buttonTwo if buttonTwo has already been passed to buttonOne because I have already imported buttonTwo's header file in buttonOne. It throws a parse error if I have both headers importing each other. I've been stuck on this for a while now and was hoping that someone might have a solution to my problem.
Thanks for any help.
Get the parent view of the current button and iterate through all the buttons inside, unselecting all of them. Then, select the current one.
// Unselect all the buttons in the parent view
for (UIView *button in currentButton.superview.subviews) {
if ([button isKindOfClass:[UIButton class]]) {
[(UIButton *)button setSelected:NO];
}
}
// Set the current button as the only selected one
[currentButton setSelected:YES];
Note: As suggested on the comments, you could keep an array of buttons and go over it the same way the above code does with the subviews of the parent view. This will improve the performance of your code in case the view containing the buttons has many other subviews inside.
I know its too late to answer this question but I did it in only small lines of code . Here is what i did :
NSArray *arrView = self.view.subviews;
for (UIButton *button in arrView) {
if ([button isKindOfClass:[UIButton class]]) {
[((UIButton *) button) setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
}
}
[button1 setTitleColor:[UIColor orangeColor] forState:UIControlStateNormal];
Simple way to do.
-(void)buttonSelected:(id)sender{
UIButton *currentButton = (UIButton *)sender;
for(UIView *view in self.view.subviews){
if([view isKindOfClass:[UIButton class]]){
UIButton *btn = (UIButton *)view;
[btn setSelected:NO];
}
}
[currentButton setSelected:YES];
}
I actually created an answer by reading all of your guys input, which I thank you greatly for. The tag property of the UIButton class was unknown to me before this post.
I created own subclass of UIButton, let's call it CustomUIButton.m. I created a NSMutableArray property for use when storing the buttons, which I'll call customButtonArray.
When I created the button, I set the tag property, and added the button to a local array on the parent view controller. After all buttons I wanted were created, I set the customButtonArray, like so:
// Initialize buttonHolderArray
NSMutableArray *buttonHolderArray = [[NSMutableArray alloc] init];
// Create a button
CustomUIButton *newButton = [[CustomUIButton alloc] initWithFrame:CGRectMake(10, 10, 50, 30)];
newButton.tag = 1;
[newButton setImage:[UIImage imageNamed:#"newButtonUnselected" forControlState:UIControlStateNormal]];
[buttonHolderArray addObject:newButton];
// Create additional buttons and add to buttonHolderArray...
// using different numbers for their tags (i.e. 2, 3, 4, etc)
// Set customButtonArray property by iterating through buttonHolderArray
NSInteger buttonCount = [buttonHolderArray count];
for (int i = 0; i < buttonCount; i++)
{
[[buttonHolderArray objectAtIndex:i] setCustomButtonArray:buttonHolderArray];
}
To deselect any other button selected when a different buttons handleTap: is called, I iterated through the customButtonArray in the subclass main file and set the selected property to NO. I also set the correct image from another array property that I manually populated with the images, which I did so the array didn't have to be populated every time a button was pressed. At the end, unselected all other buttons, like so:
// Populate two arrays: one with selected button images and the other with
// unselected button images that correspond to the buttons index in the
// customButtonArray
NSMutableArray *tempSelectedArray = [[NSMutableArray alloc] init];
[tempSelectedArray addObject:[UIImage imageNamed:#"newButtonSelected"]];
// Add the other selected button images...
// Set the property array with this array
[self setSelectedImagesArray:tempSelectedArray];
NSMutableArray *tempUnselectedArray = [[NSMutableArray alloc] init];
[tempUnselectedArray addObject:[UIImage imageNamed:#"newButtonUnselected"]];
// Add the other unselected button images...
// Set the property array with this array
[self setUnselectedImagesArray:tempUnselectedArray];
- (void)handleTap:(UIGestureRecognizer *)selector
{
// Get the count of buttons stored in the customButtonArray, use an if-elseif
// statement to check if the button is already selected or not, and iterate through
// the customButtonArray to find the button and set its properties
NSInteger buttonCount = [[self customButtonArray] count];
if (self.selected == YES)
{
for (int i = 0; i < buttonCount; i++)
{
if (self.tag == i)
{
[self setSelected:NO];
[self setImage:[[self unselectedImagesArray] objectAtIndex:i] forControlState:UIControlStateNormal];
}
}
}
else if (self.selected == NO)
{
for (int i = 0; i < buttonCount; i++)
{
if (self.tag == i)
{
[self setSelected:NO];
[self setImage:[[self selectedImagesArray] objectAtIndex:i] forControlState:UIControlStateNormal];
}
}
}
for (int i = 0; i < buttonCount; i++)
{
if (self.tag != i)
{
[self setSelected:NO];
[self setImage:[[self unselectedImagesArray] objectAtIndex:i] forControlState:UIControlStateNormal];
}
}
}
Thanks for all of the useful information though, figured I should share the final answer I came up with in detail to help anyone else that comes across this problem.
I figured out a pretty easy way to solve this. My example is for 2 buttons but you can easily add more if statements for additional buttons. Connect all buttons to the .h file as properties and name them (I did button1 & button2). Place the following code in your .m file and Connect it (via the storyboard) to all of your buttons. Make sure when you are setting up your button to set an image for BOTH the normal UIControlStateNormal & UIControlStateSelected or this wont work.
- (IBAction)selectedButton1:(id)sender {
if ([sender isSelected]) {
[sender setSelected:NO];
if (sender == self.button1) {
[self.button2 setSelected:YES];
}
if (sender == self.button2) {
[self.button1 setSelected:YES];
}
}
else
{
[sender setSelected:YES];
if (sender == self.button1) {
[self.button2 setSelected:NO];
}
if (sender == self.button2) {
[self.button1 setSelected:NO];
}
}
To answer "It throws a parse error if I have both headers importing each other"...
You should refrain from using #import in .h files as much as possible and instead declare whatever you're wanting to use as a forward class declaration:
#class MyCustomClass
#interface SomethingThatUsesMyCustomClass : UIViewController
#property (nonatomic, strong) MyCustomClass *mcc;
#end
Then #import the header in your .m file:
#import "MyCustomClass.h"
#implementation SomethingThatUsesMyCustomClass
-(MyCustomClass *)mcc
{
// whatever
}
#end
This approach will prevent errors caused by #import cycles.
Though I must say I agree with SergiusGee's comment on the question that this setup feels a bit strange.
The easiest approach here would be to get the parent UIView the buttons are on and iterate through it. Here's a quick example from my code:
for (UIView *tmpButton in bottomBar.subviews)
{
if ([tmpButton isKindOfClass:[UIButton class]])
{
if (tmpButton.tag == 100800)
{
tmpButton.selected = YES;
[tmpButton setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
[tmpButton setTitleColor:[UIColor greenColor] forState:UIControlStateHighlighted];
}else{
tmpButton.selected = NO;
[tmpButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[tmpButton setTitleColor:[UIColor redColor] forState:UIControlStateHighlighted];
}
}
}
Did you try using ReactiveCocoa framework and add some blocks for your code , this is not the most simple approach yet i would say it is the most effective when you have multiple dependencies and very good for scaling
I have created a small project for a solution to your problem using my suggested approach (I tried to adapt it to the good old MVC pattern instead of my preferred MVVM)
you can find it here
https://github.com/MWaly/MWButtonExamples
make sure to install cocoa pods file as we need "ReactiveCocoa" and "BlocksKit" for this sample
we will use two main classes
ViewController => The viewController object displaying the buttons
MWCustomButton => Custom UIButton which handles events
when creating the buttons a weak reference to the viewController is also created using the property
#property (weak) ViewController *ownerViewController ;
events will be handled using the help of blocksKit bk_addEventHandler method and pass it to the block of the ViewController (selectedButtonCallBackBlock)
[button bk_addEventHandler:^(id sender)
{
self.selectedButtonCallBackBlock(button);
} forControlEvents:UIControlEventTouchUpInside];
now in the ViewController for each button touched the callBackButtonBlock will be trigerred , where it will change its currently selected button if applicable
__weak __typeof__(self) weakSelf = self;
self.selectedButtonCallBackBlock=^(MWCustomButton* button){
__typeof__(self) strongSelf = weakSelf;
strongSelf.currentSelectedButton=button;
};
in the MWCustomButton class , it would listen for any changes in the property of "currentSelectedButton" of its ownerViewController and will change its selection property according to it using our good Reactive Cocoa
///Observing changes to the selected button
[[RACObserve(self, ownerViewController.currentSelectedButton) distinctUntilChanged] subscribeNext:^(MWCustomButton *x) {
self.selected=(self==x);
}];
i think this would solve your problem , again your question might be solved in a simpler way , however i believe using this approach would be more scalable and cleaner.
Loop through all views in parent view. Check if it is a UIButton(or your custom button class) and not the sender. Set all views isSelected to false. Once loop is finished, set sender button isSelected to true.
Swift 3 way:
func buttonPressed(sender: UIButton) {
for view in view.subviews {
if view is UIButton && view != sender {
(view as! UIButton).isSelected = false
}
}
sender.isSelected = true
}
Swift 4
//Deselect all tip buttons via IBOutlets
button1.isSelected = false
button2.isSelected = false
button3.isSelected = false
//Make the button that triggered the IBAction selected.
sender.isSelected = true
//Get the current title of the button that was pressed.
let buttonTitle = sender.currentTitle!

Resources