AVSpeechSynthesizer postUtteranceDelay timing problems - ios

The code below takes a list of directions and reads them out using AVSpeechSynthesizer. Once complete, the user will be able to select a variable amount of time and the app will read out the instructions to fit the time span.
The problem is that when I press the play button, the delay between directions is significantly longer than it should be. Instead of the two minutes I've hardcoded it with, it takes over three. I've logged the value of all my postUtteranceDelays and they add up properly. It's also not due to processing time because when set postUtteranceDelay to 0 there is no pause between directions. I'm not sure what is going on.
- (IBAction)play:(UIButton *)sender {
[sender setTitle:#"Showering" forState:UIControlStateNormal];
Shower *shower = [[SpecificShower alloc] init];
NSUInteger totalRatio = [shower calculateTotalRatio:shower];
NSNumber *offset = #18.0; // estimated time to speak instructions combined
NSNumber *seconds = #120.0; // hard coded but just for testing
int totalSeconds = seconds.intValue - offset.intValue;
self.synthesizer = [[AVSpeechSynthesizer alloc] init];
for (NSDictionary* direction in shower.directions) {
AVSpeechUtterance *aDirection = [[AVSpeechUtterance alloc] initWithString:direction[#"text"]];
NSNumber *directionLength = direction[#"length"];
aDirection.rate = .3;
aDirection.preUtteranceDelay = 0;
// totalRatio is calculated by adding all the lengths together
// then the individual direction length is divided by totalRatio
// and that fraction is multiplied by total number of seconds
// to come up with the postUtteranceDelay for each direction
aDirection.postUtteranceDelay = totalSeconds * [directionLength floatValue]/totalRatio;
NSLog(#"%f", aDirection.postUtteranceDelay);
[self.synthesizer speakUtterance:aDirection];
}
}

You're not alone. This seems to be a bug as noted with a workaround over here.
There are also radars filed here and here.
Let's hope this gets fixed soon.

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.

UIImageView spawning on other UIImageViews

I have an app where a user drives a car and collects coins. I have an array with all the bombs in it that the user tries to avoid. Basically, the problem is that when I spawn the coins, they often land on bombs, making the game impossible. So, I wrote the following code to prevent this, but coins still spawn on bombs (note that these are all uiimageviews). Here is my code:
UIImageView *one = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"goldCoin.png"]];
CGRect rectOne = CGRectMake(arc4random() % (900), arc4random() % (700), 40, 40);
BOOL coinSpawned = false;
while (coinSpawned == false) {
rectOne = CGRectMake(arc4random() % (900), arc4random() % (700), 40, 40);
coinSpawned = true;
for (UIImageView *two in bombArray) {
if (CGRectIntersectsRect(rectOne,two.frame)) {
coinSpawned = false;
}
}
}
[one setFrame:rectOne];
[self.view addSubview:one];
levelCount = levelCount + 1;
coinFrame = one.frame;
[NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(coinIntersection:)
userInfo:one
repeats:YES];
Further note that *two are the uiimageviews in the array bombArray and coinFrame is a global variable that I don't really use.Also, levelcount is something I use later, but is irrelevant for this problem.
Maybe this is just a copy-and-paste issue, but it looks like your logic for setting the coin's frame is actually inside of your while loop, so it executes regardless of the value of coinSpawned. You might not have noticed this because your indents are off. Try closing the while loop right after the for loop and see if that changes anything.
EDIT: Just realized the indents are actually off within the for loop, and your braces do appear to be correct. There are a number of other factors that might be involved here. Are your bombs' frames ever changing, and you're not accounting for the change? Are the bombs hierarchical siblings of the coins (if they are in different superviews, the coordinate systems may be different)?
Also, just an aside: why are you using true and false instead of YES and NO? This probably doesn't have to do with your problem, but it's very non-standard.

Moving an image with ios 7 parallax effect

I just saw Facebook's new paper app that makes images move based on the parallax effect. So it zoom's the image to full screen and when you tilt the screen, it scrolls the image to the side you tilted. I have been able to add the parallax effect like apple has it, but not like how facebook has it. Does anyone have any ideas how they did this. Here is the basic code I was using for parallax:
UIInterpolatingMotionEffect *interpolationHorizontal = [[UIInterpolatingMotionEffect alloc]initWithKeyPath:#"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
interpolationHorizontal.minimumRelativeValue = #-10.0;
interpolationHorizontal.maximumRelativeValue = #10.0;
UIInterpolatingMotionEffect *interpolationVertical = [[UIInterpolatingMotionEffect alloc]initWithKeyPath:#"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
interpolationVertical.minimumRelativeValue = #-10.0;
interpolationVertical.maximumRelativeValue = #10.0;
[self.backgroundView addMotionEffect:interpolationHorizontal];
[self.backgroundView addMotionEffect:interpolationVertical];
Update:
Just found a very nice 3rd party library for achieving this, it's called CRMotionView, it works very smooth and you can modify a lot of things.
here is the github link: https://github.com/chroman/CRMotionView
==================================================================================
i was thinking the same parallax when i first saw Facebook paper app. but after play with my code for a little bit, i don't think parallax is what we looking for. I could be wrong, but i went to do it all from the base, gyro motion manager. Here is my sample code:
//import the motion manager frame work first
#import <CoreMotion/CoreMotion.h>
//then need to add a motionManager
#property (strong, nonatomic) CMMotionManager *motionManager;
//you can paste all those codes in view did load
//i added a scroll view on the view controller nib file
self.mainScrollView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
//we don't want it to bounce at each end of the image
self.mainScrollView.bounces = NO;
//and we don't want to allow user scrolling in this case
self.mainScrollView.userInteractionEnabled = NO;
//set up the image view
UIImage *image= [UIImage imageNamed:#"YOUR_IMAGE_NAME"];
UIImageView *movingImageView = [[UIImageView alloc]initWithImage:image];
[self.mainScrollView addSubview:movingImageView];
//set up the content size based on the image size
//in facebook paper case, vertical rotation doesn't do anything
//so we dont have to set up the content size height
self.mainScrollView.contentSize = CGSizeMake(movingImageView.frame.size.width, self.mainScrollView.frame.size.height);
//center the image at intial
self.mainScrollView.contentOffset = CGPointMake((self.mainScrollView.contentSize.width - self.view.frame.size.width) / 2, 0);
//inital the motionManager and detec the Gyroscrope for every 1/60 second
//the interval may not need to be that fast
self.motionManager = [[CMMotionManager alloc] init];
self.motionManager.gyroUpdateInterval = 1/60;
//this is how fast the image should move when rotate the device, the larger the number, the less the roation required.
CGFloat motionMovingRate = 4;
//get the max and min offset x value
int maxXOffset = self.mainScrollView.contentSize.width - self.mainScrollView.frame.size.width;
int minXOffset = 0;
[self.motionManager startGyroUpdatesToQueue:[NSOperationQueue currentQueue]
withHandler:^(CMGyroData *gyroData, NSError *error) {
//since our hands are not prefectly steady
//so it will always have small rotation rate between 0.01 - 0.05
//i am ignoring if the rotation rate is less then 0.1
//if you want this to be more sensitive, lower the value here
if (fabs(gyroData.rotationRate.y) >= 0.1) {
CGFloat targetX = self.mainScrollView.contentOffset.x - gyroData.rotationRate.y * motionMovingRate;
//check if the target x is less than min or larger than max
//if do, use min or max
if(targetX > maxXOffset)
targetX = maxXOffset;
else if (targetX < minXOffset)
targetX = minXOffset;
//set up the content off
self.mainScrollView.contentOffset = CGPointMake(targetX, 0);
}
}];
I tested this on my device, worked pretty similar like facebook new app.
However, this is just an example code i wrote in half hour, may not be 100% accurate, but hope this would give you some ideas.

Core Plot: should adding a CPTPlotSpaceAnnotation to plot space cause the entire graph to be redrawn?

I am using Core Plot to display a time series of prices. When the user touches the graph, I display a draggable vertical line at that point. Both the time series and the draggable line are CPTScatterPlot objects within a CPTXYGraph. This works pretty well - performance as you drag the line across the time series graph is acceptable.
The next stage is to display the price and date at the point that the user has selected. The Yahoo Stocks App has a nice feature which displays the price in a label that moves as if it is attached to the top of the draggable line. I have tried to replicate this using text displayed in a CPTPlotSpaceAnnotation. This works, but it severely impacts performance. After some digging, I found that CPTLayer drawInContext: is being called multiple times - it looks like the entire graph is being redrawn each time I redraw the text label (in fact my logs imply it's being redrawn twice).
Here is the code which draws the label (work in progress). It is called by plotSpace:shouldHandlePointingDeviceDraggedEvent:atPoint:.
- (void)displayPriceAndDateForIndex:(NSUInteger)index atPoint:(CGPoint)pointInPlotArea
{
NSNumber * theValue = [[self.graphDataSource.timeSeries objectAtIndex:index] observationValue];
// if the annotations already exist, remove them
if ( self.valueTextAnnotation ) {
[self.graph.plotAreaFrame.plotArea removeAnnotation:self.valueTextAnnotation];
self.valueTextAnnotation = nil;
}
// Setup a style for the annotation
CPTMutableTextStyle *annotationTextStyle = [CPTMutableTextStyle textStyle];
annotationTextStyle.color = [CPTColor whiteColor];
annotationTextStyle.fontSize = 14.0f;
annotationTextStyle.fontName = #"Helvetica-Bold";
// Add annotation
// First make a string for the y value
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setMaximumFractionDigits:2];
NSString *currentValue = [formatter stringFromNumber:theValue];
NSNumber *x = [NSNumber numberWithDouble:[theDate timeIntervalSince1970]];
NSNumber *y = [NSNumber numberWithFloat:self.graphDataSource.maxValue];
NSArray *anchorPoint = [NSArray arrayWithObjects:x, y, nil];
// Then add the value annotation to the plot area
float valueLayerWidth = 50.0f;
float valueLayerHeight = 20.0f;
CPTTextLayer *valueLayer = [[CPTTextLayer alloc] initWithFrame:CGRectMake(0,0,valueLayerWidth,valueLayerHeight)];
valueLayer.text = currentValue;
valueLayer.textStyle = annotationTextStyle;
valueLayer.backgroundColor = [UIColor blueColor].CGColor;
self.valueTextAnnotation = [[CPTPlotSpaceAnnotation alloc] initWithPlotSpace:self.graph.defaultPlotSpace anchorPlotPoint:anchorPoint];
self.valueTextAnnotation.contentLayer = valueLayer;
// modify the displacement if we are close to either edge
float xDisplacement = 0.0;
...
self.valueTextAnnotation.displacement = CGPointMake(xDisplacement, 8.0f);
[self.graph.plotAreaFrame.plotArea addAnnotation:self.valueTextAnnotation];
// now do the date field
...
}
Is the full redraw expected behaviour? And is there a better way of managing the annotation without destroying it and recreating it each time the method is called?
You shouldn't need to destroy and create the annotation every time. Once it has been created, just update the anchorPoint. Removing and adding the annotation is probably related to the constant redraw.

Resources