不規則形狀的UIButton

有的時候,我們需要使用非規則形狀的按鈕。UIButton允許你選擇帶有alpha通道的圖像。比如,我使用下面四個圖像:


然後用Interface Builder創建用戶定義按鈕,你可以透過圖像的透明部分看到後面的按鈕(假定按鈕未定義為opaque)。.然而 UIButton 的點擊測試(hit-testing)並未考慮圖像的透明性,所以當你將圖像重疊放置時,如圖所示:


如果你點擊此處:


默認的點擊測試的結果是綠色菱形按鈕被按下,而不是藍色按鈕。當然這可能就是你需要的效果,但大部分情況下並非如你所願。那麼怎樣才能讓你的程序正常工作?實際上很簡單,你只需要一個UIButton的子類並重寫點擊測試方法。

然而,首先你需要一個方法能確定圖像上指定點是透明的。遺憾的是UIImage無法像Cocoa為NSImage提供的NSBitmapRepresentation 那樣方便地訪問位圖數據。但是每個UIImage都具有一個稱為CGImage的屬性可以訪問內部圖像數據,Apple發佈了一篇技術文章介紹了怎樣通過CGImageRef訪問內部位圖數據

根據這篇文章的介紹,我們很容易就寫出一個方法,它以CGPoint為參數,根據該點是否透明(0)與否返回YES或NO。

UIImage-Alpha.h

1
2
3
4
5
6
7
8
#import <UIKit/UIKit.h>

@interface UIImage (Alpha )

-  ( NSData  * )ARGBData;
-  ( BOOL )isPointTransparent : (CGPoint )point;

@end

UIImage-Alpha.m

1
2
3
4
5
6
7
8
9
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
CGContextRef CreateARGBBitmapContext  (CGImageRef inImage )
{
    CGContextRef    context  =  NULL;
    CGColorSpaceRef colorSpace;
     void  *          bitmapData;
     int             bitmapByteCount;
     int             bitmapBytesPerRow;

     size_t pixelsWide  = CGImageGetWidth (inImage );
     size_t pixelsHigh  = CGImageGetHeight (inImage );
    bitmapBytesPerRow    =  (pixelsWide  * 4 );
    bitmapByteCount      =  (bitmapBytesPerRow  * pixelsHigh );

    colorSpace  = CGColorSpaceCreateDeviceRGB ( );
     if  (colorSpace  ==  NULL )
         return  nil;

    bitmapData  =  malloc ( bitmapByteCount  );
     if  (bitmapData  ==  NULL )
     {
       CGColorSpaceRelease ( colorSpace  );
        return  nil;
     }

    context  = CGBitmapContextCreate  (bitmapData,
                                     pixelsWide,
                                     pixelsHigh,
                                     8,
                                     bitmapBytesPerRow,
                                     colorSpace,
                                     kCGImageAlphaPremultipliedFirst );

     if  (context  ==  NULL )
     {
        free  (bitmapData );
        fprintf  ( stderr"Context not created!" );
     }

    CGColorSpaceRelease ( colorSpace  );

     return context;
}

@implementation UIImage (Alpha )

-  ( NSData  * )ARGBData
{
    CGContextRef cgctx  = CreateARGBBitmapContext (self.CGImage );
     if  (cgctx  ==  NULL )         
         return  nil;

     size_t w  = CGImageGetWidth (self.CGImage );
     size_t h  = CGImageGetHeight (self.CGImage );
    CGRect rect  =  { {0,0 }, {w,h } };
    CGContextDrawImage (cgctx, rect, self.CGImage )

     void  *data  = CGBitmapContextGetData  (cgctx );
    CGContextRelease (cgctx );     

     if  ( !data )       
         return  nil;

     size_t dataSize  = 4  * w  * h;  // ARGB = 4 8-bit components
     return  [ NSData dataWithBytes :data length :dataSize ];
}    

-  ( BOOL )isPointTransparent : (CGPoint )point
{
     NSData  *rawData  =  [self ARGBData ];   // See about caching this
     if  (rawData  ==  nil )
        return  NO;

     size_t bpp  =  4;
     size_t bpr  = self.size.width  *  4;

    NSUInteger index  = point.x  * bpp  +  (point.y  * bpr );
     char  *rawDataBytes  =  ( char  * ) [rawData bytes ];

     return rawDataBytes [index ]  ==  0;

}

@end

一旦我們有能力確定圖像中的某點是否透明,我們就可以編寫UIButton的子類,重寫hitTest:withEvent: 方法。它將返回一個UIView的實例。如果該點在此視圖或其子視圖中未被點擊,那麼將返回nil。如果點擊在其子視圖,那麼將返回點擊中的子視圖,如果點擊中視圖,那麼返回視圖本身。

然而,我們可以進行一些簡化,這是因為儘管UIButton繼承了UIView,技術上可能具有子視圖,但這非常的少見,而且Interface Builder並不支持這樣做。所以在本文的實現中並不考慮子視圖。

IrregularShapedButton.h

1
2
3
4
5
6
7
#import <UIKit/UIKit.h>

@interface IrregularShapedButton  : UIButton  {

}

@end

IrregularShapedButton.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#import "IrregularShapedButton.h"
#import "UIImage-Alpha.h"

@implementation IrregularShapedButton

-  (UIView  * )hitTest : (CGPoint )point withEvent : (UIEvent  * )event
{
     if  ( !CGRectContainsPoint ( [self bounds ], point ) )
         return  nil;
     else
     {
        UIImage  *displayedImage  =  [self imageForState : [self state ] ];
         if  (displayedImage  ==  nil )  // No image found, try for background image
        displayedImage  =  [self backgroundImageForState : [self state ] ];
         if  (displayedImage  ==  nil )  // No image could be found, fall back to
             return self;        

         BOOL isTransparent  =  [displayedImage isPointTransparent :point ];
         if  (isTransparent )
             return  nil;

     }

     return self;
}

@end
將Interface Builder中的四個圖像按鈕改為IrregularShapedButton,它們將正常工作了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值