Cutting off the lower end of a float - objective-c

This is more of a math question but I'm just not having any luck. Basically Ive got the code below to change cell colors depending on their row equivalent in the colors array. It all works very well but I would like it so that I could somehow cut off the bottom end of the brightness spectrum so that I never end up with anything say, below 0.2. Any suggestions on how to solve this would be appreciated.
-(IBAction)reloadTable {
float arrayCount = [masterListArray count];
float increment = (1.0 / arrayCount);
NSMutableArray *tempColor = [[NSMutableArray alloc]init];
colors = [[NSMutableArray alloc]init];
for (float brightness = 0.0; brightness < 1.0; brightness += increment) {
UIColor *color = [UIColor colorWithHue:50.0f/255.0f
saturation:1.0
brightness:brightness
alpha:1.0];
[tempColor addObject:color];
NSLog(#"brightness: %f", brightness);
}
colors = [[tempColor reverseObjectEnumerator] allObjects];
[self.tableView reloadData];
}

You can use some extra variables to make sure you always end up between 1.0 and the bottom end. In the below code we use the number of cells to iterate through (better practice than what you had before) and start with brightness 1.0 and end at your bottom end. Also no need to use tempColors.
-(IBAction)reloadTable {
float arrayCount = [masterListArray count];
float bottomEnd = 0.2;
float brightnessRange = 1.0 - bottomEnd;
float increment = (brightnessRange / arrayCount);
colors = [NSMutableArray array];
for (int i = 0; i < arrayCount; i++) {
// We start with light and go darker per cell.
float brightness = bottomEnd + (brightnessRange - i * increment);
UIColor *color = [UIColor colorWithHue:50.0f/255.0f
saturation:1.0
brightness:brightness
alpha:1.0];
[colors addObject:color];
NSLog(#"brightness: %f", brightness);
}
[self.tableView reloadData];
}

Related

How to get X coordinate of a word inside a NSString

I have a label with a sentence in it, that is one string. I need to get the x coordinate for a specific word in the string. So for example, if I have a sentence that says "The dog ran" inside of the sentence, I need to be able to find the x and y coordinates, as well as the width and height to place a UITextField over it. Here is my code so far:
- (void)insertTextFields:(NSString *)string inLabel:(UILabel *)label
{
CGFloat stringWidth = [self getWidthOfString:string inLabel:label];
CGFloat stringHeight = label.bounds.size.height;
CGFloat stringYOrigin = label.bounds.origin.y;
CGFloat stringXOrigin = [self getXOriginOfString:string fromString:label.text inLabel:label];
CGRect textFieldRect = CGRectMake(stringXOrigin, stringYOrigin, stringWidth, stringHeight);
UITextField *textField = [[UITextField alloc] initWithFrame:textFieldRect];
[label addSubview:textField];
}
- (CGFloat)getWidthOfString:(NSString *)string inLabel:(UITextField *)label
{
CGFloat maxWidth = CGRectGetMaxX(label.frame);
CGSize stringSize = [string sizeWithFont:label.font forWidth:maxWidth lineBreakMode:NSLineBreakByCharWrapping];
CGFloat width = stringSize.width;
return width;
}
- (CGFloat)getXOriginOfString:(NSString *)string fromString:(NSString *)sentenceString inLabel:(UILabel *)label
{
CGFloat maxWidth = CGRectGetMaxX(label.frame);
CGSize sentenceStringSize = [sentenceString sizeWithFont:label.font forWidth:maxWidth lineBreakMode:NSLineBreakByWordWrapping];
CGSize stringSize = [string sizeWithFont:label.font forWidth:maxWidth lineBreakMode:NSLineBreakByWordWrapping];
//I have the width of both the individual word and the sentence
//now I need to find the X coordinate for the individual word inside of the sentence string
return xOrigin;
}
Could someone tell me what I need to do to fix this?
Is that the x position of the start of the word? Just measure the string that precedes the word....
- (CGFloat)getXOriginOfString:(NSString *)string fromString:(NSString *)sentenceString inLabel:(UILabel *)label {
CGFloat maxWidth = CGRectGetMaxX(label.frame);
NSRange range = [sentenceString rangeOfString:string];
NSString *prefix = [sentenceString substringToIndex:range.location];
return [prefix sizeWithFont:label.font
forWidth:maxWidth
lineBreakMode:NSLineBreakByWordWrapping].width;
}
The end of the word will be this result + the stringSize in the code you posted. Or maybe I'm misunderstanding the question?

UIScrollview questions

I'm trying to implement a uiscrollview with small images.
I need that uiscrollview last image in every end remain in the center when scroll finishes, leaving one side empty. Now last image remain in the sides when scroll finishes.
I need to know which image is in the center of the scollview, to apply a shadow on it,
jugadorSlide.pagingEnabled = YES;
NSError *error = nil;
fotosJugadores = [[NSFileManager defaultManager] contentsOfDirectoryAtPath: #"/Users/exular/Developer/GBC/exular/GBC/Caras/" error: &error];
NSInteger numberOfViews = [fotosJugadores count];
for (int i = 0; i < numberOfViews; i++) {
UIImage *myImage = [UIImage imageNamed:[fotosJugadores objectAtIndex:i]];
CGFloat yOrigin = i * myImage.size.width;
UIImageView *awesomeView = [[UIImageView alloc] initWithFrame:CGRectMake(yOrigin, 0, myImage.size.width, myImage.size.height)];
awesomeView.alpha = 0.5;
awesomeView.image = myImage;
[self.jugadorSlide addSubview:awesomeView];
awesomeView=nil;
}
jugadorSlide .contentSize = CGSizeMake(65 * numberOfViews,78);
jugadorSlide.layer.cornerRadius = 11;
jugadorSlide.layer.masksToBounds = YES;
Many thanks for your help.
To allow the first and last image to be in the center just add an empty space before the first image and after your last image. In other words: start your yOrigin at 65 and add 65 to the contentSize width (I am assuming that 65 is your images width):
...
CGFloat yOrigin = i * myImage.size.width + 65;
...
jugadorSlide.contentSize = CGSizeMake(65 * numberOfViews + 65,78);
...
To find out which image is in the center add the following inside the UIScrollViewDelegate method scrollViewDidEndDecelerating:.
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
int currentIndex = roundf(scrollView.contentOffset.x / 65) - 1;
UIImageView *currentImage = [scrollView viewWithTag:currentIndex];
}
To easily get the currentImage, add tags to your images when you create them:
...
awesomeView.tag = i;
...

Having problems with CALayer

So I have a class that returns a CALayer that has an array of CALayers added as sublayers, but the problem is that it doesn't seem to be rendering any of them. I think I may just be missing something about how CALayers work as I'm not very familiar yet. Could someone please take a look at my code to see what I am doing wrong?
- (CALayer*)drawMap {
CALayer* theLayer = [CALayer layer];
for (int y = 0; y < [self.height intValue]; y++) {
for (int x = 0; x < [self.width intValue]; x++) {
CALayer* tileLayer = [CALayer layer];
tileLayer.bounds = CGRectMake((x * [self.tileWidth intValue]), (y * [self.tileHeight intValue]), [self.tileWidth intValue], [self.tileHeight intValue]);
[tileLayer setBackgroundColor:[UIColor colorWithHue:(CGFloat)x saturation:(CGFloat)y brightness:255 alpha:1.0].CGColor];
[theLayer addSublayer:tileLayer];
}
}
[theLayer setBounds:CGRectMake([self.width intValue] * [self.tileWidth intValue], [self.height intValue] * [self.tileHeight intValue], 10, 10)];
return theLayer;
}
This is the code that initializes and draws the map.
Map* myMap = [[Map alloc] initFromFile];
[myMap setWidth:[NSNumber numberWithInt:100]];
[myMap setHeight:[NSNumber numberWithInt:100]];
[self.view.layer insertSublayer:[myMap drawMap] above:self.view.layer];
I can provide the headers if needed, but I don't think the problem is there and I don't want a huge post.
I think the problem is the bounds you're setting to your parent layer:
[theLayer setBounds:
CGRectMake(
[self.width intValue] * [self.tileWidth intValue],
[self.height intValue] * [self.tileHeight intValue],
10, 10)];
That would appear to set the bounds so that it starts just exactly past all the sublayers you've just added and extends for 10 points in both directions. I think the following would be more appropriate:
[theLayer setBounds:
CGRectMake(
0,
0,
[self.width intValue] * [self.tileWidth intValue],
[self.height intValue] * [self.tileHeight intValue])];
The following also looks fishy:
[self.view.layer insertSublayer:[myMap drawMap] above:self.view.layer];
[myMap drawMap] will become a sublayer of self.view.layer. self.view.layer is not one of its own subviews so it won't know what to do with that above: instruction. You probably want just addSublayer:.
Finally, this:
tileLayer.bounds = CGRectMake((x * [self.tileWidth intValue]), (y * [self.tileHeight intValue]), [self.tileWidth intValue], [self.tileHeight intValue]);
gives every single one of your sublayers exactly the same frame. Much like UIViews, the bounds are the source area for information and the frame is where that information will be put within the super layer. I think probably you want to set frame.
Finally, UIColor +colorWithHue:... clamps hue and saturation to the range [0, 1] so you probably want (CGFloat)x / [self.width floatValue] and the equivalent for saturation to ensure all your tiles come out in different colours.
EDIT: so, tying all that together, I modified your code slightly so that I didn't have to write the full encompassing class and added it to the Xcode view-based project template as follows:
#define kTileWidth 3
#define kTileHeight 3
#define kWidth 100
#define kHeight 100
- (CALayer*)drawMap {
CALayer* theLayer = [CALayer layer];
for (int y = 0; y < kHeight; y++)
{
for (int x = 0; x < kWidth; x++)
{
CALayer* tileLayer = [CALayer layer];
tileLayer.frame = CGRectMake((x * kTileWidth), (y * kTileHeight), kTileWidth, kTileHeight);
[tileLayer setBackgroundColor:[UIColor colorWithHue:(CGFloat)x/kWidth saturation:(CGFloat)y/kHeight brightness:255 alpha:1.0].CGColor];
[theLayer addSublayer:tileLayer];
}
}
[theLayer setFrame:CGRectMake(0, 0, kWidth * kTileWidth, kHeight * kTileHeight)];
return theLayer;
}
// ...
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self.view.layer addSublayer:[self drawMap]];
}
That gives the hue/saturation plane familiar from many an art package.

"Flatten" or "Merge" Quartz 2D drawn lines

I'm trying to figure out how you can flatten or merge alot of dynamicly drawn lines in Quartz 2D,
I'm drawing random lines over time on my stage, I add new line coordinates each time to an array and draw the array (with my drawRect and I push a new line in my array with a timer and a setNeedDisplay to acctually redraw all the previous lines plus the new one)
now the PROBLEM: after a while it starts to get slower and slower because the arrays are getting very long, so I though I should merge the coordinates into a flat image or something and clear the arrays to keep it memory healthy, but how do I do this?
This is my current workflow:
Call timer
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(drawdrawdraw) userInfo:nil repeats:YES];
Refresh the drawRect in my "drawdrawdraw" function
-(void)drawdrawdraw{
[self setNeedsDisplay];
}
my drawRect
-(void)drawRect:(CGRect)rect{
viewContext = UIGraphicsGetCurrentContext();
int count = [mijnArray_r_x count];
float r_x = (float)(random() % 768);
[mijnArray_r_x insertObject:[NSNumber numberWithFloat:r_x] atIndex:count];
float r_y = (float)(random() % 1004);
[mijnArray_r_y insertObject:[NSNumber numberWithFloat:r_y] atIndex:count];
float r_w = (float)(random() % 100);
[mijnArray_r_w insertObject:[NSNumber numberWithFloat:r_w] atIndex:count];
float r_a = (float)(random() % 100);
[mijnArray_r_a insertObject:[NSNumber numberWithFloat:r_a] atIndex:count];
CGContextSetLineWidth(viewContext, 2.0);
CGContextSetStrokeColorWithColor(viewContext, [UIColor blackColor].CGColor);
for (int k = 0; k <= count; k++) {
float temp_x = [[mijnArray_r_x objectAtIndex: k] floatValue];
float temp_y = [[mijnArray_r_y objectAtIndex: k] floatValue];
float temp_w = [[mijnArray_r_w objectAtIndex: k] floatValue];
float temp_a = [[mijnArray_r_a objectAtIndex: k] floatValue];
CGPoint pointpointpoint = CGPointMake(temp_x, temp_y);
CGPoint pointpointpointpoint = CGPointMake(temp_w, temp_a);
CGContextMoveToPoint(viewContext, pointpointpoint.x, pointpointpoint.y);
CGContextAddLineToPoint(viewContext, pointpointpoint.x - pointpointpointpoint.x, pointpointpoint.y + pointpointpointpoint.y);
CGContextStrokePath(viewContext);
}
} }
You can draw your lines into a UIImage by using UIGraphicsBeginImageContext(). This way, the time needed to draw will be constant, because in each iteration you're only drawing the cached image as a bitmap and the new lines you add.
- (void)drawRect:(CGRect)rect {
[self.currentImage drawInRect:[self bounds]];
UIGraphicsBeginImageContext([self bounds].size);
CGContextRef cgContext = UIGraphicsGetCurrentContext();
//Draw your lines into the cgContext here...
self.currentImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
This assumes that you have the property currentImage of type UIImage declared.

How do you stroke the _outside_ of an NSAttributedString?

I've been using NSStrokeWidthAttributeName on NSAttributedString objects to put an outline around text as it's drawn. The problem is that the stroke is inside the fill area of the text. When the text is small (e.g. 1 pixel thick), the stroking makes the text hard to read. What I really want is a stroke on the outside. Is there any way to do that?
I've tried an NSShadow with no offset and a blur, but it's too blurry and hard to see. If there was a way to increase the size of the shadow without any blur, that would work too.
While there may be other ways, one way to accomplish this is to first draw the string with only a stroke, then draw the string with only a fill, directly overtop of what was previously drawn. (Adobe InDesign actually has this built-in, where it will appear to only apply the stroke to the outside of letter, which helps with readability).
This is just an example view that shows how to accomplish this (inspired by http://developer.apple.com/library/mac/#qa/qa2008/qa1531.html):
First set up the attributes:
#implementation MDInDesignTextView
static NSMutableDictionary *regularAttributes = nil;
static NSMutableDictionary *indesignBackgroundAttributes = nil;
static NSMutableDictionary *indesignForegroundAttributes = nil;
- (void)drawRect:(NSRect)frame {
NSString *string = #"Got stroke?";
if (regularAttributes == nil) {
regularAttributes = [[NSMutableDictionary
dictionaryWithObjectsAndKeys:
[NSFont systemFontOfSize:64.0],NSFontAttributeName,
[NSColor whiteColor],NSForegroundColorAttributeName,
[NSNumber numberWithFloat:-5.0],NSStrokeWidthAttributeName,
[NSColor blackColor],NSStrokeColorAttributeName, nil] retain];
}
if (indesignBackgroundAttributes == nil) {
indesignBackgroundAttributes = [[NSMutableDictionary
dictionaryWithObjectsAndKeys:
[NSFont systemFontOfSize:64.0],NSFontAttributeName,
[NSNumber numberWithFloat:-5.0],NSStrokeWidthAttributeName,
[NSColor blackColor],NSStrokeColorAttributeName, nil] retain];
}
if (indesignForegroundAttributes == nil) {
indesignForegroundAttributes = [[NSMutableDictionary
dictionaryWithObjectsAndKeys:
[NSFont systemFontOfSize:64.0],NSFontAttributeName,
[NSColor whiteColor],NSForegroundColorAttributeName, nil] retain];
}
[[NSColor grayColor] set];
[NSBezierPath fillRect:frame];
// draw top string
[string drawAtPoint:
NSMakePoint(frame.origin.x + 200.0, frame.origin.y + 200.0)
withAttributes:regularAttributes];
// draw bottom string in two passes
[string drawAtPoint:
NSMakePoint(frame.origin.x + 200.0, frame.origin.y + 140.0)
withAttributes:indesignBackgroundAttributes];
[string drawAtPoint:
NSMakePoint(frame.origin.x + 200.0, frame.origin.y + 140.0)
withAttributes:indesignForegroundAttributes];
}
#end
This produces the following output:
Now, it's not perfect, since the glyphs will sometimes fall on fractional boundaries, but, it certainly looks better than the default.
If performance is an issue, you could always look into dropping down to a slightly lower level, such as CoreGraphics or CoreText.
Just leave here my solution based on answer of #NSGod, result is pretty the same just having proper positioning inside UILabel
It is also useful when having bugs on iOS 14 when stroking letters with default system font (refer also this question)
Bug:
#interface StrokedTextLabel : UILabel
#end
/**
* https://stackoverflow.com/a/4468880/3004003
*/
#implementation StrokedTextLabel
- (void)drawTextInRect:(CGRect)rect
{
if (!self.attributedText) {
[super drawTextInRect:rect];
return;
}
NSMutableAttributedString *attributedText = self.attributedText.mutableCopy;
[attributedText enumerateAttributesInRange:NSMakeRange(0, attributedText.length) options:0 usingBlock:^(NSDictionary<NSAttributedStringKey, id> *attrs, NSRange range, BOOL *stop) {
if (attrs[NSStrokeWidthAttributeName]) {
// 1. draw underlying stroked string
// use doubled stroke width to simulate outer border, because border is being stroked
// in both outer & inner directions with half width
CGFloat strokeWidth = [attrs[NSStrokeWidthAttributeName] floatValue] * 2;
[attributedText addAttributes:#{NSStrokeWidthAttributeName : #(strokeWidth)} range:range];
self.attributedText = attributedText;
// perform default drawing
[super drawTextInRect:rect];
// 2. draw unstroked string above
NSMutableParagraphStyle *style = [NSMutableParagraphStyle new];
style.alignment = self.textAlignment;
[attributedText addAttributes:#{
NSStrokeWidthAttributeName : #(0),
NSForegroundColorAttributeName : self.textColor,
NSFontAttributeName : self.font,
NSParagraphStyleAttributeName : style
} range:range];
// we use here custom bounding rect detection method instead of
// [attributedText boundingRectWithSize:...] because the latter gives incorrect result
// in this case
CGRect textRect = [self boundingRectWithAttributedString:attributedText forCharacterRange:NSMakeRange(0, attributedText.length)];
[attributedText boundingRectWithSize:rect.size options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
// adjust vertical position because returned bounding rect has zero origin
textRect.origin.y = (rect.size.height - textRect.size.height) / 2;
[attributedText drawInRect:textRect];
}
}];
}
/**
* https://stackoverflow.com/a/20633388/3004003
*/
- (CGRect)boundingRectWithAttributedString:(NSAttributedString *)attributedString
forCharacterRange:(NSRange)range
{
NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:[self bounds].size];
textContainer.lineFragmentPadding = 0;
[layoutManager addTextContainer:textContainer];
NSRange glyphRange;
// Convert the range for glyphs.
[layoutManager characterRangeForGlyphRange:range actualGlyphRange:&glyphRange];
return [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];
}
#end
Swift version
import Foundation
import UIKit
/// https://stackoverflow.com/a/4468880/3004003
#objc(MUIStrokedTextLabel)
public class StrokedTextLabel : UILabel {
override public func drawText(in rect: CGRect) {
guard let attributedText = attributedText?.mutableCopy() as? NSMutableAttributedString else {
super.drawText(in: rect)
return
}
attributedText.enumerateAttributes(in: NSRange(location: 0, length: attributedText.length), options: [], using: { attrs, range, stop in
guard let strokeWidth = attrs[NSAttributedString.Key.strokeWidth] as? CGFloat else {
return
}
// 1. draw underlying stroked string
// use doubled stroke width to simulate outer border, because border is being stroked
// in both outer & inner directions with half width
attributedText.addAttributes([
NSAttributedString.Key.strokeWidth: strokeWidth * 2
], range: range)
self.attributedText = attributedText
// perform default drawing
super.drawText(in: rect)
// 2. draw unstroked string above
let style = NSMutableParagraphStyle()
style.alignment = textAlignment
let attributes = [
NSAttributedString.Key.strokeWidth: NSNumber(value: 0),
NSAttributedString.Key.foregroundColor: textColor ?? UIColor.black,
NSAttributedString.Key.font: font ?? UIFont.systemFont(ofSize: 17),
NSAttributedString.Key.paragraphStyle: style
]
attributedText.addAttributes(attributes, range: range)
// we use here custom bounding rect detection method instead of
// [attributedText boundingRectWithSize:...] because the latter gives incorrect result
// in this case
var textRect = boundingRect(with: attributedText, forCharacterRange: NSRange(location: 0, length: attributedText.length))
attributedText.boundingRect(
with: rect.size,
options: .usesLineFragmentOrigin,
context: nil)
// adjust vertical position because returned bounding rect has zero origin
textRect.origin.y = (rect.size.height - textRect.size.height) / 2
attributedText.draw(in: textRect)
})
}
/// https://stackoverflow.com/a/20633388/3004003
private func boundingRect(
with attributedString: NSAttributedString?,
forCharacterRange range: NSRange
) -> CGRect {
guard let attributedString = attributedString else {
return .zero
}
let textStorage = NSTextStorage(attributedString: attributedString)
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: bounds.size)
textContainer.lineFragmentPadding = 0
layoutManager.addTextContainer(textContainer)
var glyphRange = NSRange()
// Convert the range for glyphs.
layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange)
return layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer)
}
}