将汉堡包按钮添加到导航栏
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 中的动画代码,所以你可能需要采取一些额外的步骤来实现它。
我正在尝试将此 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 中的动画代码,所以你可能需要采取一些额外的步骤来实现它。