【问题标题】:Custom NSButton not displaying as expected自定义 NSButton 未按预期显示
【发布时间】:2020-12-04 09:26:48
【问题描述】:

我正在尝试制作一个带有圆角、阴影和平面的自定义 NSButton。

这就是我想要的样子(HTML/CSS 演示 here):

(部分粗体标签和悬停效果不在范围内)

我正在使用此代码:

@IBDesignable class FlatButton: NSButton {
    @IBInspectable let backgroundColor: NSColor = .white

    override func draw(_ dirtyRect: NSRect) {
        // Set corner radius
        self.wantsLayer = true
        self.layer?.cornerRadius = 18
        self.layer?.borderWidth = 0
        self.layer?.borderColor = backgroundColor.cgColor
        layer?.backgroundColor = backgroundColor.cgColor
        frame.size.height = 32
        
        // Darken background color when highlighted
        if isHighlighted {
            layer?.backgroundColor =  backgroundColor.blended(
                withFraction: 0.2, of: .black
            )?.cgColor
        } else {
            layer?.backgroundColor = backgroundColor.cgColor
        }
        
        self.shadow = NSShadow()
        self.layer?.shadowOffset = CGSize(width: 8, height: 18)
        self.layer?.shadowColor = .black
        self.layer?.shadowRadius = 9
        self.layer?.masksToBounds = true
        self.layer?.shadowOpacity = 0.5
        
        // Super
        super.draw(dirtyRect)
    }
}

但这就是我使用该代码得到的结果:

(有边框)

(无边框)

【问题讨论】:

    标签: swift macos cocoa appkit nsbutton


    【解决方案1】:

    我可能对您的问题有些不理解,但是从 NSView 子类创建自定义按钮并使用鼠标事件似乎可以正常工作。悬停功能可以添加跟踪区域。

    import Cocoa
    
    var isSelected : Bool
    isSelected = false
    
    class CustomView: NSView {
    
     override func draw(_ rect: NSRect ) {
     super.draw(rect)
     let backgroundColor: NSColor = .white
    
     // Set corner radius
     self.wantsLayer = true
     self.layer?.cornerRadius = 18
     self.layer?.borderWidth = 0
     self.layer?.borderColor = backgroundColor.cgColor
     layer?.backgroundColor = backgroundColor.cgColor
     frame.size.height = 32
     
     // Shadow
     self.shadow = NSShadow()
     self.layer?.shadowOffset = CGSize(width: 8, height: 18)
     self.layer?.shadowColor = .black
     self.layer?.shadowRadius = 9
     self.layer?.masksToBounds = false
     self.layer?.shadowOpacity = 0.5
           
     // Darken background color when selected
      if isSelected {
       layer?.backgroundColor = backgroundColor.blended( withFraction: 0.2, of: .black )?.cgColor
       } else {
       layer?.backgroundColor = backgroundColor.cgColor
      }
    
     // Add attributed text
     let text: NSString = "Custom button test"
     let font = NSFont.systemFont(ofSize: 22)
     let attr: [NSAttributedString.Key: Any] = [.font: font,.foregroundColor: NSColor.red]
     text.draw(at:NSMakePoint(50,5), withAttributes:attr)
    }
    
    override func mouseDown(with event: NSEvent) {
     print("mouse down in button.")
     isSelected = true
     self.needsDisplay = true 
    }
    
    override func mouseUp(with event: NSEvent) {
     print("mouse up in button.")
     isSelected = false
     self.needsDisplay = true
    }
    
    }
    
    class ApplicationDelegate: NSObject, NSApplicationDelegate {
    
    func buildMenu() {
    let mainMenu = NSMenu()
     NSApp.mainMenu = mainMenu
     // **** App menu **** //
     let appMenuItem = NSMenuItem()
     mainMenu.addItem(appMenuItem)
     let appMenu = NSMenu()
     appMenuItem.submenu = appMenu
     appMenu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q") 
    }
    
    func buildWnd() {
     let _wndW : CGFloat = 500
     let _wndH : CGFloat = 400
    
    let window = NSWindow(contentRect: NSMakeRect( 0, 0, _wndW, _wndH ), styleMask:[.titled, .closable, .miniaturizable, .resizable], backing: .buffered, defer: false)
     window.center()
     window.title = "Swift Test Window"
     window.makeKeyAndOrderFront(window)
    
    // **** Custom view **** //
     let view = CustomView( frame:NSMakeRect(60, _wndH - 160, 300, 32)) 
     view.autoresizingMask = [.width, .height]      
     window.contentView!.addSubview (view)
        
    // **** Quit btn **** //
     let quitBtn = NSButton (frame:NSMakeRect( _wndW - 50, 10, 40, 40 ))
     quitBtn.bezelStyle = .circular
     quitBtn.autoresizingMask = [.minXMargin,.maxYMargin]
     quitBtn.title = "Q"
     quitBtn.action = #selector(NSApplication.terminate)
     window.contentView!.addSubview(quitBtn)
    }
     
    func applicationDidFinishLaunching(_ notification: Notification) {
     buildMenu()
     buildWnd()
    }
    
    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
     return true
    }
    
    }
    let applicationDelegate = ApplicationDelegate()
    
    let application = NSApplication.shared
    application.setActivationPolicy(NSApplication.ActivationPolicy.regular)
    application.delegate = applicationDelegate
    application.activate(ignoringOtherApps:true)
    application.run()
    
    
    

    【讨论】:

    • 感谢您的帮助。我如何添加阴影和文字呢?
    • 我们应该能够为文本和阴影使用属性字符串。我无法让阴影在你的帖子中起作用,所以我不确定它应该是什么样子。如果您向我展示您想要的效果,我会尝试添加它(也许我错过了它)。文本示例显示在我编辑的答案中。
    • 如果你将 .maskToBounds 设置为 false,阴影就会出现。请参阅第二次编辑以回答。
    • 谢谢,我以前没有像这样使用过 NSViews。如何将设计部分的点击操作链接到视图控制器?我有几个按钮具有不同的点击操作
    • 它只允许我向视图控制器添加一个新的出口。 NSView 没有“已发送操作”部分
    【解决方案2】:

    为了减少绘制函数的工作量,可以在初始化窗口时完成很多视图自定义:

    import Cocoa
    
    var isSelected : Bool
    isSelected = false
    
    class CustomView: NSView {
    
     override func draw(_ rect: NSRect ) {
     super.draw(rect)
    
     let backgroundColor: NSColor = .white
           
     // Darken background color when selected
     if isSelected {
      layer?.backgroundColor = backgroundColor.blended( withFraction: 0.2, of: .black )?.cgColor
      } else {
      layer?.backgroundColor = backgroundColor.cgColor
     }
    
     // Add attributed text
     let text: NSString = "Custom button text"
     let font = NSFont.systemFont(ofSize: 22)
     let attr: [NSAttributedString.Key: Any] = [.font: font,.foregroundColor: NSColor.red]
     text.draw(at:NSMakePoint(50,5), withAttributes:attr)
    }
    
    func myBtnAction( ) {
      NSSound.beep()
    }
    
    override func mouseDown(with event: NSEvent) {
     print("mouse down in button.")
     isSelected = true
     self.needsDisplay = true 
    }
    
    override func mouseUp(with event: NSEvent) {
     print("mouse up in button.")
     isSelected = false
     myBtnAction()
     self.needsDisplay = true
    }
    
    }
    
    class ApplicationDelegate: NSObject, NSApplicationDelegate {
    
    func buildMenu() {
    let mainMenu = NSMenu()
     NSApp.mainMenu = mainMenu
     // **** App menu **** //
     let appMenuItem = NSMenuItem()
     mainMenu.addItem(appMenuItem)
     let appMenu = NSMenu()
     appMenuItem.submenu = appMenu
     appMenu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q") 
    }
    
    func buildWnd() {
     let _wndW : CGFloat = 500
     let _wndH : CGFloat = 400
    
    let window = NSWindow(contentRect: NSMakeRect( 0, 0, _wndW, _wndH ), styleMask:[.titled, .closable, .miniaturizable, .resizable], backing: .buffered, defer: false)
     window.center()
     window.title = "Swift Test Window"
     window.makeKeyAndOrderFront(window)
    
    // **** Custom view **** //
     let view = CustomView( frame:NSMakeRect(60, _wndH - 160, 300, 32)) 
     view.autoresizingMask = [.maxXMargin,.minYMargin]       
     view.wantsLayer = true
     view.layer?.borderColor = NSColor.blue.cgColor
     view.layer?.cornerRadius = 18
     view.layer?.borderWidth = 2
     view.layer?.shadowColor = NSColor.black.cgColor
     view.layer?.shadowRadius = 9
     view.layer?.shadowOpacity = 0.5
     view.layer?.masksToBounds = false
     window.contentView!.addSubview (view)
    
    // **** Quit btn **** //
     let quitBtn = NSButton (frame:NSMakeRect( _wndW - 50, 10, 40, 40 ))
     quitBtn.bezelStyle = .circular
     quitBtn.autoresizingMask = [.minXMargin,.maxYMargin]
     quitBtn.title = "Q"
     quitBtn.action = #selector(NSApplication.terminate)
     window.contentView!.addSubview(quitBtn)
    }
     
    func applicationDidFinishLaunching(_ notification: Notification) {
     buildMenu()
     buildWnd()
    }
    
    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
     return true
    }
    
    }
    let applicationDelegate = ApplicationDelegate()
    
    let application = NSApplication.shared
    application.setActivationPolicy(.regular)
    application.delegate = applicationDelegate
    application.activate(ignoringOtherApps:true)
    application.run()
    
    
    

    【讨论】:

    • 要连接自定义视图按钮的操作,请参阅已编辑的第二个答案。向视图类添加一个名为“myBtnAction”的新函数,并在释放鼠标时调用它。
    • 如果我有几个 NSViews 怎么办?我怎样才能确保每个 NSView 都有自己的操作?在 NSButton 中添加这些样式不是更好吗?
    • 可能必须为每个按钮使用单独的类,或者根据您拥有的按钮数量使用一组视图。您还可以将图像或属性文本添加到 NSButtons,但我认为您无法像自定义按钮那样获得那些漂亮的圆角;您的电话。
    【解决方案3】:

    以下是在 Marc Marset 描述的代码中创建 NSButton 的技术的修改(上面引用过),应该满足您的要求。 'customNSButton' 功能将允许创建多个按钮,并通过 Target-Action 为每个按钮设置个性化操作。计时器用于“眨眼”背景颜色,并在按钮下方创建阴影。标题是属性文本。

    import Cocoa
    
    class AppDelegate: NSObject, NSApplicationDelegate {
    var window:NSWindow!
    var button:NSButton!
    
    @objc func resetBkgrndColor(_ sender:AnyObject) {
     let backgroundColor: NSColor = .white
     button.layer?.backgroundColor = backgroundColor.cgColor
    }
    
    @objc func myBtnAction(_ sender:AnyObject ) {
     let backgroundColor: NSColor = .white
     button.layer?.backgroundColor = backgroundColor.blended( withFraction: 0.2, of: .black )?.cgColor
     Timer.scheduledTimer(timeInterval:0.25, target:self, selector:#selector(self.resetBkgrndColor(_:)), userInfo:nil, repeats:false)
     NSSound.beep()
    }
    
    func customNSButton( bkgrndColor: NSColor?, borderColor: NSColor?, borderWidth: CGFloat?, cornerRadius: CGFloat? ) -> NSButton {        
     button = NSButton()
        
     button.wantsLayer = true
     button.layer?.backgroundColor = bkgrndColor?.cgColor ?? .clear
     button.layer?.masksToBounds = true
     button.layer!.cornerRadius = cornerRadius ?? 0
     button.layer!.borderColor = borderColor?.cgColor
     button.layer!.borderWidth = borderWidth ?? 0
    
     button.layer?.shadowOffset = CGSize(width: 8, height: 18)
     button.layer?.shadowColor = .black
     button.layer?.shadowRadius = 9
     button.layer?.masksToBounds = false
     button.layer?.shadowOpacity = 0.5
          
     return button
    }
    
    func buildMenu() {
     let mainMenu = NSMenu()
     NSApp.mainMenu = mainMenu
     // **** App menu **** //
     let appMenuItem = NSMenuItem()
     mainMenu.addItem(appMenuItem)
     let appMenu = NSMenu()
     appMenuItem.submenu = appMenu
     appMenu.addItem(withTitle: "Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q") 
    }
        
    func buildWnd() {
        
    let _wndW : CGFloat = 400
    let _wndH : CGFloat = 300
    
     window = NSWindow(contentRect:NSMakeRect(0,0,_wndW,_wndH),styleMask:[.titled, .closable, .miniaturizable, .resizable], backing:.buffered, defer:false)
     window.center()
     window.title = "Swift Test Window"
     window.makeKeyAndOrderFront(window)
    
    // **** Custom Button **** //
     let myBtn = customNSButton(bkgrndColor:.white, borderColor:.black, borderWidth:2, cornerRadius:22)
     myBtn.frame = NSMakeRect(60,120,300,40)
     myBtn.bezelStyle = .roundRect
     myBtn.isBordered = false
     myBtn.attributedTitle = NSAttributedString(string: "Btn Text", attributes: [.foregroundColor: NSColor.red, .font:NSFont(name:"Menlo Bold", size:28)])
     myBtn.action = #selector(self.myBtnAction(_:))
     window.contentView!.addSubview(myBtn)
    
    // **** Quit btn **** //
     let quitBtn = NSButton (frame:NSMakeRect( _wndW - 50, 10, 40, 40 ))
     quitBtn.bezelStyle = .circular
     quitBtn.autoresizingMask = [.minXMargin,.maxYMargin]
     quitBtn.title = "Q"
     quitBtn.action = #selector(NSApplication.terminate)
     window.contentView!.addSubview(quitBtn)
    
    }
     
    func applicationDidFinishLaunching(_ notification: Notification) {
     buildMenu()
     buildWnd()
    }
    
    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
     return true
    }
    
    }
    let appDelegate = AppDelegate()
    
    // **** main.swift **** //
    let app = NSApplication.shared
    app.delegate = appDelegate
    app.setActivationPolicy(.regular)
    app.activate(ignoringOtherApps:true)
    app.run()
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-09
      • 1970-01-01
      • 2022-11-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多