美文网首页
NSButton仿UIButton功能

NSButton仿UIButton功能

作者: Jesscia_Liu | 来源:发表于2022-04-08 16:59 被阅读0次

    swift方法参考

    • CustomButton.swift
    import Cocoa
    
    @IBDesignable
    open class CustomButton: NSButton {
        private let titleLayer = CATextLayer()
        private var isMouseDown = false
    
        public static func circularButton(title: String, radius: Double, center: CGPoint) -> CustomButton {
            with(CustomButton()) {
                $0.title = title
                $0.frame = CGRect(x: center.x - radius, y: center.y - radius, width: radius * 2, height: radius * 2)
                $0.cornerRadius = radius
                $0.font = .systemFont(ofSize: radius * 2 / 3)
            }
        }
    
        override open var wantsUpdateLayer: Bool { true }
        
        @IBInspectable public var isHandCursor: Bool = false
        
        @IBInspectable override public var title: String {
            didSet {
                setTitle()
            }
        }
    
        @IBInspectable public var textColor: NSColor = .labelColor {
            didSet {
                titleLayer.foregroundColor = textColor.cgColor
            }
        }
    
        @IBInspectable public var activeTextColor: NSColor = .labelColor {
            didSet {
                if state == .on {
                    titleLayer.foregroundColor = textColor.cgColor
                }
            }
        }
    
        @IBInspectable public var cornerRadius: Double = 0 {
            didSet {
                layer?.cornerRadius = cornerRadius
            }
        }
    
        @IBInspectable public var hasContinuousCorners: Bool = true {
            didSet {
                if #available(macOS 10.15, *) {
                    layer?.cornerCurve = hasContinuousCorners ? .continuous : .circular
                }
            }
        }
    
        @IBInspectable public var borderWidth: Double = 0 {
            didSet {
                layer?.borderWidth = borderWidth
            }
        }
    
        @IBInspectable public var borderColor: NSColor = .clear {
            didSet {
                layer?.borderColor = borderColor.cgColor
            }
        }
    
        @IBInspectable public var activeBorderColor: NSColor = .clear {
            didSet {
                if state == .on {
                    layer?.borderColor = activeBorderColor.cgColor
                }
            }
        }
    
        @IBInspectable public var backgroundColor: NSColor = .clear {
            didSet {
                layer?.backgroundColor = backgroundColor.cgColor
            }
        }
    
        @IBInspectable public var activeBackgroundColor: NSColor = .clear {
            didSet {
                if state == .on {
                    layer?.backgroundColor = activeBackgroundColor.cgColor
                }
            }
        }
    
        @IBInspectable public var shadowRadius: Double = 0 {
            didSet {
                layer?.shadowRadius = shadowRadius
            }
        }
    
        @IBInspectable public var activeShadowRadius: Double = -1 {
            didSet {
                if state == .on {
                    layer?.shadowRadius = activeShadowRadius
                }
            }
        }
    
        @IBInspectable public var shadowOpacity: Double = 0 {
            didSet {
                layer?.shadowOpacity = Float(shadowOpacity)
            }
        }
    
        @IBInspectable public var activeShadowOpacity: Double = -1 {
            didSet {
                if state == .on {
                    layer?.shadowOpacity = Float(activeShadowOpacity)
                }
            }
        }
    
        @IBInspectable public var shadowColor: NSColor = .clear {
            didSet {
                layer?.shadowColor = shadowColor.cgColor
            }
        }
    
        @IBInspectable public var activeShadowColor: NSColor? {
            didSet {
                if state == .on, let activeShadowColor = activeShadowColor {
                    layer?.shadowColor = activeShadowColor.cgColor
                }
            }
        }
    
        override public var font: NSFont? {
            didSet {
                setTitle()
            }
        }
    
        override public var isEnabled: Bool {
            didSet {
                alphaValue = isEnabled ? 1 : 0.6
            }
        }
    
        public convenience init() {
            self.init(frame: .zero)
        }
    
        public required init?(coder: NSCoder) {
            super.init(coder: coder)
            setup()
        }
    
        override init(frame: CGRect) {
            super.init(frame: frame)
            setup()
        }
    
        // Ensure the button doesn't draw its default contents.
        override open func draw(_ dirtyRect: CGRect) {}
        override open func drawFocusRingMask() {}
    
        override open func layout() {
            super.layout()
            positionTitle()
        }
    
        override open func viewDidChangeBackingProperties() {
            super.viewDidChangeBackingProperties()
    
            if let scale = window?.backingScaleFactor {
                layer?.contentsScale = scale
                titleLayer.contentsScale = scale
            }
        }
    
        private lazy var trackingArea = TrackingArea(
            for: self,
            options: [
                .mouseEnteredAndExited,
                .activeInActiveApp
            ]
        )
    
        override open func updateTrackingAreas() {
            super.updateTrackingAreas()
            trackingArea.update()
        }
    
        private func setup() {
            let isOn = state == .on
    
            wantsLayer = true
    
            layer?.masksToBounds = false
    
            layer?.cornerRadius = cornerRadius
            layer?.borderWidth = borderWidth
            layer?.shadowRadius = isOn && activeShadowRadius != -1 ? activeShadowRadius : shadowRadius
            layer?.shadowOpacity = Float(isOn && activeShadowOpacity != -1 ? activeShadowOpacity : shadowOpacity)
            layer?.backgroundColor = isOn ? activeBackgroundColor.cgColor : backgroundColor.cgColor
            layer?.borderColor = isOn ? activeBorderColor.cgColor : borderColor.cgColor
            layer?.shadowColor = isOn ? (activeShadowColor?.cgColor ?? shadowColor.cgColor) : shadowColor.cgColor
    
            if #available(macOS 10.15, *) {
                layer?.cornerCurve = hasContinuousCorners ? .continuous : .circular
            }
    
            titleLayer.alignmentMode = .center
            titleLayer.contentsScale = window?.backingScaleFactor ?? 2
            titleLayer.foregroundColor = isOn ? activeTextColor.cgColor : textColor.cgColor
            layer?.addSublayer(titleLayer)
            setTitle()
    
            needsDisplay = true
        }
    
        public typealias ColorGenerator = () -> NSColor
    
        private var colorGenerators = [KeyPath<CustomButton, NSColor>: ColorGenerator]()
    
        /**
        Gets or sets the color generation closure for the provided key path.
    
        - Parameter keyPath: The key path that specifies the color related property.
        */
        public subscript(colorGenerator keyPath: KeyPath<CustomButton, NSColor>) -> ColorGenerator? {
            get { colorGenerators[keyPath] }
            set {
                colorGenerators[keyPath] = newValue
            }
        }
    
        private func color(for keyPath: KeyPath<CustomButton, NSColor>) -> NSColor {
            colorGenerators[keyPath]?() ?? self[keyPath: keyPath]
        }
    
        override open func updateLayer() {
            animateColor()
        }
    
        private func setTitle() {
            titleLayer.string = title
    
            if let font = font {
                titleLayer.font = font
                titleLayer.fontSize = font.pointSize
            }
    
            needsLayout = true
        }
    
        private func positionTitle() {
            let titleSize = title.size(withAttributes: [.font: font as Any])
            titleLayer.frame = titleSize.centered(in: bounds).roundedOrigin()
        }
    
        private func animateColor() {
            let isOn = state == .on
            let duration = isOn ? 0.2 : 0.1
            let backgroundColor = isOn ? color(for: \.activeBackgroundColor) : color(for: \.backgroundColor)
            let textColor = isOn ? color(for: \.activeTextColor) : color(for: \.textColor)
            let borderColor = isOn ? color(for: \.activeBorderColor) : color(for: \.borderColor)
            let shadowColor = isOn ? (activeShadowColor ?? color(for: \.shadowColor)) : color(for: \.shadowColor)
    
            layer?.animate(\.backgroundColor, to: backgroundColor, duration: duration)
            layer?.animate(\.borderColor, to: borderColor, duration: duration)
            layer?.animate(\.shadowColor, to: shadowColor, duration: duration)
            titleLayer.animate(\.foregroundColor, to: textColor, duration: duration)
        }
    
        private func toggleState() {
            state = state == .off ? .on : .off
            animateColor()
        }
    
        override open func hitTest(_ point: CGPoint) -> NSView? {
            isEnabled ? super.hitTest(point) : nil
        }
    
        override open func mouseDown(with event: NSEvent) {
            isMouseDown = true
            toggleState()
        }
    
        override open func mouseEntered(with event: NSEvent) {
            if isHandCursor {
                NSCursor.pointingHand.set()
            }
            if isMouseDown {
                toggleState()
            }
        }
    
        override open func mouseExited(with event: NSEvent) {
            if isHandCursor {
                NSCursor.arrow.set()
            }
            if isMouseDown {
                toggleState()
                isMouseDown = false
            }
        }
    
        override open func mouseUp(with event: NSEvent) {
            if isMouseDown {
                isMouseDown = false
                toggleState()
                _ = target?.perform(action, with: self)
            }
        }
    }
    
    extension CustomButton: NSViewLayerContentScaleDelegate {
        public func layer(_ layer: CALayer, shouldInheritContentsScale newScale: CGFloat, from window: NSWindow) -> Bool { true }
    }
    
    
    • Utilities.swift
    
    import Cocoa
    
    /**
    Convenience function for initializing an object and modifying its properties.
    
    \```
    let label = with(NSTextField()) {
        $0.stringValue = "Foo"
        $0.textColor = .systemBlue
        view.addSubview($0)
    }
    \```
    */
    @discardableResult
    func with<T>(_ item: T, update: (inout T) throws -> Void) rethrows -> T {
        var this = item
        try update(&this)
        return this
    }
    
    
    /**
    Convenience class for adding a tracking area to a view.
    
    \```
    final class HoverView: NSView {
        private lazy var trackingArea = TrackingArea(
            for: self,
            options: [
                .mouseEnteredAndExited,
                .activeInActiveApp
            ]
        )
    
        override func updateTrackingAreas() {
            super.updateTrackingAreas()
            trackingArea.update()
        }
    }
    \```
    */
    final class TrackingArea {
        private weak var view: NSView?
        private let rect: CGRect
        private let options: NSTrackingArea.Options
        private weak var trackingArea: NSTrackingArea?
    
        /**
        - Parameters:
            - view: The view to add tracking to.
            - rect: The area inside the view to track. Defaults to the whole view (`view.bounds`).
        */
        init(
            for view: NSView,
            rect: CGRect? = nil,
            options: NSTrackingArea.Options = []
        ) {
            self.view = view
            self.rect = rect ?? view.bounds
            self.options = options
        }
    
        /**
        Updates the tracking area.
        - Note: This should be called in your `NSView#updateTrackingAreas()` method.
        */
        func update() {
            if let oldTrackingArea = trackingArea {
                view?.removeTrackingArea(oldTrackingArea)
            }
    
            let newTrackingArea = NSTrackingArea(
                rect: rect,
                options: [
                    .mouseEnteredAndExited,
                    .activeInActiveApp
                ],
                owner: view,
                userInfo: nil
            )
    
            view?.addTrackingArea(newTrackingArea)
            trackingArea = newTrackingArea
        }
    }
    
    
    final class AnimationDelegate: NSObject, CAAnimationDelegate {
        var didStopHandler: ((Bool) -> Void)?
    
        func animationDidStop(_ animation: CAAnimation, finished flag: Bool) {
            didStopHandler?(flag)
        }
    }
    
    
    protocol LayerColorAnimation: AnyObject {}
    extension CALayer: LayerColorAnimation {}
    
    extension LayerColorAnimation where Self: CALayer {
        /**
        Animate colors.
        */
        func animate(_ keyPath: ReferenceWritableKeyPath<Self, CGColor?>, to color: CGColor, duration: Double) {
            let animation = CABasicAnimation(keyPath: keyPath.toString)
            animation.fromValue = self[keyPath: keyPath]
            animation.toValue = color
            animation.duration = duration
            animation.fillMode = .forwards
            animation.isRemovedOnCompletion = false
    
            add(animation, forKeyPath: keyPath) { [weak self] _ in
                self?[keyPath: keyPath] = color
            }
        }
    
        /**
        Animate colors.
        */
        func animate(_ keyPath: ReferenceWritableKeyPath<Self, CGColor?>, to color: NSColor, duration: Double) {
            animate(keyPath, to: color.cgColor, duration: duration)
        }
    
        /**
        Add color animation.
        */
        func add(_ animation: CAAnimation, forKeyPath keyPath: ReferenceWritableKeyPath<Self, CGColor?>, completion: @escaping ((Bool) -> Void)) {
            let animationDelegate = AnimationDelegate()
            animationDelegate.didStopHandler = completion
            animation.delegate = animationDelegate
            add(animation, forKey: keyPath.toString)
        }
    }
    
    
    extension CGPoint {
        func rounded(_ rule: FloatingPointRoundingRule = .toNearestOrAwayFromZero) -> Self {
            Self(x: x.rounded(rule), y: y.rounded(rule))
        }
    }
    
    
    extension CGRect {
        func roundedOrigin(_ rule: FloatingPointRoundingRule = .toNearestOrAwayFromZero) -> Self {
            var rect = self
            rect.origin = rect.origin.rounded(rule)
            return rect
        }
    }
    
    
    extension CGSize {
        /**
        Returns a CGRect with `self` centered in it.
        */
        func centered(in rect: CGRect) -> CGRect {
            CGRect(
                x: (rect.width - width) / 2,
                y: (rect.height - height) / 2,
                width: width,
                height: height
            )
        }
    }
    
    
    extension KeyPath where Root: NSObject {
        /**
        Get the string version of the key path when the root is an `NSObject`.
        */
        var toString: String {
            NSExpression(forKeyPath: self).keyPath
        }
    }
    
    

    OC方法参考

    《macOS开发》自定义控件之NSButton

    相关文章

      网友评论

          本文标题:NSButton仿UIButton功能

          本文链接:https://www.haomeiwen.com/subject/kzmysrtx.html