Xcode - How to draw a View using thread? - ios

I have graphic view which is drawn using -(void)drawRect:(CGRect)rect function, it is a little complex graphic which includes date as X axis and time as Y axis. So every time I draw it, it always takes 2,3 seconds to display the final graphic.The red point maybe red, green or yellow, its colour depends on the data read from data base.
My question is that how to draw this view using multithread or in a fast way.
For example, draw the X axis and Y axis in one thread, and draw each day's graphic in separated thread(using 30 threads) or draw each hour for 30 days' graphics in separated thread(using 5 threads)?
I have tried using the following function:
for(int i = 0; i < 31; i++){
NSString *threadStr = [NSString stringWithFormat:#"%i", i];
dispatch_queue_t queue1 = dispatch_queue_create([threadStr UTF8String], NULL);
dispatch_async(queue1, ^{
//Draw my graphics for one day
.....
});
}
But sometimes these threads read the database at the same time and some of them cannot get the data properly, this cause the final graphic is not complete.
Could anyone give me some suggestion?
Thank you very much.
I am trying to find out the reason why it delays 2, 3 seconds if I draw it using main thread.
Is it because of reading SQLite3 Data base ?
The procedure of drawing this graphic is :
- (void)drawRect:(CGRect)rect
{
1.draw X axis with date label;
2.draw Y axis with time label;
NSArray *timeArray = #[#"08:00", 11:00", 14:00", 17:00", 20:00"];
for(NSString *timeStr in timeArray){
for(int i = 0; i < 31; i++){
NSString *DateTimeStr = dateStr + timeStr;
int a = [DataManager getResult: DateTimeStr];
UIColor *RoundColor;
if(a == 1){
RoundColor = [UIColor colorWithRed:229/255.0 green:0/255.0 blue:145/255.0 alpha:1.0];
}
else if(a == 2){
RoundColor = [UIColor colorWithRed:0/255.0 green:164/255.0 blue:229/255.0 alpha:1.0];
}
else if(a == 3){
RoundColor = [UIColor colorWithRed:229/255.0 green:221/255.0 blue:0/255.0 alpha:colorAlpha];;
}
const CGFloat *components = CGColorGetComponents(RoundColor.CGColor);
CGContextSetRGBFillColor(context, components[0], components[1], components[2], components[3]);
CGContextFillEllipseInRect(context, CGRectMake(roundXnow,roundYnow,roundR,roundR));
}
}
}
I am thinking if the delay occurs when I am reading the SQLite3 data base, because I know there are a lot of record in my data base.

Drawing here is a very light operation, most likely the timing issues you're facing are related to the calculations (or fetching of data) or whatever else you're doing before actually drawing the dot. So use instruments, time profiler in particular. It will show you what operations take longer (look at the pikes in the graphic that will be displayed while your app is running).
This will give you the clue.
Besides as it's correctly mentioned here, all the UI should be performed in the main thread.
But those pre-calculations and fetches that you probably have with your data - that's where you should look for multithreaded solutions if it's at all possible.

Related

Showing percent in label

I use code for showing the progress of downloading a file from 1% to 100%.
if( downloadTask == _downloadTask26){
_progress26 = [[NSNumber numberWithInteger:totalBytesWritten] floatValue];
_total26 = [[NSNumber numberWithInteger:totalBytesExpectedToWrite] floatValue];
}
if( downloadTask == _downloadTask27){
_progress27 = [[NSNumber numberWithInteger:totalBytesWritten] floatValue];
_total27 = [[NSNumber numberWithInteger:totalBytesExpectedToWrite] floatValue];
}
float progress = _progress26 + _progress27;
float total = _total26 + _total27;
NSString *percentage = [NSString stringWithFormat:#"%.f%%", ((progress / total) * 100)];
(NSLog (percentage, #"%.f%%"));
if (!_label3) {
_label3 = [[UILabel alloc] initWithFrame:CGRectMake(200.43, 158.84, 42, 19)];
_label3.numberOfLines = 1;
_label3.baselineAdjustment = UIBaselineAdjustmentAlignBaselines;
_label3.adjustsFontSizeToFitWidth = YES;
_label3.minimumScaleFactor = 10.0f/12.0f;
_label3.clipsToBounds = YES;
_label3.backgroundColor = [UIColor clearColor];
_label3.textColor = [UIColor whiteColor];
_label3.textAlignment = NSTextAlignmentCenter;
[_scroller addSubview:_label3];
}
}
_label3.text = percentage;
if ([_label3.text isEqual: #"100%"]) {
}
But when file downloading, percent is not displayed in ascending order. Percent is displayed in a different order like in the video below. How do I fix it?
video
https://drive.google.com/file/d/0B0EJbcHq3ZALUVRzanJ6SndscWc/view
May be it's due to downloading task condition which check it is downloadingTask26 or 27 put logs inside this two conditions and check downloaded and total size . Total task may be changes.
Have you simplified the code in your question, where the code generating the video has more that just downloads 26 and 27 included? With only two downloads, I'd expect it to drop down only once, but if you have many downloads, I'd expect it to drop down each time a new download start.
Bottom line, if you really have many downloads, the result makes perfect sense because it's quite likely that you don't have values for total bytes for many of the downloads until they actually start and when a later download starts, suddenly the sum of total bytes downloaded will increase (and thus the percentage will drop).
One solution is to use NSProgress, create a child NSProgress for each download up front and then update the percentage for parent progress. That way, downloads that haven't started yet will be reflected in the total percentage.

Card dealing, playing

Problem
I've never made a card game before, and I'm having quite some difficulty at the moment.
However, I've managed to create the deck and such.
-(NSMutableArray *)arrayWithDeck:(id)sender
{
[sender removeAllObjects];
NSArray *faces = [[NSArray alloc] initWithObjects:#"A",#"2",#"3",#"4",#"5",#"6",#"7",#"8",#"9",#"10",#"J",#"Q",#"K", nil];
NSArray *suits = [[NSArray alloc] initWithObjects:#"h",#"d",#"c",#"s", nil];
for (int i = 0; i < 52; i++) {
NSString *cardToAdd = [NSString stringWithFormat:#"%#%#", faces[i % 13], suits[i / 13]];
[sender addObject:cardToAdd];
}
return sender;
}
Then deal the cards to the players ( just to one player at the moment )
-(void) dealPlayersWithPlayers: (int) players withDealtCards: (int) dealt {
if (players == 2){
__block float add = 0;
for (int i = 0; i < dealt; i++) {
add = add + ((self.frame.size.width / 100) * 10);
NSString *imageName = [NSString stringWithFormat:#"%#", deck[i]];
NSString *bundle = [[NSBundle mainBundle] pathForResource:imageName ofType:#"png"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:bundle];
SKTexture *texture = [SKTexture textureWithImage:image];
cardDisplay = [SKSpriteNode spriteNodeWithTexture:texture];
cardDisplay.size = CGSizeMake(104, 144);
cardDisplay.anchorPoint = CGPointMake(0.5, 0.5);
cardDisplay.position = CGPointMake(-self.frame.size.width/2.5 + add, -218);
cardDisplay.zPosition = 1;
cardDisplay.userInteractionEnabled = NO;
cardDisplay.name = [NSString stringWithFormat:#"card"];
[self addChild:cardDisplay];
}
}
}
Then when touchesBegan, on a card it animates to the centre of the screen, i.e a pre-phase for a "lay cards" button. However, I'm really struggling to figure a way to keep track of the card pressed. I.e, is the card Js, Ah, 8c or whatever it could be so it can obviously be used, However the SKSpriteNode.name is already taken for the detection on touchesBegan.
Another issue I'm having is when the cards are layed. They are messing up the z-index. However, an easy fix for this could be to just keep incrementing the z-index but is it the best way of doing it? My example here shows what I'm talking about.
I would say the fact that you have them rendering and moving up you are off to a great start.
My recommendation though would be to look at a MVC (Model View Controller) approach to the problem. Keep the info for cards played and what the player has for cards in a separate object from your view. That way when you touch your card you can have your controller work with your model to identify it and decide what happens next. It will be very hard to manage the game if you are relaying only on the SKSpriteNode and looking at its name with no pointer to it to compare to anything.
As far as sorting your z index your model would know which card was added first in your hand and your controller can then inform the view the appropriate position and z index.
At the very least I would consider subclassing SKSpriteNode and make a CardSpriteNode at least then you don't have to look at the sprite name for touch and could just check to see if it is a CardSpriteNode.
if ([node isKindOfClass:[CardSpriteNode class]])
//handle touch logic if a card
MVC is a simple concept and I would look at some write ups on that. Everyone has a slightly different approach to what can see what, but all agree on keeping the information separate.
Hope that helps.

Color loop produces (null) instead of colors

#BJ Homer was good enough to post this code a couple of years ago. It was actually written to populate an array with colors, but I modified it slightly to populate a core data store for the purpose of driving a UICollectionView. It seems to be doing that, but all my colors are null (black).
Here's the code:
+(void) loadColorsIntoCD
{
NSManagedObjectContext *localContext = [NSManagedObjectContext MR_contextForCurrentThread];
int tag = 0;
float INCREMENT = 0.0625;
for (float hue = 0.0; hue < 1.0; hue += INCREMENT)
{
UIColor *color = [UIColor colorWithHue:hue
saturation:1.0
brightness:1.0
alpha:1.0];
[CatColor MR_createInContext:localContext];
CatColor *thisColor;
thisColor.color = color;
thisColor.isTaken = NO;
tag++;
thisColor.idNumber = [NSNumber numberWithInt:tag];
NSLog(#"ThisColor.idNumber is %#",thisColor.idNumber);
NSLog(#"ThisColor.color is %#",thisColor.color);
[localContext MR_saveToPersistentStoreAndWait];
}
NSLog(#"%i CatColors available", [CatColor MR_countOfEntities]);
}
Problem is, my NSLogs are reading:
ThisColor.idNumber is (null)
and
ThisColor.color is (null)
but then:
16 CatColors available
I'm really confused by this value which changes with each iteration through the loop, seeming to indicate that colors are being produced:
color UIDeviceRGBColor * 0x8d83870 0x08d83870
Can someone please point out my mistake? I've been beating on this thing for the better part of a day.
Thanks for looking. All help much appreciated!
You're not assigning the created entity to your local variable. You should do this:
CatColor *thisColor = [CatColor MR_createInContext:localContext];

Core-Plot: calling reloadData on Plot or Graph from within block doesn't work

I want to be able to update / reload a ScatterPlot on iOS Devices during runtime. To be specific, I record Audio input, do some funny stuff and put the result as a simple NSNumber value into an array. Whenever that happens, reloadData is called on the Plot I want to update but sadly, exactely nothing happens. Basically, I do the same as described in the answer here: real time plotting on iPhone using core plot? . It just doesn't work, so I assume I made some stupid mistake.
This the relevant method:
-(NSNumber *) numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index {
double val = (index/5.0) -10;
if (fieldEnum == CPTScatterPlotFieldX) {
return [NSNumber numberWithDouble:val];
} else {
if ([plot.identifier isEqual: #"X Squared Plot"]) {
return [NSNumber numberWithDouble:10-(0.25*val*val)];
} else if( [plot.identifier isEqual:#"LivePlot"]) {
if (!_isMeasuring) {
return [NSNumber numberWithFloat:0.0f];
} else {
printf("%f", [[_plotIndizesAndVolume objectAtIndex:index] floatValue]);
return [NSNumber numberWithFloat:[[_plotIndizesAndVolume objectAtIndex:index] floatValue]];
}
} else {
return 0;
}
}
}
The X Squared Plot is irrelevant to my needs for now, it's just a reference. The LivePlot is the one I need and want to update. The _plotIndizesAndVolume is the NSMutableArray I store my values in. It has 500 Elements, all of which are initialized with [NSNumber numberWithFloat:0.0f] at the beginning of my execution. The Plot has 500 indizes as well, therefor we don't get out of bounds or whatever.
The code executes fine, the method is being called, the printf statement works and correctly displays the floats, the Plot just doesn't update.
I also tried
if (index < [_plotIndizesAndVolume count]) {
return [NSNumber numberWithFloat:[_plotIndizesAndVolume objectAtIndex:index]];
} else {
return [NSNumber numberWithFloat:0.0f];
}
without having the 500 0.0fs in the array, so that I just access values differently from 0.0f. This of course is better, it just doesn't work either.
Any ideas?
Update 1:
If I fill the _plotIndizesAndVolume with just random numbers at the beginning of my application, the plot perfectly follows these numbers.
If I printf in the method I can perfectly read the updated values once reload has been called on the plot.
However, these updated values aren't shown in the plot. The plot remains the way it is after calling -reloadData on it, no matter which values changed in the _plotIndizesAndVolume-Array.
So, from what I can see: I can access the array properly, I can update and read the new values properly, the plot still stays the same and doesn't change. I'm confused. Probably I still made some random stupid mistake, I just don't see it.
Update2:
Well, it seems as if my problem isn't the reloadData itself, but from where I call it. Some more context: I'm using novocaine to measure decibel via the device microphone. This works fine. I then want to visualize part of the measured dB in a graph through corePlot (which I described above). I got a method called startMeasuring which I call somewhen and then - obviously - start measuring decibel. Whenever that happens I put the measured value int _plotIndicesAndVolume and call reloadData on the plot.
The method looks like this (and is largely copied directly from the novocain examples):
-(void) startMeasuring {
if (_isMeasuring) {
return;
}
__weak ViewController * wself = self;
self.ringBuffer = new RingBuffer(32768, 2);
self.audioManager = [Novocaine audioManager];
// MEASURE SOME DECIBELS!
// ==================================================
__block float dbVal = 0.0;
__block int count = 0;
__block int limiter = 0;
[self.audioManager setInputBlock:^(float *data, UInt32 numFrames, UInt32 numChannels) {
vDSP_vsq(data, 1, data, 1, numFrames*numChannels);
float meanVal = 0.0;
vDSP_meanv(data, 1, &meanVal, numFrames*numChannels);
float one = 1.0;
vDSP_vdbcon(&meanVal, 1, &one, &meanVal, 1, 1, 0);
dbVal = dbVal + 0.2*(meanVal - dbVal);
float reducedVal = (dbVal + 66.0f)/3;
if (!wself.measuringPaused && limiter > 10) {
[wself.plotIndizesAndVolume replaceObjectAtIndex:count withObject:[NSNumber numberWithFloat:reducedVal]];
[[wself.graph plotWithIdentifier:#"LivePlot"] reloadData];
limiter = -1;
++count;
}
++limiter;
}];
[self.audioManager play];
_isMeasuring = true;
}
I call reloadData from within the measuring block. I don't know exactely much about using blocks, I just assumed I could do that. If I, however, try to change the data in the array and afterwards reload the plot manually from somewhere else, it works and the plot updates accordingly. Is the problem related to my call of the method from within the block?
Even then I'm still unsure why it doesn't work, as the printf statement works perfectly - so I'm under the assumption that the updateData method is correctly invoked and the values are correctly updated.
Update 3:
If I call reloadData from somewhere else during the measurement, the graph updates just fine and according to whatever is in _plotIndizesAndVolume. It simply doesn't update if I call it from inside the block (even if I call another method from within the block which in turn calles reloadData). However, I need the live update. I assume I could reload the plot once every x miliseconds as well (going to try that next), I'm still curious as to why it doesn't work in the first place.
Update 4: As assumed in Update 3, if I call reloadData from somewhere else through a NSTimer, it does exactely what I want it to do. Why can't I call it from within the block then?
Call -reloadData (and any other Core Plot methods) from the main thread.
Probably you need to update your plot space x-range/y-range in your timer
CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)self.graph.defaultPlotSpace;
[plotSpace scaleToFitPlots:[self.graph allPlots]];
CPTPlot *thePlot = [self.graph plotWithIdentifier:kIdentifier];
[thePlot reloadData];

MKOverlayView performance

I am adding about 3000 MKOverlays to my map, and as you can imagine, it takes a while, up to about eight seconds sometimes. I'm looking for a way to use threading to improve performance, so the user can move the map around while overlays are being added. Preferably, the overlays would be added sequentially, starting with the just the ones within the map's region. I have tried something along these lines with GCD:
- (MKOverlayView*)mapView:(MKMapView*)mapView viewForOverlay:(id)overlay {
__block MKPolylineView* polyLineView;
//do the heavy lifting (I presume this is the heavy lifting part, but
// because this code doesn't compile, I can't actually *test* it)
// on a background thread
dispatch_async(backgroundQueue, ^ {
polyLineView = [[[MKPolylineView alloc] initWithPolyline:overlay] autorelease];
[polyLineView setLineWidth:11.0];
//if the title is "1", I want a blue line, otherwise red
if([((LocationAnnotation*)overlay).title intValue]) {
[polyLineView setStrokeColor:[UIColor blueColor]];
} else {
[polyLineView setStrokeColor:[UIColor redColor]];
}
//return the overlay on the main thread
dispatch_async(dispatch_get_main_queue(), ^(MKOverlayView* polyLineView){
return polyLineView;
});
});
}
But because GCD blocks are defined with void parameter and return types, this code doesn't work- I get an incompatible pointer type error on the return line. Is there something I am missing here, or another way to thread this? Or perhaps an entirely different way to improve the performance of the overlay-adding process? I appreciate any and all help!
Edit:
I have found that the problem is not where I actually add the overlays here:
for(int idx = 1; idx < sizeOverlayLat; idx++) {
CLLocationCoordinate2D coords[2];
coords[0].latitude = [[overlayLat objectAtIndex:(idx - 1)] doubleValue];
coords[0].longitude = [[overlayLong objectAtIndex:(idx - 1)] doubleValue];
coords[1].latitude = [[overlayLat objectAtIndex:idx] doubleValue];
coords[1].longitude = [[overlayLong objectAtIndex:idx] doubleValue];
MKPolyline* line = [MKPolyline polylineWithCoordinates:coords count:2];
[line setTitle:[overlayColors objectAtIndex:idx]];
[mapViewGlobal addOverlay:line];
}
Adding all 3000 takes maybe 100ms here. The part that takes a long time (I assume) is where I actually create the overlays, in the first method I showed.
There is a little gap between what you want and what the compiler can do. When you are calling dispatch_async, you are actually telling the CPU "here, have this chunk of code, and run it whenever you feel like it, not now, not blocking my user interface thread". But, your method has to return now. There is simply no way for you to create anything in a background thread, because you are going to have to wait for it anyway before mapView:viewForOverlay: returns, since it has to return something.
This method is not the place to use GCD or any background code. If your problem is the addition of a big number of overlays at once, I would split all the overlays into chunks of say 100 and add them to the map with a delay of 100ms between each batch.

Resources