1

我想知道使用 Swift 3/MacOS Sierra 以编程方式在 NSStackView 中添加 NSTableView。

我们的想法是让 2 个 NSTextFields 通过 .leading 重力空间中的 centerY 轴对齐,然后是 .center 重力空间中的一个 tableview,然后再有 2 个 NSTextFields 通过 .trailing 重力空间中的 centerY 轴对齐。堆栈视图将跨越 NSView 的宽度——就像一个标题。

这是个好主意还是我应该避免这样做?很难让它看起来正确——尽管添加了约束以尝试将其固定到固定宽度,但表格的宽度总是太大。

任何见解将不胜感激。我是 MacOS 编程新手。

谢谢,

这是 Interface Builder 中的 输出:headerview 的输出

这是我正在使用的 NSView 的代码: 视图控制器在别处,但视图控制器并没有真正的问题——它正确地显示了表中的数据。这只是 tableview 的大小/定位(我试图通过 NSStackView 在 NSView 中做)总是错误的。它的宽度应该为 650,但宽度为 907,并且我在调试控制台中一直收到相同的错误:

2017-09-12 17:43:36.041062-0500 RaceProgram[795:36958] [Layout] 检测到 < RacingProgram.RaceImportViewHeader: 0x6000001ccd50 > 缺少约束。无法放置,因为没有足够的约束来完全定义大小和原点。添加缺少的约束,或设置 translatesAutoresizingMaskIntoConstraints=YES 并且将为您生成约束。如果此视图在 macOS 10.12 及更高版本上手动布局,您可以选择不从覆盖中调用 [super layout]。在 DETECTED_MISSING_CONSTRAINTS 上设置断点进行调试。此错误只会记录一次。

import Cocoa

@IBDesignable
class RaceImportViewHeader: NSView {

// MARK: Properties
private var raceQualificationsTableView:NSTableView

private var raceImportHeaderStackView:NSStackView

private var raceNumberTitle: NSTextField
private var raceNumberValue: NSTextField

public var raceQualificationsTableRowHeight: CGFloat            

@IBInspectable var borderColor:NSColor = .black
@IBInspectable var backgroundColor:NSColor = .lightGray

enum InitMethod {
    case Coder(NSCoder)
    case Frame(CGRect)
}

override convenience init(frame: CGRect) {
    self.init(.Frame(frame))!
}

required convenience init?(coder: NSCoder) {
    self.init(.Coder(coder))
}

private init?(_ initMethod: InitMethod) {
    // Group together the initializers for this view class

    raceQualificationsTableView = NSTableView()

    raceImportHeaderStackView = NSStackView()

    raceNumberTitle = NSTextField()
    raceNumberValue = NSTextField()

    raceQualificationsTableRowHeight = 17.0         // Initialize the row height for raceQualifications

    switch initMethod {
    case let .Coder(coder): super.init(coder: coder)
    case let .Frame(frame): super.init(frame: frame)
    }

    self.translatesAutoresizingMaskIntoConstraints = false

    drawUI()
}


override func draw(_ dirtyRect: NSRect) {
    super.draw(dirtyRect)

    let viewSize: NSRect = self.frame
    let newRect = NSRect(x: 0, y: 0, width: viewSize.width, height: viewSize.height)


    // Outline the Header --> Only for layout debug purposes
    let path = NSBezierPath(rect: newRect)
    backgroundColor.setFill()
    path.fill()
    borderColor.setStroke()         // Set the stroke color
    path.stroke()                   // Fill the stroke or border of the rectangle

}

// MARK: UI Construction

func drawUI() {
    let viewFrame = self.frame       // with respect to the super class
    let viewBounds = self.bounds     // with respect to the view

    // MARK: Race Number Setup

    func addRaceNumberTitle(startingPositionX: CGFloat) {
        // This configures label for race Number

        let width:CGFloat = 60.0                    //Arbitrary at the moment
        let height:CGFloat = 40.0
        let leftPadding:CGFloat = 2.5              // The super view (frame)is the NSView in this case
        let topPadding:CGFloat = (viewBounds.height - height)/2
        let raceNumberTitleNSRect = NSRect(x: leftPadding + startingPositionX, y: viewBounds.height - height - topPadding, width: width, height: height)

        //Swift.print("The raceNumberTitleNSRect title NSRect is \(raceNumberTitleNSRect)")

        raceNumberTitle = NSTextField(frame: raceNumberTitleNSRect)
        raceNumberTitle.stringValue = "Race\nNumber"
        raceNumberTitle.maximumNumberOfLines = 2
        raceNumberTitle.isEditable = false
        raceNumberTitle.isBordered = false
        raceNumberTitle.alignment = .center
        raceNumberTitle.backgroundColor = .clear
        raceNumberTitle.sizeToFit()

        let updatedHeight = raceNumberTitle.frame.height
        let newUpdatedPadding = (viewBounds.height - updatedHeight) / 2
        let oldOriginX = raceNumberTitle.frame.origin.x
        let newOriginY = viewBounds.height - updatedHeight - newUpdatedPadding

        let newOrigin = NSPoint(x: oldOriginX, y: newOriginY)
        raceNumberTitle.setFrameOrigin(newOrigin)

        //addSubview(raceNumberTitle)      // Add to view
        raceImportHeaderStackView.addView(raceNumberTitle, in: .leading)
    }

    func addRaceNumberValue(startingPositionX: CGFloat) {
        // This configures value label for race number

        let width:CGFloat = 20.0                    //Arbitrary at the moment
        let height:CGFloat = 40.0
        let leftPadding:CGFloat = 5.0               // The super view (frame)is the NSView in this case
        let topPadding:CGFloat = (viewBounds.height - height)/2
        let raceNumberInRect = NSRect(x: startingPositionX + leftPadding, y: viewBounds.height - height - topPadding, width: width, height: height)

        Swift.print("The raceNumberInRect title NSRect is \(raceNumberInRect)")

        raceNumberValue = NSTextField(frame: raceNumberInRect)
        raceNumberValue.identifier = "raceNumber"
        raceNumberValue.placeholderString = "1"
        raceNumberValue.font = NSFont(name: "Impact", size: 20.0)
        raceNumberValue.maximumNumberOfLines = 1
        raceNumberValue.isEditable = false
        raceNumberValue.isBordered = true
        raceNumberValue.alignment = .center
        raceNumberValue.backgroundColor = .clear
        raceNumberValue.sizeToFit()


        let updatedHeight = raceNumberValue.frame.height
        let oldOriginX = raceNumberValue.frame.origin.x
        let newUpdatedPadding = (viewBounds.height - updatedHeight) / 2
        let newOriginY = viewBounds.height - updatedHeight - newUpdatedPadding

        let newOrigin = NSPoint(x: oldOriginX, y: newOriginY)
        raceNumberValue.setFrameOrigin(newOrigin)

        //addSubview(raceNumberValue)      // Add to view
        raceImportHeaderStackView.addView(raceNumberValue, in: .leading)
    }


    // MARK: Race Qualifications Table Setup

    func addRaceQualificationsTable(startingPositionX: CGFloat) {

        // Padding variables
        let leftPadding:CGFloat = 5.0
        let topPadding:CGFloat = 5.0

        // Table Properties
        let width:CGFloat = 650.0
        let height:CGFloat = 40

        let tableRect = CGRect(x: startingPositionX + leftPadding, y: viewBounds.height - height - topPadding, width: width, height: height)

        //let insetForTableView:CGFloat = 1.0
        //let scrollRect = CGRect(x: tableRect.origin.x-insetForTableView, y: tableRect.origin.y-insetForTableView, width: tableRect.width+2*insetForTableView, height: tableRect.height+2*insetForTableView)

        let tableNSSize = NSSize(width: tableRect.width, height: tableRect.height)
        let scrollNSRect = NSScrollView.frameSize(forContentSize: tableNSSize, horizontalScrollerClass: nil, verticalScrollerClass: nil, borderType: .bezelBorder, controlSize: .regular, scrollerStyle: .legacy)

        Swift.print("tableRect \(tableRect)")
        Swift.print("scrollNSRect \(scrollNSRect)")
        //Swift.print("scrollRect \(scrollRect)")

        let scrollViewOrigin:CGPoint = tableRect.origin
        let scrollViewNSSize:CGSize = scrollNSRect
        let scrollRect = NSRect(origin: scrollViewOrigin, size: scrollViewNSSize)

        Swift.print("scrollRect \(scrollRect)")

        let tableScrollView = NSScrollView(frame: scrollRect)
        raceQualificationsTableView = NSTableView(frame: tableRect)
        raceQualificationsTableView.identifier = "raceQualificationsTable"          // Setup identifier
        raceQualificationsTableView.rowHeight = 20.0

        Swift.print("instrinic size \(raceQualificationsTableView.intrinsicContentSize)")

        //Swift.print("tableScrollView contentsize \(tableScrollView.contentSize)")

        tableScrollView.documentView = raceQualificationsTableView
        tableScrollView.autoresizingMask = .viewNotSizable
        Swift.print("tableScroll content size \(tableScrollView.contentSize)")
        //self.addSubview(tableScrollView)

        raceImportHeaderStackView.addView(tableScrollView, in: .center)


    }

    func configureRaceQualificationsTable(showRaceNumberCol: Bool, showRaceCodeCol: Bool) {

        let headerAlignment = NSTextAlignment.center    // Easy way to change justification of headers

        // MARK: Race Number Column Options
        let raceNumberColumn = NSTableColumn(identifier: "raceNumberCol")
        raceNumberColumn.title = "Race"
        raceNumberColumn.minWidth = 40.0
        raceNumberColumn.width = 40.0
        raceNumberColumn.headerToolTip = "Race Number from the Imported Card"
        raceNumberColumn.headerCell.alignment = headerAlignment
        // Note: Word Race is always going to be wider than the race number value
        // So size to Fit is appropriate here.
        raceNumberColumn.sizeToFit()

        if showRaceNumberCol {
            // Option of not adding this to the table
            raceQualificationsTableView.addTableColumn(raceNumberColumn)
        }


        // MARK: Driver Column Options
        let breedColumn = NSTableColumn(identifier: "driverCol")
        driverColumn.title = "Driver"
        driverColumn.minWidth = 10
        driverColumn.headerToolTip = "Driver information"
        driverColumn.headerCell.alignment = headerAlignment
        driverColumn.sizeToFit()
        raceQualificationsTableView.addTableColumn(driverColumn)

        // MARK: Race Code Column Options
        let raceTypeCodeColumn = NSTableColumn(identifier: "raceTypeCodeCol")
        raceTypeCodeColumn.title = "Race Code"
        raceTypeCodeColumn.minWidth = 40
        raceTypeCodeColumn.headerToolTip = "Race Classification Code"
        raceTypeCodeColumn.headerCell.alignment = headerAlignment
        raceTypeCodeColumn.sizeToFit()

        if showRaceCodeCol {
            // Option of not adding to the table
            raceQualificationsTableView.addTableColumn(raceTypeCodeColumn)
        }

        // MARK: Race Type Code Description Options
        let raceTypeCodeDescColumn = NSTableColumn(identifier: "raceTypeCodeDescCol")
        raceTypeCodeDescColumn.title = "Race Desc"
        raceTypeCodeDescColumn.minWidth = 50
        raceTypeCodeDescColumn.width = 100
        raceTypeCodeDescColumn.headerToolTip = "Race Classification Full Description"
        raceTypeCodeDescColumn.headerCell.alignment = headerAlignment

        raceQualificationsTableView.addTableColumn(raceTypeCodeDescColumn)

        // MARK: Race Restriction Column Options
        let raceRestrictionColumn = NSTableColumn(identifier: "raceRestrictionCol")
        raceRestrictionColumn.title = "Restrictions"
        raceRestrictionColumn.minWidth = 50
        raceRestrictionColumn.width = 80
        raceRestrictionColumn.headerToolTip = "Race Restrictions"
        raceRestrictionColumn.headerCell.alignment = headerAlignment

        raceQualificationsTableView.addTableColumn(raceRestrictionColumn)

        // MARK: Sex Restriction Column Options
        let sexRestrictionColumn = NSTableColumn(identifier: "sexRestrictionCol")
        sexRestrictionColumn.title = "Sex"
        sexRestrictionColumn.minWidth = 100
        sexRestrictionColumn.width = 100
        sexRestrictionColumn.headerToolTip = "Sex Restrictions"
        sexRestrictionColumn.headerCell.alignment = headerAlignment

        raceQualificationsTableView.addTableColumn(sexRestrictionColumn)

        // MARK: Age Restriction Column Options
        let ageRestrictionColumn = NSTableColumn(identifier: "ageRestrictionCol")
        ageRestrictionColumn.title = "Age"
        ageRestrictionColumn.minWidth = 100
        ageRestrictionColumn.width = 100
        ageRestrictionColumn.headerToolTip = "Age Restrictions"
        ageRestrictionColumn.headerCell.alignment = headerAlignment

        raceQualificationsTableView.addTableColumn(ageRestrictionColumn)

        // MARK: Division Column Options
        let divisionColumn = NSTableColumn(identifier: "divisionCol")
        divisionColumn.title = "Division"
        divisionColumn.minWidth = 50

        let minDivisionColumnWidth = raceQualificationsTableView.frame.width - raceNumberColumn.width - driverColumn.width - raceTypeCodeColumn.width - raceTypeCodeDescColumn.width - raceRestrictionColumn.width - sexRestrictionColumn.width - ageRestrictionColumn.width

        // Calculate the available room for the division column
        if (showRaceCodeCol && showRaceNumberCol) {
            // This is the minimum case
            // No idea why we need the 25.0 manual adjustment
            divisionColumn.width = minDivisionColumnWidth - 25.0
        } else if (showRaceCodeCol && !showRaceNumberCol) {
            // Add back race type code
            // No idea why we need to manually adjust 53.5
            divisionColumn.width = minDivisionColumnWidth + raceTypeCodeColumn.width - 53.5
        } else if (!showRaceCodeCol && showRaceNumberCol) {
            // Add back race number col
            divisionColumn.width = minDivisionColumnWidth + raceNumberColumn.width
        } else {
            // Else it's the maximum space
            // This code was making the frame too large -- it was increasing the
            // the frame size of the column to 670.0  I put a manual reduction of
            // 20 to keep the frame size the same.  Not sure where this 20 is coming from.
            divisionColumn.width = minDivisionColumnWidth + raceNumberColumn.width + raceTypeCodeColumn.width - 20.0
        }

        //Swift.print("The division column width is \(divisionColumn.width)")

        divisionColumn.headerToolTip = "Division -- Unknown what this means"
        divisionColumn.headerCell.alignment = headerAlignment

        raceQualificationsTableView.addTableColumn(divisionColumn)

        //Swift.print("raceQualificationsTableView.frame.width is \( raceQualificationsTableView.frame.width)")
    }

    // MARK: Race Distance Surface Course Setup

    func addRaceDistanceSurfaceCourseTable(startingPositionX: CGFloat) {
        // Table Properties
        let width:CGFloat = 250.0
        let height:CGFloat = 40.0

        // Padding variables
        let leftPadding:CGFloat = 5.0

        let topPosition:CGFloat = (viewBounds.height - ((viewBounds.height - height)/2) - height)

        let tableRect = CGRect(x: leftPadding + startingPositionX, y: topPosition, width: width, height: height)

        let tableScrollView = NSScrollView(frame: tableRect)
        raceDistanceSurfaceCourseTableView = NSTableView(frame: tableRect)
        raceDistanceSurfaceCourseTableView.identifier = "raceDistanceSurfaceCourseTable"          // Setup identifier
        //raceDistanceSurfaceCourseTableView.rowHeight = 20.0
        raceDistanceSurfaceCourseTableView.intercellSpacing = NSSize(width: 1.0, height: 1.0)
        raceDistanceSurfaceCourseTableView.headerView = ImportRaceTableHeaders()

        tableScrollView.documentView = raceDistanceSurfaceCourseTableView
        //tableScrollView.hasVerticalScroller = false
        //tableScrollView.verticalScroller = nil          // Turn off vertical scrolling
        //tableScrollView.verticalScrollElasticity = .none

        //raceDistanceSurfaceCourseTableView = NSTableViewHeader

        //self.addSubview(tableScrollView)
        raceImportHeaderStackView.addView(raceDistanceSurfaceCourseTableView, in: .center)

    }



    // MARK: Construct the fields:

    //configureHeaderView()
    configureStackView()
    addRaceNumberTitle(startingPositionX: 0.0)              // Add the race number title
    addRaceNumberValue(startingPositionX: raceNumberTitle.frame.origin.x + raceNumberTitle.frame.width)   //Add the Race Number value text field

    addRaceQualificationsTable(startingPositionX: raceNumberValue.frame.origin.x + raceNumberValue.frame.width)
    configureRaceQualificationsTable(showRaceNumberCol: false, showRaceCodeCol: false)

}

// MARK: TableView Functions

func reloadTableViewData(identifier: String) {

    Swift.print("Manual reload of data for identifier \(identifier)")

    switch identifier {

        case "raceQualificationsTable":
            raceQualificationsTableView.reloadData()
        case "raceDistanceSurfaceCourseTable":
            raceDistanceSurfaceCourseTableView.reloadData()
        default:
            break
    }
}


// MARK: Delegate/DataSources Outlets for TableViews

// Race Qualification Table (the header table)
@IBOutlet weak var raceQualificationsDelegate: NSTableViewDelegate? {
    get {
        return raceQualificationsTableView.delegate
    }
    set {
        raceQualificationsTableView.delegate = newValue
    }
}

@IBOutlet weak var raceQualificationsDataSource: NSTableViewDataSource? {
    get {
        return raceQualificationsTableView.dataSource
    }
    set {
        raceQualificationsTableView.dataSource = newValue
    }
}

// Race Distance Surface Course
@IBOutlet weak var raceDistanceSurfaceCourseDelegate: NSTableViewDelegate? {
    get {
        return raceDistanceSurfaceCourseTableView.delegate
    }
    set {
        raceDistanceSurfaceCourseTableView.delegate = newValue
    }
}

@IBOutlet weak var raceDistanceSurfaceCourseDataSource: NSTableViewDataSource? {
    get {
        return raceDistanceSurfaceCourseTableView.dataSource
    }
    set {
        raceDistanceSurfaceCourseTableView.dataSource = newValue
    }
}

// MARK: Label Outlets
@IBOutlet var raceNumber:String? {
    get {
        return raceNumberValue.stringValue
    }
    set {
        raceNumberValue.stringValue = newValue!
    }
}

}

4

0 回答 0