I have a class called CategoryCell which us UICollectionViewCell.
On the CellForItemAt function:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
drawfunction.draw_Footing(withView: cell.view)
the drawing will be in the cell.view.
draw_Footing functions is a function to draw some lines, and it is located in drawfunction class which it NSObject. in the same class, I have the function call animateShape which can animate a single line between two points (CGpoint).
func animateShape(view: UIView, p1: CGpoint, p2: CGPoint) {
let shapeLayer = CAShapeLayer()
shapeLayer.removeFromSuperlayer()
// create whatever path you want
shapeLayer.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
shapeLayer.strokeColor = color.cgColor
shapeLayer.lineWidth = linewidth//CGFloat(1.5)
shapeLayer.path = path.cgPath
// animate it
view.layer.addSublayer(shapeLayer)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.duration = duration//0.5
shapeLayer.add(animation, forKey: "MyAnimation")
}
I have 4 points G1, G2, G3, G4. I need to animate a line between these 4 points. So, if I do:
animateShape(view, p1: G1, p2: G2)
animateShape(view, p1: G2, p2: G3)
animateShape(view, p1: G3, p2: G4)
All the line will be animated in the same time. I need to animate first the line between G1 and G2, and after completion, need to animate the line between G2 and G3 and not in the same time. I tried to include dispatchQueue, but I am not sure and I don't know how.
Any advise?
CodePudding user response:
The things is, I do not see how the path
was actually created using your points p1
and p2
Anyways, I am assuming your end goal is to do a drawing line path animation in a UICollectionViewCell
and that is what I tried to achieve based on the given the description in your question.
First the drawing class:
class DrawFunction: NSObject
{
weak var shapeLayer: CAShapeLayer?
// Change as you wish
let duration = 2.0
// Responsible for drawing the lines from any number of points
func drawFooting(points: [CGPoint])
{
guard !points.isEmpty else { return }
// Remove old drawings
shapeLayer?.removeFromSuperlayer()
let path = UIBezierPath()
// This is actual drawing path using the points
// I don't see this in your code
for (index, point) in points.enumerated()
{
// first pair of points
if index == 0
{
// Move to the starting point
path.move(to: point)
continue
}
// Draw a line from the previous point to the current
path.addLine(to: point)
}
// Create a shape layer to visualize the path
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = randomColor().cgColor
shapeLayer.lineWidth = 5
shapeLayer.path = path.cgPath
self.shapeLayer = shapeLayer
}
// Animate function to be called after shape has been drawn
// by specifying the view to show this animation in
func animateShape(in view: UIView)
{
if let shapeLayer = shapeLayer
{
view.layer.addSublayer(shapeLayer)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.duration = duration
shapeLayer.add(animation, forKey: "MyAnimation")
}
}
// You can ignore this function, just for convenience
private func randomColor() -> UIColor
{
let red = CGFloat(arc4random_uniform(256)) / 255.0
let blue = CGFloat(arc4random_uniform(256)) / 255.0
let green = CGFloat(arc4random_uniform(256)) / 255.0
return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
}
}
Then basic custom cell set up, nothing fancy, just added for completeness
// Basic Cell, nothing unique here
class CategoryCell: UICollectionViewCell
{
static let identifier = "cell"
override init(frame: CGRect)
{
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}
private func configure()
{
contentView.backgroundColor = .lightGray
contentView.layer.cornerRadius = 5.0
contentView.clipsToBounds = true
}
}
The view controller set up where the most interesting parts are in the willDisplay cell
function
class LineAnimateVC: UICollectionViewController
{
// Random points to draw lines
let points = [[CGPoint(x: 0.0, y: 10),
CGPoint(x: UIScreen.main.bounds.midX, y: 10),
CGPoint(x: UIScreen.main.bounds.midX, y: 50)],
[CGPoint(x: 0.0, y: 10),
CGPoint(x: UIScreen.main.bounds.midX, y: 10),
CGPoint(x: UIScreen.main.bounds.midX, y: 50),
CGPoint(x: UIScreen.main.bounds.maxX, y: 50)],
[CGPoint(x: 0.0, y: 10),
CGPoint(x: UIScreen.main.bounds.midX, y: 10),
CGPoint(x: UIScreen.main.bounds.midX, y: 50),
CGPoint(x: UIScreen.main.bounds.midX 40, y: 50),
CGPoint(x: UIScreen.main.bounds.midX 40, y: UIScreen.main.bounds.maxY),
CGPoint(x: UIScreen.main.bounds.maxX, y: UIScreen.main.bounds.maxY)],
[CGPoint(x: 0.0, y: 10),
CGPoint(x: UIScreen.main.bounds.midX, y: 10),
CGPoint(x: UIScreen.main.bounds.midX, y: 50)],
[CGPoint(x: 0.0, y: 10),
CGPoint(x: UIScreen.main.bounds.midX, y: 10),
CGPoint(x: UIScreen.main.bounds.midX, y: 50),
CGPoint(x: UIScreen.main.bounds.midX 40, y: 50),
CGPoint(x: UIScreen.main.bounds.midX 40, y: UIScreen.main.bounds.maxY),
CGPoint(x: UIScreen.main.bounds.maxX, y: UIScreen.main.bounds.maxY)]
]
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = .white
title = "Line animate"
collectionView.register(CategoryCell.self,
forCellWithReuseIdentifier: CategoryCell.identifier)
collectionView.backgroundColor = .white
}
// Number of cells equals to points we have
override func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int
{
return points.count
}
override func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CategoryCell.identifier,
for: indexPath) as! CategoryCell
return cell
}
// Add animation when cell is about to be displayed
override func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
let cell = cell as! CategoryCell
// Draw the path and perform the animation
let drawingFunction = DrawFunction()
drawingFunction.drawFooting(points: points[indexPath.row])
drawingFunction.animateShape(in: cell.contentView)
}
}
Just for completeness, my flow layout set up
// Basic set up stuff
extension LineAnimateVC: UICollectionViewDelegateFlowLayout
{
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
sizeForItemAt indexPath: IndexPath) -> CGSize
{
return CGSize(width: collectionView.frame.width, height: 300)
}
func collectionView(_ collectionView: UICollectionView,
layout collectionViewLayout: UICollectionViewLayout,
minimumLineSpacingForSectionAt section: Int) -> CGFloat
{
return 20
}
}
This gives me an animated path in the collectionview cell
Hope this gives you some ideas to achieve your task
Update
Based on OP, Xin Lok's comment:
However still did not get what I want, lets say I have path1 = [p1,p2,p3,p4,p5] and path2 = [m1,m2,m3], if I run drawFooting(points: path1) and drawFooting(path2), both of the 2 paths will be animated in the same time , and this what I don't want, I need to complete animation for Path1, and then after finish to proceed with animation of Path2. I tried to insert sleep, but it did not work
Based on that comment, One way I can think of achieving that is to I think the key is to reuse and persist with the shape layer and the path.
Here are some updates I made based on that conclusion
First I just made a simple struct so we can create lines easily
struct Line
{
var points: [CGPoint] = []
init(_ points: [CGPoint])
{
self.points = points
}
}
Then I create some random lines and grouped them in an array
// Random lines
// Random lines
let line1 = Line([CGPoint(x: 0, y: 10),
CGPoint(x: UIScreen.main.bounds.midX, y: 10)])
let line2 = Line([CGPoint(x: 0, y: 70),
CGPoint(x: UIScreen.main.bounds.midX, y: 70),
CGPoint(x: UIScreen.main.bounds.midX, y: 100)])
let line3 = Line([CGPoint(x: 0, y: 150),
CGPoint(x: UIScreen.main.bounds.midX, y: 110),
CGPoint(x: UIScreen.main.bounds.maxX, y: 190)])
let line4 = Line([CGPoint(x: 0, y: 210),
CGPoint(x: UIScreen.main.bounds.maxX / 4, y: 235),
CGPoint(x: UIScreen.main.bounds.maxX * 0.75, y: 220),
CGPoint(x: UIScreen.main.bounds.maxX,
y: UIScreen.main.bounds.maxY)])
var lineGroups: [[Line]] = []
private func setLines()
{
// First cell, it should draw lines in the order 3 -> 1 -> 2
// Second cell, in the order 4 -> 3 -> 2 -> 1
lineGroups = [[line3, line1, line2],
[line4, line3, line2, line1]]
}
Importantly note the line order in each array, because this is the order they will be drawn
In the drawing class, I made some changes to persist the CAShapeLayer
and path
A special mention to jrturton in the comments for suggesting CGMutablePath
and simplifying the path creation.
class DrawFunction: NSObject
{
weak var shapeLayer: CAShapeLayer?
var path: CGMutablePath?
// Change as you wish
let duration = 5.0
// Responsible for drawing the lines from any number of points
func drawFooting(line: Line)
{
var shapeLayer = CAShapeLayer()
if self.shapeLayer != nil
{
shapeLayer = self.shapeLayer!
}
if path == nil
{
path = CGMutablePath()
}
// Thank you @jrturton for this
path?.addLines(between: line.points)
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = randomColor().cgColor
shapeLayer.lineWidth = 5
shapeLayer.path = path
self.shapeLayer = shapeLayer
}
// Animate function to be called after shape has been drawn
// by specifying the view to show this animation in
func animateShape(in view: UIView)
{
if let shapeLayer = shapeLayer
{
view.layer.addSublayer(shapeLayer)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.duration = duration
shapeLayer.add(animation, forKey: "MyAnimation")
}
}
// You can ignore this function, just for convenience
private func randomColor() -> UIColor
{
let red = CGFloat(arc4random_uniform(256)) / 255.0
let blue = CGFloat(arc4random_uniform(256)) / 255.0
let green = CGFloat(arc4random_uniform(256)) / 255.0
return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
}
}
Then some minor changes in the collectionview cell configuration
// Number of cells equals to lines we have
override func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int
{
return lineGroups.count
}
override func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
let cell
= collectionView.dequeueReusableCell(withReuseIdentifier: CategoryCell.identifier,
for: indexPath) as! CategoryCell
return cell
}
// Add animation when cell is about to be displayed
override func collectionView(_ collectionView: UICollectionView,
willDisplay cell: UICollectionViewCell,
forItemAt indexPath: IndexPath)
{
let cell = cell as! CategoryCell
let lines = lineGroups[indexPath.item]
// Draw the path and perform the animation
let drawingFunction = DrawFunction()
for line in lines
{
drawingFunction.drawFooting(line: line)
}
drawingFunction.animateShape(in: cell.contentView)
}
Now again, for convenience, remember the order in which they should be drawn:
First cell, it should draw lines in the order 3 -> 1 -> 2
Second cell, in the order 4 -> 3 -> 2 -> 1
The end result: