【问题标题】:UILabel clipping italic (oblique) text at left and right edges of content ( iOS 6+)UILabel 在内容的左右边缘剪裁斜体(倾斜)文本(iOS 6+)
【发布时间】:2014-01-25 22:19:01
【问题描述】:

问题: UILabel 可能会在左右边缘剪切斜体(斜)字符甚至脚本。以下屏幕截图显示了该问题。在左边缘,'j' 的下降部分被剪裁;在右边缘,'l' 的上升部分被剪裁。我意识到这是微妙的,并不是每个人都会关心(然而,更大的字体大小会变得更糟)。

这是一个使用 Zapfino 的不那么微妙的例子,大小为 22。注意 jupiter 中的“j”看起来几乎像一个“i”:

在上面的示例中,标签的背景颜色为橙色,文本左对齐,并且标签保持其固有的内容大小。

这是UILabel 的默认行为,对于多个版本的 iOS 也是如此(所以我不期待 Apple 提供修复)。

我尝试过的: 将标签的 clipsToBounds 属性设置为 NO 并不能解决问题。我也知道我可以在标签上设置一个固定的宽度约束,以便在后缘给文本更多的空间。但是,在上面的示例中,固定宽度约束不会为“j”提供更多空间。

我将使用一个利用Auto Layout 和标签alignmentRectInsets 的解决方案来回答我自己的问题。

【问题讨论】:

    标签: ios ios7 uilabel autolayout


    【解决方案1】:

    顶部标签显示UILabel 在文本左对齐且标签保持其固有内容大小时的默认行为。底部标签是UILabel 的一个简单(几乎是微不足道的)子类。底部标签不夹“j”或“l”;相反,它为文本在左右边缘提供了一些喘息的空间,而无需居中对齐文本(糟糕)。

    虽然标签本身在屏幕上没有显示对齐,但它们的文本显示是对齐的;更重要的是,在 IB 中,标签的左边缘实际上是对齐的,因为我在 UILabel 子类中覆盖了 alignmentRectInsets

    下面是配置这两个标签的代码:

    #import "ViewController.h"
    #import "NonClippingLabel.h"
    
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UILabel *topLabel;
    @property (weak, nonatomic) IBOutlet NonClippingLabel *bottomLabel;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        NSString *string = @"jupiter ariel";
    
        UIFont *font = [UIFont fontWithName:@"Helvetica-BoldOblique" size:28];
    
        NSDictionary *attributes = @{NSFontAttributeName: font};
    
        NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:attributes];
    
        self.topLabel.attributedText = attrString;
        self.bottomLabel.attributedText = attrString;     
    }
    

    这是NonClippingLabel子类的实现:

    #import <UIKit/UIKit.h>
    
    @interface NonClippingLabel : UILabel
    
    @end
    
    @implementation NonClippingLabel
    
    #define GUTTER 4.0f // make this large enough to accommodate the largest font in your app
    
    - (void)drawRect:(CGRect)rect
    {
        // fixes word wrapping issue
        CGRect newRect = rect;
        newRect.origin.x = rect.origin.x + GUTTER;
        newRect.size.width = rect.size.width - 2 * GUTTER;
        [self.attributedText drawInRect:newRect];
    }
    
    - (UIEdgeInsets)alignmentRectInsets
    {
        return UIEdgeInsetsMake(0, GUTTER, 0, GUTTER);
    }
    
    - (CGSize)intrinsicContentSize
    {
        CGSize size = [super intrinsicContentSize];
        size.width += 2 * GUTTER;
        return size;
    }
    
    @end
    

    不编辑字体文件,不使用Core Text;对于那些使用 iOS 6+ 和 Auto Layout 的用户来说,这只是一个相对简单的 UILabel 子类。

    更新:

    Augie 发现我的原始解决方案阻止了多行文本的自动换行。我通过使用drawInRect: 而不是drawAtPoint: 在标签的drawRect: 方法中绘制文本来解决了这个问题。

    截图如下:

    顶部标签是普通的UILabel。底部标签是NonClippingLabel,具有极端的装订线设置,以适应尺寸为 22.0 的 Zapfino。两个标签都使用自动布局左右对齐。

    【讨论】:

    • 这很好,但它打破了换行
    • @Augie 感谢您发现自动换行问题。请参阅我的更新以获取修复。
    • 超级答案!如果您像我一样对使用 ÅÄÖ 之类的字符进行垂直剪裁有问题,请在代码中将宽度换成高度,将 x 换成 y
    • 重要提示。你应该修改方法sizeThatFits,以防sizeToFit正常工作,否则发送sizeToFit后内容会被截断。这是代码:-(CGSize)sizeThatFits:(CGSize)size { CGSize size_ = [super sizeThatFits:size]; size_.width += 2 * GUTTER; return size_; }
    • 绝妙的答案,在我使用sizeToFit 时使用@purrrminator 的方法实现,完美运行,谢谢!
    【解决方案2】:

    NonClippingLabel 的 Swift 版本,带有来自 bilobatum 答案的固定 sizeThatFits 方法。

    class NonClippingLabel: UILabel {
    
        let gutter: CGFloat = 4
    
        override func draw(_ rect: CGRect) {
            super.drawText(in: rect.insetBy(dx: gutter, dy: 0))
        }
    
        override var alignmentRectInsets: UIEdgeInsets {
            return .init(top: 0, left: gutter, bottom: 0, right: gutter)
        }
    
        override var intrinsicContentSize: CGSize {
            var size = super.intrinsicContentSize
            size.width += gutter * 2
    
            return size
        }
    
        override func sizeThatFits(_ size: CGSize) -> CGSize {
            let fixedSize = CGSize(width: size.width - 2 * gutter, height: size.height)
            let sizeWithoutGutter = super.sizeThatFits(fixedSize)
    
            return CGSize(width: sizeWithoutGutter.width + 2 * gutter,
                          height: sizeWithoutGutter.height)
        }
    
    }
    

    【讨论】:

      【解决方案3】:

      不必通过一堆体操来解决这个愚蠢的 Apple 错误(我做过),一个快速而肮脏的 hack 只是在你的末尾添加一个 空格字符串来停止最后一个斜体字母被剪裁。显然对多行标签没有帮助,或者剪裁的首字母下降...

      【讨论】:

      • 这对我来说已经足够了 :-)
      【解决方案4】:

      SwiftSwiftUI 版本基于 Vadim Akhmerov 和 bilobatum 的答案。现在所有四个边缘都可以自定义,并且可以更改/更新。

      也作为 Github Gist 托管:https://gist.github.com/ryanlintott/2340f35977bf2d1f7b6ea40aa379bcc6

      import SwiftUI
      import UIKit
      
      struct NoClipText: UIViewRepresentable {
          typealias UIViewType = NoClipLabel
          
          let text: String
          let font: UIFont
          let clipExtension: EdgeSizes
      
          func makeUIView(context: Context) -> UIViewType {
              let uiView = UIViewType()
              uiView.text = text
              uiView.font = font
              uiView.clipExtension = clipExtension
              return uiView
          }
          
          func updateUIView(_ uiView: UIViewType, context: Context) {
              uiView.text = text
              uiView.font = font
              uiView.clipExtension = clipExtension
          }
      }
      
      class NoClipLabel: UILabel {
          static let defaultClipExtension: EdgeSizes = .all(10)
          
          var clipExtension: EdgeSizes
          
          var top: CGFloat { clipExtension.top }
          var left: CGFloat { clipExtension.left }
          var bottom: CGFloat { clipExtension.bottom }
          var right: CGFloat { clipExtension.right }
          var width: CGFloat { left + right }
          var height: CGFloat { bottom + top }
          
          required init(clipExtension: EdgeSizes = defaultClipExtension) {
              self.clipExtension = clipExtension
              super.init(frame: CGRect.zero)
          }
      
          override init(frame: CGRect) {
              clipExtension = Self.defaultClipExtension
              super.init(frame: frame)
          }
          
          required init?(coder aDecoder: NSCoder) {
              clipExtension = Self.defaultClipExtension
              super.init(coder: aDecoder)
          }
          
          override func draw(_ rect: CGRect) {
              super.drawText(in: rect.inset(by: UIEdgeInsets(top: top, left: left, bottom: bottom, right: right)))
          }
      
          override var alignmentRectInsets: UIEdgeInsets {
              return .init(top: top, left: left, bottom: bottom, right: right)
          }
      
          override var intrinsicContentSize: CGSize {
              var size = super.intrinsicContentSize
              size.width += width
              size.height += height
      
              return size
          }
      
          override func sizeThatFits(_ size: CGSize) -> CGSize {
              let fixedSize = CGSize(width: size.width - width, height: size.height - height)
              let sizeWithoutExtension = super.sizeThatFits(fixedSize)
      
              return CGSize(width: sizeWithoutExtension.width + width,
                            height: sizeWithoutExtension.height + height)
          }
      
      }
      
      struct EdgeSizes: Equatable {
          let top: CGFloat
          let left: CGFloat
          let bottom: CGFloat
          let right: CGFloat
          
          init(top: CGFloat = 0, left: CGFloat = 0, bottom: CGFloat = 0, right: CGFloat = 0) {
              self.top = top
              self.left = left
              self.bottom = bottom
              self.right = right
          }
          
          init(vertical: CGFloat = 0, horizontal: CGFloat = 0) {
              self.top = vertical
              self.left = horizontal
              self.bottom = vertical
              self.right = horizontal
          }
          
          init(_ all: CGFloat) {
              self.top = all
              self.left = all
              self.bottom = all
              self.right = all
          }
          
          static let zero = EdgeSizes(0)
          
          static func all(_ size: CGFloat) -> EdgeSizes {
              EdgeSizes(size)
          }
          
          static func vertical(_ size: CGFloat) -> EdgeSizes {
              EdgeSizes(vertical: size)
          }
          
          static func horizontal(_ size: CGFloat) -> EdgeSizes {
              EdgeSizes(horizontal: size)
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2016-03-03
        • 2014-05-21
        • 2011-05-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-11-26
        • 2017-08-22
        • 2023-01-11
        相关资源
        最近更新 更多