使用Cocoa for OS X,Xcode 9.3,Swift 4.1复制视图问题

时间:2018-05-04 00:17:10

标签: swift xcode macos cocoa

我是编程并尝试使用Xcode 9.3和Swift 4.1学习Cocoa for Mac OS的新手。我已经完成了有关C ++和Objective-C的书籍。现在我正在阅读Big Nerd Ranch的书,Mac OS的Cocoa Programming,第5版,它使用Swift 2.0(我自己)。尝试解决从Swift 2.0到4.1的差异一直存在挑战,我不知道这个问题是否是其中的一部分。

总结一下这个练习,我创造了一个模具。部分练习是通过双击模具使模具更改其数量。我这样做了,而且很有效。现在我需要复制骰子两次,总共三个骰子。

我按照步骤复制骰子(突出显示dieView和命令-D),现在我有三个骰子。每个视图都接受First Responder,每个视图随后接受键盘输入以更改模具上显示的数字。我可以用鼠标选择关键窗口,高亮显示哪个窗口处于活动状态。但是,双击“滚动”骰子只适用于原始骰子,而不是其他两个骰子。当它们完全重复时怎么会这样?因为它们被复制了,我怎么能在其他两个骰子中双击才能使用相同的代码呢?

我也尝试将原始模具移动到第三个位置,移动两个新骰子,现在它仍然是第一个位置的模具,双击而不是最后两个。当我将骰子移动到一个堆叠的位置,一个在另一个之上,在另一个之上时,我首先将它们放在窗口的左侧,并且双击对它们中的任何一个都不起作用。我调整了窗口的大小,使它们全部堆叠在左侧,双击仅适用于底部模具。

我知道这与骰子所在的自定义视图窗口有关,但是不应该鼠标点击在该窗口的任何位置注册?显然点击正在注册,因为我可以更改每个骰子的关键窗口。这只是双击功能无法正常工作。

这是我的dieView代码:

import Cocoa

@IBDesignable class DieView: NSView {

    var intValue: Int? = 1 {
        didSet {
            needsDisplay = true
        }
    }

    var pressed: Bool = false {
        didSet {
            needsDisplay = true
        }
    }

    var dieShape = NSBezierPath()

    override var intrinsicContentSize: NSSize {
        return NSSize(width: 20, height: 20)
    }

    override func draw(_ dirtyRect: NSRect) {
        let backgroundColor = NSColor.lightGray
        backgroundColor.set()
        NSBezierPath.fill(bounds)
        drawDieWithSize(size: bounds.size)
    }

    func metricsForSize(size: CGSize) -> (edgeLength: CGFloat, dieFrame: CGRect) {
        let edgeLength = min(size.width, size.height)
        let padding = edgeLength/10.0
        let drawingBounds = CGRect(x: 0, y: 0, width: edgeLength, height: edgeLength)
        var dieFrame = drawingBounds.insetBy(dx: padding, dy: padding)
        if pressed {
            dieFrame = dieFrame.offsetBy(dx: 0, dy: -edgeLength/40)
        }
        return (edgeLength, dieFrame)
    }

    func drawDieWithSize(size: CGSize) {
        if let intValue = intValue {
            let (edgeLength, dieFrame) = metricsForSize(size: size)
            let cornerRadius:  CGFloat = edgeLength/5.0
            let dotRadius = edgeLength/12.0
            let dotFrame = dieFrame.insetBy(dx: dotRadius * 2.5, dy: dotRadius * 2.5)

            // The glint must be within the dot.
            let glintFrame = dotFrame

            NSGraphicsContext.saveGraphicsState()

            let shadow = NSShadow()
            shadow.shadowOffset = NSSize(width: 0, height: -1)
            //shadow.shadowBlurRadius = edgeLength/20
            shadow.shadowBlurRadius = (pressed ? edgeLength/100 : edgeLength/20)
            shadow.set()

            // Draw the rounded shape of the die profile:
            // Challenge use color Gradient - commented portions are used to make white die and were removed to make code more readable in this post
            let gradient = NSGradient(starting: NSColor.red, ending: NSColor.blue)
            dieShape =
                NSBezierPath(roundedRect: dieFrame, xRadius: cornerRadius, yRadius: cornerRadius)
            gradient?.draw(in: dieShape, angle: 1.0)

            // Challlenge - use stroke() to add a border the die
            NSColor.black.set()
            dieShape.lineWidth = 4
            dieShape.stroke()

            NSGraphicsContext.restoreGraphicsState()
            // Shadow will not apply to subequent drawing commands

            // ready to draw the dots.
            // Nested Function to make drawing dots cleaner:
            func drawDot(u: CGFloat, v: CGFloat) {
                let dotOrigin = CGPoint(x: dotFrame.minX + dotFrame.width * u,
                                        y: dotFrame.minY + dotFrame.height * v)
                let dotRect =
                    CGRect(origin: dotOrigin, size: CGSize.zero).insetBy(dx: -dotRadius, dy: -dotRadius)
                // The dots will be black:
                NSColor.black.set()
                NSBezierPath(ovalIn: dotRect).fill()
                }


            // nested function to draw a glint in each dot
            func drawGlint(u: CGFloat, v: CGFloat) {
                let glintOrigin = CGPoint(x: glintFrame.minX + glintFrame.width * u,
                                          y: glintFrame.minY + glintFrame.height * v)
                let glintRect =
                    CGRect(origin: glintOrigin,
                           size: CGSize(width: 3.5, height: 3.5)).insetBy(dx: -0.5, dy: -0.5)

                // Glints will be white
                NSColor.white.set()
                NSBezierPath(rect: glintRect).fill()
            }

            // If intVlaue is in range...
            if intValue >= 1 && intValue <= 6 {
                // Draw the dots:
                if intValue == 1 || intValue == 3 || intValue == 5 {
                    drawDot(u: 0.5, v: 0.5)     // Center dot
                    drawGlint(u: 0.55, v: 0.55)
                }
                if intValue >= 2 && intValue <= 6 {
                    drawDot(u: 0, v: 1)     // upper left
                    drawGlint(u: 0.05, v: 1.05)
                    drawDot(u: 1, v: 0)     // Lower right
                    drawGlint(u: 1.05, v: 0.05)
                }
                if intValue >= 4 && intValue <= 6 {
                    drawDot(u: 1, v: 1)     // Upper right
                    drawGlint(u: 1.05, v: 1.05)
                    drawDot(u: 0, v: 0)     // lower left
                    drawGlint(u: 0.05, v: 0.05)
                }
                if intValue == 6 {
                    drawDot(u: 0, v: 0.5)   // Mid left/right
                    drawGlint(u: 0.05, v: 0.55)
                    drawDot(u: 1, v: 0.5)
                    drawGlint(u: 1.05, v: 0.55)
                }
            } else {
                let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
                paraStyle.alignment = .center
                let font = NSFont.systemFont(ofSize: edgeLength * 0.5)
                let attrs = [NSAttributedStringKey.foregroundColor: NSColor.black,
                    NSAttributedStringKey.font : font,
                    NSAttributedStringKey.paragraphStyle: paraStyle ]
                let string = "\(intValue)" as NSString
                string.drawCentered(in: dieFrame, attributes: attrs)

            }
        }
    }

    func randomize() {
        intValue = Int(arc4random_uniform(5)) + 1
    }

    // MARK: - Mouse Events

    override func mouseDown(with event: NSEvent) {
        if dieShape.contains(event.locationInWindow) {
            Swift.print("mouseDown CLICKCOUNT: \(event.clickCount)")
            let dieFrame = metricsForSize(size: bounds.size).dieFrame
            let pointInView = convert(event.locationInWindow, from: nil)
            pressed = dieFrame.contains(pointInView)
        }
    }

    override func mouseDragged(with event: NSEvent) {
        Swift.print("mouseDragged")
    }

    override func mouseUp(with event: NSEvent) {
        if dieShape.contains(event.locationInWindow) {
            Swift.print("mouseUp clickCount: \(event.clickCount)")
            if event.clickCount == 2 {
                randomize()
            }
            pressed = false
        }
    }

    // MARK: - First Responder

    override func drawFocusRingMask() {
        NSBezierPath.fill(bounds)
    }

    override var focusRingMaskBounds: NSRect {
        return bounds
    }

    override var acceptsFirstResponder: Bool { return true }

    override func becomeFirstResponder() -> Bool {
        return true
    }

    override func resignFirstResponder() -> Bool {
        return true
    }

    // MARK: Ketboard Events

    override func keyDown(with event: NSEvent) {
        interpretKeyEvents([event])
    }

    override func insertText(_ insertString: Any) {
        let text = insertString as! String
        if let number = Int(text) {
            intValue = number
        }
    }

    override func insertTab(_ sender: Any?) {
        window?.selectNextKeyView(sender)
    }

    override func insertBacktab(_ sender: Any?) {
        window?.selectPreviousKeyView(sender)
    }
}

1 个答案:

答案 0 :(得分:0)

快速浏览后,我的猜测是if dieShape.contains(event.locationInWindow)中的问题。

diaShape位于本地(视图)坐标中。但是event.locationInWindow在窗口坐标中。在测试生命值之前,您需要先将窗口坐标转换为本地坐标。

请参阅locationInWindow的文档:

let eventLocation = event.locationInWindow
let localPoint = self.convert(eventLocation, from: nil)
if dieShape.contains(localPoint)
   ...

如果您的视图接近窗口的原点,则窗口和视图坐标之间的差异足够小,可能会起作用,但距离较远的区域不会。