Hide NSWindow title bar - objective-c

Is there a way to hide the titlebar in an NSWindow? I don't want to have to completely write a new custom window. I can't use NSBorderlessWindowMask because I have a bottom bar on my window, and using NSBorderlessWindowMask makes that disappear. I also tried using setContentBorderThickness:forEdge: with NSMaxYEdge and setting it to 0, that didn't work either.
Any help is appreciated

[yourWindow setStyleMask:NSBorderlessWindowMask];

Starting from OS X 10.10, you can hide title bar.
window1.titlebarAppearsTransparent = true
window1.titleVisibility = .Hidden
Maybe you want to override window style.
window1.styleMask = NSResizableWindowMask
| NSTitledWindowMask
| NSFullSizeContentViewWindowMask

Kind of Welcome screen NSWindow / NSViewController setup (Swift 4.1)
extension NSWindow {
enum Style {
case welcome
}
convenience init(contentRect: CGRect, style: Style) {
switch style {
case .welcome:
let styleMask: NSWindow.StyleMask = [.closable, .titled, .fullSizeContentView]
self.init(contentRect: contentRect, styleMask: styleMask, backing: .buffered, defer: true)
titlebarAppearsTransparent = true
titleVisibility = .hidden
standardWindowButton(.zoomButton)?.isHidden = true
standardWindowButton(.miniaturizeButton)?.isHidden = true
}
}
}
class WelcomeWindowController: NSWindowController {
private (set) lazy var viewController = WelcomeViewController()
private let contentWindow: NSWindow
init() {
contentWindow = NSWindow(contentRect: CGRect(x: 400, y: 200, width: 800, height: 472), style: .welcome)
super.init(window: contentWindow)
let frameSize = contentWindow.contentRect(forFrameRect: contentWindow.frame).size
viewController.view.setFrameSize(frameSize)
contentWindow.contentViewController = viewController
}
}
class WelcomeViewController: NSViewController {
private lazy var contentView = View()
override func loadView() {
view = contentView
}
init() {
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
contentView.backgroundColor = .white
}
}
class View: NSView {
var backgroundColor: NSColor?
convenience init() {
self.init(frame: NSRect())
}
override func draw(_ dirtyRect: NSRect) {
if let backgroundColor = backgroundColor {
backgroundColor.setFill()
dirtyRect.fill()
} else {
super.draw(dirtyRect)
}
}
}
Result

What happens if you get the superview of the close button? Can you hide that?
// Imagine that 'self' is the NSWindow derived class
NSButton *miniaturizeButton = [self standardWindowButton:NSWindowMiniaturizeButton];
NSView* titleBarView = [miniaturizeButton superview];
[titleBarView setHidden:YES];

The only way I know would be to create a window without a titlebar (see
NSBorderlessWindowMask). Note that you can't (easily) create a window without a
titlebar in IB, so you will have to do a bit of work in code (there are a
couple of different approaches, you can probably figure it out).
A big drawback with using a window without a titlebar is that you're now on the
hook for much more of the standard appearance and behaviour - rounded corners
and such.

I had an experience that when I first set content view of my window and then set the window borderless:
[yourWindow setStyleMask:NSBorderlessWindowMask];
Nothing would appear in my window. So i first set the style mask and after that i've set the content view:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// 1. borderless window
[[self window] setStyleMask: NSBorderlessWindowMask];
// 2. create the master View Controller
self.masterViewController = [[MasterViewController alloc] initWithNibName:#"MasterViewController" bundle:nil];
// 3. Add the view controller to the Window's content view
[self.window.contentView addSubview:self.masterViewController.view];
self.masterViewController.view.frame = ((NSView*)self.window.contentView).bounds;
}
And voila, the content of my window has appeared.

Select Window in storyboard or XIB and tick the red circled option.

You can use WAYInAppStoreWindow available on GitHub which works on Yosemite and Mavericks.

Swift
NSApp.mainWindow?.styleMask = .borderless

Related

UITextView text content doesn't start from the top

I have a long text coming from my JSON file but when I click the link from my UITableViewCell to go to my UIViewController page, the UITextView text loads the string content but it does not show the content from the beginning and I have to scroll up all the time.
What I need to do?
I had the same problem, and turns out I had to set the content offset in viewDidLayoutSubviews for it to take effect. I'm using this code to display attributed static text.
- (void)viewDidLayoutSubviews {
[self.yourTextView setContentOffset:CGPointZero animated:NO];
}
SWIFT 3:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.textView.setContentOffset(CGPoint.zero, animated: false)
}
This is the only way that worked for me. I disable the scroll of the UITextView before the view is loaded and then i enable it again:
override func viewWillAppear(_ animated: Bool) {
yourTextView.isScrollEnabled = false
}
override func viewDidAppear(_ animated: Bool) {
yourTextView.isScrollEnabled = true
}
[self.textView scrollRangeToVisible:NSMakeRange(0, 1)];
in viewDidLoad
By Programmatically before loading the content disable the scrolling property of textview
textview.scrollenabled = NO;
And after loading enable the scrolling of textview textview.scrollenabled = YES;
As well check the XIB, always non-check the scrolling enabled of Textview.
The answers for the question Blank space at top of UITextView in iOS 10 provide a much cleaner end user experience.
In viewDidLoad of the view controller containing the text view:
self.automaticallyAdjustsScrollViewInsets = false
Setting textView.setContentOffset(CGPointMake(0,0), animated: false) and some of these other suggestions do work when called in the viewDidLayoutSubviews() but on older devices like iPad 2 and older you will actually see the text get scrolled when the screen is displayed. That is not something you want the end user to see.
I was still having problems after using these solutions. The problem definitely seems to relate to having transparent navigation bars and selecting to automatically adjust content insets on the view controller. If you don't care about your text scrolling underneath the navigation bar then it's best to leave these settings off and constrain the top of your textview to the bottom of the navigation bar, rather than to the top of the viewcontroller.
If like me you wanted it to appear underneath your navigation bar when you scroll down; then the solution that worked for me was to add this.
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
CGFloat offset = self.navigationController.navigationBar.frame.size.height+[UIApplication sharedApplication].statusBarFrame.size.height;
[self.textView setContentOffset:CGPointMake(0, -offset) animated:NO];
}
This just looks for the height of the navigation bar and status bar and adjusts the content offset accordingly.
Note that one downside of this approach is that when the device rotates you'll end up scrolling back to the top.
For me fine works this code:
textView.attributedText = newText //or textView.text = ...
//this part of code scrolls to top
textView.contentOffset.y = -64 //or = 0 if no Navigation Bar
textView.scrollEnabled = false
textView.layoutIfNeeded()
textView.scrollEnabled = true
For scroll to exact position and show it on top of screen I use this code:
var scrollToLocation = 50 //<needed position>
textView.contentOffset.y = textView.contentSize.height
textView.scrollRangeToVisible(NSRange.init(location: scrollToLocation, length: 1))
Setting contentOffset.y scrolls to the end of text, and then scrollRangeToVisible scrolls up to value of scrollToLocation. Thereby, needed position appears in first line of scrollView.
Similar to some other answers, but with the added benefit that you won't cause a scroll to top on subsequent device rotations. Works well in Swift 2.2
/// Whether view has laid out subviews at least once before.
var viewDidLayoutSubviewsAtLeastOnce = false
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if !viewDidLayoutSubviewsAtLeastOnce {
textView.setContentOffset(CGPoint(x: 0, y: -textView.contentInset.top), animated: false)
}
viewDidLayoutSubviewsAtLeastOnce = true
}
Swift Version
A combination of things will be needed:
1.) Set your outlet
#IBOutlet var textView: UITextView!
2.) In storyboard on View Controller turn off "Adjust Scroll View Insets"
3.) Set content to zero top by adding below to your view controller
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
myUITextView.setContentOffset(CGPointZero, animated: false)
}
Instead of setting content offset from viewDidLayoutSubviews you can write layoutIfNeeded from viewDidLoad to set the proper position of textview as below:
self.textView.layoutIfNeeded()
self.textView.setContentOffset(CGPoint.zero, animated: false)
Cheers !!
In Swift 2, Xcode 7 solution, to leave scroll Enabled as well as have the text start at the top, this is all you need:
#IBOutlet weak var myUITextView: UITextView!
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
//start scroll at top of text
myUITextView.scrollRangeToVisible(NSMakeRange(0, 0))
}
Swift 3.0
override func viewWillAppear(_ animated: Bool) {
privacyText.isScrollEnabled = false
}
override func viewDidAppear(_ animated: Bool) {
privacyText.isScrollEnabled = true
}
This worked the best for me! I placed this within viewDidLoad().
//TextView Scroll starts from the top
myTextView.contentOffset.y = 0
Here's another way to do it that always works for me. Objective-C:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.textView setContentOffset:CGPointZero animated:NO];
}
And in Swift:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
textView.setContentOffset(CGPointZero, animated: false)
}
Swift version:
override func viewDidLayoutSubviews() {
yourTextView.setContentOffset(CGPointZero, animated: false)
}
add the following function to your view controller class...
Swift 3
override func viewDidLayoutSubviews() {
self.mainTextView.setContentOffset(.zero, animated: false)
}
Swift 2.1
override func viewDidLayoutSubviews() {
self.mainTextView.setContentOffset(CGPointZero, animated: false)
}
Objective C
- (void)viewDidLayoutSubviews {
[self.mainTextView setContentOffset:CGPointZero animated:NO];
}
或者 你在ViewDidAppear 里面加上滚动,这样用户会看到他往上滚动到第一行
in swift 4 with attributed text any of answer does not help me and i combine some answers in topic.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
uiTextview.isScrollEnabled = false
}
override func viewDidAppear(_ animated: Bool) {
uiTextview.isScrollEnabled = true
uiTextview.setContentOffset(CGPoint.zero, animated: false)
}
Swift 3, 4, 5 solution:
Steps to solve the issue:
Disable the UITextView scroll
set scrollRectToVisible
enable UITextView scroll
Code:
yourTextView.isScrollEnabled = false
let rect:CGRect = CGRect(x: 0, y: 0, width: 1, height: 1)
yourTextView.scrollRectToVisible(rect, animated: false)
yourTextView.isScrollEnabled = true
This Worked for me. Hope that will help!
This is how i did it. I subclassed textview, and:
override func willMoveToSuperview(newSuperview: UIView?) {
self.scrollEnabled = false
}
override func layoutSubviews() {
super.layoutSubviews()
self.scrollEnabled = true
}
From storyboard, select the view controller on which you have you text view placed. In the attributes inspector, uncheck "Adjust Scroll View Insets". That's it.
Put this code on your class
override func viewDidLayoutSubviews() {
self.About_TV.setContentOffset(.zero, animated: false) // About_TV : your text view name)
}
Add code to the viewdidload
self.automaticallyAdjustsScrollViewInsets = NO;

Animate a UINavigationBar's barTintColor

The app I'm working on changes the barTintColor of its navigation bar when pushing new view controllers. Right now we set that colour in the destination view controller's viewWillAppear:method, but we have a few issues with that.
With the way we're doing this right now, the navigation bar's colour changes abruptly, while the rest of the bar content animates as usual. What I'd like is for the bar to fade between the source and destination colour. Is there any way to achieve this with public Cocoa Touch APIs?
You can add extra animations that match the timing and animation curve of the view controller transition using UIViewControllerTransitionCoordinator.
A view controller's transitionCoordinator will be set after a view controller's animation has started (so in viewWillAppear of the presented view controller). Add any extra animations using animateAlongsideTransition:completion: on the transition coordinator.
An example:
[[self transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.navigationController.navigationBar.translucent = NO;
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
self.navigationController.navigationBar.tintColor = [UIColor whiteColor];
self.navigationController.navigationBar.barTintColor = [UIColor redColor];
} completion:nil];
Here is a simpler fix. The issue with barTintColor not animating correctly on pop occurs when you try to set the navigation bar appearance in viewWillDisappear. The fix is to set it in willMove(toParentViewController:) instead.
The code below will produce a smooth fading transition during both push and pop, and regardless of whether it is initiated by a gesture or button tap. Tested on iOS 10 and 11.
This also works for animating barStyle.
import UIKit
class RedViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
self.title = "Red"
self.navigationController?.navigationBar.barTintColor = .red
self.navigationController?.navigationBar.tintColor = .white
}
override func willMove(toParentViewController parent: UIViewController?) {
self.navigationController?.navigationBar.barTintColor = .white
self.navigationController?.navigationBar.tintColor = nil
}
}
To get a smooth animation during both push and pop, I had to make the navigation bar transparent and animate my own background color view behind it.
Here's my UINavigationController subclass that handles it:
import Foundation
import UIKit
class ColorTransitionNavigationController: UINavigationController {
var navigationBarBackgroundView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Put a background view behind the navigation bar
navigationBarBackgroundView = UIView()
view.insertSubview(navigationBarBackgroundView, belowSubview: navigationBar)
// Make the navigation bar transparent
navigationBar.isTranslucent = true
navigationBar.setBackgroundImage(UIImage(), for: .default)
// Size the colored background to match the navigation bar
navigationBarBackgroundView.translatesAutoresizingMaskIntoConstraints = false
navigationBarBackgroundView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
navigationBarBackgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
navigationBarBackgroundView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
// I used a hard-coded 64 instead of constraining to the height of the navigation bar because
// when calling navigationController.setNavigationBarHidden(true), the height of the navigation bar becomes 0
navigationBarBackgroundView.heightAnchor.constraint(equalToConstant: 64.0).isActive = true
}
func setBarTintColor(color: UIColor, animated: Bool, transitionCoordinator: UIViewControllerTransitionCoordinator?) {
guard let transitionCoordinator = transitionCoordinator, animated else {
navigationBarBackgroundView.backgroundColor = color
return
}
transitionCoordinator.animateAlongsideTransition(in: view, animation: { [weak self] (context) in
let transition = CATransition()
transition.duration = context.transitionDuration
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
self?.navigationBarBackgroundView.layer.add(transition, forKey: nil)
self?.navigationBarBackgroundView.backgroundColor = color
}, completion:nil)
}
}
Usage:
If you want a UIViewController to animate the navigation bar color when it appears, override viewWillAppear and call setBarTintColor.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let navigationController = navigationController as? ColorTransitionNavigationController else { return }
navigationController.setBarTintColor(color: UIColor.green, animated: animated, transitionCoordinator: transitionCoordinator)
}

Cursor not displaying in UISearchBar for our app

For the UISearchBars in our app, there's no cursor shown in the bar with focus when running under iOS 7. How do we make that show?
We are using the SDK 7, with a minimum target of 6. We do have the translucency off for the navigation bars, and set the color at runtime. I can't think of anything else we are doing differently.
Our problem was that the tint color was set to white, so I didn't see it.
Set
searchBar.tintColor = [UIColor blueColor];
In the searchbox property window
open View Section>Set Tint color - default.
Hope this will help.
This is how it can be done in Swift :
override func viewWillAppear(animated: Bool) {
self.searchBar.tintColor = UIColor.whiteColor()
let view: UIView = self.searchBar.subviews[0] as! UIView
let subViewsArray = view.subviews
for (subView: UIView) in subViewsArray as! [UIView] {
println(subView)
if subView.isKindOfClass(UITextField){
subView.tintColor = UIColor.blueColor()
}
}
}
searchBar.tintColor = view.tintColor // self.view usually has the proper tintColor
Better than .blue or whatever.
Just set the tintColor for UISearchBar, in your storyboard, xib or code. Xcode seems to ignore the default tintColor.
You could loop through the searchBars subviews and obtain the uitextfield subview and set its #"insertionPointColor" value to your desired color. Works but is private api
for (UIView *subView in self.searchBar.subviews) {
if ([subView isKindOfClass:[UITextField class]]) {
[[(UITextField *) subView valueForKey:#"textInputTraits"] setValue:[UIColor blackColor] forKey:#"insertionPointColor"];
}
}

Customize right click highlight on view-based NSTableView

I have a view-based NSTableView with a custom NSTableCellView and a custom NSTableRowView. I customized both of those classes because I want to change the appearance of each row. By implementing the [NSTableRowView draw...] methods I can change the background, the selection, the separator and the drag destination highlight.
My question is: how can I change the highlight that appears when the row is right clicked and a menu appears?
For example, this is the norm:
And I want to change the square highlight to a round one, like this:
I'd imagine this would be done in NSTableRowView by calling a method like drawMenuHighlightInRect: or something, but I can't find it. Also, how can the NSTableRowView class be doing this if I customized, in my subclass, all of the drawing methods, and I don't call the superclass? Is this drawn by the table itself?
EDIT:
After some more experimenting I found out that the round highlight can be achieved by setting the tableview as a source list. Nonetheless, I want to know how to customize it if possible.
I know I'm a bit late to offer any help to the OP, but hopefully this can spare some other folks a little bit of time. I subclassed NSTableRowView to achieve the right-click contextual menu highlight (why Apple doesn't have a public drawing method to override this is beyond me). Here it is in all its glory:
BSDSourceListRowView.h
#import <Cocoa/Cocoa.h>
#interface BSDSourceListRowView : NSTableRowView
// This needs to be set when a context menu is shown.
#property (nonatomic, assign, getter = isShowingMenu) BOOL showingMenu;
#end
BSDSourceListRowView.m
#import "BSDSourceListRowView.h"
#implementation BSDSourceListRowView
- (void)drawBackgroundInRect:(NSRect)dirtyRect
{
[super drawBackgroundInRect:dirtyRect];
// Context menu highlight:
if ( self.isShowingMenu ) {
[self drawContextMenuHighlight];
}
}
- (void)drawContextMenuHighlight
{
BOOL selected = self.isSelected;
CGFloat insetY = ( selected ) ? 2.f : 1.f;
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:NSInsetRect(self.bounds, 2.f, insetY) xRadius:6.f yRadius:6.f];
NSColor *fillColor, *strokeColor;
if ( selected ) {
fillColor = [NSColor clearColor];
strokeColor = [NSColor whiteColor];
} else {
fillColor = [NSColor colorWithCalibratedRed:95.f/255.f green:159.f/255.f blue:1.f alpha:0.12f];
strokeColor = [NSColor alternateSelectedControlColor];
}
[fillColor setFill];
[strokeColor setStroke];
[path setLineWidth:2.f];
[path fill];
[path stroke];
}
- (void)drawSelectionInRect:(NSRect)dirtyRect
{
[super drawSelectionInRect:dirtyRect];
if ( self.isShowingMenu ) {
[self drawContextMenuHighlight];
}
}
- (void)setShowingMenu:(BOOL)showingMenu
{
if ( showingMenu == _showingMenu )
return;
_showingMenu = showingMenu;
[self setNeedsDisplay:YES];
}
#end
Feel free to use any of it, change any of it, or do whatever you want with any of it. Have fun!
Updated for Swift 3.x:
SourceListRowView.swift
import Cocoa
open class SourceListRowView : NSTableRowView {
open var isShowingMenu: Bool = false {
didSet {
if isShowingMenu != oldValue {
needsDisplay = true
}
}
}
override open func drawBackground(in dirtyRect: NSRect) {
super.drawBackground(in: dirtyRect)
if isShowingMenu {
drawContextMenuHighlight()
}
}
override open func drawSelection(in dirtyRect: NSRect) {
super.drawSelection(in: dirtyRect)
if isShowingMenu {
drawContextMenuHighlight()
}
}
private func drawContextMenuHighlight() {
let insetY: CGFloat = isSelected ? 2 : 1
let path = NSBezierPath(roundedRect: bounds.insetBy(dx: 2, dy: insetY), xRadius: 6, yRadius: 6)
let fillColor, strokeColor: NSColor
if isSelected {
fillColor = .clear
strokeColor = .white
} else {
fillColor = NSColor(calibratedRed: 95/255, green: 159/255, blue: 1, alpha: 0.12)
strokeColor = .alternateSelectedControlColor
}
fillColor.setFill()
strokeColor.setStroke()
path.lineWidth = 2
path.fill()
path.stroke()
}
}
Note: I haven't actually run this, but I'm pretty sure this should do the trick in Swift.
Stop Default Drawing
Several answers describe how to draw a custom contextual-click highlight. However, AppKit will continue to draw the default one. There is an easy trick to stop that and I didn't want it to get lost in a comment: subclass NSTableView and override -menuForEvent:
// NSTableView subclass
override func menu(for event: NSEvent) -> NSMenu?
{
// DO NOT call super's implementation.
return self.menu
}
Here, I assume that you've assigned a menu to the tableView in IB or have set the tableView's menu property programatically. NSTableView's default implementation of -menuForEvent: is what draws the contextual menu highlight.
Solve Bad Apple Engineering
Now that we're not calling super's implementation of menuForEvent:, the clickedRow property of our tableView will always be -1 when we right-click, which means our menuItems won't target the correct row of our tableView.
But fear not, we can do Apple Engineering's job for them. On our custom NSTableView subclass, we override the clickedRow property:
class MyTableView: NSTableView
{
private var _clickedRow: Int = -1
override var clickedRow: Int {
get { return _clickedRow }
set { _clickedRow = newValue }
}
}
Now we update the -menuForEvent: method:
// NSTableView subclass
override func menu(for event: NSEvent) -> NSMenu?
{
let location: CGPoint = convert(event.locationInWindow, from: nil)
clickedRow = row(at: location)
return self.menu
}
Great. We solved that problem. Onwards to the next thing:
Tell Your RowView To Do Custom Drawing
As others have suggested, add a custom Bool property to your NSTableRowView subclass. Then, in your drawing code, inspect that value to decide whether to draw your custom contextual highlight. However, the correct place to set that value is in the same NSTableView method:
// NSTableView subclass
override func menu(for event: NSEvent) -> NSMenu?
{
let location: CGPoint = convert(event.locationInWindow, from: nil)
clickedRow = row(at: location)
if clickedRow > 0,
let rowView: MyCustomRowView = rowView(atRow: tableRow, makeIfNecessary: false) as? MyCustomRowView
{
rowView.isContextualMenuTarget = true
}
return self.menu
}
Above, I've created MyCustomRowView (a subclass of NSTableRowView) and have added a custom property: isContextualMenuTarget. That custom property looks like this:
// NSTableRowView subclass
var isContextualMenuTarget: Bool = false {
didSet {
needsDisplay = true
}
}
In my drawing method, I inspect the value of that property and, if it's true, draw my custom highlight.
Clean Up When The Menu Closes
You have a controller that implements the datasource and delegate methods for your tableView. That controller is also likely the delegate for the tableView's menu. (You can assign that in IB or programatically.)
Whatever object is your menu's delegate, implement the menuDidClose: method. Here, I'm working in Objective-C because my controller is still ObjC:
// NSMenuDelegate object
- (void) menuDidClose:(NSMenu *)menu
{
// We use a custom flag on our rowViews to draw our own contextual menu highlight, so we need to reset that.
[_outlineView enumerateAvailableRowViewsUsingBlock:^(__kindof MyCustomRowView * _Nonnull rowView, NSInteger row) {
rowView.isContextualMenuTarget = NO;
}];
}
Performance Note: My tableView will never have more than about 50 entries. If you have a table with THOUSANDS of visible rows, you would be better served to save the rowView that you set isContextualMenuTarget=true on, then access that rowView directly in -menuDidClose: so you don't have to enumerate all rowViews.
Single-Column: This example assumes a single column tableView that has the same NSMenu for each row. You could adapt the same technique for multi-column and/or varying NSMenus per row.
And that's how you beat AppKit in the face until it does what you want.
This is already a bit old, but I've wasted on it quite a bit of time, so posting my solution in case it could help anyone:
In my case, I wanted to remove the lines completely
Lines are not "Focus" rings, they are some stuff Apple is doing in undocument API
The ONLY way I found to remove them (Without using Undocumented API) is by opening NSMenu programmatically, without Interface Builder.
For that, I had to cache "right-click" event on TableViewRow, which has some issue since not always called, so I've dealt with that issue too.
A. Subclass NSTableView:
Overriding right click event, calculating the location of click to get a correct row, and transferring it to my custom NSTableRowView!
class TableView: NSTableView {
override func rightMouseDown(with event: NSEvent) {
let location = event.locationInWindow
let toMyOrigin = self.superview?.convert(location, from: nil)
let rowIndex = self.row(at: toMyOrigin!)
if (rowIndex < 0 || self.numberOfRows < rowIndex) {
return
}
if let isRowExists = self.rowView(atRow: rowIndex, makeIfNecessary: false) {
if let isMyTypeRow = isRowExists as? MyNSTableRowView {
isMyTypeRow.costumRightMouseDown(with: event)
}
}
}
}
B. Subclass MyNSTableRowView
Presenting NSMenu programmatically
class MyNSTableRowView: NSTableRowView {
//My custom selection colors, don't have to implement this if you are ok with the default system highlighted background color
override func drawSelection(in dirtyRect: NSRect) {
if self.selectionHighlightStyle != .none {
let selectionRect = NSInsetRect(self.bounds, 0, 0)
Colors.tabSelectedBackground.setStroke()
Colors.tabSelectedBackground.setFill()
let selectionPath = NSBezierPath.init(roundedRect: selectionRect, xRadius: 0, yRadius: 0)
selectionPath.fill()
selectionPath.stroke()
}
}
func costumRightMouseDown(with event: NSEvent) {
let menu = NSMenu.init(title: "Actions:")
menu.addItem(NSMenuItem.init(title: "Some", action: #selector(foo), keyEquivalent: "a"))
NSMenu.popUpContextMenu(menu, with: event, for: self)
}
#objc func foo() {
}
}
I agree with MCMatan that this is not something you can tweak by changing any draw calls. The box will remain.
I took his approach of bypassing the default menu launch, but left the context menu setup as default in my NSTableView. I think this is a simpler way.
I derive from NSTableView and add the following:
public private(set) var rightClickedRow: Int = -1
override func rightMouseDown(with event: NSEvent)
{
guard let menu = self.menu else { return }
let windowClickLocation = event.locationInWindow
let outlineClickLocation = convert(windowClickLocation, from: nil)
rightClickedRow = row(at: outlineClickLocation)
menu.popUp(positioning: nil, at: outlineClickLocation, in: self)
}
override func rightMouseUp(with event: NSEvent) {
rightClickedRow = -1
}
My rightClickedRow is analogous to clickedRow for the table view. I have an NSViewController that looks after my table, and it is set as the table's menu delegate. I can use rightClickedRow in the delegate calls, such as menuNeedsUpdate().
I'd take a look at the NSTableRowView documentation. It's the class that is responsible for drawing selection and drag feedback in a view-based NSTableView.

Best way to change the background color for an NSView

I'm looking for the best way to change the backgroundColor of an NSView. I'd also like to be able to set the appropriate alpha mask for the NSView. Something like:
myView.backgroundColor = [NSColor colorWithCalibratedRed:0.227f
green:0.251f
blue:0.337
alpha:0.8];
I notice that NSWindow has this method, and I'm not a big fan of the NSColorWheel, or NSImage background options, but if they are the best, willing to use.
Yeah, your own answer was right. You could also use Cocoa methods:
- (void)drawRect:(NSRect)dirtyRect {
// set any NSColor for filling, say white:
[[NSColor whiteColor] setFill];
NSRectFill(dirtyRect);
[super drawRect:dirtyRect];
}
In Swift:
class MyView: NSView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
// #1d161d
NSColor(red: 0x1d/255, green: 0x16/255, blue: 0x1d/255, alpha: 1).setFill()
dirtyRect.fill()
}
}
An easy, efficient solution is to configure the view to use a Core Animation layer as its backing store. Then you can use -[CALayer setBackgroundColor:] to set the background color of the layer.
- (void)awakeFromNib {
self.wantsLayer = YES; // NSView will create a CALayer automatically
}
- (BOOL)wantsUpdateLayer {
return YES; // Tells NSView to call `updateLayer` instead of `drawRect:`
}
- (void)updateLayer {
self.layer.backgroundColor = [NSColor colorWithCalibratedRed:0.227f
green:0.251f
blue:0.337
alpha:0.8].CGColor;
}
That’s it!
If you are a storyboard lover, here is a way that you don't need any line of code.
Add NSBox as a subview to NSView and adjust NSBox's frame as the same with NSView.
In Storyboard or XIB change Title position to None, Box type to Custom, Border Type to "None", and Border color to whatever you like.
Here is a screenshot:
This is the result:
If you setWantsLayer to YES first, you can directly manipulate the layer background.
[self.view setWantsLayer:YES];
[self.view.layer setBackgroundColor:[[NSColor whiteColor] CGColor]];
Think I figured out how to do it:
- (void)drawRect:(NSRect)dirtyRect {
// Fill in background Color
CGContextRef context = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];
CGContextSetRGBFillColor(context, 0.227,0.251,0.337,0.8);
CGContextFillRect(context, NSRectToCGRect(dirtyRect));
}
edit/update: Xcode 8.3.1 • Swift 3.1
extension NSView {
var backgroundColor: NSColor? {
get {
guard let color = layer?.backgroundColor else { return nil }
return NSColor(cgColor: color)
}
set {
wantsLayer = true
layer?.backgroundColor = newValue?.cgColor
}
}
}
usage:
let myView = NSView(frame: NSRect(x: 0, y: 0, width: 100, height: 100))
print(myView.backgroundColor ?? "none") // NSView's background hasn't been set yet = nil
myView.backgroundColor = .red // set NSView's background color to red color
print(myView.backgroundColor ?? "none")
view.addSubview(myView)
I went through all of these answers and none of them worked for me unfortunately. However, I found this extremely simple way, after about an hour of searching : )
myView.layer.backgroundColor = CGColorCreateGenericRGB(0, 0, 0, 0.9);
Best Solution :
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.wantsLayer = YES;
}
return self;
}
- (void)awakeFromNib
{
float r = (rand() % 255) / 255.0f;
float g = (rand() % 255) / 255.0f;
float b = (rand() % 255) / 255.0f;
if(self.layer)
{
CGColorRef color = CGColorCreateGenericRGB(r, g, b, 1.0f);
self.layer.backgroundColor = color;
CGColorRelease(color);
}
}
In Swift:
override func drawRect(dirtyRect: NSRect) {
NSColor.greenColor().setFill()
NSRectFill(dirtyRect)
super.drawRect(dirtyRect)
}
Use NSBox, which is a subclass of NSView, allowing us to easily style
Swift 3
let box = NSBox()
box.boxType = .custom
box.fillColor = NSColor.red
box.cornerRadius = 5
Without doubt the easiest way, also compatible with Color Set Assets:
Swift:
view.setValue(NSColor.white, forKey: "backgroundColor")
Objective-C:
[view setValue: NSColor.whiteColor forKey: "backgroundColor"];
Interface Builder:
Add a user defined attribute backgroundColor in the interface builder, of type NSColor.
Just set backgroundColor on the layer (after making the view layer backed).
view.wantsLayer = true
view.layer?.backgroundColor = CGColor.white
I tested the following and it worked for me (in Swift):
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.blackColor().colorWithAlphaComponent(0.5).CGColor
In Swift 3, you can create an extension to do it:
extension NSView {
func setBackgroundColor(_ color: NSColor) {
wantsLayer = true
layer?.backgroundColor = color.cgColor
}
}
// how to use
btn.setBackgroundColor(NSColor.gray)
In swift you can subclass NSView and do this
class MyView:NSView {
required init?(coder: NSCoder) {
super.init(coder: coder);
self.wantsLayer = true;
self.layer?.backgroundColor = NSColor.redColor().CGColor;
}
}
This supports changing systemwide appearance (turning dark mode on or off) while the application is running. You can also set the background colour in Interface Builder, if you set the class of the view to BackgroundColorView first.
class BackgroundColorView: NSView {
#IBInspectable var backgroundColor: NSColor? {
didSet { needsDisplay = true }
}
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
wantsLayer = true
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
wantsLayer = true
}
override var wantsUpdateLayer: Bool { return true }
override func updateLayer() {
layer?.backgroundColor = backgroundColor?.cgColor
}
}
Have a look at RMSkinnedView. You can set the NSView's background color from within Interface Builder.
Just small reusable class (Swift 4.1)
class View: NSView {
var backgroundColor: NSColor?
convenience init() {
self.init(frame: NSRect())
}
override func draw(_ dirtyRect: NSRect) {
if let backgroundColor = backgroundColor {
backgroundColor.setFill()
dirtyRect.fill()
} else {
super.draw(dirtyRect)
}
}
}
// Usage
let view = View()
view.backgroundColor = .white