用自定义视图替换字符串

Posted

技术标签:

【中文标题】用自定义视图替换字符串【英文标题】:Replace string with custom view 【发布时间】:2017-02-08 10:00:57 【问题描述】:

我有一个带有字符串的文本视图:

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

这是我必须做的一个例子。我想用下拉列表替换#OPTION,这意味着使用自定义视图。这取决于问题,它只能是一个#OPTION 或更多。提前致谢。

编辑:

字符串来自 API,出现在 UITextView 或 UILabel 中

【问题讨论】:

无法理解您想要达到的目标?你能详细解释一下或给我看一些示例 UI 吗? UITextFieldUITextView? UITextView 应该更容易。如何?通过使用NSAttributedString,将NSLinkAttributedName 赋予“#OPTION”,并在其委托方法的textView:shouldInteractWithURL:inRange:interaction: 上显示您的菜单。 您必须使用 Core Text 引擎来管理您的文本布局。 Core Text 上有一个很棒的 tutorial。尽管它展示了如何在文本中嵌入图像,但我认为您将设法对其进行修改以嵌入下拉菜单。 我已经更新了问题 不再需要一直深入到 Core Text,因为与此同时 TextKit 在 ios7 中发布 【参考方案1】:

了解您的需求后,您面临的主要问题是不知道在哪里添加选择列表。

我为您的案例创建了 2 个类别,分别用于 UILabel 和 UITextView, 关注这些包含相关答案的帖子:

How do I locate the CGRect for a substring of text in a UILabel?

Get X and Y coordinates of a word in 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);

【讨论】:

这篇文章看起来很有帮助,但我需要它来实现objective-c,我正在尝试自己理解和转换它,但似乎很难! @Fisnik,如果您查看我添加的帖子,它们对于大多数扩展都有一个 Objective-C 变体,只需在目标 c 中为 UILabel / UITextView 创建一个扩展类并更改代码 @Fisnik,我添加了一个 Objective-C 变体,让你更容易使用 @unkgd 感谢您的好评。

以上是关于用自定义视图替换字符串的主要内容,如果未能解决你的问题,请参考以下文章

用自定义警报视图替换对话框“打开位置服务以允许应用程序确定您的位置服务”[关闭]

用自定义函数替换 Print 语句调用

C#Regex使用匹配值替换

是否可以用自定义 UI 替换 Android 的内置 Caller?

php 用自定义徽标替换仪表板WordPress徽标

php 用自定义WordPress仪表板替换默认的WordPress仪表板