I created a table based on NSTextTableBlock and am trying to add a nested table to this table. Unfortunately it doesn’t work, does anyone have any ideas to do this. I tried the same thing before using HTML, but when creating the CSS table, the style only worked on the first attribute.
@IBOutlet var textView: NSTextView!
override func viewDidLoad() {
super.viewDidLoad()
let table = getTableString()
textView.textStorage?.setAttributedString(table)
}
func getTableString() -> NSMutableAttributedString {
var table = NSTextTable()
table.numberOfColumns = 4
var mas = NSMutableAttributedString()
// Header
mas.append(createTableBlock(string: "Header. 1", textTable: table, row: 0, column: 0, width: 25, isHeader: true))
mas.append(createTableBlock(string: "Header. 2", textTable: table, row: 0, column: 1, width: 25, isHeader: true))
mas.append(createTableBlock(string: "Header. 3", textTable: table, row: 0, column: 2, width: 25, isHeader: true))
mas.append(createTableBlock(string: "Header. 4", textTable: table, row: 0, column: 3, width: 25, isHeader: true))
// Row 1
mas.append(createTableBlock(string: "Nr. 1", textTable: table, row: 1, column: 0, width: 25))
mas.append(createTableBlock(string: "Nr. 2", textTable: table, row: 1, column: 1, width: 25))
mas.append(createTableBlock(string: "-mark-", textTable: table, row: 1, column: 2, width: 50))
// Create the nested table
var nestedTable = NSTextTable()
nestedTable.numberOfColumns = 2
var nestedMAS = NSMutableAttributedString()
nestedMAS.append(createTableBlock(string: "Nested 1", textTable: nestedTable, row: 0, column: 0, width: 24, isNested: true))
nestedMAS.append(createTableBlock(string: "Nested 2", textTable: nestedTable, row: 0, column: 1, width: 24, isNested: true))
let needleRange = mas.string.range(of: "-mark-")!
let insertionRange = NSRange(needleRange, in: mas.string)
mas.replaceCharacters(in: insertionRange, with: nestedMAS)
return mas
}
func createTableBlock(string: String, textTable: NSTextTable, row: Int, column: Int, width: CGFloat, isHeader: Bool = false, isNested: Bool = false) -> NSMutableAttributedString {
var offset = 1
if isHeader || isNested {
offset = 1
} else if column >= 2 {
offset = 2
}
let block = NSTextTableBlock(table: textTable, startingRow: row, rowSpan: 1, startingColumn: column, columnSpan: offset)
block.backgroundColor = isHeader ? .lightGray : .white
block.setBorderColor(isNested ? .red : .green)
block.setWidth(1.0, type: NSTextBlock.ValueType.absoluteValueType, for: NSTextBlock.Layer.border)
block.setContentWidth(width, type: .percentageValueType)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = isNested ? .center : .left
paragraphStyle.textBlocks = [block]
var mas = NSMutableAttributedString(string: string + "\n")
mas.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, mas.length))
return mas
}
what I expect should look like this:
but here's the result:
Updated issue: I was able to insert a string into a cell (thanks @Willeke), but unfortunately I couldn’t insert it as a double cell.
@IBOutlet var textView: NSTextView!
override func viewDidLoad() {
super.viewDidLoad()
let table = getTableString()!
textView.textStorage?.setAttributedString(table)
}
func getTableString() -> NSMutableAttributedString? {
let table = NSTextTable()
table.numberOfColumns = 4
let mas: NSMutableAttributedString = NSMutableAttributedString()
// Header
mas.append(addCell(string: "Header. 1\n", textTable: table, row: 0, column: 0, width: 25, isHeader: true))
mas.append(addCell(string: "Header. 2\n", textTable: table, row: 0, column: 1, width: 25, isHeader: true))
mas.append(addCell(string: "Header. 3\n", textTable: table, row: 0, column: 2, width: 25, isHeader: true))
mas.append(addCell(string: "Header. 4\n", textTable: table, row: 0, column: 3, width: 25, isHeader: true))
// Row 1
mas.append(addCell(string: "Nr. 1\n", textTable: table, row: 1, column: 0, width: 25))
mas.append(addCell(string: "Nr. 2\n", textTable: table, row: 1, column: 1, width: 25))
// Composed nested table
mas.append(addNestedCell(textTable: table, row: 1, column: 2, width: 50))
return mas
}
// Regular cell
func addCell(string:String, textTable: NSTextTable, row:Int, column:Int, columnSpan:Int=1, width:CGFloat, isHeader:Bool=false, isNested:Bool=false) -> NSMutableAttributedString {
let block = createTextTableBlock(table: textTable, row: row, column: column, columnSpan: columnSpan, width: width, isHeader: isHeader, isNested: isNested)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = isNested ? .center : .left
paragraphStyle.textBlocks = [block]
let mas = NSMutableAttributedString(string: string)
mas.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, mas.length))
return mas
}
// Nested cell
func addNestedCell(textTable: NSTextTable, row: Int, column: Int, width: CGFloat) -> NSMutableAttributedString {
let parentBlock = createTextTableBlock(table: textTable, row: row, column: column, columnSpan: 2, width: width)
// Create the nested table
let nestedTable = NSTextTable()
nestedTable.numberOfColumns = 2
let nestedMAS = NSMutableAttributedString()
// Nested cell 1
nestedMAS.append(addCell(string: "Nested 1\n", textTable: nestedTable, row: 0, column: 0, width: 25, isNested: true))
// Nested cell 2
nestedMAS.append(addCell(string: "Nested 2\n", textTable: nestedTable, row: 0, column: 1, width: 25, isNested: true))
let paraNestStyle = getParagraphStyle(nestedMAS)
let textNestBlocks = paraNestStyle!.textBlocks
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
paragraphStyle.textBlocks = [parentBlock]
paragraphStyle.textBlocks.append(contentsOf: textNestBlocks)
nestedMAS.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, nestedMAS.length))
return nestedMAS
}
// TableBlock builder
func createTextTableBlock(table textTable:NSTextTable, row:Int, column:Int, columnSpan:Int=1, width:CGFloat, isHeader:Bool=false, isNested:Bool=false) -> NSTextTableBlock {
let block = NSTextTableBlock(table: textTable, startingRow: row, rowSpan: 1, startingColumn: column, columnSpan: columnSpan)
block.backgroundColor = isHeader ? .lightGray : .white
block.setBorderColor(isNested ? .red : .green)
block.setWidth(1.0, type: NSTextBlock.ValueType.absoluteValueType, for: NSTextBlock.Layer.border)
block.setContentWidth(width, type: .percentageValueType)
return block
}
// Get paragraph style from NSMutableAttributedString
func getParagraphStyle(_ attributedString:NSMutableAttributedString, at index:Int=0) -> NSMutableParagraphStyle? {
let range = NSMakeRange(0, attributedString.length)
guard let paraStyle = attributedString.attribute(NSAttributedString.Key.paragraphStyle, at:index, longestEffectiveRange:nil, in:range) as? NSMutableParagraphStyle else { return nil }
return paraStyle
}
now it looks like this
The nested cells are in both tables and
paragraphStyle.textBlocks
must contain aNSTextTableBlock
for the cell in each table.Example:
Result: