How to add blinking circle inside draw:inRect: method of UIView? - ios

Inside my custom UIView and draw:inRect initializer I have following code:
override func draw(_ rect: CGRect) {
super.draw(rect)
//...
let axisPath = UIBezierPath()
axisPath.move(to: CGPoint(x: paddingHorizontal + 20, y: paddingVertical))
axisPath.addLine(to: CGPoint(x: leftOffset, y: bottomOffset))
axisPath.addLine(to: CGPoint(x: rect.width - paddingHorizontal, y: bottomOffset))
axisPath.lineWidth = 1
UIColor.black.set()
axisPath.stroke()
var currentIndex = 0
for yearData in data {
//...
let circle = UIBezierPath(roundedRect: CGRect(x: x - 4, y: y - 4, width: 8, height: 8), cornerRadius: 4)
circle.fill()
circle.stroke() //red circle, I need to make it blinking somehow
//...
}
//...
}
The result is following:
Now I need to make red circle blinking:) How can I do that?

I would take a different approach. Use draw(_:) only for the static part of the graph.
The blinking red circle should be a simple little subview added over the graph view. Use a repeating UIView animation to fade the little circle view in and out using its alpha property.
See How do I get this fade animation to work? for an example of performing the blinking animation.

Setup a NSTimer and draw and undraw your red circle based on that. You have at least two choices: Create the red circle as an UIView and just set Hidden flag appropriately, or toggle a flag on your timer and invalidate the area encompassed by the red circle.

Related

dotted line is not coming till the end in uiview

I am trying to create a dotted line, I have tried with the code below:
private func createDottedView(for view:UIView) {
let width: CGFloat = 1
let color: CGColor = UIColor.black.cgColor
let caShapeLayer = CAShapeLayer()
caShapeLayer.strokeColor = color
caShapeLayer.lineWidth = width
caShapeLayer.lineDashPattern = [9,3]
let cgPath = CGMutablePath()
let cgPoint = [CGPoint(x: view.layer.bounds.minX, y: 0), CGPoint(x: view.layer.bounds.maxX, y: 0)]
cgPath.addLines(between: cgPoint)
caShapeLayer.path = cgPath
view.layer.addSublayer(caShapeLayer)
}
But this is not coming till end, how can i get this dotted line till end?
You can use an image misted of making a uiview a dotted line because of the CAShapeLayer()
It can cause this type of problem. So you can just download a similar image and place the image view inside the uiview and use the image to make it scale to fill it should work.
Or if you want to do it with coding only you can change your code with the given live of code the rest of the code will work fine.
let cgPoint = [CGPoint(x: view.layer.bounds.minX, y: 0), CGPoint(x:
view.layer.bounds.maxX+40, y: 0)]
We can add trailing and leading constraints on the UIView as shown below in the storyboard.
Added constrainsts to the lineView
Output of dotted UIView

With strokeColor, a line is drawn from the center to the circumference

I am using swift 4.
I wrote the code below and tried to draw a circle with stroke.
However, with the code below I could not draw my desired circle.
The problem is that the line is drawn from the center of the circle to the outer periphery (strictly speaking, toward the coordinate point of the 'startAngle' property).
I want to erase the line, what should I do?
(I prepared an image.)
class Something{
var line:[CAShapeLayer] = []
var path:[UIBezierPath] = []
func drawingNow(){
let layer = CAShapeLayer()
self.layer.addSublayer(layer)
self.line.append(layer)
let addPath: UIBezierPath = UIBezierPath()
addPath.move(to: CGPoint(x: 100, y: 100))
addPath.addArc(
withCenter: CGPoint(x: 100, y: 100),
radius: CGFloat(50),
startAngle: CGFloat(//someangle),
endAngle: CGFloat(//someangle),
clockwise: true
)
self.path.append(addPath)
//self.line.last!.strokeColor = etc... (If don't use "override func draw()")
self.line.last!.fillColor = UIColor.clear.cgColor
self.line.last!.path = addPath.cgPath
self.setNeedsDisplay()
}
override func draw(_ rect: CGRect) {
if self.path.count != 0{
UIColor.orange.setStroke()
self.path.last!.stroke()
}
}
}
Image
After calling addPath.move(to: CGPoint(x: 100, y: 100)), the UIBezierPath moved to the specified coordinate. Now you are telling it to add an arc from there with a centre of (100, 100). To draw an arc with a centre (100, 100), it first need to move to the circumference. But at this point it already starts drawing! That's just how addArc works. The same goes for addLine. Look at the docs:
This method adds the specified arc beginning at the current point.
So the arc always starts at the current point, which is (100, 100).
Instead of telling it to move to the centre first, just tell it to draw an arc by removing the move(to:)` line.

How do I use this circle drawing code in a UIView?

I am making an app including some breathing techniques for a client. What he wants is to have a circle in the middle. For breathing in it becomes bigger, for breathing out tinier. The thing is, that he would like to have a cool animated circle in the middle, not just a standard one. I showed him this picture from YouTube:
The code used in the video looks like this:
func drawRotatedSquares() {
UIGraphicsBeginImageContextWithOptions(CGSize(width: 512, height: 512), false, 0)
let context = UIGraphicsGetCurrentContext()
context!.translateBy(x: 256, y: 256)
let rotations = 16
let amount = M_PI_2 / Double(rotations)
for i in 0 ..< rotations {
context!.rotate(by: CGFloat(amount))
//context!.addRect(context, CGRect(x: -128, y: -128, width: 256, height: 256))
context!.addRect(CGRect(x: -128, y: -128, width: 256, height: 256))
}
context!.setStrokeColor(UIColor.black as! CGColor)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
imageView.image = img
}
But if I run it, my simulator shows just a white screen. How do I get this circle into my Swift 3 app and how would the code look like? And is it possible not to show it in an ImageView but simply in a view?
Thank you very much!
Here is an implementation as a UIView subclass.
To set up:
Add this class to your Swift project.
Add a UIView to your Storyboard and change the class to Circle.
Add an outlet to your viewController
#IBOutlet var circle: Circle!
Change the value of multiplier to change the size of the circle.
circle.multiplier = 0.5 // 50% of size of view
class Circle: UIView {
var multiplier: CGFloat = 1.0 {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
let context = UIGraphicsGetCurrentContext()!
// Calculate size of square edge that fits into the view
let size = min(bounds.width, bounds.height) * multiplier / CGFloat(sqrt(2)) / 2
// Move origin to center of the view
context.translateBy(x: center.x, y: center.y)
// Create a path to draw a square
let path = UIBezierPath()
path.move(to: CGPoint(x: -size, y: -size))
path.addLine(to: CGPoint(x: -size, y: size))
path.addLine(to: CGPoint(x: size, y: size))
path.addLine(to: CGPoint(x: size, y: -size))
path.close()
UIColor.black.setStroke()
let rotations = 16
let amount = .pi / 2 / Double(rotations)
for _ in 0 ..< rotations {
// Rotate the context
context.rotate(by: CGFloat(amount))
// Draw a square
path.stroke()
}
}
}
Here it is running in a Playground:
You posted a singe method that generates a UIImage and installs it in an image view. If you don't have the image view on-screen then it won't show up.
If you create an image view in your view controller and connect an outlet to the image view then the above code should install the image view into your image and draw it on-screen.
You could rewrite the code you posted as the draw(_:) method of a custom subclass of UIView, in which case you'd get rid of the context setup and UIImage stuff, and simply draw in the current context. I suggest you search on UIView custom draw(_:) methods for more guidance.

Animated CGAffineTransform(rotate) of a UIBezier path that is under func draw()

I want to make an animated of a path with CGAffineTransform(rotationAngle:) that is under a UIView`s draw. I couldn't find any way to do it...
This is my code:
public override func draw(_ rect: CGRect) {
//Drawing a dot at the center
let dotRadius = max(bounds.width/15, bounds.height/15)
let dotWidth:CGFloat = 10
let dotCenter = CGPoint(x:bounds.width/2, y: bounds.height/2)
let dotStartAngle: CGFloat = 0
let dotEndAngle: CGFloat = CGFloat(2 * M_PI) // Ļ€
var path = UIBezierPath(arcCenter: dotCenter,
radius: dotRadius/2,
startAngle: dotStartAngle,
endAngle: dotEndAngle,
clockwise: true)
path.lineWidth = dotWidth
counterColor.setStroke()
counterColor.setFill()
path.stroke()
path.fill()
arrowLayer.frame = CGRect(x:bounds.width/2, y: bounds.height/2, width: bounds.width, height: bounds.height)
arrow.path = UIBezierPath(rect: CGRect(x: 0, y:0 - 1, width: -250, height: 1)).cgPath
arrow.backgroundColor = UIColor.red.cgColor
arrow.fillColor = UIColor.clear.cgColor
arrow.strokeColor = counterColor.cgColor
arrow.anchorPoint = CGPoint(x:0.5, y:0.5)
arrow.lineWidth = 3
arrow.setAffineTransform(CGAffineTransform(rotationAngle:self.radians(arrowDegree)))
arrowLayer.addSublayer(arrow)
self.layer.addSublayer(arrowLayer)
}
And this is what I'm trying to do:
func rotateButton() {
UIView.animate(withDuration: 1, animations: {
self.arrow.setAffineTransform(CGAffineTransform(rotationAngle:self.radians(self.arrowDegree + 10)))
self.setNeedsDisplay()
})
}
I don't know if I was specific.. tell me if more information is needed.
I would recommend that you separate the animation from the drawing. Overriding draw() is generally suited for custom drawing but ill-suited for any type of animation.
Based on the properties that's being assigned to arrow it looks like it's a CAShapeLayer. This is good because it means that there is already some separation between the two.
However, it's not a good idea to add subviews or sublayers inside of draw(). It should only be used for drawing. Otherwise these things are going to get re-added every time the view is redrawn. In this case it's less noticeable because the same layer is added over and over. But it should still be avoided.
You can still use draw() to render the centered dot. But it's not expected to be part of the animation.
As for the rotation animation; unless I greatly misremember the underlying mechanics, standalone layers (those that aren't backing a view (most likely you've created them)) don't care about the UIView animation context. Instead you would use a CoreĀ Animation level API to animate them. This could either be done by changing the property within a CATransaction, or by creating a CABasicAnimation from one angle to the other and adding it to the layer.
Note that adding an animation doesn't change the "model" value (the properties of the layer), and that once the animation finishes the layer would render as it did before the animation was added. If the layer is expected to animate to a value and stay there you would both add the animation and update the layer property.

I have custom UIButtons which are rotated 45 Degrees - how do I make it so that only the space within the actual shape is clickable?

So my custom button looks like this:
override func drawRect(rect: CGRect) {
let mask = CAShapeLayer()
mask.frame = self.layer.bounds
let width = self.layer.frame.size.width
let height = self.layer.frame.size.height
let path = CGPathCreateMutable()
CGPathMoveToPoint(path,nil,width/2, 0)
CGPathAddLineToPoint(path,nil,width, height/2)
CGPathAddLineToPoint(path,nil,width/2, height)
CGPathAddLineToPoint(path,nil, 0, height/2)
mask.path = path
self.layer.mask = mask
}
I've added the button to a UIView in Storyboard, and subclassed it to this UIButton subclass. I set the background to blue in Storyboard.
The result:
The problem is then I click around the corners (the white space, as if the button was still being recognised as a square), it is still clickable. This is an issue because I want to add multiple shapes like this next to each other, so I don't want any of the Buttons to block each other out.
You can override the pointInside function in your custom UIButton to control when a touch in your view's bounds should be considered a hit.
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
let width = bounds.size.width
let height = bounds.size.height
// Make a UIBezierPath that contains your clickable area
let path = UIBezierPath()
path.moveToPoint(CGPoint(x: width / 2, y: 0))
path.addLineToPoint(CGPoint(x: width, y: height / 2))
path.addLineToPoint(CGPoint(x: width / 2, y: height))
path.addLineToPoint(CGPoint(x: 0, y: height / 2))
path.closePath()
// Let a hit happen if the point touched is in the path
return path.containsPoint(point)
}
See also
Allowing interaction with a UIView under another UIView
How about adding UITapGestureRecognizer to the button view, get the coordinate of tap and convert the coordinates to parent view coordinate and compare if its in the twisted cube.
//sender being the UITapGestureRecognizer
let coordinate = containerView.convertPoint(sender.locationInView(ContainerView), toCoordinateFromView: containerView)

Resources