I have set the intercell spacing in my NSTableView to 0 by sending:
[self.tableView setIntercellSpacing:NSMakeSize(0, 0)];
in the window controller's awakeFromNib but there is still an (possibly 1 pixel wide) empty space between the rows, which I think is where the grid lines are drawn although I'm not using the grid lines. How can I get rid of this space between the rows?
update:
The NSTableView documentation seems to say that this 1 pixel separation should go away when the intercell separation is set to 0, 0. In my case, its not. Maybe it's a bug?
As suggested by trudyscousin, I'll post how I fixed my problem:
As it turns out, the empty space does in fact disappear when you set the intercell spacing to 0 as I did. My problem was that the drawing code in my NSTableCellView subclass wasn't drawing all the way to the edge of the view. The gap I was seeing wasn't the intercell separation, it was the border of my NSTableCellView subclass.
In my case the task was to have 0.5 pt (1 px on Retina display) separator. Seems even when intercellSpacing set to .zero (or 0.5 pt in my case) AppKit still preserving 1 pt space between rows when drawing selection.
I ended up by subclassing NSTableRowView. With custom row I can set separator to any height. Here is a Swift 4.2 example:
class WelcomeRecentDocumentsView: View {
private lazy var tableView = TableView().autolayoutView()
private lazy var scrollView = ScrollView(tableView: tableView).autolayoutView()
override var intrinsicContentSize: NSSize {
return CGSize(width: 240, height: 400)
}
private var recentDocumentsURLs = (0 ..< 10).map { $0 }
}
extension WelcomeRecentDocumentsView: NSTableViewDataSource {
func numberOfRows(in tableView: NSTableView) -> Int {
return recentDocumentsURLs.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
return tableView.makeView(ItemView.self)
}
}
extension WelcomeRecentDocumentsView: NSTableViewDelegate {
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView? {
// Returning custom RowView.
return RowView(separatorHeight: separatorHeight, separatorColor: tableView.gridColor)
}
}
extension WelcomeRecentDocumentsView {
private var separatorHeight: CGFloat {
return convertFromBacking(1)
}
override func setupUI() {
addSubview(scrollView)
let column = NSTableColumn(identifier: "com.example.column", resizingMask: .autoresizingMask)
tableView.addTableColumn(column)
tableView.columnAutoresizingStyle = .uniformColumnAutoresizingStyle
tableView.headerView = nil
tableView.setAutomaticRowHeight(estimatedHeight: ItemView.defaultHeight)
tableView.intercellSpacing = CGSize(height: separatorHeight)
tableView.gridStyleMask = .solidHorizontalGridLineMask
}
override func setupHandlers() {
tableView.delegate = self
tableView.dataSource = self
}
override func setupLayout() {
LayoutConstraint.pin(to: .bounds, scrollView).activate()
}
override func setupDefaults() {
tableView.sizeLastColumnToFit()
}
}
extension WelcomeRecentDocumentsView {
class RowView: NSTableRowView {
let separatorHeight: CGFloat
let separatorColor: NSColor
init(separatorHeight: CGFloat = 1, separatorColor: NSColor = .gridColor) {
self.separatorHeight = separatorHeight
self.separatorColor = separatorColor
super.init(frame: .zero)
}
required init?(coder decoder: NSCoder) {
fatalError()
}
override func drawSelection(in dirtyRect: NSRect) {
let rect = bounds.insetBottom(by: -separatorHeight)
NSColor.alternateSelectedControlColor.setFill()
rect.fill()
}
override func drawSeparator(in dirtyRect: NSRect) {
let rect = bounds.insetTop(by: bounds.height - separatorHeight)
separatorColor.setFill()
rect.fill()
}
}
class ItemView: View {
static let defaultHeight: CGFloat = 52
override var intrinsicContentSize: NSSize {
return CGSize(intrinsicHeight: type(of: self).defaultHeight)
}
}
}
Related
So I have an issue I've been battling with for days now. I have a collectionView which is designed to have large cells (width-wise) which go off screen. The height is fixed. The idea is to basically scroll to the end (see below):
To achieve being able to scroll left and right, I've had to override the width inside collectionViewContentSize in my flowLayout. Like below.
override var collectionViewContentSize: CGSize {
var size = super.collectionViewContentSize
size.width = largeWidth
return size
}
This achieves increasing the horizontal scroll area (which is what I want) but the cells start to disappear once I reach a certain point. It's almost as if the cells are being dequeued when they shouldn't be. Any ideas on this. This is the final straw for my project but I'm out of ideas.
Many thanks
Code snippet can be found below. You should be able to just copy and paste this into any project:
class HomeViewController: UIViewController {
let collectionView: UICollectionView
let collectionViewLayout = CustomCollectionViewFlowLayout()
init() {
collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
super.init(nibName: nil, bundle: nil)
collectionView.backgroundColor = UIColor.red.withAlphaComponent(0.4)
collectionView.register(SomeCell.self, forCellWithReuseIdentifier: "SomeCell")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
collectionView.delegate = self
collectionView.dataSource = self
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.frame = view.bounds
view.addSubview(collectionView)
}
}
class SomeCell: UICollectionViewCell {
}
extension HomeViewController: UICollectionViewDataSource,
UICollectionViewDelegate,
UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 150
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SomeCell", for: indexPath) as! SomeCell
cell.backgroundColor = .blue
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: 10000, height: 70)
}
}
class CustomCollectionViewFlowLayout: UICollectionViewFlowLayout {
override var collectionViewContentSize: CGSize {
var size = super.collectionViewContentSize
size.width = 10000
return size
}
}
Refer to my attached image.
Notice the last column for some reason is always short on the width. I can't for the life of me figure out why or how to fix this?
Here is my code for my controller.
import Cocoa
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
#IBOutlet weak var theTableview: NSTableView!
var data:NSArray = [""] //#JA - This is used
override func viewDidLoad() {
super.viewDidLoad()
//First remove all columns
let columns = self.theTableview.tableColumns
columns.forEach {
self.theTableview.removeTableColumn($0)
}
//self.theTableview?.columnAutoresizingStyle = .sequentialColumnAutoresizingStyle
for index in 0...100 {
let column = NSTableColumn(identifier: "defaultheader")
if(index != 0){
column.title = "Month \(index)"
}else{
column.title = "Factors"
}
self.theTableview.addTableColumn(column)
}
// Do any additional setup after loading the view.
data = ["Group 1","Group 2","Group 3","Group 4"]
self.theTableview.reloadData()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func numberOfRows(in tableView: NSTableView) -> Int {
return data.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if let cell = tableView.make(withIdentifier: "defaultcell", owner: nil) as? NSTableCellView {
cell.textField?.stringValue = data.object(at: row) as! String
return cell
}
return nil
}
#IBAction func startsimulation(_ sender: NSButton) {
//Recalculates the data variable for updating the table.
data = ["group1","group2"]
theTableview.reloadData()
}
}
NSTableColumn has a property resizingMask and NSTableView has a property columnAutoresizingStyle. Both can be set in IB or in code. Figure out a configuration so the columns behave like you want. The default Column Sizing of the table view in IB is 'Last Column Only', switching to 'None' will fix your problem.
Another solution is setting minWidth of the columns.
I have a Today Widget which displaying the upcoming train departures based on a pair of stations chosen in the main application. Given that the departure times changes with time, I have an API call in viewWillAppear fetching the new times and repopulating the cells of a tableView.
Currently, the widget starts off with a height of what looks like 150-200 points in height. My updating logic attempts to set the preferredContent height but this doesn't work. I have the tableView constrained to the top/bottom layout guides. I have tried resetting the tableView's frame height to the main view after telling it a new preferredContent.
Presumably, the callback of my network happens after viewWillAppear, so I invoke layoutIfNeeded to trigger viewDidLayoutSubviews where I reload the tableview.
None of this gets me what I want to see. A tableview populated by the departure times which is N times the height of a cell where N is the number of departure times most recently fetched by my API call.
What am I missing? Any help would be greatly appreciated.
EDIT:
This is the relevant code where I attempt to re-height the extension.
override func viewWillAppear(_ animated: Bool) {
dao.fetchTrips { (trips) in
self.tripsDataSource.dataStore = trips
let numTrips = trips?.count
let rowHeight = 44 // static
let h = numTrips! * rowHeight
self.preferredContentSize = CGSize(width: 0, height: h)
self.view.layoutIfNeeded()
}
}
override func viewDidLayoutSubviews() {
self.tableView.reloadData()
}
I have noticed viewDidLayoutSubviews is not called after layoutIfNeeded is invoked in the completion handler. Although that may not be the issue, as the app-extension main view does not change after setting a new preferredContentSize.
I had the same problem. Few minutes ago I solved it. The default view of widget doesn't show the max height. You need to press show more , next to App title.
Remove your code and just add this to your widget class and on else branch set the height what you want. This will call when you hit show less/ show more.
#available(iOSApplicationExtension 10.0, *)
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize){
if (activeDisplayMode == NCWidgetDisplayMode.compact) {
self.preferredContentSize = CGSize(width: 0, height: maxSize.height);
}
else {
self.preferredContentSize = CGSize(width: 0, height: self.tableView.contentSize.height);
}
}
Edited: My code:
import UIKit
import NotificationCenter
let groupappname = "group.***.***"
class MyCollectionViewController: UICollectionViewController , NCWidgetProviding,UICollectionViewDelegateFlowLayout {
var isEmptyData = true
var array = NSMutableArray()
var defaults = UserDefaults.init(suiteName: groupappname)!
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOSApplicationExtension 10.0, *) {
self.extensionContext?.widgetLargestAvailableDisplayMode = NCWidgetDisplayMode.expanded
} else {
// Fallback on earlier versions
}
registerObserver()
refreshData()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
removeObserver()
}
func refreshData(){
self.preferredContentSize = (self.collectionView?.contentSize)!
#somecode
self.collectionView?.reloadData()
}
func refreshData_NoData(){
self.preferredContentSize = (self.collectionView?.contentSize)!
self.collectionView?.reloadData()
}
func widgetPerformUpdate(completionHandler: #escaping (NCUpdateResult) -> Void) {
print("widgetperformupdate")
completionHandler(NCUpdateResult.newData)
}
#available(iOSApplicationExtension 10.0, *)
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize){
if (activeDisplayMode == NCWidgetDisplayMode.compact) {
self.preferredContentSize = CGSize(width: 0, height: maxSize.height)
}
else {
self.preferredContentSize = CGSize(width: 0, height: (self.collectionView?.contentSize.height)!)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: UICollectionViewDataSource
override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return array.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
#somecode
self.preferredContentSize = collectionView.contentSize
return cell
}
}
I tried many different ways to create a NSTableview with custom NSTableCellView but I could not make it work. The question is, how can I do that thing?
Here is the last thing I tried:
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
var cellIdentifier: String = ""
if tableColumn == tableView.tableColumns[0] {
cellIdentifier = "CellID"
if let cell = tableView.makeViewWithIdentifier(cellIdentifier, owner: self ) as? MyTableCellView {
cell.identifier = cellIdentifier
// array is an array that contains NSView with layers with different colors
cell.myView = array[row]
return cell
}
}
return nil
}
After adding a label:
And the full code:
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
tableview.setDelegate(self)
tableview.setDataSource(self)
let view = NSView(frame: NSRect(x: 0, y: 0, width: 200, height: 200))
view.layer?.backgroundColor = NSColor.blueColor().CGColor
array.append(view)
let view2 = NSView(frame: NSRect(x: 0, y: 0, width: 200, height: 200))
view2.layer?.backgroundColor = NSColor.greenColor().CGColor
array.append(view2)
array2label.append("bu")
array2label.append("buu")
tableview.reloadData()
// Do any additional setup after loading the view.
}
override func viewWillAppear() {
//tableview.reloadData()
laView.layer?.backgroundColor = NSColor.greenColor().CGColor
}
#IBOutlet weak var laView: NSView!
#IBOutlet weak var tableview: NSTableView!
var array = [NSView]()
var array2label = [String]()// = ["bu","buu"]
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
if (tableView.identifier == "Taula") {
return array.count
//return taulaGrafics.count
} else {
return 0
}
}
#IBOutlet weak var viewDeProva: NSView!
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
print ( "Preparem la TableView" )
var cellIdentifier: String = ""
if tableColumn == tableView.tableColumns[0] {
cellIdentifier = "CellID"
if let cell = tableView.makeViewWithIdentifier(cellIdentifier, owner: self ) as? MyTableCellView {
print ( "aqui" )
print(array)
print(array2label)
cell.identifier = cellIdentifier
cell.myView = array[row]
cell.label.stringValue = array2label[row]
return cell
}
}
return nil
}
#IBAction func afegir(sender: NSButton) {
let view = NSView(frame: NSRect(x: 0, y: 0, width: 200, height: 200))
view.layer?.backgroundColor = NSColor.yellowColor().CGColor
array.append(view)
array2label.append("buLabel")
tableview.reloadData()
}
#IBAction func treure(sender: NSButton) {
array.removeLast()
tableview.reloadData()
}
}
if let cell = tableView.makeViewWithIdentifier(cellIdentifier, owner: self ) as? MyTableCellView {
print ( "aqui" )
print(array)
print(array2label)
cell.identifier = cellIdentifier
cell.myView = array[row]
cell.myView.wantsLayer = true
cell.label.stringValue = array2label[row]
return cell
}
With this changes the view show the color that is expected to be:
class ViewController: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
override func viewDidLoad() {
super.viewDidLoad()
print("DidLoad")
tableview.setDelegate(self)
tableview.setDataSource(self)
}
override func viewWillAppear() {
print("WillAppear")
array.append(NSColor.blueColor().CGColor)
array2label.append("buIni")
tableview.reloadData()
laView.layer?.backgroundColor = NSColor.greenColor().CGColor
}
#IBOutlet weak var laView: NSView!
#IBOutlet weak var tableview: NSTableView!
var array = [CGColor]()
var array2label = [String]()
func numberOfRowsInTableView(tableView: NSTableView) -> Int {
if (tableView.identifier == "Taula") {
return array.count
//return taulaGrafics.count
} else {
return 0
}
}
#IBOutlet weak var viewDeProva: NSView!
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
print ( "Preparem la TableView" )
var cellIdentifier: String = ""
if tableColumn == tableView.tableColumns[0] {
cellIdentifier = "CellID"
if let cell = tableView.makeViewWithIdentifier(cellIdentifier, owner: self ) as? MyTableCellView {
print ( "Here" )
print(array)
print(array2label)
cell.identifier = cellIdentifier
cell.myView.layer?.backgroundColor = array[row]
cell.label.stringValue = array2label[row]
return cell
}
}
return nil
}
#IBAction func afegir(sender: NSButton) {
let color = NSColor.yellowColor().CGColor
array.append(color)
array2label.append("buLabel")
tableview.reloadData()
}
#IBAction func treure(sender: NSButton) {
array.removeLast()
array2label.removeLast()
tableview.reloadData()
}
}
The problem was that the NSView inside the NSTableViewCell can't be replaced by another view, because what you do doing that is changing the prototype cell. So if you replace the view the NSTableViewCell doesn't recognize that NewView layer (I'm not sure 100% why). Looks like just some information is shared.
So, how can we pass info to the tableview cell? How can we show a layer there?? Well the answer is that just modifying the prototype cell NSTableCellView added on the interface builder or its subclass (like my case, see bellow the cell). Modifying its content, but not the NSView!, inside the viewForTableColumn function. See in this piece of code:
if let cell = tableView.makeViewWithIdentifier(cellIdentifier, owner: self ) as? MyTableCellView {
cell.identifier = cellIdentifier // that is to identify the cell
cell.myView.layer?.backgroundColor = array[row] // see that the array contains CGCOLORS not NSViews
// The program was not showing the colors because the
// view copied doesn't copy its layer, or at least
// doesn't show it.
// Even though, if you say: *******---This is incorrect:
cell.myView = arrayOfNSViews[row] //the program will
// crash when removing 2 rows, myView = nil !!!!.
// That is because when you remove the item of the array
// somehow you are removing myView too, because it make a
// copy or a reference to it (Not sure what exactly).
// Here continues the correct program: ******---
cell.label.stringValue = array2label[row]
return cell
}
Also see that in the textField case: cell.label.stringValue = array2label[row], you change the string value of the textfield, not the whole NSTextfield.
So guys remember and repeat my words: "I'm not going to change the view of the cell, just its properties". I just spend 4 days to find that...
Here is the NStableCellView promised:
class MyTableCellView: NSTableCellView {
#IBOutlet weak var myView: NSView!
#IBOutlet weak var label: NSTextField!
}
One Image of the view hierarchy:
I am trying to figure out how to display a message within a table view when the table is empty. I would like it to say something like: "You haven't added any transactions yet. Tap the add button to get started.". Obviously I would need it to revert back to this message if the user deletes all of the cells, too.
This is the code that I currently have in my table view controller:
class ThirdViewController: UITableViewController {
override func viewWillAppear(animated: Bool) {
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// #pragma mark - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return arrayObject.paymentsArray().count
}
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell:CustomTransactionTableViewCell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomTransactionTableViewCell
cell.paymentNameLabel.text = (arrayObject.paymentsArray().objectAtIndex(indexPath.row)) as String
cell.costLabel.text = (arrayObject.costArray().objectAtIndex(indexPath.row)) as String
cell.dateLabel.text = (arrayObject.dateArray().objectAtIndex(indexPath.row)) as String
if arrayObject.imageArray().objectAtIndex(indexPath.row) as NSObject == 0 {
cell.paymentArrowImage.hidden = false
cell.creditArrowImage.hidden = true
} else if arrayObject.imageArray().objectAtIndex(indexPath.row) as NSObject == 1 {
cell.creditArrowImage.hidden = false
cell.paymentArrowImage.hidden = true
}
return cell
}
override func tableView(tableView: UITableView!, canEditRowAtIndexPath indexPath: NSIndexPath!) -> Bool {
return true
}
override func tableView(tableView: UITableView!, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath!) {
if (editingStyle == UITableViewCellEditingStyle.Delete) {
if let tv=tableView {
arrayDataCost.removeObjectAtIndex(indexPath!.row)
arrayDataImage.removeObjectAtIndex(indexPath!.row)
arrayDataPayments.removeObjectAtIndex(indexPath!.row)
arrayDataDate.removeObjectAtIndex(indexPath!.row)
tv.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
}
}
Any help would be much appreciated!
You might want to set the backgroundView to a UILabel (Or some view you made when the table is empty
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.numberOfRow == 0{
var emptyLabel = UILabel(frame: CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height))
emptyLabel.text = "No Data"
emptyLabel.textAlignment = NSTextAlignment.Center
self.tableView.backgroundView = emptyLabel
self.tableView.separatorStyle = UITableViewCellSeparatorStyle.None
return 0
} else {
return self.numberOfRow
}
}
something like this works fine for me
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
var numOfSection: NSInteger = 0
if array.count > 0
{
self.tableView.backgroundView = nil
numOfSection = 1
}
else
{
var noDataLabel: UILabel = UILabel(frame: CGRectMake(0, 0, self.tableView.bounds.size.width, self.tableView.bounds.size.height))
noDataLabel.text = "No Data Available"
noDataLabel.textColor = UIColor(red: 22.0/255.0, green: 106.0/255.0, blue: 176.0/255.0, alpha: 1.0)
noDataLabel.textAlignment = NSTextAlignment.Center
self.tableView.backgroundView = noDataLabel
}
return numOfSection
}
Override your viewDidLoad() method like this:
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.yourLabel);
if arrayObject.paymentsArray().count > 0 {
self.tableView.hidden = NO;
self.yourLabel.hidden = YES;
} else {
self.tableView.hidden = YES;
self.yourLabel.hidden = NO;
}
}
You can either hide the tableView and unhide the label, or display some sort of animation that reduces the alpha of one of the views for a 'fade' effect.
You can use this method. No function limit
Swift3
if tableView.visibleCells.isEmpty{
//tableview is not data
print("can not found data")
}else{
//do somethings
}
I needed to achieve the same thing. This is what i did.
var label: UILabel {
let label = UILabel(frame: tableView.bounds)
label.text = "empty"
return label
}
override func viewDidLoad() {
tableView.backgroundView = UIView(frame: tableView.bounds)
tableView.backgroundView?.addSubview(noPlacesMessageLabel)
}