I'm working on a markdown editor for macOS using AppKit, and have all the basics down and working. I'm using NSTextBlock for the code blocks.
One issue I'm encountering however, is that typing a Tab inside of an NSTextBlock, causes the caret to move down to the next paragraph, as opposed to inserting the tab whitespace. The desired outcome is for the user to be able to insert tabs inside of NSTextBlock.
I've created a sample project to demonstrate this behavior on GitHub.
It's a simple AppKit application. I've added an Editable, Scrollable Text View to the default ViewController in Main.storyboard. The textView is connected to the ViewController class via an IBOutlet.
Inside of the autogenerated viewDidLoad() method, I call the insertText
function, which I wrote to insert a line of text, followed by two paragraphs styled with NSTextBlocks, into the NSTextView.
func insertText(textStorage: NSTextStorage) {
//Insert first line of regular text
var attributes: [NSAttributedString.Key: Any] = [
.font: NSFont.systemFont(ofSize: 24),
.foregroundColor: NSColor.textColor,
.backgroundColor: NSColor.clear
]
let attributedString1: NSAttributedString = NSAttributedString(string: "Test\n", attributes: attributes)
textStorage.append(attributedString1)
//Insert the blue block holdig text
attributes[.foregroundColor] = NSColor.white
attributes[.paragraphStyle] = ParagraphStyle(bgColor: NSColor.systemBlue).paragraphStyle
let attributedBlock1 = NSAttributedString(string: "Hello, I'm the blue block\n", attributes: attributes)
textStorage.append(attributedBlock1)
//Insert the pink block holdig text
attributes[.foregroundColor] = NSColor.black
attributes[.paragraphStyle] = ParagraphStyle(bgColor: NSColor.systemPink).paragraphStyle
let attributedBlock2 = NSAttributedString(string: "Hello, I'm the pink block\n", attributes: attributes)
textStorage.append(attributedBlock2)
}
The effect this has:
If you launch the application and try to insert a Tab when the caret is located at the beginning of the first paragraph, Tab whitespace is added to the beginning of this paragraph as expected. However, if you try to do the same to the paragraphs styled with NSTextBlocks, the caret simply moves down to the next paragraph, and Tab whitespace is not inserted.
The other two classes I wrote for the sample project are ParagraphStyle and CustomTextBlock.
For the ParagraphStyle
class, holding the paragraph style that was used for the attributes of the blue and pink blocks:
class ParagraphStyle {
let bgColor: NSColor
let paragraphStyle: NSParagraphStyle
init(bgColor: NSColor) {
self.bgColor = bgColor
//Set paragraph style
self.paragraphStyle = {
let mutableParagraphStyle = NSMutableParagraphStyle()
let specialBlock = CustomTextBlock(bgColor: bgColor)
mutableParagraphStyle.textBlocks.append(specialBlock)
let style = mutableParagraphStyle as NSParagraphStyle
return style
}()
}}
For the CustomTextBlock
class, inheriting from NSTextBlock
, I define the color and dimensions of the block:
class CustomTextBlock: NSTextBlock {
init(bgColor: NSColor) {
super.init()
//Control the BG color of the text block
self.backgroundColor = bgColor
//Control dimensions of the text block
self.setValue(100, type: NSTextBlock.ValueType.percentageValueType, for: NSTextBlock.Dimension.width)
self.setValue(50, type: NSTextBlock.ValueType.absoluteValueType, for: NSTextBlock.Dimension.minimumHeight)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}}
ParagraphStyle
class.nextKeyView
and nextResponder
to nil.The only thing remaining (that I can think of), is to listen for the user's pressing of the tab key when the textview is first responder. When the event occurs, I would programmatically insert Tab white space where the user's caret is, and then programmatically move the caret back to where it should be. This however is an extremely round-about way of fixing this seemingly simply issue, which is why I believe I'm probably missing something obvious. Was hoping that the more experienced developers on here might have other suggestions!
Thank you in advance!
Apparently a tab in a text block moves the insertion point to the next text block, which makes sense in a table. Workaround: subclass NSTextView
, override insertTab
and insert a tab.
override func insertTab(_ sender: Any?) {
insertText("\t", replacementRange: selectedRange())
}