按照用户方向旋转collectionView

Posted

技术标签:

【中文标题】按照用户方向旋转collectionView【英文标题】:Rotate collectionView in circle following user direction 【发布时间】: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保持和之前一样的动画,我该如何实现呢,请帮忙

提前致谢

【问题讨论】:

使用集合循环布局和动态项目行为将很容易解决您的问题 能否请您用一些代码解释或分享任何演示源 developer.apple.com/library/archive/documentation/WindowsViews/… 【参考方案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 是围绕中间旋转的视图。

【讨论】:

你能用一些代码解释一下吗?我已经看过这些代码,但不确定如何在我的案例中使用这些代码,因为我不是一个很好的修改库,而且我对动画和类似的东西有点陌生。谢谢 我加了一个例子。

以上是关于按照用户方向旋转collectionView的主要内容,如果未能解决你的问题,请参考以下文章

如何旋转 GMSMarker 以显示用户移动方向?

根据 Google Maps V2 Android 上的用户方向旋转标记

以编程方式更改视图方向而无需旋转 ipad

根据行驶方向旋转标记

UIview演示仅在一个方向上旋转

iOS 应用程序方向旋转锁定/解锁由应用程序的配置