Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/ios/118.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 滑动UIView时出现动画错误_Ios_Swift_Uiview_Uipangesturerecognizer - Fatal编程技术网

Ios 滑动UIView时出现动画错误

Ios 滑动UIView时出现动画错误,ios,swift,uiview,uipangesturerecognizer,Ios,Swift,Uiview,Uipangesturerecognizer,我正在开发一个应用程序,它有一个3视图和一个卡片视图,就像在Tinder中一样。我正在for循环中创建视图。当我有超过4个视图时,一切正常。当它只有3张卡时,当应用程序打开时,起初一切看起来都很正常,但在刷完一张卡后,它就坏了。最后一张牌移动时带有一些错误。我正在尝试编辑代码以使用3卡,但无法理解。顺便说一下,ImageCard只是一个UIView类 编辑:我的问题是,当它有3张卡时,应用程序打开时屏幕上会显示3张卡,但刷卡后,最后一张卡不会显示在屏幕上,只有2张卡显示在屏幕上。刷卡后,前面的卡

我正在开发一个应用程序,它有一个3视图和一个卡片视图,就像在Tinder中一样。我正在for循环中创建视图。当我有超过4个视图时,一切正常。当它只有3张卡时,当应用程序打开时,起初一切看起来都很正常,但在刷完一张卡后,它就坏了。最后一张牌移动时带有一些错误。我正在尝试编辑代码以使用3卡,但无法理解。顺便说一下,
ImageCard
只是一个
UIView

编辑:我的问题是,当它有3张卡时,应用程序打开时屏幕上会显示3张卡,但刷卡后,最后一张卡不会显示在屏幕上,只有2张卡显示在屏幕上。刷卡后,前面的卡应转到最后面,并且应再次看到3张卡。当它有5张以上的卡时,一切正常,就像我解释的,屏幕上显示了3张卡(它需要什么)

我确信
showNextCard()
函数会出现问题,但请确保以下是完整的代码:

class WelcomeViewController: UIViewController {

/// Data structure for custom cards
var cards = [ImageCard]()

override func viewDidLoad() {
    super.viewDidLoad()
    dynamicAnimator = UIDynamicAnimator(referenceView: self.view)
    print(self.view.frame.height)
    print(self.view.frame.width)
    let screenWidth = self.view.frame.width
    let screenHeight = self.view.frame.height
    //When add new cards to self.cards and call layoutCards() again
    for i in 1...5 {
        let card = ImageCard(frame: CGRect(x: 0, y: 0, width: screenWidth - screenWidth / 5, height: screenWidth))
        card.tag = i
        card.label.text = "Card Number: \(i)"
        cards.append(card)
    }
    lastIndex = cards.count

    // 2. layout the first cards for the user
    layoutCards()

}

/// Scale and alpha of successive cards visible to the user
let cardAttributes: [(downscale: CGFloat, alpha: CGFloat)] = [(1, 1), (0.92, 0.8), (0.84, 0.6), (0.76, 0.4)]
let cardInteritemSpacing: CGFloat = 12


/// Set up the frames, alphas, and transforms of the first 4 cards on the screen
func layoutCards() {
    // frontmost card (first card of the deck)
    let firstCard = cards[0]
    self.view.addSubview(firstCard)
    firstCard.layer.zPosition = CGFloat(cards.count)
    firstCard.center = self.view.center
    firstCard.frame.origin.y += 23
    firstCard.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handleCardPan)))

    // the next 3 cards in the deck
    for i in 1...3 {
        if i > (cards.count - 1) { continue }

        let card = cards[i]

        card.layer.zPosition = CGFloat(cards.count - i)

        // here we're just getting some hand-picked vales from cardAttributes (an array of tuples)
        // which will tell us the attributes of each card in the 4 cards visible to the user
        let downscale = cardAttributes[i].downscale
        let alpha = cardAttributes[i].alpha
        card.transform = CGAffineTransform(scaleX: downscale, y: downscale)
        card.alpha = alpha
        // position each card so there's a set space (cardInteritemSpacing) between each card, to give it a fanned out look
        card.center.y = self.view.center.y + 23
        card.frame.origin.x = cards[0].frame.origin.x + (CGFloat(i) * cardInteritemSpacing * 3)
        // workaround: scale causes heights to skew so compensate for it with some tweaking
        if i == 3 {
            card.frame.origin.x += 1.5
        }

        self.view.addSubview(card)
    }

    // make sure that the first card in the deck is at the front
    self.view.bringSubview(toFront: cards[0])
}

/// This is called whenever the front card is swiped off the screen or is animating away from its initial position.
/// showNextCard() just adds the next card to the 4 visible cards and animates each card to move forward.
func showNextCard() {
    let animationDuration: TimeInterval = 0.2
    // 1. animate each card to move forward one by one
    for i in 1...3{
        if i > (cards.count - 1) { continue }
        let card = cards[i]
        let newDownscale = cardAttributes[i - 1].downscale
        let newAlpha = cardAttributes[i - 1].alpha
        UIView.animate(withDuration: animationDuration, delay: (TimeInterval(i - 1) * (animationDuration / 2)), usingSpringWithDamping: 0.8, initialSpringVelocity: 0.0, options: [], animations: {
            card.transform = CGAffineTransform(scaleX: newDownscale, y: newDownscale)
            card.alpha = newAlpha
            if i == 1 {
                card.center = self.view.center
                card.frame.origin.y += 23
            } else {
                card.center.y = self.view.center.y + 23
                card.frame.origin.x = self.cards[1].frame.origin.x + (CGFloat(i - 1) * self.cardInteritemSpacing * 3)
            }
        }, completion: { (_) in
            if i == 1 {
                card.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.handleCardPan)))
            }
        })

    }

    // 2. add a new card (now the 4th card in the deck) to the very back
    if 4 > (cards.count - 1) {
        if cards.count != 1 {
            self.view.bringSubview(toFront: cards[1])
        }else{
            //self.view.bringSubview(toFront: cards.last!)
        }
        return
    }
    let newCard = cards[4]
    newCard.layer.zPosition = CGFloat(cards.count - 4)
    let downscale = cardAttributes[3].downscale
    let alpha = cardAttributes[3].alpha

    // initial state of new card
    newCard.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
    newCard.alpha = 0
    newCard.center.y = self.view.center.y + 23
    newCard.frame.origin.x = cards[1].frame.origin.x + (4 * cardInteritemSpacing * 3)
    self.view.addSubview(newCard)

    // animate to end state of new card
    UIView.animate(withDuration: animationDuration, delay: (3 * (animationDuration / 2)), usingSpringWithDamping: 0.8, initialSpringVelocity: 0.0, options: [], animations: {
        newCard.transform = CGAffineTransform(scaleX: downscale, y: downscale)
        newCard.alpha = alpha
        newCard.center.y = self.view.center.y + 23
        newCard.frame.origin.x = self.cards[1].frame.origin.x + (3 * self.cardInteritemSpacing) + 1.5
    }, completion: { (_) in

    })
    // first card needs to be in the front for proper interactivity
    self.view.bringSubview(toFront: self.cards[1])

}

/// Whenever the front card is off the screen, this method is called in order to remove the card from our data structure and from the view.
func removeOldFrontCard() {
    cards.append(cards[0])
    cards[0].removeFromSuperview()
    cards.remove(at: 0)
    layoutCards()
}

private func isVerticalGesture(_ recognizer: UIPanGestureRecognizer) -> Bool {
    let translation = recognizer.translation(in: self.view!)
    if fabs(translation.y) > fabs(translation.x) {
        return true
    }
    return false
}

/// UIKit dynamics variables that we need references to.
var dynamicAnimator: UIDynamicAnimator!
var cardAttachmentBehavior: UIAttachmentBehavior!

/// This method handles the swiping gesture on each card and shows the appropriate emoji based on the card's center.
@objc func handleCardPan(sender: UIPanGestureRecognizer) {

    // Ensure it's a horizontal drag
    let velocity = sender.velocity(in: self.view)
    if abs(velocity.y) > abs(velocity.x) {
        return
    }

    // if we're in the process of hiding a card, don't let the user interace with the cards yet
    if cardIsHiding { return }
    // change this to your discretion - it represents how far the user must pan up or down to change the option
    // distance user must pan right or left to trigger an option
    let requiredOffsetFromCenter: CGFloat = 80

    let panLocationInView = sender.location(in: view)
    let panLocationInCard = sender.location(in: cards[0])

    switch sender.state {
    case .began:
        dynamicAnimator.removeAllBehaviors()
        let offset = UIOffsetMake(cards[0].bounds.midX, panLocationInCard.y)
        // card is attached to center
        cardAttachmentBehavior = UIAttachmentBehavior(item: cards[0], offsetFromCenter: offset, attachedToAnchor: panLocationInView)
        //dynamicAnimator.addBehavior(cardAttachmentBehavior)
        let translation = sender.translation(in: self.view)
        print(sender.view!.center.x)

        if(sender.view!.center.x < 555) {

            sender.view!.center = CGPoint(x: sender.view!.center.x + translation.x, y: sender.view!.center.y)

        }else {
            sender.view!.center = CGPoint(x:sender.view!.center.x, y:554)
        }
        sender.setTranslation(CGPoint(x: 0, y: 0), in: self.view)
    case .changed:
        //cardAttachmentBehavior.anchorPoint = panLocationInView
        let translation = sender.translation(in: self.view)
        print(sender.view!.center.y)

        if(sender.view!.center.x < 555) {

            sender.view!.center = CGPoint(x: sender.view!.center.x + translation.x, y: sender.view!.center.y)

        }else {
            sender.view!.center = CGPoint(x:sender.view!.center.x, y:554)
        }
        sender.setTranslation(CGPoint(x: 0, y: 0), in: self.view)

    case .ended:

        dynamicAnimator.removeAllBehaviors()

        if !(cards[0].center.x > (self.view.center.x + requiredOffsetFromCenter) || cards[0].center.x < (self.view.center.x - requiredOffsetFromCenter)) {
            // snap to center
            let snapBehavior = UISnapBehavior(item: cards[0], snapTo: CGPoint(x: self.view.frame.midX, y: self.view.frame.midY + 23))
            dynamicAnimator.addBehavior(snapBehavior)
        } else {
            let velocity = sender.velocity(in: self.view)
            let pushBehavior = UIPushBehavior(items: [cards[0]], mode: .instantaneous)
            pushBehavior.pushDirection = CGVector(dx: velocity.x/10, dy: velocity.y/10)
            pushBehavior.magnitude = 175
            dynamicAnimator.addBehavior(pushBehavior)
            // spin after throwing
            var angular = CGFloat.pi / 2 // angular velocity of spin

            let currentAngle: Double = atan2(Double(cards[0].transform.b), Double(cards[0].transform.a))

            if currentAngle > 0 {
                angular = angular * 1
            } else {
                angular = angular * -1
            }
            let itemBehavior = UIDynamicItemBehavior(items: [cards[0]])
            itemBehavior.friction = 0.2
            itemBehavior.allowsRotation = true
            itemBehavior.addAngularVelocity(CGFloat(angular), for: cards[0])
            dynamicAnimator.addBehavior(itemBehavior)

            showNextCard()
            hideFrontCard()

        }
    default:
        break
    }
}

/// This function continuously checks to see if the card's center is on the screen anymore. If it finds that the card's center is not on screen, then it triggers removeOldFrontCard() which removes the front card from the data structure and from the view.
var cardIsHiding = false
func hideFrontCard() {
    if #available(iOS 10.0, *) {
        var cardRemoveTimer: Timer? = nil
        cardRemoveTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true, block: { [weak self] (_) in
            guard self != nil else { return }
            if !(self!.view.bounds.contains(self!.cards[0].center)) {
                cardRemoveTimer!.invalidate()
                self?.cardIsHiding = true
                UIView.animate(withDuration: 0.2, delay: 0, options: [.curveEaseIn], animations: {
                    self?.cards[0].alpha = 0.0
                }, completion: { (_) in
                    self?.removeOldFrontCard()
                    self?.cardIsHiding = false
                })
            }
        })
    } else {
        // fallback for earlier versions
        UIView.animate(withDuration: 0.2, delay: 1.5, options: [.curveEaseIn], animations: {
            self.cards[0].alpha = 0.0
        }, completion: { (_) in
            self.removeOldFrontCard()
        })
    }
}
}

从索引1开始,但数组的索引从0开始

// the next 3 cards in the deck
for i in 1...3 {
    if i > (cards.count - 1) { continue }
    let card = cards[i]
...
}
改为:

// the next 3 cards in the deck
for i in 0...2 {
    if i > (cards.count - 1) { break }
    let card = cards[i]
...
}

我发现你在完成动画后忘记关闭dynamicAnimator。至少,您需要关闭animator about cards[0]。否则,它将变得不可预测。您可以像这样使用removeOldFrontCard()。希望这就是答案

  func removeOldFrontCard() {
    dynamicAnimator.removeAllBehaviors()
    cards.append( cards.remove(at: 0))
    layoutCards()
}

那有什么问题?你发布了一堆代码和Gif,但没有解释出问题所在。@duncac我编辑了我的问题。
如果我>(cards.count-1){continue}
也可以更改为
如果我>(cards.count-1){break}
,因为循环以升序迭代,因此,如果条件对
i
的一个值求值为
true
,则对
i
的所有连续值也将求值为
true
。它仍然存在该缺陷。你运行它了吗?你必须改变你的if语句,比如if i==3{card.frame.origin.x+=1.5},并解释有什么错误->给出一些错误消息或类似的错误,它需要在屏幕上显示3张卡(就像在第一次打开时),但刷卡后只显示2张。哇,谢谢!它就像一个符咒!我不认为这是关于dynamicAnimator的。
  func removeOldFrontCard() {
    dynamicAnimator.removeAllBehaviors()
    cards.append( cards.remove(at: 0))
    layoutCards()
}