用自定义视图替换字符串
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 吗?UITextField
或 UITextView
? 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 感谢您的好评。以上是关于用自定义视图替换字符串的主要内容,如果未能解决你的问题,请参考以下文章
用自定义警报视图替换对话框“打开位置服务以允许应用程序确定您的位置服务”[关闭]