Mobile Safariでは、下部にページコントロールがあるUIScrollView水平ページングビューのようなものを入力することにより、ページを切り替えることができます。
水平スクロール可能なUIScrollViewが次のビューのコンテンツの一部を表示するこの特定の動作を再現しようとしています。
Appleの例:PageControlは、水平ページングにUIScrollViewを使用する方法を示していますが、すべてのビューが画面幅全体を占めています。
モバイルSafariのようにUIScrollViewを取得して次のビューのコンテンツを表示するにはどうすればよいですか?
ページングを有効にしたUIScrollView
は、そのフレーム幅(または高さ)の倍数で停止します。そのため、最初のステップは、ページの幅を決定することです。 UIScrollView
の幅を確認します。次に、サブビューのサイズを必要に応じて大きく設定し、UIScrollView
の幅の倍数に基づいて中心を設定します。
それから、もちろん他のページを見たいので、mhjoyが述べたようにclipsToBounds
をNO
に設定します。トリックの一部は、ユーザーがUIScrollView
のフレームの範囲外でドラッグを開始したときにスクロールさせることです。私の解決策(最近これをやらなければならなかったとき)は次のとおりでした。
UIView
とそのサブビューを含むClipView
サブクラス(つまり、UIScrollView
)を作成します。基本的に、通常の状況でUIScrollView
が想定するフレームを持っている必要があります。 UIScrollView
をClipView
の中央に配置します。幅が親ビューの幅より小さい場合は、ClipView
のclipsToBounds
がYES
に設定されていることを確認してください。また、ClipView
にはUIScrollView
への参照が必要です。
最後のステップは、ClipView
内の- (UIView *)hitTest:withEvent:
をオーバーライドすることです。
_- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [self pointInside:point withEvent:event] ? scrollView : nil;
}
_
これは基本的に、UIScrollView
のタッチ領域を、親のビューのフレームに、まさに必要なものに拡張します。
別のオプションはUIScrollView
をサブクラス化してその- (BOOL)pointInside:(CGPoint) point withEvent:(UIEvent *) event
メソッドをオーバーライドすることですが、クリッピングを行うにはコンテナビューが必要であり、YES
は、UIScrollView
のフレームのみに基づいています。
注:Juri Pakasteの hitTest:withEvent:modification もご覧くださいサブビューのユーザーインタラクションに問題があります。
上記の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;
}
ScrollViewのフレームサイズをページサイズとして設定します。
[self.view addSubview:scrollView];
[self.view addGestureRecognizer:mainScrollView.panGestureRecognizer];
これで、self.view
、およびscrollViewのコンテンツがスクロールされます。
また、scrollView.clipsToBounds = NO;
コンテンツのクリッピングを防ぎます。
カスタムUIScrollViewを自分で使用することにしました。ただし、正確なコードが表示されなかったため、共有すると思いました。私のニーズは、小さなコンテンツを持つUIScrollViewであり、UIScrollView自体はページング効果を達成するには小さかったです。投稿にあるように、スワイプすることはできません。しかし、今はできます。
クラスCustomScrollViewとサブクラスUIScrollViewを作成します。次に、これを.mファイルに追加するだけです。
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return (point.y >= 0 && point.y <= self.frame.size.height);
}
これにより、左右にスクロールできます(水平)。それに応じて境界を調整し、スワイプ/スクロールタッチ領域を設定します。楽しい!
Scrollviewを自動的に返すことができる別の実装を作成しました。そのため、プロジェクトでの再利用を制限する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;
}
ClipViewのhitTest実装に役立つ可能性のある別の変更があります。 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;
}
この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の回答を編集することを考えましたが、変更するには大きすぎました。)
上記の提案を実装しましたが、使用していた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;
}
これにより、スワイプアクションの委任が完全に回避され、これもボーナスでした。 UIScrollViewDelegate
には、scrollViewWillEndDragging:withVelocity:targetContentOffset:
と呼ばれる同様のメソッドがあり、UITableViewsおよびUIScrollViewsのページングに使用できます。
私のバージョンは、スクロールの上にあるボタンを押す-仕事=)
- (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]のみがスクロールである必要があります
エド・マーティの回答に基づいたSwift回答ですが、スクロールビュー内でボタンをタップできるようにするためのJuri Pakasteによる修正も含まれています。
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let view = super.hitTest(point, with: event)
return view == self ? scrollView : view
}