Change NSTextField font size to fit - cocoa

Is there anything like the UILabel's adjustsFontSizeToFitWidth that can be used with a NSTextField?

In short: no. You have to do some brute force work to determine a string's -sizeWithAttributes: -boundingRectWithSize:options:attributes: with a given font size (set as an NSFont for NSFontAttributeName).
I'd start with a standard system font size and work down or up from there, depending on whether it's smaller or larger than the desired rectangle.

Swift 4 solution:
It will resize one by one until it fits, or until minimumFontSize = 3.
let minimumFontSize = 3
var sizeNotOkay = true
var attempt = 0
while sizeNotOkay || attempt < 15 { // will try 15 times maximun
let expansionRect = textField.expansionFrame(withFrame: textField.frame)
let truncated = !NSEqualRects(NSRect.zero, expansionRect)
if truncated {
if let actualFontSize : CGFloat = textField.font?.fontDescriptor.object(forKey: NSFontDescriptor.AttributeName.size) as? CGFloat {
textField.font = NSFont.systemFont(ofSize: actualFontSize - 1)
if actualFontSize < minimumFontSize {
break
}
}
} else {
sizeNotOkay = false
}
attempt += 1
}

I came up with my own solution (its not a good solution!, just in case anyone couldn't find a better solution)
extension NSTextField {
func fontSizeToFit() {
if stringValue.count > 90 {
font = NSFont.systemFont(ofSize: 40)
} else {
font = NSFont.systemFont(ofSize: CGFloat(120 - stringValue.count))
}
}
}

Related

Displaying float to a variable number of decimal places in Swift

Is there a simple way of displaying a float or double to a relevant number of decimal places in Swift.
For example, an iOS app using SI units, which can be altered depending on the property desired, and converted through up to 6 orders of magnitude depending on desired inputs and output. Therefore it needs to display not only 1mg to 1000 micrograms, but also the other way around - i.e 1 microgram = 0.001 mg.
I can easily format a string as follows:
textFieldFoo.text = NSString(format: "%.1f mg", bar) as String
However, if the user were to convert from 1mcg to 0.001mg, this would display as
0.0 mg
Yet, to include up to 6 decimal places to encompass all common possibilities would lead to an unwieldy, ugly looking UI.
Is there a simple way to format a string, in order to include a float/ double where it is displayed to a relevant number of decimal places/ significant figures? I'm sure, given time and enough boilerplate code, that I could pyramid if/ else it to get a result, but that's frankly inelegant.
There's NSMAssFormatter but it doesn't go all the way down to microgram. It was designed to format human-level weight.
You can roll your own by subclassing NSNumberFormatter:
enum MassUnit: Double {
case Microgram = 1e-6
case Milligram = 1e-3
case Gram = 1
case Kilogram = 1e3
static let allUnits: [MassUnit] = [.Microgram, .Milligram, .Gram, .Kilogram]
var unitAbbreviation: String {
get {
switch self {
case .Microgram: return "mcg"
case .Milligram: return "mg"
case .Gram: return "g"
case .Kilogram: return "kg"
}
}
}
}
class MyMassFormatter: NSNumberFormatter {
func bestFitStringForWeightInGrams(weight: Double) -> String {
var selectedString = self.stringFromNumber(weight)!
var selectedUnit = MassUnit.Gram
// Pick the unit that results in the shortest string
for unit in MassUnit.allUnits {
if let str = self.stringFromNumber(weight / unit.rawValue)
where str.characters.count < selectedString.characters.count {
selectedString = str
selectedUnit = unit
}
}
return selectedString + selectedUnit.unitAbbreviation
}
}
Usage:
let formatter = MyMassFormatter()
formatter.format = "0.######"
print(formatter.bestFitStringForWeightInGrams(0.000001)) // 1mcg
print(formatter.bestFitStringForWeightInGrams(0.005)) // 5mg
print(formatter.bestFitStringForWeightInGrams(2500)) // 2.5kg
print(formatter.bestFitStringForWeightInGrams(1234.5)) // 1234.5g
Formatting to Significant Figures using Swift
What you want is the ability to format to a fixed number of significant figures, rather than a fixed number of decimal places. A good swift option to solve this is using class extensions, with a little maths to decide how many decimal places to show based on the magnitude of the number.
The example below extends the Double class to enable formatting to a fixed number of significant figures and uses either float notation or scientific notation depending on the magnitude of the number.
import Foundation
//extension to format a Double to a fixed number of significant figures
extension Double {
func sigFigs(_ numberOfSignificantFigures: Int) -> String {
let mag = log10(abs(self))
let intMag = Int(mag)
if mag >= 0 {
if intMag < numberOfSignificantFigures {
return String(format: "%.\(numberOfSignificantFigures - intMag - 1)f",self)
}
else {
return String(format: "%.\(numberOfSignificantFigures - 1)e",self)
}
}
else {
if -intMag < numberOfSignificantFigures {
return String(format: "%.\(numberOfSignificantFigures)f",self)
}
else {
return String(format: "%.\(numberOfSignificantFigures - 1)e",self)
}
}
}
}
Usage
let num1 = 1234.5678
let num2 = 12.345678
let num3 = 0.0012345678
let num4 = 1234567.8
print(num1.sigFigs(6))
print(num1.sigFigs(2))
print(num2.sigFigs(6))
print(num2.sigFigs(2))
print(num3.sigFigs(6))
print(num3.sigFigs(2))
print(num4.sigFigs(6))
print(num4.sigFigs(2))
Output
1234.57
1.2e+03
12.3457
12
0.001235
1.2e-03
1.23457e+06
1.2e+06
If I understand you correctly you are:
using Swift
working with SI units
trying to display floating points
trying to avoid boilerplate and possibly magic numbers
You should definitely use Apple's Measurement which is :
A numeric quantity labeled with a unit of measure, with support for unit conversion and unit-aware calculations.
and MeasurementFormatter which is :
A formatter that provides localized representations of units and measurements.
MeasurementFormatter uses a NumberFormatter to format the quantity of a measurement.
NumberFormatters's usesSignificantDigits property is set to false by default but :
Set this property to true to format numbers according to the significant digits configuration specified by the minimumSignificantDigits and maximumSignificantDigits properties. By default, the minimum number of significant digits is 1, and the maximum number of significant digits is 6.
Here's an example of what you can do with masses
let micrograms = Measurement(value: 1, unit: UnitMass.micrograms) // 1.0 µg
let nanograms = micrograms.converted(to: .nanograms) // 1000.0000000000001 ng
let picograms = micrograms.converted(to: .picograms) // 1000000.0 pg
let milligrams = micrograms.converted(to: .milligrams) // 0.001 mg
let centigrams = micrograms.converted(to: .centigrams) // 0.0001 cg
let decigrams = micrograms.converted(to: .decigrams) // 1e-05 dg
let grams = micrograms.converted(to: .grams) // 1e-06 g
let kilograms = micrograms.converted(to: .kilograms) // 1e-09 kg
let ounces = micrograms.converted(to: .ounces) // 3.527399072294044e-08 oz
let pounds = micrograms.converted(to: .pounds) // 2.2046244201837776e-09 lb
let stones = micrograms.converted(to: .stones) // 1.574731232746851e-10 st
let formatter = MeasurementFormatter()
formatter.numberFormatter.usesSignificantDigits = true
formatter.unitOptions = .providedUnit
formatter.string(from: nanograms) // "1 000 ng"
formatter.string(from: picograms) // "1 000 000 pg"
formatter.string(from: micrograms) // "1 µg"
formatter.string(from: milligrams) // "0,001 mg"
formatter.string(from: centigrams) // "0,0001 cg"
formatter.string(from: decigrams) // "0,00001 dg"
formatter.string(from: grams) // "0,000001 g"
formatter.string(from: kilograms) // "0,000000001 kg"
formatter.string(from: ounces) // "0,000000035274 oz"
formatter.string(from: pounds) // "0,00000000220462 lb"
formatter.string(from: stones) // "0,000000000157473 st"

Expected Declaration Swift (Sprite Kit)

I'm using Xcode 7 with swift and when I type
class Block {
var Block = SKShapeNode(circleOfRadius: 15)
Block.fillColor = SKColor.redColor() //Error Here
Block.physicsBody = SKPhysicsBody(circleOfRadius: 15)
Block.physicsBody?.affectedByGravity = true
Block.physicsBody?.restitution = 0
Block.physicsbody?.LinearDamping = 0
self.addChild(Block)
It gives me an error that says "expected declaration" (on the line with the comment) and I don't know why
There are some errors:
You have a class and are trying to change class properties outside the scope of a method or an initializer.
The line Block.physicsbody?.LinearDamping = 0 should be Block.physicsBody?.linearDamping = 0; case-sensitivity.
You name your SKShapeNode instance as Block, the same name as your class. By naming convention, class (type) names begin with Capital letters, whereas class properties use small letters in their names.
With these three fixed, we can proceed to looking at your scene.
With help from Leo Dabus (thanks!), we should have enough to set you up with a minimal working example (using your code) of the SKScene:
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
let block = SKShapeNode(circleOfRadius: 15)
// you will also need to set your node initial position
// if you would like your red circle to fall from the middle of the top of your scene you need to use the scene frame midX and maxY (not the view frame). the scene it is not necessarily the same size of your view)
block.position = CGPoint(x: scene!.frame.midX, y: scene!.frame.maxY)
block.fillColor = SKColor.redColor()
block.physicsBody = SKPhysicsBody(circleOfRadius: 15)
block.physicsBody?.affectedByGravity = true
block.physicsBody?.restitution = 0
block.physicsBody?.linearDamping = 0
self.addChild(block)
}
// ...
}

Getting the width of Win32 TreeView control

The Win32 TreeView control does not have a built-in message/macro to get its (scrollable) width, e.g. if want to set the TreeView's width so it won't need to have a scrollbar.
How can this be done?
Here's a C function to do this:
int TreeView_GetWidth(HWND hTreeWnd)
{
SCROLLINFO scrollInfo;
SCROLLBARINFO scrollBarInfo;
scrollInfo.cbSize = sizeof(scrollInfo);
scrollInfo.fMask = SIF_RANGE;
scrollBarInfo.cbSize = sizeof(scrollBarInfo);
// To find the whole (scrollable) width of the tree control,
// we determine the range of the scrollbar.
// Unfortunately when a scrollbar isn't needed (and is invisible),
// its range isn't zero (but rather 0 to 100),
// so we need to specifically ignore it then.
if (GetScrollInfo(hTreeWnd, SB_HORZ, &scrollInfo) &&
GetScrollBarInfo(hTreeWnd, OBJID_HSCROLL, &scrollBarInfo))
{
// Only if the scrollbar is displayed
if ((scrollBarInfo.rgstate[0] & STATE_SYSTEM_INVISIBLE) == 0)
{
int scrollBarWidth = GetSystemMetrics(SM_CXVSCROLL);
// This is a hardcoded value to accomodate some extra pixels.
// If you can find a cleaner way to account for them (e.g. through
// some extra calls to GetSystemMetrics), please do so.
// (Maybe less than 10 is also enough.)
const int extra = 10;
return (scrollInfo.nMax - scrollInfo.nMin) + scrollBarWidth + extra;
}
}
return 0;
}

how to set "smart" breakpoint in Xcode when method returns a specific value?

I have a method which returns a bool value, with several exit points.
However, it does not seem to work correctly, so I would like to set an automatic breakpoint to see when it returns a YES value, so I can check all the variables and calculations in the debugger.
I would like to stop the debugger whenever a YES value is returned.
I have a similar smart breakpoint set for objc_exception_throw, so I know it's possible, I am just not sure how.
(In case it helps anyone, the way you can set the exception breakpoint: in the Breakpoints window (Run -> Show -> Breakpoints) enter objc_exception_throw as "Breakpoint", and libobjc.A.dylib as "Location")
EDIT: the specific code I would like to use it for:
- (BOOL)collisionOccured {
// Assumption: helicopter is of square shape (collision calculated by radius), walls are rectangles
// This approach is based on the solution seen here: http://stackoverflow.com/questions/401847/circle-rectangle-collision-detection-intersection/402010#402010
float helicopterImageWidth = [helicopter texture].contentSize.width;
float wallImageWidth = [[walls lastObject] texture].contentSize.width;
float wallImageHeight = [[walls lastObject] texture].contentSize.height;
float helicopterCollisionRadius = helicopterImageWidth * 0.4f;
CGPoint helicopterPosition = helicopter.position;
int numWalls = [walls count];
for (int i = 0; i < numWalls; i++) {
CCSprite *wall = [walls objectAtIndex:i];
if ([wall numberOfRunningActions] == 0) {
// The wall is not moving, we can skip checking it.
continue;
}
CGPoint wallPosition = wall.position;
float helicopterDistanceX = abs(helicopterPosition.x - wallPosition.x - wallImageWidth/2);
float helicopterDistanceY = abs(helicopterPosition.y - wallPosition.y - wallImageHeight/2);
if (helicopterDistanceX > (wallImageWidth/2 + helicopterCollisionRadius)) { return NO; }
if (helicopterDistanceY > (wallImageHeight/2 + helicopterCollisionRadius)) { return NO; }
if (helicopterDistanceX <= (wallImageWidth/2)) { return YES; }
if (helicopterDistanceY <= (wallImageHeight/2)) { return YES; }
float cornerDistance_sq = powf((helicopterDistanceX - wallImageWidth/2), 2) +
powf((helicopterDistanceY - wallImageHeight/2), 2);
return (cornerDistance_sq <= powf(helicopterCollisionRadius, 2));
}
// this should not occur
return NO;
}
This method is called via
- (void)update:(ccTime)delta {
if ([self collisionOccured]) {
NSLog(#"A collision occured");
}
}
The problem is that the update method takes delta (time passed) as argument, so I can't check what's happening frame by frame -- whenever I continue the execution, I am presented with a different scene.
(I am using cocos2d in the code)
You can set conditional breakpoints. With a slight tweak to update:
- (void)update:(ccTime)delta {
BOOL collided = [self collisionOccured];
if (collided) {
NSLog(#"A collision occured");
}
}
you can set a breakpoint as normal after the BOOL's assignment (i.e. on the if line), then right-click on the blue breakpoint arrow and select "Show Message Bubble", and add collided as the Condition. The extra variable should get optimized away in Release build mode.
If you're using a local return variable:
- (BOOL)someMethod {
BOOL ret = NO;
if (something) {
ret = YES;
} else if (something_else) {
ret = YES;
}
// ... and so on
return ret;
}
You can just set a watch point on ret
Otherwise, you're probably stuck with stepping through the code—hopefully some clever combination of conditional breakpoints will help you not have to break on every invocation. Setting a method breakpoint like you do with objc_exception_throw wouldn't work, because it will stop on every invocation, and breaking on the return value at the calling site is too late to figure out how you got there.
If you can post the code, we may be able to give more specific help as to a debugging strategy. Good luck. :-)

UISegemnted COntrol - selecting 3rd segment activates second segment

I have a segmented control with 3 defined segments. i am looking to capture the segment index so I can use it in an if statement to update variables accordingly as such:
-(IBAction)numPlayers:(id)sender;
{
numPlayersSegment = [(UISegmentedControl *)sender retain];
if (numPlayersSegment.selectedSegmentIndex == 0)
{
numOfPlayers = 2;
}
else if (numPlayersSegment.selectedSegmentIndex = 1)
{
numOfPlayers = 3;
}
else if (numPlayersSegment.selectedSegmentIndex = 2)
{
numOfPlayers = 4;
}
else if (numPlayersSegment.selectedSegmentIndex = -1)
{
numOfPlayers = 0;
}
NSLog(#"Players selected = %d", numPlayersSegment.selectedSegmentIndex);
However whenever I press the third segment (index 2) it returns the value of the second segment (index 1) and also highlights the 2nd segment. I can see nothing untoward in IB.
Has anybody seen this and have any suggestions.
I am using xcode 3.2.1 on snow leopard
thanks
== and = don't function the same. At a minimum you should fix that. You also don't want the added retain to sender.

Resources