NSTextField Vertical alignment - xcode

I am creating cocoa app in which i created NSTextField programmatically like this,
NSView *superView = [[NSView alloc] initWithFrame:NSMakeRect(0, 300, 1400, 500)];
NSTextField *myTextField = [[NSTextField alloc] initWithFrame:NSMakeRect(180, 100, 1000, 300)];
[myTextField setStringValue:myText];
[myTextField setEditable:NO];
[myTextField setBezeled:NO];
[myTextField setDrawsBackground:NO];
[myTextField setSelectable:NO];
[myTextField setFont:[NSFont fontWithName:#"Futura" size:22]];
[superView addSubview:myTextField];
[superView setAutoresizesSubviews:YES];
[myTextField setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[self.window.contentView addSubview:superView];
Now i want vertical alignment of my text and it should be adjustable according to text length.
Anyone have any suggestions? Please share your suggestion :)
Many Thanks..!!

What you want is possible, but you'll have to make a subclass of NSTextFieldCell, and then use that subclass in your NSTextField. The key methods you want override are drawingRectForBounds:, selectWithFrame:, and editWithFrame:
Here is a blog post from the fantastic Daniel Jalkut about this, he even includes a downloadable version ready to go. The post is fairly old but it should still work fine.

For adjusting the text field to string size you have to calculate the text size with NSString's sizeWithFont: constrainedToSize: lineBreakMode: before and than adjust the text field's size with setting the frame.
For vertical alignment look at this question (and the answers).

Here is a Swift version of the solution linked to by sosborn above:
open class VerticallyCenteredTextFieldCell: NSTextFieldCell {
fileprivate var isEditingOrSelecting : Bool = false
open override func drawingRect(forBounds rect: NSRect) -> NSRect {
let rect = super.drawingRect(forBounds: rect)
if !isEditingOrSelecting {
let size = cellSize(forBounds: rect)
return NSRect(x: rect.minX, y: rect.minY + (rect.height - size.height) / 2, width: rect.width, height: size.height)
}
return rect
}
open override func select(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, start selStart: Int, length selLength: Int) {
let aRect = self.drawingRect(forBounds: rect)
isEditingOrSelecting = true
super.select(withFrame: aRect, in: controlView, editor: textObj, delegate: delegate, start: selStart, length: selLength)
isEditingOrSelecting = false
}
open override func edit(withFrame rect: NSRect, in controlView: NSView, editor textObj: NSText, delegate: Any?, event: NSEvent?) {
let aRect = self.drawingRect(forBounds: rect)
isEditingOrSelecting = true
super.edit(withFrame: aRect, in: controlView, editor: textObj, delegate: delegate, event: event)
isEditingOrSelecting = false
}
}

override ViewWillDraw and position your NSTextField in the center of its parent view at this point. This works with live resizing as well. You can also adjust font size at this point to find a font which fits the new size.

Related

Aligning text vertically center in UITextView

I am setting some attributed text to textview, and giving line. I am trying to set baseline alignment vertically center but unable to set that. How can I set the text vertically center in textview.
First add/remove an observer for the contentSize key value of the UITextView when the view is appeared/disappeared:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
textView.addObserver(self, forKeyPath: "contentSize", options: NSKeyValueObservingOptions.New, context: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
textView.removeObserver(self, forKeyPath: "contentSize")
}
Apple has changed how content offsets and insets work this slightly modified solution is now required to set the top on the content inset instead of the offset.
/// Force the text in a UITextView to always center itself.
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
let textView = object as! UITextView
var topCorrect = (textView.bounds.size.height - textView.contentSize.height * textView.zoomScale) / 2
topCorrect = topCorrect < 0.0 ? 0.0 : topCorrect;
textView.contentInset.top = topCorrect
}
Try this,
UIFont *font = [UIFont fontWithName:#"HelveticaNeue-Light" size:kFontSize]; // your font choice
NSAttributedString *attributedText =
[[NSAttributedString alloc]
initWithString:self.textView.text // text to be styled.
attributes:#
{
NSFontAttributeName:font
}];
NSMutableAttributedString *attrMut = [attributedText mutableCopy];
NSInteger strLength = attrMut.length;
NSMutableParagraphStyle *style = [NSMutableParagraphStyle new];
[style setLineSpacing:3]; // LINE SPACING
[style setAlignment:NSTextAlignmentCenter];
[attrMut addAttribute:NSParagraphStyleAttributeName
value:style
range:NSMakeRange(0, strLength)];
self.textView.attributedText = [attrMut copy];

Change NSSlider bar color

I've got an NSWindow and an horizontal NSSlider.
I'd like to change the color of the right part of the slider bar when the window background color changes.
Currently, the right part of the bar isn't visible anymore when the window background is dark.
Note: the window background is actually a gradient I'm drawing by overriding drawRect in a subclass of the window's view.
I thought I could change the slider bar fill color by subclassing NSSliderCell and overriding some method like drawBarInside but I don't understand how it works: should I make a little square image and draw it repeatedly in the rect after the knob? Or maybe just draw a colored line? I've never done this before.
I've looked at this question and it's interesting but it's about drawing the knob and I don't need that for now.
I also had a look at this one which seemed very promising, but when I try to mimic this code my slider bar just disappears...
In this question they use drawWithFrame and it looks interesting but again I'm not sure how it works.
I would like to do this with my own code instead of using a library. Could somebody give me a hint about how to do this please? :)
I'm doing this in Swift but I can read/use Objective-C if necessary.
First, I created an image of a slider bar and copied it in my project.
Then I used this image in the drawBarInside method to draw in the bar rect before the normal one, so we'll see only the remainder part (I wanted to keep the blue part intact).
This has to be done in a subclass of NSSliderCell:
class CustomSliderCell: NSSliderCell {
let bar: NSImage
required init(coder aDecoder: NSCoder) {
self.bar = NSImage(named: "bar")!
super.init(coder: aDecoder)
}
override func drawBarInside(aRect: NSRect, flipped: Bool) {
var rect = aRect
rect.size = NSSize(width: rect.width, height: 3)
self.bar.drawInRect(rect)
super.drawBarInside(rect, flipped: flipped)
}
}
Pro: it works. :)
Con: it removes the rounded edges of the bar and I haven't found a way to redraw this yet.
UPDATE:
I made a Swift version of the accepted answer, it works very well:
class CustomSliderCell: NSSliderCell {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawBarInside(aRect: NSRect, flipped: Bool) {
var rect = aRect
rect.size.height = CGFloat(5)
let barRadius = CGFloat(2.5)
let value = CGFloat((self.doubleValue - self.minValue) / (self.maxValue - self.minValue))
let finalWidth = CGFloat(value * (self.controlView!.frame.size.width - 8))
var leftRect = rect
leftRect.size.width = finalWidth
let bg = NSBezierPath(roundedRect: rect, xRadius: barRadius, yRadius: barRadius)
NSColor.orangeColor().setFill()
bg.fill()
let active = NSBezierPath(roundedRect: leftRect, xRadius: barRadius, yRadius: barRadius)
NSColor.purpleColor().setFill()
active.fill()
}
}
This is correct, you have to subclass the NSSliderCell class to redraw the bar or the knob.
NSRect is just a rectangular container, you have to draw inside this container. I made an example based on an custom NSLevelIndicator that I have in one of my program.
First you need to calculate the position of the knob. You must pay attention to the control minimum and maximum value.
Next you draw a NSBezierPath for the background and another for the left part.
#import "MyCustomSlider.h"
#implementation MyCustomSlider
- (void)drawBarInside:(NSRect)rect flipped:(BOOL)flipped {
// [super drawBarInside:rect flipped:flipped];
rect.size.height = 5.0;
// Bar radius
CGFloat barRadius = 2.5;
// Knob position depending on control min/max value and current control value.
CGFloat value = ([self doubleValue] - [self minValue]) / ([self maxValue] - [self minValue]);
// Final Left Part Width
CGFloat finalWidth = value * ([[self controlView] frame].size.width - 8);
// Left Part Rect
NSRect leftRect = rect;
leftRect.size.width = finalWidth;
NSLog(#"- Current Rect:%# \n- Value:%f \n- Final Width:%f", NSStringFromRect(rect), value, finalWidth);
// Draw Left Part
NSBezierPath* bg = [NSBezierPath bezierPathWithRoundedRect: rect xRadius: barRadius yRadius: barRadius];
[NSColor.orangeColor setFill];
[bg fill];
// Draw Right Part
NSBezierPath* active = [NSBezierPath bezierPathWithRoundedRect: leftRect xRadius: barRadius yRadius: barRadius];
[NSColor.purpleColor setFill];
[active fill];
}
#end
Swift 5
class CustomSliderCell: NSSliderCell {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawBar(inside aRect: NSRect, flipped: Bool) {
var rect = aRect
rect.size.height = CGFloat(5)
let barRadius = CGFloat(2.5)
let value = CGFloat((self.doubleValue - self.minValue) / (self.maxValue - self.minValue))
let finalWidth = CGFloat(value * (self.controlView!.frame.size.width - 8))
var leftRect = rect
leftRect.size.width = finalWidth
let bg = NSBezierPath(roundedRect: rect, xRadius: barRadius, yRadius: barRadius)
NSColor.orange.setFill()
bg.fill()
let active = NSBezierPath(roundedRect: leftRect, xRadius: barRadius, yRadius: barRadius)
NSColor.purple.setFill()
active.fill()
}
}
I have achieved this without redraw or override cell at all. Using "False color" filter seems work very well and it is only a few codes!
class RLSlider: NSSlider {
init() {
super.init(frame: NSZeroRect)
addFilter()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addFilter()
}
func addFilter() {
let colorFilter = CIFilter(name: "CIFalseColor")!
colorFilter.setDefaults()
colorFilter.setValue(CIColor(cgColor: NSColor.white.cgColor), forKey: "inputColor0")
colorFilter.setValue(CIColor(cgColor: NSColor.yellow.cgColor), forKey: "inputColor1")
// colorFilter.setValue(CIColor(cgColor: NSColor.yellow.cgColor), forKey: "inputColor0")
// colorFilter.setValue(CIColor(cgColor: NSColor.yellow.cgColor), forKey: "inputColor1")
self.contentFilters = [colorFilter]
}
}

How to set up an NSTextView programmatically with explicit NSLayoutManager, NSTextStorage, NSTextContainer?

Following the apple documentation I am trying to set up a simple NSTextView via its two constructor methods.
I am placing the below code inside the viewDidAppear method of the view controller of the content view. textView is an instance of NSTextView, frameRect is the frame of the content view.
The following Swift code works (gives me an editable textView with the text showing on the screen):
textView = NSTextView(frame: frameRect!)
self.view.addSubview(textView)
textView.textStorage?.appendAttributedString(NSAttributedString(string: "Hello"))
The following does NOT work (text view is not editable and no text shown on the screen):
var textStorage = NSTextStorage()
var layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
var textContainer = NSTextContainer(containerSize: frameRect!.size)
layoutManager.addTextContainer(textContainer)
textView = NSTextView(frame: frameRect!, textContainer: textContainer)
textView.editable = true
textView.selectable = true
self.view.addSubview(textView)
textView.textStorage?.appendAttributedString(NSAttributedString(string: "Hello more complex"))
What am I doing wrong in the second example? I am trying to follow the example given in Apple's "Cocoa Text Architecture Guide" where they discuss setting up an NSTextView by explicitly instantiating its web of helper objects.
You need to keep a reference to the NSTextStorage variable you create. I'm not quite sure about the mechanics of it all, but it looks like the text view only keeps a weak reference to its text storage object. Once this object goes out of scope, it's no longer available to the text view. I guess this is in keeping with the MVC design pattern, where views (the NSTextView in this case) are meant to be independent of their models (the NSTextStorage object).
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var window: NSWindow!
var textView: NSTextView!
var textStorage: NSTextStorage! // STORE A REFERENCE
func applicationDidFinishLaunching(aNotification: NSNotification) {
var view = window.contentView as NSView
textStorage = NSTextStorage()
var layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
var textContainer = NSTextContainer(containerSize: view.bounds.size)
layoutManager.addTextContainer(textContainer)
textView = NSTextView(frame: view.bounds, textContainer: textContainer)
textView.editable = true
textView.selectable = true
view.addSubview(textView)
textView.textStorage?.appendAttributedString(NSAttributedString(string: "Hello more complex"))
}
}
Tested under Xcode 12.4. in Playgrounds:
import Cocoa
import AppKit
let textViewFrame = CGRect(x: 0, y: 0, width: 250, height: 90)
let textStorage = NSTextStorage()
var layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
var textContainer = NSTextContainer(containerSize: textViewFrame.size)
layoutManager.addTextContainer(textContainer)
let textView = NSTextView(frame: textViewFrame, textContainer: textContainer)
textView.isEditable = true
textView.isSelectable = true
textView.textColor = NSColor.red
textView.string = "Why is this so complicated..."
#import <Cocoa/Cocoa.h>
#interface TextViewController : NSObject {
NSLayoutManager *secondLayout;
IBOutlet NSSplitView *columnView;
IBOutlet NSTextView *bottomView;
}
- (IBAction) addColumn: (id)sender;
#end
#import "TextViewController.h"
#implementation TextViewController
- (void)awakeFromNib
{
NSTextStorage *storage = [bottomView textStorage];
secondLayout = [NSLayoutManager new];
[storage addLayoutManager: secondLayout];
[secondLayout release];
[self addColumn: nil];
[self addColumn: nil];
}
- (IBAction) addColumn: (id)sender
{
NSRect frame = [columnView frame];
NSTextContainer *container = [[NSTextContainer alloc]
initWithContainerSize: frame.size];
[container setHeightTracksTextView: YES];
[container setWidthTracksTextView: YES];
[secondLayout addTextContainer: container];
[container release];
NSTextView *newView = [[NSTextView alloc] initWithFrame: frame
textContainer: container];
[columnView addSubview: newView];
[newView release];
}
#end

How can I set the text color of an NSButton via Interface Builder?

There are several questions about how to set the text color programmatically. That's all fine, but there's got to be a way to do it via Interface Builder also.
The "Show Fonts" box works for changing the size of the button text, but Xcode ignores any color changes made using the widget there, and the Attributes Inspector for NSButton doesn't have a color picker...
I've no idea why this is missing still from NSButton. But here is the replacement class in Swift 4:
import Cocoa
class TextButton: NSButton {
#IBInspectable open var textColor: NSColor = NSColor.black
#IBInspectable open var textSize: CGFloat = 10
public override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func awakeFromNib() {
let titleParagraphStyle = NSMutableParagraphStyle()
titleParagraphStyle.alignment = alignment
let attributes: [NSAttributedStringKey : Any] = [.foregroundColor: textColor, .font: NSFont.systemFont(ofSize: textSize), .paragraphStyle: titleParagraphStyle]
self.attributedTitle = NSMutableAttributedString(string: self.title, attributes: attributes)
}
}
Try this solution,i hope so you will get :)
NSFont *txtFont = button.font;
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
[style setAlignment:button.alignment];
NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[NSColor whiteColor], NSForegroundColorAttributeName, style, NSParagraphStyleAttributeName, txtFont, NSFontAttributeName, nil];
NSAttributedString *attrString = [[NSAttributedString alloc]
initWithString:button.title attributes:attrsDictionary];
[button setAttributedTitle:attrString];
You can also add this extension to your code if you like the 'Throw an extension in and look if it sticks' approach.
extension NSButton {
#IBInspectable open var textColor: NSColor? {
get {
return self.attributedTitle.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? NSColor
}
set {
var attributes = self.attributedTitle.attributes(at: 0, effectiveRange: nil)
attributes[.foregroundColor] = newValue ?? NSColor.black
self.attributedTitle = NSMutableAttributedString(string: self.title,
attributes: attributes)
}
}
}
Edit: Misread question. Below is how you'd change the text of a button on an iOS app.
Just to clarify, this isn't working for you?
added button
click on it and go to Attributes Inspector
change color in "Text Color" field

NSPopUpButton arrow color

Is there a way to customize the color of a NSPopUpButton arrow? I've looked around but I've not found an answer yet
I really dont think there is an "easy" way to do this. If you look at the API description, it even states that it doesnt respond to the setImage routine. I have done quite a bit of work sub-classing button objects, etc... and I think this is where you would have to go in order to do what you are asking.
Like too many of these controls, I did it by subclassing NSPopupButton(Cell) and then doing all my own drawing in drawRect...I cheated a little though, and used an image do the actual triangle rather than trying to do it via primitives.
- (void)drawRect:(NSRect)dirtyRect
{
//...Insert button draw code here...
//Admittedly the above statement includes more work than we probably want to do.
//Assumes triangleIcon is a cached NSImage...I also make assumptions about location
CGFloat iconSize = 6.0;
CGFloat iconYLoc = (dirtyRect.size.height - iconSize) / 2.0;
CGFloat iconXLoc = (dirtyRect.size.width - (iconSize + 8));
CGRect triRect = {iconXLoc, iconYLoc, iconSize, iconSize};
[triangleIcon drawInRect:triRect];
}
i did this and its worked for me.
(void)drawImageWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
NSPopUpButton *temp = (NSPopUpButton*)controlView;
NSString *strtile = temp.title;
AppDelegate *appdel = (AppDelegate*)[NSApplication sharedApplication].delegate;
NSFont *font = [NSFont systemFontOfSize:13.5];
NSSize size = NSMakeSize(40, 10);// string size
CGRect rect = controlView.frame;
rect = CGRectMake((size.width + temp.frame.size.width)/2, rect.origin.y, 8, 17);
[self drawImage:[NSImage imageNamed:#"icon_downArrow_white.png"] withFrame:rect inView:self.
}
I have changed arrow color by using "False Color" filter without using any image. So far it is the easiest way to change cocoa control to me.
class RLPopUpButton: NSPopUpButton {
init() {
super.init(frame: NSZeroRect, pullsDown: false)
addFilter()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addFilter()
}
func addFilter() {
let colorFilter = CIFilter(name: "CIFalseColor")!
colorFilter.setDefaults()
colorFilter.setValue(CIColor(cgColor: NSColor.black.cgColor), forKey: "inputColor0")
colorFilter.setValue(CIColor(cgColor: NSColor.white.cgColor), forKey: "inputColor1")
// colorFilter.setValue(CIColor(cgColor: NSColor.yellow.cgColor), forKey: "inputColor0")
// colorFilter.setValue(CIColor(cgColor: NSColor.property.cgColor), forKey: "inputColor1")
self.contentFilters = [colorFilter]
}
}
Swift 5
In interface builder, remove default arrow setting.
Then, apply this subclass for cell, which will add an NSImageView to the right side of the NSPopUpButton.
This way you have complete control over what you set as your custom button and how you position it.
import Cocoa
#IBDesignable class NSPopUpButtonCellBase: NSPopUpButtonCell {
let textColor = NSColor(named: "white")!
let leftPadding: CGFloat = 16
let rightPadding: CGFloat = 30
override func awakeFromNib() {
super.awakeFromNib()
let imageView = NSImageView()
imageView.image = NSImage(named: "ic_chevron_down")!
controlView!.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.widthAnchor.constraint(equalToConstant: CGFloat(20)).isActive = true
imageView.heightAnchor.constraint(equalToConstant: CGFloat(20)).isActive = true
imageView.trailingAnchor.constraint(equalTo: controlView!.trailingAnchor).isActive = true
imageView.centerYAnchor.constraint(equalTo: controlView!.centerYAnchor).isActive = true
}
// overriding this removes the white container
override func drawBezel(withFrame frame: NSRect, in controlView: NSView) {
}
// overriding this allows us to modify paddings to text
override func titleRect(forBounds cellFrame: NSRect) -> NSRect {
// this gets rect, which has title's height, not the whole control's height
// also, it's origin.y is such that it centers title
let processedTitleFrame = super.titleRect(forBounds: cellFrame)
let paddedFrame = NSRect(
x: cellFrame.origin.x + leftPadding,
y: processedTitleFrame.origin.y,
width: cellFrame.size.width - leftPadding - rightPadding,
height: processedTitleFrame.size.height
)
return paddedFrame
}
// overriding this allows us to style text
override func drawTitle(_ title: NSAttributedString, withFrame frame: NSRect, in controlView: NSView) -> NSRect {
let attributedTitle = NSMutableAttributedString.init(attributedString: title)
let range = NSMakeRange(0, attributedTitle.length)
attributedTitle.addAttributes([NSAttributedString.Key.foregroundColor : textColor], range: range)
return super.drawTitle(attributedTitle, withFrame: frame, in: controlView)
}
}

Resources