草庐IT

ios - 将 UIScrollView 的交互转移到另一个 UIScrollView 中滚动

coder 2023-09-14 原文

我有一个具有以下结构的接口(interface),其中重要元素的名称在括号中:

- UIViewController
      - UIScrollView (ScrollViewA)
           - UIViewController (ProfileOverviewViewController)
           - UIViewController (ProfileDetailViewController)
               - UICollectionView (ScrollViewB)

所以基本上在另一个垂直 ScrollView (ScrollViewA) 中有一个垂直 ScrollView (ScrollViewB)。 ProfileOverviewViewControllerProfileDetailViewController 都是设备屏幕的大小,因此 ScrollViewB 只有在 ScrollViewA 滚动到时才可见底部。

分页在 ScrollViewA 上启用,因此捕捉到 ProfileOverviewViewController 获取整个屏幕 View 或 ProfileDetailViewController 获取整个屏幕 View 。

希望这张图能让布局更清晰一点:

我的问题是:

  • 如果用户滚动到 ScrollViewA 的底部,以便 ProfileDetailViewControllerScrollViewB 可见。
  • 用户在 ScrollViewB 上向下滚动一点然后松开。
  • 然后用户在 ScrollViewB 上向上滚动。
  • 在仍然按住手指的同时,当用户到达 ScrollViewB 中内容的顶部时,ScrollViewB 应该停止滚动并且 ScrollViewA应该开始向上滚动到 ProfileOverviewViewController,所有这些都在用户的同一个手指手势内。

因为 bounces 属性为真,所以 ScrollViewB 不会简单地扩展到负的 y 内容偏移量。

ScrollViewB 的滚动到达顶部后如何转移到 ScrollViewA?

提前致谢

最佳答案

这是个很好的问题,我必须深入研究才能找到合适的解决方案。这是注释代码。这个想法是将自定义平移手势添加到 scrollViewB 并将 ProfileDetailViewController 设置为其手势委托(delegate)。 当平移将 scrollViewB 带到其顶部时,ProfileOverviewViewController 会收到警告并开始滚动 scrollViewA。当用户松开手指时,ProfileOverviewViewController 决定是滚动到内容的底部还是顶部。

希望对您有所帮助:)

配置文件详细 View Controller :

//
//  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
    }
}

全局 View Controller :

//
//  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 是通过容器 View 在 Storyboard的 GlobalViewController 中设置的,但如果在代码中设置它应该也能正常工作;)

关于ios - 将 UIScrollView 的交互转移到另一个 UIScrollView 中滚动,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53844951/

有关ios - 将 UIScrollView 的交互转移到另一个 UIScrollView 中滚动的更多相关文章

  1. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  2. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  3. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  4. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  5. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  6. ruby-on-rails - 如何在 ruby​​ 交互式 shell 中有多行? - 2

    这可能是个愚蠢的问题。但是,我是一个新手......你怎么能在交互式ruby​​shell中有多行代码?好像你只能有一条长线。按回车键运行代码。无论如何我可以在不运行代码的情况下跳到下一行吗?再次抱歉,如果这是一个愚蠢的问题。谢谢。 最佳答案 这是一个例子:2.1.2:053>a=1=>12.1.2:054>b=2=>22.1.2:055>a+b=>32.1.2:056>ifa>b#Thecode‘if..."startsthedefinitionoftheconditionalstatement.2.1.2:057?>puts"f

  7. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  8. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  9. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  10. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

随机推荐