dynamic改变基于scrollView的位置
我有一个“U”形UIBezierPath
,我用myImage.layer
作为animation的path
。 我也有一个scrollView。 我的目标是有一个自定义的“拉到刷新”animation。
我遇到的问题是,我希望我的myImage.layer
根据scrollView滚动多less进行更新。
当scrollView被拉下时, myImage.layer
沿着一个“U”形path
animation。 这是我作为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)
这里是由常量minArc
和maxArc
定义的全弧段。
感谢@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 }
再次感谢帮助家伙!