Интерактивный переход ViewController, запускаемый одновременно распознавателями жестов и панорамирования
У меня есть два viewControllers:
ViewController1
Сложный стек субконтроллеров с где-то посередине imageView
ViewController2
ScrollView со встроенным в него изображением
Я пытаюсь добиться перехода между двумя viewController, который запускается путем зажима imageView из viewController 1, что приводит к его увеличению и переключению на viewController 2. Когда переход завершен, imageView должен быть увеличен до минимума. так как он был увеличен во время перехода, вызванного жестом щепотки.
В то же время я хочу поддерживать панорамирование изображения при выполнении масштабирования, чтобы, как и при масштабировании, изображение в конечном состоянии было преобразовано в место, в которое оно было панорамировано.
До сих пор я пробовал модуль переходов Hero и пользовательские переходы viewController, которые я написал сам. Проблема с переходами героев заключается в том, что изображение не привязывается должным образом к конечному состоянию во втором viewController. Проблема, с которой я столкнулся при пользовательском переходе viewController, заключается в том, что я не мог одновременно работать с масштабированием и панорамированием.
У кого-нибудь есть идеи, как реализовать это в Swift? Помощь очень ценится.
2 ответа
Вопрос можно разделить на два:
- Как реализовать масштабирование и перетаскивание, используя жест панорамирования на
imageView
- Как представить контроллер представления одним из его подпредставлений (
imageView
в vc2) позиционируется так же, как подпредставление (imageView
в vc1) в представлении контроллер представления
Масштабирование с помощьюжеста: увеличение с помощью пинча проще UIScrollView
поскольку это поддерживает это из коробки без необходимости добавлять распознаватель жестов. Создать scrollView
и добавьте представление, которое вы хотите увеличить, с помощью пинча в качестве подпредставления (scrollView.addSubview(imageView)
). Не забудьте добавить scrollView
сам по себе (view.addSubview(scrollView)
).
Настройте scrollView's
минимальное и максимальное масштабирование: scrollView.minimumZoomScale, scrollView.maximumZoomScale
, Установить делегата для scrollView.delegate
и реализовать UIScrollViewDelegate
:
func viewForZooming(in scrollView: UIScrollView) -> UIView?
Который должен вернуть ваш imageView
в этом случае и
Также соответствуют UIGestureRecognizerDelegate
и реализовать:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool
Который должен вернуть истину. Это ключ, который позволяет нам работать с распознавателем жестов панорамирования с помощью встроенного распознавателя жестов.
Перетаскивание жестов панорамирования: просто создайте распознаватель жестов панорамирования с целью и добавьте его в представление прокрутки. scrollView.addGestureRecognizer(pan)
,
Обработка жестов: масштабирование Pinch работает хорошо на этом этапе, за исключением того, что вы хотели бы представить второй контроллер представления, когда зажимание заканчивается. Реализуйте еще один UIScrollViewDelegate
метод, который будет уведомлен, когда масштабирование заканчивается:
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat)
И вызовите ваш метод, который представляет контроллер подробного представления presentDetail()
Мы реализуем это немного позже.
Следующим шагом будет обработка жеста панорамирования, я позволю самому коду объяснить:
// NOTE: Do NOT set from anywhere else than pan handler.
private var initialTouchPositionY: CGFloat = 0
private var initialTouchPositionX: CGFloat = 0
@objc func panned(_ pan: UIPanGestureRecognizer) {
let y = pan.location(in: scrollView).y
let x = pan.location(in: scrollView).x
switch pan.state {
case .began:
initialTouchPositionY = pan.location(in: imageView).y
initialTouchPositionX = pan.location(in: imageView).x
case .changed:
let offsetY = y - initialTouchPositionY
let offsetX = x - initialTouchPositionX
imageView.frame.origin = CGPoint(x: offsetX, y: offsetY)
case .ended:
presentDetail()
default: break
}
}
Реализация движется imageView
вокруг, следуя за панорамированием и звонками presentDetail()
когда жест заканчивается
Прежде чем мы осуществим presentDetail()
, перейдите к контроллеру подробного вида и добавьте свойства для хранения imageViewFrame
и image
сам. Теперь в vc1 мы реализуем presentDetail()
в качестве таких:
private func presentDetail() {
let frame = view.convert(imageView.frame, from: scrollView)
let detail = DetailViewController()
detail.imageViewFrame = frame
detail.image = imageView.image
// Note that we do not need the animation.
present(detail, animated: false, completion: nil)
}
В вашем DetailViewController
, обязательно установите imageViewFrame
и изображение, например, в viewDidLoad
и вы будете установлены.
Полный рабочий пример:
class ViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
let imageView: UIImageView = UIImageView()
let scrollView: UIScrollView = UIScrollView()
lazy var pan: UIPanGestureRecognizer = {
return UIPanGestureRecognizer(target: self, action: #selector(panned(_:)))
}()
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = // set your image
scrollView.delegate = self
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 10.0
scrollView.addSubview(imageView)
view.addSubview(scrollView)
scrollView.frame = view.frame
let w = view.bounds.width - 30 // padding of 15 on each side
imageView.frame = CGRect(x: 0, y: 0, width: w, height: w)
imageView.center = scrollView.center
scrollView.addGestureRecognizer(pan)
}
// NOTE: Do NOT set from anywhere else than pan handler.
private var initialTouchPositionY: CGFloat = 0
private var initialTouchPositionX: CGFloat = 0
@objc func panned(_ pan: UIPanGestureRecognizer) {
let y = pan.location(in: scrollView).y
let x = pan.location(in: scrollView).x
switch pan.state {
case .began:
initialTouchPositionY = pan.location(in: imageView).y
initialTouchPositionX = pan.location(in: imageView).x
case .changed:
let offsetY = y - initialTouchPositionY
let offsetX = x - initialTouchPositionX
imageView.frame.origin = CGPoint(x: offsetX, y: offsetY)
case .ended:
presentDetail()
default: break
}
}
// MARK: UIScrollViewDelegate
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
presentDetail()
}
// MARK: UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
// MARK: Private
private func presentDetail() {
let frame = view.convert(imageView.frame, from: scrollView)
let detail = DetailViewController()
detail.imageViewFrame = frame
detail.image = imageView.image
present(detail, animated: false, completion: nil)
}
}
class DetailViewController: UIViewController {
let imageView: UIImageView = UIImageView()
var imageViewFrame: CGRect!
var image: UIImage?
override func viewDidLoad() {
super.viewDidLoad()
imageView.frame = imageViewFrame
imageView.image = image
view.addSubview(imageView)
view.addSubview(backButton)
}
lazy var backButton: UIButton = {
let button: UIButton = UIButton(frame: CGRect(x: 10, y: 30, width: 60, height: 30))
button.addTarget(self, action: #selector(back(_:)), for: .touchUpInside)
button.setTitle("back", for: .normal)
return button
}()
@objc func back(_ sender: UIButton) {
dismiss(animated: false, completion: nil)
}
}
Похоже на UIView.animate(withDuration: animations: completion:)
должен помочь вам; например, в animations
блок вы можете установить новый кадр изображения, и в completion:
- присутствует второй контроллер вида (без анимации);