简体   繁体   中英

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.

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!

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

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.

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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