UIView *test=[[UIView alloc]initWithFrame:CGRectMake(20, 150, 10, 20)];
[test setBackgroundColor:[Util colorWithHexString:#"#FF00007f"]];
[self.view addSubview:test];
int testNum=0;
for(int i=0;i<50;i++){
testNum=0;
//do something spend 100 ms
while(testNum<800){
testNum+=1;
}
[test setFrame:CGRectMake(20, 150, 10+i*200/50, 20)];
}
I want to build a custom progress bar.
Just like above, but the uiview only redraw by last one of loop.
How can I resolve the problem? And, what happen?
Thanks.
I change code to
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
int testNum=0;
for(int i=0;i<50;i++){
testNum=0;
while(testNum<800){
NSLog(#"%d",testNum);
testNum+=1;
}
dispatch_async(dispatch_get_main_queue(), ^{
[test setFrame:CGRectMake(20, 150, 10+i*200/50, 20)];
});
}
});
It work, but while loop just simulate situations for block code in my project.
Actually, spend time code is alloc list item and initial it's view, so it necessary in main thread.
Any suggestions?
Thank you.
Your "do something spend 100 ms" loop takes just a few cycles to execute on the iPhone, your code is working but it's not stalling long enough for any visible progress to be seen.
Also it's very bad practice to try and lock up the UI thread like that.
Related
I am a bit confused. I have written a class, that calculates some stuff and makes an internet query. Afer this query some NSString properties of this class are updated with the resulting values.
In my view controller, I create an instance of this class. I want to show in the labels waiting for text "Loading..." until the data has arrived. As soon as the data is ready, I want to replace the text. But how do I do that? And depending on if one property, I also want to redraw one view of this view controller. Furthermore I don't want to block my UI.
This so far hasn't worked...
self.firstLabel.text = #"Loading...";
self.secondLabel.text = #"Loading...";
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[indicator setColor:[UIColor colorWithHexString:#"3375cb"]];
indicator.center = self.view.center;
[self.view addSubview:indicator];
[indicator startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self.myInstance fillLabelsWithLiveData];
dispatch_async(dispatch_get_main_queue(), ^{
[indicator stopAnimating];
self.firstLabel.text = myInstance.someText;
self.secondLabel.text = myInstance.someMoreText;
if(myInstance.myBool){
//redraw
}
});
});
You say that your code isn't working, but you don't say what the actual behaviour is, so I would like to write about how I would debug the code in order to see where the problem is.
First, I would put a few log statements into the code, in order to see what it actually does.
NSLog(#"MyViewController: Starting to load...");
self.firstLabel.text = #"Loading...";
self.secondLabel.text = #"Loading...";
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[indicator setColor:[UIColor colorWithHexString:#"3375cb"]];
indicator.center = self.view.center;
[self.view addSubview:indicator];
[indicator startAnimating];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"MyViewController: Starting to load on background queue...");
[self.myInstance fillLabelsWithLiveData];
NSLog(#"MyViewController: Done loading on background queue...");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"MyViewController: Updating main queue UI");
[indicator stopAnimating];
self.firstLabel.text = myInstance.someText;
self.secondLabel.text = myInstance.someMoreText;
if(myInstance.myBool){
NSLog(#"MyViewController: Will redraw...");
//redraw
} else {
NSLog(#"MyViewController: Will not redraw (myInstance.myBool is NO)");
}
});
});
When you do this you will see where the problem lies exactly. (Note that in tutorials all over the internet, the log messages are often ultra short. Like NSLog(#"loading...");. The problem is that if you have many logs in your app and every class logs like this, the log messages become useless. What's worse, you may see that "loading..." is printed on the console and assume that your code is called, when in fact some other place in the program prints "loading...". So I always add some context to NSLog calls. (In fact, I never use NSLog, I use custom logging libraries that also print file names and line numbers.))
Just a few comments on your code.
centering the indicator
indicator.center = self.view.center; // (1a)
This does not center the indicator in self.view
indicator.center = centerOf(self.view.bounds); // (1b)
does. You have to define centerOf:
CGPoint centerOf(CGRect rect) {
CGFloat x = CGRectGetMidX(rect);
CGFloat y = CGRectGetMidY(rect);
return CGPointMake(x, y);
}
(1a) works sufficiently well though if self.view.frame.origin is sufficiently near to (0, 0).
check if your code hangs
If you add the logs and look into the console, there are a few things that can go wrong:
The new log lines do not show up at all => in this case, you should check why your code isn't called at all
The new log lines are printed up to some point, e.g. "Done loading on background queue" is never printed to the console. => In this case, the code that is in-between hangs, obviously.
The logs look good, but the indicator doesn't show up. 2 options:
everything is so fast that you just don't see the indicator.
the loading on the background queue takes some time, but the indicator is not visible => is the superview of the indicator visible? Use the "inspect view hierarchy" feature of Xcode to check what is going on. Where is your view? Add some code like self.view.backgroundColor = [UIColor orangeColor]; to see if it makes a difference.
The logs look good, but the labels did not update.
if this is the case, I cannot help you anymore. You didn't post the real code. You posted a simplified version of it, and the bug hides behind that simplification.
- (void)createCar
{
_car = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 10)];
[_car setBackgroundColor:[UIColor redColor]];
[self addSubview:_car];
_myTimer = [NSTimer scheduledTimerWithTimeInterval:normalSpeedValue target:self selector:#selector(moveCar) userInfo:nil repeats:YES];
}
- (void)moveCar
{
static int move = 0;
move = move+1;
[_car setFrame:(CGRectMake(move, 0, 40, 10))];
}
This is how I create a view and animate it moving from left to right.
If I call the method "createCar" again, it will just create a new view but I won't animate. Why is that?
I want to be able to create more views and animate (moveCar).
The reason that additional calls to createCar creates motionless, but still visible cars, is because the callback on the timer, moveCar, only has reference to the most recently created car stored in the _car ivar.
The past created cars are still visible because the view they were added to still holds reference to them and consequently continues to draw them.
You can fix this by creating an NSMutableArray for your cars, adding them to it in createCar, and then looping over the array moving each car in the moveCar method.
Sample Code:
// ...
NSMutableArray<UIView *> *_cars; // Be sure to init this somewhere
// ...
// ...
timer = NSTimer.schedule ... // Schedule time in viewDidLoad, or somwhere
// ...
- (void)createCar
{
UIView *_car = [[UIView alloc] initWithFrame: CGRectMake(0, 0, 100, 100)];
[_car setBackgroundColor: [UIColor redColor]];
[self.view addSubview: _car];
[_cars addObject:_car];
}
- (void)moveCars
{
// go through each car
[_cars enumerateObjectsUsingBlock:^(UIView *car, NSUInteger i, BOOL *stop) {
// and set its frame.x + 1 relative to its old frame
[car setFrame: CGRectMake(car.frame.origin.x + 1, 0, 100, 100)];
}];
}
This is one simple way of doing it. But if you want flexibility like different speeds for different cars, it'll take a little reworking but not much.
Hope this helps!
Every time your move became 0 when method get called. declared it as instance variable and set it's initial value 0 (in your case) in createCar method. I think this you want. hope this will help :)
As pointed out by bbum here,
the doc says: "For the most part, UIKit classes should only be used from the main thread of an application This is especially true for derived classes UIResponder or involve the manipulation of user interface of your application in any way. ".
I thought I understood that the methods of drawings could not be called in a background thread, so that the creation could be done in the background, as the drawRect method is only called when the view is added. But maybe i am wrong.
In summary, does that this kind of code is risky?
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSString *fileName = [pathToModel stringByAppendingPathComponent:[[compDico valueForKey:#"fileName"] lastPathComponent]];
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:
fileName]];
UILabel *label=[[UILabel alloc]initWithFrame:CGRectMake(10, 62, 190, 20)];
[label setText:[[someArray objectAtIndex:i-1] someText]];
[label setNumberOfLines:0];
label.font=[UIFont fontWithName:#"arial" size:10.0f];
[label setBackgroundColor:[UIColor clearColor]];
// Create some other view here
// ...
dispatch_async(dispatch_get_main_queue(), ^{
[self.view addSubview:imageView];
[self.view addSubview:label];
//Add other view here
// ...
});
});
Thanks for in advance your responses!
Yes, this is risky. How risky it is only Apple developers can say.
If the documentation says "don't use it", just don't use it.
Note that many UI objects can (and do) use shared resources. If you use them in a background thread, you'll get a race condition on the shared resource and anything can happen.
_datePicker.frame = CGRectMake(70, self.view.frame.size.height -150,250,100);
I'm using this code under viewDidLoad to change the size of a UIDatePicker. It works great but gives me another 3 seconds on load time. I tried using this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
_datePicker.frame = CGRectMake(70, self.view.frame.size.height -150,250,1);
});
but I lose all the animation between segues if I do, don't know why. I thought about using another thread in viewDidLoad but I don't know how to do it or if it's possible.
Thanks
I'm creating some UIViews and caching them for reasons that aren't really important to the question at hand.
After I add the view X as a subview to Y one of X's subviews does not appear. If I wait 20-30 seconds it suddenly appears.
Here's how I am creating the views and adding them to the cache. These views are not yet added to the ui, that happens later.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for(int i = 0; i < 10;i++){
MyUIView *cTemp = [[MyUIView alloc] initWithFrame:CGRectZero];
[self addViewToCahce:cTemp forKey:#"key"];
}
});
but if I remove the dispatch_async it appears as it should. Anyone know what is going on here or how to prevent this unusual behavior?
Never modify UI outside of the main thread. Cocoa, like most UI frameworks, is not multithreaded. Try the following instead:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// Do whatever processing you want to do here
dispatch_async(dispatch_get_main_queue(), ^{
for(int i = 0; i < 10;i++){
MyUIView *cTemp = [[MyUIView alloc] initWithFrame:CGRectZero];
[self addViewToCahce:cTemp forKey:#"key"];
}
});
});
As for why you experience the behavior you describe, I can only speculate. I would not trust the constructor to UIView to be thread safe. If you need to create your views in another thread, I would suggest refactoring the code a bit, if possible.