将汉堡包按钮添加到导航栏

Add Hamburger button to navigation bar

我正在尝试将此 Hamburger button 添加为 Swift 中的导航栏按钮。 下面是我试过的代码...

@IBOutlet var button: HamburgerButton!        
self.button.addTarget(self, action: "toggle:", forControlEvents:.TouchUpInside)
self.button.transform = CGAffineTransformMakeScale(2.0, 2.0)

self.navigationItem.leftBarButtonItem = button; //ERROR: HamburgerButton is not convertible to UIBarButtonItem
self.navigationItem.leftBarButtonItem?.image = UIImage(named: "menu.png");

我什至尝试像 self.navigationItem.leftBarButtonItem = button as UIBarButtonItem; 一样将其转换为 UIBarButtonItem,但仍然面临同样的错误。

编辑

我对 HamburgerButton 做了一些改动 class,但几乎没有错误,我无法调试到底是什么问题。

    public class HamburgerButton: UIBarButtonItem { //Replaced UIButton with UIBarButtonItem

    public var color: UIColor = UIColor.whiteColor() {
        didSet {
            for shapeLayer in shapeLayers {
                shapeLayer.strokeColor = color.CGColor
            }
        }
    }

    private let top: CAShapeLayer = CAShapeLayer()
    private let middle: CAShapeLayer = CAShapeLayer()
    private let bottom: CAShapeLayer = CAShapeLayer()
    private let width: CGFloat = 18 //ERROR: Cannot override with a stored property (I suppose this is because we cannot change the dimensions of the UIBarButtonItem)
    private let height: CGFloat = 16 //ERROR
    private let topYPosition: CGFloat = 2
    private let middleYPosition: CGFloat = 7
    private let bottomYPosition: CGFloat = 12

    override init(frame: CGRect) { //ERROR: Initializer does not override a designated initializer from its superclass (NO IDEA)
        super.init(frame: frame)
        commonInit()
    }

    required public init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    private func commonInit() {
        let path = UIBezierPath()
        path.moveToPoint(CGPoint(x: 0, y: 0))
        path.addLineToPoint(CGPoint(x: width, y: 0))

        for shapeLayer in shapeLayers {
            shapeLayer.path = path.CGPath
            shapeLayer.lineWidth = 2
            shapeLayer.strokeColor = color.CGColor

            // Disables implicit animations.
            shapeLayer.actions = [
                "transform": NSNull(),
                "position": NSNull()
            ]

            let strokingPath = CGPathCreateCopyByStrokingPath(shapeLayer.path, nil, shapeLayer.lineWidth, kCGLineCapButt, kCGLineJoinMiter, shapeLayer.miterLimit)
            // Otherwise bounds will be equal to CGRectZero.
            shapeLayer.bounds = CGPathGetPathBoundingBox(strokingPath)

            //layer.addSublayer(shapeLayer)
        }

        let widthMiddle = width / 2
        top.position = CGPoint(x: widthMiddle, y: topYPosition)
        middle.position = CGPoint(x: widthMiddle, y: middleYPosition)
        bottom.position = CGPoint(x: widthMiddle, y: bottomYPosition)
    }

    override public func intrinsicContentSize() -> CGSize { //ERROR: Method does not override any method from its superclass (NO IDEA)
        return CGSize(width: width, height: height)
    }

    public var showsMenu: Bool = true {
        didSet {
            // There's many animations so it's easier to set up duration and timing function at once.
            CATransaction.begin()
            CATransaction.setAnimationDuration(0.4)
            CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(controlPoints: 0.4, 0.0, 0.2, 1.0))

            let strokeStartNewValue: CGFloat = showsMenu ? 0.0 : 0.3
            let positionPathControlPointY = bottomYPosition / 2
            let verticalOffsetInRotatedState: CGFloat = 0.75


            let topRotation = CAKeyframeAnimation(keyPath: "transform")
            topRotation.values = rotationValuesFromTransform(top.transform,
                endValue: showsMenu ? CGFloat(-M_PI - M_PI_4) : CGFloat(M_PI + M_PI_4))
            // Kind of a workaround. Used because it was hard to animate positions of segments' such that their ends form the arrow's tip and don't cross each other.
            topRotation.calculationMode = kCAAnimationCubic
            topRotation.keyTimes = [0.0, 0.33, 0.73, 1.0]
            top.ahk_applyKeyframeValuesAnimation(topRotation)

            let topPosition = CAKeyframeAnimation(keyPath: "position")
            let topPositionEndPoint = CGPoint(x: width / 2, y: showsMenu ? topYPosition : bottomYPosition + verticalOffsetInRotatedState)
            topPosition.path = quadBezierCurveFromPoint(top.position,
                toPoint: topPositionEndPoint,
                controlPoint: CGPoint(x: width, y: positionPathControlPointY)).CGPath
            top.ahk_applyKeyframePathAnimation(topPosition, endValue: NSValue(CGPoint: topPositionEndPoint))

            top.strokeStart = strokeStartNewValue


            let middleRotation = CAKeyframeAnimation(keyPath: "transform")
            middleRotation.values = rotationValuesFromTransform(middle.transform,
                endValue: showsMenu ? CGFloat(-M_PI) : CGFloat(M_PI))
            middle.ahk_applyKeyframeValuesAnimation(middleRotation)

            middle.strokeEnd = showsMenu ? 1.0 : 0.85


            let bottomRotation = CAKeyframeAnimation(keyPath: "transform")
            bottomRotation.values = rotationValuesFromTransform(bottom.transform,
                endValue: showsMenu ? CGFloat(-M_PI_2 - M_PI_4) : CGFloat(M_PI_2 + M_PI_4))
            bottomRotation.calculationMode = kCAAnimationCubic
            bottomRotation.keyTimes = [0.0, 0.33, 0.63, 1.0]
            bottom.ahk_applyKeyframeValuesAnimation(bottomRotation)

            let bottomPosition = CAKeyframeAnimation(keyPath: "position")
            let bottomPositionEndPoint = CGPoint(x: width / 2, y: showsMenu ? bottomYPosition : topYPosition - verticalOffsetInRotatedState)
            bottomPosition.path = quadBezierCurveFromPoint(bottom.position,
                toPoint: bottomPositionEndPoint,
                controlPoint: CGPoint(x: 0, y: positionPathControlPointY)).CGPath
            bottom.ahk_applyKeyframePathAnimation(bottomPosition, endValue: NSValue(CGPoint: bottomPositionEndPoint))

            bottom.strokeStart = strokeStartNewValue


            CATransaction.commit()
        }
    }

    private var shapeLayers: [CAShapeLayer] {
        return [top, middle, bottom]
    }
}

extension CALayer {
    func ahk_applyKeyframeValuesAnimation(animation: CAKeyframeAnimation) {
        let copy = animation.copy() as CAKeyframeAnimation

        assert(!copy.values.isEmpty)

        self.addAnimation(copy, forKey: copy.keyPath)
        self.setValue(copy.values[copy.values.count - 1], forKeyPath:copy.keyPath)
    }

    // Mark: TODO: endValue could be removed from the definition, because it's possible to get it from the path (see: CGPathApply).
    func ahk_applyKeyframePathAnimation(animation: CAKeyframeAnimation, endValue: NSValue) {
        let copy = animation.copy() as CAKeyframeAnimation

        self.addAnimation(copy, forKey: copy.keyPath)
        self.setValue(endValue, forKeyPath:copy.keyPath)
    }
}

func rotationValuesFromTransform(transform: CATransform3D, #endValue: CGFloat) -> [NSValue] {
    let frames = 4

    // values at 0, 1/3, 2/3 and 1
    return (0..<frames).map { num in
        NSValue(CATransform3D: CATransform3DRotate(transform, endValue / CGFloat(frames - 1) * CGFloat(num), 0, 0, 1))
    }
}

func quadBezierCurveFromPoint(startPoint: CGPoint, #toPoint: CGPoint, #controlPoint: CGPoint) -> UIBezierPath {
    let quadPath = UIBezierPath()
    quadPath.moveToPoint(startPoint)
    quadPath.addQuadCurveToPoint(toPoint, controlPoint: controlPoint)
    return quadPath
}

您需要通过将 customView 分配给 BarButtonItem 来 link UIBarButtonItem 到 NavBar。我已经添加了一个初始化程序来为您处理这个问题。

init(frame: CGRect, target: AnyObject, action: Selector) {
    var view = UIButton(frame: frame)
    var width = 18
    let path = UIBezierPath()
    path.moveToPoint(CGPoint(x: 0, y: 0))
    path.addLineToPoint(CGPoint(x: width, y: 0))


    var shapeLayers: [CAShapeLayer] = [top, middle, bottom]

    var color: UIColor = UIColor.blackColor() {
        didSet {
            for shapeLayer in shapeLayers {
                shapeLayer.strokeColor = color.CGColor
            }
        }
    }

    for shapeLayer in shapeLayers {
        shapeLayer.path = path.CGPath
        shapeLayer.lineWidth = 2
        shapeLayer.strokeColor = color.CGColor

        // Disables implicit animations.
        shapeLayer.actions = [
            "transform": NSNull(),
            "position": NSNull()
        ]

        let strokingPath = CGPathCreateCopyByStrokingPath(shapeLayer.path, nil, shapeLayer.lineWidth, kCGLineCapButt, kCGLineJoinMiter, shapeLayer.miterLimit)
        // Otherwise bounds will be equal to CGRectZero.
        shapeLayer.bounds = CGPathGetPathBoundingBox(strokingPath)

        //layer.addSublayer(shapeLayer)
    }

    let widthMiddle = CGFloat(width / 2)
    top.position = CGPoint(x: widthMiddle, y: topYPosition)
    middle.position = CGPoint(x: widthMiddle, y: middleYPosition)
    bottom.position = CGPoint(x: widthMiddle, y: bottomYPosition)


    for layer in shapeLayers {
        view.layer.addSublayer(layer)
    }
    view.addGestureRecognizer(UITapGestureRecognizer(target: target, action: action))
    super.init(customView: view)
}

您现在可以从视图控制器添加栏并为其分配一个操作,如下所示:

@IBAction func doSomething(sender: AnyObject) {
    println("Do Something")
    menuButton!.showsMenu = true
}

override func viewDidLoad() {
    super.viewDidLoad()
    menuButton = HamburgerButton(frame: CGRectMake(0, 0, 18, 18), target: self, action: "doSomething:")
    navigationItem.setRightBarButtonItem(menuButton!, animated: true)
}

请注意,当您分配自定义视图时,您不能使用 UIBarButtonItem 的默认 GestureRecognizer 实现,因此您必须添加自己的实现。

我没有看过你在 showsMenu 中的动画代码,所以你可能需要采取一些额外的步骤来实现它。