I am trying to render string using the CoreText api. Each character is rendered in the CAShapeLayer and i am getting the layer offset (x coordinate) for each character like this:
let offset = CTLineGetOffsetForStringIndex(line, glyphIndex, nil)
But the resulting offset doesn't seem to respect kerning between letters. Here is a an image of what i mean - the top label is a UILabel, the bottom one is my custom rendering:
As you can see the "A" letter in my custom label is placed further from the "W" letter.
I would really appreciate any help.
Thanks in advance.
In case someone has the same problem, i used an approach from this place: https://github.com/MichMich/XMCircleType/blob/master/XMCircleType/Views/XMCircleTypeView.m, the kerningForCharacter function. Here is the function itself:
- (float)kerningForCharacter:(NSString *)currentCharacter afterCharacter:(NSString *)previousCharacter
{
//Create a unique cache key
NSString *kerningCacheKey = [NSString stringWithFormat:#"%#%#", previousCharacter, currentCharacter];
//Look for kerning in the cache dictionary
NSNumber *cachedKerning = [self.kerningCacheDictionary objectForKey:kerningCacheKey];
//If kerning is found: return.
if (cachedKerning) {
return [cachedKerning floatValue];
}
//Otherwise, calculate.
float totalSize = [[NSString stringWithFormat:#"%#%#", previousCharacter, currentCharacter] sizeWithAttributes:self.textAttributes].width;
float currentCharacterSize = [currentCharacter sizeWithAttributes:self.textAttributes].width;
float previousCharacterSize = [previousCharacter sizeWithAttributes:self.textAttributes].width;
float kerning = (currentCharacterSize + previousCharacterSize) - totalSize;
//Store kerning in cache.
[self.kerningCacheDictionary setValue:#(kerning) forKey:kerningCacheKey];
//Return kerning.
return kerning;
}
Related
In Objective-C, I am attempting to set a limit of character length for a UILabel, but I could not find a way anywhere. For example, a line of text is entered into the UILabel, say 100, but if the maximum character length is set to 50, I just want it to cut off at 50 exactly and not even truncate. I just would like for it to cut off once it hits the limit.
I have tried this; but it didn't work:
NSString *string = my_uilabelText;
if ([string length] >74) {
string = [string substringToIndex:74];
}
Any insight or help would be greatly appreciated. Thank you
Based on your comment, what you actually implemented would work. You just have to ensure your syntax is correct.
Assuming your getting a string from a database:
NSString *string = stringFromDatabase;
//execute your conditional if statement
if (string.length > 50) { //meaning 51+
/* trim the string. Its important to note that substringToIndex returns a
new string containing the characters of the receiver up to, but not
including, the one at a given index. In other words, it goes up to 50,
but not 50, so that means we have to do desired number + 1.
Additionally, this method includes counting white-spaces */
string = [string substringToIndex:51];
}
Then we must set the labels text, this doesn't happen autonomously. So completely:
NSString *string = stringFromDatabase;
if (string.length > 50) {
string = [string substringToIndex:51];
}
self.someUILabel.text = string;
I think the way you are doing it is probably not user friendly. You're getting the string of a UILabel that already has text set and then resetting it. Instead, you should set the desired text to the UILabel before it is called by the delegate
This is making me feel like a bumbling idiot, and I seem like one by asking this.
I have a board game-like-game where I'm adding spaces (type DDSpace's for each space, a subclass of NSObject) to a mutable array (NSMutableArray).
Here's the function to load the spaces into the view (loadSpaces) so far:
NSLog(#"amount of spaces: %i", amountOfSpaces);
for (int i = 0; i < amountOfSpaces; i++) {
DDSpace *space = [DDSpace new];
[space setupSpriteWithTheme:gamePlace];
[spacesArray addObject:space];
}
int num = 0;
// INITIAL SPACE IS SPACE 0.
NSLog(#"adding spaces to scene... self.width = %f, count of space array = %i", self.size.width, spacesArray.count);
for (DDSpace* space in spacesArray) {
CGFloat x = 100.0 + (num * 225.0);
NSLog(#"x at num%i = %f.", num, x);
space.sprite.position = CGPointMake(x, (self.size.width / 3.0));
space.sprite.name = [NSString stringWithFormat:#"space%ld",(long)num];
if (space.sprite.position.x < self.size.width) {
NSLog(#"adding space.");
[self addChild:space.sprite];
}
}
NSLog(#"loadSpaces function finished.");
The first game loads 1,000 spaces. The function first logs the amount of spaces to make sure that's set properly (and it is), then it logs the width of the screen and the count of spaces in the array (the array's count is 0), and then everything below that simply adds the ones that fit onto the screen onto the view.
My goal is to add the spaces to the spacesArray array (an NSMutableArray) so I can access the other spaces later on, in case a character "takes their turn" (I'll have logic in there to move the others backwards, but to do this, I need an array of spaces).
My question is, how do I go about adding the DDSpaces into the spacesArray array? Is the addObject: method correct? Am I simply doing something dumb with it?
None of the spaces appear on the scene, either.
I think you need this before adding objects to it.
spaceArray = [NSMutableArray Array];
And add objects like this ( like you did )
[spacesArray addObject:space];
In order to manage automatic scrolling in a content editable UIWebView, I need to get the correct caret Y vertical coordinate.
I identified two methods, using javascript.
The first one uses getClientRects javascript function:
CGRect caretRect = [self.webView stringByEvaluatingJavaScriptFromString: #"
var sel = document.getSelection();
var range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
var r =range.getClientRects()[0];
return '{{'+r.left+','+r.top+'},{'+r.width+','+r.height+'}}';"];
int caretY = caretRect.origin.y;
With this, the caret keeps blinking and one gets its correct vertical position with one problem: when user type return key, so that the next line is empty, caretY is equal to zero until a character key is typed. So that this method cannot be use.
The second one insert a tmp span, then removes it:
int caretY = [[self.webView stringByEvaluatingJavaScriptFromString: #"
var sel = window.getSelection();
sel.collapseToStart();
var range = sel.getRangeAt(0);
var span = document.createElement(\"span\");
range.insertNode(span);
var topPosition = span.offsetTop;
span.parentNode.removeChild(span);
topPosition;"] intValue];
This gives the correct caretY in any situation. But the caret stops blinking, which is very unhelpful to see it on the screen.
Does anybody knows any method (or adaptation of these) that can make the following:
- get the correct caret Y in any situation
- keep the caret blinking in any situation
Thanks
Not sure if this is too late; the following works for me.
Add getCaretY() below to your js file:
function getCaretY() {
var y = 0;
var sel = window.getSelection();
if (sel.rangeCount) {
var range = sel.getRangeAt(0);
if (range.getClientRects) {
var rects = range.getClientRects();
if (rects.length > 0) {
y = rects[0].top;
}
}
}
return y;
}
Then call it from obj-c file as below:
NSString *yPos = [webView stringByEvaluatingJavaScriptFromString:#"getCaretY()"];
I have a chart of name versus age, where name is on the x axis and value on Y axis.
The problem is the names are getting overlapped on the x axis which do not look good.
I could not find any formatting for showing truncated name values on x axis and complete as we zoom in.
Is there any way to show names with ellipses or other formatting where the names will not overlap?
The alterTickMark: method on SChartDelegate allows you to modify a tickMark (and its corresponding tickLabel) before they are added to the axis.
You could potentially check the axisRange in this step, and decide if the range.span is small enough that you could display labels truncated or in full.
E.g.
-(void)sChart:(ShinobiChart *)chart alterTickMark:(SChartTickMark *)tickMark beforeAddingToAxis:(SChartAxis *)axis
{
if (!axis.isXAxis)
return;
if ([axis.axisRange.span doubleValue] > 5)
{
NSString *shortText = [tickMark.tickLabel.text substringToIndex:3];
tickMark.tickLabel.text = [NSString stringWithFormat:#"%#...", shortText];
//Resize, but maintain centering
CGPoint center = tickMark.tickLabel.center;
[tickMark.tickLabel sizeToFit];
tickMark.tickLabel.center = center;
}
}
As full disclosure, I work for ShinobiControls.
If you need to adjust tick label width in column series and zooming is enabled (which makes more space for label after zoom gesture), just implement SChart delegate method:
- (void)sChart:(ShinobiChart *)chart alterTickMark:(SChartTickMark *)tickMark beforeAddingToAxis:(SChartAxis *)axis
{
if (axis.isXAxis) {
// Adjusting tickmark labels
UILabel *label = [tickMark tickLabel];
CGFloat maxLabelWidth = axis.axisFrame.size.width / [axis.axisRange.span floatValue];
CGRect labelFrame = label.frame;
labelFrame.size.width = maxLabelWidth;
[label setFrame:labelFrame];
}
}
Look at the longestLabelStringOn delegate method. Example:
func sChart(_ chart: ShinobiChart, longestLabelStringOn axis: SChartAxis) -> String? {
if axis == chart.xAxis{
return "Longest Possible String"
}
else{
return ""
}
}
I'm adding a bunch of coordinates into quad tree and when I'm asking for the closest coordinate near my location, sometimes I've coordinate with 0 at the end, added automatically perhaps by the quad tree or I don't know how.
The problem is when I'm asking the double value in my core data using predicate it won't match because of the 0 digit addition to the number.
I thought about removing it when I've 0 but I'm sure there is a better way doing it.
For example:
Near location 31.123456, 34.123456, the nearest is 31.123444, 34.123450
when '34.123450' is actually 34.12345 in the database.
//Convert float to String
NSString *str_lat = #"34.123450";
NSString *trimmedString=[str_lat substringFromIndex:MAX((int)[str_lat length]-1, 0)];
if([trimmedString isEqualToString:#"0"])
{
str_lat = [str_lat substringToIndex:[str_lat length] - 1];
}
else
{
}
NSLog(#"%#",str_lat);
First: You should not store numbers as strings. 7.3 and 7.30 are the same values with simply different representations. You should not compare the representations, but the value.
Second: You should not compare floating-point numbers with == but their difference to a delta. In a calculation precision might get lost, rounding is applied and so on. The "mathematical" equal values might be physical different by a more or less small amount.
// remove the zeros from values (if you have them as floats)
NSString *valueFromTheDataBase = [NSString stringWithFormat:#"%g", 34.123450];
NSString *yourValue = [NSString stringWithFormat:#"%g", 34.12345];
if([yourValue isEqualToString:valueFromDataBase]) {
// they are equal
}
OR Make Them floats and compare them
// make them floats and compare them
CGFloat floatFromDB = [valueFromDB floatValue];
CGFloat yourFloat = [yourString floatValue];
if((floatFromDB - yourFloat) == 0) {
// they are equal
}
UPDATED as #Amin Negm says