Проблема с блокировкой вертикальной прокрутки в UIScrollView в Swift 4.0 - PullRequest
0 голосов
/ 31 декабря 2018

У меня есть карусель изображений в моем приложении. Я использую UIScrollView, чтобы показать изображения внутри.все работает отлично, просто я хочу знать, как блокировать движения в UIScrollView

Я пытаюсь заблокировать вертикальную прокрутку, выполнив:

scrollView.showsVerticalScrollIndicator = false
scrollView.contentSize  = CGSize(width: scrollView.contentSize.width, height: 0) //disable vertical

все в этомработает нормально и действительно блокирует вертикальную прокрутку

Проблема в том, что у меня также есть таймер, который программно перемещает UIScrollView, выполняя:

var frame: CGRect = scrollView.frame
frame.origin.x = frame.size.width * CGFloat(pageToMove)
frame.origin.y = -35
scrollView.scrollRectToVisible(frame, animated: true)

и как только я блокируювертикальная прокрутка, эта функция до scrollReactToVisible ничего не делает.и я не получаю никакой ошибки за это.

Есть ли способ в настоящее время также блокировать прокрутку по вертикали (и разрешать прокрутку вправо и влево как обычно), а также программно перемещать представление прокрутки?

Я подключаю свой контроллер полного вида:

class CaruselleScreenViewController: UIViewController, CaruselleScreenViewProtocol, UIScrollViewDelegate {

    var myPresenter: CaruselleScreenPresenterProtocol?

    @IBOutlet weak var pageControl: UIPageControl!
    @IBOutlet weak var scrollView: UIScrollView!

    var slides:[CaruselleTipsCard] = [];

    var timer:Timer?
    var currentPageMultiplayer = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        myPresenter = CaruselleScreenPresenter(controller: self)

        //initlizes view
        pageControl.numberOfPages = slides.count
        pageControl.currentPage = 0
        view.bringSubview(toFront: pageControl)

        //delegates
        scrollView.delegate = self

        ////blocks vertical movement
        scrollView.showsVerticalScrollIndicator = false
        //scrollView.contentSize  = CGSize(width: scrollView.contentSize.width, height: 0) //disable vertical
    }

    func scheduleTimer(_ timeInterval: TimeInterval){
        timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(timerCall), userInfo: nil, repeats: false)
    }

    @objc func timerCall(){
        print("Timer executed")

        currentPageMultiplayer = currentPageMultiplayer + 1

        if (currentPageMultiplayer == 5) {
            currentPageMultiplayer = 0
        }
        pageControl.currentPage = currentPageMultiplayer
        scrollToPage(pageToMove: currentPageMultiplayer)

        scheduleTimer(5)
    }

    func scrollToPage(pageToMove: Int) {
        print ("new one")
        var frame: CGRect = scrollView.frame
        frame.origin.x = frame.size.width * CGFloat(pageToMove)
        frame.origin.y = -35
        scrollView.scrollRectToVisible(frame, animated: true)
    }

    func createSlides() -> [CaruselleTipsCard] {

        let slide1:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
        slide1.mainPic.image = UIImage(named: "backlightingIllo")

        //
        let slide2:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
        slide2.mainPic.image = UIImage(named: "comfortableIllo")

        //
        let slide3:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
        slide3.mainPic.image = UIImage(named: "pharmacyIllo")

        //
        let slide4:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
        slide4.mainPic.image = UIImage(named: "batteryIllo")

        //
        let slide5:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
        slide5.mainPic.image = UIImage(named: "wiFiIllo")

        return [slide1, slide2, slide3, slide4, slide5]
    }

    func setupSlideScrollView(slides : [CaruselleTipsCard]) {
        scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
        scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(slides.count), height: view.frame.height)
        scrollView.isPagingEnabled = true

        for i in 0 ..< slides.count {
            slides[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)
            scrollView.addSubview(slides[i])
        }
    }


    //////

    /*
     * default function called when view is scrolled. In order to enable callback
     * when scrollview is scrolled, the below code needs to be called:
     * slideScrollView.delegate = self or
     */
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let pageIndex = round(scrollView.contentOffset.x/view.frame.width)
        pageControl.currentPage = Int(pageIndex)

        let maximumHorizontalOffset: CGFloat = scrollView.contentSize.width - scrollView.frame.width
        let currentHorizontalOffset: CGFloat = scrollView.contentOffset.x

        // vertical
        let maximumVerticalOffset: CGFloat = scrollView.contentSize.height - scrollView.frame.height
        let currentVerticalOffset: CGFloat = scrollView.contentOffset.y

        let percentageHorizontalOffset: CGFloat = currentHorizontalOffset / maximumHorizontalOffset
        let percentageVerticalOffset: CGFloat = currentVerticalOffset / maximumVerticalOffset


        /*
         * below code changes the background color of view on paging the scrollview
         */
        //        self.scrollView(scrollView, didScrollToPercentageOffset: percentageHorizontalOffset)


        /*
         * below code scales the imageview on paging the scrollview
         */
        let percentOffset: CGPoint = CGPoint(x: percentageHorizontalOffset, y: percentageVerticalOffset)

        if(percentOffset.x > 0 && percentOffset.x <= 0.25) {

            slides[0].mainPic.transform = CGAffineTransform(scaleX: (0.25-percentOffset.x)/0.25, y: (0.25-percentOffset.x)/0.25)
            slides[1].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.25, y: percentOffset.x/0.25)

        } else if(percentOffset.x > 0.25 && percentOffset.x <= 0.50) {
            slides[1].mainPic.transform = CGAffineTransform(scaleX: (0.50-percentOffset.x)/0.25, y: (0.50-percentOffset.x)/0.25)
            slides[2].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.50, y: percentOffset.x/0.50)

        } else if(percentOffset.x > 0.50 && percentOffset.x <= 0.75) {
            slides[2].mainPic.transform = CGAffineTransform(scaleX: (0.75-percentOffset.x)/0.25, y: (0.75-percentOffset.x)/0.25)
            slides[3].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.75, y: percentOffset.x/0.75)

        } else if(percentOffset.x > 0.75 && percentOffset.x <= 1) {
            slides[3].mainPic.transform = CGAffineTransform(scaleX: (1-percentOffset.x)/0.25, y: (1-percentOffset.x)/0.25)
            slides[4].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x, y: percentOffset.x)
        }
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "findingClinitionSugue" {
            let destination = segue.destination as! FirstAvailableSearchViewController
            //destination.consumer = consumer
        }

        if (timer != nil) {
            timer?.invalidate()
        }
    }

    // protocol functions

    func initlizeSlides() {
        slides = createSlides()
        setupSlideScrollView(slides: slides)
    }

    func initlizeTimer() {
        scheduleTimer(5)
    }
}

Ответы [ 6 ]

0 голосов
/ 10 января 2019

Используя UIScrollViewDelegate (или KVO для contentOffset scrollView), вы можете просто противодействовать любому вертикальному движению в карусели.Примерно так:

var oldYOffset: CGFloat ....

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let deltaY = oldYOffset - scrollView.contentOffset.y
    oldYOffset = scrollView.contentOffset.y
    scrollView.contentOffset.y -= deltaY
}

Это изменение смещения не будет видимо пользователю.Вы даже можете использовать это, чтобы увеличить скорость прокрутки, инвертировать прокрутку (панорамирование влево и прокрутка scrollView вправо) или полностью заблокировать движение scrollView, не касаясь isScrollEnabled, contentSize и т. Д.

0 голосов
/ 09 января 2019

Если я хорошо понял вашу проблему, вы можете прекратить прокрутку, когда захотите, с помощью этого

scrollView.isScrollEnabled = false
0 голосов
/ 09 января 2019

Это оказалось довольно интересной проблемой ...

Хотя UIScrollView прокрутку на одну ось легко заблокировать только с помощью UIScrollViewDelegate, невозможно обеспечить плавную прокрутку при изменениипрокрутка программно (как вы делаете с Timer) одновременно.

Ниже вы найдете класс DirectionLockingScrollView, который я только что написал, который должен упростить вам задачу.Это UIScrollView, который вы можете инициализировать либо программно, либо через Interface Builder.

Имеет свойства isHorizontalScrollingEnabled и isVerticalScrollingEnabled.

КАК ЭТО РАБОТАЕТ ВНУТРИ

Добавляет второй "элемент управления" UIScrollView, которыйидентичен основному DirectionLockingScrollView и распространяет на него все события панорамирования, предназначенные для основного вида прокрутки.Каждый раз, когда изменяются границы «прокрутки» представления прокрутки, это изменение распространяется на главное представление прокрутки НО x и y изменяются (на основе isHorizontalScrollingEnabled и isVerticalScrollingEnabled) для отключения прокруткипо запрошенной оси.

DirectionLockingScrollView.swift

/// `UIScrollView` subclass that supports disabling scrolling on any direction
/// while allowing the other direction to be changed programmatically (via
/// `setContentOffset(_:animated)` or `scrollRectToVisible(_:animated)` or changing the 
/// bounds etc.
///
/// Can be initialized programmatically or via the Interface Builder.
class DirectionLockingScrollView: UIScrollView {
    var isHorizontalScrollingEnabled = true
    var isVerticalScrollingEnabled = true

    /// The control scrollview is added below the `DirectionLockingScrollView`
    /// and is used to implement all native scrollview behaviours (such as bouncing)
    /// based on user input.
    ///
    /// It is required to be able to change the bounds of the `DirectionLockingScrollView`
    /// while maintaining scrolling in only one direction and allowing for setting the contentOffset
    /// (changing scrolling for any axis - even the disabled ones) programmatically.
    private let _controlScrollView = UIScrollView(frame: .zero)

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        installCustomScrollView()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        installCustomScrollView()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        updateCustomScrollViewFrame()
    }

    override func didMoveToSuperview() {
        super.didMoveToSuperview()
        guard let superview = superview else {
            _controlScrollView.removeFromSuperview()
            return
        }

        superview.insertSubview(_controlScrollView, belowSubview: self)
        updateCustomScrollViewFrame()
    }

    // MARK: - UIEvent propagation

    func viewIgnoresEvents(_ view: UIView?) -> Bool {
        let viewIgnoresEvents =
            view == nil ||
            view == self ||
            !view!.isUserInteractionEnabled ||
            !(view is UIControl && (view!.gestureRecognizers ?? []).count == 0)

        return viewIgnoresEvents
    }

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let view = super.hitTest(point, with: event)
        if viewIgnoresEvents(view) {
            return _controlScrollView
        }

        return view
    }

    // MARK: - Main scrollview settings propagation to `controlScrollView`

    override var contentInset: UIEdgeInsets {
        didSet {
            _controlScrollView.contentInset = contentInset
        }
    }

    override var contentScaleFactor: CGFloat {
        didSet {
            _controlScrollView.contentScaleFactor = contentScaleFactor
        }
    }

    override var contentSize: CGSize {
        didSet {
            _controlScrollView.contentSize = contentSize
        }
    }

    override var bounces: Bool {
        didSet {
            _controlScrollView.bounces = bounces
        }
    }

    override var bouncesZoom: Bool {
        didSet {
            _controlScrollView.bouncesZoom = bouncesZoom
        }
    }
}

extension DirectionLockingScrollView: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        updateBoundsFromCustomScrollView(scrollView)
    }
}

private extension DirectionLockingScrollView {
    /// Propagates `controlScrollView` bounds to the actual scrollview.
    /// - Parameter scrollView: If the scrollview provided is not the `controlScrollView`
    //                          the main scrollview bounds are not updated.
    func updateBoundsFromCustomScrollView(_ scrollView: UIScrollView) {
        if scrollView != _controlScrollView {
            return
        }

        var newBounds = scrollView.bounds.origin
        if !isHorizontalScrollingEnabled {
            newBounds.x = self.contentOffset.x
        }

        if !isVerticalScrollingEnabled {
            newBounds.y = self.contentOffset.y
        }

        bounds.origin = newBounds
    }

    func installCustomScrollView() {
        _controlScrollView.delegate = self
        _controlScrollView.contentSize = contentSize
        _controlScrollView.showsVerticalScrollIndicator = false
        _controlScrollView.showsHorizontalScrollIndicator = false

        // The panGestureRecognizer is removed because pan gestures might be triggered
        // on subviews of the scrollview which do not ignore touch events (determined
        // by `viewIgnoresEvents(_ view: UIView?)`). This can happen for example
        // if you tap and drag on a button inside the scroll view.    
        removeGestureRecognizer(panGestureRecognizer)
    }

    func updateCustomScrollViewFrame() {
        if _controlScrollView.frame == frame { return }
        _controlScrollView.frame = frame
    }
}

ИСПОЛЬЗОВАНИЕ

После включения вышеуказанного классав своем приложении не забудьте изменить класс представления прокрутки на DirectionLockingScrollView в .xib или .storyboard .

Затем обновите ваш код, как показано ниже (изменены только две строки, помеченные // *****).

class CaruselleScreenViewController: UIViewController, CaruselleScreenViewProtocol, UIScrollViewDelegate {
    var myPresenter: CaruselleScreenPresenterProtocol?

    @IBOutlet weak var pageControl: UIPageControl! 
    @IBOutlet weak var scrollView: DirectionLockingScrollView! // *****

    var slides:[CaruselleTipsCard] = [];


    var timer:Timer?
    var currentPageMultiplayer = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        myPresenter = CaruselleScreenPresenter(controller: self)

        //initlizes view
        pageControl.numberOfPages = slides.count
        pageControl.currentPage = 0
        view.bringSubview(toFront: pageControl)

        scrollView.isHorizontalScrollingEnabled = false // *****

        //delegates
        scrollView.delegate = self

        ////blocks vertical movement
        scrollView.showsVerticalScrollIndicator = false
        //scrollView.contentSize  = CGSize(width: scrollView.contentSize.width, height: 0) //disable vertical
    }

    func scheduleTimer(_ timeInterval: TimeInterval){
        timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(timerCall), userInfo: nil, repeats: false)
    }

    @objc func timerCall(){
        print("Timer executed")

        currentPageMultiplayer = currentPageMultiplayer + 1

        if (currentPageMultiplayer == 5) {
            currentPageMultiplayer = 0
        }
        pageControl.currentPage = currentPageMultiplayer
        scrollToPage(pageToMove: currentPageMultiplayer)

        scheduleTimer(5)
    }

    func scrollToPage(pageToMove: Int) {
        print ("new one")
        var frame: CGRect = scrollView.frame
        frame.origin.x = frame.size.width * CGFloat(pageToMove)
        frame.origin.y = -35
        scrollView.scrollRectToVisible(frame, animated: true)
    }


    func createSlides() -> [CaruselleTipsCard] {

        let slide1:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
        slide1.mainPic.image = UIImage(named: "backlightingIllo")

        //
        let slide2:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
        slide2.mainPic.image = UIImage(named: "comfortableIllo")

        //
        let slide3:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
        slide3.mainPic.image = UIImage(named: "pharmacyIllo")

        //
        let slide4:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
        slide4.mainPic.image = UIImage(named: "batteryIllo")

        //
        let slide5:CaruselleTipsCard = Bundle.main.loadNibNamed("CaruselleTipsCard", owner: self, options: nil)?.first as! CaruselleTipsCard
        slide5.mainPic.image = UIImage(named: "wiFiIllo")

        return [slide1, slide2, slide3, slide4, slide5]
    }

    func setupSlideScrollView(slides : [CaruselleTipsCard]) {
        scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
        scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(slides.count), height: view.frame.height)
        scrollView.isPagingEnabled = true

        for i in 0 ..< slides.count {
            slides[i].frame = CGRect(x: view.frame.width * CGFloat(i), y: 0, width: view.frame.width, height: view.frame.height)
            scrollView.addSubview(slides[i])
        }
    }


    //////

    /*
     * default function called when view is scrolled. In order to enable callback
     * when scrollview is scrolled, the below code needs to be called:
     * slideScrollView.delegate = self or
     */
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let pageIndex = round(scrollView.contentOffset.x/view.frame.width)
        pageControl.currentPage = Int(pageIndex)

        let maximumHorizontalOffset: CGFloat = scrollView.contentSize.width - scrollView.frame.width
        let currentHorizontalOffset: CGFloat = scrollView.contentOffset.x

        // vertical
        let maximumVerticalOffset: CGFloat = scrollView.contentSize.height - scrollView.frame.height
        let currentVerticalOffset: CGFloat = scrollView.contentOffset.y

        let percentageHorizontalOffset: CGFloat = currentHorizontalOffset / maximumHorizontalOffset
        let percentageVerticalOffset: CGFloat = currentVerticalOffset / maximumVerticalOffset


        /*
         * below code changes the background color of view on paging the scrollview
         */
        //        self.scrollView(scrollView, didScrollToPercentageOffset: percentageHorizontalOffset)


        /*
         * below code scales the imageview on paging the scrollview
         */
        let percentOffset: CGPoint = CGPoint(x: percentageHorizontalOffset, y: percentageVerticalOffset)

        if(percentOffset.x > 0 && percentOffset.x <= 0.25) {

            slides[0].mainPic.transform = CGAffineTransform(scaleX: (0.25-percentOffset.x)/0.25, y: (0.25-percentOffset.x)/0.25)
            slides[1].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.25, y: percentOffset.x/0.25)

        } else if(percentOffset.x > 0.25 && percentOffset.x <= 0.50) {
            slides[1].mainPic.transform = CGAffineTransform(scaleX: (0.50-percentOffset.x)/0.25, y: (0.50-percentOffset.x)/0.25)
            slides[2].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.50, y: percentOffset.x/0.50)

        } else if(percentOffset.x > 0.50 && percentOffset.x <= 0.75) {
            slides[2].mainPic.transform = CGAffineTransform(scaleX: (0.75-percentOffset.x)/0.25, y: (0.75-percentOffset.x)/0.25)
            slides[3].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x/0.75, y: percentOffset.x/0.75)

        } else if(percentOffset.x > 0.75 && percentOffset.x <= 1) {
            slides[3].mainPic.transform = CGAffineTransform(scaleX: (1-percentOffset.x)/0.25, y: (1-percentOffset.x)/0.25)
            slides[4].mainPic.transform = CGAffineTransform(scaleX: percentOffset.x, y: percentOffset.x)
        }
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "findingClinitionSugue" {
            let destination = segue.destination as! FirstAvailableSearchViewController
            //destination.consumer = consumer
        }

        if (timer != nil) {
            timer?.invalidate()
        }
    }

    // protocol functions

    func initlizeSlides() {
        slides = createSlides()
        setupSlideScrollView(slides: slides)
    }

    func initlizeTimer() {
        scheduleTimer(5)
    }
}
0 голосов
/ 04 января 2019

Может быть, вы можете создать подкласс UIScrollView и переопределить touchesBegan.

class CustomScrollView: UIScrollView {

    var touchesDisabled = false
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        if touchesDisabled {
            // here parse the touches, if they go in the horizontal direction, allow scrolling
            // set tolerance for vertical movement
            let tolerance: CGFloat = 5.0
            let variance = touches.reduce(0, { Yvariation, touch in
                Yvariation + abs(touch.location(in: view).y - touch.previousLocation(in: view).y)
            })
            if variance <= tolerance * CGFloat(touches.count) {
                let Xtravelled = touches.reduce(0, { Xstep, touch in
                    Xstep + (touch.location(in: view).x - touch.previousLocation(in: view).x)
                })
                // scroll horizontally by the x component of hand gesture
                var newFrame: CGRect = scrollView.frame
                newFrame.origin.x += Xtravelled
                self.scrollRectToVisible(frame, animated: true)
            }
        }

        else {
            super.touchesBegan(touches: touches, withEvent: event)
        }
    }
}

Таким образом, вы можете вручную перемещать представление прокрутки по горизонтали, отключая вертикальное перемещение, когда touchesDisabled установлено true.

0 голосов
/ 01 января 2019

В зависимости от приложения и функциональных возможностей, требуемых в представлении прокрутки, вы можете отключить взаимодействие пользователя с представлением прокрутки, чтобы его можно было перемещать программным способом?

Это будет просто

scrollView.isUserInteractionEnabled = false

Это будетконечно, зависит от того, нужны ли вам элементы в представлении прокрутки для интерактивности

0 голосов
/ 31 декабря 2018

Проблема может заключаться в первоначальном значении значения contentSize высоты 0, поэтому, хотя таймер хочет, чтобы scrollView перемещался, он не может этого сделать.

Можете ли вы попробовать заменить эту строку:

scrollView.contentSize = CGSize(width: scrollView.contentSize.width, height: 0)

С:

scrollView.contentInsetAdjustmentBehavior = .never
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...