I have started playing with UIProgressView in iOS5, but havent really had luck with it. I am having trouble updating view. I have set of sequential actions, after each i update progress. Problem is, progress view is not updated little by little but only after all have finished. It goes something like this:
float cnt = 0.2;
for (Photo *photo in [Photo photos]) {
[photos addObject:[photo createJSON]];
[progressBar setProgress:cnt animated:YES];
cnt += 0.2;
}
Browsing stack overflow, i have found posts like these - setProgress is no longer updating UIProgressView since iOS 5, implying in order for this to work, i need to run a separate thread.
I would like to clarify this, do i really need separate thread for UIProgressView to work properly?
Yes the entire purpose of progress view is for threading.
If you're running that loop on the main thread you're blocking the UI. If you block the UI then users can interact and the UI can't update. YOu should do all heavy lifting on the background thread and update the UI on the main Thread.
Heres a little sample
- (void)viewDidLoad
{
[super viewDidLoad];
[self performSelectorInBackground:#selector(backgroundProcess) withObject:nil];
}
- (void)backgroundProcess
{
for (int i = 0; i < 500; i++) {
// Do Work...
// Update UI
[self performSelectorOnMainThread:#selector(setLoaderProgress:) withObject:[NSNumber numberWithFloat:i/500.0] waitUntilDone:NO];
}
}
- (void)setLoaderProgress:(NSNumber *)number
{
[progressView setProgress:number.floatValue animated:YES];
}
Define UIProgressView in .h file:
IBOutlet UIProgressView *progress1;
In .m file:
test=1.0;
progress1.progress = 0.0;
[self performSelectorOnMainThread:#selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO];
- (void)makeMyProgressBarMoving {
NSLog(#"test %f",test);
float actual = [progress1 progress];
NSLog(#"actual %f",actual);
if (progress1.progress >1.0){
progress1.progress = 0.0;
test=0.0;
}
NSLog(#"progress1.progress %f",progress1.progress);
lbl4.text=[NSString stringWithFormat:#" %i %%",(int)((progress1.progress) * 100) ] ;
progress1.progress = test/100.00;//actual + ((float)recievedData/(float)xpectedTotalSize);
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:#selector(makeMyProgressBarMoving) userInfo:nil repeats:NO];
test++;
}
I had similar problem. UIProgressView was not updating although I did setProgress and even tried setNeedsDisplay, etc.
float progress = (float)currentPlaybackTime / (float)totalPlaybackTime;
[self.progressView setProgress:progress animated:YES];
I had (int) before progress in setProgress part. If you call setProgress with int, it will not update like UISlider. One should call it with float value only from 0 to 1.
Related
I have two methods that both create a random sprite node and add it to the scene. Let's call them spriteMethod1 and spriteMethod2.
I'd like to have a looping method that runs spriteMethod1, 5 times, and then spriteMethod2 once. There also needs to be a delay between each time the spriteMethods are called.
I thought the following might work, but it doesn't:
-(void) addObjects {
for (int i = 0; i < 5; i++) {
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:2];
}
[self performSelector:#selector(spriteMethod2) withObject:nil afterDelay:3];
[self performSelector:#selector(addObjects) withObject:nil afterDelay:5];
}
I know this might not be the best solution, but it works for me:
-(void)addObjects {
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:2];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:4];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:6];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:8];
[self performSelector:#selector(spriteMethod1) withObject:nil afterDelay:10];
[self performSelector:#selector(spriteMethod2) withObject:nil afterDelay:13];
[self performSelector:#selector(addObjects) withObject:nil afterDelay:18];
}
Add a timer in your interface:
#property (nonatomic, weak) NSTimer *timer;
schedule a timer somewhere in your code
self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(spriteMethod1) userInfo:nil repeats:YES];
do this
int count = 0;
- (void)spriteMethod1 {
count ++;
// do your work here
if(count == 5) {
// stop the timer
[self.timer invalidate];
// call the other methods
[self performSelector:#selector(spriteMethod2) withObject:nil afterDelay:3];
[self performSelector:#selector(addObjects) withObject:nil afterDelay:5];
}
}
The problem is that you won't see the delay because the selector is enqueued on the thread loop. From the documentation:
This method sets up a timer to perform the aSelector message on the
current thread’s run loop. The timer is configured to run in the
default mode (NSDefaultRunLoopMode). When the timer fires, the thread
attempts to dequeue the message from the run loop and perform the
selector. It succeeds if the run loop is running and in the default
mode; otherwise, the timer waits until the run loop is in the default
mode.
Another approach is to make the thread sleep for a few seconds.
[self spriteMethod1];
[NSThread sleepForTimeInterval:2.0f];
In this case your User Interface will hang if you don't execute this code in a separate thread.
This out the top of my head.. not sure if it'll do the trick:
- (void)startAddingObjects
{
NSNumber *counter = #(0);
[self performSelector:#selector(addMoreUsingCounter:)
withObject:counter
afterDelay:2];
}
- (void)addMoreUsingCounter:(NSNumber *)counter
{
int primCounter = [counter intValue];
if (primCounter < 5)
{
[self spriteMethod1];
[self performSelector:#selector(addMoreUsingCounter:)
withObject:#(primCounter++)
afterDelay:2];
}
else if (primCounter < 7)
{
[self spriteMethod2];
[self performSelector:#selector(addMoreUsingCounter:)
withObject:#(primCounter++)
afterDelay:3];
}
}
You'll probably still need to relate the delay to the counter to get the exact results you need.
I think your version doesn't work, because the "afterDelay" param is relative to the moment of invocation. You would need to multiply it with "i", in the for loop, and then use 13 and 18 respectively for the last two selectors.
Look into using an NSOperationQueue. You can set its maxConcurrentOperationCount to 1, to ensure it executes its actions sequentially. E.g.
NSOperationQueue * opQueue = [[NSOperationQueue alloc] init];
opQueue.maxConcurrentOperationCount = 1; // this ensures it'll execute the actions sequentially
NSBlockOperation *spriteMethod1Invoker = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; ++i)
{
[self spriteMethod1];
sleep(2); // sleep 2 secs between invoking spriteMethod1 again
}
}];
NSInvocationOperation *spriteMethod2Invoker = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(spriteMethod2) object:nil];
[opQueue addOperation:spriteMethod1Invoker];
[opQueue addOperation:spriteMethod2Invoker];
I am using animateWithDuration to change the value of a UISlider:
[UIView animateWithDuration:1.0f
delay:0.0f
options:UIViewAnimationOptionCurveEaseInOut
animations:^{ [myUISlider setValue:10]; }
completion:^(BOOL finished){ }];
The problem is, I need to be able to display the current value of the UISlider while it is changing. Is this even possible using animateWithDuration? I tried creating a UISlider subclass and overriding setValue in hopes of getting access to the slider's value as it is being changed:
-(void)setValue:(float)newValue
{
[super setValue:newValue];
NSLog(#"The new value is: %f",newValue);
}
But that code only gets called at the very end of the animation block. In a way that makes perfect sense, since I really only called setValue once. But I was hoping that the animation block would somehow call it over and over again within its internal mechanisms, but that appears not to be the case.
If UIView animateWithDuration is a dead-end here, I wonder if there is a better way to acheive this same functionality with something else? Perhaps another slick little block-driven part of the SDK I don't know about which allows animating more than just UIView parameters?
I think the best way is to handle it is to code a custom Slide you can creat it like progress bar which there are many demo on github.
You can also use NSTimer to do it , but I do not think it is a very better way.
When you tap , you can creat a timer :
_timer = [NSTimer scheduledTimerWithTimeInterval:.1 target:self selector:#selector(setValueAnimation) userInfo:nil repeats:YES];
and set a ivar: _value = oldValue;
in the setValueAnimation method :
- (void)setValueAnimation
{
if (_value >= newValue) {
[_timer invalidate];
_value = newValue;
[self setVale:_value];
return;
}
_value += .05;
[self setVale:_value];
}
UPDATE
how to add block:
1st:you can define a block handler :
typedef void (^CompleteHandler)();
2nd: creat your block and added it into the userInfo :
CompleteHandler block = ^(){
NSLog(#"Complete");
};
NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:block,#"block", nil];
3rd: make the NSTimer:
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(setValueAnimation:) userInfo:userInfo repeats:YES];
4th:achieve your timer method:
- (void)setValueAnimation:(NSTimer *)timer
{
if (_value >= newValue) {
[_timer invalidate];
_value = newValue;
[self setVale:_value];
// also can use [_delegate complete];
CompleteHandler block = [timer.userInfo objectForKey:#"block"];
if (block) {
block();
}
return;
}
_value += .05;
[self setVale:_value];
}
I do not know if there is any sort of notification that you could "tap into" to get the UIView animation "steps", but you can do the animations "manually" using an NSTimer, and that will allow you to continuously update your value.
If there's an out of the box solution using UIView - I'm all ears. This animation framework (only 2 classes!) - PRTween https://github.com/chris838/PRTween will give you access to convenience timing functions and also the changed value.
Here's an updated project of mine https://github.com/jdp-global/KACircleProgressView/
PRTweenPeriod *period = [PRTweenPeriod periodWithStartValue:0 endValue:10.0 duration:1.0];
PRTweenOperation *operation = [PRTweenOperation new];
operation.period = period;
operation.target = self;
operation.timingFunction = &PRTweenTimingFunctionLinear;
operation.updateSelector = #selector(update:)
[[PRTween sharedInstance] addTweenOperation:operation]
- (void)update:(PRTweenPeriod*)period {
[myUISlider setValue:period.tweenedValue];
}
I am new to iOS development, I have plain objective -c class "MoneyTimer.m" for running timer, from there i want to update the an UI label with the changing value of timer.
I want to Know how to access the UI element from non UI thread ? I am Using Xcode 4.2 and storyboarding.
In blackberry simply by getting the event lock one can update the UI's from non UI thread.
//this the code from MyTimerClass
{...
if(nsTimerUp == nil){
nsTimerUp = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(countUpH) userInfo:nil repeats: YES];
...}
(void) countUpH {
sumUp = sumUp + rateInSecH;
**//from here i want to update the UI label **
...
}
This is the quickest and simplest way to do it is:
- (void) countUpH{
sumUp = sumUp + rateInSecH;
//Accessing UI Thread
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//Do any updates to your label here
yourLabel.text = newText;
}];
}
If you do it this way you don't have to switch to a different method.
Hope this helps.
Sam
Your question doesn't give much info or detail so it is hard to know exactly what you need to do (e.g. if there is a "thread" issue at all, etc.).
At any rate, assuming your MoneyTimer instance has a reference to the current viewController you can use performSelectorOnMainThread.
//
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
The proper way is this:
- (void) countUpH {
sumUp = sumUp + rateInSecH;
//Accessing UI Thread
dispatch_async(dispatch_get_main_queue(), ^{
//Do any updates to your label here
yourLabel.text = newText;
});
}
I've done something identical in the past.
I used a function to set the label text:
- (void)updateLabelText:(NSString *)newText {
yourLabel.text = newText;
}
and then called this function on the main thread with performSelectorOnMainThread
NSString* myText = #"new value";
[self performSelectorOnMainThread:(#selector)updateLabelText withObject:myText waitUntilDone:NO];
Assuming that label lives in the same class:
if(nsTimerUp == nil){
nsTimerUp = [NSTimer scheduledTimerWithTimeInterval: 1.0 target:self selector:#selector(countUpH) userInfo:nil repeats: YES];
[self performSelectorOnMainThread:#selector(updateLabel)
withObject:nil
waitUntilDone:NO];
}
-(void)updateLabel {
self.myLabel.text = #"someValue";
}
I'm new to programming for the iPhone and i'm having a problem with this function
-(IBAction)changeX {
[self timere:field1];
[self timere:field2];
}
this is a button to move an uitextfield object across the screen. The problem is i want to run this method on the first field , complete it then go on to the second. The timere function uses NSTimer to continously move the object until it reaches a certain point at which it terminates. I have two other functions shown below. The actual program im making is much longer but the objective is the same and that code is too long. The problem is running the first function then the second. Thank you for the help.
-(void)timere:(UITextField*)f1 {
UITextField*fielder1=f1;
NSInvocation*timerInvocation = [NSInvocation invocationWithMethodSignature:
[self methodSignatureForSelector:#selector(moveP:)]];
[timerInvocation setSelector:#selector(moveP:)];
[timerInvocation setTarget:self];
[timerInvocation setArgument:&fielder1 atIndex:2];
timer1 = [NSTimer scheduledTimerWithTimeInterval:0.03 invocation:timerInvocation repeats:YES];
}
-(void) moveP:(UITextField*)f1 {
UITextField*fielder1=f1;
fielder1.center = CGPointMake(fielder1.center.x+4, fielder1.center.y);
if (fielder1.center.x>237) {
[timer1 invalidate];
}
}
The standardized method of animating UIView objects is by using methods such as the following.
+ animateWithDuration:delay:options:animations:completion:
The syntax for these methods might be a bit daunting if you are unfamiliar with Objective-C Blocks. Here's a usage example which moves the first field, then the second field afterwards.
[UIView animateWithDuration:0.3 animations:^{
[field1 setCenter:CGPointMake(FINAL_CENTER_X1, FINAL_CENTER_Y1)];
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
[field2 setCenter:CGPointMake(FINAL_CENTER_X2, FINAL_CENTER_Y2)];
}];
}];
EDIT: For a general fix on your specific problem, I would make the following modifications:
-(IBAction)changeX {
[self timere:field1];
}
-(void) moveP:(UITextField*)f1 {
UITextField*fielder1=f1;
fielder1.center = CGPointMake(fielder1.center.x+4, fielder1.center.y);
if (fielder1.center.x>237) {
[timer1 invalidate];
// I'm really not sure about the scope of field1 and field2, but
// you can figure out the encapsulation issues
if (f1 == field1) {
[self timere:field2];
}
}
}
A more generic and indeed low-level solution would be to have a state variable. Perhaps you would have some sort of NSArray *fields of UITextField * and int state = 0. Where I added the conditional in moveP above, you would state++ and call [self timere:[fields objectAtIndex:state]] if state < [fields count]. You timer invalidation code is correct, anyway.
I've already searched for the problem, and the only hint was the performSelectorOnMainThread solution. I did that, but the view is not updating while the for loop is running. After the loop the progress value is displayed correctly – but that's not the point of an "Progress" View =)
The for loop is within a controller. I call a method to set the progress value:
CGFloat pr = 0.5; // this value is calculated
[self performSelectorOnMainThread:#selector(loadingProgress:) withObject:[NSNumber numberWithFloat:pr] waitUntilDone:NO];
And this is the loading progress method
-(void)loadingProgress:(NSNumber *)nProgress{
NSLog(#"Set Progress to: %#", nProgress);
[[self progress] setProgress:[nProgress floatValue]];
}
I also tried to put the method with the for loop into a background thread by using this at the top of the method:
if([NSThread isMainThread]) {
[self performSelectorInBackground:#selector(importData) withObject:nil];
return;
}
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Maybe somebody can help me with this.
CGFloat pr = 0.5; // this value is calculated
[self performSelectorInBackground:#selector(loadingProgress:) withObject:[NSNumber numberWithFloat:pr]];
try this to set the progress value