I am trying this code to free hand drawing in UIImageView.
The problem in ( carves ).
class DrawingImageView: UIImageView {
var points: [CGPoint]?
var path: UIBezierPath?
var pathLayer: CAShapeLayer!
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
pathLayer = CAShapeLayer()
pathLayer.fillColor = UIColor.clear.cgColor
pathLayer.strokeColor = UIColor.clear.withAlphaComponent(0.5).cgColor
pathLayer.lineWidth = 15
pathLayer.lineJoin = .round
pathLayer.lineCap = .round
pathLayer.allowsEdgeAntialiasing = true
pathLayer.allowsGroupOpacity = true
pathLayer.shadowColor = UIColor.black.cgColor
pathLayer.shadowOpacity = 1
pathLayer.shadowOffset = CGSize.zero
pathLayer.shadowRadius = 5
self.layer.addSublayer(pathLayer)
if let touch = touches.first {
points = [touch.location(in: self)]
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
if let coalescedTouches = event?.coalescedTouches(for: touch) {
points? += coalescedTouches.map { $0.location(in: self) }
} else {
points?.append(touch.location(in: self))
}
if let predictedTouches = event?.predictedTouches(for: touch) {
let predictedPoints = predictedTouches.map { $0.location(in: self) }
pathLayer.path = UIBezierPath.interpolateHermiteFor(points: points! + predictedPoints, closed: false).cgPath
} else {
pathLayer.path = UIBezierPath.interpolateHermiteFor(points: points!, closed: false).cgPath
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
pathLayer.path = UIBezierPath.interpolateHermiteFor(points: points!, closed: false).cgPath
points?.removeAll()
}
}
I don't know what the problem is ( carve ).
It's not rounded and I use:
pathLayer.lineJoin = .round
pathLayer.lineCap = .round
The result is attached in image:
Related
I have an array of sprites and I move it when click on it, the problem is that it is moving all the sprites and I want it to move a single element, how can I do this? thank you in advance
This is my code:
var containerPieces: [ContainerPieces] = []
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
for touch in touches {
let location = touch.location(in: self)
let rsCurrent = self.atPoint(location)
for nodo in containerPieces {
let rsBody = nodo.block.physicsBody
if rsBody == rsCurrent.physicsBody {
if nodo.block.frame.contains(location) {
nodo.block.position = location
nodo.block.run(blinkAnimation(), withKey:"wiggle")
isFingerOnPaddle = true
}
}
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if isFingerOnPaddle {
let touch = touches.first
let touchLocation = touch!.location(in: self)
let previousLocation = touch!.previousLocation(in: self)
for nodo in containerPieces{
var paddleX = nodo.block.position.x + (touchLocation.x - previousLocation.x)
var paddleY = nodo.block.position.y + (touchLocation.y - previousLocation.y)
paddleX = max(paddleX, nodo.block.size.width/2)
paddleX = min(paddleX, size.width - nodo.block.size.width/2)
paddleY = max(paddleY, nodo.block.size.width/2)
paddleY = min(paddleY, size.width - nodo.block.size.width/2)
nodo.block.position = CGPoint(x: paddleX, y: paddleY)
}
}
}
I have been having some trouble moving a Sprite along a UIBezierPath. I am getting this path from my view controller and it is passing along the correct path. Any ideas on why this isn't animating? Eventually I want to be able to move multiple sprites along different bezier paths.
func play(){
let img = SKSpriteNode(imageNamed:"ball.png")
img.position = CGPoint(x: self.frame.size.width/4, y: self.frame.size.height/4)
self.addChild(img)
new_path = viewController.path //UIBezierPath is being returned
let followPath = SKAction.follow(new_path.cgPath, asOffset: true, orientToPath: false, duration: 5.0)
img.run(followPath)
}
I am trying to draw a bezier path here(Kind of like a whiteboard app)in a custom view and then access the bezier path when I want to animate it. The path seems to be passed through correctly.
private var path: UIBezierPath!
let newpath = UIBezierPath()
func initBezierPath() {
path = UIBezierPath()
path.lineCapStyle = CGLineCap.round
path.lineJoinStyle = CGLineJoin.round
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: AnyObject? = touches.first
lastPoint = touch!.location(in: self)
pointCounter = 0
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch: AnyObject? = touches.first
let newPoint = touch!.location(in: self)
path.move(to: lastPoint)
path.addLine(to: newPoint)
lastPoint = newPoint
pointCounter += 1
if pointCounter == pointLimit {
pointCounter = 0
renderToImage()
setNeedsDisplay()
newpath.append(path)
path.removeAllPoints()
}
else {
setNeedsDisplay()
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
pointCounter = 0
renderToImage()
setNeedsDisplay()
newpath.append(path)
path.removeAllPoints()
}
In my application i have a big button. I want it to have a glowing effect around it.
Can it be done using animations?
I have tried using the image but it does not looks clean and appealing.
I had been working in your question and this is my results,I define a custom subclass of UIButton,adding a CABasicAnimation animating shadowRadius property, setting autoreverses to true and repeatCount to infinity
Code
//
// GlowingButton.swift
// NavigationButtonRotateQuestion
//
// Created by Reinier Melian on 01/07/2017.
// Copyright © 2017 Pruebas. All rights reserved.
//
import UIKit
#IBDesignable
class GlowingButton: UIButton {
#IBInspectable var animDuration : CGFloat = 3
#IBInspectable var cornerRadius : CGFloat = 5
#IBInspectable var maxGlowSize : CGFloat = 10
#IBInspectable var minGlowSize : CGFloat = 0
#IBInspectable var glowColor : UIColor = nil ?? UIColor.red
#IBInspectable var animateAlways : Bool = false
fileprivate var animating : Bool = false
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.contentScaleFactor = UIScreen.main.scale
self.layer.masksToBounds = false
if(self.animateAlways){
self.setupButtonForContinueAnimation()
self.startAnimation()
}else{
self.setupButton()
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if(!self.animateAlways){
let layerAnimation = CABasicAnimation(keyPath: "shadowRadius")
layerAnimation.fromValue = minGlowSize
layerAnimation.toValue = maxGlowSize
layerAnimation.isAdditive = false
layerAnimation.duration = CFTimeInterval(animDuration/2)
layerAnimation.fillMode = CAMediaTimingFillMode.forwards
layerAnimation.isRemovedOnCompletion = false
self.layer.add(layerAnimation, forKey: "addGlowing")
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if(!self.animateAlways){
let layerAnimation = CABasicAnimation(keyPath: "shadowRadius")
layerAnimation.fromValue = maxGlowSize
layerAnimation.toValue = minGlowSize
layerAnimation.isAdditive = false
layerAnimation.duration = CFTimeInterval(animDuration/2)
layerAnimation.fillMode = CAMediaTimingFillMode.forwards
layerAnimation.isRemovedOnCompletion = false
self.layer.add(layerAnimation, forKey: "removeGlowing")
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
if(!self.animateAlways){
let layerAnimation = CABasicAnimation(keyPath: "shadowRadius")
layerAnimation.fromValue = maxGlowSize
layerAnimation.toValue = minGlowSize
layerAnimation.isAdditive = false
layerAnimation.duration = CFTimeInterval(animDuration/2)
layerAnimation.fillMode = CAMediaTimingFillMode.forwards
layerAnimation.isRemovedOnCompletion = false
self.layer.add(layerAnimation, forKey: "removeGlowing")
}
}
func setupButton()
{
self.layer.cornerRadius = cornerRadius
self.layer.shadowPath = CGPath(roundedRect: self.bounds, cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
self.layer.shadowRadius = 0
self.layer.shadowColor = self.glowColor.cgColor
self.layer.shadowOffset = CGSize.zero
self.layer.shadowOpacity = 1
}
func setupButtonForContinueAnimation()
{
self.setupButton()
self.layer.shadowRadius = maxGlowSize
}
func startAnimation()
{
let layerAnimation = CABasicAnimation(keyPath: "shadowRadius")
layerAnimation.fromValue = maxGlowSize
layerAnimation.toValue = minGlowSize
layerAnimation.autoreverses = true
layerAnimation.isAdditive = false
layerAnimation.duration = CFTimeInterval(animDuration/2)
layerAnimation.fillMode = CAMediaTimingFillMode.forwards
layerAnimation.isRemovedOnCompletion = false
layerAnimation.repeatCount = .infinity
self.layer.add(layerAnimation, forKey: "glowingAnimation")
}
func stopAnimations() {
self.layer.removeAllAnimations()
}
}
Edited: Added Inspectable attributes for better customization
Edited: Adapted to swift4 and added func to stop animation
The code below draws smooth curved lines by overriding touches, but there is noticeable lagging or latency. The code uses addCurveToPoint and calls setNeedsDisplay after every 4 touch points which causes a jumpy appearance as the drawing doesn't keep up with finger movements. To remove the lagging or perceived latency, touch points 1, 2, 3 (leading up to touch point 4) could be temporarily filled with addQuadCurveToPoint and addLineToPoint.
How can this actually be achieved in code to remove perceived lagging by using a temporary Line and QuadCurved line before displaying a final Curved line?
If the below class is attached to one UIView (e.g. viewOne or self), how do I make a copy of the drawing to another UIView outside the class (e.g. viewTwo) after touchesEnded?
// ViewController.swift
import UIKit
class drawSmoothCurvedLinesWithLagging: UIView {
let path=UIBezierPath()
var incrementalImage:UIImage?
var points = [CGPoint?](count: 5, repeatedValue: nil)
var counter:Int?
var strokeColor:UIColor?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func drawRect(rect: CGRect) {
autoreleasepool {
incrementalImage?.drawInRect(rect)
strokeColor = UIColor.blueColor()
strokeColor?.setStroke()
path.lineWidth = 20
path.lineCapStyle = CGLineCap.Round
path.stroke()
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
counter = 0
let touch: AnyObject? = touches.first
points[0] = touch!.locationInView(self)
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch: AnyObject? = touches.first
let point = touch!.locationInView(self)
counter = counter! + 1
points[counter!] = point
if counter == 2{
//use path.addLineToPoint ?
//use self.setNeedsDisplay() ?
}
if counter == 3{
//use path.addQuadCurveToPoint ?
//use self.setNeedsDisplay() ?
}
if counter == 4{
points[3]! = CGPointMake((points[2]!.x + points[4]!.x)/2.0, (points[2]!.y + points[4]!.y)/2.0)
path.moveToPoint(points[0]!)
path.addCurveToPoint(points[3]!, controlPoint1: points[1]!, controlPoint2: points[2]!)
self.setNeedsDisplay()
points[0]! = points[3]!
points[1]! = points[4]!
counter = 1
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.drawBitmap()
self.setNeedsDisplay()
path.removeAllPoints()
counter = 0
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
self.touchesEnded(touches!, withEvent: event)
}
func drawBitmap(){
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0.0)
strokeColor?.setStroke()
if((incrementalImage) == nil){
let rectPath:UIBezierPath = UIBezierPath(rect: self.bounds)
UIColor.whiteColor().setFill()
rectPath.fill()
}
incrementalImage?.drawAtPoint(CGPointZero)
path.stroke()
incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Yes, adding a curve every few points will give it a stuttering lag. So, yes, you can reduce this affect by adding a line to points[1], adding a quad curve to points[2] and adding a cubic curve to points[3].
As you said, make sure to add this to a separate path, though. So, in Swift 3/4:
class SmoothCurvedLinesView: UIView {
var strokeColor = UIColor.blue
var lineWidth: CGFloat = 20
var snapshotImage: UIImage?
private var path: UIBezierPath?
private var temporaryPath: UIBezierPath?
private var points = [CGPoint]()
override func draw(_ rect: CGRect) {
snapshotImage?.draw(in: rect)
strokeColor.setStroke()
path?.stroke()
temporaryPath?.stroke()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first {
points = [touch.location(in: self)]
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let point = touch.location(in: self)
points.append(point)
updatePaths()
setNeedsDisplay()
}
private func updatePaths() {
// update main path
while points.count > 4 {
points[3] = CGPoint(x: (points[2].x + points[4].x)/2.0, y: (points[2].y + points[4].y)/2.0)
if path == nil {
path = createPathStarting(at: points[0])
}
path?.addCurve(to: points[3], controlPoint1: points[1], controlPoint2: points[2])
points.removeFirst(3)
temporaryPath = nil
}
// build temporary path up to last touch point
if points.count == 2 {
temporaryPath = createPathStarting(at: points[0])
temporaryPath?.addLine(to: points[1])
} else if points.count == 3 {
temporaryPath = createPathStarting(at: points[0])
temporaryPath?.addQuadCurve(to: points[2], controlPoint: points[1])
} else if points.count == 4 {
temporaryPath = createPathStarting(at: points[0])
temporaryPath?.addCurve(to: points[3], controlPoint1: points[1], controlPoint2: points[2])
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
finishPath()
}
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
finishPath()
}
private func finishPath() {
constructIncrementalImage()
path = nil
setNeedsDisplay()
}
private func createPathStarting(at point: CGPoint) -> UIBezierPath {
let localPath = UIBezierPath()
localPath.move(to: point)
localPath.lineWidth = lineWidth
localPath.lineCapStyle = .round
localPath.lineJoinStyle = .round
return localPath
}
private func constructIncrementalImage() {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
strokeColor.setStroke()
snapshotImage?.draw(at: .zero)
path?.stroke()
temporaryPath?.stroke()
snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
You could even marry this with iOS 9 predictive touches (as I described in my other answer), which could reduce lag even further.
To take this resulting image and use it elsewhere, you can just grab the incrementalImage (which I renamed to snapshotImage, above), and drop it into an image view of the other view.
For Swift 2 rendition, see previous revision of this answer.
The below class attaches to a UIView and draws lines while moving a finger across the screen. However, when moving a finger very fast from left to right moving from top to bottom, the drawing temporarily shows sharp pointy edges on changing direction. This occurs both on the device and simulator.
What is causing this issue and how can this artefact be eliminated in the code so that only smooth rounded, not sharp edges are seen when changing direction fast?
class drawLine: UIView
{
var comittedSegments: Int = 0
var points = [CGPoint]()
var committedPath = UIBezierPath()
var drawPath = UIBezierPath()
var incrementalImage: UIImage?
var strokeColor:UIColor?
override func drawRect(rect: CGRect) {
autoreleasepool {
incrementalImage?.drawInRect(rect)
strokeColor = UIColor.darkGrayColor()
strokeColor?.setStroke()
drawPath.lineWidth = 20
drawPath.lineCapStyle = CGLineCap.Round
drawPath.stroke()
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch: AnyObject? = touches.first
comittedSegments = 0
committedPath.removeAllPoints()
points.removeAll()
points.append( touch!.locationInView(self) )
}
override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch: AnyObject? = touches.first
let point = touch!.locationInView(self)
points.append( point )
if points.count == 5
{
points[3] = CGPointMake((points[2].x + points[4].x)/2.0, (points[2].y + points[4].y)/2.0)
committedPath.moveToPoint(points[0])
committedPath.addCurveToPoint(points[3], controlPoint1: points[1], controlPoint2: points[2])
comittedSegments = comittedSegments + 1
self.setNeedsDisplay()
points[0] = points[3]
points[1] = points[4]
points.removeRange(2...4)
drawPath = committedPath
}
else if points.count > 1
{
drawPath = committedPath.copy() as! UIBezierPath
drawPath.CGPath = committedPath.CGPath
drawPath.moveToPoint( points[0] )
for point in points[1..<points.count] {
drawPath.addLineToPoint(point)
}
self.setNeedsDisplay()
}
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.drawBitmap()
self.setNeedsDisplay()
committedPath.removeAllPoints()
points.removeAll()
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
self.touchesEnded(touches!, withEvent: event)
}
func drawBitmap() {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0.0)
strokeColor?.setStroke()
if(incrementalImage == nil) {
let rectPath:UIBezierPath = UIBezierPath(rect: self.bounds)
UIColor.whiteColor().setFill()
rectPath.fill()
}
incrementalImage?.drawAtPoint(CGPointZero)
committedPath.stroke()
incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
}
In addition to setting lineCapStyle, set lineJoinStyle:
drawPath.lineJoinStyle = .Round
drawPath.lineCapStyle = .Round