Çok zor bir durumdasın, söylemeliyim.
pagingEnabled=YES
Sayfalar arasında geçiş yapmak için bir UIScrollView kullanmanız gerektiğini , ancak pagingEnabled=NO
dikey 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. hitTest
Yö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
, touchesMoved
vb. 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ı touchesCancelled
kaydı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
delaysContentTouches
olaylar 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
cancelsTouches
HAYIR, sonra olaylar asla olmayacak kaydırma, bir kontrole gönderilir kez.
Not tarafımıza ulaşan UIScrollView olduğunu tüm touchesBegin
, touchesMoved
, touchesEnded
ve touchesCanceled
CocoaTouch gelen olaylar (onun yüzünden hitTest
sö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 delaysContentTouches
HAYIR 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 super
yatay kaydırmaya karar verdiyseniz olayı (UIScrollView) 'a iletmeniz gerekir.
Ancak geçmek zorunda touchesBegan
Eğ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). touchesBegan
UIScrollView'a daha sonra gönderemezsiniz çünkü touches
bağımsız değişkeni saklayamazsınız : bir sonraki touchesMoved
olaydan önce değiştirilecek nesneleri içerir ve eski durumu yeniden oluşturamazsınız.
Bu yüzden touchesBegan
hemen UIScrollView'a geçmelisiniz , ancak touchesMoved
yatay olarak kaydırmaya karar verene kadar bundan sonraki olayları gizleyeceksiniz . Hayır touchesMoved
, kaydırma yok anlamına gelir, bu nedenle bu baş touchesBegan
harfin bir zararı olmaz. Ancak delaysContentTouches
HAYIR 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 touchesBegan
olay 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 touchesCancelled
ve touchesEnded
. Sen çevirmek zorunda touchesEnded
içine touchesCancelled
UIScrollView yorumlamak çünkü, ancak, touchesBegan
, touchesEnded
dokunmatik 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ı touchesXxx
yeniden 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 scrollViewDidScroll
temsilci yönteminizden anında değiştirebilir .
Çapraz kaydırmayı önlemek için önce contentOffset in hatırlamayı scrollViewWillBeginDragging
ve 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
, touchesMoved
vb 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.