Как реализовать интерактивное перелистывание ячеек для запуска переходной анимации, такой как WhatsApp
Я ищу, как реализовать смахивание ячеек WhatsApp, я уже реализовал анимацию смахивания ячеек с помощью UIPanGestureRecognizer, осталось только выполнить интерактивную анимацию - добавление нового UIViewController
в окно и показывает его на основе скорости распознавателя жестов и значения оси X-.
Некоторое дополнительное примечание, чтобы быть точным относительно того, чего я хочу достигнуть:
у меня есть
UITableViewController
, который имеет обычайUITableViewCell
в этом. Я хочу иметь возможность перетаскивать ячейку слева направо для запуска интерактивной анимации. (Примечание: я уже реализовал смахивание ячеек).Новый
UIViewController
будет толкаться слева направо.Проводя ячейку,
UITableViewController
вид будет двигаться вправо, в этот момент я хочу показать толканиеUIViewController
рядом с этим.
Вот GIF для более подробной информации о том, что мне нужно (GIF проводит пальцем по ячейке справа налево, мне нужно наоборот):
4 ответа
Я предлагаю использовать https://github.com/John-Lluch/SWRevealViewController. Это очень легко настроить, используя их руководство, и это выглядит абсолютно великолепно. Я обнаружил, что он работает даже лучше, когда вы предварительно загружаете UIViewController
что вы используете, чтобы быть тем, что показано внизу.
Это добавляет отличный пользовательский опыт для функциональности, которую вы ищете.
Он также может быть интерактивным для пользователя, если вы хотите включить эту функцию. Я не использовал интерактивную функцию, но ее очень легко настроить, запустив всего несколько строк кода:
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let mainVC = storyboard.instantiateInitialViewController()
let menuStoryboard = UIStoryboard(name: "Menu", bundle: sdkBundle)
let menuNav = menuStoryboard.instantiateInitialViewController() as! UINavigationController
let mainRevealVC = SWRevealViewController(rearViewController: menuNav, frontViewController: mainVC)
mainRevealVC?.modalTransitionStyle = .crossDissolve
present(mainRevealVC!, animated: true, completion: nil)
И чтобы раскрыть UIViewController
чтобы показать, вы просто позвоните
// Every UIViewController will have a `self.revealViewController()` when you `import SWRevealViewController`
self.revealViewController().revealToggle(animated: true)
Я согласен с @DonMag, iOS slide menu
может быть, ваш лучший выбор. Вот пример простого: SimpleSideMenu
Обязательно ли должен быть новый контроллер за табличным представлением? Позвольте мне попытаться объяснить мой подход на примере WhatsApp. Давайте предположим, что приложение имеет ChatController
который имеет вид таблицы с чатом и ChatDetailController
это раскрывается с проведением.
Когда вы выбираете разговор, вместо представления ChatController
представить вместо ChatParent
, который автоматически создает и добавляет двух детей. ChatController
а также ChatDetailController
, Далее определите протокол под названием SwipeableCellDelegate
с функцией cellDidSwipe(toPosition position: CGPoint)
и сделать ChatParent
соответствовать этому. Когда ячейка проведена, родитель может принять решение, следует ли убрать чат, и если да, то сколько. Затем он может просто переместить ChatController
смотреть прямо через его .view
собственность, раскрывающая второго ребенка, ChatDetailController
за этим.
Есть два недостатка по сравнению с GIF, который вы опубликовали.
- Панель навигации не исчезает от чата к деталям чата. Я бы, однако, утверждал, что лучше обновить панель навигации после завершения анимации, по крайней мере, я лично не являюсь поклонником этого постепенного изменения, когда вы можете одновременно видеть оба набора элементов навигации. Я думаю, что если чат находится на экране, то элементы чата должны присутствовать и только при полном отображении подробного представления элементы должны обновляться.
- Второе - это анимированное увольнение с клавиатуры. Я понятия не имею, как изменить рамку клавиатуры, чтобы она исчезла пропорционально тому, как далеко пользователь прокручивает, но, возможно, она может быть автоматически отклонена, как только будет обнаружен удар? Это стандартная практика для многих приложений, поэтому она должна быть достойным решением.
Удачи!
Существует очень простая, но идеально подходящая для вашей ситуации библиотека, которая называется https://github.com/CEWendel/SWNavigationController, которая реализует так же, как интерактивный PopGestureRecognizer UINavigationController, а также interactivePushGestureRecognizer. В вашем случае вы не хотите, чтобы толчок запускался из UIScreenEdgePangesturerecognizer, поэтому вам лучше настраивать реализацию, а не устанавливать модуль, что я и сделал. Здесь вы можете найти полный простой проект, который делает то, что вы просили.
Я внес несколько изменений в SWNavigationController для поддержки замены UIScreenEdgePangesturerecognizer на UIPanGestureRecognizer
import UIKit
// First in AppDelegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
let firstVc = ViewController()
let initialViewController: SWNavigationController = SWNavigationController(rootViewController: firstVc)
self.window?.rootViewController = initialViewController
self.window?.makeKeyAndVisible()
return true
}
// Your chat viewController
class ViewController: UIViewController {
var backgroundColors: [IndexPath : UIColor] = [ : ]
var swNavigationController: SWNavigationController {
return navigationController as! SWNavigationController
}
/// The collectionView if you're not using UICollectionViewController
lazy var collectionView: UICollectionView = {
let cv: UICollectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: self.layout)
cv.backgroundColor = UIColor.white
cv.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
cv.dataSource = self
return cv
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "chat vc"
view.addSubview(collectionView)
let panGestureRecognizer: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(ViewController.handlePan(_:)))
panGestureRecognizer.delegate = self
collectionView.addGestureRecognizer(panGestureRecognizer)
// Replace navigation controller's interactivePushGestureRecognizer with our own pan recognizer.
// SWNavigationController uses uiscreenedgerecognizer by default which we don't need in our case.
swNavigationController.interactivePushGestureRecognizer = panGestureRecognizer
}
func handlePan(_ recognizer: UIPanGestureRecognizer) {
guard fabs(recognizer.translation(in: collectionView).x) > fabs(recognizer.translation(in: collectionView).y) else {
return
}
// create the new view controller upon .began
if recognizer.state == .began {
// disable scrolling(optional)
collectionView.isScrollEnabled = false
// pan location
let location: CGPoint = recognizer.location(in: collectionView)
// get indexPath of cell where pan is taking place
if let panCellIndexPath: IndexPath = collectionView.indexPathForItem(at: location) {
// clear previously pushed viewControllers
swNavigationController.pushableViewControllers.removeAllObjects()
// create detail view controller for pan indexPath
let dvc = DetailViewController(indexPath: panCellIndexPath, backgroundColor: backgroundColors[panCellIndexPath]!)
swNavigationController.pushableViewControllers.add(dvc)
}
} else if recognizer.state != .changed {
collectionView.isScrollEnabled = true
}
// let navigation controller handle presenting
// (you can consume the initial pan translation on x axis to drag the cell to the left until a defined threshold and call handleRightSwipe: only after that)
swNavigationController.handleRightSwipe(recognizer)
}
}
// Cell detail view controller
class DetailViewController: UIViewController {
var indexPath: IndexPath
var backgroundColor: UIColor
init(indexPath: IndexPath, backgroundColor: UIColor) {
self.indexPath = indexPath
self.backgroundColor = backgroundColor
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "detail vc at: \(indexPath.row)"
view.backgroundColor = backgroundColor
}
}