简体   繁体   中英

How can I animate a UIView's rotation only within a certain degree range?

In my app, I've got a simulation of an analog readout dial, like you might find on a car dashboard or a control panel. Run the following snippet for an example of what I mean:

 svg { height: 100vh; } #dial { fill: white; stroke: #CCCCCC; stroke-dasharray: 405; transform: rotate(135deg); transform-origin: center; } #pointer { fill: #FF0000; animation: 2s infinite alternate dialrotate; transform-origin: bottom center; animation-timing-function: ease-in-out; } @keyframes dialrotate { from { transform: rotate(-135deg); } to { transform: rotate(135deg); } } 
 <?xml version="1.0" encoding="utf-8"?> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"> <circle id="dial" cx="99.5" cy="100.5" r="86"/> <polygon id="pointer" points="100.1,25.5 98.2,100 102,100"/> </svg> 

(I'm not using SVGs in my app--I just threw the example together using an SVG because I'm primarily a web developer)

Here's how I'm animating the dial movement right now:

UIView.animate(
    withDuration: 0.1,
    delay: 0.0,
    options: .curveEaseOut,
    animations: {
        self.dialPointer.transform = CGAffineTransform.init(rotationAngle: radians);
    },
    completion: nil
)

I want to animate the pointer's direction between the two extremes (270 degrees). But I DON'T want the pointer to rotate across the shorter 90 degree path outside the dial's bounds; ie this is WRONG:

 svg { height: 100vh; } #dial { fill: white; stroke: #CCCCCC; stroke-dasharray: 405; transform: rotate(135deg); transform-origin: center; } #pointer { fill: #FF0000; animation: 2s infinite alternate dialrotate; transform-origin: bottom center; animation-timing-function: ease-in-out; } @keyframes dialrotate { from { transform: rotate(225deg); } to { transform: rotate(135deg); } } 
 <?xml version="1.0" encoding="utf-8"?> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200"> <circle id="dial" cx="99.5" cy="100.5" r="86"/> <polygon id="pointer" points="100.1,25.5 98.2,100 102,100"/> </svg> 

How can I force the UIView's rotation to rotate the long way around, instead of rotating over the shortest direction?

As you have discovered, by default UIView animation takes the shortest path when interpolating angle values. You have a couple of options:

  1. You can decompose the animation into several shorter steps, or keyframe it.
  2. You can use a CABasicAnimation instead and animate your layer's transform using a cumulative animation

Playground example:

import PlaygroundSupport
import UIKit

let rect = CGRect(x: 0, y: 0, width: 300, height: 300)
let view = UIView(frame: rect)
let bar = UIView(frame: rect.insetBy(dx: 40, dy: 140))
bar.backgroundColor = .red
view.addSubview(bar)

let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.toValue = 1.25 * CGFloat.pi
animation.isCumulative = true
animation.duration = 5
bar.layer.add(animation, forKey: "spin")
bar.layer.transform = CATransform3DMakeRotation(1.25 * CGFloat.pi, 0, 0, 1)
PlaygroundPage.current.liveView = view

You can do it like this

UIView.animate(
    withDuration: 0.1,
    delay: 0.0,
    options: .curveEaseOut,
    animations: {
        if radians > CGFloat.pi {
            self.dialPointer.transform = CGAffineTransform.init(rotationAngle: CGFloat.pi);
        }
        self.dialPointer.transform = CGAffineTransform.init(rotationAngle: radians);
    },
    completion: nil
)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM