• UIView 中的控件事件穿透 Passthrough 的实现


    我们在有多个 UIView 层叠时,比如一个按钮被一个 UIView 遮盖时,想要在点击最上层的 UIView 时能触发按钮的相应事件,我们该如何实现呢,初步可以想到几种办法:

    1. 把按钮上层的所有 UIView 的 userInteractionEnabled 属性设置为 NO,要是 UIView 有自己的交互事件该如何办呢?而且这个 userInteractionEnabled 不能动态设置,等到点击后决定设置它的 NO 是没用的

    2. UIView 接受到点击事件后主动去触发下面按钮的点击,这时的关题有三,按钮没有点击过程中的交换效果、多层 UIView 时不切实际,逐层下传吗、还有就是其他双击、三击或别的手势如何处理

    我也一直被前面两种方式纠缠着,同时也让 UIPopoverController 的 NSArray *passthroughViews 属性提醒着,因为对于 UIPopoverController,设置到它的 passthoughViews 属性中的控件事件可以完全从 UIDimmingView 下透出来。但苦于不可能看到 UIPopoverController 的源码,还是后面一而再的 Google 终于发现了 UIView 的方法:

    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;   // recursively calls -pointInside:withEvent:. point is in the receiver's coordinate system

    只要实现了最顶层的 UIView 的 hitTest 方法,在某些情况返回下层的某个按钮实例,即相当于把那个按钮的事件透出来了,比如在点击落在该按钮上时,不管这个按钮在 UIView 下多少层都可以把它挖出来。

    先看效果图:

      

    三个图分别是:

    1. 所见到的,按钮被半透明红色 View 遮住了一部分
    2. 可点击未遮住的按钮部分,可看到按钮被点下未抬起的效果
    3. 在红色的 View 中点击按钮被遮住部分,同样触发了按钮的相应事件,且有中间效果,也就是说按钮穿透出来了

    再看代码实现,有两部分代码,分别是 ViewController 和  CustomController

    ViewController.h

     
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    //
    //  ViewController.h
     
    //
    //  Created by Unmi on 11/15/11.
    //  Copyright (c) 2011 http://unmi.cc. All rights reserved.
    //
     
    #import <UIKit/UIKit.h>
     
    @interface ViewController : UIViewController{
    }
     
    @end

    ViewController.h

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    //
    //  ViewController.m
    //
    //  Created by Unmi on 11/15/11.
    //  Copyright (c) 2011 http://unmi.cc. All rights reserved.
    //
     
    #import "ViewController.h"
    #import "CustomView.h"
     
    @implementation ViewController{
        UIButton *passthroughButton;
    }
     
    #pragma mark - View lifecycle
     
    - (void)viewDidLoad
    {
        [super viewDidLoad];
     
        passthroughButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
        [passthroughButton setTitle:@"Passthrough" forState:UIControlStateNormal];
        [self.view addSubview:passthroughButton];
        passthroughButton.frame = CGRectMake(20, 50, 120, 28);
        [passthroughButton release];
         
        CustomView *customView = [[CustomView alloc] initWithFrame:CGRectMake(80, 10, 300, 200)];
        customView.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:.5];
        customView.passthroughViews = [NSArray arrayWithObject:passthroughButton];
        [self.view addSubview:customView];
        [customView release];
    }
     
    - (void)dealloc {
        [super dealloc];
    }
    @end

    CustomView.h

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    //
    //  CustomView.h
    //  TestPopover
    //
    //  Created by Unmi on 2/19/12.
    //  Copyright (c) 2012 http://unmi.cc. All rights reserved.
    //
     
    #import <UIKit/UIKit.h>
     
    @interface CustomView : UIView
    @property (nonatomic, copy) NSArray *passthroughViews;
    @end

    CustomView.m

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    //
    //  CustomView.m
    //
    //  Created by Unmi on 2/19/12.
    //  Copyright (c) 2012 http://unmi.cc. All rights reserved.
    //
     
    #import "CustomView.h"
     
    @interface CustomView()
    -(BOOL) isPassthroughView: (UIView*) view;
    @end
     
    @implementation CustomView{
        BOOL testHits;
    }
     
    @synthesize passthroughViews=_passthroughViews;
     
    -(UIView*) hitTest:(CGPoint)point withEvent:(UIEvent *)event{
        if(testHits){
            return nil;
        }
         
        if(!self.passthroughViews
            || (self.passthroughViews && self.passthroughViews.count == 0)){
            return self;
        } else {
             
            UIView *hitView = [super hitTest:point withEvent:event];
             
            if (hitView == self) {
                //Test whether any of the passthrough views would handle this touch
                testHits = YES;
                CGPoint superPoint = [self.superview convertPoint:point fromView:self];
                UIView *superHitView = [self.superview hitTest:superPoint withEvent:event];
                testHits = NO;
                 
                if ([self isPassthroughView:superHitView]) {
                    hitView = superHitView;
                }
            }
             
            return hitView;
        }
    }
     
    - (BOOL)isPassthroughView:(UIView *)view {
         
        if (view == nil) {
            return NO;
        }
         
        if ([self.passthroughViews containsObject:view]) {
            return YES;
        }
         
        return [self isPassthroughView:view.superview];
    }             
     
    -(void) dealloc{
        self.passthroughViews = nil;
    }
     
    @end

    关键要理解 hitTest 方法的作用,可参考:

    1. http://blog.sina.com.cn/s/blog_677089db01012wpg.html
    2. https://github.com/werner77/WEPopover

     
     
  • 相关阅读:
    C++ UFT-8和GB2312间的转换
    ssh不输入密码
    thinkpad T480安装WIN7
    删除目录下的不规则文件
    使用组合的方式来创建新类, 并可以重新定制某些类的特定行为
    使用gradle 编译生成 apk出现的问题
    android studio 使用 SVN
    Fiddler 抓取 Genymotion 数据包
    在eclipse中查看android源代码
    计算 md5
  • 原文地址:https://www.cnblogs.com/superchao8/p/2697263.html
Copyright © 2020-2023  润新知