在运行时和方向更改期间居中和 sizeToFit 子视图

Posted

技术标签:

【中文标题】在运行时和方向更改期间居中和 sizeToFit 子视图【英文标题】:Centering and sizeToFit subview at run time and during orientation changes 【发布时间】:2013-01-08 20:42:36 【问题描述】:

这是我在设置UIViews 和子视图时遇到的几个问题中的第一个。我有一个UIView,它在运行时动态定位在屏幕上。 UIView (master) 包含另一个 UIView (child),它包装了 UIImageViewUILabel。以下是我对这种安排的要求:

    当设备旋转时,子 UIView 必须保持在主 UIView 的中心。 UILabel 中的文本可以很长也可以很短,带有图像和文本的子 UIView 必须保持居中。 我想避免子类化 UIView 来处理这种情况,我还想避免 willRotateToInterfaceOrientation. 中的任何框架/定位代码我想通过 I.B. 中的一些 autoresizingMask 设置来处理所有这些。如果可能的话,可能还会强制调整代码大小。

这是 Interface Builder 中的控件排列(以红色突出显示):

使用 Interface Builder,autoresizingMask 属性已按如下方式设置,用于所述控件

UIView (master): 弹性上边距、弹性左边距、弹性右边距、弹性宽度 UIView (child): 弹性上边距、弹性下边距、弹性左边距、弹性右边距、弹性宽度、弹性高度。 (所有模式,无除外) UIImageView: 灵活的右边距 UILabel: 灵活的右边距

这是在纵向模式下在运行时以编程方式添加后的视图(带有图像和文本的红色条):

大师UIView的背景是浅红色的图像。孩子UIView的背景比这要暗一些,UILabel的背景更暗。我为它们着色,以便在应用响应旋转时看到它们的边界。

我很清楚:

它不是居中的,而是... 在将文本从 I.B 中的默认值更改为“此地图范围内没有数据”之后。到“TEST1, 123”。标签正确收缩。

这是在纵向添加然后旋转到横向模式后的视图:

从这里我可以看到:

它仍然没有居中,可能在旋转之前的原始帧原点 UIView(子视图)已扩展以填充更多不应该的屏幕。 UIView(主)已正确展开以填充屏幕宽度。

这就是让我走到现在的代码。我从 viewDidLoad 调用方法showNoDataStatusView

// Assuming

#define kStatusViewHeight 20

- (void)showNoDataStatusView 

    if (!self.noDataStatusView.superview) 

        self.noDataStatusView.frame = CGRectMake(self.mapView.frame.origin.x,
                                             self.mapView.frame.origin.y,
                                             self.mapView.frame.size.width,
                                             kStatusViewHeight);

        self.noDataStatusView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"bgRedStatus.png"]];

        // Position the label view in the center
        self.noDataStatusLabelView.center = CGPointMake(self.noDataStatusView.frame.size.width/2,
                                                    self.noDataStatusView.frame.size.height/2);

        // Test different text
        self.noDataStatusLabel.text = @"Testing, 123.";

        // Size to fit label
        [self.noDataStatusLabel sizeToFit];

        // Test the status label view resizing
        [self.noDataStatusLabelView resizeToFitSubviews];

        // Add view as subview
        [self.view addSubview:self.noDataStatusView];

    

请注意以下几点:

resizeToFitSubviews 是我放置在 UIView 上的一个类别,一旦我发现 UIView 不会自动调整大小以适应其子视图,即使您调用 sizeToFit。 This question 和 this question 解释了这个问题。请参阅下面的类别代码。 我曾考虑过创建一个 UIView 子类来为我处理所有这些逻辑,但这似乎有点矫枉过正。在 I.B. 中安排这个应该很简单。对吗? 我已经尝试设置书中的每个调整大小蒙版设置,以及调整标签和视图大小调整发生的顺序以及主视图添加为子视图的点。似乎没有任何效果,因为我每次都得到奇怪的结果。

UIView resizeToFitSubviews 类实现方法:

-(void)resizeToFitSubviews

    float width = 0;
    float height = 0;

    // Loop through subviews to determine max height/width
    for (UIView *v in [self subviews]) 

        float fw = v.frame.origin.x + v.frame.size.width;
        float fh = v.frame.origin.y + v.frame.size.height;

        width = MAX(fw, width);
        height = MAX(fh, height);
    

    [self setFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y, width, height)];

我想知道的是为什么UIView(子)在它的父视图被添加到视图层次结构后没有正确居中。它看起来好像有合适的宽度,但不知何故保留了它在 I.B. 中的框架。当标签显示“此地图范围内没有数据”时。

我还想知道为什么它在设备旋转后没有居中,以及我在这里采用的方法是否明智。也许这导致了我遇到的其他问题。任何 UIView 布局帮助将不胜感激。

谢谢!

【问题讨论】:

【参考方案1】:

如果您能够以 ios 6 为目标,您可以使用新的 Auto Layout 功能来简化管理 - 我一直在阅读 Ray Wenderlich 的 great tutorial,它似乎可以完美地解决您的问题正在看。

【讨论】:

这可能会解决我的 UIViews 的问题,但是我现在需要支持 5.0+。不过,很棒的文章。 能否将子视图的中心设置为主视图的中心,看看水平定位是否正确?如果是这样,那么只需将子视图的中心 y 值调整为子视图的高度/2? 我确实尝试过,但无济于事。在考虑了一些要求以及我有 5 个与此非常相似的其他“状态视图”这一事实之后,我不得不继承 UIView 并实现 layoutSubviews。就在那时我意识到我的问题是 UIView (master) 没有向它的子控件发送任何调整大小的消息,并且在旋转时只有 UIView (master) 表现正确。【参考方案2】:

这里的问题是我的UIView(master)在设备旋转时不会自动布局它的子视图,并且用于定位图像和内部UIView 的“弹簧和支柱”布局方法效率低下。我通过做两件事解决了这个问题。

    我去掉了内部的UIView(子)实例,只留下了UIView(主)和一个UILabelUIImageView。 然后我创建了一个名为StatusViewUIView 子类,并在其中实现了layoutSubviews 方法。在其构造函数中,我添加了 UIImageViewUILabel 并动态定位它们。 UILabel 首先根据文本的大小定位,然后将 UIImageView 放置在文本的左侧并垂直居中。就是这样。在layoutSubviews 中,我确保为新框架调整元素的位置。

此外,由于在某些情况下我需要交换背景、消息和可能的图像,因此使用自定义类是有意义的。这里/那里可能存在内存问题,但当我使用分析工具运行此问题时,我会解决它们。

最后,我不能完全确定这段代码是否坚如磐石,但它确实有效。我也不知道我的 init 方法中是否需要布局代码。布局子视图似乎在视图添加为子视图后不久被调用。

这是我的班级标题:

#import <UIKit/UIKit.h>

typedef enum 
    StatusViewRecordCountType = 0,
    StatusViewReachedMaxRecordCountType = 1,
    StatusViewZoomInType = 2,
    StatusViewConnectionLostType = 3,
    StatusViewConnectionFoundType = 4,
    StatusViewNoDataFoundType = 5,
    StatusViewGeographyIntersectionsType = 6,
    StatusViewRetreivingRecordsType = 7
 StatusViewType;

@interface StatusView : UIView

@property (strong, nonatomic) NSString *statusMessage;
@property (nonatomic) StatusViewType statusViewType;

- (id)initWithFrame:(CGRect)frame message:(NSString*)message type:(StatusViewType)type;

@end

...和实施:

#import "StatusView.h"

#define kConstrainSizeWidthOffset 10
#define kImageBufferWidth 15

@interface StatusView ()

@property (nonatomic, strong) UILabel *statusMessageLabel;
@property (nonatomic, strong) UIFont *statusMessageFont;
@property (nonatomic, strong) UIImage *statusImage;
@property (nonatomic, strong) UIImageView *statusImageView;

@end

@implementation StatusView

@synthesize statusMessageLabel = _statusMessageLabel;
@synthesize statusMessageFont = _statusMessageFont;
@synthesize statusImageView = _statusImageView;
@synthesize statusMessage = _statusMessage;
@synthesize statusViewType = _statusViewType;
@synthesize statusImage = _statusImage;

- (id)initWithFrame:(CGRect)frame message:(NSString *)message type:(StatusViewType)type 

    self = [super initWithFrame:frame];

    if (self) 

        if (message != nil) 

            _statusMessage = message;
            _statusMessageFont = [UIFont fontWithName:@"Avenir-Roman" size:15.0];

            CGSize constrainSize = CGSizeMake(self.frame.size.width - kImageBufferWidth - kConstrainSizeWidthOffset, self.frame.size.height);

            // Find the size appropriate for this message
            CGSize messageSize = [_statusMessage sizeWithFont:_statusMessageFont constrainedToSize:constrainSize];

            // Create label and position at center of status view
            CGRect labelFrame = CGRectMake(self.frame.origin.x,
                                       self.frame.origin.y,
                                       messageSize.width,
                                       messageSize.height);

            _statusMessageLabel = [[UILabel alloc] initWithFrame:labelFrame];
            _statusMessageLabel.backgroundColor = [UIColor clearColor];
            _statusMessageLabel.textColor = [UIColor whiteColor];
            _statusMessageLabel.font = _statusMessageFont;

            // Set shadow and color
            _statusMessageLabel.shadowOffset = CGSizeMake(0, 1);
            _statusMessageLabel.shadowColor = [UIColor blackColor];

            // Center the label
            CGPoint centerPoint = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
            _statusMessageLabel.center = centerPoint;

            // Gets rid of fuzziness
            _statusMessageLabel.frame = CGRectIntegral(_statusMessageLabel.frame);

            // Flex both the width and height as well as left and right margins
            _statusMessageLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;

            // Set label text
            _statusMessageLabel.text = _statusMessage;

            [self addSubview:_statusMessageLabel];
        

        self.statusViewType = type;

        if (_statusImage != nil) 

            // Create image view
            _statusImageView = [[UIImageView alloc] initWithImage:_statusImage];

            // Vertically center the image
            CGPoint centerPoint = CGPointMake(_statusMessageLabel.frame.origin.x - kImageBufferWidth,
                                          self.frame.size.height / 2);

            _statusImageView.center = centerPoint;

            [self addSubview:_statusImageView];
        
    

    return self;


- (void)layoutSubviews 

    CGSize constrainSize = CGSizeMake(self.frame.size.width - kImageBufferWidth - kConstrainSizeWidthOffset, self.frame.size.height);

    // Find the size appropriate for this message
    CGSize messageSize = [_statusMessage sizeWithFont:_statusMessageFont constrainedToSize:constrainSize];

    // Create label and position at center of status view
    CGRect labelFrame = CGRectMake(self.frame.origin.x,
                               self.frame.origin.y,
                               messageSize.width,
                               messageSize.height);

    _statusMessageLabel.frame = labelFrame;

    // Center the label
    CGPoint centerPoint = CGPointMake(self.frame.size.width / 2, self.frame.size.height / 2);
    _statusMessageLabel.center = centerPoint;

    // Gets rid of fuzziness
    _statusMessageLabel.frame = CGRectIntegral(_statusMessageLabel.frame);

    if (_statusImageView != nil) 

        // Vertically center the image
        CGPoint centerPoint = CGPointMake(_statusMessageLabel.frame.origin.x - kImageBufferWidth,
                                      self.frame.size.height / 2);

        _statusImageView.center = centerPoint;
    


#pragma mark - Custom setters

- (void)setStatusMessage:(NSString *)message 

    if (_statusMessage == message) return;

    _statusMessage = message;

    _statusMessageLabel.text = _statusMessage;

    // Force layout of subviews
    [self setNeedsLayout];
    [self layoutIfNeeded];


- (void)setStatusViewType:(StatusViewType)statusViewType 

    _statusViewType = statusViewType;

    UIColor *bgColor = nil;

    switch (_statusViewType) 

        // Changes background and image based on type
    

    self.backgroundColor = bgColor;

    if (_statusImageView != nil) 
        _statusImageView.image = _statusImage;
    


@end

然后在我的视图控制器中我可以这样做:

CGRect statusFrame = CGRectMake(self.mapView.frame.origin.x,
                                    self.mapView.frame.origin.y,
                                    self.mapView.frame.size.width,
                                    kStatusViewHeight);

self.staticStatusView = [[StatusView alloc] initWithFrame:statusFrame message:@"600 records found :)" type:StatusViewRecordCountType];

self.staticStatusView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;

[self.view addSubview:self.staticStatusView];

...以后我可以通过这样做来改变它:

self.staticStatusView.statusMessage = @"No data was found here";
self.staticStatusView.statusViewType = StatusViewNoDataFoundType;

现在我有了一个可重用的类,而不是 12 个 UIView 实例在我的 NIB 周围浮动,具有各种设置和属性。

【讨论】:

以上是关于在运行时和方向更改期间居中和 sizeToFit 子视图的主要内容,如果未能解决你的问题,请参考以下文章

-g 标志更改程序的运行时和编译

在 Android 2.1 中方向更改期间保存 WebView 滚动位置的问题

在方向更改期间防止白边

在带有阴影的 UILabel 上使用 SizeToFit()?

ScrollView中的UILabel sizeToFit

如何防止 sizeToFit 更改 UILabel 宽度?