I have this code in a button click [NSThread detachNewThreadSelector: #selector(spinBegininapp) toTarget:self withObject:nil]; to show a activity indicator for user that the background thread is running ,i put this code to enable the activity-indicator
- (void)spinBegininapp
{
_activityindictor.hidden = NO;
}
and it works fine,when i click the button it shows the activity-indictor animating ,when the thread goes it hides the activity-indicator,but my need is to show a progressView instead of activity-indicator,it progresses according to the thread,and if the thread finishes it need to reach progress completely and self hide.is that possible .
#pragma mark - Loading Progress
static float progress = 0.0f;
-(IBAction)showWithProgress:(id)sender {
progress = 0.0f;
_progressView.progress = progress;
[self performSelector:#selector(increaseProgress) withObject:nil afterDelay:0.3];
}
-(void)increaseProgress {
progress+=0.1f;
_progressView.progress = progress;
if(progress < 1.0f)
[self performSelector:#selector(increaseProgress) withObject:nil afterDelay:0.3];
else
[self performSelector:#selector(dismiss) withObject:nil afterDelay:0.2f];
}
-(void)dismiss {
[self progressCompleted];
}
now call the following function whenever/where ever you want to show progress
[self showWithProgress:nil];
progress range lies between 0.0 and 1.0
1.0 means 100%
you can add a progress view no doubt but usually it used for definite quantities like time or data.. for eg. If you are downloading a 2mb file then you can always tell how much data you have downloaded and show the in the progress view as a factor. So if something similar is happening inside your thread you can use this..
UIProgressView *progressView = [[UIProgressView alloc] initWithProgressViewStyle:whateverStyle];
progressView.progress = 0.75f;
[self.view addSubview: progressView]
[progressView release];
you just need to update your progress as the value changes.... hoping this helps.
Related
I am developing an application that makes use of a UIPageViewController. I noticed that if I change multiple pages too quickly, it causes several problems at runtime.
Is there a way to set a delay (such 2 or 3 milliseconds) between two page changes?
Thanks in advance.
************ DETAILED ANSWER **************
The solution is this:
-(void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed{
if(completed) {
[pageViewController.view setUserInteractionEnabled:NO];
[self performSelector:#selector(enableUserInteraction) withObject:nil afterDelay:0.2];
}
}
-(void)enableUserInteraction{
[self.view setUserInteractionEnabled:YES];
}
In your animation block, set userInteraction = NO until the animation finishes. This mean that the user will not be able to interact with the screen and thus change the page until it finishes animating.
I put it in the pageViewController:willTransitionToViewControllers: and used dispatch_after. With this solution the user cannot swipe quickly 2-3 times like in
pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
- (void)pageViewController:(UIPageViewController *)pageViewController willTransitionToViewControllers:(NSArray<UIViewController *> *)pendingViewControllers {
pageViewController.view.userInteractionEnabled = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
pageViewController.view.userInteractionEnabled = YES;
});
}
first of all sorry for my English :-) not so good.
I have a strange memory leak with the following code (code after the explanation).
I have a class, FLWaitingView. It is a simple view with a waiting indicator (plus a view with background), used to say to the user "wait for the data to be loaded".
It has two simple methods: show and dismiss.
In the show method, I find the main Application Window and add the subviews (the waiting view and a background view, with different animations). In the dismiss method, I remove it from superview.
In every show, I verify that the view isn't already visible using a static bool var (is_visible).
The strange thing is this: In the dismiss method, I use:
[self.view removeFromSuperview];
[self.waitingView removeFromSuperview];
to remove the two views from the Window, to avoid them to be retained. They are correctly removed, I can verify this with NSLog (for cicle on each window subview). But, in INSTRUMENTS, using the "mark heap" function, I see that in every single reload (new instance of FLWaitingView, then show, then dismiss) the old instance remains in memory and continues to increase memory usage. Obviously is not a problem of the calling code, because I correctly release the object:
//CALLING CODE
//customWaitingView is a property retained
self.customWaitingView = [[[FLWaitingView alloc]init]autorelease];
[self.customWaitingView show];
Moreover, and I think that this is the most important information, if I move the view dismission in another method, called by a selector, the leak disappear!!!
Now I show the "wrong" code and, after, the "correction". I would like to understand why it happens.
- (void)show
{
if (!is_visible){
id appDelegate = [[UIApplication sharedApplication] delegate];
UIWindow *window = [appDelegate window];
self.waitingLabel.text = #"Attendere";
self.view.alpha = 1.0;
self.waitingView.alpha = 1.0;
[window addSubview:self.view];
[window addSubview:self.waitingView];
[self.waitingIndicator startAnimating];
self.view.frame = window.frame;
self.waitingView.center = window.center;
// "Pop in" animation for alert
[self doPopInAnimationWithDelegate:self];
// "Fade in" animation for background
[self doFadeInAnimation];
is_visible = YES;
} else {
NSLog(#"FLWaitingView %# already visible, do nothing", self);
}
}
- (void)dismiss
{
[UIView beginAnimations:nil context:nil];
self.view.alpha = 0.0;
self.waitingView.alpha = 0.0;
[UIView commitAnimations];
[self.waitingIndicator stopAnimating];
//here is the problem
[self.view removeFromSuperview];
[self.waitingView removeFromSuperview];
is_visible = NO;
}
the code above is the "wrong" one, but if I add
[self performSelector:#selector(alertDidFadeOut) withObject:nil afterDelay:0.5];
in the dismiss method and a new method (obviously removing the redundant code from dismiss method):
- (void)alertDidFadeOut
{
//here the memory is correctly released
[self.view removeFromSuperview];
[self.waitingView removeFromSuperview];
is_visible = NO;
}
the memory is correctly released.
Why??????
Thank you in advance
Fabio
Your view isn't getting released as you would be expecting because at the moment you're releasing it there are still animations linked to it. You can only properly release it after the animations are finished.
Your second method works because the animation lasts less than 0.5 seconds - the releasing code is called after view is freed of all the animations.
Proper way to animate the view would be to either create an animation and assign its delegate or maybe a bit more elegant soulution is to use block-based animation like this:
- (void)dismiss
{
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[UIView animateWithDuration: 0.15
animations: ^{
self.view.alpha = 0.0;
self.waitingView.alpha = 0.0;
}
completion: ^(BOOL finished){
[self.waitingIndicator stopAnimating];
[self.view removeFromSuperview];
[self.waitingView removeFromSuperview];
is_visible = NO;
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
}];
}
I have an app processing a couple of images using Quartz, and i wanted to have a UIProgressView that changes after each actions. (e.g. 0.0 0.2 0.4 0.6 0.8 1.0)
The problem is, it seems while my image is being processed the UI is completely locked, and the value only changes after all of the process is done (meaning it just gets to 1.0 without going through the sub-steps),
Did any of you ever encounter this ?
Pseudo:
for(uint i=0;i<5;i++){
// Execute some Quartz based action here, such as CGContextDrawTiledImage etc...
myProgress.progress = (i+1) * 0.2;
}
So actually instead of the progress bar changing after each action, it only changes once at the end to 1.0. Would appreciate your feedback or experience or this.
Thank you
Shai.
You'll need to update either your assets or your progress bar in a separate thread so that the two can update in parallel.
Have a look at [NSThread detachNewThreadSelector:selector toTarget:target withObject:object];
Make your progress a member variable
[NSThread detachNewThreadSelector:#selector(updateFilterProgress) toTarget:self withObject:nil];
prgLoader.progress = x;
- (void) updateFilterProgress{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
while ([prgLoader.progress floatValue] < 1.0f) //Keep this thread alive till done loading. You might want to include a way to escape (a BOOL flag)
{
GTMLoggerInfo(#"Updating progress to %f", [progress floatValue]);
prgLoader.progress = [progress floatValue];
}
[pool release];
}
When my app starts for the first time, I have a background process which runs off of a thread and the user should see a progressView which should show the progress being made. The code below shows how I am setting up my thread and progressView
//from viewDidLoad
progView.hidden = NO;
progView.progress = 0.0;
[NSThread detachNewThreadSelector:#selector(buildTable) toTarget:self withObject:nil];
-(void)buildTable
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:#selector(addData) withObject:nil waitUntilDone:NO];
[pool release];
}
the function addData is where I update the progressView using the following code -
progView.progress = 0.1
However, the progressView is visible but even though the background process is updating the progressView using the above code it is not appearing so on the screen. Do I need to use some form of StartAnimating while the background process is running?
You should only update user interface items from the main thread. You're probably not seeing any change to the progress indicator because you're trying to change it from a background thread.
A very simple strategy to do this is to call performSelectorOnMainThread from your background thread to call a simple method that updates the progress bar on the main thread.
For example, in your addData method (in the background thread) you can call:
[self performSelectorOnMainThread:#selector(updateProgressBar:) withObject:[NSNumber numberWithFloat:newProgressValue] waitUntilDone:false];
And then in your updateProgressBar method (which will run on the main thread), do the progress bar update using the given data:
- (void)updateProgressBar:(NSNumber *)progressValue {
progView.progress = [progressValue floatValue];
}
I'm trying to do something like this:
- (void)sectionChanged:(id)sender {
[self.view addSubview:loadingView];
// Something slow
[loadingView removeFromSuperview];
}
where loadingView is a semi-transparent view with a UIActivityIndicatorView. However, it seems like added subview changes don't take effect until the end of this method, so the view is removed before it becomes visible. If I remove the removeFromSuperview statement, the view shows up properly after the slow processing is done and is never removed. Is there any way to get around this?
Run your slow process in a background thread:
- (void)startBackgroundTask {
[self.view addSubview:loadingView];
[NSThread detachNewThreadSelector:#selector(backgroundTask) toTarget:self withObject:nil];
}
- (void)backgroundTask {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// do the background task
[self performSelectorOnMainThread:#selector(backgroundTaskDone) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)backgroundTaskDone {
[loadingView removeFromSuperview];
}
Two potential problems spring to mind, both centred around how you've implemented the 'do something slow here' code.
First off, if it's locking up the main thread then it's possible the application's UI isn't being redrawn in time to display the view, i.e. Add Subview, tight loop/intensive processing tying up the main thread, then immediately after the view is removed.
Secondly if the 'something slow' is being done asynchronously, then the view is being removed while the slow processing is running.
One things for sure, your requirements are as follows:
Add a subview to display some kind of 'loading' view
Invoke a slow running piece of functionality
Once the slow running functionality completes, remove the 'loading' subview.
- (void)beginProcessing {
[self.view addSubview:loadingView];
[NSThread detachNewThreadSelector:#selector(process) toTarget:self withObject:nil];
}
- (void)process {
// Do all your processing here.
[self performSelectorOnMainThread:#selector(processingComplete) withObject:nil waitUntilDone:NO];
}
- (void)processingComplete {
[loadingView removeFromSuperview];
}
You could also achieve something similar with NSOperations.