Manually calculate font size for drawn NSAttributedString based on container width - uikit

UILabel has the property adjustsFontSizeToFitWidth which calculates the perfect font size if the bounding box is smaller than what text can be displayed at the current font size.
I am drawing text on a UIGraphicsImageRendererContext which is then exported to an image. I would like to recreate the behavior of adjustsFontSizeToFitWidth with the NSAttributedStrings I am currently using. This is the code I use to draw:
let text = NSAttributedString(string: "Example Text", attributes: [NSAttributedString.Key.font: font.withSize(16)])
text.draw(in: CGRect(x: 100, y: 100, width: 100, height: 20))
Is there a way to calculate the perfect font size based on the available width? I assume this would be an algorithm?

I achieved this by finding the scale factor by diving the smaller container width with the proposed string width, then using that scale factor to determine a new font size.
var title = NSAttributedString(string: "Example", attributes: [NSAttributedString.Key.font: font.withSize(fontSize)])
let titleWidth = title.size().width
if titleWidth > containerWidth {
let scaleFactor = containerWidth / titleWidth
title = NSAttributedString(string: title.string, attributes: [NSAttributedString.Key.font: font.withSize(fontSize * scaleFactor)])
}
title.draw(in: CGRect(x: 100, y: 100, width: containerWidth, height: containerHeight))

Related

FabricJS: How to auto-resize image to fit group or rectangle object?

Goal: I'm seeking to add an image to a FabricJS group or rectangle object, and for the image to maintain it's original aspect ratio and center fit it's parent width/height.
I don't want the image overflow to show, but will show the overflow as a less opaque green in the examples below:
Landscape Image Example:
If it was a landscape oriented image, it would scale to max height, but then center itself with regards to width placement:
Since I'm not looking for the image overflow to show, the final product should look like this:
Portrait Image Example:
Similarly, if the image was portrait oriented, the image would scale 100% in width, but center itself in height:
And then negating the overflow, this is what I'm looking for as a final product:
Here's my stackblitz so far: https://stackblitz.com/edit/angular-gpfkkw
With the base-case code as follows:
this.canvas = new fabric.Canvas('canvas');
var rect = new fabric.Rect({
left: 100,
top: 50,
width: 450,
height: 200,
fill: '#e3e3e3',
});
var rectGroup = new fabric.Group([rect], {
name: 'Rectangle',
});
this.canvas.add(rectGroup);
fabric.Image.fromURL('https://placehold.it/888x500&text=16:9', (img) => {
let bounds = rectGroup.getBoundingRect();
const scaleFactor = Math.min(
Math.min(bounds.width / img.width),
Math.min(bounds.height / img.height)
);
img.scale(scaleFactor);
img.set({
top: bounds.top + Math.max(bounds.height - img.height * scaleFactor, 0)/2,
left: bounds.left + Math.max(bounds.width - img.width * scaleFactor, 0)/2,
});
rectGroup.addWithUpdate(img);
this.canvas.renderAll();
}
This is obviously not the right solution, but a start. Any advice?
I figured it out, here's the Stackblitz: https://stackblitz.com/edit/angular-yoo8h5
First, I compared whether or not the rectangle aspect ratio is proportional to the image through an if/else statement which decides whether or not to initially scale the image to the rectangle width/height.
Then I set either the top or the left attribute of the image to the rectangle boundary point. Then calculating the center point of the rectangle and subtracting half of the image's boundary and lastly, setting the rectangle group object's clipPath to that of the rectangle, it worked!
fabric.Image.fromURL('https://placehold.it/888x500&text=16:9', (img) => {
let bounds = rectGroup.getBoundingRect();
if ((bounds.height / bounds.width) >= (img.height / img.width)) {
img.scaleToHeight(bounds.height);
img.set({
top: bounds.top,
left: (bounds.left + (bounds.width/2)) - (img.getBoundingRect().width/2)
});
}
else {
img.scaleToWidth(bounds.width);
img.set({
top: (bounds.top + (bounds.height/2)) - (img.getBoundingRect().height/2),
left: bounds.left
});
}
rectGroup.addWithUpdate(img);
rectGroup.clipPath = rect;
this.canvas.renderAll();
});
A bit late here but chipping in in case this might be helpful.
You were heading in the right direction with Math.min, but you can use Math.max instead for the side with the larger group/image ratio to scale to the full length of the side. You can then set the x y origins to center.
fabric.Image.fromURL('https://placehold.it/888x500&text=16:9', (img) => {
let bounds = rectGroup.getBoundingRect();
const scaleFactor = Math.max(bounds.width / img.width, bounds.height / img.height);
img.set({
originX: 'center',
originY: 'center',
scaleX: scaleFactor,
scaleY: scaleFactor
});
rectGroup.addWithUpdate(img);
this.canvas.renderAll();
}

How to add UIImageViews to a UIStackView that is constrained to the centerXAnchor of a view?

I'm trying to add profile icons via UIImageViews to a UIStackView in order to keep the icons centered in a view. How would I go about adding UIImageViews of a fixed frame to a UIStackView and keep the UIStackView centered in the main view according to varying numbers of UIImageViews in the UIStackView?
let memberIcons: UIStackView = {
let iconView = UIStackView()
iconView.translatesAutoresizingMaskIntoConstraints = false
iconView.axis = .horizontal
iconView.spacing = 5
iconView.distribution = .equalSpacing
iconView.alignment = .center
return iconView
}()
for member in story!.members {
let circle = UIImageView()
circle.frame = CGRect(x: 0, y: 0, width: 36, height: 36)
circle.translatesAutoresizingMaskIntoConstraints = false
circle.layer.cornerRadius = CGFloat(circle.frame.width / 2)
circle.image = member.profilePicture
circle.contentMode = .scaleAspectFill
circle.clipsToBounds = true
memberIcons.addArrangedSubview(circle)
}
Because you set memberIcons.distribution = .equalSpace, the stack view will ask its subviews for their intrinsic sizes. When asked, the UIImage (i.e. circle) will calculate its intrinsic size as "image pixel size / scale", which is not what you want -- you want the image to be of fixed size (36 x 36).
Use Auto Layout on circle:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(memberIcons)
memberIcons.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
memberIcons.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
// Limit the stack view's width to no more than 75% of the superview's width
// Adjust as needed
memberIcons.widthAnchor.constraint(lessThanOrEqualTo: view.widthAnchor, multiplier: 0.75).isActive = true
let width: CGFloat = 36.0
for member in story!.members {
// We don't care about the frame here as we're gonna use auto layout
let circle = UIImageView(frame: .zero)
circle.translatesAutoresizingMaskIntoConstraints = false
circle.layer.cornerRadius = width / 2
circle.image = member.profilePicture
circle.contentMode = .scaleAspectFill
circle.clipsToBounds = true
circle.layer.borderWidth = 1
circle.layer.borderColor = UIColor.lightGray.cgColor
memberIcons.addArrangedSubview(circle)
circle.widthAnchor.constraint(equalToConstant: width).isActive = true
circle.heightAnchor.constraint(equalToConstant: width).isActive = true
}
}
Result:
Because we limit the width of the UIStackView, there a maximum number of profile images you can add (7 in this case) before you get a bunch of auto layout error on the console. You can enclose the Stack View inside a Scroll View or use a Collection View for a matrix-like display.

change the color and the font of NStextView

I am trying to change the color and the font size of NSTextView but it doesn't seem to work.This is my code.I have checked other posts but they dont seem to be working.This is my code.
var area:NSRange = NSMakeRange(0, textView.textStorage!.length)
self.textView.setTextColor(NSColor.grayColor(), range: area)
self.textView.textStorage?.font = NSFont(name: "Calibri", size: 16)
You should use attributed strings:
let textView = NSTextView(frame: NSMakeRect(0, 0, 100, 100))
let attributes = [NSForegroundColorAttributeName: NSColor.redColor(),
NSBackgroundColorAttributeName: NSColor.blackColor()]
let attrStr = NSMutableAttributedString(string: "my string", attributes: attributes)
let area = NSMakeRange(0, attrStr.length)
if let font = NSFont(name: "Helvetica Neue Light", size: 16) {
attrStr.addAttribute(NSFontAttributeName, value: font, range: area)
textView.textStorage?.appendAttributedString(attrStr)
}

Resize UIImageView based on image with respect to UIImageViewWidth

I have a UIImageView that is the width of the entire screen and the height is 400 pixels.
The end result I am looking for is that every single image has the exact same width (the screen width) and the height is adjusted to accommodate this while keeping its aspect ratio.
So if an image is 400 pixels wide, it needs to reduce to 320 pixels wide, and the height of the image view should adjust and become SHORTER to keep the ratio.
If an image is 240 pixels wide, it needs to increase its width to 320 and adjust the hight to be TALLER to keep the ratio.
I have been looking through many posts that all seem to just point to setting the content mode to aspect fit, but this does nothing like what I am looking for.
Any help would be great, thanks!
So it looks like shortly after I posted it, I checked storyboard, and for some reason the code was not overwriting the storyboard.
If I change it to Aspect Fit in storyboard, it actually functions the way it is supposed to.
::face palm::
You just need to set the content mode property to Aspect Fit in your imageview.
UIImage *originalImage = [UIImage imageNamed:#"xxx.png"];
double width = originalImage.size.width;
double height = originalImage.size.height;
double apectRatio = width/height;
//You can mention your own width like 320.0
double newHeight = [[UIScreen mainScreen] bounds].size.width/ apectRatio;
self.img.frame = CGRectMake(0, 0, [[UIScreen mainScreen] bounds].size.width, newHeight);
self.img.center = self.view.center;
self.img.image = originalImage;
func resizeImage(image: UIImage, targetSize: CGSize) -> UIImage {
let size = image.size
let widthRatio = targetSize.width / image.size.width
let heightRatio = targetSize.height / image.size.height
// Figure out what our orientation is, and use that to form the rectangle
var newSize: CGSize
if(widthRatio > heightRatio) {
newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
} else {
newSize = CGSize(width: size.width * widthRatio, height: size.height * widthRatio)
}
// This is the rect that we've calculated out and this is what is actually used below
let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
// Actually do the resizing to the rect using the ImageContext stuff
UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
image.draw(in: rect)
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return newImage!
}
Now get the resized image from the original image, as I done it like:
let image = UIImage(named: "YOUR IMAGE NAME")
let newHeight = (image?.size.height/image?.size.width) * YOUR_UIIMAGE_VIEW_WIDTH
let newSize = CGSize(width: YOUR_UIIMAGE_VIEW_WIDTH, height: newHeight)
let newResizedImage = resizeImage(image: image, targetSize: newSize)
Hope, this will help.

KineticJS - Crop fill image

I'm trying to use a tileset in my game. I want to crop the fill image but can't because
I can't fill a shape with a Kinetic.Image() object.
I can't crop a Image() object.
var rect = new Kinetic.Rect({
x: 0,
y: 0,
width: 64,
height: 64,
fill:{
image: imageObj,
crop:{
x: 128,
y: 128,
width: 64,
height: 64
},
},
strokeWidth: 1
});
This doesn't work, and I also can't replace image: imageObj with a Kinetic.Image() object that is precropped. Any ideas?
EDIT: Looks like it might be possible by drawing the Kinetic.Image to the scene, using toDataUrl to load the cropped Kinetic.Image into a image object useable by fill, then continue with the map script. This introduces a ton of performance issues/loading times so I'm going to go ahead and assume what I want to do just isn't feasible. If anyone has any ideas on how to do this properly please let me know.
You can get fill to accept an image, as shown in this example with the Vader/Yoda:
http://www.html5canvastutorials.com/kineticjs/html5-canvas-set-fill-with-kineticjs/
What i'd suggest is loading your images into an array (as he does in the example) then calling the images out as you create the tile.
I changed the code for the first Pentagon to a Rectangle on that example like so:
var colorPentagon = new Kinetic.Rect({
x: 40,
y: 50,
width: 100,
height: 100,
fill: {
image: images.darthVader,
offset: [-220, -70]
},
stroke: "black",
strokeWidth: 4,
draggable: true
});
And it worked, so no reason that can't give you what you want.

Resources