Swift: Add image to CAShapeLayer

2019-02-20 10:24发布

问题:

I have a CAShapeLayer with a fill color, and want to add an icon in the center of this shape:

var shape = CAShapeLayer()
shape.fillColor = UIColor(white: 0.90, alpha: 1).CGColor
var image = UIImage(named: "some_image")
shape.contents = image?.CGImage

This code does compile and does show the grey shape- but no image. :(

I looked into these posts, but I can't cast the CGImage like they do.

add UIImage in CALayer and Display an image or UIImage with a plain CALayer

Is there another way? Or a workaround to the id cast?

Full code:

ViewController.swift

import UIKit

class ViewController: UIViewController {

    @IBOutlet var menuView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

    override func viewDidAppear(animated: Bool) {

        let menu: CircularMenu = CircularMenu(frame: menuView.bounds, numberOfMenuItems: 4, hasProgressBar: false)
        self.menuView.addSubview(menu)
    }

}

CircularMenu.swift

import UIKit

// *** Helper methods start
func DegreesToRadians (value: Double) -> CGFloat {
    return CGFloat(value * M_PI / 180.0)
}

func RadiansToDegrees (value: Double) -> CGFloat {
    return CGFloat(value * 180.0 / M_PI)
}
// *** Helper methods end

class CircularMenu: UIControl {

    var parent: UIViewController!

    var centerButton: UIButton!
    var menuButtonShapes = [CAShapeLayer]()
    var menuButtons = [UIButton]()

    var numberOfButtons: Double = 4

    var bigSize: CGFloat = 300
    var centerButtonSize: CGFloat = 100
    var centerButtonPadding: CGFloat = 70
    let paddingBetweenButtons = 1

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    convenience init(frame: CGRect, numberOfMenuItems: Int, hasProgressBar: Bool = false) {
        self.init(frame: frame)
        self.bounds = frame

        self.bigSize = frame.width
        self.centerButtonSize = bigSize/2
        self.centerButtonPadding = bigSize/4-5

        if hasProgressBar {
            self.centerButtonSize -= 10
        }

        if numberOfMenuItems != 0 && numberOfMenuItems <= 10 {
            self.numberOfButtons = Double(numberOfMenuItems)
        }

        // Draw center button
        var centerButtonView = UIView(frame: CGRectMake(0, 0, centerButtonSize, centerButtonSize))
        centerButtonView.backgroundColor = UIColor(red: 160.0/255, green: 197.0/255, blue: 25.0/255, alpha: 1)

        centerButtonView.center = self.center;
        centerButtonView.layer.cornerRadius = centerButtonSize/2
        self.addSubview(centerButtonView)
        self.centerButton = UIButton(frame: centerButtonView.bounds)


        let degreesPerButton: Double = 360.0/numberOfButtons

        // Draw button shapes
        for (var i: Double = 1; i <= numberOfButtons; i++)
        {
            // Determine start and end radians of pizza slice
            let startAngle = DegreesToRadians(degreesPerButton*i+degreesPerButton-Double(paddingBetweenButtons))
            let endAngle = DegreesToRadians(degreesPerButton*i+Double(paddingBetweenButtons))

            // Determine center and angle of slice
            var buttonCenter = self.center;
            var offsetDirection = (startAngle - endAngle)/2 + endAngle

            // Shove slice outward to make space for inner padding
            var offsetX = cos(offsetDirection)*tan(DegreesToRadians(Double(paddingBetweenButtons)))*bigSize/2
            var offsetY = sin(offsetDirection)*tan(DegreesToRadians(Double(paddingBetweenButtons)))*bigSize/2

            buttonCenter.x += offsetX
            buttonCenter.y += offsetY

            // Create mask for outer rim of slice to a circular form (from triangle to pizza slice)
            let outerCircleRadius = bigSize/2;
            var bigCircle = UIBezierPath(arcCenter: self.center, radius: bigSize/2, startAngle: startAngle , endAngle: endAngle, clockwise: false);
            bigCircle.addLineToPoint(buttonCenter)
            bigCircle.closePath()

            // Take padding into consideration when creating mask
            let tmpBigSizeX = (bigSize+2*abs(offsetX))
            let tmpBigSizeY = (bigSize+2*abs(offsetY))

            // Create mask for inner rim of slice to a circular form (from pizza slice to donut slice)
            let smallSize = bigSize-centerButtonSize-centerButtonPadding
            let innerCircleRadius = outerCircleRadius-bigSize/2/2;
            var smallCircle = UIBezierPath(arcCenter: self.center, radius: smallSize, startAngle: 0 , endAngle: DegreesToRadians(360), clockwise: false);

            // Apply masks for everything but the donut slice shape
            var buttonShape = CAShapeLayer()
            var maskLayer = CAShapeLayer()
            var maskPath = CGPathCreateMutable();

            CGPathAddRect(maskPath, nil, CGRectMake(outerCircleRadius-tmpBigSizeX/2, outerCircleRadius-tmpBigSizeY/2, tmpBigSizeX, tmpBigSizeY));
            CGPathAddPath(maskPath, nil, smallCircle.CGPath);
            maskLayer.path = maskPath;
            maskLayer.fillRule = kCAFillRuleEvenOdd;

            buttonShape.path = bigCircle.CGPath
            buttonShape.mask = maskLayer
            buttonShape.fillColor = UIColor(white: 0.90, alpha: 1).CGColor

            // Add icons and labels to the button
            var image = UIImage(named: "menu-icon_users_grey")
            let imageSubLayer = CALayer()
            imageSubLayer.contents = image?.CGImage
            buttonShape.addSublayer(imageSubLayer)

            // Save references to both shape and button
            self.menuButtons.append(UIButton(frame: buttonShape.frame))
            self.menuButtonShapes.append(buttonShape)

            // Add shape to view
            self.layer.addSublayer(buttonShape)
        }
    }

}

Lines of interest are in the bottom of the custom UIControl under the comment:

"Add icons and labels to the button"

回答1:

You have two options:

The not-so-good one, transform the UIImage in a color and then cast it to CGColor, but since your code snippet is so small, I don't know what is going to work:

let image = UIImage(named: "")
if let realImage = image {
    let color = UIColor(patternImage: realImage)
    shape.fillColor = color.CGColor
}

And the recommended one, create a new layer and set the UIImage on its contents:

let imageSubLayer = CALayer()
imageSubLayer.contents = image?.CGImage
shape.addSublayer(imageSubLayer)

I can't test it right now, so tell me if it works.