This is the animation to complete:

First add the required code. Here, you need to replace the ViewController of the storyboard with
TableViewController, cancel the Under Top Bars and Under Bottom Bars to, and then
Embed in Navigation Controller,
import UIKit func delay(seconds: Double, completion: @escaping ()-> Void) { DispatchQueue.main.asyncAfter(deadline: .now() + seconds, execute: completion) } class ViewController: UITableViewController { let packItems = ["Ice cream money", "Great weather", "Beach ball", "Swimsuit for him", "Swimsuit for her", "Beach games", "Ironing board", "Cocktail mood", "Sunglasses", "Flip flops", "Spare flip flops"] override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") self.title = "try" self.navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white] self.tableView.rowHeight = 64.0 self.view.backgroundColor = UIColor(red: 0.0, green: 154.0/255.0, blue: 222.0/255.0, alpha: 1.0) } // MARK: Table View methods override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 11 } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell cell.accessoryType = .none cell.textLabel!.text = packItems[indexPath.row] cell.imageView!.image = UIImage(named: "summericons_100px_\(indexPath.row).png") return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) } }
(slide to show more)
Then create a file, create a MyRefreshView class, and init needs to pass in frame and UIScrollView, which is used to listen to external pull actions.
class MyRefreshView: UIView, UIScrollViewDelegate { var scrollView: UIScrollView init(frame: CGRect, scrollView: UIScrollView) { self.scrollView = scrollView super.init(frame: frame) //add the background image let imgView = UIImageView(image: UIImage(named: "refresh-view-bg.png")) imgView.frame = bounds imgView.contentMode = .scaleAspectFill imgView.clipsToBounds = true addSubview(imgView) } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
(slide to show more)
Next, use ShapeLayer and layer to create circular points and aircraft images.
//Declare properties let ovalShapeLayer: CAShapeLayer = CAShapeLayer() let airplaneLayer: CALayer = CALayer()
(slide to show more)
Then set the relevant attributes. Here, the radius of the circle is set to half of the view height * 0.8. Here, lineDashPattern is the dashed pattern (NSNumbers array) applied when creating the stroked version of the path. The default value is nil. If it is set to [2, 3], the previous line will be cut into one by one. Then the plane is set to the hidden state.
ovalShapeLayer.strokeColor = UIColor.white.cgColor ovalShapeLayer.fillColor = UIColor.clear.cgColor ovalShapeLayer.lineWidth = 4.0 ovalShapeLayer.lineDashPattern = [2, 3] let refreshRadius = frame.size.height/2 * 0.8 ovalShapeLayer.path = UIBezierPath(ovalIn: CGRect( x: frame.size.width/2 - refreshRadius, y: frame.size.height/2 - refreshRadius, width: 2 * refreshRadius, height: 2 * refreshRadius) ).cgPath layer.addSublayer(ovalShapeLayer) let airplaneImage = UIImage(named: "airplane.png")! airplaneLayer.contents = airplaneImage.cgImage airplaneLayer.bounds = CGRect(x: 0.0, y: 0.0, width: airplaneImage.size.width, height: airplaneImage.size.height) airplaneLayer.position = CGPoint( x: frame.size.width/2 + frame.size.height/2 * 0.8, y: frame.size.height/2) layer.addSublayer(airplaneLayer) airplaneLayer.opacity = 0.0
(slide to show more)
Use UIScrollViewDelegate here, then call scrollViewDidScroll and
scrollViewWillEndDragging to monitor the pulling action and height.
func scrollViewDidScroll(_ scrollView: UIScrollView) { } func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { }
(slide to show more)
Then add refreshVie in viewController. Declare a RefreshView property.
var refreshView: RefreshView!
Then, set the property in viewDidLoad and add it as a child View.
let refreshRect = CGRect(x: 0.0, y: -kRefreshViewHeight, width: view.frame.size.width, height: kRefreshViewHeight) refreshView = RefreshView(frame: refreshRect, scrollView: self.tableView) refreshView.delegate = self view.addSubview(refreshView)
(slide to show more)
Rewrite the scrollViewDidScroll and scrollViewWillEndDragging methods in the viewController, then call the methods in refreshView correspondingly and pass in the parameters.
// MARK: Scroll view methods override func scrollViewDidScroll(_ scrollView: UIScrollView) { refreshView.scrollViewDidScroll(scrollView) } override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { refreshView.scrollViewWillEndDragging(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset) }
(slide to show more)
Then perform corresponding processing in the scrollViewDidScroll and scrollViewWillEndDragging methods in the refreshView.
Here, you need to judge the progress according to the rolling height, and specify a CGFloat attribute of progress first.
var progress: CGFloat = 0.0
Calculate the height of upward scrolling in scrollViewDidScroll, then process the size of its own view and compare it with 1 to get the minimum value, and then set the strokeEnd of ovalShapeLayer and the opacity of airplane layer according to the obtained progress.
func scrollViewDidScroll(_ scrollView: UIScrollView) { let offsetY = max(-(scrollView.contentOffset.y + scrollView.contentInset.top), 0.0) progress = min(max(offsetY / frame.size.height, 0.0), 1.0) redrawFromProgress(self.progress) } func redrawFromProgress(_ progress: CGFloat) { ovalShapeLayer.strokeEnd = progress airplaneLayer.opacity = Float(progress) }
(slide to show more)
Next, add animation for ovalShapeLayer and airplaneLayer. Here, change the contentInset of the scrollView to display the view, add the animation of strokeStart and strokeEnd for the ovalShapeLayer, and then add the change of the position around the circle and the change of the picture angle for the airplane layer.
func beginRefreshing() { UIView.animate(withDuration: 0.3) { var newInsets = self.scrollView.contentInset newInsets.top += self.frame.size.height self.scrollView.contentInset = newInsets } let strokeStartAnimation = CABasicAnimation(keyPath: "strokeStart") strokeStartAnimation.fromValue = -0.5 strokeStartAnimation.toValue = 1.0 let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd") strokeEndAnimation.fromValue = 0.0 strokeEndAnimation.toValue = 1.0 let strokeAnimationGroup = CAAnimationGroup() strokeAnimationGroup.duration = 1.5 strokeAnimationGroup.repeatDuration = 5.0 strokeAnimationGroup.animations = [strokeStartAnimation, strokeEndAnimation] ovalShapeLayer.add(strokeAnimationGroup, forKey: nil) let flightAnimation = CAKeyframeAnimation(keyPath: "position") flightAnimation.path = ovalShapeLayer.path flightAnimation.calculationMode = .paced let airplaneOrientationAnimation = CABasicAnimation(keyPath: "transform.rotation") airplaneOrientationAnimation.fromValue = 0 airplaneOrientationAnimation.toValue = 2.0 * .pi let flightAnimationGroup = CAAnimationGroup() flightAnimationGroup.duration = 1.5 flightAnimationGroup.repeatDuration = 5.0 flightAnimationGroup.animations = [flightAnimation, airplaneOrientationAnimation] airplaneLayer.add(flightAnimationGroup, forKey: nil) }
(slide to show more)
After the animation, you need to adjust the contentInset of scrollView back.
func endRefreshing() { UIView.animate(withDuration: 0.3, delay:0.0, options: .curveEaseOut, animations: { var newInsets = self.scrollView.contentInset newInsets.top -= self.frame.size.height self.scrollView.contentInset = newInsets }, completion: {_ in //finished } ) }
(slide to show more)
Then declare an attribute isRefreshing to determine whether the animation is in progress
isRefreshing = true
Set it to true in beginRefreshing and false in endRefreshing. Then judge in scrollViewDidScroll that redrawFromProgress will not be called if the animation is being executed.
if !isRefreshing { redrawFromProgress(self.progress) }
(slide to show more)
Judge from scrollviewwillenddrawing that if no animation is being executed and progress is greater than or equal to 1, the animation will be executed.
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { if !isRefreshing && self.progress >= 1.0 { beginRefreshing() } }
(slide to show more)
It's up to the outside world to decide when to end the refresh, so you can use delegate or closure to notify the outside world to start the animation.
Here we declare a closure
var refreshViewDidRefresh :(() -> Void)?
If it is judged that it is not empty in scrollViewWillEndDragging, call
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { if !isRefreshing && self.progress >= 1.0 { if (refreshViewDidRefresh != nil) { refreshViewDidRefresh!() } beginRefreshing() } }
(slide to show more)
Used in ViewController
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { if !isRefreshing && self.progress >= 1.0 { if (refreshViewDidRefresh != nil) { refreshViewDidRefresh!() } beginRefreshing() } }
(slide to show more)
So the animation is finished.
Complete code
import UIKit func delay(seconds: Double, completion: @escaping ()-> Void) { DispatchQueue.main.asyncAfter(deadline: .now() + seconds, execute: completion) } let kRefreshViewHeight: CGFloat = 110.0 class ViewController: UITableViewController { let packItems = ["Ice cream money", "Great weather", "Beach ball", "Swimsuit for him", "Swimsuit for her", "Beach games", "Ironing board", "Cocktail mood", "Sunglasses", "Flip flops", "Spare flip flops"] var refreshView: MyRefreshView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. self.view.backgroundColor = .clear self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") self.title = "try" self.navigationController?.navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white] self.tableView.rowHeight = 64.0 self.view.backgroundColor = UIColor(red: 0.0, green: 154.0/255.0, blue: 222.0/255.0, alpha: 1.0) let refreshRect = CGRect(x: 0.0, y: -kRefreshViewHeight, width: view.frame.size.width, height: kRefreshViewHeight) refreshView = MyRefreshView(frame: refreshRect, scrollView: self.tableView) refreshView.refreshViewDidRefresh = { [weak self] in delay(seconds: 4) { self?.refreshView.endRefreshing() } } view.addSubview(refreshView) } // MARK: Table View methods override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 11 } override func scrollViewDidScroll(_ scrollView: UIScrollView) { refreshView.scrollViewDidScroll(scrollView) } override func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { refreshView.scrollViewWillEndDragging(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset) } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell cell.accessoryType = .none cell.textLabel!.text = packItems[indexPath.row] cell.imageView!.image = UIImage(named: "summericons_100px_\(indexPath.row).png") return cell } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) } }
(slide to show more)
import UIKit import QuartzCore protocol MyRefreshViewDelegate: class { func refreshViewDidRefresh(_ refreshView: MyRefreshView) } class MyRefreshView: UIView, UIScrollViewDelegate { var scrollView: UIScrollView weak var delegate: MyRefreshViewDelegate? let ovalShapeLayer: CAShapeLayer = CAShapeLayer() let airplaneLayer: CALayer = CALayer() var progress: CGFloat = 0.0 var isRefreshing = false var refreshViewDidRefresh :(() -> Void)? init(frame: CGRect, scrollView: UIScrollView) { self.scrollView = scrollView super.init(frame: frame) //add the background image let imgView = UIImageView(image: UIImage(named: "refresh-view-bg.png")) imgView.frame = bounds imgView.contentMode = .scaleAspectFill imgView.clipsToBounds = true addSubview(imgView) ovalShapeLayer.strokeColor = UIColor.white.cgColor ovalShapeLayer.fillColor = UIColor.clear.cgColor ovalShapeLayer.lineWidth = 4.0 ovalShapeLayer.lineDashPattern = [2, 3] let refreshRadius = frame.size.height/2 * 0.8 ovalShapeLayer.path = UIBezierPath(ovalIn: CGRect( x: frame.size.width/2 - refreshRadius, y: frame.size.height/2 - refreshRadius, width: 2 * refreshRadius, height: 2 * refreshRadius) ).cgPath layer.addSublayer(ovalShapeLayer) let airplaneImage = UIImage(named: "airplane.png")! airplaneLayer.contents = airplaneImage.cgImage airplaneLayer.bounds = CGRect(x: 0.0, y: 0.0, width: airplaneImage.size.width, height: airplaneImage.size.height) airplaneLayer.position = CGPoint( x: frame.size.width/2 + frame.size.height/2 * 0.8, y: frame.size.height/2) layer.addSublayer(airplaneLayer) airplaneLayer.opacity = 0.0 } // MARK: Scroll View Delegate methods func scrollViewDidScroll(_ scrollView: UIScrollView) { let offsetY = max(-(scrollView.contentOffset.y + scrollView.contentInset.top), 0.0) progress = min(max(offsetY / frame.size.height, 0.0), 1.0) if !isRefreshing { redrawFromProgress(self.progress) } } func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { if !isRefreshing && self.progress >= 1.0 { if (refreshViewDidRefresh != nil) { refreshViewDidRefresh!() } beginRefreshing() } } func redrawFromProgress(_ progress: CGFloat) { ovalShapeLayer.strokeEnd = progress airplaneLayer.opacity = Float(progress) } func beginRefreshing() { isRefreshing = true UIView.animate(withDuration: 0.3) { var newInsets = self.scrollView.contentInset newInsets.top += self.frame.size.height self.scrollView.contentInset = newInsets } let strokeStartAnimation = CABasicAnimation(keyPath: "strokeStart") strokeStartAnimation.fromValue = -0.5 strokeStartAnimation.toValue = 1.0 let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd") strokeEndAnimation.fromValue = 0.0 strokeEndAnimation.toValue = 1.0 let strokeAnimationGroup = CAAnimationGroup() strokeAnimationGroup.duration = 1.5 strokeAnimationGroup.repeatDuration = 5.0 strokeAnimationGroup.animations = [strokeStartAnimation, strokeEndAnimation] ovalShapeLayer.add(strokeAnimationGroup, forKey: nil) let flightAnimation = CAKeyframeAnimation(keyPath: "position") flightAnimation.path = ovalShapeLayer.path flightAnimation.calculationMode = .paced let airplaneOrientationAnimation = CABasicAnimation(keyPath: "transform.rotation") airplaneOrientationAnimation.fromValue = 0 airplaneOrientationAnimation.toValue = 2.0 * .pi let flightAnimationGroup = CAAnimationGroup() flightAnimationGroup.duration = 1.5 flightAnimationGroup.repeatDuration = 5.0 flightAnimationGroup.animations = [flightAnimation, airplaneOrientationAnimation] airplaneLayer.add(flightAnimationGroup, forKey: nil) } func endRefreshing() { isRefreshing = false UIView.animate(withDuration: 0.3, delay:0.0, options: .curveEaseOut, animations: { var newInsets = self.scrollView.contentInset newInsets.top -= self.frame.size.height self.scrollView.contentInset = newInsets }, completion: {_ in //finished } ) } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } }
(slide to show more)
Original link:
https://blog.csdn.net/LinShunIos/article/details/121290126