【问题标题】:Replace custom UIViews arranged by Interface Builder with UILabels programmatically以编程方式用 UILabels 替换由 Interface Builder 排列的自定义 UIView
【发布时间】:2016-02-03 03:13:50
【问题描述】:

使用Interface Builder,我构建了一个很长的ScrollView,其中填充了自定义UIViews、常规UIViewsStackViewsUILabelsUIButtons等。

对于某些自定义 UIView,如果它们没有任何数据,那么我想用一个显示“No Data Available”的 UILabel 替换它们,并且我希望能够设置边距并将其文本居中UIL标签。

鉴于所有视图均使用interface builder? 排列,在我的ViewController 中以编程方式执行此操作的最佳/最简单方法是什么?

提前感谢您的帮助!

【问题讨论】:

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


    【解决方案1】:

    如果你想确保你不会弄乱你没有控制的控件,你可以通过添加一个带有一些简单约束的 UILabel 来做到这一点。

    我设置了一个简单的测试应用程序来展示这种方法的工作原理

    这有一个包含一些图像的堆栈视图、一个文本视图和一个用于触发示例的按钮。

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

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

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

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

    【讨论】:

    • 好答案。关键是隐藏视图仍然参与布局。
    • 除了 OP 需要切换视图的状态(如果它从没有数据变回有数据,反之亦然),你处于一个尴尬的位置,基于狩猎on frame 对应的标签。
    • 如果 OP 确实需要跟踪这些,修改上述方法以返回占位符而不是 void,然后将其存储在绑定它的字典(或类似的东西)中就足够简单了到目标视图。然后可以删除占位符并恢复原始视图的可见性,而无需做任何时髦的事情来查找标签。
    • 我稍后会尝试这两种解决方案,看看会发生什么。谢谢!
    【解决方案2】:

    一个想法是,与其用标签替换自定义视图,不如给它们一个“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;
    }
    

    编辑

    如果你想将自定义视图视为不可触摸,你可以在包含它们的视图控制器中处理状态,但这有点尴尬,因为我们需要解决关联 noData 的问题带有子视图的标签。像这样的东西可以工作......

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

    数组 noDataViews 可以包含字典。该字典将包含具有 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;
    }
    

    根据视图将状态更改为 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];
        }
    }
    

    这有点冗长,因为通过将我们的手从自定义视图上移开,我们将更改它们的外观(覆盖它们)的逻辑放在视图控制器中。

    也许一个更好的主意可以避免自定义视图,将它们包装在一个包含视图中,该视图执行添加 noData 状态的额外工作。

    例如,假设CustomView 来自第三方。创建一个名为 CustomViewWrapper 的类,其中包含作为子项的 CustomView 并添加上述 noData 行为。与其在 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;
    }
    

    【讨论】:

    • 嘿,丹,谢谢你的建议。不幸的是,这对我来说效果不佳,因为我想要替换的一些视图来自 3rd 方库,我可能不应该编辑它们。
    • @stellarowl12 - 我明白了。提供了两种选择。我更喜欢第二个。
    • 我稍后会尝试这两种解决方案,看看会发生什么。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-22
    • 1970-01-01
    • 2017-09-02
    • 2010-10-14
    • 2015-05-25
    • 2010-10-04
    相关资源
    最近更新 更多