我知道这个问题已经有将近 2 年半的历史了,但以防万一有人通过搜索引擎找到这个问题:
我想说你最好的选择是使用UIViewPropertyAnimator。这里有一篇很棒的文章:http://www.swiftkickmobile.com/building-better-app-animations-swift-uiviewpropertyanimator/
编辑:
我设法得到了一个使用 UIViewPropertyAnimator 的简单原型,这是它的 GIF:
这是 Github 上的项目:https://github.com/Luigi123/UIViewPropertyAnimatorExample
基本上我有两个UIViewControllers,一个叫ViewController,一个叫BottomSheetViewController。辅助视图有一个 UIPanGestureRecognizer 以使其可拖动,并且在识别器的回调函数中我做了 3 件事(在实际移动它之后):
①计算屏幕被拖动了多少百分比,
② 触发辅助视图本身的动画,
③ 通知主视图有关拖动动作,以便它可以触发它的动画。在这种情况下,我使用Notification,将百分比传递到notification.userInfo。
我不确定如何表达①,例如,如果屏幕是 500 像素高并且用户将辅助视图拖到第 100 像素,我计算出用户将其拖了 20% .这个百分比正是我需要传递到UIViewPropertyAnimator 实例中的fractionComplete 属性的值。
⚠️ 需要注意的一点是,我无法使其与实际的导航栏一起使用,因此我使用了一个“普通”视图,其中包含一个标签。
我尝试通过删除一些实用功能来缩小代码,例如检查用户交互是否完成,但这意味着用户可以停止在屏幕中间拖动并且应用程序根本不会做出反应,所以我真的建议您在 github repo 中查看整个代码。但好消息是执行动画的整个代码大约只有 100 行代码。
考虑到这一点,这是主屏幕的代码,ViewController:
import UIKit
import MapKit
import NotificationCenter
class ViewController: UIViewController {
@IBOutlet weak var someView: UIView!
@IBOutlet weak var blackView: UIView!
var animator: UIViewPropertyAnimator?
func createBottomView() {
guard let sub = storyboard!.instantiateViewController(withIdentifier: "BottomSheetViewController") as? BottomSheetViewController else { return }
self.addChild(sub)
self.view.addSubview(sub.view)
sub.didMove(toParent: self)
sub.view.frame = CGRect(x: 0, y: view.frame.maxY - 100, width: view.frame.width, height: view.frame.height)
}
func subViewGotPanned(_ percentage: Int) {
guard let propAnimator = animator else {
animator = UIViewPropertyAnimator(duration: 3, curve: .linear, animations: {
self.blackView.alpha = 1
self.someView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8).concatenating(CGAffineTransform(translationX: 0, y: -20))
})
animator?.startAnimation()
animator?.pauseAnimation()
return
}
propAnimator.fractionComplete = CGFloat(percentage) / 100
}
func receiveNotification(_ notification: Notification) {
guard let percentage = notification.userInfo?["percentage"] as? Int else { return }
subViewGotPanned(percentage)
}
override func viewDidLoad() {
super.viewDidLoad()
createBottomView()
let name = NSNotification.Name(rawValue: "BottomViewMoved")
NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil, using: receiveNotification(_:))
}
}
以及辅助视图的代码 (BottomSheetViewController):
import UIKit
import NotificationCenter
class BottomSheetViewController: UIViewController, UIGestureRecognizerDelegate {
@IBOutlet weak var navBarView: UIView!
var panGestureRecognizer: UIPanGestureRecognizer?
var animator: UIViewPropertyAnimator?
override func viewDidLoad() {
gotPanned(0)
super.viewDidLoad()
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(respondToPanGesture))
view.addGestureRecognizer(gestureRecognizer)
gestureRecognizer.delegate = self
panGestureRecognizer = gestureRecognizer
}
func gotPanned(_ percentage: Int) {
if animator == nil {
animator = UIViewPropertyAnimator(duration: 1, curve: .linear, animations: {
let scaleTransform = CGAffineTransform(scaleX: 1, y: 5).concatenating(CGAffineTransform(translationX: 0, y: 240))
self.navBarView.transform = scaleTransform
self.navBarView.alpha = 0
})
animator?.isReversed = true
animator?.startAnimation()
animator?.pauseAnimation()
}
animator?.fractionComplete = CGFloat(percentage) / 100
}
// MARK: methods to make the view draggable
@objc func respondToPanGesture(recognizer: UIPanGestureRecognizer) {
let translation = recognizer.translation(in: self.view)
moveToY(self.view.frame.minY + translation.y)
recognizer.setTranslation(.zero, in: self.view)
}
private func moveToY(_ position: CGFloat) {
view.frame = CGRect(x: 0, y: position, width: view.frame.width, height: view.frame.height)
let maxHeight = view.frame.height - 100
let percentage = Int(100 - ((position * 100) / maxHeight))
gotPanned(percentage)
let name = NSNotification.Name(rawValue: "BottomViewMoved")
NotificationCenter.default.post(name: name, object: nil, userInfo: ["percentage": percentage])
}
}