Заменить строку пользовательским представлением

У меня есть текстовое представление со строкой:

@"The best football player in the world is #OPTION, and the best basketball player is #OPTION?"

Это пример того, что я должен сделать. Я хочу заменить #OPTION раскрывающимся списком, что означает настраиваемый вид. Это зависит от вопроса, это может быть только один #OPTION или несколько. Заранее спасибо.

Отредактировано: Вот как это должно выглядеть

Строка получена из API и отображается в UITextView или UILabel.


person Fisfej    schedule 08.02.2017    source источник
comment
Не в состоянии понять, чего вы пытаетесь достичь? Можете ли вы подробно объяснить или показать мне пример пользовательского интерфейса?   -  person CodeChanger    schedule 08.02.2017
comment
UITextField или UITextView? UITextView должно быть проще. Как? Используя NSAttributedString, указав NSLinkAttributedName в #OPTION, и покажите свое меню в textView:shouldInteractWithURL:inRange:interaction: своего метода делегата.   -  person Larme    schedule 08.02.2017
comment
Вам придется иметь дело с движком Core Text, чтобы управлять макетом вашего текста. Есть отличное руководство. по основному тексту. Несмотря на то, что он показывает, как вставлять изображения в текст, я думаю, вам удастся изменить его, чтобы встроить раскрывающиеся списки.   -  person Dan Karbayev    schedule 08.02.2017
comment
я обновил вопрос   -  person Fisfej    schedule 08.02.2017
comment
переход к основному тексту больше не нужен, так как в настоящее время TextKit был выпущен в iOS7   -  person vikingosegundo    schedule 08.02.2017


Ответы (1)


После понимания ваших потребностей основная проблема, с которой вы сталкиваетесь, заключается в том, что вы не знаете, куда добавлять списки выбора.

Я создал 2 категории для вашего случая, для UILabel и для UITextView, после этих сообщений, которые содержат соответствующие ответы для этого:

Как мне найти CGRect для подстроки текста в UILabel?

Получить координаты X и Y слова в UITextView

Эти категории находят CGRect для строки внутри, где вы должны расположить свои средства выбора.

Недостатком этого для UILabel является то, что он плохо обрабатывает режим разрыва строки wordWrap и, следовательно, не находит правильное местоположение, чтобы правильно обработать это, вы должны добавлять разрывы строк, когда это необходимо, в случае, если вы используете UILabel.

UILabel:

extension UILabel
{
    func rectFor(string str : String, fromIndex: Int = 0) -> (CGRect, NSRange)?
    {
        // Find the range of the string
        guard self.text != nil else { return nil }

        let subStringToSearch : NSString = (self.text! as NSString).substring(from: fromIndex) as NSString

        var stringRange = subStringToSearch.range(of: str)

        if (stringRange.location != NSNotFound)
        {
            guard self.attributedText != nil else { return nil }

            // Add the starting point to the sub string
            stringRange.location += fromIndex

            let storage = NSTextStorage(attributedString: self.attributedText!)
            let layoutManager = NSLayoutManager()

            storage.addLayoutManager(layoutManager)

            let textContainer = NSTextContainer(size: self.frame.size)
            textContainer.lineFragmentPadding = 0
            textContainer.lineBreakMode = .byWordWrapping

            layoutManager.addTextContainer(textContainer)

            var glyphRange = NSRange()

            layoutManager.characterRange(forGlyphRange: stringRange, actualGlyphRange: &glyphRange)

            let resultRect = layoutManager.boundingRect(forGlyphRange: glyphRange, in:textContainer)

            return (resultRect, stringRange)
        }

        return nil
    }
}

Использование для бесконечного поиска всей доступной подстроки (рекомендую добавить в viewDidLayoutSubviews(), если вы используете авто-макет:

var lastFoundIndex : Int = 0

while let result = self.label.rectFor(string: "#OPTION", fromIndex: lastFoundIndex)
{
    let view : UIView = UIView(frame: result.0)
    view.backgroundColor = UIColor.red

    self.label.addSubview(view)

    lastFoundIndex = result.1.location + 1
}

И то же самое для UITextView:

extension UITextView
{
    func rectFor(string str : String, fromIndex: Int = 0) -> (CGRect, NSRange)?
    {
        // Find the range of the string
        guard self.text != nil else { return nil }

        let subStringToSearch : NSString = (self.text! as NSString).substring(from: fromIndex) as NSString

        var stringRange = subStringToSearch.range(of: str)

        if (stringRange.location != NSNotFound)
        {
            guard self.attributedText != nil else { return nil }

            // Add the starting point to the sub string
            stringRange.location += fromIndex

            // Find first position
            let startPosition = self.position(from: self.beginningOfDocument, offset: stringRange.location)
            let endPosition = self.position(from: startPosition!, offset: stringRange.length)

            let resultRange = self.textRange(from: startPosition!, to: endPosition!)

            let resultRect = self.firstRect(for: resultRange!)

            return (resultRect, stringRange)
        }

        return nil
    }
}

Применение:

var lastFoundTextIndex : Int = 0

while let result = self.textView.rectFor(string: "#OPTION", fromIndex: lastFoundTextIndex)
{
    let view : UIView = UIView(frame: result.0)
    view.backgroundColor = UIColor.red

    self.textView.addSubview(view)

    lastFoundTextIndex = result.1.location + 1
}

В вашем случае textview дает наилучшие результаты и использует для этого включенные методы, в примере кода используется метка и текстовое представление, инициализированное в коде:

self.label.text = "The best football player in the world is\n#OPTION, and the best basketball player\n is #OPTION?"
self.textView.text = "The best football player in the world is #OPTION, and the best basketball player is #OPTION?"

И вывод просто добавляет представления поверх строк «#OPTION»:

Надеюсь это поможет

РЕДАКТИРОВАТЬ - Добавлен вариант Objective-C:

Создайте 2 расширения — 1 для UITextView и 1 для UILabel:

UILabel:

@interface UILabel (UILabel_SubStringRect)

- (NSDictionary*)rectForString:(NSString*)string fromIndex:(int)index;

@end

#import "UILabel+SubStringRect.h"

@implementation UILabel (UILabel_SubStringRect)

- (NSDictionary*)rectForString:(NSString*)string fromIndex:(int)index
{
    if (string != nil)
    {
        NSString* subStringToSearch = [self.text substringFromIndex:index];
        NSRange stringRange = [subStringToSearch rangeOfString:string];

        if (stringRange.location != NSNotFound)
        {
            if (self.attributedText != nil)
            {
                stringRange.location += index;

                NSTextStorage* storage = [[NSTextStorage alloc] initWithAttributedString:self.attributedText];
                NSLayoutManager* layoutManager = [NSLayoutManager new];

                [storage addLayoutManager:layoutManager];

                NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:self.frame.size];
                textContainer.lineFragmentPadding = 0;
                textContainer.lineBreakMode = NSLineBreakByWordWrapping;

                [layoutManager addTextContainer:textContainer];

                NSRange glyphRange;

                [layoutManager characterRangeForGlyphRange:stringRange actualGlyphRange:&glyphRange];

                CGRect resultRect = [layoutManager boundingRectForGlyphRange:glyphRange inTextContainer:textContainer];

                return @{ @"rect" : [NSValue valueWithCGRect:resultRect], @"range" : [NSValue valueWithRange:stringRange] };
            }
        }
    }

    return nil;
}

@end

UITextView:

@interface UITextView (SubStringRect)

- (NSDictionary*)rectForString:(NSString*)string fromIndex:(int)index;

@end

#import "UITextView+SubStringRect.h"

@implementation UITextView (SubStringRect)

- (NSDictionary*)rectForString:(NSString*)string fromIndex:(int)index
{
    if (string != nil)
    {
        NSString* subStringToSearch = [self.text substringFromIndex:index];
        NSRange stringRange = [subStringToSearch rangeOfString:string];

        if (stringRange.location != NSNotFound)
        {
            if (self.attributedText != nil)
            {
                stringRange.location += index;

                UITextPosition* startPosition = [self positionFromPosition:self.beginningOfDocument offset:stringRange.location];
                UITextPosition* endPosition = [self positionFromPosition:startPosition offset:stringRange.length];

                UITextRange* resultRange = [self textRangeFromPosition:startPosition toPosition:endPosition];
                CGRect resultRect = [self firstRectForRange:resultRange];

                return @{ @"rect" : [NSValue valueWithCGRect:resultRect], @"range" : [NSValue valueWithRange:stringRange] };
            }
        }
    }

    return nil;
}

@end

Пример использования — UILabel:

int lastFoundIndex = 0;
NSDictionary* resultDict = nil;

do
{
    resultDict = [self.label rectForString:@"#OPTION" fromIndex:lastFoundIndex];

    if (resultDict != nil)
    {
        NSLog(@"result: %@", resultDict[@"rect"]);
        UIView* view = [[UIView alloc] initWithFrame:[resultDict[@"rect"] CGRectValue]];
        [view setBackgroundColor:[UIColor redColor]];

        [self.label addSubview:view];

        lastFoundIndex = (int)[resultDict[@"range"] rangeValue].location + 1;
    }
} while (resultDict != nil);

UITextView:

int lastFoundTextIndex = 0;
NSDictionary* resultTextDict = nil;

do
{
    resultTextDict = [self.textview rectForString:@"#OPTION" fromIndex:lastFoundTextIndex];

    if (resultTextDict != nil)
    {
        NSLog(@"result: %@", resultTextDict[@"rect"]);
        UIView* view = [[UIView alloc] initWithFrame:[resultTextDict[@"rect"] CGRectValue]];
        [view setBackgroundColor:[UIColor redColor]];

        [self.textview addSubview:view];

        lastFoundTextIndex = (int)[resultTextDict[@"range"] rangeValue].location + 1;
    }
} while (resultTextDict != nil);
person unkgd    schedule 08.02.2017
comment
Это выглядит действительно полезным постом, но мне он нужен для Objective-C, я пытаюсь понять и преобразовать его самостоятельно, но это кажется таким сложным! - person Fisfej; 08.02.2017
comment
@Fisnik, если вы посмотрите на сообщения, которые я добавил, у каждого из них есть вариант Objective-C для большей части расширения, просто создайте класс расширения для UILabel / UITextView в цели c и измените код - person unkgd; 08.02.2017
comment
@Fisnik, я добавил вариант Objective-C, чтобы вам было проще его использовать. - person unkgd; 08.02.2017
comment
@unkgd Спасибо за отличный ответ. - person Fisfej; 09.06.2017