我试图让这样的事情发挥作用。这是优步应用程序。用户可以在背景视图前向上滑动另一个视图。
背景视图相当简单,已经完成。将在顶部刷过的视图将是UITableView。我希望用户能够在应用程序启动时首先看到一个小的顶部,然后稍微滑动它应该在中间停止然后在完全向上滑动之后应该将它一直带到顶部,替换背景图。
我看过的框架是适用于iOS的可拉取视图。但它太老了,并没有得到任何好的动画。我也看过SWRevealViewController,但我无法弄清楚如何从下面向上滑动。
我也试过使用一个按钮,所以当用户点击它时,表视图控制器会以模态显示,覆盖垂直,但这不是我想要的。它需要识别手势。
非常感谢任何帮助。感谢。
编辑:如果将来有人有更好的实施方式,我将不接受我的回答并保持开放。即使我的回答有效,也可以改进。另外,它并不像优步应用程序中的内容那样流畅,这是我原来的问题。
答案 0 :(得分:3)
编辑:所以,一段时间过去了,现在有一个非常棒的名为Pulley的库。它完全符合我的要求,而且设置轻而易举!
原始回答:
感谢Rikh和Tj3n给我提示。我设法做了一些非常基本的事情,它没有像优步这样的好动画,但它完成了工作。
使用以下代码,您可以滑动任何UIViewController。我在我的图像上使用UIPanGestureRecognizer,它将始终位于拖动视图的顶部。基本上,您使用该图像并识别它被拖动的位置,并根据用户的输入设置视图的帧。
首先转到你的故事板并添加一个将被拖动的UIViewController的标识符。
然后在MainViewController中,使用以下代码:
class MainViewController: UIViewController {
// This image will be dragged up or down.
@IBOutlet var imageView: UIImageView!
// Gesture recognizer, will be added to image below.
var swipedOnImage = UIPanGestureRecognizer()
// This is the view controller that will be dragged with the image. In my case it's a UITableViewController.
var vc = UIViewController()
override func viewDidLoad() {
super.viewDidLoad()
// I'm using a storyboard.
let sb = UIStoryboard(name: "Main", bundle: nil)
// I have identified the view inside my storyboard.
vc = sb.instantiateViewController(withIdentifier: "TableVC")
// These values can be played around with, depending on how much you want the view to show up when it starts.
vc.view.frame = CGRect(x: 0, y: self.view.frame.height, width: self.view.frame.width, height: -300)
self.addChildViewController(vc)
self.view.addSubview(vc.view)
vc.didMove(toParentViewController: self)
swipedOnImage = UIPanGestureRecognizer(target: self, action: #selector(self.swipedOnViewAction))
imageView.addGestureRecognizer(swipedOnImage)
imageView.isUserInteractionEnabled = true
}
// This function handles resizing of the tableview.
func swipedOnViewAction() {
let yLocationTouched = swipedOnImage.location(in: self.view).y
imageView.frame.origin.y = yLocationTouched
// These values can be played around with if required.
vc.view.frame = CGRect(x: 0, y: yLocationTouched, width: UIScreen.main.bounds.width, height: (UIScreen.main.bounds.height) - (yLocationTouched))
vc.view.frame.origin.y = yLocationTouched + 50
}
最终产品
现在,我的答案可能不是最有效的方法,但我是iOS的新手,所以这是我暂时想出来的最好的。
答案 1 :(得分:1)
您可以将该表视图嵌入到自定义滚动视图中,该视图仅在触摸该表视图部件时处理触摸(覆盖hittest),然后将其向上拖动(禁用tableview滚动),直到上部然后禁用滚动视图并启用tableview再次滚动
或者,您只需将滑动手势添加到您的桌面视图中并更改其框架,并在其到达顶部时禁用滑动
试验这些并最终达到你想要的效果
答案 2 :(得分:1)
正如Tj3n指出的那样,您可以使用UISwipeGesture
来显示UITableView
。所以使用约束(而不是框架)继承了你如何做到这一点:
转到您希望显示UIViewController
的故事板中的UITableView
。拖放UITableView
并向UITableView
添加前导,尾随和高度。现在在UIViewController
和UITableView
之间添加一个垂直约束,以便UITableView
在UIViewController
下方显示 (使用此垂直值播放,直到您可以显示UITableView
的顶部以满足您的需要)。为垂直间距约束和高度约束创建出口(如果您需要设置在运行时可以计算出的特定高度)。在向上滑动时,只需动画设置垂直约束等于高度排序的负值,如:
topSpaceToViewControllerConstraint.constant = -mainTableViewHeightConstraint.constant
UIView.animate(withDuration: 0.3) {
view.layoutIfNeeded()
};
<强>替代地强>
如果您希望能够根据平移量({取决于用户在屏幕上移动了多少或有多快)来启用UITableView
,则应使用UIPanGestureRecognizer
代替并尝试为UITableView
设置框架而不是autoLayout(因为我不是反复调用view.layoutIfNeeded
的忠实粉丝。我在某处读到这是一项昂贵的操作,如果有人确认,我会很感激或纠正这个。)
func handlePan(sender: UIPanGestureRecognizer) {
if sender.state == .Changed {
//update y origin value here based on the pan amount
}
}
或者使用UITableViewController
如果您愿意,也可以使用UITableViewController
执行您想要执行的操作,但通过创建自定义UINavigationControllerDelegate
主要用于创建将使用的自定义动画,它会涉及大量伪装和努力UIPercentDrivenInteractiveTransition
根据平底锅数量,如果您需要UITableViewController
,请使用UIPanGestureRecognizer
。否则,您只需添加UISwipeGestureRecognizer
即可显示UITableViewController
,但您仍需要再次创建自定义动画以“伪造”所需的效果。
答案 3 :(得分:1)
我知道这个问题已经存在了将近2年半,但以防万一有人通过搜索引擎找到了这个问题:
我想说,最好的选择是使用UIViewPropertyAnimator
。这里有一篇很棒的文章:http://www.swiftkickmobile.com/building-better-app-animations-swift-uiviewpropertyanimator/
编辑:
我设法得到了一个与UIViewPropertyAnimator
一起工作的简单原型,这是它的GIF:
这是Github上的项目:https://github.com/Luigi123/UIViewPropertyAnimatorExample
基本上,我有两个UIViewController
,主要的一个叫ViewController
,次要的一个叫BottomSheetViewController
。辅助视图具有UIPanGestureRecognizer
以使其可拖动,并且在识别器的回调函数中,我做了3件事(实际上是在移动它之后):
①计算已拖动屏幕的百分比,
②在辅助视图本身中触发动画,
③将拖动动作通知主视图,以便它可以触发其动画。在这种情况下,我使用Notification
,并将百分比传递到notification.userInfo
内。
我不确定如何传达①,例如,如果屏幕高500像素,并且用户将辅助视图拖动到第100个像素,我计算出用户将其向上拖动了20% 。这个百分比正是我需要传递到fractionComplete
实例内的UIViewPropertyAnimator
属性中的百分比。
⚠️要注意的一件事是,我无法使其与实际的导航栏一起使用,因此我使用了带有标签的“普通”视图。
我尝试通过删除一些实用程序功能(例如检查用户交互是否完成)来使代码更小,但这意味着用户可以在屏幕中间停止拖动,并且该应用程序完全不响应,所以我确实建议您在github存储库中看到整个代码。但是好消息是,执行动画的整个代码都适合大约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])
}
}