13

UILabel触れたかどうかを確認したい。しかし、それ以上のものが必要です。テキストに触れましたか?現在、UILabelこれを使用してフレームに触れた場合にのみ、true/false を取得します。

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    if (CGRectContainsPoint([self.currentLetter frame], [touch locationInView:self.view]))
    {
        NSLog(@"HIT!");
    }
}

これを確認する方法はありますか?文字の外側のどこかに触れるとすぐに、UILabelfalse が返されます。

実際の黒でレンダリングされた「テキスト ピクセル」がいつ触れられたか知りたいです。

ありがとう!

4

8 に答える 8

21

tl;dr:テキストのパスをヒット テストできます。Gist はこちらから入手できます


私が使用するアプローチは、タップ ポイントがテキストのパス内にあるかどうかを確認することです。詳細に入る前に、手順の概要を説明します。

  1. サブクラス UILabel
  2. Core Text を使用して、テキストの CGPath を取得します
  3. pointInside:withEvent:ポイントを内側と見なすかどうかを決定できるようにオーバーライドします。
  4. タップ ジェスチャ レコグナイザーなどの「通常の」タッチ処理を使用して、いつヒットしたかを確認します。

このアプローチの大きな利点は、フォントに正確に従うことと、以下に示すようにパスを変更して「ヒット可能な」領域を拡大できることです。黒とオレンジの両方の部分がタップ可能ですが、ラベルに描画されるのは黒の部分だけです。

タップエリア

サブクラス UILabel

UILabel呼び出されたのサブクラスを作成TextHitTestingLabelし、テキスト パスのプライベート プロパティを追加しました。

@interface TextHitTestingLabel (/*Private stuff*/)
@property (assign) CGPathRef textPath;
@end

iOS ラベルは atextまたは an のいずれかを持つことができるattributedTextため、これらのメソッドをサブクラス化し、テキスト パスを更新するメソッドを呼び出すようにしました。

- (void)setText:(NSString *)text {
    [super setText:text];

    [self textChanged];
}

- (void)setAttributedText:(NSAttributedString *)attributedText {
    [super setAttributedText:attributedText];

    [self textChanged];
}

また、NIB/ストーリーボードからラベルを作成することもできます。この場合、テキストはすぐに設定されます。その場合、awake from nib の最初のテキストをチェックします。

- (void)awakeFromNib {
    [self textChanged];
}

Core Text を使用してテキストのパスを取得します

Core Text は、テキストのレンダリングを完全に制御できる低レベルのフレームワークです。プロジェクトに追加CoreText.frameworkしてファイルにインポートする必要があります

#import <CoreText/CoreText.h>

内部で最初に行うことtextChangedは、テキストを取得することです。iOS 6 以前かどうかによっては、属性付きのテキストも確認する必要があります。ラベルには、これらのうちの 1 つのみが含まれます。

// Get the text
NSAttributedString *attributedString = nil;
if ([self respondsToSelector:@selector(attributedText)]) { // Available in iOS 6
    attributedString = self.attributedText; 
}
if (!attributedString) { // Either earlier than iOS6 or the `text` property was set instead of `attributedText`
    attributedString = [[NSAttributedString alloc] initWithString:self.text
                                                       attributes:@{NSFontAttributeName: self.font}];
}

次に、すべての文字グリフの新しい可変パスを作成します。

// Create a mutable path for the paths of all the letters.
CGMutablePathRef letters = CGPathCreateMutable();

コアテキスト「魔法」

Core Text は、テキスト行とグリフおよびグリフ ランで動作します。たとえば、「He​​llo」というテキストがあり、「Hello」のような属性を持っているとします(わかりやすくするためにスペースが追加されています)。次に、2 つのグリフ ラン (1 つはボールド、もう 1 つは通常) を持つ 1 行のテキストになります。最初のグリフ ランには 3 つのグリフが含まれ、2 番目のグリフ ランには 2 つのグリフが含まれます。

すべてのグリフ ランとそのグリフを列挙し、 でパスを取得しますCTFontCreatePathForGlyph()。次に、個々のグリフ パスが可変パスに追加されます。

// Create a line from the attributed string and get glyph runs from that line
CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef)attributedString);
CFArrayRef runArray = CTLineGetGlyphRuns(line);

// A line with more then one font, style, size etc will have multiple fonts.
// "Hello" formatted as " *Hel* lo " (spaces added for clarity) is two glyph
// runs: one italics and one regular. The first run contains 3 glyphs and the
// second run contains 2 glyphs.
// Note that " He *ll* o " is 3 runs even though "He" and "o" have the same font.
for (CFIndex runIndex = 0; runIndex < CFArrayGetCount(runArray); runIndex++)
{
    // Get the font for this glyph run.
    CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runArray, runIndex);
    CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);

    // This glyph run contains one or more glyphs (letters etc.)
    for (CFIndex runGlyphIndex = 0; runGlyphIndex < CTRunGetGlyphCount(run); runGlyphIndex++)
    {
        // Read the glyph itself and it position from the glyph run.
        CFRange glyphRange = CFRangeMake(runGlyphIndex, 1);
        CGGlyph glyph;
        CGPoint position;
        CTRunGetGlyphs(run, glyphRange, &glyph);
        CTRunGetPositions(run, glyphRange, &position);

        // Create a CGPath for the outline of the glyph
        CGPathRef letter = CTFontCreatePathForGlyph(runFont, glyph, NULL);
        // Translate it to its position.
        CGAffineTransform t = CGAffineTransformMakeTranslation(position.x, position.y);
        // Add the glyph to the 
        CGPathAddPath(letters, &t, letter);
        CGPathRelease(letter);
    }
}
CFRelease(line);

コア テキストの座標系は、通常の UIView 座標系とは上下が逆になっているため、画面に表示されるものと一致するようにパスを反転させます。

// Transform the path to not be upside down
CGAffineTransform t = CGAffineTransformMakeScale(1, -1); // flip 1
CGSize pathSize = CGPathGetBoundingBox(letters).size; 
t = CGAffineTransformTranslate(t, 0, -pathSize.height); // move down

// Create the final path by applying the transform
CGPathRef finalPath = CGPathCreateMutableCopyByTransformingPath(letters, &t);

// Clean up all the unused path
CGPathRelease(letters);

self.textPath = finalPath;

これで、ラベルのテキストの完全な CGPath ができました。

オーバーライドpointInside:withEvent:

ラベルがそれ自体の内部と見なすポイントをカスタマイズするには、内部のポイントをオーバーライドし、ポイントがテキスト パスの内部にあるかどうかをチェックします。UIKit の他の部分は、ヒット テストのためにこのメソッドを呼び出します。

// Override -pointInside:withEvent to determine that ourselves.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    // Check if the points is inside the text path.
    return CGPathContainsPoint(self.textPath, NULL, point, NO);
}

通常のタッチ操作

これで、すべてが通常のタッチ処理で動作するようにセットアップされました。NIB のラベルにタップ認識エンジンを追加し、View Controller のメソッドに接続しました。

- (IBAction)labelWasTouched:(UITapGestureRecognizer *)sender {
    NSLog(@"LABEL!");
}

それだけです。ここまでスクロールして、さまざまなコードを貼り付けたくない場合は、ダウンロードして使用できる Gist に .m ファイル全体があります

ほとんどのフォントは、タッチの精度 (44px) に比べて非常に薄いため、タッチが「ミス」と見なされると、ユーザーは非常に不満を感じる可能性が高いことに注意してください。そうは言っても、ハッピーコーディング!


アップデート:

ユーザーに少し優しくするために、ヒット テストに使用するテキスト パスをストロークできます。これにより、タップ可能なヒット領域が大きくなりますが、テキストをタップしているような感覚が得られます。

CGPathRef endPath = CGPathCreateMutableCopyByTransformingPath(letters, &t);

CGMutablePathRef finalPath = CGPathCreateMutableCopy(endPath);
CGPathRef strokedPath = CGPathCreateCopyByStrokingPath(endPath, NULL, 7, kCGLineCapRound, kCGLineJoinRound, 0);
CGPathAddPath(finalPath, NULL, strokedPath);

// Clean up all the unused paths
CGPathRelease(strokedPath);
CGPathRelease(letters);
CGPathRelease(endPath);

self.textPath = finalPath;

下の画像のオレンジ色の領域もタップ可能になります。これでもテキストに触れているように感じられますが、アプリのユーザーの煩わしさは軽減されます。タップエリア

必要に応じて、これをさらに進めてテキストをヒットしやすくすることもできますが、ある時点で、ラベル全体がタップ可能になっているように感じるでしょう。

巨大なタップエリア

于 2013-06-29T11:19:00.690 に答える
0

viewDidLoad または IB を介してラベルを作成し、以下のコードとセレクターを使用して tapGesture を追加すると、ラベルをタップするとログが出力されます (これは singletap にあります:)

- (void)viewDidLoad
{
[super viewDidLoad];    
UILabel * label = [[UILabel alloc] initWithFrame:CGRectMake(30, 0, 150, 35)];
label.userInteractionEnabled = YES;
label.backgroundColor = [UIColor greenColor];
label.text = @"label";
label.textAlignment = NSTextAlignmentCenter;

UITapGestureRecognizer * single = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(singletap:)];
[label addGestureRecognizer:single];
single.numberOfTapsRequired = 1;
[self.view addSubview:label];


}
-(void) singletap:(id)sender
{
NSLog(@"single tap");
//do your stuff here
}

あなたがそれを見つけたら、それを肯定的な幸せなコーディングとマークしてください

于 2013-07-24T20:21:00.633 に答える
0

追跡したい UILabel インスタンスが userInteractionEnabled であると仮定します。

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch *touch = [[event allTouches] anyObject];
    UIView *touchView = touch.view;
    if([touchView isKindOfClass:[UILabel class]]){
        NSLog(@"Touch event occured in Label %@",touchView);
    }
}
于 2013-06-27T10:24:52.073 に答える