Programatically created NSTableView column widths do not line up with header - swift

I have a programatically created NSTableView that I am adding new columns to:
func createColumnsOnTable(table : NSTableView, columnNames: String[], hideColumns: String[])
{
for columnNum in 0 .. 2
{
let columnName = columnNames[columnNum]
var newTableColumn = NSTableColumn()
var newHeaderCell = NSTableHeaderCell()
newTableColumn.setHeaderCell(newHeaderCell)
newTableColumn.setIdentifier(columnName.lowercaseString)
newHeaderCell.setTitle(columnName)
newTableColumn.setEditable(true)
newTableColumn.sizeToFit()
newTableColumn.setResizingMask(NSTableColumnUserResizingMask)
//newHeaderCell.drawSortIndicatorWithFrame(table.headerView().frame(), inView: table.headerView(), ascending: true, priority: 0)
if isInSet(columnName, valueArray: hideColumns)
{
newTableColumn.setHidden(true)
}
var sortDescriptor = NSSortDescriptor(key: newTableColumn.identifier(), ascending: true, selector: "compare")
newTableColumn.setSortDescriptorPrototype(sortDescriptor)
table.addTableColumn(newTableColumn)
}
}
My problem is the new columns display fine and look OK but the vertical gridline is not lined up with the divider in the header. In addition as the divider is dragged to make the column wider the column gets wider but the alignment gets even more skewed. I'm not sure how to get them to stay lined up when resizing the column or how to even get them to start off lined up. When I use a tableview created in IB I don't have this problem so I know I'm missing something but just can't find what.
Thanks for any help you can provide.

I discovered the reason. When trying to work out why I had no scroll bars I had added a setBoundsSize setting on the contentView of the scrollView containing the tableView. Once I got rid of that line all started behaving as normal. Just in case anyone else runs into this issue (however unlikely that may be) the line I removed was:
newScrollView.contentView().setBoundsSize(newScrollView.bounds().size)

Related

sectionHeaderTopPadding doesn't apply on Grouped and InsetGrouped UITableView

since iOS15 a strange top gap started to appear
after research i found out that
if #available(iOS 15, *) {
tableView.sectionHeaderTopPadding = 0
}
should solve the issue
however this works only for plain style table (UITableView.Style.plain), but my table is grouped and it looks like this property is taking no effect on GROUPED style (UITableView.Style.grouped)
is this a bug? how to remove the gap on grouped table?
According to the What's new in UIKit video from WWDC2021, top padding seems to have been added only to the plain style of UITableView.
Here's the part that mentions it
We have a new appearance for headers in iOS 15. For plain lists, section headers now display seamlessly in line with the content, and only display a visible background material when becoming pinned to the top as you scroll down. In addition, there's new padding inserted above each section header to visually separate the sections with this new design.
You should use this plain style in conjunction with index bars for fast scrubbing when list content is long as demonstrated in the Contacts app.
https://developer.apple.com/videos/play/wwdc2021/10059/?time=438
You can try this (this worked for me for grouped/insetGrouped)
if #available(iOS 15.0, *) {
UITableView.appearance().sectionHeaderTopPadding = 0
let tableHeaderView = UIView()
tableHeaderView.translatesAutoresizingMaskIntoConstraints = false
tableHeaderView.heightAnchor.constraint(equalToConstant: 5).isActive = true
UITableView.appearance().tableHeaderView = tableHeaderView
} else {
// Fallback on earlier versions
}

How to get the index of a cell given the text inside in XCUITest?

I'm testing a tableview the cell content in XCUItest. In my case, I don't know the order of the cell text, nor am I allowed to set an accessibility id for the text. How can I get the index of a cell given the text inside?
For instance, if I wanted to get the index of the cell containing text "Cell 2 Text" I would try something like this:
func testSample() {
let app = XCUIApplication()
let table = app.tables
let cells = table.cells
let indexOfCell2Text = cells.containing(.staticText, identifier: "Cell 2 Text").element.index(ofAccessibilityElement: "I dunno")
print(indexOfCell2Text)
}
I feel like I'm close, but I'm unsure. Can anyone suggest a solution?
I apologize if this question has been asked before. I wasn't able to find anything specific about this.
References I visited beforehand:
https://developer.apple.com/documentation/xctest/xcuielementquery/1500842-element
How can I verify existence of text inside a table view row given its index in an XCTest UI Test?
iOS UI Testing tap on first index of the table
The most reliable way really is to add the index into the accessibility identifier. But, you can't. Can you change the accessibility identifier of the cell instead of the text ?
Anyway, if you don't scroll your table view, you can handle it like that :
let idx = 0
for cell in table.cells.allElementsBoundByIndex {
if cell.staticTexts["Text you are looking for"].exists {
return idx
}
idx = idx + 1
}
Otherwise, the index you will use is related to cells which are displayed on the screen. So, after scrolling, the new first visible cell would become the cell at index 0 and would screw up your search.
for index in 0..<table.cells.count {
if table.cells.element(boundBy: index).staticTexts["Your Text"].exists {
return index
}
}

How to setup printing in cocoa, swift?

I have made printing functionality for custom NSView of NSPopover by the assigning the following action to button for this NSView in mainController:
#IBOutlet var plasmidMapIBOutlet: PlasmidMapView!
#IBAction func actionPrintfMap(sender: AnyObject)
{
plasmidMapIBOutlet.print(sender)
}
It is working, but the print window has no option for Paper Size and Orientation, see screenshot below.
What should I do to get these options in the print window?
And, how to make the NSView fitting to the printable area? Now it is not fitting.
I have figured out some moments, but not completely. So, I can setup the printing by the following code
#IBAction func actionPrintMap(sender: AnyObject)
{
let printInfo = NSPrintInfo.sharedPrintInfo()
let operation: NSPrintOperation = NSPrintOperation(view: plasmidMapIBOutlet, printInfo: printInfo)
operation.printPanel.options = NSPrintPanelOptions.ShowsPaperSize
operation.printPanel.options = NSPrintPanelOptions.ShowsOrientation
operation.runOperation()
//plasmidMapIBOutlet.print(sender)
}
But, I still have problem. From the code above I can get only orientation (the last, ShowsOrientation), but not both PaperSize and Orientation. How can I manage both ShowsPaperSize and ShowsOrientation?
Finally I have found the answer which is simple to write but it is not really obvious from apple documentation.
operation.printPanel.options.insert(NSPrintPanelOptions.showsPaperSize)
operation.printPanel.options.insert(NSPrintPanelOptions.showsOrientation)
The problem in the code originally posted is that options is being assigned twice, so the first value assigned, ShowsPaperSize is overwritten by the value ShowsOrientation. That's why you only see the ShowsOrientation option in the dialog.
By using multiple insert operations, you are adding options rather than overwriting each time. You can also do it this way which I think reads better:
operation.printPanel.options.insert([.showsPaperSize, .showsOrientation])
And finally, it also works to "set" the options, and by supplying the existing options as the first array value, you achieve the affect of appending:
operation.printPanel.options = [
operation.printPanel.options,
.showsPaperSize,
.showsOrientation
]
(The first array element operation.printPanel.options means that the old options are supplied in the list of new options.)

How to have a subject in RxSwift push values to itself without creating an infinite loop

I have a UITableView, which I want to put into an editing state if certain conditions are met. The primary way to toggling edit is through an edit button.
So the view elements I have are
let tableView = UITableView()
let editButton = UIButton()
And whether the tableView should be in editing mode is fed from:
let editing = BehaviorSubject(value: false)
Which will be hooked up to the tableView using something like:
editing.subscribeNext { isEditing in
tableView.setEditing(isEditing, animated: true)
}
When the edit button is tapped, I want that to push a new value to editing, that is the negation of the most recent value sent to editing. The most recently value may have been set by a tap on editButton, or it may have come from somewhere else.
I don't understand how to combine the stream for the button press with the stream for editing in such a way that allows this without an infinite loop e.g.
Obervable.combineLatest(editButton.rx_tap.asObservable(), editing) { _, isEditing in
editing.onNext(!isEditing)
}
I'm aware that the tableView has an editing property, but I don't want to rely on that as I am looking for a more general solution that I can re-use elsewhere. I'm also not looking to track the value of isEditing in an instance var, or even as a Variable(), as I am looking for a stateless, stream based solution (if this is at all possible).
Thank you!
With some help from the RxSwift GitHub issues forum I've now worked it out :). The key was withLatestFrom. I've included an example of this below in case it will help anyone else. editButton is the primary way to trigger editing mode on or off, and I've included an event sent via tableView.rx_itemSelected as an additional input example (in this case, I want editing to end any time an item is selected).
let isEditing = BehaviorSubject(value: false)
let tableView = UITableView()
let editButton = UIButton()
tableView.rx_itemSelected
.map { _ in false }
.bindTo(isEditing)
editButton.rx_tap.withLatestFrom(isEditing)
.map { !$0 }
.bindTo(isEditing)
isEditing.subscribeNext { editing in
tableView.setEditing(editing, animated: true)
}
Note: This solution sends .Next(false) to isEditing every time an item is selected, even if the table isn't currently in editing mode. If you feel this is a bad thing, and want to filter rx_itemSelected to only send .Next(false) if the table is actually in editing mode, you could probably do this using a combination of withLatestFrom and filter.
What if you define editing as a Variable instead of a BehaviourSubject. A Variable cannot error out which makes sense in this case. The declaration would look like this:
let editing = Variable(value: false)
You could subscribe to a button tap and change the value of editing to the negated current one:
editButton.rx_tap.asObservable().subscribeNext { editing.value = !editing.value }
With changing the value property of editing this method is called
editing.subscribeNext { isEditing in
tableView.setEditing(isEditing, animated: true)
}
All of this is not tested, but might lead you in the right direction for the right solution.

Layout question Titanium Mobile

I'm having a bit of trouble with the layout of the GUI in Titanium Mobile (iPhone). Is there a guide/article somewhere that explains how height/width/positioning/scrolling/etc. works in Titanium Mobile?
Right now I'm stuck on two problems:
I want a scrollable window with an ImageView in the top part, and a TableView beneath it. They should both scroll together. I tried adding them both to a ScrollView and stretching the ScrollView to the size of the window, but then the ImageView is fixed to the top of the screen, and the TableView is scrollable in the bottom half of the screen, whereas everything should scroll together within the window.
I want to create an editable grouped TableView similar to the "Contacts" app on iPhone. For the blue labels on the left I created a label and added them to the TableRow, then added a textfield for the rest of the row. This works, but is it possible to give the label an 'auto' width large enough to fit the text and a bit of padding left and right, and have the textfield on the right to fill the rest of the row (I tried setting the width of the label to 'auto', that doesn't do the trick).
I hope my questions are clear, otherwise leave a comment and I'll make some screenshots describing the problems visually.
Thanks for any help!
0) Nope, there is no "layout" documentation in one nice place.
1) Make the entire display a table. Put the image view into the headerView of a table. That's how I did this:
2) I don't know about textfields, sorry. But as a fallback, you can do labels like above, and then load a separate form in another screen. That may be your simplest/best option.
var baseScrollView = Ti.UI.createScrollView({
top:0,
contentWidth:'auto',
contentHeight:'auto',
showVerticalScrollIndicator:true,
showHorizontalScrollIndicator:false,
backgroundColor:'white',
scrollType : 'vertical'
});
var iv = Ti.UI.createImageView({
borderRadius:10,
top:10,
width:300,
height:300,
image:currentWindow._rowObject["image"]
});
baseScrollView.add(iv);
var data = [];
tableView = Titanium.UI.createTableView({
top:320,
height:400,
scrollable: false,
data:data,
touchEnabled : true,
allowsSelection : false,
font: {
fontFamily:"Trebuchet MS",
fontSize:12
},
style : Titanium.UI.iPhone.TableViewStyle.GROUPED
});
baseScrollView.add(tableView);