Search code examples
swiftprogress-barcashapelayerdownload-manager

How do I remove a CAShapeLayer in progressbar circle


I have the following code, which I am implementing to download a PDF with a progress circle.

It is working for me, first I check that the file is already saved in the "Documents" directory and if it is true I simply show the PDF, and if the pdf file is not yet loaded then begindDownloadFile ()

lass ViewController: UIViewController, URLSessionDownloadDelegate {
    @IBOutlet weak var pdfView: PDFView!

    let urlString = "https://d0.awsstatic.com/whitepapers/KMS-Cryptographic-Details.pdf"

    let shapeLayer = CAShapeLayer()

    let percentageLabel: UILabel = {
        let label = UILabel()
        label.text = "Descargar"
        label.textAlignment = .center
        label.font = UIFont.boldSystemFont(ofSize: 16)
        return label
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(percentageLabel)
        percentageLabel.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        percentageLabel.center = view.center

        //start drawing circle

        let center  = view.center

        //create the track layer, the softer color underneath the bar that it is going to fill
        let trackLayer = CAShapeLayer()
        let circularPath = UIBezierPath(arcCenter: .zero, radius: 100, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)

        trackLayer.path = circularPath.cgPath
        trackLayer.strokeColor = UIColor.lightGray.cgColor
        trackLayer.lineWidth = 10 //the width of the bar
        trackLayer.fillColor = UIColor.clear.cgColor //amek the middle area have clear color
        trackLayer.lineCap = kCALineCapRound //this to make the bar has rounded corner
        trackLayer.position = center
        trackLayer.opacity = 1
        view.layer.addSublayer(trackLayer)

//        let circularPath = UIBezierPath(arcCenter: center, radius: 100, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true) //start angle like this to have the bar start at 12 o'clock
        shapeLayer.path = circularPath.cgPath
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.lineWidth = 10 //the width of the bar
        shapeLayer.fillColor = UIColor.clear.cgColor //amek the middle area have clear color
        shapeLayer.lineCap = kCALineCapRound //this to make the bar has rounded corner
        shapeLayer.strokeEnd = 0
        shapeLayer.position = center

        shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi / 2, 0, 0, 1)

        view.layer.addSublayer(shapeLayer)

        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleTap)))


      let myUrl = URL(string: urlString)

        // then lets create your document folder url
        let documentsDirectoryURL =  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

        // lets create your destination file url
        let destinationUrl = documentsDirectoryURL.appendingPathComponent((myUrl?.lastPathComponent)!)
        print(destinationUrl)


        if FileManager.default.fileExists(atPath: destinationUrl.path) {
            print("The file already exists at path")

            // hidden label and remove sublayers (circle)
            //self.percentageLabel.isHidden = true
            //shapeLayer.removeFromSuperlayer()
            //trackLayer.removeFromSuperlayer()

            trackLayer.removeFromSuperlayer()

            /************** show pdf ****************/
            let pdfUrl = destinationUrl.path
            let rutafile = URL(fileURLWithPath: pdfUrl)
            print(pdfUrl)
            if let document = PDFDocument(url: rutafile) {
                self.pdfView.autoScales = true
                self.pdfView.document = document
            }
            /************** show pdf ****************/


        } else{

             begindDownloadFile()


            }




    } // end DidLoad



    //the require func for URLSessionDownloadDelegate delegate
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        print("finish downloading file")

    }

    //optional func from the delegate URLSessionDownloadDelegate
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
//        print(totalBytesWritten, totalBytesExpectedToWrite)
        let percentage = CGFloat(totalBytesWritten) / CGFloat(totalBytesExpectedToWrite)
        print("percentage : ", percentage)

        //this need to be in the main thread because the url session downloading is not on the main thread, so if without DispatchQueue.main, then the UI wont update
        DispatchQueue.main.async {
            self.percentageLabel.text = "\(Int(percentage * 100))%"
            self.shapeLayer.strokeEnd = percentage
        }

    }

    private func begindDownloadFile(){

        //this fix the bar going back and forth
        shapeLayer.strokeEnd = 0

        let configuration = URLSessionConfiguration.default
        let operationQueue = OperationQueue()
        let urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: operationQueue)

        guard let url = URL(string: urlString) else { return }
        let downloadTask = urlSession.downloadTask(with: url)



        /****/

        // then lets create your document folder url
        let documentsDirectoryURL =  FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

        // lets create your destination file url
        let destinationUrl = documentsDirectoryURL.appendingPathComponent(url.lastPathComponent)
        print(destinationUrl)


        // you can use NSURLSession.sharedSession to download the data asynchronously
        URLSession.shared.downloadTask(with: url, completionHandler: { (location, response, error) -> Void in



            guard let location = location, error == nil else { return }
            do {
                // after downloading your file you need to move it to your destination url
                try FileManager.default.moveItem(at: location, to: destinationUrl)
                print("File moved to documents folder")
                print("file has already been downloaded")




                DispatchQueue.main.async {

                    // escondemos label y removemos sublayers (circulo)

                    self.percentageLabel.isHidden = true
                    //self.shapeLayer.removeFromSuperlayer()
                    //self.trackLayer.removeFromSuperlayer()

                    //self.shapeLayer.removeAllAnimations()
                    self.shapeLayer.opacity = 0



                    /************** show pdf ****************/
                    let pdfUrl = destinationUrl.path
                    let rutafile = URL(fileURLWithPath: pdfUrl)
                    print(pdfUrl)
                    if let document = PDFDocument(url: rutafile) {
                        self.pdfView.autoScales = true
                        self.pdfView.document = document
                    }

                    /************** show pdf ****************/


                }


            } catch let error as NSError {
                print(error.localizedDescription)
            }





        }).resume()



        /***/



      downloadTask.resume()





    }

    fileprivate func animateCircle() {
        let basicAnimation = CABasicAnimation(keyPath: "strokeEnd") //animate the shapeLayer.strokeEnd
        basicAnimation.toValue = 1

        basicAnimation.duration = 2


        //need these 2 lines to make the bar stopped at the final point, if not it will be removed upon completion
        basicAnimation.fillMode = kCAFillModeForwards
        basicAnimation.isRemovedOnCompletion = false


        shapeLayer.add(basicAnimation,forKey: "customString")


    }

    @objc private func handleTap(){
        print("animate here")

        //begindDownloadFile()

//        animateCircle()//custom string for forKey value, not sure where will use it later

    }






}

My problem is that I can not hide the circle, I have tried inside DispatchQueue.main.async withself.shapeLayer.removeFromSuperlayer ()and it does not work for me, either withself.shapeLayer.opacity = 0

I would appreciate any suggestions

screenshot


Solution

  • Your shapeLayer is red, your trackLayer is gray. What you are showing in the image is your shapeLayer removed but your trackLayer persisting which you would also need to call removeFromSuperlayer.

    Also move the initial declaration of your trackLayer out of your viewDidLoad() and put it with your shapeLayer declaration so you can access it outside just that method. ie:

    let urlString = "https://d0.awsstatic.com/whitepapers/KMS-Cryptographic-Details.pdf"
    
    let shapeLayer = CAShapeLayer()
    let trackLayer = CAShapeLayer()