-2.1 C
New York
Thursday, December 26, 2024

Stop backward tracing in iOS Drawing App (Swift)


I’m utilizing SwiftyDraw library to attract a path for my app. Drawing the trail is working effectively however there’s a downside if person begin drawing path and transfer the finger backwards and forwards in the identical place. Nonetheless its drawing the trail and it will get accomplished. Right here I’ve my complete code.

lazy var targetView: UIView = {
    let sTargetView =  UIView(body: CGRect(x: 0,
                                            y: 0,
                                            width: self.view.body.width,
                                            top: self.view.body.top/1.3))
    sTargetView.middle = CGPoint(x: UIScreen.principal.bounds.dimension.width/2,
                                 y: UIScreen.principal.bounds.dimension.top/2)
    sTargetView.backgroundColor = .clear
    sTargetView.isUserInteractionEnabled = true

    canvasView.isUserInteractionEnabled = true
    canvasView.backgroundColor = .clear
    sTargetView.addSubview(canvasView)
    canvasView.brush = .thick
    canvasView.brush.shade =  self.assistiveTouchSwitch.isOn ? Shade(.clear) : Shade.init(self.accentColor)
    canvasView.body = sTargetView.bounds
    canvasView.delegate = self
    return sTargetView
}()
var swiped = false
var lastPoint = CGPoint.zero
var accentColor = UIColor.purple//UIColor(hex: "00acc1")
let canvasView = SwiftyDrawView()
var isTutorialAnimationInProgresss = false

var strokeAnimationCounter = 0
var currentPathToTrace : CGPath!
var defaultTransform  = CGAffineTransform()
var assistiveDrawLayersArray = [CAShapeLayer()]
var pathToHitTestAgainst : CGPath!
let dashLayer = CAShapeLayer()
let tutorialLayer = CALayer()
var strokePathsArray = [MyBezierPath]()
var strokeIndex = 0 {
    didSet {
        self.pathToHitTestAgainst = self.strokePathsArray[strokeIndex].cgPath.copy(strokingWithWidth: 10, lineCap: .spherical, lineJoin: .spherical, miterLimit: 0, remodel: defaultTransform)
    }
}
@IBOutlet weak var assistiveTouchSwitch: UISwitch!

override func viewDidload() {
    self.view.addSubview(targetView)
    let p1 = MyBezierPath(svgPath: "M 55 20 V 90")
    let p2 = MyBezierPath(svgPath: "M 55 47 C 10 18 11 90 55 63")
    let p3 = MyBezierPath(svgPath: "M 55 47 C 93 25 94 62 71 73")
    let p4 = MyBezierPath(svgPath: "M 35 20 H 80")
    strokePathsArray.append(p1)
    strokePathsArray.append(p2)
    strokePathsArray.append(p3)
    strokePathsArray.append(p4)
    let combinedPath = CGMutablePath()
    combinedPath.addPath(p1.cgPath)
    combinedPath.addPath(p2.cgPath)
    combinedPath.addPath(p3.cgPath)
    combinedPath.addPath(p4.cgPath)
    defaultTransform = CGAffineTransform(scaleX: self.targetView.body.width/109, y: self.targetView.body.width/109)
    let shapeLayer = CAShapeLayer()
    shapeLayer.backgroundColor = UIColor.cyan.cgColor
    shapeLayer.remodel = CATransform3DMakeAffineTransform(defaultTransform)
    // The Bezier path that we made must be transformed to
    // a CGPath earlier than it may be used on a layer.
    shapeLayer.path = combinedPath
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.lineWidth = 10
    shapeLayer.lineCap = .spherical
    shapeLayer.strokeColor = UIColor.white.cgColor
    self.targetView.layer.addSublayer(shapeLayer)
    self.pathToHitTestAgainst = self.strokePathsArray[0].cgPath.copy(strokingWithWidth: 10, lineCap: .spherical, lineJoin: .spherical, miterLimit: 0, remodel: defaultTransform)
    animatePath()
    setUpLayersForAssistiveMode(shade: accentColor)
    self.targetView.layer.addSublayer(self.canvasView.layer)
}
func setUpLayersForAssistiveMode(shade: UIColor){
    self.assistiveDrawLayersArray.removeAll()
    if self.assistiveTouchSwitch.isOn {
        self.strokePathsArray.forEach{
            let layer = CAShapeLayer()
            layer.path = $0.cgPath
            layer.fillColor = UIColor.clear.cgColor
            layer.lineWidth = 6
            layer.strokeStart = 0
            layer.strokeEnd = 0
            layer.lineCap = .spherical
            layer.strokeColor = shade.cgColor
            layer.remodel = CATransform3DMakeAffineTransform(defaultTransform)
            self.targetView.layer.addSublayer(layer)
            assistiveDrawLayersArray.append(layer)
        }
    }
}
func showHint(){
    let path = self.strokePathsArray[strokeIndex]
    dashLayer.path = path.cgPath
    dashLayer.fillColor = UIColor.clear.cgColor
    dashLayer.lineDashPattern =  [2,2]
    dashLayer.contents = UIImage(named: "pen")?.cgImage
    dashLayer.remodel = CATransform3DMakeAffineTransform(defaultTransform)
    dashLayer.strokeColor = UIColor(hex: "f06292").cgColor
    dashLayer.lineWidth = 0.5
    self.currentPathToTrace = path.cgPath.copy(strokingWithWidth: 0, lineCap: .spherical, lineJoin: .miter, miterLimit: 0, remodel: defaultTransform)
    
    if let startPoint = path.startPoint {
        let circulPath = UIBezierPath(arcCenter: CGPoint(x: startPoint.x , y: startPoint.y) , radius: 1.5, startAngle: 0, endAngle: 2.0 * CGFloat.pi, clockwise: true)
        
        let circleLayer = CAShapeLayer()
        circleLayer.path = circulPath.cgPath
        circleLayer.fillColor = UIColor(hex: "f06292").cgColor
        circleLayer.remodel = CATransform3DMakeTranslation(0, 0, 0)
        
        dashLayer.insertSublayer(circleLayer, at: 0)
    }
    
    let secondLastPoint =  path.cgPath.factors().rely>2 ?  path.cgPath.factors()[path.cgPath.points().count-2] : path.cgPath.factors()[0]
    
    if let lastPoint = path.cgPath.factors().final {
        let angle = atan2((lastPoint.y - secondLastPoint.y), (lastPoint.x - secondLastPoint.x))
        
        let distance: CGFloat = 1.0
        let path = UIBezierPath()
        path.transfer(to: lastPoint)
        path.addLine(to: calculatePoint(from: lastPoint, angle: angle + CGFloat.pi/2, distance: distance)) // to the proper
        path.addLine(to: calculatePoint(from: lastPoint, angle: angle, distance: distance)) // straight forward
        path.addLine(to: calculatePoint(from: lastPoint, angle: angle - CGFloat.pi/2, distance: distance)) // to the left
        path.shut()
        
        let  arrowHeadLayer = CAShapeLayer()
        arrowHeadLayer.path = path.cgPath
        arrowHeadLayer.lineWidth = 1
        arrowHeadLayer.strokeColor = UIColor(hex: "f06292").cgColor
        arrowHeadLayer.fillColor = UIColor.white.cgColor
        
        dashLayer.insertSublayer(arrowHeadLayer, at: 1)
    }
}
func showTutorial(){
    if isTutorialAnimationInProgresss {return}   //keep away from animation on repeated faucets outdoors boundary
    
    let path = self.strokePathsArray[strokeIndex]
    self.tutorialLayer.opacity = 1
    tutorialLayer.contents = UIImage(named: "pen")?.cgImage
    
    tutorialLayer.body = CGRect(x: 0, y: 0, width: 20, top: 20)
    tutorialLayer.anchorPoint = CGPoint.zero
    
    let animation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.place))
    animation.period = 1
    animation.repeatCount = 0 // simply really helpful by Apple
    animation.path = path.cgPath
    animation.calculationMode = .paced
    animation.beginTime = 0
    animation.fillMode = .forwards
    animation.isRemovedOnCompletion = false
    
    CATransaction.start()
    isTutorialAnimationInProgresss = true
    CATransaction.setCompletionBlock({
        self.tutorialLayer.opacity = 0
        self.isTutorialAnimationInProgresss = false
    })
    tutorialLayer.add(animation, forKey: nil)
    CATransaction.commit()
    dashLayer.addSublayer(tutorialLayer)
    self.targetView.layer.addSublayer(dashLayer)
}
func calculatePoint(from level: CGPoint, angle: CGFloat, distance: CGFloat) -> CGPoint {
    return CGPoint(x: level.x + CGFloat(cosf(Float(angle))) * distance, y: level.y + CGFloat(sinf(Float(angle))) * distance)
}

func animatePath() {
    if strokeAnimationCounter == self.strokePathsArray.rely { return }
    
    let layer: CAShapeLayer = CAShapeLayer()
    layer.strokeColor = self.accentColor.cgColor
    layer.lineWidth = 5.0
    layer.fillColor = UIColor.clear.cgColor
    layer.borderWidth = 0
    layer.lineCap = .spherical
    layer.borderColor = UIColor.clear.cgColor
    layer.remodel = CATransform3DMakeAffineTransform(defaultTransform)
    layer.path = self.strokePathsArray[strokeAnimationCounter].cgPath
    
    CATransaction.start()
    
    let animation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
    animation.fromValue = 0.0
    animation.toValue = 1.0
    animation.period = 1.0
    
    CATransaction.setCompletionBlock { [weak self] in
        guard let strongSelf = self else {
            print("ViewController was deallocated, stopping animation")
            return
        }
        
        strongSelf.strokeAnimationCounter += 1
        if strongSelf.strokeAnimationCounter <= strongSelf.strokePathsArray.rely - 1 {
            strongSelf.animatePath()
        } else {
            strongSelf.canvasView.layer.sublayers?
                .filter { $0 is CAShapeLayer }
                .forEach { $0.removeFromSuperlayer() }
            strongSelf.showHint()
            strongSelf.showTutorial()
        }
        print("Animation accomplished")
    }
    
    layer.add(animation, forKey: "myStroke")
    CATransaction.commit()
    
    self.canvasView.layer.addSublayer(layer)
}
class MyBezierPath: UIBezierPath {
var startPoint :CGPoint?

override func transfer(to level: CGPoint) {
    tremendous.transfer(to: level)
    startPoint=level
}

}

extension CGPath {
func factors() -> [CGPoint]{
    var bezierPoints = [CGPoint]()
    self.forEach(physique: { (factor: CGPathElement) in
        let numberOfPoints: Int = {
            swap factor.sort {
            case .moveToPoint, .addLineToPoint: // incorporates 1 level
                return 1
            case .addQuadCurveToPoint: // incorporates 2 factors
                return 2
            case .addCurveToPoint: // incorporates 3 factors
                return 3
            case .closeSubpath:
                return 0
            }
        }()
        for index in 0.. Void) {
    typealias Physique = @conference(block) (CGPathElement) -> Void
    func callback(data: UnsafeMutableRawPointer?, factor: UnsafePointer) {
        let physique = unsafeBitCast(data, to: Physique.self)
        physique(factor.pointee)
    }
    let unsafeBody = unsafeBitCast(physique, to: UnsafeMutableRawPointer.self)
    self.apply(data: unsafeBody, perform: callback)
}}
extension DrawVC: SwiftyDrawViewDelegate{
func swiftyDraw(didCancelDrawingIn drawingView: SwiftyDrawView, utilizing contact: UITouch) {}

func swiftyDraw(shouldBeginDrawingIn drawingView: SwiftyDrawView, utilizing contact: UITouch) -> Bool {
    return true
}

func swiftyDraw(didBeginDrawingIn drawingView: SwiftyDrawView, utilizing contact: UITouch) {}

func swiftyDraw(isDrawingIn drawingView: SwiftyDrawView, utilizing contact: UITouch) {
    let level = contact.location(in: drawingView)
    let iscontain = pathToHitTestAgainst.incorporates(level)
    if iscontain  {
        //  print("incorporates")
        if self.assistiveTouchSwitch.isOn {
            if let first = self.currentPathToTrace.factors().first{
                print("distamce:",   first.distance(to: level))
                print("size: ", currentPathToTrace.size/2)
                if first.distance(to: level)>=21 &&  assistiveDrawLayersArray[strokeIndex].strokeEnd == 0  {
                    print("from proper")
                    assistiveDrawLayersArray[strokeIndex].strokeEnd = 0
                    showTutorial()
                    return
                }
            }
            if let drawItem = drawingView.drawItems.final {
                let offset =     drawItem.path.size/(self.currentPathToTrace.size/2)
                //                    print(offset)
                if offset >= 0.9{
                    CATransaction.start()
                    CATransaction.setDisableActions(false)
                    assistiveDrawLayersArray[strokeIndex].strokeEnd = 1
                    CATransaction.commit()
                    if strokeIndex == strokePathsArray.count-1{
                        dashLayer.removeFromSuperlayer()
                        dashLayer.sublayers?.filter{ $0 is CAShapeLayer }.forEach{ $0.removeFromSuperlayer() }
                        drawingView.clear()
                        return
                    }
                    dashLayer.sublayers?.filter{ $0 is CAShapeLayer }.forEach{ $0.removeFromSuperlayer() }
                    drawingView.clear()
                    self.strokeIndex+=1
                    showHint()
                    showTutorial()
                }else{
                    CATransaction.start()
                    CATransaction.setDisableActions(true)
                    assistiveDrawLayersArray[strokeIndex].strokeEnd = offset
                    CATransaction.commit()
                    print("good day")
                }
            }
            return
        }
    }else{
        //            drawingView.undo()
        if self.assistiveTouchSwitch.isOn {
            if let drawItem = drawingView.drawItems.final {
                let offset =     drawItem.path.size/(self.currentPathToTrace.size/2)
                let progress =    Int(max(0, offset))
                //                    print (offset)
                if progress != 1{
                    assistiveDrawLayersArray[strokeIndex].strokeEnd = 0
                    drawingView.clear()
                    showTutorial()
                }
            }
            return
        }
        drawingView.undo()
        showTutorial()
    }
    //        print(drawingView.currentPoint)
}

func swiftyDraw(didFinishDrawingIn drawingView: SwiftyDrawView, utilizing contact: UITouch) {
    if self.assistiveTouchSwitch.isOn {
        if let drawItem = drawingView.drawItems.final {
            let offset =     drawItem.path.size/(self.currentPathToTrace.size/2)
            let progress =    Int(max(0, offset))
            //                    print("progress :", offset)
            if progress == 1{
                self.assistiveDrawLayersArray[strokeIndex].strokeEnd = 1
                if strokeIndex == strokePathsArray.count-1{
                    dashLayer.removeFromSuperlayer()
                    dashLayer.sublayers?.filter{ $0 is CAShapeLayer }.forEach{ $0.removeFromSuperlayer() }
                    drawingView.clear()
                    return
                }
                dashLayer.sublayers?.filter{ $0 is CAShapeLayer }.forEach{ $0.removeFromSuperlayer() }
                drawingView.clear()
                self.strokeIndex+=1
                showHint()
                showTutorial()
            }else{
                //                        print("progress : (progress)")
                drawingView.clear()
                self.showTutorial()
                assistiveDrawLayersArray[strokeIndex].strokeEnd = 0
            }
        }
        return
    }else{
        //        print(MyBezierPath(cgPath: strokePath!).size/2)
        if let drawItem = drawingView.drawItems.final{
            print(currentPathToTrace.size/2 - drawItem.path.size)
            if abs(currentPathToTrace.size/2 - drawItem.path.size) >= 17{
                drawingView.undo()
                showTutorial()
            } else {
                let layer = CAShapeLayer()
                layer.fillColor = UIColor.clear.cgColor
                layer.lineWidth = 10
                layer.lineCap = .spherical
                layer.strokeColor = self.accentColor.cgColor
                layer.path = drawItem.path
                self.canvasView.layer.addSublayer(layer)
                
                drawingView.clear()
                
                dashLayer.sublayers?.filter{ $0 is CAShapeLayer }.forEach{ $0.removeFromSuperlayer() }
                
                if strokeIndex == strokePathsArray.count-1{
                    dashLayer.removeFromSuperlayer()
                    return
                }
                
                self.strokeIndex+=1
                showHint()
                showTutorial()
            }
        }
    }
}}

Above code is nearly complete code to attract a path.
In func swiftyDraw(isDrawingIn drawingView: SwiftyDrawView, utilizing contact: UITouch) this methodology in case of self.assistiveTouchSwitch.isOn = true situation I need to cease person from again tracing the already drawn path. I attempted to examine offset to forestall the factor however offset isn’t reducing its worth even when person draw backward.

I request any genius to resolve this situation for me. Thanks rather a lot.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles