R3ptor R3ptor - 2 months ago 16
Swift Question

Draw Line and fill with UIVisualEffectView

So the drawing lines part is setup:

func drawLineFrom(_ fromPoint: CGPoint, toPoint: CGPoint) {

let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.dark)
let blurEffectView = UIVisualEffectView(effect: blurEffect)

UIGraphicsBeginImageContextWithOptions(tempImageView.frame.size, false, UIScreen.main.scale)

let context = UIGraphicsGetCurrentContext()
tempImageView.image?.draw(in: CGRect(x: 0, y: 0, width: tempImageView.frame.size.width, height: tempImageView.frame.size.height), blendMode: .normal, alpha: 1.0)

context?.move(to: CGPoint(x: fromPoint.x, y: fromPoint.y))
context?.addLine(to: CGPoint(x: toPoint.x, y: toPoint.y))

context?.setStrokeColor(blurEffectView(effect: blurEffect))


var img = UIGraphicsGetImageFromCurrentImageContext()
tempImageView.image = img
tempImageView.alpha = opacity



It is basically using touchesBegan, touchesMoved and touchesEnded and this is where
is coming from.

And like you can already see I'm trying to fill this line up with an UIVisualEffectView instead of a Color. Obviously my method doesn't really work.
What would be the best solution to do this?



A UIVisualEffectView doesn't really have any content by itself, rather it depends on the views below it. If you want to give the effect of drawing with a UIVisualEffect you should construct your view hierarchy that you want the impression of drawing on top of. Perhaps something like view with an image view, displaying some image, then an effect view on top of that, like:

let imageView = UIImageView(image: UIImage(named: "image.jpg"))
let blurEffect = UIBlurEffect(style: .dark)
let effectView = UIVisualEffectView(effect: blurEffect)
effectView.frame = imageView.bounds
let view = UIView(frame: imageView.bounds)

Then you'll need to snapshot the view hierarchy in this state. This will act like the view if it were completely filled in by the touches. You can do that by adding an extension on UIView:

extension UIView
    var snapshot: UIImage?
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0)
        self.drawHierarchy(in: self.bounds, afterScreenUpdates: true)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        return image


let image = view.snapshot!

We can now replace out UIVisualEffectView with an UIImageView holding our snapshotted view hierarchy:

let topImageView = UIImageView(image: image)

You can then mask topImageView to drawing paths by declaring a CAShapeLayer property and setting it as the mask layer for topImageView

let shapeLayer = CAShapeLayer()


topImageView.layer.mask = shapeLayer


Now you can write your drawLine function:

func drawLine(from fromPoint: CGPoint, to toPoint: CGPoint)
    let path: UIBezierPath
    if let layerPath = self.shapeLayer.path
        path = UIBezierPath(cgPath: layerPath)
        path = UIBezierPath()
    path.move(to: fromPoint)
    path.addLine(to: toPoint)
    self.shapeLayer.path = path.cgPath
    self.shapeLayer.lineWidth = brushWidth
    self.shapeLayer.lineCap = "round"
    self.shapeLayer.strokeColor = UIColor.black.cgColor

In a playground in the live view without calling drawLine my view looks like:

enter image description here

After calling:

drawLine(from: CGPoint(x: 0, y: 0), to: CGPoint(x: imageView.frame.maxX, y: imageView.frame.maxY))
drawLine(from: CGPoint(x: 0, y: imageView.frame.maxY), to: CGPoint(x: imageView.frame.maxX, y: 0))

It looks like:

enter image description here

You should be aware that this is processor intensive and may not be performant.

You can see the code I used for my playground here.