UILabel not beeing displaying instantly using thread - ios

I want to load some views in thread to avoid UI freeze waiting loading end.
I'm not used to thread so I make a quick test. My code just try to create views in a thread and add this views in the current viewcontroller view in the main thread.
My UIView is working, but for my UILabel I have to wait between 20-60s to have it on the screen.
I make a test with a UIButton and in this case the button display instantly but the label inside the button display with the same delay than my UILabel.
The only way to have it working as I want is to add a [lbl setNeedsDisplay]; in the main thread to force the UILabel to be displayed instantly. Why? Is it possible to do the job without this line?
dispatch_queue_t queue = dispatch_queue_create("myqueue", NULL);
dispatch_async(queue, ^{
// NEW THREAD
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 100, 48)];
lbl.text = #"FOO";
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
view.backgroundColor = [UIColor redColor];
// MAIN THREAD
dispatch_async(dispatch_get_main_queue(), ^{
[self.view addSubview:lbl];
[lbl setNeedsDisplay]; // Needeed to see the UILabel. WHY???
[self.view addSubview:view];
});
});
dispatch_release(queue);

You should set the text of the label on the main queue as well:
dispatch_async( dispatch_get_main_queue(), ^{
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 100, 48)]
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)];
lbl.text = #"FOO";
[self.view addSubview:lbl];
[self.view addSubview:view];
});
It best to keep all UI stuff on the main queue.
UPDATE:
It appears initWithFrame: is not thread safe (found on the SO answer, see also Threading Considerations in the UIView docs).

Related

UIActivityIndicator doesn't stop in iOS

I am showing spinner(UIActivityIndicator) while download is going on. It shows spinner as download starts, but it doesn't stop showing once download is finished. i have seen some question for the same issue but did not help. I need to make it work in ios7 as well as iOS8.
I am using below code...
- (void) startSpinner:(NSString *)message {
container = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
container.center = self.view.center;
container.backgroundColor = [UIColor colorWithRed:0.9 green:0.9 blue:0.9 alpha:0.4];
container.layer.cornerRadius = 10;
container.layer.masksToBounds = true;
container.tag = 134;
activityLabel = [[UILabel alloc] init];
activityLabel.frame = CGRectMake(0, 70, 100, 30);
activityLabel.text = message;
activityLabel.textColor = [UIColor darkGrayColor];
activityLabel.textAlignment = NSTextAlignmentCenter;
activityLabel.font = [UIFont boldSystemFontOfSize:10];
[container addSubview:activityLabel];
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.center = CGPointMake(container.frame.size.width/2, container.frame.size.height/2);
[container addSubview:activityIndicator];
[self.view addSubview:container];
[activityIndicator startAnimating];
}
-(void)stopSpinner{
[activityIndicator stopAnimating];
container = [self.view viewWithTag:134];
[container removeFromSuperview];
activityIndicator = nil;
NSLog(#"activity indicator should be removed %#",activityIndicator);
}
And one more issue it does not show spinner in the center of the device screen.
Any help will be appreciated....
There are a few things wrong here.
container.center = self.view.center;
If you are trying to center the container in self.view, then this won't do it. self.view.center is the center of the view's frame but you would need to use the center of the view's bounds. Here is the corrected code:
container.center = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
You also need to change how you set the activity indicator's frame:
activityIndicator.center = CGPointMake(CGRectGetMidX(container.bounds), CGRectGetMidY(container.bounds));
Also, if you call startSpinner more than once, you can have more than one spinner added to the view, which could possibly be the reason your spinner doesn't seem to be stopping. I recommend that you alloc and init only one spinner and do it in the class constructor. You can do the same with the container and activityLabel objects. Alternatively, you could call stopSpinner at the top of startSpinner (and add some null-checks in stopSpinner to avoid referencing a null pointer).
It's impossible to say for sure based on your code why it appears that the spinner doesn't stop, but my guess is that you're calling startSpinner several times in a row without any intervening call to stopSpinner.
If you're only calling stopSpinner once then verify that you're doing so in the main thread.
You can add the spinner view on navigation controller, so it will look at the center as you want. I think this will solve your problem.
[self.navigationController.view addSubview:coverView];
And don't forget to remove it from self.navigationController.view

Preventing UICollectionView From Redrawing

My aim is to display a row of lazy loaded thumbnails (number not known) using a UICollectionView, the user can then scroll the thumbnails horizontally.
I am using the code below to achieve close to what I am after but I am getting an error.
The first 4 thumbnails load okay but as you scroll the thumbnails start to get drawn on top of each other and in random orders.
I am trying to get it so that it achieves the following:
Item 1 ----- Item 2 ----- Item 3 ---- Item 4
I would like it so that the cells are only drawn once similar to what you would get when you use a UITableView. I only want the UICollectionView to reload the data when I call for it.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewCell *cell=[collectionView dequeueReusableCellWithReuseIdentifier:#"cellIdentifier" forIndexPath:indexPath];
UIView *tmpView;
if([self.selectedEffects isEqualToString:#"Effects"]){
int index = indexPath.row;
NSLog(#"%i",index);
tmpView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 70, 90)];
[tmpView setBackgroundColor:[UIColor clearColor]];
UIImageView *imageViewWithEffect = [[UIImageView alloc] initWithFrame:CGRectMake(5, 0, 60, 60)];
imageViewWithEffect.layer.cornerRadius = 10.00;
imageViewWithEffect.clipsToBounds = YES;
UILabel *labelEffect = [[UILabel alloc] initWithFrame:CGRectMake(0, 65, 70, 20)];
[labelEffect setText:[[self.activeEffectsArray objectAtIndex:index] objectAtIndex:1]];
labelEffect.textAlignment = NSTextAlignmentCenter;
labelEffect.textColor = [UIColor whiteColor];
[labelEffect setFont:[UIFont boldSystemFontOfSize:8]];
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[activityIndicator setFrame:CGRectMake(0, 0, 60, 60)];
activityIndicator.alpha = 1.0;
[activityIndicator startAnimating];
UIButton *buttonSelect = [UIButton buttonWithType:UIButtonTypeCustom];
[buttonSelect setFrame:CGRectMake(0, 0, 60, 90)];
[buttonSelect setTag:index];
[buttonSelect addTarget:self action:#selector(applyEffects:) forControlEvents:UIControlEventTouchUpInside];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
UIImage *imageWithEffect = [self editImage:[[self.activeEffectsArray objectAtIndex:indexPath.row] objectAtIndex:0] ImageToEdit:self.thumbnailImage];
dispatch_async(dispatch_get_main_queue(), ^{
[imageViewWithEffect setImage:imageWithEffect];
[activityIndicator removeFromSuperview];
});
});
[tmpView addSubview:imageViewWithEffect];
[tmpView addSubview:labelEffect];
[tmpView addSubview:activityIndicator];
[tmpView addSubview:buttonSelect];
}
[cell addSubview:tmpView];
return cell;
}
You are adding subviews to the cell every time. This is incorrect because the cells may have been reused. Check if the subviews have already been created, and modify them if they're already there. You can do this by subclassing UICollectionViewCell, or using viewWithTag: (the former is preferable). There are hundreds of examples of this around so I won't rehash here.
You also are referencing the same views in your dispatch block. This is also incorrect, because the cell may have been reused by the time the block is called. Instead, use the indexPath to get the cell again, and modify it that way.
Finally, you may want to consider rounding the image corners on a background thread instead of using layer.cornerRadius - this approach will cause the image to be blended, which can cause choppy scrolling animations when you display lots of images at once.

iOS7 : UIImageView Takes Forever to Appear

I have a UIView which creates and inserts views using GCD. It's working on iOS7 but the UIImageViews takes forever to show.
Here's the sample code that I am using.
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 120)];
for (int i = 0; i < 200; i++) {
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(77 * i, 0, 77, 88)];
[scrollView addSubview:view];
dispatch_queue_t queue = dispatch_queue_create("image", NULL);
dispatch_async(queue, ^{
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"photo#hd.png"]];
imageView.frame = CGRectMake(0, 0, 77, 88);
[view addSubview:imageView];
dispatch_async(dispatch_get_main_queue(), ^{
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
label.text = [NSString stringWithFormat:#"Name %d", i];
[label sizeToFit];
label.center = CGPointMake(38.5, 20);
[imageView addSubview:label];
});
});
[scrollView setContentSize:CGSizeMake(view.frame.size.width * (i+1), view.frame.size.height)];
}
[self.view addSubview:scrollView];
By the way, I am creating many UIViews that's why it is threaded. Around 200 views at max.
How should I make it work? The methods inside the dispatch blocks are being called, it is just the UIViews are not being displayed.
Thanks!

iOS Adding Image to Tableview Background View

I have a tableview that I am using to display data loaded from a web service. Sometimes it loads fast, other times it take awhile. While it's loading, the tableview just shows a blank screen (in my case it is a gray screen). I want to display in the tableview background view a simple image that says loading and has a loading icon that I will animate to spin, but I cannot get it to show up at all. Here is my code:
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, self.view.bounds.size.height)];
[backgroundView setBackgroundColor:[UIColor colorWithRed:227.0 / 255.0 green:227.0 / 255.0 blue:227.0 / 255.0 alpha:1.0]];
self.tableLoading.image = [UIImage imageNamed:#"loading.png"];
self.tableLoading.frame = CGRectMake(145, 80, 30, 30);
[backgroundView addSubview:self.tableLoading];
[self.feedTableView setBackgroundView:backgroundView];
The background view shows up as expected, hence the gray background, but I CANNOT get the image to show up at all.
Any suggestions? This seems like a simple problem but I have already spent a long time at it with no success. Thanks in advance!
You can avoid using your UIView and instead you can use an activity indicator.
Try something like this:
-(void)viewDidLoad{
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, self.view.bounds.size.height)];
backgroundView.tag=1;
[backgroundView setBackgroundColor:[UIColor colorWithRed:227.0 / 255.0 green:227.0 / 255.0 blue:227.0 / 255.0 alpha:1.0]];
self.tableLoading.image = [UIImage imageNamed:#"loading.png"];
self.tableLoading.frame = CGRectMake(145, 80, 30, 30);
[backgroundView addSubview:self.tableLoading];
[self.view addSubview:backgroundView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Do your web service call asynchronous
dispatch_sync(dispatch_get_main_queue(), ^{
//All UI needs to be here, in this thread
// remove from superview your loading image to show your tableview content
for (UIView *subview in [self.view subviews]) {
if (subview.tag == 1) {
[subview removeFromSuperview];
}
}
[self.yourTableView reloadData];
});
});
}
How are you doing your webservice call? I suppose it's done on an asynchronous block. Maybe something like AFNetworking NSOperation block? If so, where are you setting the image as a background? All user interface (UI) stuff should not be done on a background thread, it should be done on the main thread since the main thread is the only one who should be involved to UI.
Try the following:
dispatch_async(dispatch_get_main_queue(), ^{
UIView *backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, self.view.bounds.size.height)];
[backgroundView setBackgroundColor:[UIColor colorWithRed:227.0 / 255.0 green:227.0 / 255.0 blue:227.0 / 255.0 alpha:1.0]];
self.tableLoading.image = [UIImage imageNamed:#"loading.png"];
self.tableLoading.frame = CGRectMake(145, 80, 30, 30);
[backgroundView addSubview:self.tableLoading];
[self.feedTableView setBackgroundView:backgroundView];
});
You can start displaying the UITableView background image when you start the request, and just dismiss it right after the request is finishing networking.
For instance, SLRequest has a sending request method called performRequestWithHandler: which has a handler to be called when the request is done, within that handler, you can dismiss the custom indicator or remove it from your view controller.
Hope this help.

Displaying UIActivityIndicatorView until UIView is loaded

In my UIViewController's loadView method I start a UIActivityIndicator. Later in loadView I load a complex UIView, which takes about 2-3 secs to load. (UIScrollView with a lot of pictures more specifically). My problem is that the UIActivityIndicator spinner is only visible, after my other layer has loaded too. (which is of course useless to me). What is a correct way to handle this?
- (void)loadView {
CGRect fullScreenRect=[[UIScreen mainScreen] bounds];
UIView *contentView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 340, fullScreenRect.size.width)]autorelease];
self.view = contentView;
spinner = [[[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite] autorelease];
spinner.frame = CGRectMake(0.0, 0.0, 40.0, 40.0);
spinner.center = self.view.center;
[self.view addSubview:spinner];
[spinner startAnimating];
scrollView=[[[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 340, fullScreenRect.size.width)]autorelease];
...setting up scrollView...
[self.view addSubview:scrollView];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[spinner removeFromSuperview];
}
I have found a somewhat similar thread:
UIActivityIndicatorView not showing until after loading done
But it suggests to load things in a background thread, but loading and displaying a UIView is only possible in the main thread as far as I know.
I am a beginner, and sorry if my question is fundamentally wrong.
I managed to make it the way Carl Veazey suggested. It was pretty easy actually. I spawned a new background thread, and loaded my UIScrollView there.
I used, in LoadView:
spinner = [[[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite] autorelease];
spinner.frame = CGRectMake(0.0, 0.0, 40.0, 40.0);
spinner.center = self.view.center;
[self.view addSubview:spinner];
[spinner startAnimating];
[self performSelectorInBackground:#selector(populateScrollView) withObject:nil];
And:
-(void) populateScrollView{
...creating scrollView...
[self.view addSubview:scrollView];
[spinner removeFromSuperview];
}
It works perfectly. What is really weird for me, is that I am adding the scrollview to the UI in a background thread, not in the main thread. I thought I could only mess with the UI in the main thread.
(I could do the same thing with GCD. When the background thread finished loading the scrollview, I could display scrollview in the main thread's queue. But the former solution somehow works as well...)
Sorry for ugly text formatting.
There is a very cute way to do what you want.
Firstly you have to show the indicator view and only after a little amount of time (0.1 sec or even lesser) you ask your scrollView to populate.
- (void)loadView {
CGRect fullScreenRect=[[UIScreen mainScreen] bounds];
UIView *contentView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 340, fullScreenRect.size.width)]autorelease];
self.view = contentView;
spinner = [[[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite] autorelease];
spinner.frame = CGRectMake(0.0, 0.0, 40.0, 40.0);
spinner.center = self.view.center;
[self.view addSubview:spinner];
[spinner startAnimating];
double delayInSeconds = 0.1;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
scrollView=[[[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 340, fullScreenRect.size.width)]autorelease];
...setting up scrollView...
[self.view addSubview:scrollView];
});
}
Try this simple method, Its working well for me....
UIActivityIndicatorView *activityIndicator= [[UIActivityIndicatorView alloc]initWithFrame:CGRectMake(0, 0, 50, 50)];
activityIndicator.layer.cornerRadius = 05;
activityIndicator.opaque = NO;
activityIndicator.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.6f];
activityIndicator.center = self.view.center;
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
[activityIndicator setColor:[UIColor colorWithRed:0.6 green:0.8 blue:1.0 alpha:1.0]];
[self.view addSubview: activityIndicator];
Add following lines while load your view
//-- Add this line while processing to load your view
[activityIndicator startAnimating];
//-- Add this line when after you view loaded
[activityIndicator stopAnimating];

Resources