How to Achieve a "Clock Wipe"/ Radial Wipe Effect in iOS

How do you achieve a clock wipe / radial wipe effect in iOS?

Answer:

You'd create CAShapeLayer and install it as a mask on your view's layer.

You'd then install an arc into the shape layer that spanned a full 360 degrees, and had a radius that was half the center-to-corner distance of your image. You'd give it a line width that was the same as the radius.

Finally, you'd create a CABasicAnimation that animates the strokeEnd property of the shape layer from 0 to 1.0. This would cause the shape to start out empty (image fully masked) and animate the mask as a "pie slice" that got bigger and bigger, until it was the full size of the image.

You can download a sample project on github that I wrote that shows working code for this animation, among other Core Animation techniques:

CA Animation demo on github



EDIT Update for Swift 5:

Ok, this thread is REALLY old. I tried to get the sample project to build and gave up fighting with Xcode after about 45 minutes. Too many settings are out of date with current project formats.

The clock wipe demo needed to be rewritten in Swift, so I just started over.

The new project can be found at this link: https://github.com/DuncanMC/ClockWipeSwift

How do you create a feathered Circle wipe animation in iOS?

You could use the shadowPath on a CAShapeLayer to create a circle with a blurred outline to use as the mask.

Create a shape layer with no fill or stroke (we only want to see the shadow) and add it as the layer mask to your view.

let shapeLayer = CAShapeLayer()
shapeLayer.shadowColor = UIColor.black.cgColor
shapeLayer.shadowOffset = CGSize(width: 0, height: 0)
shapeLayer.shadowOpacity = 1
shapeLayer.shadowRadius = 5
self.maskLayer = shapeLayer
self.layer.mask = shapeLayer

And animate it with a CABasicAnimation.

func show(_ show: Bool) {

let center = CGPoint(x: self.bounds.width/2, y: self.bounds.height/2)
var newPath: CGPath
if show {
let radius = sqrt(self.bounds.height*self.bounds.height + self.bounds.width*self.bounds.width)/2 //assumes view is square
newPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true).cgPath
} else {
newPath = UIBezierPath(arcCenter: center, radius: 0.01, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true).cgPath
}

let shadowAnimation = CABasicAnimation(keyPath: "shadowPath")
shadowAnimation.fromValue = self.maskLayer.shadowPath
shadowAnimation.toValue = newPath
shadowAnimation.duration = totalDuration

self.maskLayer.shadowPath = newPath
self.maskLayer.add(shadowAnimation, forKey: "shadowAnimation")

}

As you can set any path for the shadow it should also work for other shapes (star, box etc.).

Make clockwise disappearing circle with PaintCode

You are like 50% complete. :) Now you need to create a class to hold your circular view and manipulate the end angle.

But before that, in PaintCode, hook up an angle type variable to the circle End Angle property. Like so:

PaintCode

Notice: in my example I'm using an image as the circle's Fill. Not sure if that is the approach you envisioned, but there you go. If you are going to follow this path, make sure the image you used in PaintCode is also in Xcode under Assets.xcassets (using the same name referenced in the StyleKit class).

Now back to that class. This could be a regular UIView, with a reference to the angle type property; as well as functions to increment it, start and stop the animation:

@IBDesignable class CircleView: UIView {

// MARK: - Properties

var angle: CGFloat = 0 {
didSet {
setNeedsDisplay()
}
}

// MARK: - Private Properties

private var animationTimer = Timer()

// MARK: - Lifecycle

override func draw(_ rect: CGRect) {
StyleKit.drawCircleShape(frame: rect, resizing: .aspectFill, angle: angle)
}
}

// MARK: - Interface

extension CircleView {

func startAnimating() {
animationTimer = Timer.scheduledTimer(
timeInterval: 0.01,
target: self,
selector: #selector(updateAngle),
userInfo: nil,
repeats: true
)
animationTimer.fire()
}

func stopAnimating() {
animationTimer.invalidate()
}

@objc func updateAngle() {
if angle == 360 { angle = 0 }
angle += 1
}
}

This of course is just an example to give you an idea. Your implementation may vary.

Finally, you could use a ViewController to start / stop the animation as in:

class CircleViewController: UIViewController {

// MARK: - Private Properties

@IBOutlet private weak var circleView: CircleView! {
didSet {
circleView.startAnimating()
}
}

// MARK: - Lifecycle

override func viewDidDisappear(_ animated: Bool) {
circleView.stopAnimating()
}
}

Final result:

Example

That's it. You can find the project here (it's the Fifth test):

https://github.com/backslash-f/paintcode-tests

Ah, another thing to note: as soon as I started using an image to fill my circular shape, Interface Builder gave me some weird errors: IB Designables: Failed to render and update auto layout status for... That's something to investigate. Not sure if it's reproducible tho.

Cheers!

Animate layer.mask (not the .path of a shape layer)

Yes you can. I created a whole series of "wipe" transition animations doing that. You just make the CAShapeLayer the mask, and animate the path of that mask's shape layer. (In my case I animated the start or end property of the path.)

The thread is pretty old at this point, and the code is written in Objective-C, but here is a SO thread explaining how to do create a "clock wipe" animation using a CAShapeLayer as a layer mask.

How do you achieve a "clock wipe"/ radial wipe effect in iOS?

How to implement a line's travel path

You could do this with colored views and UIView animation.

Set up a view with a background color that will fill the screen. Set this view's background color to "the other color". Let's call this the "fill view". Pin it to the left side of your content view, and give it a right edge constraint that's also pinned to the left edge of the content view. Add an outlet to the left edge constraint. Let's call that constraint outlet "rightEdgeConstraint.

Create a line view (call it the "line view") with a fixed width constraint and a different background color. Add a left edge constraint to the line view attaching it to the right edge of the fill view.

Now in your code, animate the right edge constraint:

UIView.animateWithDuration(1.0) {
//Make the fill view as wide as the content view.
rightEdgeConstraint.constant = view.width
view.layoutIfNeeded()
}

If you want the user to be able to drag the line left to right then you'd need to attach a pan gesture recognizer to the line view and use the change in X position to change the value of the rightEdgeConstraint.constant (and then call layoutIfNeeded() as above.)

CABasicAnimation not working on device (iPhone 5s)

I think the problem (or at least part of it) may be this line:

[_resultsImage setFrame:_imageView.bounds];

That should read

[_resultsImage setBounds:_imageView.bounds];

Instead. If you set the FRAME to the bounds of the image view, you're going to move the image view to 0.0 in its superview.

I'm also not clear what _imageView is, as opposed to _resultsImage.

I would step through your code in the debugger, looking at the frame rectangles that are being set for the image view and mask layer, and all the other values that are calculated.

Typescript react - Could not find a declaration file for module ''react-materialize'. 'path/to/module-name.js' implicitly has an any type

For those who wanted to know that how did I overcome this . I did a hack kind of stuff .

Inside my project I created a folder called @types and added it to tsconfig.json for find all required types from it . So it looks somewhat like this -

"typeRoots": [
"../node_modules/@types",
"../@types"
]

And inside that I created a file called alltypes.d.ts . To find the unknown types from it . so for me these were the unknown types and I added it over there.

declare module 'react-materialize';
declare module 'react-router';
declare module 'flux';

So now the typescript didn't complain about the types not found anymore . :) win win situation now :)



Related Topics



Leave a reply



Submit