UITextView text content doesn't start from the top - objective-c

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;

Related

Presenting modal in iOS 13 with custom height

In iOS 13 there is a new behaviour for modal view controller when being presented.And I found the build-in App Photo presents a smaller model view controller.
How can I present a viewController with a custom size like this,and can slide up to a larger height?
Picture screenshots from system photo app.
Yes it is possible Presenting modal in iOS 13 with custom height.
You just need to add the below code into your Presenting modal
override func updateViewConstraints() {
self.view.frame.size.height = UIScreen.main.bounds.height - 150
self.view.frame.origin.y = 150
self.view.roundCorners(corners: [.topLeft, .topRight], radius: 10.0)
super.updateViewConstraints()
}
extension UIView {
func roundCorners(corners: UIRectCorner, radius: CGFloat) {
let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.cgPath
layer.mask = mask
}
}
Answer in Swift
I was looking for a way to replicate that type of ViewController behaviour, albeit with basic UI and have found a rather simple solution.
Basically, you create a ViewController (CardViewContoller) with a transparent background and then add to it a card-like view with a UIPanGestureReconizer, that will enable you to drag it around and dismiss it with the ViewController.
To present you simply call present, setting the modalPresentationStyle to .overCurrentContext and modalTransitionStyle to .coverVertical:
let cardVC = CardViewController()
cardVC.modalPresentationStyle = .overCurrentContext
cardVC.modalTransitionStyle = .coverVertical
present(cardVC, animated: true, completion: nil)
The in CardViewController, which can be created programmatically or using Interface Builder, you add a UIPanGestureRecognizer to your card view (contentView):
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handleDismiss(recognizer:)))
panGestureRecognizer.cancelsTouchesInView = false
contentView.addGestureRecognizer(panGestureRecognizer)
Then just add an #objc function that will respond to the UIPanGestureRecognizer:
#objc
func handleDismiss (recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .changed:
viewTranslation = recognizer.translation(in: view)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
guard self.viewTranslation.y > 0 else {return}
self.view.transform = CGAffineTransform(translationX: 0, y: self.viewTranslation.y)
})
case .ended:
if viewTranslation.y < swipeThreshold {
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.transform = .identity
})
} else {
dismiss(animated: true, completion: nil)
}
default:
break
}
}
The swipeThreshold is a CGFloat variable with a value of your choosing (200 works great for me), that if the UIPanGestureRecognizer y translation exceeds, will trigger the dismissal of the ViewController along with all the elements.
Likewise, you can add a simple button that will dismiss the ViewController on .touchUpInside calling dismiss()
If you want, you can have a look at this repo, in which I have a sample project that exhibits this behaviour. That way you can build your own totally customisable cards.

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)
}

Make UICollectionView scroll right to left?

The natural direction for a UICollectionView to scroll when set horizontally is from left to right. Is there any way to reverse this? The simpler the better.
I'm not sure exactly what you mean -- if you set the scrolling to horizontal, it scrolls equally well, left and right. If you want it to start it from the right side, you can use this method:
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForItem:self.theData.count - 1 inSection:0] atScrollPosition:UICollectionViewScrollPositionRight animated:NO];
This assumes that you have 1 section, and the array populating the collection view is called theData.
Swift4 solution
in cellForItemAt collectionView function
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = categoryBook.dequeueReusableCell(withReuseIdentifier: "HomeCategoryCell", for: indexPath) as! HomeCategoryCell
collectionView.transform = CGAffineTransform(scaleX:-1,y: 1);
cell.transform = CGAffineTransform(scaleX:-1,y: 1);
}
but this solution in some cases did not work properly if it dose not you can use ColletctionView scrollToItem method and you can implement it after you reload the data .
self.YourCollectionView.reloadData()
self.YourCollectionView.scrollToItem(at: NSIndexPath(item: self.YourObjectListData.count - 1, section: 0) as IndexPath, at: .right, animated: false)
Same thing for swift:
collectionView?.scrollToItemAtIndexPath(NSIndexPath(forItem: theData.count - 1, inSection: 0), atScrollPosition: .Right, animated: false)
Use This Extention
extension UICollectionViewFlowLayout {
open override var flipsHorizontallyInOppositeLayoutDirection: Bool {
return true //RETURN true if collection view needs to enable RTL
}
}
I have found using xCode 12.4 with an app that targets iOS 12 that this there seems to be no need to load the items in a different order or do any transforms. The only issue has to do with the initial scroll position. So all I need to do to get things working in both RTL and LTR is the following:
collectionView.reloadData {
if self.collectionView.effectiveUserInterfaceLayoutDirection == .rightToLeft {
self.collectionView?.scrollToItem(at: IndexPath(row:0, section:0), at: .right, animated: false)
}
}

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.

Hide NSWindow title bar

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