Çok zor bir durumdasın, söylemeliyim.
pagingEnabled=YESSayfalar arasında geçiş yapmak için bir UIScrollView kullanmanız gerektiğini , ancak pagingEnabled=NOdikey olarak kaydırmanız gerektiğini unutmayın .
2 olası strateji vardır. Hangisinin işe yarayacağını / uygulanması daha kolay olduğunu bilmiyorum, bu yüzden ikisini de deneyin.
İlk: yuvalanmış UIScrollViews. Açıkçası, bu işe sahip olan birini henüz görmedim. Ancak kişisel olarak yeterince denemedim ve benim pratiğim, yeterince denediğinizde UIScrollView'ın istediğiniz her şeyi yapmasını sağlayabileceğinizi gösteriyor .
Dolayısıyla strateji, dış kaydırma görünümünün yalnızca yatay kaydırmayı ve iç kaydırma görünümlerinin yalnızca dikey kaydırmayı işlemesine izin vermektir. Bunu başarmak için, UIScrollView'ün dahili olarak nasıl çalıştığını bilmeniz gerekir. hitTestYöntemi geçersiz kılar ve her zaman kendisini döndürür, böylece tüm dokunma olayları UIScrollView'a gider. Sonra içeride touchesBegan, touchesMovedvb. Olayla ilgilenip ilgilenmediğini kontrol eder ve onu ya idare eder ya da iç bileşenlere aktarır .
Dokunmanın işlenip yönlendirilmeyeceğine karar vermek için, UIScrollView ilk dokunduğunuzda bir zamanlayıcı başlatır:
Parmağınızı 150 ms içinde önemli ölçüde hareket ettirmediyseniz, olayı iç görünüme aktarır.
Parmağınızı 150 ms içinde önemli ölçüde hareket ettirdiyseniz, kaydırmaya başlar (ve olayı asla iç görünüme geçirmez).
Bir tabloya (kaydırma görünümünün alt sınıfı olan) dokunduğunuzda ve hemen kaydırmaya başladığınızda, dokunduğunuz satırın asla vurgulanmayacağına dikkat edin.
Eğer varsa değil 150ms içinde önemli ölçüde parmağınızı taşındı ve UIScrollView iç görünümüne olaylarının iletilmesi başladı ama sonra Başlamadan kaydırma, UIScrollView aramaları için yeterince parmak taşındı touchesCancelledkaydırma iç görünümü ve başlar üstünde.
Bir masaya dokunduğunuzda, parmağınızı biraz tuttuğunuzda ve ardından kaydırmaya başladığınızda, dokunduğunuz satırın önce vurgulandığını, ancak daha sonra vurgusunun kaldırıldığını unutmayın.
Bu olay dizisi, UIScrollView konfigürasyonu ile değiştirilebilir:
- Eğer
delaysContentTouchesolaylar hemen iç kontrole gitmek (ama yeterince parmağınızı taşırsanız o zaman iptal edilir) - HAYIR, o zaman hiçbir zamanlayıcı kullanılır
- Eğer
cancelsTouchesHAYIR, sonra olaylar asla olmayacak kaydırma, bir kontrole gönderilir kez.
Not tarafımıza ulaşan UIScrollView olduğunu tüm touchesBegin , touchesMoved, touchesEndedve touchesCanceledCocoaTouch gelen olaylar (onun yüzünden hitTestsöyler bunu yapmak için). Daha sonra isterse, istediği sürece onları iç görünüme yönlendirir.
Artık UIScrollView hakkında her şeyi bildiğinize göre, davranışını değiştirebilirsiniz. Bahse girerim, kullanıcı görünüme dokunduğunda ve parmağını hareket ettirmeye başladığında (biraz da olsa), görünüm dikey yönde kaydırmaya başlar; ancak kullanıcı parmağını yatay yönde yeterince hareket ettirdiğinde, dikey kaydırmayı iptal etmek ve yatay kaydırmaya başlamak istersiniz.
Dış UIScrollView'unuzu alt sınıflara ayırmak istiyorsunuz (örneğin, sınıfınıza RemorsefulScrollView adını verin), böylece varsayılan davranış yerine tüm olayları hemen iç görünüme iletir ve yalnızca önemli yatay hareket algılandığında kaydırılır.
RemorsefulScrollView'ın bu şekilde davranmasını nasıl sağlayabilirim?
Görünüşe göre dikey kaydırmayı devre dışı bırakmak ve delaysContentTouchesHAYIR olarak ayarlamak , yuvalanmış UIScrollViews çalışacak. Maalesef öyle değil; UIScrollView, hızlı hareketler (devre dışı bırakılamaz) için bazı ek filtreleme yapıyor gibi görünmektedir, böylece UIScrollView yalnızca yatay olarak kaydırılabilse bile, her zaman yeterince hızlı dikey hareketleri yer (ve yok sayar).
Etki o kadar şiddetlidir ki, iç içe geçmiş bir kaydırma görünümü içinde dikey kaydırma kullanılamaz. (Tam olarak bu kuruluma sahip olduğunuz görülüyor, bu yüzden deneyin: parmağınızı 150ms boyunca tutun ve ardından dikey yönde hareket ettirin - yuvalanmış UIScrollView beklendiği gibi çalışır!)
Bu, olay işleme için UIScrollView kodunu kullanamayacağınız anlamına gelir; RemorsefulScrollView'daki dört dokunma işleme yönteminin tümünü geçersiz kılmanız ve önce kendi işleminizi yapmanız, yalnızca superyatay kaydırmaya karar verdiyseniz olayı (UIScrollView) 'a iletmeniz gerekir.
Ancak geçmek zorunda touchesBeganEğer (daha sonra bu karar eğer bir taban gelecek yatay kaydırma için koordinat hatırlamak istiyorum, çünkü, UIScrollView için olan bir yatay kaydırma). touchesBeganUIScrollView'a daha sonra gönderemezsiniz çünkü touchesbağımsız değişkeni saklayamazsınız : bir sonraki touchesMovedolaydan önce değiştirilecek nesneleri içerir ve eski durumu yeniden oluşturamazsınız.
Bu yüzden touchesBeganhemen UIScrollView'a geçmelisiniz , ancak touchesMovedyatay olarak kaydırmaya karar verene kadar bundan sonraki olayları gizleyeceksiniz . Hayır touchesMoved, kaydırma yok anlamına gelir, bu nedenle bu baş touchesBeganharfin bir zararı olmaz. Ancak delaysContentTouchesHAYIR olarak ayarlayın , böylece ek sürpriz zamanlayıcılar müdahale etmez.
(Dışı mesajlar - senin aksine, UIScrollView olabilir düzgün dokunuşlar saklamak ve yeniden ve orijinal iletebilirsiniz touchesBeganolay sonradan O yayınlanmamış API'leri kullanarak bir haksız avantaja sahiptir, bunlar mutasyona önce teneke klon dokunmatik nesneleri böylece..)
Hep ileriye göz önüne alındığında touchesBegan, aynı zamanda iletmek zorunda touchesCancelledve touchesEnded. Sen çevirmek zorunda touchesEndediçine touchesCancelledUIScrollView yorumlamak çünkü, ancak, touchesBegan, touchesEndeddokunmatik tıklama olarak diziyi ve iç görünümüne iletmeyi ederim. Halihazırda uygun olayları kendiniz iletiyorsunuz, bu nedenle UIScrollView'ın hiçbir şeyi iletmesini istemezsiniz.
Temel olarak işte yapmanız gerekenler için sözde kod. Basit olması için, çoklu dokunma olayı meydana geldikten sonra yatay kaydırmaya asla izin vermem.
@interface RemorsefulScrollView : UIScrollView {
CGPoint _originalPoint;
BOOL _isHorizontalScroll, _isMultitouch;
UIView *_currentChild;
}
@end
#define kThresholdX 12.0f
#define kThresholdY 4.0f
@implementation RemorsefulScrollView
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.delaysContentTouches = NO;
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
self.delaysContentTouches = NO;
}
return self;
}
- (UIView *)honestHitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *result = nil;
for (UIView *child in self.subviews)
if ([child pointInside:point withEvent:event])
if ((result = [child hitTest:point withEvent:event]) != nil)
break;
return result;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesBegan:touches withEvent:event];
if (_isHorizontalScroll)
return;
if ([touches count] == [[event touchesForView:self] count]) {
_originalPoint = [[touches anyObject] locationInView:self];
_currentChild = [self honestHitTest:_originalPoint withEvent:event];
_isMultitouch = NO;
}
_isMultitouch |= ([[event touchesForView:self] count] > 1);
[_currentChild touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (!_isHorizontalScroll && !_isMultitouch) {
CGPoint point = [[touches anyObject] locationInView:self];
if (fabsf(_originalPoint.x - point.x) > kThresholdX && fabsf(_originalPoint.y - point.y) < kThresholdY) {
_isHorizontalScroll = YES;
[_currentChild touchesCancelled:[event touchesForView:self] withEvent:event]
}
}
if (_isHorizontalScroll)
[super touchesMoved:touches withEvent:event];
else
[_currentChild touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (_isHorizontalScroll)
[super touchesEnded:touches withEvent:event];
else {
[super touchesCancelled:touches withEvent:event];
[_currentChild touchesEnded:touches withEvent:event];
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[super touchesCancelled:touches withEvent:event];
if (!_isHorizontalScroll)
[_currentChild touchesCancelled:touches withEvent:event];
}
@end
Bunu çalıştırmayı ve hatta derlemeyi denemedim (ve tüm sınıfı bir düz metin düzenleyicisine yazdım), ancak yukarıdakilerle başlayabilir ve umarım çalıştırabilirsiniz.
Gördüğüm tek gizli yakalama, RemorsefulScrollView'a herhangi bir UIScrollView olmayan alt görünüm eklerseniz, çocuk UIScrollView gibi dokunuşları her zaman işlemiyorsa, çocuğa ilettiğiniz dokunma olayları yanıtlayıcı zinciri aracılığıyla size geri dönebilir. Kurşun geçirmez bir RemorsefulScrollView uygulaması touchesXxxyeniden girişe karşı koruma sağlar .
İkinci strateji: Eğer herhangi bir nedenle yuvalanmış UIScrollViews işe yaramazsa veya düzeltilmesi çok zor olursa, yalnızca bir UIScrollView ile anlaşmaya çalışabilir, pagingEnabledözelliğini scrollViewDidScrolltemsilci yönteminizden anında değiştirebilir .
Çapraz kaydırmayı önlemek için önce contentOffset in hatırlamayı scrollViewWillBeginDraggingve scrollViewDidScrollçapraz bir hareket algılarsanız contentOffset'i kontrol edip sıfırlamayı denemelisiniz . Denenecek başka bir strateji, kullanıcının parmağının hangi yöne gittiğine karar verdikten sonra, yalnızca bir yönde kaydırmayı etkinleştirmek için contentSize'yi sıfırlamaktır. (UIScrollView, delege yöntemlerinden contentSize ve contentOffset ile uğraşmak konusunda oldukça bağışlayıcı görünüyor.)
O özensiz görseller iş ya da sonuç değil ise, geçersiz kılma zorunda touchesBegan, touchesMovedvb ve UIScrollView değil ileri diyagonal hareket olaylar. (Ancak bu durumda kullanıcı deneyimi yetersiz olacaktır, çünkü onları tek bir yöne zorlamak yerine çapraz hareketleri görmezden gelmek zorunda kalacaksınız. Gerçekten maceraperest hissediyorsanız, RevengeTouch gibi kendi UITouch'ınızı benzer şekilde yazabilirsiniz. -C düz eski C'dir ve dünyada C'den daha basit bir şey yoktur; kimse nesnelerin gerçek sınıfını kontrol etmediği sürece, ki ben kimsenin yapmadığını düşünürüm, herhangi bir sınıfı diğer sınıflar gibi gösterebilirsiniz. İstediğiniz herhangi bir koordinatla istediğiniz dokunuşları sentezleme imkanı.)
Yedekleme stratejisi: Three20 kitaplığında UIScrollView'in mantıklı bir yeniden uygulaması olan TTScrollView var . Maalesef, kullanıcıya çok doğal ve telefonla uyuşmuyor. Ancak, her UIScrollView kullanma girişimi başarısız olursa, özel kodlanmış bir kaydırma görünümüne geri dönebilirsiniz. Mümkünse buna karşı tavsiye ederim; UIScrollView kullanmak, gelecekteki iPhone işletim sistemi sürümlerinde nasıl gelişirse gelişsin, yerel görünüm ve hissi elde etmenizi sağlar.
Tamam, bu küçük makale biraz fazla uzadı. Birkaç gün önce ScrollingMadness üzerinde çalıştıktan sonra hala UIScrollView oyunlarına girdim.
Not: Bu çalışmalardan herhangi birini alırsanız ve paylaşmak istediğinizi düşünüyorsanız, lütfen ilgili kodu andreyvit@gmail.com adresinden bana e-posta ile gönderin, bunu ScrollingMadness hile çantama eklemekten mutluluk duyarım .
PPS Bu küçük makaleyi ScrollingMadness README'ye eklemek.