简体   繁体   English

以编程方式将由Interface Builder安排的自定义UIView替换为UILabels

[英]Replace custom UIViews arranged by Interface Builder with UILabels programmatically

Using Interface Builder , I have built a really long ScrollView filled with Custom UIViews , regular UIViews , StackViews , UILabels , UIButtons , etc. 使用Interface Builder ,我建立了一个很长的ScrollView充满了自定义UIViews ,定期UIViewsStackViewsUILabelsUIButtons等。

For some of the Custom UIViews, if they do not have any data, then I want to replace them with a UILabel that says " No Data Available " and I want to be able to set the margins and center the text of that UILabel. 对于某些自定义UIView,如果它们没有任何数据,那么我想用显示“ No Data Available ”的UILabel替换它们,并且我希望能够设置边距并使该UILabel的文本居中。

What's the best/easiest way to do this programmatically in my ViewController given that all the views are arranged using interface builder? 鉴于所有视图都是使用interface builder?排列的,因此在ViewController以编程方式进行此操作的最佳/最简便方法是什么interface builder?

Thanks for your help in advance! 谢谢您的帮助!

One idea is, instead of replacing the custom views with labels, give them an "noData" mode where they present the right thing if there's no data... 一个想法是,不要用标签替换自定义视图,而是给它们提供“ noData”模式,在这种模式下,如果没有数据,它们将显示正确的内容...

// CustomView.h

@interface CustomView : UIView
@property(assign,nonatomic) BOOL noData;
@end

// CustomView.m

@interface CustomView ()
@property(weak,nonatomic) UILabel *noDataLabel;
@end

- (void)setNoData:(BOOL)noData {
    _noData = noData;
    self.noDataLabel.alpha = (noData)? 1.0 : 0.0;
}

- (UILabel *)noDataLabel {
    if (!_noDataLabel) {
        UILabel *noDataLabel = [[UILabel alloc] initWithFrame:self.bounds];
        noDataLabel.backgroundColor = self.backgroundColor;
        noDataLabel.textAlignment = NSTextAlignmentCenter;
        noDataLabel.text = @"NO DATA";
        // configure font, etc.
        [self addSubview:noDataLabel];
        _noDataLabel = noDataLabel;
    }
    return _noDataLabel;
}

EDIT 编辑

If you want to treat the custom views as untouchable , you can handle the state in the view controller that contains them, but it's a little awkward because we need to solve the problem of associating the noData label with the subview. 如果要将自定义视图视为不可触摸 ,则可以在包含它们的视图控制器中处理其状态,但这有点尴尬,因为我们需要解决将noData标签与子视图关联的问题。 Something like this can work... 这样的事情可以工作...

// in the view controller that contains the views that should be covered with labels
@interface ViewController ()
@property(weak,nonatomic) NSMutableArray *noDataViews;
@end

// initialize noDataViews early, like in viewDidLoad
_noDataViews = [@[] mutableCopy];

The array noDataViews can contain dictionaries. 数组noDataViews可以包含字典。 The dictionary will contain the view that has noData (this can be an instance of your third-party custom view), and a UILabel intended to cover it. 词典将包含具有noData的视图(这可以是第三方自定义视图的实例),以及打算覆盖该视图的UILabel。

- (void)setView:(UIView *)view hasNoData:(BOOL)noData {
    // find the dictionary corresponding to view
    NSDictionary *dictionary;
    for (NSDictionary *d in self.noDataViews) {
        if (d[@"view"] == view) {
            dictionary = d;
            break;
        }
    }
    // if it doesn't exist, insert it
    if (!dictionary) {
        UILabel *label = [self labelToCover:view];
        dictionary = @{ @"view":view, @"label":label };
        [self.noDataViews addObject:dictionary];
    }
    // get the label
    UILabel *label = dictionary[@"label"];
    label.alpha = (noData)? 1.0 : 0.0;
}

// create a label that will cover the passed view, add it as a subview and return it
- (UILabel *)labelToCover:(UIView *)view {
    UILabel *noDataLabel = [[UILabel alloc] initWithFrame:view.frame];
    noDataLabel.backgroundColor = view.backgroundColor;
    noDataLabel.textAlignment = NSTextAlignmentCenter;
    noDataLabel.text = @"NO DATA";
    // configure font, etc.
    [self.view addSubview:noDataLabel];
    return noDataLabel;
}

Depending on how often the views change state to the noData state, you might want to clean up the dictionaries, removing those whose label's alpha == 0.0. 根据视图将状态更改为noData状态的频率,您可能希望清除字典,删除那些标签的alpha == 0.0的字典。

- (void)releaseNoDataViews {
    NSMutableArray *removeThese = [@[] mutableCopy];
    // work out which ones to remove
    for (NSDictionary *d in self.noDataViews) {
        UILabel *label = d[@"label"];
        if (label.alpha == 0.0) {
            [removeThese addObject:d];
        }
    }
    for (NSDictionary *d in removeThese) {
        UILabel *label = d[@"label"];
        [label removeFromSuperview];
        [self.noDataViews removeObject:d];
    }
}

This a little verbose because by keeping our hands off the custom views, we put the logic to change how they look (cover them) in the view controller. 这有点冗长,因为通过不使用自定义视图,我们将逻辑更改为在视图控制器中改变它们的外观(覆盖它们)。

Maybe a better idea that keeps hands off the custom views is to wrap them in a containing view that does the additional work adding the noData state. 最好不要使用自定义视图,最好是将它们包装在一个包含视图中,该视图可以完成添加noData状态的附加工作。

For example, say CustomView comes from the third party. 例如,说CustomView来自第三方。 Create a class called CustomViewWrapper that contains the CustomView as a child and adds the noData behavior outlined above. 创建一个名为CustomViewWrapper的类,该类包含CustomView作为子级,并添加上面概述的noData行为。 Instead of painting CustomViews in IB, paint CustomViewWrappers.... 代替在IB中绘制CustomView,而绘制CustomViewWrappers。

// CustomViewWrapper.h

@class CustomView;

@interface CustomViewWrapper : UIView
@property(assign,nonatomic) BOOL noData;
@end

// CustomViewWrapper.m

#import "CustomView.h"

@interface CustomViewWrapper ()
@property(weak,nonatomic) CustomView *customView;
@property(weak,nonatomic) UILabel *noDataLabel;
@end

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecorder];
    if (self) {
        CustomView *customView = [[CustomView alloc] init];
        [self addSubView:customView];
        _customView = customView;
    }
    return self;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    self.customView.frame = self.bounds;
}

- (void)setNoData:(BOOL)noData {
    _noData = noData;
    self.noDataLabel.alpha = (noData)? 1.0 : 0.0;
}

- (UILabel *)noDataLabel {
    if (!_noDataLabel) {
        UILabel *noDataLabel = [[UILabel alloc] initWithFrame:self.bounds];
        noDataLabel.backgroundColor = self.backgroundColor;
        noDataLabel.textAlignment = NSTextAlignmentCenter;
        noDataLabel.text = @"NO DATA";
        // configure font, etc.
        [self addSubview:noDataLabel];
        _noDataLabel = noDataLabel;
    }
    return _noDataLabel;
}

You can do this by adding a UILabel, with some simple constraints, over the views you want to cover instead of inside them if you want to ensure you aren't messing with controls you don't, well, control. 您可以通过在要覆盖的视图上添加一个带有一些简单约束的UILabel来实现此目的,而不是在视图内部添加一个UILabel(如果要确保您不会弄乱那些不是您想要的控件)。

I set up a simple test app to show how this method can work 我设置了一个简单的测试应用程序,以展示此方法的工作原理 在显示一些控件的图像之前

This has a stack view with some images in it, a text view, and a button to trigger the sample. 它具有一个带有一些图像的堆栈视图,一个文本视图以及一个触发样本的按钮。

You should be able to apply this method to your views as you determine in your code that you have no data to show, and want to show the placeholder, but in my example I've set up an IBOutletCollection that has both the stack view and the text view in it, and am running this on both views when the button is pressed. 在代码中确定没有要显示的数据并且想要显示占位符时,您应该能够将此方法应用于视图,但是在我的示例中,我设置了一个IBOutletCollection,它同时具有堆栈视图和文本视图,并在按下按钮时在两个视图上都运行此视图。

后图显示占位符而不是控件

All you need to do is provide the placeholder text and the view you want to replace to this method 您所需要做的就是为此方法提供占位符文本和要替换的视图

/// This method will hide a view and put a placeholder label in that view's superview, centered in the target view's frame.
- (void)showPlaceholderText:(NSString *)placeholder forView:(UIView *)view
{
    // Build the placeholder with the same frame as the target view
    UILabel *placeholderLabel = [[UILabel alloc] initWithFrame:view.frame];
    placeholderLabel.textAlignment = NSTextAlignmentCenter;
    placeholderLabel.text = placeholder;
    placeholderLabel.translatesAutoresizingMaskIntoConstraints = NO;

    // Hide the target view
    view.hidden = YES;

    // Put our placeholder into the superview, overtop the target view
    [view.superview addSubview:placeholderLabel];

    // Set up some constraints to ensure the placeholder label stays positioned correctly
    [view.superview addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:placeholderLabel attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f]];
    [view.superview addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:placeholderLabel attribute:NSLayoutAttributeRight multiplier:1.0f constant:0.0f]];
    [view.superview addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:placeholderLabel attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f]];
    [view.superview addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:placeholderLabel attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f]];
}

The constraints added to the placeholder should keep it positioned correctly, through rotation or any other layout activity in the view. 添加到占位符的约束应通过旋转或视图中的任何其他布局活动来使其正确定位。

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

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