【问题标题】:Kerning in iOS UITextViewiOS UITextView 中的字距调整
【发布时间】:2012-10-25 17:04:30
【问题描述】:

对于一个明显的“错误”,UITextView 似乎不像其他 UIKit 元素那样支持字距调整。是否有解决方法可以让字距调整工作?

为了清楚起见,我说的是字体文件中定义的字符对之间的间距,而不是所有字符之间的一般间距。使用 NSAttributedStringNSKernAttributeName 将调整所有字符之间的间距,但内置字符对仍然不起作用。

例如:

有解决办法吗?

我发现的一个简单的解决方法是将 css 样式添加到 UITextView 以启用字距调整。如果我使用未记录的方法 setContentToHTMLString: 我可以将 css 注入文本视图中的秘密 webview。

NSString *css = @"*{text-rendering: optimizeLegibility;}";
NSString *html = [NSString stringWithFormat:@"<html><head><style>%@</style></head><body>Your HTML Text</body></html>", css];
[textView performSelector:@selector(setContentToHTMLString:) withObject:html];

这会立即解决问题;但是,这似乎很可能会导致应用被拒绝。有没有一种安全的方法可以将一些 CSS 潜入文本视图?

我尝试过的其他解决方法包括:

使用 webview 和 contenteditable 属性。这并不理想,因为 webview 做了很多额外的事情,例如不容易隐藏的输入附件视图。

继承 UITextView 并使用核心文本手动呈现文本。这比我希望的要复杂,因为所有选择的东西也需要重新配置。由于UITextViewUITextPositionUITextRange 的私有子类,即使不是完全不可能,这也是一个巨大的痛苦。

【问题讨论】:

  • 你可以使用 NSAttributedStrings。仅当您需要 iOS6 时才有效。
  • 好电话。我还忘了提到,由于“错误”字距调整不适用于 UITextView 中的 NSAttributedString。
  • 当你说它是一个错误时,这是​​否意味着你已经在 bugreport.apple.com 上打开了雷达?
  • 如果是这样,你能把它贴出来让我们欺骗吗?
  • 您尝试的属性字符串是什么?在我的实验中,我能够为 kerning value 属性指定一个负值以将字母更多地切换在一起:[attributedString addAttribute:NSKernAttributeName value:[NSNumber numberWithFloat:-10] range:NSMakeRange(0, 3)];

标签: objective-c ios css uitextview kerning


【解决方案1】:

您是对的,您的应用可能会被使用@selector(setContentToHTMLString:) 拒绝。但是,您可以使用一个简单的技巧来动态构造选择器,以便在验证期间不会检测到它。

NSString *css = @"*{text-rendering: optimizeLegibility;}";
NSString *html = [NSString stringWithFormat:@"<html><head><style>%@</style></head><body>Your HTML Text</body></html>", css];
@try {
    SEL setContentToHTMLString = NSSelectorFromString([@[@"set", @"Content", @"To", @"HTML", @"String", @":"] componentsJoinedByString:@""]);
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    [textView performSelector:setContentToHTMLString withObject:html];
    #pragma clang diagnostic pop
}
@catch (NSException *exception) {
    // html+css could not be applied to text view, so no kerning
}

通过将调用包装在 @try/@catch 块中,您还可以确保在未来版本的 iOS 中删除 setContentToHTMLString: 方法时您的应用不会崩溃。

通常不建议使用私有 API,但在这种情况下,我认为使用小技巧与使用 CoreText 重写 UITextView 完全有意义。

【讨论】:

  • 非常聪明。并且偷偷摸摸。 :-)
  • 请注意,检查 -respondsToSelector: 比检查 @try/@catch 块更好地检查被删除的方法。异常处理程序更适合应对您可能传入 API 无法处理的内容
  • 谢谢@0xced,好把戏。我曾假设在应用商店审查过程中,他们在跟踪所有 api 调用的环境中运行应用程序,因此他们甚至可以检查动态生成的消息。谁能确认这会偷偷溜过去?
  • 我有使用这个技巧发布的应用程序——但我使用了 -respondsToSelector:,而不是 @try/@catch
  • 其实我并没有混淆选择器,我只是使用了NSSelectorFromString(selectorName)
【解决方案2】:

在研究了一下之后,我发现缺少 Kerning 的原因是 UITextView 内部使用了默认情况下关闭 Kerning 的 UIWebDocumentView。

关于 UITextView 内部工作原理的一些信息:http://www.cocoanetics.com/2012/12/uitextview-caught-with-trousers-down/

很遗憾,没有经过批准的方法来启用字距调整,我绝对建议不要使用伪装选择器的黑客攻击。

这是我的雷达,我建议你上当:http://www.cocoanetics.com/2012/12/radar-uitextview-ignores-font-kerning/

在此我认为,当通过 setAttributedText 在 UITextView 上设置文本时,我们开发人员希望默认启用字距调整,因为这将最接近通过 CoreText 呈现文本的输出。

【讨论】:

    【解决方案3】:

    试试这个:

    [textView_ setValue:@"hello, <b>world</b>" forKey:@"contentToHTMLString"];
    

    它在我的测试中有效。我当然没有在应用商店中尝试过。

    【讨论】:

      【解决方案4】:

      你看过这里吗:https://github.com/AliSoftware/OHAttributedLabel?查看这个项目,app store 中似乎有几个应用程序正在使用标记(由此类提供)。也许您可以使用这个类或从中收集一些实现想法。也许您的应用会通过审核。

      【讨论】:

      • 谢谢马克,我看过那个项目。我认为现在大多数 UIKit 元素都支持属性字符串相对没有实际意义。我还需要多行文本输入,所以我无法从中收集到太多信息。
      【解决方案5】:

      在 iOS6 中,UITextView 有一个新属性 attributedText,您可以为其分配一个 NSAttributedString。我没有尝试过,但是如果你设置文本视图的这个属性而不是text 属性,看看你是否得到了你想要的字距调整会很有趣。它还具有作为公共接口的额外好处。

      【讨论】:

      • 没错,NSAttributedString 对大多数文本格式都适用,但正如我在问题中提到的,由于可能是一个错误,UITextViews 中的字距调整被破坏了。虽然您可以使用属性字符串属性来调整特定巴黎的字距调整,但无法启用字体的内置字距调整。
      【解决方案6】:

      没有必要求助于私有方法。您可以通过订阅UITextFieldTextDidChangeNotification 通知,在每次文本更改时动态更改文本字段的attributedText 属性来轻松伪造它:

      - (void)viewDidLoad {
          // your regular stuff goes here
          [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChangeNotification:) name:UITextFieldTextDidChangeNotification object:nil];
      }
      
      
      - (void)textDidChangeNotification:(NSNotification *)notification {
          // you can of course specify other attributes here, such as font and text color
          CGFloat kerning = -3.; // change accordingly to your desired value
          NSDictionary *attributes = @{
                                       NSKernAttributeName: @(kerning),
                                       };
          UITextField *textField = [notification object];
          textField.attributedText = [[NSAttributedString alloc] initWithString:textField.text attributes:attributes];
      }
      

      【讨论】:

      • 这个答案没有解决关于使用字体内置字距调整的问题,而不是关于如何为特定对设置字距调整值的问题。按照您的建议将 kern 属性设置为一个数字会调整整行文本的跟踪。
      • 好答案。我不知道为什么这被否决了,但 +1 总是使用非私有 API,这很干净。这可能有缺点,但对我来说效果很好。谢谢!
      猜你喜欢
      • 2014-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多