How can I use the same button and action multiple times, but affect the layer instead of the view

Viewed 81

I am trying to use the same button in each createCircle() function, but when I press a button and it runs the handleTap() function, it only applies to the most recently added circle. I would like to use the same button but when I click on an individual button it should run the animation on the one I pressed.

import UIKit

class SecondViewController: UIViewController {
    
    let shapeLayer = CAShapeLayer()
    
    let percentageLabel: UILabel = {
        let label = UILabel()
        label.text = ""
        label.textAlignment = .center
        label.font = UIFont.boldSystemFont(ofSize: 28)
        label.textColor = UIColor(red: 0.59, green: 0.42, blue: 0.23, alpha: 1.00)
        return label
        
    }()
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        _ = createCircle(positionX: 100, positionY: 200)
        _ = createCircle(positionX: 100, positionY: 375)
        _ = createCircle(positionX: 100, positionY: 550)
        _ = createCircle(positionX: 100, positionY: 725)
        _ = createCircle(positionX: 300, positionY: 200)
        _ = createCircle(positionX: 300, positionY: 375)
        _ = createCircle(positionX: 300, positionY: 550)
        _ = createCircle(positionX: 300, positionY: 725)
        
    }
    //MARK: - Create Circle
    func createCircle(positionX: Int, positionY: Int) -> (CGPoint) {
        let trackLayer = CAShapeLayer()
        
        let button = UIButton(type: .custom)
        button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        button.layer.cornerRadius = 0.5 * button.bounds.size.width
        button.clipsToBounds = true
        button.center = view.center
        button.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
        view.addSubview(button)
        
        let circularPath = UIBezierPath(arcCenter: .zero, radius: 50, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
        
        trackLayer.path = circularPath.cgPath
        
        trackLayer.strokeColor = UIColor(red: 0.82, green: 0.69, blue: 0.52, alpha: 1.00).cgColor
        trackLayer.fillColor = UIColor.clear.cgColor
        trackLayer.lineWidth = 10
        trackLayer.position = CGPoint(x: positionX, y: positionY)
        view.layer.addSublayer(trackLayer)
        
        shapeLayer.path = circularPath.cgPath
        
        shapeLayer.strokeColor = UIColor(red: 0.59, green: 0.42, blue: 0.23, alpha: 1.00).cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineWidth = 10
        shapeLayer.lineCap = CAShapeLayerLineCap.round
        shapeLayer.position = CGPoint(x: positionX, y: positionY)
        shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi / 2, 0, 0, 1)
        
        view.addSubview(percentageLabel)
        
        percentageLabel.frame = CGRect(x: 0, y: 0, width: 150, height: 150)
        percentageLabel.center = CGPoint(x: positionX, y: positionY)
        return CGPoint(x: positionX, y: positionY)
        
    }
    //MARK: - Tap function
    var done = 0
    var toDo = 0
    
    @objc func handleTap(sender: UIButton) {
        toDo = 5
        if done < toDo {
            done += 1
        } else {
            done -= toDo
        }
        let percentage = CGFloat(done) / CGFloat(toDo)
        percentageLabel.text = "\(Int(percentage * 100))%"
        
        DispatchQueue.main.async {
            self.shapeLayer.strokeEnd = percentage
        }
        view.layer.addSublayer(shapeLayer)
    }
}
Main View

This is a picture of what happens when I run my code. When I click on any of the pictured buttons, instead of running the progress bar animation on whichever button I pressed, it only runs it on the button that was added last to the view controller.

1 Answers

Instead of using createCircle() in your view controller, make a new file for it and initialize the function like this inside.

import UIKit

class Button: UIButton {
    override init(frame: CGRect) {
        super.init(frame: frame)
        createCircle()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    let percentageLabel: UILabel = {
        let label = UILabel()
        label.text = ""
        label.textAlignment = .center
        label.font = UIFont.boldSystemFont(ofSize: 28)
        label.textColor = UIColor(red: 0.59, green: 0.42, blue: 0.23, alpha: 1.00)
        return label
        
    }()
    let shapeLayer = CAShapeLayer()
    
    func createCircle() {
        
        let trackLayer = CAShapeLayer()
        
        let button = UIButton(type: .custom)
        button.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        button.layer.cornerRadius = 0.5 * button.bounds.size.width
        button.clipsToBounds = true
        button.center = center
        button.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
        addSubview(button)
        
        let circularPath = UIBezierPath(arcCenter: .zero, radius: 50, startAngle: 0, endAngle: 2 * CGFloat.pi, clockwise: true)
        
        trackLayer.path = circularPath.cgPath
        
        trackLayer.strokeColor = UIColor(red: 0.82, green: 0.69, blue: 0.52, alpha: 1.00).cgColor
        trackLayer.fillColor = UIColor.clear.cgColor
        trackLayer.lineWidth = 10
        trackLayer.position = center
        layer.addSublayer(trackLayer)
        
        shapeLayer.path = circularPath.cgPath
        
        shapeLayer.strokeColor = UIColor(red: 0.59, green: 0.42, blue: 0.23, alpha: 1.00).cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineWidth = 10
        shapeLayer.lineCap = CAShapeLayerLineCap.round
        shapeLayer.position = center
        shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi / 2, 0, 0, 1)
        
        addSubview(percentageLabel)
        
        percentageLabel.frame = CGRect(x: 0, y: 0, width: 150, height: 150)
        //  percentageLabel.center = CGPoint(x: positionX, y: positionY)
        //  return CGPoint(x: positionX, y: positionY)
        
    }
    var done = 0
    var toDo = 0
    
    @objc func handleTap(sender: UIButton) {
        toDo = 5
        if done < toDo {
            done += 1
        } else {
            done -= toDo
        }
        let percentage = CGFloat(done) / CGFloat(toDo)
        percentageLabel.text = "\(Int(percentage * 100))%"
        
        DispatchQueue.main.async {
            self.shapeLayer.strokeEnd = percentage
        }
        layer.addSublayer(shapeLayer)
    }
}

You can remove the code from your secondViewController that has to do with your button and call the circle function inside of your viewDidLoad(). The problem was that your animation was affecting your view (not an individual circle), by creating a class for your circle button, you can call it in your secondViewController and it will affect only the circle in which the button was pressed.

Related