简体   繁体   English

扩展Multiline UILabel

[英]Expanding Multiline UILabel

In my iPhone app, I have a multiline label that I would like to expand/contract with a "More" button. 在我的iPhone应用程序中,我有一个多行标签,我想用“更多”按钮扩展/收缩。 Like this: 像这样:

Lorem ipsum dolor sit amet, consectetur 
adipiscing elit. Donec fringilla, turpis 
in porttitor imperdiet, eros turpis...

                                "<More>"

Should animate into this: 应该动画到这个:

Lorem ipsum dolor sit amet, consectetur 
adipiscing elit. Donec fringilla, turpis 
in porttitor imperdiet, eros turpis laoreet 
magna, id tempor ante lorem pulvinar lacus.
Duis vitae nisl quis sapien dictum pellentesque.

                                "<Less>"

I am trying to get an effect where every line is revealed individually as the label grows, and then individually hidden as it shrinks. 我试图获得一个效果,每个线条随着标签的增长而单独显示,然后在收缩时单独隐藏。 Growing works great, but it jumps to 3 lines during the shrink animation. 增长效果很好,但在缩小动画期间它会跳到3行。 Any ideas? 有任何想法吗? Code and properties below: 代码和属性如下:


Grow animation: 成长动画:

[UIView animateWithDuration:0.5 animations:^{
        view.frame = CGRectMake(startFrame.origin.x, startFrame.origin.y, startFrame.size.width, startFrame.size.height + 40.0);
    }];

Shrink animation: 收缩动画:

[UIView animateWithDuration:0.5 animations:^{
        view.frame = CGRectMake(startFrame.origin.x, startFrame.origin.y, startFrame.size.width, startFrame.size.height - 40.0);
    }];

UILabel properties: UILabel属性:

  • Lines: 0 线条:0
  • Line Breaks: Truncate Tail 换行:截断尾巴
  • Content Mode: Top 内容模式:顶部

You can animate this by using autolayout constraints and modifying the numberOfLines from 3 to 0. (0 is a special value that means show any number of lines). 您可以使用autolayout约束并将numberOfLines从3修改为0来设置动画。(0是一个特殊值,表示显示任意数量的行)。

The label has an intrinsic size that will change when you modify the numberOfLines and those will effect the constraints. 标签具有固有大小,当您修改numberOfLines时这些大小会发生变化,这些大小会影响约束。 The code looks like this: 代码如下所示:

@IBAction func buttonTapped(sender: UIButton) {
  let numberOfLines = label.numberOfLines == 0 ? 3 : 0
  label.numberOfLines = numberOfLines
  let newTitle = numberOfLines == 0 ? "Less" : "More"    
  sender.setTitle(newTitle, forState: .Normal)
  UIView.animateWithDuration(0.5) { self.view.layoutIfNeeded() }
}

You need to tell the view containing the label and the button that it needs to layout in the animation block. 您需要告诉包含标签的视图以及在动画块中布局所需的按钮。

在此输入图像描述

Animating a UILabel had a similar result to what you described, only the shrinking animation worked and the growing one jumped to the full size. 动画UILabel的结果与你描述的相似,只有缩小的动画才有效,而正在增长的动画会跳到全尺寸。

I experimented also with a UITextView, but that didn't work either. 我也使用UITextView进行了实验,但这也没有用。

My solution was to take another UIView, named coverView, and put it under the UILabel. 我的解决方案是采用另一个名为coverView的UIView,并将其放在UILabel下。 When the UILabel is expanded it is resized without animating it and the coverView hides the expanded part, then the coverView's origin.y and size.height is modified in an animation to reveal the expanded UILabel. 当UILabel展开时,它会调整大小而不对其进行动画处理,并且coverView会隐藏展开的部分,然后在动画中修改coverView的origin.y和size.height以显示展开的UILabel。 When the UILabel is contracted, the coverView's origin.y and size.height is modified in an animation to gradually hide the UILabel. 收缩UILabel时,会在动画中修改coverView的origin.y和size.height以逐渐隐藏UILabel。 In the animation's completion block the UILabel's frame is resized to the contracted dimensions. 在动画的完成块中,UILabel的帧大小调整为缩小的尺寸。

I created a custom UIView for this: AnimatedLabel. 我为此创建了一个自定义UIView:AnimatedLabel。 You can find the complete code below. 您可以在下面找到完整的代码。 I wrote it quickly and didn't comment it, but it shouldn't be hard to understand. 我写得很快,没有发表评论,但这应该不难理解。

How to use it (works with IB, too): 如何使用它(也适用于IB):

// Init
AnimatedLabel *animLabel = [[AnimatedLabel alloc] initWithFrame:/*desired CGRect*/];

// Set the surroundingBackgroundColor, this color will be used for the coverView
// you should set it to the container view's backgroundColor
// IMPORTANT: transparency is not supported! (if its transparent you'll see the UILabel)
animLabel.surroundingBackgroundColor = self.view.backgroundColor;

// Set the text
animLabel.label.text = /*text text text ...*/;

// Call sizeToFit to modify the label's size to fit the text perfectly
// so that the text is not moved after the label is expanded (UILabel always
// centers the text vertically and if the label
// is not resized you will see that the text is repositioned after the animation)
[animLabel sizeToFit];

// Open the label
[animLabel open];

// Close the label
[animLabel close];

// You can resize the animLabel and the subviews will be correctly resized
animLabel.frame = /*CGRect*/;

AnimatedLabel.h AnimatedLabel.h

//
//  AnimatedLabel.h
//  labeltest
//
//  Created by Alpar Szotyori on 20/06/2012.
//  Use it, modify it, improve it, share it!
//

#import <UIKit/UIKit.h>

@interface AnimatedLabel : UIView

@property (nonatomic, strong) UILabel *label;
@property (nonatomic, strong) UIColor *surroundingBackgroundColor;
@property (nonatomic) BOOL isOpen;

// Public methods

- (void)open;
- (void)close;

@end

AnimatedLabel.m: AnimatedLabel.m:

//
//  AnimatedLabel.m
//  labeltest
//
//  Created by Alpar Szotyori on 20/06/2012.
//  Use it, modify it, improve it, share it!
//

#import "AnimatedLabel.h"

@interface NSString (visibleText)

- (NSString*)stringVisibleInRect:(CGRect)rect withFont:(UIFont*)font constrainedToSize:(CGSize)size;

@end

@implementation NSString (visibleText)

- (NSString*)stringVisibleInRect:(CGRect)rect withFont:(UIFont*)font constrainedToSize:(CGSize)size
{
    BOOL addEllipse = NO;
    NSString *visibleString = @"";
    for (int i = 2; i <= self.length; i++)
    {
        NSString *testString = [self substringToIndex:i];
        CGSize stringSize = [testString sizeWithFont:font constrainedToSize:size];
        if (stringSize.height > rect.size.height || stringSize.width > rect.size.width) {
            addEllipse = YES;
            break;
        }

        visibleString = testString;
    }

    if (addEllipse) {
        if (visibleString.length >= 3) {
            visibleString = [[visibleString substringToIndex:visibleString.length - 3] stringByAppendingString:@"…"];
        }
    }

    return visibleString;
}

@end


@interface AnimatedLabel()

@property (nonatomic, strong) UIView *coverView;
@property (nonatomic, strong) UIColor *backgrndColor;
@property (nonatomic) CGFloat origLabelHeight;
@property (nonatomic, strong) NSString *origLabelText;
@property (nonatomic) BOOL animating;
@property (nonatomic) BOOL labelKVOSet;

- (void)changeHeight:(CGFloat)height animated:(BOOL)animated;
- (void)addKVOToLabel;
- (void)removeKVOFromLabel;

@end


@implementation AnimatedLabel

#pragma mark - Properties

@synthesize label;
@synthesize surroundingBackgroundColor = i_surroundingBackgroundColor;
@synthesize isOpen;

- (void)setSurroundingBackgroundColor:(UIColor *)surroundingBackgroundColor {
    i_surroundingBackgroundColor = surroundingBackgroundColor;
    self.coverView.backgroundColor = i_surroundingBackgroundColor;
}

#pragma mark - Initialization

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        self.label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, self.frame.size.width, self.frame.size.height)];
        self.label.numberOfLines = 0;
        self.label.backgroundColor = self.backgrndColor;
        self.origLabelHeight = self.label.frame.size.height;
        [self addKVOToLabel];

        self.coverView = [[UIView alloc] initWithFrame:CGRectZero];
        self.coverView.backgroundColor = [UIColor whiteColor];

        [self addSubview:self.label];
        [self addSubview:self.coverView];

        self.animating = NO;
        self.isOpen = NO;
    }
    return self;
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        self.label = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, frame.size.width, frame.size.height)];
        self.label.numberOfLines = 0;
        self.label.backgroundColor = self.backgrndColor;
        self.origLabelHeight = self.label.frame.size.height;
        [self addKVOToLabel];

        self.coverView = [[UIView alloc] initWithFrame:CGRectZero];
        self.coverView.backgroundColor = [UIColor whiteColor];

        [self addSubview:self.label];
        [self addSubview:self.coverView];

        self.animating = NO;
        self.isOpen = NO;
    }
    return self;
}

#pragma mark - Overriden methods

- (void)setFrame:(CGRect)frame {
    if (!self.animating && self.isOpen) {
        self.isOpen = NO;
        [self changeHeight:self.origLabelHeight animated:NO];
        frame.size.height = self.origLabelHeight;
    }

    [super setFrame:frame];

    if (!self.animating) {        
        self.label.frame= CGRectMake(0.0, 0.0, frame.size.width, frame.size.height);
        self.origLabelHeight = self.label.frame.size.height;

        [self removeKVOFromLabel];

        NSString *visibleText = [self.origLabelText stringVisibleInRect:self.label.frame withFont:self.label.font constrainedToSize:CGSizeMake(frame.size.width, LONG_MAX)];
        self.label.text = visibleText;
        [self.label sizeToFit];

        [self addKVOToLabel];
    }
}

- (void)setBackgroundColor:(UIColor *)backgroundColor {
    [super setBackgroundColor:[UIColor clearColor]];

    self.backgrndColor = backgroundColor;

    self.label.backgroundColor = self.backgrndColor;
}

- (UIColor *)backgroundColor {
    return self.backgrndColor;
}

- (void)sizeToFit {
    [self removeKVOFromLabel];

    NSString *visibleText = [self.origLabelText stringVisibleInRect:self.label.frame withFont:self.label.font constrainedToSize:CGSizeMake(self.frame.size.width, LONG_MAX)];
    self.label.text = visibleText;
    [self.label sizeToFit];
    self.origLabelHeight = self.label.frame.size.height;

    [self addKVOToLabel];

    self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.label.frame.size.width, self.label.frame.size.height);
}

#pragma mark - KVO methods

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
     if ([keyPath isEqualToString:@"text"]) {
         self.origLabelText = [change objectForKey:NSKeyValueChangeNewKey];
     }
}

#pragma mark - Public methods

- (void)open {
    self.origLabelHeight = self.label.frame.size.height;
    [self removeKVOFromLabel];
    self.label.text = self.origLabelText;
    [self addKVOToLabel];
    [self.label sizeToFit];

    if (self.origLabelHeight == self.label.frame.size.height) {
        return;
    }

    [self changeHeight:self.label.frame.size.height animated:YES];
    self.isOpen = YES;
}

- (void)close {
    if (self.frame.size.height == self.origLabelHeight) {
        return;
    }

    [self changeHeight:self.origLabelHeight animated:YES];
    self.isOpen = NO;
}

#pragma mark - Private methods

#pragma mark - Properties

@synthesize coverView, backgrndColor, origLabelHeight, origLabelText, animating, labelKVOSet;

#pragma mark - Methods

- (void)changeHeight:(CGFloat)height animated:(BOOL)animated {
    if (self.frame.size.height == height) {
        return;
    }

    if (height > self.frame.size.height) {
        self.animating = YES;

        [self.label sizeToFit];
        height = self.label.frame.size.height;

        self.coverView.frame = CGRectMake(0.0, self.frame.size.height, self.frame.size.width, height - self.frame.size.height);
        self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, height);
        self.label.frame = CGRectMake(0.0, 0.0, self.frame.size.width, self.frame.size.height);

        if (animated) {
            [UIView animateWithDuration:0.5 animations:^(){
                self.coverView.frame = CGRectMake(0.0, self.frame.size.height, self.frame.size.width, 0.0); 
            } completion:^(BOOL completed){
                self.animating = NO;
            }];
        } else {
            self.coverView.frame = CGRectMake(0.0, self.frame.size.height, self.frame.size.width, 0.0); 
            self.animating = NO;
        }
    } else {
        self.animating = YES;

        if (animated) {
            [UIView animateWithDuration:0.5 animations:^(){
                self.coverView.frame = CGRectMake(0.0, height, self.frame.size.width, self.frame.size.height - height);
            } completion:^(BOOL completed){
                self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, height);
                self.coverView.frame = CGRectMake(0.0, height, self.frame.size.width, 0.0);
                self.label.frame = CGRectMake(0.0, 0.0, self.frame.size.width, height);
                self.animating = NO;
            }];
        } else {
            self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, height);
            self.coverView.frame = CGRectMake(0.0, height, self.frame.size.width, 0.0);
            self.label.frame = CGRectMake(0.0, 0.0, self.frame.size.width, height);
            self.animating = NO;            
        }
    }
}

- (void)addKVOToLabel {
    if (self.label == nil) {
        return;
    }

    if (!self.labelKVOSet) {
        self.labelKVOSet = YES;
        [self.label addObserver:self forKeyPath:@"text" options:NSKeyValueObservingOptionNew context:nil];
    }
}

- (void)removeKVOFromLabel {
    if (self.label == nil) {
        return;
    }

    if (self.labelKVOSet) {
        self.labelKVOSet = NO;
        [self.label removeObserver:self forKeyPath:@"text"];
    }
}

@end

I had a similar problem trying to make a view grow/shrink. 我有一个类似的问题试图使视图增长/缩小。 Here is my SO post. 这是我的SO职位。 Basically I had to animate the frame to grow and then bounds/center to shrink. 基本上我不得不动画框架增长,然后限制/居中收缩。 A bit awkward I know but I got the effect I wanted. 我知道有点尴尬,但我得到了我想要的效果。

The problem is not in the code you posted. 问题不在您发布的代码中。 More information is needed to solve this issue. 需要更多信息来解决此问题。 I recommend that you check the following: 我建议您检查以下内容:

  1. That the view object you're resizing is the correct one. 您正在调整大小的视图对象是正确的。
  2. Add a colored background to the view object you're resizing to be able to see how its size changes in real time. 将彩色背景添加到要调整大小的视图对象,以便能够实时查看其大小的变化情况。

If that doesn't help, please post the complete methods that are executing these animation snippets you posted. 如果这没有帮助,请发布执行这些动画片段的完整方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM