iOSはviewにドラッグ機能を追加

3278 ワード

前言


現在の生放送アプリには、サスペンションウィンドウ機能があり、サスペンションウィンドウはドラッグ&ドロップでき、リバウンドアニメーションがあり、UIDIewの分類実装を設計し、侵入性を低減することができます.

主なコードと構想


構想

  • viewにpanのジェスチャーを追加し、gestureの状態で判断(例えば境界のリバウンド)を行い、最終的にジェスチャーをドラッグして終了するとblockでジェスチャーをコールバックする.

  • コード#コード#

    #import "UIView+dragable.h"
    #import 
    
    #define ScreenWidth                         [[UIScreen mainScreen] bounds].size.width
    #define ScreenHeight                        [[UIScreen mainScreen] bounds].size.height
    
    static const char *ActionHandlerPanGestureKey;
    
    @implementation UIView (dragable)
    
    - (void)addDragableActionWithEnd:(void (^)(CGRect endFrame))endBlock; {
        //       
        UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanAction:)];
        [self addGestureRecognizer:panGestureRecognizer];
        
        //   block
        objc_setAssociatedObject(self, ActionHandlerPanGestureKey, endBlock, OBJC_ASSOCIATION_COPY);
    }
    
    @end
    
    
  • uiviewの分類でruntimeを用いてコールバックが必要なblock
  • を関連付けた
    - (void)handlePanAction:(UIPanGestureRecognizer *)sender {
        CGPoint point = [sender translationInView:[sender.view superview]];
        
        CGFloat senderHalfViewWidth = sender.view.frame.size.width / 2;
        CGFloat senderHalfViewHeight = sender.view.frame.size.height / 2;
        
        __block CGPoint viewCenter = CGPointMake(sender.view.center.x + point.x, sender.view.center.y + point.y);
        //       
        if (sender.state == UIGestureRecognizerStateEnded) {
            [UIView animateWithDuration:0.4 animations:^{
                if ((sender.view.center.x + point.x - senderHalfViewWidth) <= 12) {
                    viewCenter.x = senderHalfViewWidth + 12;
                }
                if ((sender.view.center.x + point.x + senderHalfViewWidth) >= (ScreenWidth - 12)) {
                    viewCenter.x = ScreenWidth - senderHalfViewWidth - 12;
                }
                if ((sender.view.center.y + point.y - senderHalfViewHeight) <= 12) {
                    viewCenter.y = senderHalfViewHeight + 12;
                }
                if ((sender.view.center.y + point.y + senderHalfViewHeight) >= (ScreenHeight - 12)) {
                    viewCenter.y = ScreenHeight - senderHalfViewHeight - 12;
                }
                sender.view.center = viewCenter;
            } completion:^(BOOL finished) {
                void (^endBlock)(CGRect endFrame) = objc_getAssociatedObject(self, ActionHandlerPanGestureKey);
                if (endBlock) {
                    endBlock(sender.view.frame);
                }
            }];
            [sender setTranslation:CGPointMake(0, 0) inView:[sender.view superview]];
        } else {
            // UIGestureRecognizerStateBegan || UIGestureRecognizerStateChanged
            viewCenter.x = sender.view.center.x + point.x;
            viewCenter.y = sender.view.center.y + point.y;
            sender.view.center = viewCenter;
            [sender setTranslation:CGPointMake(0, 0) inView:[sender.view superview]];
        }
    }
    
  • UIGestureRecognizerStateの状態はUIGestureRecognizerStateEndedであり、ドラッグが終了すると、ドラッグビューの位置がスクリーン境界を超えているかどうかを判断する必要があり、境界を超えている場合は境界内に戻る必要がある.
  • リバウンドアニメーションが終了した後、runtimeで関連するblockを取り出し、最終的なビューのframeをコールバックして外部で使用します.