swift 与 js的交互_如何使用Swift的UIViewPropertyAnimator实现交互式动画

swift 与 js的交互

by Trevor Phillips

特雷弗·菲利普斯(Trevor Phillips)

如何使用Swift的UIViewPropertyAnimator实现交互式动画 (How to implement interactive animations with Swift’s UIViewPropertyAnimator)

Let’s scrap the ugly UIView.animate(…) code and give it an upgrade, shall we?

让我们废弃丑陋的UIView.animate(…)代码并对其进行升级,对吧?

Here we’ll dive into a practical example using Apple’s UIViewPropertyAnimator to create smooth animations combined with user interaction.

在这里,我们将深入探讨一个使用Apple的UIViewPropertyAnimator来创建结合用户交互的平滑动画的实际示例。

You can check out the end result of these animations in the free app Bloq, which uses the techniques described below as its foundation for gameplay.

您可以在免费的应用程序Bloq中查看这些动画的最终结果,该应用程序使用以下所述的技术作为游戏玩法的基础。

背景 (Background)

It’s a class introduced in iOS 10 which offers more capabilities than traditional UIView.animate(...) functions:

这是iOS 10中引入的类,它提供了比传统的UIView.animate(...)函数更多的功能:

  • Programmatically start, stop, pause, or resume the animation at any time

    随时以编程方式开始,停止,暂停或恢复动画
  • Add animation blocks and completion blocks to the animator at your leisure

    随意将动画块和完成块添加到动画师
  • Reverse the animation at any time

    随时反转动画
  • “Scrub” the animation, that is, programmatically set how far along it should be right now

    “清理”动画,即以编程方式设置动画现在应该走多远

入门 (Getting Started)

First, we’ll define a custom view controller BlockViewController which will represent each colored square within the game. Note: I am not including code for the colors, rounded corners, or other aspects which are irrelevant to this tutorial.

首先,我们将定义一个自定义视图控制器BlockViewController ,它将代表游戏中的每个彩色正方形。 注意 :我不包括颜色,圆角或与本教程无关的其他方面的代码。

class BlockViewController: UIViewController {    var startingXOffset: CGFloat = 0    var endingXOffset: CGFloat = 0    var startingYOffset: CGFloat = 0    var endingYOffset: CGFloat = 0
var topConstraint = NSLayoutConstraint()    var leadingConstraint = NSLayoutConstraint()
var animationDirection: AnimationDirection = .undefined    var isVerticalAnimation: Bool {        return animationDirection == .up            || animationDirection == .down    }    var transitionAnimator: UIViewPropertyAnimator?    var animationProgress: CGFloat = 0}

The properties topConstraint and leftConstraint define the offset of the view controller’s view from the top and left sides of its superview (respectively).

属性topConstraintleftConstraint定义视图控制器的视图相对于leftConstraint视图的顶部和左侧的偏移量。

The offset properties are used by the UIViewPropertyAnimator to determine where the animation should begin and where it should end. Since the blocks in the game can move both left/right and up/down, we define both X and Y offsets.

UIViewPropertyAnimator使用offset属性确定动画应在何处开始和应在何处结束。 由于游戏中的方块可以左右移动,也可以上下移动,因此我们定义了XY偏移量。

We also have a simple enum AnimationDirection to assist with the logic necessary for animations.

我们还有一个简单的枚举AnimationDirection来辅助动画所必需的逻辑。

enum AnimationDirection: Int {    case up, down, left, right, undefined}

Now in the view controller’s viewDidLoad() function, we can set up the constraints something like this:

现在,在视图控制器的viewDidLoad()函数中,我们可以设置如下约束:

topConstraint = view.topAnchor.constraint(equalTo: superview.topAnchor, constant: startingYOffset)
leadingConstraint = view.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: startingXOffset)
topConstraint.isActive = true
leadingConstraint.isActive = true
let recognizer = UIPanGestureRecognizer()
recognizer.addTarget(self, action: #selector(viewPanned(recognizer:))) // will be defined later!
view.addGestureRecognizer(recognizer)
辅助功能 (Helper Functions)

Let’s also set up a few “helper” functions which will be useful later. The following functions will swap the offset values:

让我们还设置一些“辅助”功能,这些功能将在以后使用。 以下函数将交换偏移值:

private func swapXConstraints() {    let tmp = endingXOffset    endingXOffset = startingXOffset    startingXOffset = tmp}
private func swapYConstraints() {    let tmp = endingYOffset    endingYOffset = startingYOffset    startingYOffset = tmp}

This one will be useful for resetting the animation:

这对于重置动画很有用:

private func nullifyAnimations() {    transitionAnimator = nil    animationDirection = .undefined}

And here we have a function to reverse the animator:

这里我们有一个反转动画师的功能:

private func reverseAnimation() {    guard let animator = transitionAnimator else { return }    animator.isReversed = !animator.isReversed}

If the animation is already running and isReversed is true, then we know that the animation is running in the reversed direction. If the animation is not running and isReversed is true, the animation will run in the reversed direction when started.

如果动画已经在运行,并且isReversedtrue ,那么我们知道动画在反向运行。 如果动画没有运行并且isReversedtrue ,则动画在开始时将以相反的方向运行。

Finally, this little function takes velocity represented as a CGPoint, and determines which, if any, direction the animation should go based on whether the x-component or the y-component of the velocity is greater in magnitude:

最后,这个小函数将velocity表示为CGPoint ,并根据velocity的x分量或y分量的大小是否较大来确定动画应朝哪个方向移动:

private func directionFromVelocity(_ velocity: CGPoint) -> AnimationDirection {    guard velocity != .zero else { return .undefined }    let isVertical = abs(velocity.y) > abs(velocity.x)    var derivedDirection: AnimationDirection = .undefined    if isVertical {        derivedDirection = velocity.y < 0 ? .up : .down    } else {        derivedDirection = velocity.x < 0 ? .left : .right    }    return derivedDirection}

用户互动 (User Interaction)

Let’s get to the essential stuff: the animations and user interaction!

让我们来看看基本内容:动画和用户交互!

In viewDidLoad() we attached a pan gesture recognizer to the BlockViewController's view. This gesture recognizer calls the function viewPanned(recognizer: UIPanGestureRecognizer) when its state changes.

viewDidLoad()我们将平移手势识别器附加到BlockViewController的视图。 当其状态更改时,此手势识别器将调用函数viewPanned(recognizer: UIPanGestureRecognizer)

@objcfunc viewPanned(recognizer: UIPanGestureRecognizer) {  switch recognizer.state {  case .began:    animationProgress = transitionAnimator?.fractionComplete ?? 0  case .changed:    didChangePan(recognizer: recognizer) // described below  case .ended:    didEndPan(recognizer: recognizer) // described below default:    break  }}

Remember how I mentioned the “scrubbing” ability of UIViewPropertyAnimator? The fractionComplete property allows us to get and set how far along the animator should be with its animations. This value ranges from 0.0 to 1.0.

还记得我曾提到过UIViewPropertyAnimator的“清理”功能吗? fractionComplete属性使我们能够获取并设置动画制作器与动画之间的距离。 取值范围是0.0〜1.0。

The animationProgress is captured in recognizer.state = .began because we may have a situation shown above, where a pan gesture is initiated midway through the animation. In this case, we want to “catch” the animation in its current state. The animationProgress property is used to enable this “catching” behavior.

由于我们可能遇到上面所示的情况,因此在动画的中途启动了平移手势,所以将animationProgressrecognizer.state = .began捕获。 在这种情况下,我们要“捕获”处于当前状态的动画。 animationProgress属性用于启用此“捕获”行为。

The function viewPanned(recognizer: UIPanGestureRecognizer)offloads most of its logic into two functions, described below. Our code will become a bit more complex, so for enhanced readability and syntax-highlighting, I’ll switch to Github Gists now.

函数viewPanned(recognizer: UIPanGestureRecognizer)将其大部分逻辑分担给两个函数,如下所述。 我们的代码将变得更加复杂,因此为了增强可读性和语法高亮显示,我现在切换到Github Gists

The comments describe what is going on. Note that we actually begin the animation (if it doesn’t exist) when the state in viewPanned(recognizer: UIPanGestureRecognizer) is changed rather than began. This is because the velocity when state = .began is always zero. We can’t determine the animation direction until the velocity is non-zero, hence waiting until state = .changed to start the animation.

这些评论描述了正在发生的事情。 请注意,我们真正开始动画(如果不存在的话),当stateviewPanned(recognizer: UIPanGestureRecognizer)changed ,而不是began 。 这是因为当state = .began时的速度始终为零。 在速度不为零之前我们无法确定动画的方向,因此要等到state = .changed才能开始动画。

When we call transitionAnimator.continueAnimation(...) we are basically saying, “Okay animator, the user is done interacting, so go and finish up your business now!” Passing nil for the timing parameter and 0 for the duration factor will not make the animation finish instantaneously. It will still animate smoothly to the finish.

当我们调用transitionAnimator.continueAnimation(...)我们基本上是在说:“好的动画师,用户已经完成了交互,所以现在就完成您的业务!” 为时间参数传递nil并为持续时间因子传递0 不会使动画立即完成。 它将仍然平滑地完成动画。

逻辑解释 (The Logic, Explained)

At the end of this function, do you see the isOpposite variable and some confusing logic regarding animator.isReversed? Let’s understand what is going on here.

在该函数的结尾,您是否看到isOpposite变量以及一些与animator.isReversed有关的令人困惑的逻辑? 让我们了解这里发生了什么。

private func oppositeOfInitialAnimation(velocity: CGPoint) -> Bool {    switch animationDirection {    case .up:        return velocity.y > 0    case .down:        return velocity.y < 0    case .left:        return velocity.x > 0    case .right:        return velocity.x < 0    case .undefined:        return false    }}

The variable isOpposite uses the above helper function. It simply takes a velocity as input and returns true if this velocity is going opposite of the current animation direction.

变量isOpposite使用上面的辅助函数。 它只是将一个速度作为输入,如果该速度与当前动画方向相反,则返回true

Then we have an if-else statement with two scenarios:

然后我们有一个if-else语句,它包含两种情况:

Case 1: The pan gesture has ended in the reverse of its initial direction, but the animator has not been reversed. This means we need to reverse the animator before calling transitionAnimator.continueAnimation(...).

情况1 :平移手势已在其初始方向的相反方向结束,但动画制作者并未反转。 这意味着我们需要在调用transitionAnimator.continueAnimation(...)之前反转动画制作者。

Case 2: The pan gesture has finished in its initial direction, but the animator has been reversed at some point. This means that again, we must reverse the animator before calling transitionAnimator.continueAnimation(...).

情况2 :平移手势已在其初始方向完成,但是动画制作者在某些时候已反转 。 这意味着,再次,我们必须在调用transitionAnimator.continueAnimation(...)之前反转动画制作器。

动画 (Animation)

In didChangePan(...) we called beginAnimation() if the transition animator was nil. Here is the implementation for this function:

如果过渡动画师为nil则在didChangePan(...)调用了beginAnimation() 。 这是此功能的实现:

The important things going on are:

重要的事情是:

  • We notify a delegate that it should set startingXOffset, endingXOffset, startingYOffset, and endingYOffset

    我们通知委托人,它应该设置startingXOffsetendingXOffsetstartingYOffsetendingYOffset

  • We initialize the transitionAnimator with an animation block which updates the view’s constraints, then call layoutIfNeeded()

    我们使用动画块初始化transitionAnimator ,该动画块更新视图的约束,然后调用layoutIfNeeded()

  • We configure the animator’s completion block (described below)

    我们配置动画师的完成块(如下所述)
  • If the animation was programmatically initiated (no pan gesture involved), we call transitionAnimator.continueAnimation(...) to allow the animation to finish on its own

    如果动画是通过编程方式启动的(不涉及平移手势),则调用transitionAnimator.continueAnimation(...)以允许动画自行完成

  • If the animation was initiated from a pan gesture, we immediately pause the animation rather than allow it to complete. This is because the animation progress will be scrubbed in didChangePan(...)

    如果动画是从平移手势开始的,则我们将立即暂停动画而不是使其完成。 这是因为动画进度将在didChangePan(...)didChangePan(...)

动画完成 (Animation Completion)

The last function to be addressed is configureAnimationCompletionBlock(), described below:

要解决的最后一个函数是configureAnimationCompletionBlock() ,如下所述:

If the animator finished where it started, we reset the constraints back to how they were before the animation.

如果动画师在其开始处完成,则将约束重设回动画之前的状态。

If the animator finished as expected, in a different position, we swap the constraints. This allows the view to be animated back and forth, again and again.

如果动画师按预期完成,则在其他位置进行交换约束。 这允许视图一次又一次地来回动画。

Lastly we do a quick sanity check to make sure that if the position state is .end, the view has actually changed position. When developing the app I ran into some buggy behavior, but this solved the issue.

最后,我们进行快速完整性检查,以确保如果position状态为.end ,则视图实际上已更改位置。 在开发应用程序时,我遇到了一些错误的行为,但这解决了这个问题。

摘要 (Summary)

The sample BlockViewController code can be found here, but please keep in mind that it is taken out of context from a larger application. It will not work out of the box.

可以在此处找到示例BlockViewController代码,但请记住,它是从较大的应用程序中脱离上下文使用的。 开箱即用。

For more interesting projects ranging from Node.js to Raspberry Pi, please feel free to check out my website. Or download Bloq for free on the App Store.

有关从Node.js到Raspberry Pi的更多有趣项目,请随时访问我的网站 。 或在App Store上免费下载Bloq

翻译自: https://www.freecodecamp.org/news/interactive-animations-with-swifts-uiviewpropertyanimator-284262530a0a/

swift 与 js的交互

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值