web-dev-qa-db-ja.com

ScrollView内のTableViewのスクロールを自然に動作させる方法

奇妙な構成のこのアプリを実行する必要があります。

次の画像に示すように、メインビューはUIScrollViewです。次に、内部にUIPageViewがあり、PageViewの各ページにUITableViewがあります。

figure

これまでにこれをすべて実行しました。しかし、私の問題は、behavenaturallyへのスクロールが必要なことです。

次は私が意味することです自然に。現在、UITableViewの1つをスクロールすると、スクロールビューではなくテーブルビューがスクロールされます。しかし、ScrollViewがスクロールできないためにScrollViewをスクロールさせて、最上部または最下部に移動させたい場合(その場合、TableViewをスクロールさせたい)。

たとえば、スクロールビューが現在上部にスクロールされているとします。次に、(表示されている現在のページの)テーブルビューに指を置き、スクロールを開始しますdown。この場合、私はscrollviewをスクロールさせます(tableviewなし)。スクロールビューを下にスクロールし続けて下部に到達した場合、ディスプレイから指を離してtebleviewの上に戻し、再度下にスクロールすると、ScrollViewが下部に到達したため、TableViewを今すぐ下にスクロールしたいスクロールを続けることができます。

このスクロールを実装する方法についてのアイデアはありますか?

私はこれで本当に迷っています。どんな助けでも大歓迎です:(

ありがとう!

60

スクロールビューとテーブルビューを同時に処理するソリューションは、UIScrollViewDelegateを中心に展開します。したがって、View Controllerをそのプロトコルに適合させてください。

class ViewController: UIViewController, UIScrollViewDelegate {

スクロールビューとテーブルビューをアウトレットとして表します。

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var tableView: UITableView!

また、画面の高さだけでなく、スクロールビューのコンテンツの高さも追跡する必要があります。理由は後でわかります。

let screenHeight = UIScreen.mainScreen().bounds.height
let scrollViewContentHeight = 1200 as CGFloat

viewDidLoad:には少し設定が必要です:

override func viewDidLoad() {
    super.viewDidLoad()

    scrollView.contentSize = CGSizeMake(scrollViewContentWidth, scrollViewContentHeight)
    scrollView.delegate = self
    tableView.delegate = self 
    scrollView.bounces = false
    tableView.bounces = false
    tableView.scrollEnabled = false
}

シンプルにするためにバウンスをオフにしました。 キー設定は、スクロールビューとテーブルビューのデリゲートであり、最初はテーブルビューのスクロールをオフにします。

これらは、scrollViewDidScroll:デリゲートメソッドがスクロールビューの下部に到達し、テーブルビューの上部に到達することを処理できるようにするために必要です。その方法は次のとおりです。

func scrollViewDidScroll(scrollView: UIScrollView) {
    let yOffset = scrollView.contentOffset.y

    if scrollView == self.scrollView {
        if yOffset >= scrollViewContentHeight - screenHeight {
            scrollView.scrollEnabled = false
            tableView.scrollEnabled = true
        }
    }

    if scrollView == self.tableView {
        if yOffset <= 0 {
            self.scrollView.scrollEnabled = true
            self.tableView.scrollEnabled = false
        }
    }
}

デリゲートメソッドが行うことは、スクロールビューが最下部に到達したことを検出することです。その場合、テーブルビューをスクロールできます。また、スクロールビューが再度有効になっているテーブルビューが上部に到達したことも検出します。

結果を示すためにGIFを作成しました。

enter image description here

49
Daniel Zhang

ダニエルの答えを修正して、より効率的でバグのないものにしました。

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var tableHeight: NSLayoutConstraint!

override func viewDidLoad() {
    super.viewDidLoad()
    //Set table height to cover entire view
    //if navigation bar is not translucent, reduce navigation bar height from view height
    tableHeight.constant = self.view.frame.height-64
    self.tableView.isScrollEnabled = false
    //no need to write following if checked in storyboard
    self.scrollView.bounces = false
    self.tableView.bounces = true
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 20
}

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let label = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 30))
    label.text = "Section 1"
    label.textAlignment = .center
    label.backgroundColor = .yellow
    return label
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    cell.textLabel?.text = "Row: \(indexPath.row+1)"
    return cell
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView == self.scrollView {
        tableView.isScrollEnabled = (self.scrollView.contentOffset.y >= 200)
    }

    if scrollView == self.tableView {
        self.tableView.isScrollEnabled = (tableView.contentOffset.y > 0)
    }
}

完全なプロジェクトはここで見ることができます: https://gitlab.com/vineetks/TableScroll.git

19

多くの試行錯誤の後、これは私にとって最もうまくいったものです。このソリューションでは、2つのニーズを解決する必要があります。1)誰がスクロールプロパティを使用するかを決定します。 tableViewまたはscrollView? 2)tableViewがテーブル/コンテンツの最上部に到達するまでscrollViewに権限を与えないことを確認してください。

Scrollviewをスクロールとtableviewのどちらに使用するかを確認するために、tableviewのすぐ上のUIViewがフレーム内にあるかどうかを確認しました。 UIViewがフレーム内にある場合、scrollViewにはスクロールする権限が必要であると言っても安全です。 UIViewがフレーム内にない場合、tableViewがウィンドウ全体を占有しているため、スクロールする権限が必要です。

func scrollViewDidScroll(_ scrollView: UIScrollView) {

    if scrollView.bounds.intersects(UIView.frame) == true {
     //the UIView is within frame, use the UIScrollView's scrolling.   

        if tableView.contentOffset.y == 0 {
            //tableViews content is at the top of the tableView.

        tableView.isUserInteractionEnabled = false
       tableView.resignFirstResponder()
        print("using scrollView scroll")

        } else {

            //UIView is in frame, but the tableView still has more content to scroll before resigning its scrolling over to ScrollView.

            tableView.isUserInteractionEnabled = true
            scrollView.resignFirstResponder()
            print("using tableView scroll")
        }

    } else {

        //UIView is not in frame. Use tableViews scroll.

        tableView.isUserInteractionEnabled = true
       scrollView.resignFirstResponder()
        print("using tableView scroll")

    }

}

これが誰かを助けることを願っています!

5
Felecia Genet

ここでの答えはどれも私にとって完璧に機能しませんでした。それぞれに独自の微妙な問題がありました(1つのスクロールビューが下になったときに繰り返しスワイプするか、スクロールインジケーターが正しく表示されないなど)。

Ole Begemannがこれを正確に行うことについて素晴らしい記事を書いています https://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/

古い投稿であるにもかかわらず、概念は現在のAPIにも適用されます。さらに、彼のアプローチのObjective-C実装が維持されています(Xcode 9互換) https://github.com/eyeem/OLEContainerScrollView

2
Andy Obusek

2つの選択肢があると思います。

スクロールビューとメインビューのサイズがわかっているため、スクロールビューが下にあるかどうかはわかりません。

if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
    // reach bottom
}

ヒットしたとき;基本的に設定します

[contentScrollView setScrollEnabled:NO];

tableViewのその他の方法。

もう1つは、より正確に考えると、ビューにジェスチャーを追加することです。

UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(respondToTapGesture:)];

// Specify that the gesture must be a single tap
tapRecognizer.numberOfTapsRequired = 1;

// Add the tap gesture recognizer to the view
[self.view addGestureRecognizer:tapRecognizer];

// Do any additional setup after loading the view, typically from a nib

そのため、ジェスチャーを追加するときに、setScrollEnabledrespondToTapGestureを変更することで、アクティブビューを簡単に制御できます。

1
Berke Atmaca

私もこの問題に苦労していました。非常に簡単な解決策があります。

インターフェイスビルダーで:

  1. シンプルなViewControllerを作成する
  2. 単純なビューを追加すると、ヘッダーになり、スーパービューに制限されます
    • 以下の例の赤いビューです
    • 上、左、右から12ピクセルを追加し、固定高さを128ピクセルに設定しました
  3. pageViewControllerを埋め込み、ヘッダーではなくスーパービューに制約されるようにします

ib

さて、ここからがおもしろい部分です。追加するページごとに、tableViewの上部からのオフセットを確認してください。それでおしまい。たとえば、このコードを使用すると、次のことができます(UITableViewControllerをページとして使用すると仮定します)。

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let tables = viewControllers.compactMap { $0 as? UITableViewController }
    tables.forEach {
        $0.tableView.contentInset = UIEdgeInsets(top: headerView.bounds.height, left: 0, bottom: 0, right: 0)
        $0.tableView.contentOffset = CGPoint(x: 0, y: -headerView.bounds.height)
    }
}

テーブルビュー内のスクロール内の乱雑なスクロール、デリゲートとのマングリング、重複したスクロール、完全に自然な動作はありません。ヘッダーが表示されない場合は、おそらくtableViewの背景色が原因です。 tableViewの下からヘッダーを表示するには、クリアに設定する必要があります。

1
Mateusz

素晴らしいライブラリを見つけました MXParallaxHeader

StoryboardでUIScrollViewクラスをMXScrollViewに設定すると、魔法が発生します。

UIScrollViewコンテナビューを埋め込むときに、このクラスを使用してUIPageViewControllerを処理しました。さらに詳細に視差ヘッダービューを挿入することもできます。

また、このライブラリはCocoapodsおよびCarthageを提供します

以下にUIViewHierarchyを表す画像を添付しました。 MXScrollView階層

1

正解としてマークされたソリューションを試しましたが、正しく機能しませんでした。ユーザーはスクロールのためにテーブルビューを2回クリックする必要があり、その後、画面全体を再度スクロールすることはできませんでした。したがって、viewDidLoad()で次のコードを適用しました。

tableView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(tableViewSwiped)))
scrollView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(scrollViewSwiped)))

そして、以下のコードはアクションの実装です:

func tableViewSwiped(){
    scrollView.isScrollEnabled = false
    tableView.isScrollEnabled = true
}

func scrollViewSwiped(){
    scrollView.isScrollEnabled = true
    tableView.isScrollEnabled = false
}
0
Vinícius Rocha

たぶんブルートフォースですが、完全に機能しています。ところで、自動レイアウトを使用しています。

tableView(またはcollectionViewなど)の場合、ストーリーボードで任意の高さを設定し、クラスへのアウトレットを作成します。必要に応じて、(viewDidLoad()または...)tableViewをスクロールする必要がないように、tableViewの高さを十分に大きく設定します。 (行数を事前に知る必要があります)そうすると、外側のscrollViewのみがうまくスクロールします。

0
Brian Hong