I'm making a matching game in iOS using objective-C
there's a 12 set of cards (3 columns and 4 rows) which is an array of UIButton connected to IBOutletCollection,the problem is when you tap the first card, you can also tap the other cards simultaneously, but the allowable card to tap must be only 2 cards. How can I prevent tapping of cards, if the tapped cards are greater than 1 and less than 2.
//One action for all buttons
-(void)buttonAction:(UIButton*)button
{
//if user select three button at a time, dont do further operations
if (previousButton && currentButton) {
return;
}
//previous button is the first button
if(previousButton==nil)
{
previousButton=button;
}
//current button is the second button
else if(currentButton==nil)
{
currentButton=button;
}
//if button is selected, set button as not selected and vice versa
if([button isSelected]==NO)
{
[button setSelected:YES];
}
else
{
[button setSelected:NO];
}
//if user press the same button again and again
if (currentButton==previousButton)
{
currentButton=nil;
if([previousButton isSelected]==NO)
{
previousButton=nil;
}
return;
}
else if((currentButton!=previousButton)&&(currentButton!=nil))
{
//both button tags are same, that means both selected state images are same
if(previousButton.tag==currentButton.tag)
{
[self performSelector:#selector(delay) withObject:nil afterDelay:0.5];
}
else
{
[self performSelector:#selector(delayTwo) withObject:nil afterDelay:0.5];
}
}
}
-(void)delay
{
//[currentButton setHidden:YES];
//[previousButton setHidden:YES];
currentButton=nil;
previousButton=nil;
score++;
NSLog(#"Score %d",score);
//the final stage, that means left only two buttons
if(score==6)
{
//[self endGame];
}
}
-(void)delayTwo
{
[currentButton setSelected:NO];
[previousButton setSelected:NO];
currentButton=nil;
previousButton=nil;
}
In viewDidLoad, assign all button selected & normal state images or title as you required
I'm not 100% sure what you are getting at here, but you could try having multiple UITapGestureRecognizer instances. You could have one for 1 tap, one for 2, and one for 3+. That way, you can select the cards for 1 or 2 touches, but ignore it if there are 3 or more.
Related
I'm trying to add a gesture recognizer to specific tabBar item's subview. I can successfully add one to the tabBar itself, but not a specific index.
I was able to trick it into reacting based on a selectedIndex by implementing this in my AppDelegate:
[self.tabBar.view addGestureRecognizer:longPressNotificationsGR];
-(void)showFilterForNotifications:(UILongPressGestureRecognizer *)gesture {
if (self.tabBar.selectedIndex == 4) {
if (gesture.state == UIGestureRecognizerStateBegan) {
NSLog(#"Began");
} else if (gesture.state == UIGestureRecognizerStateEnded) {
NSLog(#"Ended");
}
}
}
Is there a better way? And yes I know this isn't what Apple had in mind but this is what I need for my particular project. I just feel like this isn't the best workaround, if there even is one, and i'm not sure how this will behave performance wide since its in the AppDelegate. But ultimately, I don't want to do it this way, I want to add it to the objectAtIndex:n so users can't just press and hold anywhere on the tabBar, even if 4 is the currently selected index. Right now users can tap & hold on index 1 icon, if 4 is selected, and the gesture methods get called. I want it to happen only if user is tapping & holding on objectAtIndex:n
You can get the UIView of a tab at a specific index with the following category method on UITabBar:
- (UIView*)tabAtIndex:(NSUInteger)index
{
BOOL validIndex = index < self.items.count;
NSAssert(validIndex, #"Tab index out of range");
if (!validIndex) {
return nil;
}
NSMutableArray* tabBarItems = [NSMutableArray arrayWithCapacity:[self.items count]];
for (UIView* view in self.subviews) {
if ([view isKindOfClass:NSClassFromString(#"UITabBarButton")] && [view respondsToSelector:#selector(frame)]) {
// check for the selector -frame to prevent crashes in the very unlikely case that in the future
// objects that don't implement -frame can be subViews of an UIView
[tabBarItems addObject:view];
}
}
if ([tabBarItems count] == 0) {
// no tabBarItems means either no UITabBarButtons were in the subView, or none responded to -frame
// return CGRectZero to indicate that we couldn't figure out the frame
return nil;
}
// sort by origin.x of the frame because the items are not necessarily in the correct order
[tabBarItems sortUsingComparator:^NSComparisonResult(UIView* view1, UIView* view2) {
if (view1.frame.origin.x < view2.frame.origin.x) {
return NSOrderedAscending;
}
if (view1.frame.origin.x > view2.frame.origin.x) {
return NSOrderedDescending;
}
NSLog(#"%# and %# share the same origin.x. This should never happen and indicates a substantial change in the framework that renders this method useless.", view1, view2);
return NSOrderedSame;
}];
if (index < [tabBarItems count]) {
// viewController is in a regular tab
return tabBarItems[index];
}
else {
// our target viewController is inside the "more" tab
return [tabBarItems lastObject];
}
return nil;
}
Then, you just need to add your gesture recognizer:
[[self.tabBarController.tabBar tabAtIndex:4] addGestureRecognizer:myGestureRecognizer];
If my button is in the "selected" state, I want an activity indicator to appear to let the user know that something is happening and that my app is searching for data. I've tried to accomplish this FIRST by using NSLog to see if I can get this method working FIRST. So I did this:
- (void)viewDidLoad
{
[super viewDidLoad];
searchedItem.delegate = self;
if(searchButton.selected == YES)
{
NSLog(#"The button was selected");
}
}
For some reason, it won't work.
Put this checkmark in your button click event. It perfect work when click on button as first time then got to Not Selected and after click on second time go to Selected.
-(IBAction)ClickBtn:(UIButton *)sender
{
sender.selected = ! sender.selected;
if (sender.selected)
{
NSLog(#" Not Selected");
}
else
{
NSLog(#" Selected");
}
}
try this
- (void)viewDidLoad
{
[super viewDidLoad];
searchedItem.delegate = self;
[searchButton setSelected:YES];
if(searchButton.selected == YES)
{
NSLog(#"The button was 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!
In my xib I have taken 4 UIbuttons named button 1, 2, 3 and 4. These four buttons are connected two four different IBAction methods which perform different functions.
Now I have one more button called "Save" This has also a different IBAction method.
- (IBAction)Save:(id)sender
{
}
Now here I want to check which of the above 4 UIButtons have been clicked.
For this I tried checking this way
- (IBAction)Save:(id)sender
{
if(sender == button1)
{
//Do this
}
else if (sender == button2)
{
//Do this
}
}
But this is not working. I am doing something wrong.Please help me out
Regards
Ranjit.
You can set the tag values for each button in the interface builder and set actions of all buttons to this method
//set global variable flag.
int flag;
- (IBAction)buttonClicked:(id)sender
{
switch ([sender tag])
{
case 0:
{
flag =0;
// implement action for first button
}
break;
case 1:
{
flag =1;
// implement action for second button
}
break;
case 2:
{
flag =2;
// implement action for third button
}
break;
//so on
default:
break;
}
}
for save button
- (IBAction)save:(id)sender
{
switch (flag)
{
case 0:
{
// first button clicked
}
break;
case 1:
{
// second button clicked
}
break;
case 2:
{
// third button clicked
}
break;
//so on
default:
break;
}
}
Define a class level ivar as
UIButton *selectedBtn;
Then in you IBActions
- (IBAction)button1:(id)sender {
selectedBtn = sender // or button1
}
- (IBAction)button2:(id)sender {
selectedBtn = sender // or button2
}
- (IBAction)button3:(id)sender {
selectedBtn = sender // or button3
}
- (IBAction)button4:(id)sender {
selectedBtn = sender // or button4
}
- (IBAction)Save:(id)sender
{
//Check output of below statement to ensure you're getting a sender
NSLog(#"Sender: %#", sender);
if(selectedBtn == button1)
{
NSLog(#"Button 1 pressed");
//Do this
}
else if (selectedBtn == button2)
{
NSLog(#"Button 2 pressed");
//Do this
}
else if (selectedBtn == button3)
{
NSLog(#"Button 3 pressed");
//Do this
}
else if (selectedBtn == button4)
{
NSLog(#"Button 4 pressed");
//Do this
}
}
Can you try this:
- (IBAction)Save:(id)sender
{
UIButton *pressedButton = (UIButton*)sender;
//Check output of below statement to ensure you're getting a sender
NSLog(#"Sender: %#", sender);
if([pressedButton isEqual:button1])
{
NSLog(#"Button 1 pressed");
//Do this
}
else if ([pressedButton isEqual:button2])
{
NSLog(#"Button 2 pressed");
//Do this
}
}
In your save method, check the Selected property of the other 4 buttons. If you do not want to keep the buttons in a selected state, but just want to see if they were clicked at some point, then define a property (e.g. an array) to track which buttons were clicked during the session, and check this property in your save method.
just wondering how to make a button "START" that when pressed triggers a function and the text changes to "STOP". then when pressed again the function will stop and the text changes back to "START"
Ive got the button already that starts the function. and i can handle changing the title, just not sure on what to use to make the 1 button have 2 functions
Add the IBAction method like:
- (IBAction)buttonTapped:(id)sender
{
UIButton *btn = (UIbutton *)sender;
NSString *title=btn.titleLabel.text;
if ([title isEqualToString:#"Start"])
{
//Start
}else
{
//Stop
}
}
Please try this:
- (IBAction) buttonAction:(id)sender
{
if([[(UIButton *)sender currentTitle]isEqualToString:#"START"])
{
[actionButton setTitle:#"STOP" forState:UIControlStateNormal];
//start the action here and change the button text to STOP
}
else if([[(UIButton *)sender currentTitle]isEqualToString:#"STOP"])
{
[actionButton setTitle:#"START" forState:UIControlStateNormal];
//stop the action here and change the button text to START
}
}
you have at least two options here:
Check for the title of your button and depending on the value call an action or the other.
Create two different buttons, each one with his action and show/hide them.