88

Mobile Safariを使用すると、下部にページコントロールがある一種のUIScrollView水平ページングビューを入力してページを切り替えることができます。

水平方向にスクロール可能なUIScrollViewが次のビューのコンテンツの一部を表示する、この特定の動作を再現しようとしています。

Appleが提供した例:PageControlは、水平ページングにUIScrollViewを使用する方法を示していますが、すべてのビューが画面幅全体を占めています。

モバイルSafariのように、UIScrollViewに次のビューのコンテンツを表示させるにはどうすればよいですか?

4

11 に答える 11

265

ページングが有効になっているAUIScrollViewは、フレーム幅(または高さ)の倍数で停止します。したがって、最初のステップは、ページの幅を決定することです。の幅を作成しUIScrollViewます。次に、サブビューのサイズを必要な大きさに設定し、UIScrollView幅の倍数に基づいて中心を設定します。

次に、もちろん他のページを見たいので、mhjoyが述べたように設定clipsToBoundsします。NOここでの秘訣は、ユーザーがUIScrollViewのフレームの範囲外でドラッグを開始したときにスクロールさせることです。私の解決策(ごく最近これをしなければならなかったとき)は次のとおりでした:

とそのサブビューを含むUIViewサブクラス(つまりClipView)を作成します。基本的に、それはあなたが通常の状況下で持っているUIScrollViewとあなたが想定するであろうもののフレームを持っているべきです。を中央にUIScrollView配置します。幅が親ビューの幅よりも小さい場合は、 「s」がに設定されていることを確認してください。また、への参照が必要です。UIScrollViewClipViewClipViewclipsToBoundsYESClipViewUIScrollView

最後のステップは、- (UIView *)hitTest:withEvent:の内部をオーバーライドすることClipViewです。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  return [self pointInside:point withEvent:event] ? scrollView : nil;
}

これにより、基本的に、のタッチ領域がUIScrollView親のビューのフレームに拡張されます。これはまさに必要なものです。

別のオプションは、そのメソッドをサブクラス化UIScrollViewしてオーバーライドすることですが、クリッピングを実行するにはコンテナビューが必要であり、のフレームのみに基づいて- (BOOL)pointInside:(CGPoint) point withEvent:(UIEvent *) eventいつ戻るかを決定するのは難しい場合があります。YESUIScrollView

注: サブビューのユーザー操作に問題がある場合は、Juri PakasteのhitTest:withEvent:変更も確認する必要があります。

于 2009-08-03T03:48:49.503 に答える
70

上記のClipView解決策は私にとってはうまくいきましたが、私は別の-[UIView hitTest:withEvent:]実装をしなければなりませんでした。Ed Martyのバージョンでは、水平スクロールビューの内側にある垂直スクロールビューでユーザーインタラクションが機能しませんでした。

次のバージョンが私のために働いた:

-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
    UIView* child = nil;
    if ((child = [super hitTest:point withEvent:event]) == self)
        return self.scrollView;     
    return child;
}
于 2009-09-03T12:25:46.520 に答える
8

ページサイズに合わせてscrollViewのフレームサイズを設定します。

[self.view addSubview:scrollView];
[self.view addGestureRecognizer:mainScrollView.panGestureRecognizer];

これで、にパンできself.view、scrollViewのコンテンツがスクロールされます。
またscrollView.clipsToBounds = NO;、コンテンツのクリッピングを防ぐために使用します。

于 2014-12-09T13:35:48.443 に答える
5

私は、カスタムUIScrollViewを自分で使用することにしました。これは、私にとって最も速くて簡単な方法だったからです。ただし、正確なコードは表示されなかったため、共有することにしました。私のニーズは、コンテンツが小さいUIScrollViewでした。したがって、ページング効果を実現するにはUIScrollView自体が小さかったです。投稿にあるように、スワイプすることはできません。しかし今、あなたはそうすることができます。

クラスCustomScrollViewとサブクラスUIScrollViewを作成します。次に、これを.mファイルに追加するだけです。

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
  return (point.y >= 0 && point.y <= self.frame.size.height);
}

これにより、左右(水平)にスクロールできます。それに応じて境界を調整し、スワイプ/スクロールタッチエリアを設定します。楽しみ!

于 2013-11-19T03:06:47.727 に答える
4

スクロールビューを自動的に返すことができる別の実装を作成しました。したがって、プロジェクトでの再利用を制限するIBOutletを用意する必要はありません。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if ([self pointInside:point withEvent:event]) {
        for (id view in [self subviews]) {
            if ([view isKindOfClass:[UIScrollView class]]) {
                return view;
            }
        }
    }
    return nil;
}
于 2012-08-17T10:28:59.283 に答える
3

私は上記の賛成の提案を実装しましたが、UICollectionView私はフレーム外のものを画面外と見なして使用していました。これにより、ユーザーがセルに向かってスクロールしているときにのみ、近くのセルが範囲外にレンダリングされました。これは理想的ではありませんでした。

私がやったことは、デリゲート(またはUICollectionViewLayout)に以下のメソッドを追加することにより、スクロールビューの動作をエミュレートすることでした。

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{    
  if (velocity.x > 0) {
    proposedContentOffset.x = ceilf(self.collectionView.contentOffset.x / pageSize) * pageSize;
  }
  else {
    proposedContentOffset.x = floorf(self.collectionView.contentOffset.x / pageSize) * pageSize;
  }

  return proposedContentOffset;
}

これにより、スワイプアクションの委任が完全に回避されます。これもボーナスでした。には、 UITableViewsとUIScrollViewsのページングに使用できるUIScrollViewDelegate同様のメソッドがあります。scrollViewWillEndDragging:withVelocity:targetContentOffset:

于 2012-10-29T17:01:34.650 に答える
3

ClipViewhitTestの実装に役立つ可能性のある別の変更があります。ClipViewへのUIScrollView参照を提供する必要はありませんでした。以下の私の実装では、ClipViewクラスを再利用して、あらゆるもののヒットテスト領域を拡張でき、参照を提供する必要はありません。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (([self pointInside:point withEvent:event]) && (self.subviews.count >= 1))
    {
        // An extended-hit view should only have one sub-view, or make sure the
        // first subview is the one you want to expand the hit test for.
        return [self.subviews objectAtIndex:0];
    }

    return nil;
}
于 2012-03-02T21:27:10.057 に答える
3

このSO質問の手法をサポートしながら、スクロールビューの子ビューでタップイベントを発生させることができます。読みやすくするために、スクロールビュー(self.scrollView)への参照を使用します。

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = nil;
    NSArray *childViews = [self.scrollView subviews];
    for (NSUInteger i = 0; i < childViews.count; i++) {
        CGRect childFrame = [[childViews objectAtIndex:i] frame];
        CGRect scrollFrame = self.scrollView.frame;
        CGPoint contentOffset = self.scrollView.contentOffset;
        if (childFrame.origin.x + scrollFrame.origin.x < point.x + contentOffset.x &&
            point.x + contentOffset.x < childFrame.origin.x + scrollFrame.origin.x + childFrame.size.width &&
            childFrame.origin.y + scrollFrame.origin.y < point.y + contentOffset.y &&
            point.y + contentOffset.y < childFrame.origin.y + scrollFrame.origin.y + childFrame.size.height
        ){
            hitView = [childViews objectAtIndex:i];
            return hitView;
        }
    }
    hitView = [super hitTest:point withEvent:event];
    if (hitView == self)
        return self.scrollView;
    return hitView;
}

これを子ビューに追加して、タッチイベントをキャプチャします。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

(これはuser1856273のソリューションのバリエーションです。読みやすくするためにクリーンアップされ、Bartserkのバグ修正が組み込まれました。user1856273の回答を編集することを考えましたが、変更が大きすぎて実行できませんでした。)

于 2013-07-25T17:49:49.633 に答える
3

これは、Ed Martyの回答に基づくSwiftの回答ですが、スクロールビュー内でボタンタップなどを許可するためのJuriPakasteによる変更も含まれています。

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let view = super.hitTest(point, with: event)
    return view == self ? scrollView : view
}
于 2019-08-19T17:21:34.237 に答える
2

私のバージョン巻物の上にあるボタンを押す-仕事=)

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView* child = nil;
    for (int i=0; i<[[[[self subviews] objectAtIndex:0] subviews] count];i++) {

        CGRect myframe =[[[[[self subviews] objectAtIndex:0] subviews]objectAtIndex:i] frame];
        CGRect myframeS =[[[self subviews] objectAtIndex:0] frame];
        CGPoint myscroll =[[[self subviews] objectAtIndex:0] contentOffset];
        if (myframe.origin.x < point.x && point.x < myframe.origin.x+myframe.size.width &&
            myframe.origin.y+myframeS.origin.y < point.y+myscroll.y && point.y+myscroll.y < myframe.origin.y+myframeS.origin.y +myframe.size.height){
            child = [[[[self subviews] objectAtIndex:0] subviews]objectAtIndex:i];
            return child;
        }


    }

    child = [super hitTest:point withEvent:event];
    if (child == self)
        return [[self subviews] objectAtIndex:0];
    return child;
    }

ただし、[[self subviews] objectAtIndex:0]のみがスクロールである必要があります

于 2013-03-15T16:57:07.853 に答える
0

Swift 5 / iOS 15

TLDR-受け入れられた答えが言うことを行いますが、以下の私のバージョンを使用してhitTest(...)ください

ユーザーがページを左または右にタップして中央にスクロールできるようにしたかったのですが、既存のソリューションは機能しませんでした(このために各ページにジェスチャ認識機能を追加し、ページに対してジェスチャがトリガーされることはありませんでした)scrollViewフレームの外側)。

受け入れられた答えは私にとってほとんどうまくいきましたが、hitTestオーバーライドは良くありませんでした-各ページの要素はタッチに反応しませんでした(私はタップに反応しないUISwitchを持っていました)。境界外のページのタップはページに送信されなかったためhitTest(...)、各ページでインタラクティブ性を許可するための提案された変更も完了しませんでした(推奨される回答は、にタッチを渡すだけで、にタッチを渡すことはありません)そのコンテンツはフレームの外側にあります)。scrollViewscrollView

代わりに次のコードを使用しましたが、完全に機能しています。このようにのコンテンツビューにアクセスするのは好きではありませんscrollViewが、コードがクラッシュすることはなく、元の提案されたコードに安全にフォールバックします。のコンテンツビューにタッチを渡すためのより良い方法を誰かが知っているならscrollView、私はそれをいただければ幸いです。

public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard let scrollViewContentView = scrollView.subviews.first else {
        // fallback behavior - user can scroll carousel if touch starts outside the scrollView frame, but taps on elements outside the scrollView frame will be ignored
        let view = super.hitTest(point, with: event)
        return view == self ? scrollView : view
    }
    
    let convertedPoint = scrollViewContentView.convert(point, from: self)
    return self.point(inside: point, with: event) ? scrollViewContentView.hitTest(convertedPoint, with: event) : nil
}
于 2021-09-29T00:37:53.963 に答える