Replace custom UIViews arranged by Interface Builder with UILabels programmatically

时间:2016-02-03 03:13:51

标签: ios objective-c iphone uiview interface-builder

Using Interface Builder, I have built a really long ScrollView filled with Custom UIViews, regular UIViews, StackViews, UILabels, UIButtons, etc.

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.

What's the best/easiest way to do this programmatically in my ViewController given that all the views are arranged using interface builder?

Thanks for your help in advance!

2 个答案:

答案 0 :(得分:2)

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...

// 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. 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. 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.

- (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.

- (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.

For example, say CustomView comes from the third party. Create a class called CustomViewWrapper that contains the CustomView as a child and adds the noData behavior outlined above. Instead of painting CustomViews in IB, paint 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;
}

答案 1 :(得分:2)

你可以通过在想要覆盖的视图中添加一个带有一些简单约束的UILabel而不是在它们内部来实现这一点,如果你想确保你没有弄乱你没有控制的控件。< / p>

我设置了一个简单的测试应用,以展示此方法的工作原理 Before image showing some controls

它有一个堆栈视图,里面有一些图像,一个文本视图和一个触发样本的按钮。

您应该能够在代码中确定您没有要显示的数据并希望显示占位符时将此方法应用于您的视图,但在我的示例中,我设置了一个同时包含两者的IBOutletCollection堆栈视图和文本视图,并在按下按钮时在两个视图上运行它。

After image showing placeholders instead of controls

您需要做的就是提供占位符文本和要替换此方法的视图

/// 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]];
}

添加到占位符的约束应通过旋转或视图中的任何其他布局活动使其正确定位。