I have created a drum sequencer using Objective C. I want the slider to control the tempo. At the moment, everything works, and the interval between each step is being controlled by:
while (self.running)
{
// sleep until the next step is due
[NSThread sleepUntilDate:time];
// update step
int step = self.step + 1;
// wrap around if we reached NUMSTEPS
if (step >= NUMSTEPS)
step = 0;
// store
self.step = step;
// time duration until next step
time = [time dateByAddingTimeInterval:0.2];
}
So the time between each step is 0.2 seconds. I have tried to implement a tempo slider like so in the view controller .m (the slider has a range of 0.3 to 1.0 so will output similar value to what is currently in time):
- (IBAction)sliderMoved:(UISlider *)sender
{
AppDelegate* app = [[UIApplication sharedApplication] delegate];
app.tempo = sender.value;
}
and by changing the line in the while(self.running) thread to:
time = [time dateByAddingTimeInterval: (NSTimeInterval) _tempo];
However, this causes the time between steps to be far too short (tempo is crazy fast) and when any control in the app is touched, it crashes.
I wonder if I need to set up a function like this, but I'm not sure what would go inside to enable the tempo slider to work:
- (void)setTempo:(float)tempo
{
}
I have tried to be as clear as I can, if anyone can help me I'd be very grateful, thanks in advance
-(void) startDrumTick{
[self.myDrumTimer invalidate]; // stop any current existing timer
// perform the call to the method 'drumMethodOperation:'
// every 0.2 sec. NB: drumMethodOperation will run on main thread.
// this means that if you expect to do long-blocking operation,
// you will need to move that op to an async thread, in order to avoid
// the UI blocking
self.myDrumTimer = [NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:#selector(drumMethodOperation:)
userInfo:nil
repeats:YES];
}
-(void)drumMethodOperation:(id)sender
{
// update step
int step = self.step + 1;
// wrap around if we reached NUMSTEPS
if (step >= NUMSTEPS)
step = 0;
// store
self.step = step;
// any other needed operation to run every 0.2 secs
}
Below an example for an async thread management using GCD
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// Long blocking operation ( DO NOT PERFORM ANY UI OPERATION, like changing a text label, setting an image to an UIImageView, etc. )
[self myLongDbQuery];
dispatch_async(dispatch_get_main_queue(), ^(void){
//Perform you UI Updates here
self.myLabel.text = #"Query done!!!";
});
});
Hope it helps
Luca is right about using GCD. If talk about your initial solution.
Did you set initial value for _tempo? Looks like your bug can be caused by _tempo = 0 initially. As you understand sliderMoved will be called only after some user action so you need to set initial value.
Related
I am making a timer app on XCode 7 using Objective-C and I would like to disable the button that is connected to the action startCount while the following code is being executed
- (IBAction)startCount:(id)sender {
countInt = 0;
self.Label.text = [NSString stringWithFormat:#"%i", countInt];
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(countTimer) userInfo:nil repeats:YES];
}
What line of code would I need to add to this to disable the button connected to the action startCount?
Thanks
You need to disable the sender via the enabled flag:
((UIButton*)sender).enabled = false;
Don't forget to re-enable the button after the timer finishes.
If the enabled state is NO, the control ignores touch events [...]
Alternatively to the cast I made in the above code: Change your method signature to take in a UIButton*, not just an id, that way you can make sure the cast will not fail. A slight variation of the cast would be to cast to UIControl* instead.
If I understand you correctly: to make your code easier to understand (and to avoid unpleasant race conditions if there are multiple buttons that might be tapped/disabled), I would suggest avoiding using a timer. Instead, consider dispatch_after() like this:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
sender.userInteractionEnabled = false;
});
We get sender being passed into the method, and it will be the button that was tapped. 1 * NSEC_PER_SEC means "delay for one second."
I want to illustrate the progress on MBProgressHUD item, but when i triger this method :
- (IBAction)signInBttn:(id)sender {
MBProgressHUD *hudd = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hudd.mode = MBProgressHUDModeAnnularDeterminate;
hudd.labelText = #"Loading";
__block float value = 0;
for (int j = 0; j<2000; j++) {
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i<20000 ; i++) {
}
value += 0.001;
dispatch_async( dispatch_get_main_queue(), ^{
hudd.progress = value;
});
});
}
}
hud appears fully to 100%. This is only for my information, I dont have idea how to create background task which calculate something and when he done with e.g. 40% the HUD is refreshing to 40% of his progress. I hope I made myself clear, and if anyone has time to help improve my code, thanks a lot for any answers
In this case, you can solve the problem by decoupling the updating of the counter from the updating of your HUD in your UI. Apple refers to this as "updating the state asynchronously" in WWDC 2012 video Asynchronous Design Patterns with Blocks, GCD, and XPC.
Generally this isn't necessary (most of the time the stuff we're doing asynchronously is slow enough that we don't have problems), but if doing something that is running faster than the UI can hope to keep up with, you create a "dispatch source" for this. I'm going to illustrate it with a UIProgressView, but the same applies to pretty much any UI:
// create source for which we'll be incrementing a counter,
// and tell it to run the event handler in the main loop
// (because we're going to be updating the UI)
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
// specify what you want the even handler to do (i.e. update the HUD or progress bar)
dispatch_source_set_event_handler(source, ^{
self.iterations += dispatch_source_get_data(source);
[self.progressView setProgress: (float) self.iterations / kMaxIterations];
});
// start the dispatch source
dispatch_resume(source);
// now, initiate the process that will update the source
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (long i = 0; i < kMaxIterations; i++)
{
// presumably, do something meaningful here
// now increment counter (and the event handler will take care of the UI)
dispatch_source_merge_data(source, 1);
}
// when all done, cancel the dispatch source
dispatch_source_cancel(source);
});
In my example, iterations is just a long property:
#property (nonatomic) long iterations;
And I defined my kMaxIterations constant as follows:
static long const kMaxIterations = 10000000l;
First off, if you want to delay execution use dispatch_after: Apple Doc since it could be that Clang is optimizing your loop (i.e. by making it not exist).
Within that block call dispatch_sync on the main thread to update the UI, since dispatch_async is not guaranteed to execute 'evenly'. Something like this ought to work...
for (...) {
dispatch_after(<some formula of i>, DEFAULT_PRIORITY, ^{
dispatch_sync(MAIN_QUEUE, ^{ hudd.progress = value });
}
}
I am making an application that randomly selects a picture from a preset group of pictures and displays it to a image view. This should happen every second or so until it has gone through 20 cycles.
Hear is my header and implementation code:
#interface spinController : UIViewController
{
IBOutlet UIImageView *imageHolder;
NSTimer *MovementTimer;
}
-(IBAction)Button:(id)sender;
-(void)displayPic;
#end
#implementation spinController
-(IBAction)Button:(id)sender
{
int count = 0;
while (count <20)
{
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(displayPic) userInfo:nil repeats:NO];
count++;
}
}
-(void)displayPic
{
int r = arc4random() % 2;
if(r==0)
{
imageHolder.image = [UIImage imageNamed:#"puppy1.jpg"];
}
else
{
imageHolder.image = [UIImage imageNamed:#"puppy2.jpg"];
}
}
-(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
#end
I have made this application in a much more advanced form in WPF and ran into similar issues where the pictures do no cycle through properly. If I hit spin it randomizes but does not go through the 20 cycles... just one. This is my first application in objective-c and realize the efficiency of the method I choose will determine how good my application will run in a more complex form. Any help would be greatly appreciated.
The problem is that you're calling the timer repeatedly within the while loop; and since that particular while loop will complete within a millisecond or so, you're creating 20 timers one after another in immediate succession. Because of this, only the final image will show up in the imageHolder view. Edit: Even if the loop were to take more than a millisecond per iteration, I believe the NSTimer wouldn't actually fire until the method exits anyway.
In order to show the images one after another as you're trying to do, (1) use the NSTimer without the while loop, (2) keep track of the iterations using a count class instance variable so as not to lose the value of the variable upon the completion of the various methods, and (3) pass along the NSTimer to the displayPic method so you can invalidate the timer from there upon the 20th iteration. For example:
// Declare the "count" instance variable.
int count;
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
-(IBAction)Button:(id)sender {
// The count starts at 0, so initialize "count" to 0.
count = 0;
// Use an NSTimer to call displayPic: repeatedly every 1 second ("repeats" is set to "YES")
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(displayPic:) userInfo:nil repeats:YES];
}
// Pass along the NSTimer to the displayPic method so that it can be invalidated within this method upon the 20th iteration
-(void)displayPic:(NSTimer *)timer {
// Get the random number
int r = arc4random() % 2;
// If the number is 0, display puppy1.jpg, else display puppy2.jpg.
if(r == 0) {
imageHolder.image = [UIImage imageNamed:#"puppy1.jpg"];
}
else {
imageHolder.image = [UIImage imageNamed:#"puppy2.jpg"];
}
// Increment "count" to reflect the number of times the NSTimer has called this method since the button press
count ++;
// If the count == 20, stop the timer.
if (count == 20)
[timer invalidate];
}
#end
Change repeats to YES. This causes the timer to run again and again. Then instead of a while loop check the count in the method itself.
I'm very very new to iOS programming but I already have a big issue I can't solve. It seems so easy.
I have a button, I click on it to change a label called message
Here is the code:
- (IBAction)react:(id)sender {
int hasard ;
hasard=3;
message.text=[NSString stringWithFormat:#"1 %d",hasard];
sleep (1);
message.text=[NSString stringWithFormat:#"2 %d",hasard];
}
It works well but I don't see the first message.text change.
When I click the button, I have to wait one second and I see 2 3
I thought I could see 1 3, wait a second and then see 2 3.
What is missing? It seems so obvious.
sleep() will suspend the current threads execution which is the main thread so you are blocking all UI operations until the method completes. You need to schedule the second assignment to run after the specified time without blocking the current run loop. This can be achieved with GCD.
- (IBAction)react:(id)sender {
int hasard ;
hasard=3;
message.text=[NSString stringWithFormat:#"1 %d",hasard];
int64_t delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
message.text=[NSString stringWithFormat:#"2 %d",hasard];
});
}
You are blocking the main thread (well, rather sleep() blocks it). If you do so, committed changes to the UI won't appear - you'll only see the final result. You have to do something else (blocking the UI is a very bad idea in terms of user experience, by the way). You can try using a timer, for example:
int hasard = 3;
- (void)react:(id)sender
{
message.text = [NSString stringWithFormat:#"1 %d", hasard];
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(timer:) userInfo:nil repeats:NO];
}
- (void)timer:(NSTimer *)tmr
{
message.text = [NSString stringWithFormat:#"2 %d", hasard];
}
I have a view-based template app and have UILabel & UIButton. For debugging purposes I'm showing and hiding the button whilst changing the UILabel.text.
In C++ I would 'thread root();' to execute the root method but I don't know how to in Objective-c. How to run my 'root' method once the view loads?
-(void) root
{
[bombButton1 setHidden:NO];
int s = 0;
int j = 10;
while ( s < j )
{
[bombButton1 setHidden:YES];
NSString *debugLabelString = [NSString stringWithFormat:#"%d", s];
debugLabel.text=debugLabelString;
s++;
}
Edit:
Right, now I have: (but I get ERROR: Expected method body on the "-(void) rootMethod: NSTimer * timer {" line)
-(void) applicationDidFinishLaunching : (UIApplication *) application {
spawnTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(rootMethod:) userInfo:nil repeats: YES];
}
-(void) rootMethod: NSTimer * spawnTimer {
int s = 0;
int j = 10;
while ( s < j )
{
NSString *debugLabelString = [NSString stringWithFormat:#"%d", s];
debugLabel.text=debugLabelString;
//debugLabel.text=#"debug test complete";
s++;
}
}
Several ways to do this, I think. Here's one:
[self performSelectorInBackground:#selector(root) withObject:nil];
You'd make this call in say, your -(void)viewDidAppear: method.
You may run into issues running code on threads other than the main thread that tries to manipulate the UI.
That sleep(1) is worrisome. You could use a repeating NSTimer instead and eliminate the sleep(1) entirely. Something like:
[NSTimer scheduledTimerWithInterval:2.0 target:self
selector:#selector(root:) userInfo:nil repeats:YES];
For NSTimer, you'd have to change your method, root, to have a signature like
- (void)root:(NSTimer*)theTimer
You need to implement a called viewDidLoad.
- (void) viewDidLoad() {
// your code here
}
I'm sure you have your reasons, but you really shouldn't iteract with UI components in anything other than the UI thread. What you actually need to do is use an NSTimer to call a method on the UI thread multiple times.
What you should be doing is performSelectorOnMainThread when you want to update the UI Thread.
Do your running in the background and update variables that will contain the updated values, then use performSelectorOnMainThread on the View, sending it to a method that will merely update the Textbox with the data in the variables.
You can do anything in a background thread, except update the ui.
Edit: Furthermore I dont recommend using Timers in place of background threads, I have had instances when using Timers, where only so many get created and when I expected a background thread to fire, it never did. The timer actually never fired, even tho it was created.