【问题标题】:Rotate collectionView in circle following user direction按照用户方向旋转collectionView
【发布时间】:2019-01-31 21:04:46
【问题描述】:

我正在尝试使用循环器布局创建collectionView,并且我希望collectionView 在用户以任何方向在屏幕上滑动手指时旋转。我发现collectionView的圆形布局是我到目前为止所做的

为了旋转这个collectionView,我写了这段代码

为collectionView添加手势

panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.gestureReader(_:)))
    panGesture.cancelsTouchesInView = false
    self.collectionView.addGestureRecognizer(panGesture)

这里是gestureReader和animation方法

@objc private func gestureReader(_ gesture: UIPanGestureRecognizer) {
    var startLocation = CGPoint.zero
    var endLocation = CGPoint.zero
    let currentLocation = gesture.location(in: self.collectionView)

    if gesture.state == .began {
        startLocation = currentLocation
    }

    if gesture.state == .ended {
        endLocation = currentLocation
        self.startRotatingView(start: startLocation, end: endLocation)
    }
}

private func startRotatingView(start:CGPoint, end: CGPoint) {
    let dx = end.x - start.x
    let dy = end.y - start.y

    let distance = abs(sqrt(dx*dx + dy*dy))
    print(distance)


    if start.x > end.x {
        if start.y > end.y {
            //positive value of pi
            self.circleAnimation(-distance)
        }else {
            //negitive value of pi
            self.circleAnimation(distance)
        }
    }else {
        if start.y > end.y {
            //positive value of pi
            self.circleAnimation(-distance)
        }else {
            //negitive value of pi
            self.circleAnimation(distance)
        }
    }
}

private func circleAnimation(_ angle:CGFloat) {
    UIView.animate(withDuration: 0.7, delay: 0,  options: .curveLinear, animations: {
        self.collectionView.transform = CGAffineTransform.identity
        self.collectionView.transform = CGAffineTransform.init(rotationAngle: angle)
    }) { (true) in
        //
    }
}

首先动画无法正常工作,其次当 collectionView 旋转时,这就是我得到的

问题1:我还需要添加什么才能使这个动画流畅并跟随用户的手指? 问题2:我想让collectionViewcells保持和之前一样的动画,我该如何实现呢,请帮忙

提前致谢

【问题讨论】:

标签: ios swift uicollectionview rotation uipangesturerecognizer


【解决方案1】:

我在这里给你看一个例子。装饰视图 S1View 是 UICollectionViewCell 的子类,标识符为“background”。

代码不难理解,但组合起来很乏味。如何控制动画师是另一回事。

        class TestCollectionViewLayout: UICollectionViewLayout {

            lazy var dataSource : UICollectionViewDataSource? = {
                self.collectionView?.dataSource
            }()

            var layouts : [IndexPath: UICollectionViewLayoutAttributes?] = [:]

            var itemNumber : Int  {
                return   dataSource!.collectionView(collectionView!, numberOfItemsInSection: 0)
            }

            override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?{
                var itemArray = (0..<itemNumber).map{ self.layoutAttributesForItem(at: IndexPath.init(row: $0, section: 0))!}
                itemArray.append(self.layoutAttributesForDecorationView(ofKind:"background"
                    , at: IndexPath.init(row: 0, section: 0)))
                return itemArray
            }

            override var collectionViewContentSize: CGSize { get{
                return  self.collectionView?.frame.size ?? CGSize.zero
                }
            }

            lazy var  dynamicAnimator = {UIDynamicAnimator(collectionViewLayout: self)}()

            private func updateCurrentLayoutAttributesForItem(at indexPath: IndexPath, current: UICollectionViewLayoutAttributes?) -> UICollectionViewLayoutAttributes?{
                return current
            }

            private func initLayoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?{
                let layoutAttributes =   UICollectionViewLayoutAttributes(forCellWith: indexPath)
                let center = (collectionView?.center)!
                let angle = (CGFloat(indexPath.row)  /  CGFloat(itemNumber) * CGFloat.pi * 2)
                layoutAttributes.center = CGPoint.init(x:  center.x + cos(angle) * CGFloat(radius)   , y: center.y + sin(angle) * CGFloat(radius) )
                layoutAttributes.bounds  = CGRect.init(x: 0, y: 0, width: 100, height: 100 )

                if let decorator = self.decorator {
                    let itemBehavior =
                        UIAttachmentBehavior.pinAttachment(with: layoutAttributes, attachedTo: decorator, attachmentAnchor: layoutAttributes.center)
                    dynamicAnimator.addBehavior(itemBehavior)
                    layouts[indexPath] = layoutAttributes
                }
                return layoutAttributes
            }

            override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?{
                guard let currentLayout = layouts[indexPath] else {
                    return initLayoutAttributesForItem(at:indexPath)}
                return currentLayout
            }


            private let radius = 200

            private var decorator: UICollectionViewLayoutAttributes?

            override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes{
                guard let decorator = self.decorator else {
                    let layoutAttributes =   UICollectionViewLayoutAttributes.init(forDecorationViewOfKind: elementKind, with: indexPath)
                    layoutAttributes.center = (self.collectionView?.center)!
                    layoutAttributes.bounds = CGRect.init(x: 0, y: 0, width: radius, height: radius)
                    self.decorator = layoutAttributes
                    return layoutAttributes
                }
                return decorator
            }


            lazy var s: UIDynamicItemBehavior = {
             let decorator = self.decorator!
             let s =   UIDynamicItemBehavior.init(items: [decorator])
             s.angularResistance = 1
             dynamicAnimator.addBehavior(s)
                return s
            }()


            func rotate(_ speed: CGFloat){
                   guard let decorator = self.decorator else {return}
                s.addAngularVelocity(speed, for: decorator)
            }

        }



    class TestCollectionViewController: UICollectionViewController {


        var startLocation = CGPoint.zero
        var endLocation = CGPoint.zero

        @objc private func gestureReader(_ gesture: UIPanGestureRecognizer) {

            let currentLocation = gesture.location(in: self.collectionView)

            if gesture.state == .began {
                startLocation = currentLocation
            }

           else  if gesture.state == .ended {
                endLocation = currentLocation
                self.startRotatingView(start: startLocation, end: endLocation)
            }
        }

        private func startRotatingView(start:CGPoint, end: CGPoint) {
            let dx = end.x - start.x
            let dy = end.y - start.y

            let distance = abs(sqrt(dx*dx + dy*dy))




            if start.x < end.x {
                if start.y > end.y {
                    //positive value of pi
                    self.circleAnimation(-distance)
                }else {
                    //negitive value of pi
                    self.circleAnimation(distance)
                }
            }else {
                if start.y > end.y {
                    //positive value of pi
                    self.circleAnimation(-distance)
                }else {
                    //negitive value of pi
                    self.circleAnimation(distance)
                }
            }
        }

        private func circleAnimation(_ angle:CGFloat) {

            (collectionView.collectionViewLayout as? TestCollectionViewLayout).map{
                $0.rotate(angle / 100)
            }
  //                UIView.animate(withDuration: 0.7, delay: 0,  options: .curveLinear, animations: {
  //                    self.collectionView.transform = CGAffineTransform.identity
  //                    self.collectionView.transform = CGAffineTransform.init(rotationAngle: angle)
  //                }) { (true) in
  //                    //
 //                }
        }



        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
        //                Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) { (Timer) in
       //                    self.rotate()
  //                }
        }

        override func viewDidLoad() {


            super.viewDidLoad()
            collectionView.collectionViewLayout = TestCollectionViewLayout()
            collectionView.collectionViewLayout.register(UINib.init(nibName: "S1View", bundle: nil) , forDecorationViewOfKind: "background")

         let   panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.gestureReader(_:)))
            panGesture.cancelsTouchesInView = false
            self.collectionView.addGestureRecognizer(panGesture)

        }

        var data: [Int] = [1,2,3,4,5,6,7]
        override func numberOfSections(in collectionView: UICollectionView) -> Int {
            return 1
        }

        override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return data.count
        }

        override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)

            return cell
      }
    }

【讨论】:

  • 看起来不错,尝试后我会接受答案,非常感谢兄弟
  • 你能帮我解决问题1,它是跟随手指旋转
  • 太复杂了。
  • 我将您的旋转功能与动画联系起来。起始定位有错误。
  • 你能解释什么错误也许我们可以一起解决
【解决方案2】:

也许本教程会有所帮助:https://www.raywenderlich.com/1702-uicollectionview-custom-layout-tutorial-a-spinning-wheel

您的第一个问题是您正在旋转整个集合视图。想象一下,就像您将这些圆圈放在一张纸上,然后旋转那张纸。您不想旋转整个集合视图。您可能不想围绕一个点旋转圆圈,因为旋转会影响圆圈中的图像和文本。你只是想在圆周运动中改变圆圈的位置。

如果 UICollectionView 不起作用,您可以放弃它并使用常规 UIView 并将它们放置在圆形图案中(这些功能应该会有所帮助:https://gist.github.com/akhilcb/8d03f1f88f87e996aec24748bdf0ce78)。将视图布置成圆形后,您只需在用户拖动手指时更新每个视图的角度。将先前的角度存储在视图上,并在用户拖动手指时添加任何您想要的角度。一点点试错,应该不会太糟糕。

更新

使用集合视图的主要原因是如果您有很多项目并且需要像列表一样重用视图。如果您不需要重用视图,那么使用 UICollectionView 可能很难理解、自定义和更改内容。这是一个使用 UIPanGestureRecognizer 输入使用围绕圆圈旋转的常规视图的简单示例。

示例

import UIKit

class ViewController: UIViewController {

  var rotatingViews = [RotatingView]()
  let numberOfViews = 8
  var circle = Circle(center: CGPoint(x: 200, y: 200), radius: 100)
  var prevLocation = CGPoint.zero

  override func viewDidLoad() {
    super.viewDidLoad()

    for i in 0...numberOfViews {
      let angleBetweenViews = (2 * Double.pi) / Double(numberOfViews)
      let viewOnCircle = RotatingView(circle: circle, angle: CGFloat(Double(i) * angleBetweenViews))
      rotatingViews.append(viewOnCircle)
      view.addSubview(viewOnCircle)
    }

    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didPan(panGesture:)))
    view.addGestureRecognizer(panGesture)
  }

  @objc func didPan(panGesture: UIPanGestureRecognizer){
    switch panGesture.state {
    case .began:
      prevLocation = panGesture.location(in: view)
    case .changed, .ended:
      let nextLocation = panGesture.location(in: view)
      let angle = circle.angleBetween(firstPoint: prevLocation, secondPoint: nextLocation)

      rotatingViews.forEach({ $0.updatePosition(angle: angle)})
      prevLocation = nextLocation
    default: break
    }
  }
}


struct Circle {
  let center: CGPoint
  let radius: CGFloat

  func pointOnCircle(angle: CGFloat) -> CGPoint {
    let x = center.x + radius * cos(angle)
    let y = center.y + radius * sin(angle)

    return CGPoint(x: x, y: y)
  }

  func angleBetween(firstPoint: CGPoint, secondPoint: CGPoint) -> CGFloat {
    let firstAngle = atan2(firstPoint.y - center.y, firstPoint.x - center.x)
    let secondAnlge = atan2(secondPoint.y - center.y, secondPoint.x - center.x)
    let angleDiff = (firstAngle - secondAnlge) * -1

    return angleDiff
  }
}


class RotatingView: UIView {
  var currentAngle: CGFloat
  let circle: Circle

  init(circle: Circle, angle: CGFloat) {
    self.currentAngle = angle
    self.circle = circle
    super.init(frame: CGRect(x: 0, y: 0, width: 60, height: 60))
    center = circle.pointOnCircle(angle: currentAngle)
    backgroundColor = .blue
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  func updatePosition(angle: CGFloat) {
    currentAngle += angle
    center = circle.pointOnCircle(angle: currentAngle)
  }
}

Circle 是一个结构,它只保存所有视图的中心、您希望它们相距多远(半径)以及用于计算上述 GitHub 链接中的角度的辅助函数。

RotatingViews 是围绕中间旋转的视图。

【讨论】:

  • 你能用一些代码解释一下吗?我已经看过这些代码,但不确定如何在我的案例中使用这些代码,因为我不是一个很好的修改库,而且我对动画和类似的东西有点陌生。谢谢
  • 我加了一个例子。
猜你喜欢
  • 2016-07-26
  • 1970-01-01
  • 2022-01-07
  • 2015-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-13
相关资源
最近更新 更多