NSTextField alignment when selected in NSTableView - macos

I have a programmatically created NSTableView with n rows and 1 column. The embedded text field is selectable, and not editable. I am able to observe the mouseDown event in the text field, but as you can see in the image, the selected row is repositioned and word wrapped. I suspect this may be related to the field editor, but my MacOS odyssey has proven me wrong innumerable times.
My question: What do I need to do to maintain the layout of the selected row to be consistent with the other rows in the table?
Update: adding a textfield.cell.wraps = false to the tableview delegate eliminated the word wrap, but still have the issue with the selected text field indented.
Code snippets:
From the table delegate:
func tableView (_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
{
let text = dataArray [row]
var v = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier.init(rawValue: "TableColumn"), owner: self) as? MyTextFieldExt
if v == nil
{
v = MyTextFieldExt ()
v?.identifier = NSUserInterfaceItemIdentifier(rawValue: "TableColumn")
v?.maximumNumberOfLines = 1
v?.autoresizingMask = [.width]
v?.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 251), for: .horizontal)
v?.translatesAutoresizingMaskIntoConstraints = false
v?.isEditable = false
v?.isSelectable = true
v?.cell?.wraps = false
}
v!.stringValue = text
v!.font = NSFont.monospacedSystemFont(ofSize: 10, weight: .regular)
v!.frame = CGRect(x: 0, y: 0, width: 3000, height: 0)
return v!
}
From the text field (custom) that traps the mouseDown:
override func mouseDown(with event: NSEvent)
{
super.mouseDown(with: event)
let cEditor = self.currentEditor() as? NSTextView
let localPos = convert (event.locationInWindow, to: nil)
let location = cEditor?.selectedRange().location
if let r = cEditor?.selectedRange()
{
self.select(withFrame: self.frame, editor: cEditor!, delegate: self, start: r.location, length: r.length+10)
}
}
From the view controller that creates the scrollview, table view, and column:
func setupTableView ()
{
let tableView = MyTableViewExt ()
tableView.selectionHighlightStyle = .none
tableView.headerView = nil
tableView.columnAutoresizingStyle = .lastColumnOnlyAutoresizingStyle
tableView.autoresizesSubviews = true
tableView.autoresizingMask = [.width, .height]
let column = NSTableColumn ()
column.identifier = NSUserInterfaceItemIdentifier(rawValue: "TableColumn")
column.width = 426
column.minWidth = 40
column.maxWidth = 1000
column.resizingMask = [.autoresizingMask, .userResizingMask] // verify in debugger
tableView.addTableColumn(column)
tableView.delegate = self
tableView.dataSource = self
let scrollView = NSScrollView (frame: self.view.bounds)
scrollView.autoresizesSubviews = true
scrollView.autoresizingMask = [.height, .width]
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.hasHorizontalScroller = true
scrollView.hasVerticalScroller = true
self.view.addSubview(scrollView)
self.view.addConstraint(NSLayoutConstraint (item: self.view, attribute: .trailing, relatedBy: .equal, toItem: scrollView, attribute: .trailing, multiplier: 1.0, constant: 20))
self.view.addConstraint(NSLayoutConstraint (item: scrollView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 20))
self.view.addConstraint(NSLayoutConstraint (item: self.view, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1.0, constant: 20))
self.view.addConstraint(NSLayoutConstraint (item: scrollView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 20))
scrollView.documentView = tableView
}

So my problem was resolved by modifying the code in the text field mouseDown logic. The corrected code looks like this, with a change in setting the selected range:
override func mouseDown(with event: NSEvent)
{
super.mouseDown(with: event)
let cEditor = self.currentEditor() as? NSTextView
let localPos = convert (event.locationInWindow, to: nil)
let insertionPoint = cEditor?.characterIndexForInsertion(at: localPos)
let location = cEditor?.selectedRange().location
if let r = cEditor?.selectedRange()
{
// self.select(withFrame: self.frame, editor: cEditor!, delegate: self, start: r.location, length: r.length+10) <-- Source of problem
cEditor?.setSelectedRange(NSMakeRange(r.location, r.length+10)) <-- Solution to problem
}
}
And just to be clear, the statement of not requiring the textfield.cell?.wraps = false I made in a comment is incorrect. That line is needed in my table view delegate to avoid the word wrap.

Related

NSOutlineView with usesAutomaticRowHeights causes log "WARNING: Application performed a reentrant operation in its NSTableView delegate."

What does this warning mean? In my main app, whenever this warning is logged, the outline view begins to behave weirdly after that, overlapping rows and not responding to clicks anymore. When not using automatic row heights or group rows, the warning doesn't appear anymore.
An interesting thing is that even if there are no group rows, simply implementing the data source method
func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool {
return false
}
makes the warning appear.
Here is the full code:
import Cocoa
class ViewController: NSViewController, NSOutlineViewDataSource, NSOutlineViewDelegate {
var outlineView: NSOutlineView!
let data: Any = [[[[[[[0], [0, [0,0,0]]], [0, [0, [0,0]],0,0,0,0,0]]], [[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]],1]]]]
override func loadView() {
outlineView = NSOutlineView(dataSource: self, delegate: self, columns: [
NSTableColumn(identifier: "", title: "", width: 400),
NSTableColumn(identifier: "", title: "", width: 100)
])
let scrollView = NSScrollView(documentView: outlineView)
view = NSView()
view.addSubview(scrollView)
NSLayoutConstraint.constrain(scrollView, toMarginsOf: view, edgeInsets: .zero)
outlineView.reloadData()
expandItem(data)
}
private func expandItem(_ item: Any) {
if let item = item as? [Any] {
outlineView.expandItem(item)
for content in item {
expandItem(content)
}
}
}
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool {
return item is [Any]
}
func outlineView(_ outlineView: NSOutlineView, isGroupItem item: Any) -> Bool {
return false
}
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int {
return ((item ?? data) as! [Any]).count
}
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any {
return ((item ?? data) as! [Any])[index]
}
func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
let view = NSTableCellView()
return view
}
}
extension NSOutlineView {
convenience init(dataSource: NSOutlineViewDataSource, delegate: NSOutlineViewDelegate, columns: [NSTableColumn]) {
self.init()
self.dataSource = dataSource
self.delegate = delegate
for column in columns {
addTableColumn(column)
}
usesAutomaticRowHeights = true
}
}
extension NSTableColumn {
convenience init(identifier: String, title: String, width: Double, minWidth: Double = 10, maxWidth: Double = .infinity) {
self.init(identifier: NSUserInterfaceItemIdentifier(identifier))
self.title = title
self.width = width
self.minWidth = minWidth
self.maxWidth = maxWidth
}
}
extension NSLayoutConstraint {
static func constrain(_ view: NSView, toMarginsOf superview: NSView, edgeInsets: NSEdgeInsets) {
NSLayoutConstraint.activate([NSLayoutConstraint(item: view, attribute: .top, relatedBy: .equal, toItem: superview, attribute: .top, multiplier: 1, constant: edgeInsets.top), NSLayoutConstraint(item: view, attribute: .left, relatedBy: .equal, toItem: superview, attribute: .left, multiplier: 1, constant: edgeInsets.left), NSLayoutConstraint(item: superview, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: edgeInsets.bottom), NSLayoutConstraint(item: superview, attribute: .right, relatedBy: .equal, toItem: view, attribute: .right, multiplier: 1, constant: edgeInsets.right)])
}
}
extension NSEdgeInsets {
static var zero: NSEdgeInsets {
return NSEdgeInsets(all: 0)
}
init(all: Double) {
self.init(top: all, left: all, bottom: all, right: all)
}
}
extension NSScrollView {
convenience init(documentView: NSView) {
self.init()
self.documentView = documentView
hasVerticalScroller = true
translatesAutoresizingMaskIntoConstraints = false
}
}

Resize NSScrollView within superview

I’m embarrassed to be asking this question, but even with the wealth of information available on SO and Internet searches, I’m unable to to accomplish my goal, which is to resize an NSScrollView contained within an NSView.
The details:
I have an NSViewController that is the window content of the main application window. The view controller contains an NSView to which I’ve programmatically added an NSScrollView, which in itself contains an NSTableView. The main application window and NSViewController are the freebies I get with IB, scroll view and table view are created programatically.
The NSTableView displays the rows and single column I’ve created as expected, but when I resize the window in the horizontal and vertical dimensions, the scroll view doesn’t resize. It appears that the containing view is restricted by the size I specify in creating the scroll view, but without a size the scroll view doesn’t call its delegate methods. My attempts to address that behavior don’t result in expected behavior and so clearly I don’t fully understand the cause of the problem.
My question then is this: what do I need to do to have the scroll view match the containing view when I resize the window?
//
// MyViewController.swift
// HelloTableViewXX
//
//
import Cocoa
fileprivate let ME = "ViewController"
class ViewController: NSViewController
{
private var dataArray: [String] = []
override func viewDidAppear()
{
super.viewDidAppear()
setupView()
setupTableView()
}
func setupView ()
{
self.view.translatesAutoresizingMaskIntoConstraints = false
}
func setupTableView ()
{
let tableView = NSTableView ()
tableView.headerView = nil
let column = NSTableColumn ()
column.identifier = NSUserInterfaceItemIdentifier(rawValue: "TableColumn")
column.width = 400
column.minWidth = 40
column.maxWidth = 4000
tableView.addTableColumn(column)
tableView.delegate = self
tableView.dataSource = self
let scrollView = NSScrollView (frame: self.view.bounds)
//scrollView.hasHorizontalScroller = true
scrollView.hasVerticalScroller = true
self.view.addSubview(scrollView)
scrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
scrollView.documentView = tableView
}
}
extension ViewController : NSTableViewDataSource
{
func numberOfRows(in tableView: NSTableView) -> Int
{
return 20
}
}
extension ViewController : NSTableViewDelegate
{
func tableView (_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
{
let text = "abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz_abcdefghijklmnopqrstuvwxyz"
var v = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier.init(rawValue: "TableColumn"), owner: self) as? NSTextField
if v == nil
{
v = NSTextField ()
v?.identifier = NSUserInterfaceItemIdentifier(rawValue: "TableColumn")
v?.maximumNumberOfLines = 1
}
else
{
print (ME + ".\(#function) tableView reuse")
}
v!.stringValue = text
v!.font = NSFont.monospacedSystemFont(ofSize: 10, weight: .regular)
return v!
}
}
I have an answer for this specific issue. I created a separate project, with IB instantiated NSViewController, and an embedded NSScrollView and NSTableView, giving the expected view hierarchy of controller, view, scrollview, clip view, etc., and configured the settings in IB to produce the results I wanted. I then opened the storyboard in an XML editor, and used the definitions as a guide for the settings in the project with my programmatically set scroll view and table view, which solved my problem. I now have a resizing scroll view and table as the window is resized. The code looks like this:
import Cocoa
class MyTableViewController: NSViewController
{
private var initialized = false
private var dataArray: [String] = []
override func viewDidAppear()
{
super.viewDidAppear()
loadData()
//setupView()
setupTableView()
}
func setupView ()
{
}
func setupTableView ()
{
let tableView = NSTableView ()
tableView.headerView = nil
tableView.columnAutoresizingStyle = .lastColumnOnlyAutoresizingStyle
tableView.autoresizesSubviews = true
tableView.autoresizingMask = [.width, .height]
let column = NSTableColumn ()
column.identifier = NSUserInterfaceItemIdentifier(rawValue: "TableColumn")
column.width = 426
column.minWidth = 40
column.maxWidth = 1000
column.resizingMask = [.autoresizingMask, .userResizingMask] // verify in debugger
tableView.addTableColumn(column)
tableView.delegate = self
tableView.dataSource = self
let scrollView = NSScrollView (frame: self.view.bounds)
scrollView.autoresizesSubviews = true
scrollView.autoresizingMask = [.height, .width]
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.hasHorizontalScroller = true
scrollView.hasVerticalScroller = true
self.view.addSubview(scrollView)
self.view.addConstraint(NSLayoutConstraint (item: self.view, attribute: .trailing, relatedBy: .equal, toItem: scrollView, attribute: .trailing, multiplier: 1.0, constant: 20))
self.view.addConstraint(NSLayoutConstraint (item: scrollView, attribute: .leading, relatedBy: .equal, toItem: self.view, attribute: .leading, multiplier: 1.0, constant: 20))
self.view.addConstraint(NSLayoutConstraint (item: self.view, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1.0, constant: 20))
self.view.addConstraint(NSLayoutConstraint (item: scrollView, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1.0, constant: 20))
scrollView.documentView = tableView
}
}
extension MyTableViewController : NSTableViewDataSource
{
func numberOfRows(in tableView: NSTableView) -> Int
{
return dataArray.count
}
}
extension MyTableViewController : NSTableViewDelegate
{
func tableView (_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?
{
let text = dataArray [row]
var v = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier.init(rawValue: "TableColumn"), owner: self) as? NSTextField
if v == nil
{
v = NSTextField ()
v?.identifier = NSUserInterfaceItemIdentifier(rawValue: "TableColumn")
v?.maximumNumberOfLines = 1
v?.autoresizingMask = [.width]
v?.setContentHuggingPriority(NSLayoutConstraint.Priority(rawValue: 251), for: .horizontal)
v?.translatesAutoresizingMaskIntoConstraints = false
}
else
{
print (ME + ".\(#function) tableView reuse")
}
v!.stringValue = text
v!.font = NSFont.monospacedSystemFont(ofSize: 10, weight: .regular)
v!.frame = CGRect(x: 0, y: 0, width: 3000, height: 0)
return v!
}

NSScrollView truncate text and no scroll bar

I'm having difficult to set up NSScrollView for my text file display.
Defined a window with proper size:
......
let contentRect = NSMakeRect(0.0, 0.0, 800, 600)
let styleMask:NSWindow.StyleMask = [.titled, .closable, .miniaturizable, .resizable]
let window = NSWindow(contentRect:contentRect, styleMask:styleMask, backing:.buffered, defer:true)
window.minSize = NSMakeSize(800.0, 600.0)
window.isReleasedWhenClosed = false
window.tabbingMode = .disallowed
window.makeKeyAndOrderFront(nil)
window.center() // Wait until after makeKeyAndOrderFront so the window sizes properly first
window.title = NSLocalizedString("GCode File", comment:"GCode File window")
......
set up scrollview and put it into windows and then set up window constraints:
......
let scrollView = NSScrollView(frame: (window.contentView?.frame)!)
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.borderType = .noBorder
scrollView.backgroundColor = NSColor.gray
scrollView.hasVerticalScroller = true
window.contentView?.addSubview(scrollView)
window.contentView?.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[scrollView]|", options: [], metrics: nil, views: ["scrollView": scrollView]))
window.contentView?.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[scrollView]|", options: [], metrics: nil, views: ["scrollView": scrollView]))
......
set up clipview and put clipview as scrollview's content view and then configure scrollview and clipview's constraints:
......
let clipView = NSClipView()
clipView.translatesAutoresizingMaskIntoConstraints = false
scrollView.contentView = clipView
scrollView.addConstraint(NSLayoutConstraint(item: clipView, attribute: .left, relatedBy: .equal, toItem: scrollView, attribute: .left, multiplier: 1.0, constant: 0))
scrollView.addConstraint(NSLayoutConstraint(item: clipView, attribute: .top, relatedBy: .equal, toItem: scrollView, attribute: .top, multiplier: 1.0, constant: 0))
scrollView.addConstraint(NSLayoutConstraint(item: clipView, attribute: .right, relatedBy: .equal, toItem: scrollView, attribute: .right, multiplier: 1.0, constant: 0))
scrollView.addConstraint(NSLayoutConstraint(item: clipView, attribute: .bottom, relatedBy: .equal, toItem: scrollView, attribute: .bottom, multiplier: 1.0, constant: 0))
......
setup nstextview and load file content into nstextview:
......
var textView: NSTextView!
var textStorage: NSTextStorage!
textStorage = NSTextStorage()
let layoutManager = NSLayoutManager()
textStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(containerSize: window.contentView!.bounds.size)
layoutManager.addTextContainer(textContainer)
textView = NSTextView(frame: window.contentView!.bounds, textContainer: textContainer)
textView.isEditable = true
textView.isSelectable = true
textView.textStorage?.append(file)
scrollView.documentView = textView
......
Then I run the application, I got windows and content rendering up as:
What's wrong? I don't see the full text (truncated) that load from file and there is no vertical scroll bar shows as well. I believe there are some thing missed in my constraints configuration.
If any one have the experience, please advise!

Adding the WebView to NSPopover doesn't show the WebView, just the NSView

In the AppDelegate init() I have
popover = NSPopover()
popover.behavior = .Transient
popover.contentViewController = ContentViewController()
Now in the ContentViewController : NSViewController
override func loadView() {
view = NSView()
view.translatesAutoresizingMaskIntoConstraints = false
view.addConstraint(NSLayoutConstraint(
item: view, attribute: .Width, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 580))
view.addConstraint(NSLayoutConstraint(
item: view, attribute: .Height, relatedBy: .Equal,
toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 425))
NSUserDefaults.standardUserDefaults().registerDefaults(["UserAgent": "Tick Mac App"])
let url = NSURL(string: tickExtensionURL )!
var request = NSURLRequest(URL: url)
var webView = WebView(frame: view.bounds)
webView.mainFrame.loadRequest(request)
view.addSubview(webView)
}
As you can see the view appears but not the WebView. What am i doing wrong?
you have to tell the controller whats its view so add this at the end of load view
self.view = view

iOS 8 Custom Keyboard appear pretty slow

I am trying to build a custom keyboard using Swift for iOS 8 and I created each button programatically. When switching to my custom keyboard from build-in keyboard, the first time it's pretty slow as it took like 2 seconds to appear. I am not entirely sure if I am doing it correctly. Below are my code and I just show 2 buttons but there are a lot more:
class KeyboardViewController: UIInputViewController {
#IBOutlet var nextKeyboardButton: UIButton!
#IBOutlet var qButton: UIButton!
var buttonFontSize:CGFloat = 22.0
var gapBtwButton:CGFloat = +7.0
// a lot more button below
override func updateViewConstraints() {
super.updateViewConstraints()
// Add custom view sizing constraints here
}
override func viewDidLoad() {
super.viewDidLoad()
setupEnKeyboard()
}
func setupEnKeyboard(){
println("setupEnKeyboard")
addEnKeyboardButtons()
}
func addEnKeyboardButtons() {
addQButton()
addNextKeyboardButton()
// a lot more button to be added
}
func setupButton(label: String, functionName: Selector, imageOnButton: String) -> UIButton {
// initialize the button
let keyButton:UIButton = UIButton.buttonWithType(.System) as UIButton
var testVar: String? = imageOnButton
if imageOnButton.isEmpty {
keyButton.setTitle(label, forState: .Normal)
} else {
let image = UIImage(named: imageOnButton) as UIImage
keyButton.setImage(image, forState: .Normal)
}
keyButton.sizeToFit()
keyButton.setTranslatesAutoresizingMaskIntoConstraints(false)
// adding a callback
keyButton.addTarget(self, action: functionName, forControlEvents: .TouchUpInside)
// make the font bigger
keyButton.titleLabel?.font = UIFont.systemFontOfSize(self.buttonFontSize)
// add rounded corners
keyButton.backgroundColor = UIColor(white: 0.9, alpha: 1)
keyButton.setTitleColor(UIColor.blackColor(), forState: .Normal)
keyButton.layer.cornerRadius = 5
return keyButton
}
func addQButton() {
qButton = setupButton("Q", functionName:"didTapQButton", imageOnButton:"")
view.addSubview(qButton)
var leftSideConstraint = NSLayoutConstraint(item: qButton, attribute: .Left, relatedBy: .Equal, toItem: view, attribute: .Left, multiplier: 1.0, constant: +6.0)
var topConstraint = NSLayoutConstraint(item: qButton, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1.0, constant: +10.0)
view.addConstraints([leftSideConstraint, topConstraint])
}
func didTapQButton(){
var proxy = textDocumentProxy as UITextDocumentProxy
proxy.insertText("q")
}
func addNextKeyboardButton() {
nextKeyboardButton = setupButton("N", functionName:"advanceToNextInputMode", imageOnButton:"globe")
view.addSubview(nextKeyboardButton)
var leftSideConstraint = NSLayoutConstraint(item: nextKeyboardButton, attribute: .Left, relatedBy: .Equal, toItem: showNumbersButton, attribute: .Right, multiplier: 1.0, constant: self.gapBtwButton)
var bottomConstraint = NSLayoutConstraint(item: nextKeyboardButton, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1.0, constant: -3.0)
view.addConstraints([leftSideConstraint, bottomConstraint])
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated
}
override func textWillChange(textInput: UITextInput) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(textInput: UITextInput) {
// The app has just changed the document's contents, the document context has been updated.
var textColor: UIColor
var proxy = self.textDocumentProxy as UITextDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.Dark {
textColor = UIColor.whiteColor()
} else {
textColor = UIColor.blackColor()
}
//self.nextKeyboardButton.setTitleColor(textColor, forState: .Normal)
}
}
Appreciate any comment please :)
Thanks,
Mark Thien

Resources