dynamic改变基于scrollView的位置

我有一个“U”形UIBezierPath ,我用myImage.layer作为animation的path 。 我也有一个scrollView。 我的目标是有一个自定义的“拉到刷新”animation。

我遇到的问题是,我希望我的myImage.layer根据scrollView滚动多less进行更新。

当scrollView被拉下时, myImage.layer沿着一个“U”形pathanimation。 这是我作为UIBezierPath创build的代码中的path

这是我如何计算scrollView被拉下多远:

 func scrollViewDidScroll(scrollView: UIScrollView) { let offsetY = CGFloat(max(-(scrollView.contentOffset.y + scrollView.contentInset.top), 0.0)) self.progress = min(max(offsetY / frame.size.height, 0.0), 1.0) if !isRefreshing { redrawFromProgress(self.progress) } } 

这是dynamic更新位置的function(它不工作):

 func redrawFromProgress(progress: CGFloat) { // PROBLEM: This is not correct. Only the `x` position is dynamic based on scrollView position. // The `y` position is static. // I want this to be dynamic based on how much the scrollView scrolled. myImage.layer.position = CGPoint(x: progress, y: 50) } 

基本上,这是我想要的:

  • 如果scrollView滚动为0.0,那么myImage.layer位置应该是CGPoint(x:0,y:0)或path的起点。

  • 如果滚动的scrollView是0.5(50%),那么myImage.layer位置应该在path 50%,我不知道CGPoint值在这里。

  • 等等…

我试着得到沿着UIBezierPath的CGPoint值,并基于滚动的scrollView%,分配该CGPoint值,但不知道如何做到这一点。 我也看了这个post,但是我不能为它工作。

编辑问题1:

通过使用这个扩展,我可以得到一个CGPoints数组,其中包含10个基于我的UIBezierPath值:

 extension CGPath { func forEachPoint(@noescape body: @convention(block) (CGPathElement) -> Void) { typealias Body = @convention(block) (CGPathElement) -> Void func callback(info: UnsafeMutablePointer<Void>, element: UnsafePointer<CGPathElement>) { let body = unsafeBitCast(info, Body.self) body(element.memory) } // print(sizeofValue(body)) let unsafeBody = unsafeBitCast(body, UnsafeMutablePointer<Void>.self) CGPathApply(self, unsafeBody, callback) } func getPathElementsPoints() -> [CGPoint] { var arrayPoints : [CGPoint]! = [CGPoint]() self.forEachPoint { element in switch (element.type) { case CGPathElementType.MoveToPoint: arrayPoints.append(element.points[0]) case .AddLineToPoint: arrayPoints.append(element.points[0]) case .AddQuadCurveToPoint: arrayPoints.append(element.points[0]) arrayPoints.append(element.points[1]) case .AddCurveToPoint: arrayPoints.append(element.points[0]) arrayPoints.append(element.points[1]) arrayPoints.append(element.points[2]) default: break } } return arrayPoints } 

我还重写了上面称为redrawFromProgress(progress: CGFloat)的函数:

 func redrawFromProgress(progress: CGFloat) { let enterPath = paths[0] let pathPointsArray = enterPath.CGPath let junctionPoints = pathPointsArray.getPathElementsPoints() // print(junctionPoints.count) // There are 10 junctionPoints // progress means how much the scrollView has been pulled down, // it goes from 0.0 to 1.0. if progress <= 0.1 { myImage.layer.position = junctionPoints[0] } else if progress > 0.1 && progress <= 0.2 { myImage.layer.position = junctionPoints[1] } else if progress > 0.2 && progress <= 0.3 { myImage.layer.position = junctionPoints[2] } else if progress > 0.3 && progress <= 0.4 { myImage.layer.position = junctionPoints[3] } else if progress > 0.4 && progress <= 0.5 { myImage.layer.position = junctionPoints[4] } else if progress > 0.5 && progress <= 0.6 { myImage.layer.position = junctionPoints[5] } else if progress > 0.6 && progress <= 0.7 { myImage.layer.position = junctionPoints[6] } else if progress > 0.7 && progress <= 0.8 { myImage.layer.position = junctionPoints[7] } else if progress > 0.8 && progress <= 0.9 { myImage.layer.position = junctionPoints[8] } else if progress > 0.9 && progress <= 1.0 { myImage.layer.position = junctionPoints[9] } } 

如果我拉下scrollView非常慢, myImage.layer实际上遵循的path。 唯一的问题是,如果我非常快地拉下scrollView, myImage.layer跳转到最后一点。 难道是因为我写上面的if statement的方式吗?

有任何想法吗?

您的代码依靠scrollViewDidScroll委托callback来被频繁调用,以击中所有关键帧点。 在滚动视图上快速拖动时,它不会频繁调用该方法,导致跳转。

您可能需要尝试根据代表当前位置与所需位置之间path的弧线段来计算自定义path。 基于这个animation,而不是解构你的自定义path(它看起来非常接近弧),可能会更容易。

CGPathAddArc()的x,y和r是常量,应该让你90%到你现在的path。 您也可以更喜欢在path开始处添加线段的path。 为了让所有的“我处于这个位置,让我走向另一个位置”的逻辑,我们需要做更多的工作来获得部分path。

此外,你会发现CAKeyframeAnimation类是有用的。 你可以给它一个CGPath(也许一个基于弧线段的旅行)和animation的时间,它可以使你的图层遵循path。

来源: https : //developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGPath/index.html#//apple_ref/c/func/CGPathAddArc

来源: https : //developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CAKeyframeAnimation_class/index.html

编辑:

以下是一些示例代码,介绍如何在CGPath上从当前进度到新进度绘制部分弧线。 我也做了相反的工作。 你可以玩数字和常量,但这是如何绘制一个弧段从一定比例到一定比例的想法。

请注意看CoreGraphicsmath,它可能看起来倒退(顺时针和逆时针等)。 这是因为UIKit翻转所有东西,把原点放在左上angular ,CG的起点在左下angular

 // start out with start percent at zero, but then use the last endPercent instead let startPercent = CGFloat(0.0) // end percent is the "progress" in your code let endPercent = CGFloat(1.0) // reverse the direction of the path if going backwards let clockwise = startPercent > endPercent ? false : true let minArc = CGFloat(M_PI) * 4/5 let maxArc = CGFloat(M_PI) * 1/5 let arcLength = minArc - maxArc let beginArc = minArc - (arcLength * startPercent) let endArc = maxArc + (arcLength * (1.0 - endPercent)) let myPath = CGPathCreateMutable() CGPathAddArc(myPath, nil, view.bounds.width/2, 0, 160, beginArc, endArc, clockwise) 

这里是由常量minArcmaxArc定义的全弧段。

全弧段

感谢@Sam Falconer让我意识到这一点:

您的代码依靠scrollViewDidScroll委托callback来被频繁调用,以击中所有关键帧点。 在滚动视图上快速拖动时,它不会频繁调用该方法,导致跳转。

一旦我确认了这一点,他还提到:

此外,你会发现CAKeyframeAnimation类是有用的。

使用CAKeyfraneAnimation我可以用这个代码手动控制它的值:

 func scrollViewDidScroll(scrollView: UIScrollView) { let offsetY = CGFloat(max(-(scrollView.contentOffset.y + scrollView.contentInset.top), 0.0)) self.progress = min(max(offsetY / frame.size.height, 0.0), 1.0) if !isRefreshing { redrawFromProgress(self.progress) } } func redrawFromProgress(progress: CGFloat) { // Animate image along enter path let pathAnimation = CAKeyframeAnimation(keyPath: "position") pathAnimation.path = myPath.CGPath pathAnimation.calculationMode = kCAAnimationPaced pathAnimation.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)] pathAnimation.beginTime = 1e-100 pathAnimation.duration = 1.0 pathAnimation.timeOffset = CFTimeInterval() + Double(progress) pathAnimation.removedOnCompletion = false pathAnimation.fillMode = kCAFillModeForwards imageLayer.addAnimation(pathAnimation, forKey: nil) imageLayer.position = enterPath.currentPoint } 

再次感谢帮助家伙!