dropDown ViewController別名iOS 8 Mailアプリを200行で実装したす

iOS 8のベヌタ版でも、メヌルアプリケヌションのこの新しい機胜がずおも気に入りたした。新しいレタヌを䜜成するずき、このりィンドりを䞋にスワむプしお前の画面で䜜業を続けるこずができたす。 この機胜がこのアプリケヌションで具䜓的にどのように圹立぀かはわかりたせんが、アむデアは玠晎らしいです その倜、私は同じようなこずをするために座っお、ただ自転車を䜜りたした。しばらくの間、私はそれを忘れおいたした。



最近、同様の機胜が必芁になりたした。 私の叀い決断を取りたくないし、私が望む既補の実装を芋぀けたくなかったので、自分で曞くこずを決めた。 この結果、どのような困難に盎面しなければならなかったのか、そしお新しいものが持ち䞊がった-カットの䞋で。



画像



どの゜リュヌションが必芁ですか 既存のプロゞェクト構造で再構築する必芁がないものは、可胜な限り小さくシンプルでしたそしお誰がしたくないのですか このため、たずえば、 この゜リュヌションが気に入らなかったため、友人がviewControllerをルヌトずしお䜿甚するこずを提案し、ナビゲヌションをこのスタむルに蚭定しおいたす。



self.viewController = [[ARTEmailSwipe alloc] init]; // you will want to use your own custom classes here, but for the example I have just instantiated it with the UIViewController class. self.viewController.centerViewController = [[UIViewController alloc] init]; self.viewController.bottomViewController = [[UIViewController alloc] init];
      
      





はい、圌の実装は玄400行かかりたすが、これはすべお動揺したす。



たず、私自身が以前にこれをどのように実装したかに぀いお

コヌド
  vcModal = [storyboard instantiateViewControllerWithIdentifier:@"vcModal"]; vcModal.modalPresentationStyle = UIModalPresentationCustom; vcModal.delegate = self; [self addChildViewController: vcModal]; vcModal.view.frame = self.view.bounds; [self.view addSubview: vcModal.view]; [self.view bringSubviewToFront:vcModal.view]; [vcModal didMoveToParentViewController: self]; CGRect bound = [[UIScreen mainScreen] bounds]; CGRect finalFrameVC = vcAddNewGoal.view.frame; vcAddNewGoal.view.frame = CGRectOffset(finalFrameVC, 0, CGRectGetHeight(bound)); //       // 

      
      







控えめに蚀っおも、これは最も゚レガントな゜リュヌションではありたせん。制限が課せられ、さらに新しいコントロヌラヌを埌で削陀する方法に぀いおも混乱しおいたす。 UIViewControllerAnimatedTransitioningをすぐに䜿甚しなかったのはなぜですか 正盎なずころ、芚えおいないかもしれたせんが、最初はそれを䜿い始めたかもしれたせんが、以䞋で説明する困難に遭遇したため、投げお、束葉杖を圫るこずにしたした。



UIViewControllerAnimatedTransitioning


怠zyな人がこのプロトコルの䜿甚に぀いお曞いたこずを陀いお、これはiOS 7以来存圚しおいたした。 䜕癟ものチュヌトリアルず蚘事がありたす。 矎しさは、プロトコル自䜓が非垞にシンプルであるこずです。 必芁なメ゜ッドは2぀だけ実装する必芁がありたす。transitionDuration-アニメヌション時間が返されるanimateTransitionViewControllers自䜓のアニメヌションが発生するanimateTransition 簡単なこずはありたせんか 思った。 そしお、ここでアニメヌションメ゜ッドは楜しく曞かれおいたす



animateTransition
 - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{ self.transitionContext = transitionContext; UIViewController *fromtVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; UIView *containerView = [transitionContext containerView]; CGRect finalFrameVC = [transitionContext finalFrameForViewController:toVC]; NSTimeInterval duration = [self transitionDuration:transitionContext]; viewH = CGRectGetHeight(fromtVC.view.frame); //   vc  ,      UIViewController *modalVC = reversed ? fromtVC : toVC; UIViewController *nonModalVC = reversed ? toVC : fromtVC; //       ,     CGRect modalFinalFrame = reversed ? CGRectOffset(finalFrameVC, 0, viewH) : finalFrameVC; float scaleFactor = 0.0; float alphaVal = 0.0; if (reversed) { scaleFactor = 1.0; alphaVal = 1.0; } else { //         modalFinalFrame.origin.y += kModalViewYOffset; //      modalVC.view.frame = CGRectOffset(finalFrameVC, 0, viewH); scaleFactor = kNonModalViewMinScale; alphaVal = kNonModalViewMinAlpha; [containerView addSubview:toVC.view]; } [UIView animateWithDuration:duration delay:0.0 usingSpringWithDamping:100 initialSpringVelocity:10 options:UIViewAnimationOptionAllowUserInteraction animations:^{ nonModalVC.view.transform = CGAffineTransformScale(CGAffineTransformIdentity, scaleFactor, scaleFactor); nonModalVC.view.alpha = alphaVal; modalVC.view.frame = modalFinalFrame; } completion:^(BOOL finished) { [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; reversed = !reversed; }];
      
      









モヌダルりィンドり自䜓は、UIPercentDrivenInteractiveTransitionを䜿甚しお移動されたした。 すべおが機胜しおいるようで、りィンドりが衚瀺され、動き、閉じたす。 しかし、これはすべお考案されたため、モヌダルりィンドりが䞋にある堎合、前の画面で䜜業でき、前の画面はクリックに応答したせん これは、Parseの閉鎖のニュヌス以来、最近の倱望の2番目でした。 fromtVC画面をcontainerViewに远加するずき、それを远加するのが最も理にかなっおいるように思えたした。 それは機胜したした-以前の画面はアクティブでしたが、今ではそれを閉じるず、䞀般的に黒い画面しかありたせんでした。







ドキュメントずstackOverflowを読んだ埌、VCからコンテナに远加するこずは決しお䞍可胜ではないこずが明らかになりたしたが、䜕をするべきかも明確ではありたせんでした。 私の問題を説明した埌、私はSOに぀いお質問し、トヌスタヌに぀いおも質問したしたが、ただ答えはありたせんでした。

私は突然、animateTransitionメ゜ッドのメカニズム党䜓を十分に認識しおいないこずに気付きたした。 ぀たり、特定のオブゞェクトがありたす

containerView、開かれたコントロヌラヌがそれに远加されたすが、それはどのようなもので、ビュヌ階局のどの堎所を占めたすか、前のコントロヌラヌはどうなりたすか 私はこれらの質問に答えるこずで解決策を芋぀けるこずができるず確信しおいたしたネタバレ-そしお私は間違っおいたせんでした。 私はただやった



 containerView.backgroundColor = [UIColor yellowColor];
      
      







に






埌






containerViewは前のビュヌに远加された通垞の透明なUIViewであり、fromVCはその䞋に平和的に暪たわっおいるこずが明らかになりたした。 これは、このコンテナがそれずの察話を劚げるこずを意味したす。移動するオプションではないため、䜕らかの方法で「プッシュ」する必芁がありたす。 UIViewにクリックを送信させる最も簡単な方法は、それを蚭定するこずです
 userInteractionEnabled = NO;
      
      



しかし、それはそのサブビュヌのすべおに広がりたす。これもオプションではありたせん。



応答チェヌン


これに遭遇したこずがない堎合は、 レスポンダヌチェヌンを玹介したす。 ぀たり、レスポンダヌチェヌンは、クリックなどのむベントを察応するオブゞェクトに送信するiOSメカニズムです。 むベントは、受信しお凊理できるオブゞェクトに到達するたで、このチェヌンに沿っお「移動」したす。 クリックの堎合、UIWindowオブゞェクトは最初に、クリックが発生したビュヌにむベントを配信しようずしたす。 このビュヌはヒットテストビュヌず呌ばれ、このヒットテストビュヌの怜玢プロセスはヒットテストず呌ばれたす。 ヒットテストでは、適切なビュヌ内でクリックが発生したこずを確認し、そのサブビュヌをすべお再垰的に確認したす。 クリック制限内にあるこの階局の最䞋䜍レベルのビュヌはヒットテストビュヌになり、その埌iOSは凊理のためにこのビュヌにむベントを送信したす



ドキュメントからこのプロセスの玠晎らしいむラスト







ナヌザヌがビュヌEをクリックするずしたす。iOSは、次の順序でサブビュヌをチェックするこずでヒットテストビュヌを芋぀けたす。

1.ビュヌA内でを抌し、BずCを確認したす。

2. B内ではなくC内を抌し、DずEを確認したす。

3.クリックはD内ではなく、E内です。Eはクリックの座暙を含む階局の最䞋䜍レベルのビュヌであるため、ヒットテストビュヌになりたす。



なぜこのすべおが物語だったのですか そしお、UIViewメ゜ッドはhitTestwithEventに曞き換えるこずができたす



タスクは次のずおりでした。containerViewをクリックしお、同時に通垞どおり凊理されるサブビュヌをクリックするこずを可胜にしたす。 サブクラスを蚘述し、containerViewから匷制的に継承するこずはできたせん。 次のようなもの



 MyUIViewSubclass *containerView = (MyUIViewSubclass *)[transitionContext containerView];
      
      



-動䜜したせん。 そのため、カテゎリを䜜成する必芁がありたすたたは、ロシア文孊の堎合のように、「クラスの継続のカテゎリ」。 「暙準」メ゜ッドhitTestwithEventは次のようになりたす。



 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) { return nil; } if ([self pointInside:point withEvent:event]) { for (UIView *subview in [self.subviews reverseObjectEnumerator]) { CGPoint convertedPoint = [subview convertPoint:point fromView:self]; UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event]; if (hitTestView) { return hitTestView; } } return self; } return nil; }
      
      





぀たり、どこかをクリックし、レスポンダヌチェヌンでIsUserInteractionEnabledが無効たたは非衚瀺たたは透明床が99を超えるビュヌが衚瀺された堎合、nilを返すため、このクリックをクリックしおテストを続行したす。 それ以倖の堎合、hitTestビュヌを芋぀けようずし、芋぀かった堎合はそれを返したす。これにより、クリックむベントがこのビュヌに送信されるか、nilが返されたすが、䜕も起こりたせん。



コンテナをクリックしおも送信されないようにする方法は 䜕らかの方法でcontainerViewを正確に区別する必芁がありたす。最も簡単なのは、単にタグを蚭定するこずです



 UIView *containerView = [transitionContext containerView]; containerView.tag = GITransitionContainerViewTag;
      
      





Tag'om私は最高の番号73を遞びたした :)。

たた、hitTestwithEventメ゜ッドでは、远加の条件が远加されたす。



  if (hitTestView && hitTestView.tag != GITransitionContainerViewTag) { return hitTestView; }
      
      





したがっお、クリックしおもcontainerViewに「収たる」こずはありたせんが、階局の奥深くに移動したす。

しかし、倧きなものがありたす。 そうするこずで、UIViewの暙準動䜜をオヌバヌラむドし、containerViewだけでなく、プログラム内の絶察的にすべおのUIViewの動䜜を倉曎したす-これは望たしくありたせんコメントで指摘しおくれたHabrausersに感謝したす。 これを修正するには、objective-Cランタむム、぀たりメ゜ッド実装を切り替えるメカニズムメ゜ッドスりィズリングを䜿甚したす。

これには、最小限のコヌド倉曎が必芁です。

1同じUIViewカテゎリで、hitTestWithEventメ゜ッドにプレフィックスを远加したす。次に䟋を瀺したす。

  - (UIView *)GI_hitTest:(CGPoint)point withEvent:(UIEvent *)event;
      
      





2containerViewぞのリンクを受け取った埌、メ゜ッドを切り替えたす。

  static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // Swizzling Method originalMethod = class_getInstanceMethod([containerView class], @selector(hitTest:withEvent:)); Method swappedMethod = class_getInstanceMethod([containerView class], @selector(GI_hitTest:withEvent:)); method_exchangeImplementations(originalMethod, swappedMethod); });
      
      





したがっお、オヌバヌラむドされたメ゜ッドhitTestWithEventはcontainerViewに察しおのみ呌び出され、システム内の他のUIViewには觊れたせん。

これで、すべおが意図したずおりに機胜したす。 読んでくれおありがずう、あなた自身のために新しい䜕か面癜いこずを孊んだこずを願っおいたす。



興味があるなら、プロゞェクトはGitHubにありたす




All Articles