Mobil Safari sekmeleri gibi UIScrollView yatay sayfalama


88

Mobile Safari, altta bir sayfa kontrolü ile bir tür UIScrollView yatay sayfalama görünümü girerek sayfaları değiştirmenize olanak tanır.

Yatay olarak kaydırılabilir bir UIScrollView'in sonraki görünümün içeriğinin bir kısmını gösterdiği bu belirli davranışı kopyalamaya çalışıyorum.

Apple tarafından sağlanan örnek: PageControl, yatay sayfalama için bir UIScrollView'ın nasıl kullanılacağını gösterir, ancak tüm görünümler tüm ekran genişliğini kaplar.

Mobil Safari'nin yaptığı gibi sonraki görünümün bazı içeriğini göstermek için bir UIScrollView'ı nasıl edinebilirim?


2
Sadece bir düşünce ... kaydırma görünümünün sınırlarını ekrandan daha küçük yapmaya çalışın ve görünümlerin düzgün görüntülenmesini sağlamak için uğraşın. (ve kaydırma görünümünün klipleriniToBounds'u HAYIR olarak ayarlayın)
mjhoy

Uiscrollview genişliğini (yatay kaydırma) daha büyük sayfalara sahip olmak istedim. Ve mjhoy'un düşüncesi aslında bana yardımcı oldu!
Sasho

Yanıtlar:


263

Bir UIScrollViewetkin çağn ile çerçeve genişliği (veya yüksekliği) adedi durur. Yani ilk adım, sayfalarınızın ne kadar geniş olmasını istediğinizi belirlemektir. Bunu UIScrollView. Ardından, alt görüntünüzün boyutlarını, ihtiyaç duyduğunuz kadar büyük ayarlayın ve merkezlerini UIScrollViewgenişliğin katlarına göre ayarlayın .

Ardından, set elbette diğer sayfaları, görmek istiyorum çünkü clipsToBoundshiç NOmhjoy belirtildiği gibi. İşin püf noktası, kullanıcı UIScrollViewçerçevesinin aralığı dışında sürüklemeye başladığında kaydırmasını sağlamaktır . Benim çözümüm (bunu çok yakın zamanda yapmak zorunda kaldığımda) şuydu:

Ve UIViewalt görünümlerini ClipViewiçerecek bir alt sınıf (yani ) oluşturun UIScrollView. Esasen, UIScrollViewnormal koşullar altında sahip olacağını varsayacağınız çerçeveye sahip olmalıdır . 'Nin UIScrollViewortasına yerleştirin ClipView. Genişliği üst görünümünden daha azsa ClipView's' in clipsToBoundsayarlandığından emin olun YES. Ayrıca, ClipViewbir başvuru gerekiyor UIScrollView.

Son adım geçersiz kılmak için - (UIView *)hitTest:withEvent:içeride ClipView.

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

Bu, temelde, dokunma alanını UIScrollView, tam da ihtiyacınız olana, üst öğesinin görünümünün çerçevesine genişletir .

Diğer bir seçenek UIScrollView, - (BOOL)pointInside:(CGPoint) point withEvent:(UIEvent *) eventyöntemini alt sınıfa ayırmak ve geçersiz kılmak olabilir , ancak yine de kırpmayı yapmak için bir kap görünümüne ihtiyacınız olacak ve YESyalnızca UIScrollViewkaresine bağlı olarak ne zaman döneceğinizi belirlemek zor olabilir .

NOT: Alt görünüm kullanıcı etkileşimiyle ilgili sorunlar yaşıyorsanız , Juri Pakaste'nin hitTest: withEvent: modifikasyonuna da bir göz atmalısınız.


3
Teşekkürler Ed. UIScrollViewGörünümlerin tembel olarak yüklenmesi (in layoutSubviews) için bir alt sınıfla gittim clippingRectve pointInside:withEvent:testi yapmak için a kullandım . Gerçekten iyi çalışıyor ve ek kapsayıcı görünümü gerekmiyor.
ohhorob

1
Mükemmel cevap. UIScrollView@Ohhorob gibi bir alt sınıf kullandım, ancak kırpmak ve geri dönmek için bir konteyner görünümü [super pointInside:CGPointMake(self.contentOffset.x + self.frame.size.width/2, self.contentOffset.y + self.frame.size.height/2) withEvent:event];ile pointInside:withEvent:. Bu çok iyi çalışıyor ve @ JuriPakaste'nin cevabında bahsedilen kullanıcı etkileşimiyle ilgili herhangi bir sorun yok.
cduck

1
Vay canına, bu gerçekten iyi bir çözüm. Yine de, kurulumumda, eklediğim sayfalarda UIButton'ları var. HitTest yöntemini geçersiz kıldığımda, bu düğmelere dokunmama izin vermiyor. Kaydırabiliyorum, ancak herhangi bir sayfada hiçbir eylem seçilemez.
A Salcedo

8
Bununla daha yakın zamanda karşılaşanlar için - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset, iOS5'te UIScrollViewDelegate'e eklenmenin artık bu palaverden geçmek zorunda olmadığınız anlamına geldiğini unutmayın.
Mike Pollard

1
@MikePollard etkisi sessiz değil, targetContentOffset bu duruma pek uymuyor.
Kyle Dişi

70

Yukarıdaki ClipViewçözüm benim için çalıştı, ancak farklı bir -[UIView hitTest:withEvent:]uygulama yapmak zorunda kaldım . Ed Marty'nin sürümü, yatay olanın içindeki dikey kaydırma görünümleriyle çalışan kullanıcı etkileşimini almadı.

Aşağıdaki sürüm benim için çalıştı:

-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
    UIView* child = nil;
    if ((child = [super hitTest:point withEvent:event]) == self)
        return self.scrollView;     
    return child;
}

Bu ayrıca, kullanıcı etkileşimi çalışırken düğmeler ve diğer alt görünümlerin alınmasına yardımcı olur. :) teşekkürler
Thomas Clayson

4
Bu kod için teşekkür ederiz, kaydırma görünümü sınırlarında yer almayan alt görünümlerden (düğmeler) olayları almak için bu değişikliği nasıl kullanabilirim? Örneğin, bir düğme merkezi kaydırma görünümünün sınırları içinde ise ancak dışında değilse çalışır.
DreamOfMirrors

4
Bu gerçekten yardımcı oldu. Seçilen cevabın bir değişikliği olarak dahil edilmelidir.
Pacu

2
Evet! Bu ayrıca kaydırma görünümündeki kullanıcı etkileşiminin yeniden çalışmasını sağlar! Bunun çalışması için ClipView'da userInteractionEnabled'ı YES olarak ayarlamak zorunda kaldım.
Tom van Zummeren

Self.scrollView.subviews'i yinelemek ve önce hitTest'i gerçekleştirmek zorunda kaldım.
Bao Lei

8

ScrollView çerçeve boyutunu, sayfanızın boyutu şu şekilde ayarlayın:

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

Artık kaydırabilirsiniz self.viewve scrollView'daki içerik kaydırılacaktır. İçeriğin kırpılmasını önlemek için
de kullanın scrollView.clipsToBounds = NO;.


5

Bana göründüğü en hızlı ve daha basit yöntem olduğu için özel UIScrollView ile devam ettim. Ancak, tam bir kod görmedim, bu yüzden paylaşacağımı düşündüm. İhtiyaçlarım küçük içeriğe sahip bir UIScrollView idi ve bu nedenle UIScrollView'ın kendisi sayfalama etkisini elde etmek için küçüktü. Gönderide belirtildiği gibi, hızlıca kaydıramazsınız. Ama şimdi yapabilirsin.

Bir sınıf CustomScrollView ve alt sınıf UIScrollView oluşturun. Daha sonra yapmanız gereken tek şey bunu .m dosyasına eklemek:

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

Bu, bir yandan diğer yana (yatay) kaydırma yapmanızı sağlar. Kaydırma / kaydırma dokunma alanınızı ayarlamak için sınırları buna göre ayarlayın. Zevk almak!


4

Kaydırma görünümünü otomatik olarak döndürebilen başka bir uygulama yaptım. Dolayısıyla, projede yeniden kullanımı sınırlandıracak bir IBOutlet'e sahip olması gerekmez.

- (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;
}

1
Zaten bir xib / storyboard'unuz varsa kullanacağınız budur. Aksi takdirde, Paging / Scrollview referansını nasıl elde edeceğinizden emin değilim. Herhangi bir fikir?
mskw

3

ClipView hitTest uygulaması için potansiyel olarak yararlı başka bir değişikliğim var. ClipView'a bir UIScrollView referansı sağlamaktan hoşlanmadım. Aşağıdaki uygulamam, herhangi bir şeyin hit-test alanını genişletmek için ClipView sınıfını yeniden kullanmanıza izin verir ve ona bir referans sağlamak zorunda kalmazsınız.

- (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;
}

3

Yukarıdaki oylanan öneriyi uyguladım, ancak UICollectionViewkullandığım çerçeve dışında herhangi bir şeyin ekranın dışında olduğu düşünülüyordu. Bu, yakındaki hücrelerin yalnızca kullanıcı onlara doğru ilerlerken sınırların dışına çıkmasına neden oldu, ki bu ideal değildi.

Yaptığım şey, aşağıdaki yöntemi temsilciye (veya UICollectionViewLayout) ekleyerek bir kaydırma görünümünün davranışını taklit etmekti .

- (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;
}

Bu, aynı zamanda bir bonus olan kaydırma eyleminin yetkilendirilmesini tamamen önler. UIScrollViewDelegateAdlı benzer bir yöntem vardır scrollViewWillEndDragging:withVelocity:targetContentOffset:sayfa UITableViews ve UIScrollViews için kullanılabilir.


2
Dave, UICollectionView kullanarak da böyle bir davranışa ulaşmak için mücadele ediyordum ve burada anlattığınız yaklaşımı kullandım. Ancak, kaydırma deneyimi, sayfalama etkinleştirilmiş bir kaydırma görünümü kadar iyi değildir. Daha büyük bir hızla kaydırdığımda, birkaç sayfa kaydırıldı ve önerilen sayfada durdu. Başarmak istediğim şey, davranışı sayfalama etkinken normal kaydırma görünümü olarak kaydetmektir - hangi hızı kullanırsam kullanayım, yalnızca 1 sayfa daha fazla alacağım. Nasıl yapılacağına dair bir fikrin var mı?
Peter Stajger

3

Bu SO sorusunun tekniğini desteklerken kaydırma görünümünün alt görünümlerinde tetikleme dokunma olaylarını etkinleştirin. Okunabilirlik için kaydırma görünümüne (self.scrollView) bir başvuru kullanır.

- (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;
}

Dokunma olayını yakalamak için bunu çocuğunuzun görünümüne ekleyin:

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

(Bu, user1856273'ün çözümünün bir varyasyonudur. Okunabilirlik için temizlendi ve Bartserk'in hata düzeltmesini dahil etti. User1856273'ün cevabını düzenlemeyi düşündüm, ancak bu çok büyük bir değişiklikti.)


Alt görünümde gömülü bir UIButton üzerinde çalışmak üzere dokunmaya başlamak için hitView = [childViews objectAtIndex: i] kodunu değiştirdim; ile // [[childViews objectAtIndex: i] alt görünümlerinde UIView * paneView] için UIButton bul) {if ([paneView isKindOfClass: [UIButton class]]) return paneView; }
CharlesA

2

benim sürümüm Kaydırma üzerinde duran düğmeye basmak - iş =)

- (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;
    }

ancak yalnızca [[self subviews] objectAtIndex: 0] bir kaydırma olmalıdır


Bu benim sorunumu çözdü :) Sınırları kontrol ederken point.x'e kaydırma ofsetini eklemediğinizi unutmayın ve bu, kodun yatay kaydırmalarda iyi çalışmamasına neden olur. Bunu ekledikten sonra gayet iyi çalıştı!
Bartserk

2

İşte Ed Marty'nin cevabına dayanan bir Swift cevabı ama aynı zamanda Juri Pakaste tarafından kaydırma görünümünde düğme dokunuşlarına vb. İzin vermek için yapılan değişiklik de dahil.

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

Bunu güncellediğiniz için teşekkürler :)
Liam Bolling
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.