I have an array of objects (scaleNotes) that are assigned to buttons that I would like to iterate through. I want the app to wait for the first set of buttons to be pressed, then to wait for the second, and so on. However, with the function I currently have the app freezes.
The method that contains the array:
-(void)fingerScale :(UIButton*)button1 :(UIButton*)button2 :(UIButton*)button3 :(UIButton*)button4 :(UILabel*)label {
Note *note = [[Note alloc]init];
for (int i = 0; i < [_scaleNotes count]; i++) {
note = _scaleNotes[i];
label.text = note.noteName;
[note waitForNote:button1 :button2 :button3 :button4 :note];
NSLog(#"Waiting... %i", i);
}
}
The "waitForNote" method:
-(void)waitForNote:(UIButton *)button1 :(UIButton *)button2 :(UIButton *)button3 :(UIButton *)button4 :(Note*)Note {
bool loop = YES;
while (loop) {
switch ([Note.fingering count]) {
case 0:
loop = NO;
break;
case 1:
button1 = Note.fingering[0];
if (button1.touchInside) {
loop = NO;
}
break;
case 2:
button1 = Note.fingering[0];
button2 = Note.fingering[1];
if (button1.touchInside && button2.touchInside) {
loop = NO;
}
break;
case 3:
button1 = Note.fingering[0];
button2 = Note.fingering[1];
button3 = Note.fingering[2];
if (button1.touchInside && button2.touchInside && button3.touchInside) {
loop = NO;
}
break;
default:
break;
}
}
}
I have been told that using addTarget:action:forControlEvents may solve my problem instead of using a while loop, but I am unsure about how to implement that into my code. Please help.
Your while loop is an infinite loop, and that's why your app appears to freeze. Most GUI systems -- iOS included -- won't work that way. You have to give the app's event loop, or run loop, time to gather and process events.
-addTarget:action:forControlEvents: will be part of the solution, but you really need to spend some time learning more about how apps and controls work in general. Briefly, your controls should be connected to appropriate actions; if you want to enforce some order for the buttons you might enable or disable some buttons as necessary.
To expand on Caleb's answer a little bit:
iOS, like most modern interactive OS'es, is event-driven.
You write code that responds to events. The OS detects events, and then invokes the methods in your app that respond to those events.
You do NOT write a while loop that keeps looping, waiting for things to happen.
For buttons, you create methods of type IBAction, and you connect them to your buttons (usually connected to a "touch up inside" event.) Then, when the user taps and releases a button, the system calls your action method.
For a slider, you would write an action method and attach it to the "value changed" event for the slider. Then, when the user changed the value of the slider the system would call you.
So for your buttons you would write an action method and attach it to your buttons. (It's more common to create buttons and attach them to actions in Interface Builder, but you can also create them in code and then attach them using the -addTarget:action:forControlEvents: method you mentioned.)
If you've got multiple buttons that do the same thing but with different values, you might want to attach all the buttons to the same IBAction method, and then put different tag values on each button. Then in your IBAction method, look at the tag on the sender (The button that invoked the action) and use that to figure out which button was pressed.
Related
I have a custom UIControl subclass with an action method callback. I want to display the value of the control element on a UILabel while it is being adjusted, and then I want the label to become hidden when the user stops adjusting the control.
Therefore, I have connected the action for both UIControlEventValueChanged and UIControlEventTouchUpInside. Both successfully invoke my action method. However, to do different things in this method based on the action I need to know which event triggered the method. How can I do this? I've looked through UIControl and don't see an obvious property. state seems to return 1 for both actions.
So something like this:
- (void)handleSlider1:(CustomSlider*)sender {
if (sender.state == UIControlEventValueChanged) {
// code
} else {
// different code
}
}
You can distinguish the two events pretty easily by connecting them each to separate IBActions. Each new action then would call your single handler, passing the appropriate UIControlEvent value along:
- (IBAction)sliderValueChanged:(CustomSlider *)slider
{
[self handleSlider1:slider forEvent:UIControlEventValueChanged];
}
- (IBAction)sliderValueChanged:(CustomSlider *)slider
{
[self handleSlider1:slider forEvent:UIControlEventTouchUpInside];
}
- (void)handleSlider1:(CustomSlider *)sender forEvent:(UIControlEvents)event
{
if (event == UIControlEventValueChanged)
//...
}
If you want to use the same method to handle both events then you can check the highlighted property of the control:
if (sender.highlighted) {
// slider is changing value (value changed)
} else {
// slider has stopped changing value (touch up inside)
}
Alternatively, you could simply create two separate action methods and connect each event to the required method.
Xcode 5, iOS 7
I'm trying to create/display several objects sequentially, with sound, and preferably with some specified timing.
For example, I'd like to create and display 10 labels.
- Each time a label is created, play a sound.
- Control the display of the label (i.e. animation block for timing)
- Create the next label only after the previous one has been created, sound played, and displayed. Preferably insert a delay between labels.
What I have now is a For Loop which calls
1: Method A - instantiate the label
2: Method A calls Method B which contains an animation block to a)play the sound then b)animate the display
My problem is that all 10 labels appear so fast that it doesn't look sequential and I only hear the sound once.
How can I chain these together and control the display and timing?
If you aren't using SpriteKit, then this is probably close to what you are looking for:
-(void)managerMethod
{
float timeBetweenEvents = 3.0;
for (int i = 0; i < 10; i++) {
NSObject *someObjectCreatedHere;
[self performSelector:#selector(doSomethingBasedOnThisObject:) withObject:someObjectCreatedHere afterDelay:(i * timeBetweenEvents)];
}
}
-(void)doSomethingBasedOnThisObject:(NSObject*)thisObject{
//do stuff to thisObject
//load stuff from thisObject
//etc
}
First off, as a disclaimer, I am new to objective-c, xcode, and cocos2d. I have found a way in my app to refresh a screen conveniently, but I don't know if it is bad practice. Here's what I am doing. I have a class called Player with a variable NSString *name. I am displaying this and several other variables on the screen using this code in a function:
label = [CCLabelTTF labelWithString:string fontName:GLOBAL_FONT fontSize:font_size ];
label.color = color_back;
label.position = ccp(x+1, y-1);
[self addChild:label];
When a button is pressed, I am modifying player->name along with several other variables. Because several variable are changing (on this screen and eventually others) when the button is pressed I also set a flag to indicate that the screen needs to be refreshed. I then check this code with a scheduler:
if(panelPrev != currentPanel || refreshScreen) //do we need to initialize the panel?
{
[self removeAllChildrenWithCleanup:true]; //clear all objects from display
//Decide which objects to display
switch (G_display_panel) {
case P_Main:
[Display_Main init_Panel:self];
break;
case P_NewGame:
[Display_New init_Panel:self];
break;
default:
break;
}
refreshScreen = false;
}
My first question: Is this an acceptable way to display things to the screen and refresh them? It seemed much more convenient than updating every variable that is being displayed. The buttons are not being pressed very often so I am not concerned about performance.
Second: If it is ok to do it this way, why is it that when I press a button and change the value of player->name I am getting this: "Thread 1: EXC_BAD_ACCESS (code=1, address=...)"? If I step through with the debugger the value gets assigned to player->name correctly and the screen refresh works. But if I just let it run it gets the EXC_BAD_ACCESS when I try to access player->name and the data looks corrupted (e.g. (NSString *) name = 0x15927f80 when I am expecting (NSString *) name = #"Bob").
Some additional details.
I am not setting refreshScreen to 'true' until after changing the value of player->name
To prevent refreshing before the value was truly changed, I set a delay on the refresh. After the button was pressed I would modify player->name and wait about 10 seconds but I would still see the same problem.
Any ideas? Thanks.
try this:
[self addChild:label];
I figured out the problem. It was a memory management issue. I added a getter and setter for the variable using the example specified here: developer.apple.com/library/mac/documentation/Cocoa/Conceptual/… –
I'm writing some code in Xcode for an iPhone app and I want to be able to detect if a method has been run (i.e. a button was pressed, causing that method to run) in another method (I want to use an if statement so that if the button was pressed it will do this, but if it wasn't then it will do something else).
There is no has_method_been_run() function but you can check to see if the state was changed.
For example say method button_clicked() calls method change_font_to_blue(). In this case you can check to see if the font is blue and there for the method was called.
That's a very basic example of course but you could check any number of variables / the state of the UI to see if it was changed.
OR you can add a boolean to an object and just set it to true when you execute you're method that you're watching.
If your example is simply a button press I would change the UIButton's selected state
- (IBAction)buttonSelected:(UIButton *)sender {
sender.selected = YES;
// OR:
// self.myButton.selected = YES;
}
- (void)otherMethod {
if (self.myButton.isSelected) {
// button has been run through the selector method
// if you want, reset button's selected state
self.myButton.selected = NO;
} else {
// button has NOT been run through the selector method
}
}
This is a simple idea and by default the selected state of a UIButton is not visually different. If you want it to be visually changed then you can simply go into IB and change the visuals there for the selected state (including the title):
Then when the button is set to selected (self.myButton.selected = YES;) it will automatically change what the button looks like!
I have a simple question about event handling in iOS applications... suppose you have an app with some buttons that react to the TouchUpInside event calling the same action, what is the best way within the action method to understand what is the button that triggered the event? I know that it can be easily done using the title of the button, but I think it is not the best way if you have a localized app in which button text may change (unless it is possible to reverse the localization of the title, i.e. retrieve the original string from a localized string)... is there a good practice about this topic? Should I use some other property of buttons to distinguish among different buttons?
Thank you in advance for any help.
There is something called a "Tag" that you can set for UIButtons, or anything that can respond to an event for that matter. If you are using Interface Builder, click the attributes inspector for the item and select a value for the tag (integer). In your code do something like this...
...
- (IBAction)buttonReceived:(id)sender
{
if ([sender tag] == 1) {
//Do something
}
else if ([sender tag] == 2) {
//Do something else
}
}
In addition to the tag property, or just in case you are already using the tag for some other purpose that would mean duplicate tag values for one or more different buttons, you can always set up an IBOutlet ivar to each button you needed to check, and then in the IBAction, do something like this:
- (IBAction)buttonReceived:(UIButton *)sender
{
if (sender == myButtonA) {
// processing for button A
}
else if (sender == myButtonB) {
// processing for button B
}
}
It is a bit more work, but it can come in handy at times.