7

編集:ハイライトでシステム全体の青を「コピー/すべて選択/定義」ポップオーバーで使用したくないため、UITextView の代わりに UILabel を使用する必要があると考えています。

カスタム テキスト ビューを作成しようとしていますが、適切なアプローチに関するヘルプを探しています。私はiOS n00bなので、主に問題に取り組む最善の方法についてのアイデアを探しています。

次の動作で、カスタム テキスト ビューを作成したいと思います。

  1. ビューは小さく始まります。タップを受け取ると、モーダル ウィンドウとして親ビューのサイズに拡大 (アニメーション) します (左上の画像、次に右上の画像)。
  2. この大きな状態で、個々の単語がタップされると、その単語が何らかの形で強調表示され、デリゲート メソッドが呼び出され、タップされた を含む文字列が渡されます (左下の図)。

新しいテキスト ビュー
(出典:telliott.net

複雑なのは、クリックされた単語の識別にあると思われるので、そこから始めましょう。これを試す方法はいくつか考えられます。

  1. UILabel をサブクラス化します。タッチ ジェスチャ レコグナイザーを追加します。タッチが識別されたら、何らかの方法でタッチ ポイントの (xy) 座標を取得し、ビューに表示されている文字列を見て、位置に基づいてどの単語が押されたに違いないかを判断します。ただし、ワードラップを使用すると、これが非常に複雑になることがわかります。タッチの (x,y) 座標を取得する方法 (かなり単純だと思いますが) や、各文字のデバイス依存のテキスト幅などを取得する方法がわかりません。誰かが私にそれが恐ろしいことではないことを納得させることができます!
  2. UIView をサブクラス化し、単語ごとに UILabel を追加して文を偽造します。各 UILabel の幅を測定し、自分でレイアウトします。これはより賢明なアプローチのように思えますが、この方法でテキストをレイアウトすることも、試し始める頃には思ったよりも難しくなるだろうと心配しています.

もっと賢明な方法はありますか?UILabel で触れられた単語を別の方法で取得できますか?

オプション 2 を選択すると、小さなテキストから大きなテキストへのアニメーションが複雑になる可能性があると思います。単語が興味深い方法で折り返されるからです。そこで、文を小さな状態で保持するために、別のサブビュー (今回は UITextView) を持つことを考えていました。これを大きくアニメーション化してから非表示にし、同時に UIView を単語ごとに 1 ビューで表示します。

どんな考えでも大歓迎です。前もって感謝します :)

4

2 に答える 2

9

アップデート

CoreText に NSLayoutManager が追加されたおかげで、iOS 7 ではこれがずっと簡単になりました。UITextView を扱っている場合は、ビューのプロパティとしてレイアウト マネージャーにアクセスできます。私の場合、UILabel に固執したかったので、同じサイズのレイアウト マネージャーを作成する必要があります。

NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:labelText];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
[textStorage addLayoutManager:layoutManager];
CGRect bounds = label.bounds;
NSTextContainer *textContainer = [[NSTextContainer alloc] initWithSize:bounds.size];
[layoutManager addTextContainer:textContainer];

あとは、クリックされた文字のインデックスを見つけるだけです。これは簡単です。

NSUInteger characterIndex = [layoutManager characterIndexForPoint:location
                                                  inTextContainer:textContainer
                         fractionOfDistanceBetweenInsertionPoints:NULL];

これにより、単語自体を見つけるのは簡単になります。

if (characterIndex < textStorage.length) {
  [labelText.string enumerateSubstringsInRange:NSMakeRange(0, textStorage.length)
                                       options:NSStringEnumerationByWords
                                    usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
                                      if (NSLocationInRange(characterIndex, enclosingRange)) {
                                        // Do your thing with the word, at range 'enclosingRange'
                                        *stop = YES;
                                      }
                                    }];
}

iOS < 7で機能する元の回答

これを機能させるためのヒントを提供してくれた @JP Hribovsek に感謝します。これは少しハックな感じがし、大量のテキストにはうまく機能しない可能性がありますが、一度に (これは私が必要としている) 段落ごとには問題ありません。

インセット値を設定できる単純な UILabel サブクラスを作成しました。

#import "WWLabel.h"

#define WWLabelDefaultInset 5

@implementation WWLabel

@synthesize topInset, leftInset, bottomInset, rightInset;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.topInset = WWLabelDefaultInset;
        self.bottomInset = WWLabelDefaultInset;
        self.rightInset = WWLabelDefaultInset;
        self.leftInset = WWLabelDefaultInset;
    }
    return self;
}

- (void)drawTextInRect:(CGRect)rect
{
    UIEdgeInsets insets = {self.topInset, self.leftInset,
        self.bottomInset, self.rightInset};

    return [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
}

次に、カスタム ラベルを含む UIView サブクラスを作成し、タップ時に、タップ位置のサイズを超えるまで、ラベル内の各単語のテキストのサイズを作成しました。これがタップされた単語です。完全ではありませんが、今のところ十分に機能します。

次に、単純な NSAttributedString を使用してテキストを強調表示しました。

#import "WWPhoneticTextView.h"
#import "WWLabel.h"

#define WWPhoneticTextViewInset 5
#define WWPhoneticTextViewDefaultColor [UIColor blackColor]
#define WWPhoneticTextViewHighlightColor [UIColor yellowColor]

#define UILabelMagicTopMargin 5
#define UILabelMagicLeftMargin -5

@implementation WWPhoneticTextView {
    WWLabel *label;
    NSMutableAttributedString *labelText;
    NSRange tappedRange;
}

// ... skipped init methods, very simple, just call through to configureView

- (void)configureView
{
    if(!label) {
        tappedRange.location = NSNotFound;
        tappedRange.length = 0;

        label = [[WWLabel alloc] initWithFrame:[self bounds]];
        [label setLineBreakMode:NSLineBreakByWordWrapping];
        [label setNumberOfLines:0];
        [label setBackgroundColor:[UIColor clearColor]];
        [label setTopInset:WWPhoneticTextViewInset];
        [label setLeftInset:WWPhoneticTextViewInset];
        [label setBottomInset:WWPhoneticTextViewInset];
        [label setRightInset:WWPhoneticTextViewInset];

        [self addSubview:label];
    }


    // Setup tap handling
    UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc]
                                               initWithTarget:self action:@selector(handleSingleTap:)];
    singleFingerTap.numberOfTapsRequired = 1;
    [self addGestureRecognizer:singleFingerTap];
}

- (void)setText:(NSString *)text
{
    labelText = [[NSMutableAttributedString alloc] initWithString:text];
    [label setAttributedText:labelText];
}

- (void)handleSingleTap:(UITapGestureRecognizer *)sender
{
    if (sender.state == UIGestureRecognizerStateEnded)
    {
        // Get the location of the tap, and normalise for the text view (no margins)
        CGPoint tapPoint = [sender locationInView:sender.view];
        tapPoint.x = tapPoint.x - WWPhoneticTextViewInset - UILabelMagicLeftMargin;
        tapPoint.y = tapPoint.y - WWPhoneticTextViewInset - UILabelMagicTopMargin;

        // Iterate over each word, and check if the word contains the tap point in the correct line
        __block NSString *partialString = @"";
        __block NSString *lineString = @"";
        __block int currentLineHeight = label.font.pointSize;
        [label.text enumerateSubstringsInRange:NSMakeRange(0, [label.text length]) options:NSStringEnumerationByWords usingBlock:^(NSString* word, NSRange wordRange, NSRange enclosingRange, BOOL* stop){

            CGSize sizeForText = CGSizeMake(label.frame.size.width-2*WWPhoneticTextViewInset, label.frame.size.height-2*WWPhoneticTextViewInset);
            partialString = [NSString stringWithFormat:@"%@ %@", partialString, word];

            // Find the size of the partial string, and stop if we've hit the word
            CGSize partialStringSize  = [partialString sizeWithFont:label.font constrainedToSize:sizeForText lineBreakMode:label.lineBreakMode];

            if (partialStringSize.height > currentLineHeight) {
                // Text wrapped to new line
                currentLineHeight = partialStringSize.height;
                lineString = @"";
            }
            lineString = [NSString stringWithFormat:@"%@ %@", lineString, word];

            CGSize lineStringSize  = [lineString sizeWithFont:label.font constrainedToSize:label.frame.size lineBreakMode:label.lineBreakMode];
            lineStringSize.width = lineStringSize.width + WWPhoneticTextViewInset;

            if (tapPoint.x < lineStringSize.width && tapPoint.y > (partialStringSize.height-label.font.pointSize) && tapPoint.y < partialStringSize.height) {
                NSLog(@"Tapped word %@", word);
                if (tappedRange.location != NSNotFound) {
                    [labelText addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:tappedRange];
                }

                tappedRange = wordRange;
                [labelText addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:tappedRange];
                [label setAttributedText:labelText];
                *stop = YES;
            }
        }];        
    }
}
于 2012-11-12T01:58:01.107 に答える
1

UITextView には、選択が変更されたときにトリガーされるデリゲート メソッドが既にあります (テキストビュー内でカーソルを移動することは、選択を変更することと同じであることに注意してください。ユーザーは、これを呼び出すためにテキストを実際に「選択」する必要はありません)。

- (void)textViewDidChangeSelection:(UITextView *)textView

これがトリガーされるたびに、次のように selectedRange を取得します。

NSRange range=textView.selectedRange;

ユーザーが手動でカーソルを移動したり、単語全体を選択したりできる場合は、ほぼ完了です。それ以外の場合は、selectedRange の文字列の周りに処理を追加して、カーソルの周りの単語が何であるかを把握し、それを強調表示します。お好みの方法で。
たとえば、テキストビュー内のすべての単語を列挙し、現在の選択 (またはカーソル) が含まれている単語を特定し、単語全体を選択できます (これは、以前の iOS 6 を強調表示する方法です)。

- (void)textViewDidChangeSelection:(UITextView *)textView{
NSRange range=textView.selectedRange;
[textView.text enumerateSubstringsInRange:NSMakeRange(0, [textView.text length]) options:NSStringEnumerationByWords usingBlock:^(NSString* word, NSRange wordRange, NSRange enclosingRange, BOOL* stop){
    NSRange intersectionRange=NSIntersectionRange(range,wordRange);
    if(interesectionRange.length>0){
        [textView setSelectedRange:wordRange];
    }
}];
}
于 2012-11-10T03:03:08.250 に答える