I am an iOS developer currently developing an iphone application. I have a simple question regarding updating the contents of a ViewController and I would greatly appreciate it if I can get someone’s feedback who is aware of this issue or have a suggested solution.
I’m writing a method that constantly updates the text of a label (and other components such as the background colour of a UIImage “box1” and “box2” ).
The issue : update to the text of the label (along with other changes) only takes effect at the end when the code was done execution. So I have written a simple infinite loop that does this as follows:
-(void) stateTest{
int i=0;
for(;;){
if (i==5) {
self.stateLabel.text=#"Opened"; //stateLabel previously declared
[self.box2 setBackgroundColor:[UIColor whiteColor]]; // box2 declared as UIImage
[self.box1 setBackgroundColor:[UIColor purpleColor]];
}
if (i==10){
self.stateLabel.text=#"Closed";
[self.box2 setBackgroundColor:[UIColor redColor]];
[self.box1 setBackgroundColor:[UIColor whiteColor]];
break;
}
i++;
}
}
and in the viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
self.stateLabel.text=#“View did Load";
[self.box1 setBackgroundColor:[UIColorwhiteColor]];
[self.box2 setBackgroundColor:[UIColorwhiteColor]];
[self stateTest];
}
I’m stepping through the code (breakpoints) and this is the issue I’m facing:
when i==5 the label text does NOT get updated (same as the color of the boxes)
when i==10, the label text does NOT get updated
The update (both the label text and the box color) show up after the code is done executing with
stateLabel : "Closed",
Box 2: red background color
I have tried calling upon several functions at the end of the stateTest method (after the i++) hoping that it will “REFRESH” the contents of the view, label and/or the UIImage:
[self.stateLabel setNeedsLayout];
[self.stateLabel setNeedsDisplay];
[self.box1 setNeedsDisplay];
[self.box2 setNeedsDisplay];
[self.view setNeedsDisplay];
Unfortunately non of these trials worked. I have also put an NSLog that outputs the text of the label and that works just fine, as I want. But the problem is with the dynamic update of the contents of the view controller during execution of code/method.
I would greatly appreciate any help and I am open to any suggestion
Ideally, this code is used in parallel with a face detection algorithm that detects the state of the mouth. There is a processImage method that processes the image every frame. I call [self stateTest] every time the image is processed; if the mouth is opened → stateLabel.text=#”Open”…etc
Thank you in advance for your contribution
Cheers!
The UI update will happen only when the run loop is completed. normally i run the infinite loop in background thread and post the UI update in the main thread. This will take care of the run loop.
Below code is for reference.
-(void) viewDidLoad
{
[super viewDidLoad];
self.subView = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 30, 30)];
self.subView.backgroundColor = [UIColor blueColor];
[self.view addSubview:self.subView];
dispatch_queue_t newQueue = dispatch_queue_create("newQueue", nil);
dispatch_async(newQueue, ^{
[self stateTest];
});
}
-(void) stateTest{
int i=0;
for(;;){
if (i==5) {
[self performSelectorOnMainThread:#selector(purpleColor) withObject:self waitUntilDone:NO];
}
if (i==10){
[self performSelectorOnMainThread:#selector(blueColor) withObject:self waitUntilDone:NO];
// break;
}
if (i==15){
[self performSelectorOnMainThread:#selector(redColor) withObject:self waitUntilDone:NO];
//break;
}
if (i==20){
[self performSelectorOnMainThread:#selector(greenColor) withObject:self waitUntilDone:NO];
break;
}
sleep(1);
i++;
}
}
-(void) purpleColor
{
[self.subView setBackgroundColor:[UIColor purpleColor]];
}
-(void) blueColor
{
[self.subView setBackgroundColor:[UIColor blueColor]];
}
-(void) redColor
{
[self.subView setBackgroundColor:[UIColor redColor]];
}
-(void) greenColor
{
[self.subView setBackgroundColor:[UIColor greenColor]];
}
it's not a very good idea to run a infinite loop on the main thread (uithread). Instead can I suggest you to use a timer or a dispatch event ? below is some code you can use for a timer. and I would also point you out that NSTimer cannot be re used. hence if you wish to do this operation after you invalidated it you need to re create the timer & add it into a runloop.
// properties in your controller
#property (nonatomic) BOOL state;
#property (nonatomic, strong) NSTimer *timer;
// where to create it . timer interval is in seconds & use decimals if you want split seconds.
- (void)viewDidLoad
{
[super viewDidLoad];
self.timer = [NSTimer timerWithTimeInterval:0.1 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}
// timer callback method
int count = 0;
- (void)onTimer:(NSNotification *)sender
{
self.state = !self.state;
if (self.state)
{
self.box1.backgroundColor = [UIColor redColor];
self.box2.backgroundColor = [UIColor blueColor];
}
else
{
self.box1.backgroundColor = [UIColor yellowColor];
self.box2.backgroundColor = [UIColor greenColor];
}
count++;
if (count == 100)
{
[self.timer invalidate];
[[[UIAlertView alloc] initWithTitle:#"timer done" message:#"timer finished" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil] show];
}
}
Related
I am adding activity indicator on top of the view and wish to disable the selections in the background when the activity indicator is on. Also for some reason, my activity indicator is still spins for about 30-45 seconds(depending on the network speed) after the data is displayed on the table view. I have created a category for activity indicator.
Activity Indicator category code:
- (UIView *)overlayView {
return objc_getAssociatedObject(self, OverlayViewKey);
}
- (void)setOverlayView:(UIView *)overlayView {
objc_setAssociatedObject(self, OverlayViewKey, overlayView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)showActivityIndicatorForView:(UIView *)view {
self.overlayView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
self.center = self.overlayView.center;
[view setUserInteractionEnabled:NO];
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
[self.overlayView setUserInteractionEnabled:NO];
[self startAnimating];
[self.overlayView addSubview:self];
[view addSubview:self.overlayView];
[view bringSubviewToFront:self.overlayView];
self.hidesWhenStopped = YES;
self.hidden = NO;
}
- (void)hideActivityIndicatorForView:(UIView *)view {
[self stopAnimating];
[self.overlayView setUserInteractionEnabled:YES];
[self.overlayView removeFromSuperview];
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
[view setUserInteractionEnabled:YES];
}
Usages in table view controller:
#interface MyTableViewController()
#property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
#end
#implementation MyTableViewController
- (id) initWithSomething:(NSString *)something {
self = [super init];
if (self) {
self.activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
self.activityIndicator.overlayView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self getDataServiceRequest];
[self.activityIndicator showActivityIndicatorForView:self.navigationController.view];
}
- (void)requestCompletionCallBack sender:(ServiceAPI *)sender {
// Do something here with the data
[self.activityIndicator hideActivityIndicatorForView:self.navigationController.view];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
#end
What am I doing wrong here? Why am I still able to select the data in the background when the activity indicator is on and even after disabling the user interaction.
Move your call to hideActivityIndicatorForView to inside the call to dispatch_async(dispatch_get_main_queue(). It's a UI call, and needs to be done on the main thread.
As for how to disable other actions on your view controller, you have a few options. One simple thing I've done is the put the activity indicator inside a view that's pinned to the whole screen, set to opaque=false, and with a color that's black with an alpha setting of 0.5. That way the content underneath is visible but the user can't click on it. You need to add an outlet to your "coveringView" and show-hide it instead of showing/hiding the activity indicator view.
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
fix it
[self performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
I have created a custom UIView to show an UIActivityIndicatorView and a message. Idea is to reuse this across my app for consistency. Below is the code:
#implementation CDActivityIndicator
#synthesize activityIndicator, message;
//init method called when the storyboard wants to instantiate this view
- (id) initWithCoder:(NSCoder*)coder {
if ((self = [super initWithCoder:coder])) {
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
message = [[UILabel alloc] initWithFrame:CGRectZero];
[message setBackgroundColor:[UIColor clearColor]];
[message setTextColor:[UIColor whiteColor]];
[message setTextAlignment:NSTextAlignmentCenter];
[message setFont:[UIFont fontWithName:#"HelveticaNeue" size:15.0f]];
[self addSubview:activityIndicator];
[self addSubview:message];
//set background color
[self setBackgroundColor:[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5]];
}
return self;
}
-(void) layoutSubviews {
[super layoutSubviews];
//position the indicator at the center of the view
[activityIndicator setCenter:[self center]];
//position the label
[message setFrame:[self frame]];
}
- (void) begin {
//make sure the current view is visible, and message is hidden
[self setHidden:NO];
[activityIndicator setHidden:NO];
[self bringSubviewToFront:activityIndicator];
[message setHidden:YES];
[self performSelectorOnMainThread:#selector(startAnimating) withObject:self waitUntilDone:YES];
}
- (void) startAnimating {
if( !activityIndicator.isAnimating ) {
[activityIndicator startAnimating];
}
}
To try this out, I added a UIView to one of my views in the storyboard and set the class for that view to CDActivityIndicator. When I call begin() from the corresponding View Controller, the CDActivityIndicator gets shown as an empty view with the expected background color. However, the activity indicator doesn't show. All methods are getting called as expected.
Any idea what I might be missing? Thanks!
You should change your subviews frame setting like this.
CGPoint point = [self.superview convertPoint:self.center toView:self];
[activityIndicator setCenter:point];
[message setFrame:self.bounds];
The frame defines the origin and dimensions of the view in the coordinate system of its superview. Every UIView has its own coordinate system. You should take this into account.
I developed an iOS app that has two UIButton however the buttons seem to be lagging sometimes when I press them. Sometimes the lag is really bad (takes 10 seconds sometimes). I am almost positive it has something to do with the NSTimer I am using. I just want to make it so the UIViewControllers are switched as soon as the buttons are pressed, I don't want there to be any delay at all. Here is my code:
RealTimeModeViewController.m
#import "RealTimeModeViewController.h"
#interface RealTimeModeViewController ()
#end
#implementation RealTimeModeViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(UpdateTime:)
userInfo:nil
repeats:YES];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void)UpdateTime:(id)sender
{
// This is where I do everything in my app
}
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(UpdateTime:)
userInfo:nil
repeats:YES];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void)UpdateTime:(id)sender
{
// Most of code goes here
}
#end
The two UIButton are connected to each other ViewController via Modal style. Maybe I am supposed to put the buttons in the main thread? I am not very familiar with threads. Thank you.
If I had to guess, you are doing something in the UpdateTime: method that is time consuming. Because it is on the main thread and it is running every 1 second, you are probably slowing down everything else. UIButton events are on the main thread, so it's likely that because you are doing everything on the main thread, it is "clogged".
For a timer implementation that won't be as accurate, but will not hang the main thread, see this answer: https://stackoverflow.com/a/8304825/3708242
If you must have the consistency of the NSTimer implementation, try reducing the amount of stuff that UpdateTime: does.
I've had this problem before. You should use UIActivityIndicatorView
The button delay is probably caused by the UpdateTime method. Use UIActivityIndicatorView in ViewWillAppear of ViewDidLoad then perform the UpdateTime method.
Here is what I mean:
-(void) ViewDidLoad
{
UIActivityIndicatorView *act = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
[act setFrame:CGRectMake(320/2-50, 130, 100, 100)];
act.layer.cornerRadius = 10;
[act.layer setBackgroundColor:[[UIColor colorWithWhite: 0.0 alpha:0.30] CGColor]];
UILabel *lable = [[UILabel alloc] initWithFrame:CGRectMake(0, 80, act.frame.size.width, 20)];
[lable setText:[NSString stringWithFormat:#"%#", string]];
[lable setFont:[UIFont fontWithName:#"Helvetica-Light" size:12.0f]];
[lable setTextAlignment:NSTextAlignmentCenter];
[lable setTextColor:[UIColor whiteColor]];
[act addSubview:lable];
[self.view addSubview: act];
[act startAnimating];
// perform the method here, and set your delay time..
[self performSelector:#selector(UpdateTime:) withObject:nil afterDelay:1.0];
}
Then...
- (void)UpdateTime:(id)sender
{
// Most of your code
}
Comment and tell me if this helps :)
I have an animation that is not animating for some reason.
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.navigationBar.hidden = YES;
self.nameLabel.text = [self.name uppercaseString];
self.gradient1.alpha = .9;
self.gradient1.image = [UIImage imageNamed:#"heart.png"];
self.gradient1.animationImages = [[NSArray alloc] initWithObjects:
[UIImage imageNamed:#"gradiant1.png"],
[UIImage imageNamed:#"gradiant2.png"],
...
[UIImage imageNamed:#"gradiant26.png"], nil];
self.gradient1.animationDuration = .5;
self.gradient1.animationRepeatCount = 1;
[self updateStats:nil];
}
-(void)updateStats:(NSTimer*)timer{
Utilities *utility = [[Utilities alloc] init];
[utility getDataWithSSID:self.ssID completion:^(bool *finished, NSArray *objects) {
if (finished){
...
[self.gradient1 startAnimating];
}
self.twoSecTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self
selector:#selector(updateStats:)
userInfo:nil
repeats:NO];
}];
}
Sometimes it animates once or twice and then stops even though the timer continues to hit over and over. Other times it just doesn't animate at all. However, I have a couple placeholder buttons that I put on the storyboard that aren't tied to anything and whenever I press one of the buttons it animates the next time the timer hits (Actually whenever I hit the screen even though I don't have any sort of gesture recognizers..). Also, when I add in all the other garbage I want to do in this ViewController it animates every other time it's being called. So I think it's something deeper than me forgetting so set some property.
It might be a better idea to move the animation trigger to a moment when that view is actually visible - hence to viewDidAppear:.
Also your way of creating a recursive timer scheduling makes me shiver. I guess it works fine for you but I would definitely suggest to use the repeats flag set to YES instead.
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.navigationBar.hidden = YES;
self.nameLabel.text = [self.name uppercaseString];
self.gradient1.alpha = .9;
self.gradient1.image = [UIImage imageNamed:#"heart.png"];
self.gradient1.animationImages = [[NSArray alloc] initWithObjects:
[UIImage imageNamed:#"gradiant1.png"],
[UIImage imageNamed:#"gradiant2.png"],
...
[UIImage imageNamed:#"gradiant26.png"], nil];
self.gradient1.animationDuration = .5;
self.gradient1.animationRepeatCount = 1;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//animate right away...
[self updateStats:nil];
//trigger new animations every two seconds
self.twoSecTimer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self
selector:#selector(updateStats:)
userInfo:nil
repeats:YES];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self stopUpdatingStats];
}
-(void)stopUpdatingStats
{
[self.twoSecTimer invalidate];
}
-(void)updateStats:(NSTimer*)timer
{
NSLog(#"updating");
[self.gradient1 startAnimating];
}
I'm trying to set the progress of a UIProgressView, but the progressView doesn't "update".
Why? Do you know what I am doing wrong?
Here is my code in ViewController.h:
IBOutlet UIProgressView *progressBar;
That's the code in ViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
progressBar.progress = 0.0;
[self performSelectorOnMainThread:#selector(progressUpdate) withObject:nil waitUntilDone:NO];
}
-(void)progressUpdate {
float actual = [progressBar progress];
if (actual < 1) {
progressBar.progress = actual + 0.1;
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self `selector:#selector(progressUpdate) userInfo:nil repeats:NO];`
}
else {
}
}
Try calling [progressBar setProgress:<value> animated:YES]; instead of progressBar.progress = <value>
EDIT: Take a look at this, which looks a lot like your example: Objective c : How to set time and progress of uiprogressview dynamically?
Double check that progressBar is linked correctly in Interface Builder.
If it still doesn't work, try using [progressBar setProgress:0]; and [progressBar setProgress:actual+0.1];.
Also know that the UIProgressView has a [progressBar setProgress:<value> animated:YES]; method. May look cleaner.
Its should look like this:
- (void)viewDidLoad
{
[super viewDidLoad];
progressBar.progress = 0.0;
[self performSelectorInBackground:#selector(progressUpdate) withObject:nil];
}
-(void)progressUpdate {
for(int i = 0; i<100; i++){
[self performSelectorOnMainThread:#selector(setProgress:) withObject:[NSNumber numberWithFloat:(1/(float)i)] waitUntilDone:YES];
}
}
- (void)setProgress:(NSNumber *)number
{
[progressBar setProgress:number.floatValue animated:YES];
}
I had the same problem this morning. It appeared that the progressBar was partly over a UITableView. I moved it a little, so that it was not overlapping anymore.
I'm not sure why this is. It should have been working in the original case, but that is how I solved it.