UILabel在内容的左右边缘剪切斜体(斜)文本(iOS 6+)

时间:2014-01-08 00:55:43

标签: ios ios7 uilabel autolayout

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

enter image description here

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

enter image description here

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

这是UILabel的默认行为,对于多个版本的iOS来说就是这样(所以我不希望苹果公司提供修复)。

我尝试了什么: 将标签的clipsToBounds属性设置为NO无法解决问题。我也知道我可以在标签上设置固定的宽度约束,使文本在后端有更多的空间。但是,固定宽度约束不会给上面示例中的'j'提供更多空间。

我将使用利用自动布局和标签alignmentRectInsets的解决方案来回答我自己的问题。

4 个答案:

答案 0 :(得分:21)

顶部标签显示文本左对齐时UILabel的默认行为,标签保持其内在内容大小。底部标签是UILabel的简单(几乎是微不足道的)子类。底部标签不会剪切'j'或'l';相反,它使文本在左右边缘有一些呼吸空间,而没有居中对齐文本(yuck)。

enter image description here

虽然标签本身在屏幕上看起来没有对齐,但它们的文字看起来是对齐的;而且,在IB中,标签的左边缘实际上是对齐的,因为我覆盖了alignmentRectInsets子类中的UILabel

enter image description here

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

#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+和自动布局的人来说,这只是一个相对简单的UILabel子类。

<强>更新

Augie发现我的原始解决方案阻止了多行文字的自动换行。我使用drawInRect:代替drawAtPoint:来修复该问题,以便在标签的drawRect:方法中绘制文字。

以下是截图:

enter image description here

顶级标签是普通香草UILabel。底部标签是NonClippingLabel,具有极端的排水沟设置,以容纳22.0尺寸的Zapfino。两个标签都使用自动布局左右对齐。

enter image description here

答案 1 :(得分:2)

来自bilobatum的固定NonClippingLabel方法的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)
    }

}

答案 2 :(得分:0)

无需费力去解决这个愚蠢的Apple bug(我做到了),快速而简单的修改只是在您的末尾添加一个空格字符串以停止截断最后一个斜体字母。显然对多行标签a,或裁剪的第一个字母降序没有帮助...

答案 3 :(得分:0)

Swift SwiftUI 版本基于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)
    }
}
相关问题