Это довольно хороший вопрос, и мне пришлось немного покопаться, чтобы найти подходящее решение.Вот закомментированный код.Идея состоит в том, чтобы добавить собственный жест панорамирования в scrollViewB и установить ProfileDetailViewController в качестве его делегата жеста.Когда панорамирование приводит scrollViewB к его вершине, ProfileOverviewViewController получает предупреждение и начинает прокрутку scrollViewA.Когда пользователь отпускает палец, ProfileOverviewViewController решает, прокрутить ли до конца или вверху содержимого.
Надеюсь, это поможет:)
ProfileDetailViewController:
//
// ProfileDetailViewController.swift
// Sandbox
//
// Created by Eric Blachère on 23/12/2018.
// Copyright © 2018 Eric Blachère. All rights reserved.
//
import UIKit
protocol OverflowDelegate: class {
func onOverflowEnded()
func onOverflow(delta: CGFloat)
}
/// State of overflow of scrollView
///
/// - on: The scrollview is overflowing : ScrollViewA should take the lead. We store the last trnaslation of the gesture
/// - off: No overflow detected
enum OverflowState {
case on(lastRecordedGestureTranslation: CGFloat)
case off
var isOn: Bool {
switch self {
case .on:
return true
case .off:
return false
}
}
}
class ProfileDetailViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate {
@IBOutlet weak var scrollviewB: UIScrollView!
weak var delegate: OverflowDelegate?
/// a pan gesture added on scrollView B
var customPanGesture: UIPanGestureRecognizer!
/// The state of the overflow
var overflowState = OverflowState.off
override func viewDidLoad() {
super.viewDidLoad()
// create a custom pan gesture recognizer added on scrollview B. This way we can be delegate of this gesture & follow the finger
customPanGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panRecognized(gesture:)))
scrollviewB.addGestureRecognizer(customPanGesture)
customPanGesture.delegate = self
scrollviewB.delegate = self
}
@objc func panRecognized(gesture: UIPanGestureRecognizer) {
switch overflowState {
case .on(let lastRecordedGestureTranslation):
// the user just released his finger
if gesture.state == .ended {
print("didEnd !!")
delegate?.onOverflowEnded() // warn delegate
overflowState = .off // end of overflow
scrollviewB.panGestureRecognizer.isEnabled = true // enable scroll again
return
}
// compute the translation delta & send it to delegate
let fullTranslationY = gesture.translation(in: view).y
let delta = fullTranslationY - lastRecordedGestureTranslation
overflowState = .on(lastRecordedGestureTranslation: fullTranslationY)
delegate?.onOverflow(delta: delta)
case .off:
return
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y <= 0 { // scrollview B is at the top
// if the overflow is starting : initilize
if !overflowState.isOn {
let translation = self.customPanGesture.translation(in: self.view)
self.overflowState = .on(lastRecordedGestureTranslation: translation.y)
// disable scroll as we don't scroll in this scrollView from now on
scrollView.panGestureRecognizer.isEnabled = false
}
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true // so that both the pan gestures on scrollview will be triggered
}
}
GlobalViewController:
//
// GlobalViewController.swift
// Sandbox
//
// Created by Eric Blachère on 23/12/2018.
// Copyright © 2018 Eric Blachère. All rights reserved.
//
import UIKit
class GlobalViewController: UIViewController, OverflowDelegate {
@IBOutlet weak var scrollViewA: UIScrollView!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "secondSegue", let ctrl = segue.destination as? ProfileDetailViewController else {
return
}
ctrl.delegate = self
}
func onOverflowEnded() {
// scroll to top if at least one third of the overview is showed (you can change this fraction as you please ^^)
let shouldScrollToTop = (scrollViewA.contentOffset.y <= 2 * scrollViewA.frame.height / 3)
if shouldScrollToTop {
scrollViewA.scrollRectToVisible(CGRect(x: 0, y: 0, width: 1, height: 1), animated: true)
} else {
scrollViewA.scrollRectToVisible(CGRect(x: 0, y: scrollViewA.contentSize.height - 1, width: 1, height: 1), animated: true)
}
}
func onOverflow(delta: CGFloat) {
// move the scrollview content
if scrollViewA.contentOffset.y - delta <= scrollViewA.contentSize.height - scrollViewA.frame.height {
scrollViewA.contentOffset.y -= delta
print("difference : \(delta)")
print("contentOffset : \(scrollViewA.contentOffset.y)")
}
}
}
Редактировать: ProfileOverviewViewController & ProfileDetailViewController устанавливаются в GlobalViewController в раскадровке через представления контейнера, но это также должно работать, если установлено в коде;)