【问题标题】:UIView subclass with its own XIB [duplicate]具有自己的 XIB 的 UIView 子类 [重复]
【发布时间】:2011-07-11 21:23:05
【问题描述】:

我创建了一个自定义 UIView 子类,并且不希望在 UIView 子类中的代码中布局 UI。我想为此使用xib。所以我做的是以下。

我创建了一个类“ShareView”,它是 UIView 的子类。我创建了一个 XIB 文件,其文件所有者设置为“ShareView”。然后我链接了我在“ShareView.h”中声明的一些网点。

接下来我有一个 ViewController,MainViewController,它将 ShareView 添加为子视图。使用此代码:

NSArray *arr = [[NSBundle mainBundle] loadNibNamed:@"ShareView" owner:nil options:nil];
UIView *fv = [[arr objectAtIndex:0] retain];
fv.frame = CGRectMake(0, 0, 320, 407);
[self.view addSubview:fv];

但现在我在 ShareView 中声明的网点出现 NSUnknownKeyException 错误。

我做这一切的原因是因为我想要一个 UIView,它有自己的逻辑在一个单独的 XIB 文件中。我在几个地方读到 ViewControllers 仅用于管理全屏,即不是屏幕的一部分...... 那么我做错了什么?我希望我的 ShareView 逻辑在一个单独的类中,所以我的 MainController 类不会因 ShareView 的逻辑而臃肿(我认为这是解决这个问题的一种方法?)

【问题讨论】:

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


【解决方案1】:

您可以创建在 xib 中设计的自定义 UIView,甚至可以使用 IB_DESIGNABLE 使 Interface Builder 在新 Xcode 6 中的其他 xib 文件或情节提要中显示它。在 xib 中将文件所有者设置为您的自定义类,但不要设置 UIView 类以避免重复加载问题。只需保留默认的 UIView 类,您就会将此 UIView 添加为自定义类视图的子视图。将您的所有出口连接到文件所有者,并在您的自定义类中加载您的 xib,如下面的代码所示。你可以在这里查看我的视频教程:https://www.youtube.com/watch?v=L97MdpaF3Xg

IB_DESIGNABLE
@interface CustomControl : UIView
@end

@implementation CustomControl

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super initWithCoder:aDecoder])
    {
        [self load];
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame])
    {
        [self load];
    }
    return self;
}

- (void)load
{
    UIView *view = [[[NSBundle bundleForClass:[self class]] loadNibNamed:@"CustomControl" owner:self options:nil] firstObject];
    [self addSubview:view];
    view.frame = self.bounds;
}

@end

如果您使用的是自动布局,那么您可能需要将:view.frame = self.bounds; 更改为:

[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)]];

【讨论】:

    【解决方案2】:

    要将 Yang 的模式与 Auto-Layout 一起使用,您需要在 -awakeWithCoder: 方法中的某处添加以下内容。

        theRealThing.translatesAutoresizingMaskIntoConstraints = NO;
    

    如果您不关闭 -translatesAutoResizingMaskIntoConstraints,它可能会导致您的布局不正确以及在控制台中导致大量的调试废话。

    编辑:自动布局仍然很痛苦。某些约束不受尊重,但其他约束(例如,固定到底部不起作用,但固定到顶部)。我们不确定原因,但您可以通过手动将约束从占位符传递给 theRealThing 来解决此问题。

    同样值得注意的是,这种模式在 Storyboard 上的工作方式与在常规 .xib 上的工作方式相同(即,您可以在 .xib 中创建一个 UI 元素,然后按照您的步骤将其放入 StoryBoard 视图控制器中。)

    【讨论】:

      【解决方案3】:

      我想补充一下答案。我希望人们会改进这个答案。

      首先它确实有效。

      XIB:

      结果:

      我很想继承 UIView 很长一段时间,尤其是 tableViewCell。

      我就是这样做的。

      这是成功的,但在我看来,有些部分仍然“尴尬”。

      首先,我创建了一个常用的 .h、.m 和 xib 文件。请注意,如果您创建的子类不是 UIViewController 的子类,Apple 没有自动创建 xib 的复选框。无论如何都要创建它们。

      #import <UIKit/UIKit.h>
      #import "Business.h"
      
      @interface BGUIBusinessCellForDisplay : UITableViewCell
      
      + (NSString *) reuseIdentifier;
      
      
      - (BGUIBusinessCellForDisplay *) initWithBiz: (Business *) biz;
      @end
      

      非常简单的 UITableViewCell,我想用 biz 初始化后者。

      我把你应该为 UITableViewCell 做的重用标识符放了

      //#import "Business.h"
      @interface BGUIBusinessCellForDisplay ()
      @property (weak, nonatomic) IBOutlet UILabel *Title;
      @property (weak, nonatomic) IBOutlet UIImageView *Image;
      @property (weak, nonatomic) IBOutlet UILabel *Address;
      @property (weak, nonatomic) IBOutlet UILabel *DistanceLabel;
      @property (weak, nonatomic) IBOutlet UILabel *PinNumber;
      @property (strong, nonatomic) IBOutlet BGUIBusinessCellForDisplay *view;
      
      @property (weak, nonatomic) IBOutlet UIImageView *ArrowDirection;
      @property (weak, nonatomic) Business * biz;
      @end
      
      @implementation BGUIBusinessCellForDisplay
      
      - (NSString *) reuseIdentifier {
          return [[self class] reuseIdentifier];
      };
      + (NSString *) reuseIdentifier {
          return NSStringFromClass([self class]);
      };
      

      然后我删除了大多数初始化代码并改为:

      - (BGUIBusinessCellForDisplay *) initWithBiz: (Business *) biz
      {
          if (self.biz == nil) //First time set up
          {
              self = [super init]; //If use dequeueReusableCellWithIdentifier then I shouldn't change the address self points to right
              NSString * className = NSStringFromClass([self class]);
              //PO (className);
              [[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
              [self addSubview:self.view]; //What is this for? self.view is of type BGCRBusinessForDisplay2. That view should be self, not one of it's subview Things don't work without it though
          }
          if (biz==nil)
          {
              return self;
          }
      
          self.biz = biz;
          self.Title.text = biz.Title; //Let's set this one thing first
          self.Address.text=biz.ShortenedAddress;
      
          //if([self.distance isNotEmpty]){
          self.DistanceLabel.text=[NSString stringWithFormat:@"%dm",[biz.Distance intValue]];
          self.PinNumber.text =biz.StringPinLineAndNumber;
      

      请注意,这真的很尴尬。

      首先,init 可以有两种使用方式。

      1. 可以直接用在aloc之后
      2. 如果我们有另一个现有的类,然后我们只想将该 现有 单元格初始化到另一个业务,则可以使用它。

      所以我做到了:

      if (self.biz == nil) //First time set up
      {
          self = [super init]; //If use dequeueReusableCellWithIdentifier then I shouldn't change the address self points to right
          NSString * className = NSStringFromClass([self class]);
          //PO (className);
          [[NSBundle mainBundle] loadNibNamed:className owner:self options:nil];
          [self addSubview:self.view]; //What is this for? self.view is of type BGCRBusinessForDisplay2. That view should be self, not one of it's subview Things don't work without it though
      }
      

      我做的另一件令人讨厌的事情是当我做 [self addSubview:self.view];

      问题是我希望自己成为 视图。不是self.view。不知何故,它仍然有效。所以是的,请帮助我改进,但这本质上是实现您自己的 UIView 子类的方式。

      【讨论】:

      【解决方案4】:

      托马斯,

      关于将行为封装在自定义视图中,我们有类似的想法(例如,带有用于最小/最大/当前值的伴随标签的滑块,值更改事件也由控件在内部处理)。

      在我们当前的最佳实践中,我们将在 Interface Builder (ShareView.xib) 中设计 ShareView,正如 Eimantas 在他的回答中所描述的那样。然后我们将 ShareView 嵌入到 MainViewController.xib 中的视图层次结构中。

      我在我们的 iOS 开发者博客中写了我们如何embed custom-view Nibs inside other Nibs。关键是在您的自定义视图中覆盖 -awakeAfterUsingCoder:,将从 MainViewController.xib 加载的对象替换为从“嵌入式” Nib (ShareView.xib) 加载的对象。

      类似的东西:

      // ShareView.m
      - (id) awakeAfterUsingCoder:(NSCoder*)aDecoder {
          BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);
          if (theThingThatGotLoadedWasJustAPlaceholder) {
              // load the embedded view from its Nib
              ShareView* theRealThing = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([ShareView class]) owner:nil options:nil] objectAtIndex:0];
      
              // pass properties through
              theRealThing.frame = self.frame;
              theRealThing.autoresizingMask = self.autoresizingMask;
      
              [self release];
              self = [theRealThing retain];
          }
          return self;
      }
      

      【讨论】:

      • 听起来不错,除非在使用 ARC 时,您不能在 init 方法之外分配给 self :-(
      • 您对 ARC 的看法是正确的。请参阅我在blog.yangmeyer.de/blog/2012/07/09/… 的后续帖子
      • 使用此技术时需要注意的是,对于 theRealThing(即,对于同一个实例),awakeFromNib 将被调用两次。如果您的实现覆盖它,则需要考虑这一点。
      • @Yang Meyer 您答案中的链接以及博文不再有效。你能更新一下吗?
      【解决方案5】:

      你为什么不继承 UIViewController,而不是继承 UIView。查看以下链接。通过创建前两个类的实例并在 MultipleViewsController 的 viewDidLoad 方法中添加 [self.view addSubview:red.view][self.view addSubview:blue.view] 来制作带有 xib 的“RedView”和“BlueView”UIViewControllers 并将它们添加到 MultipleViewsController 视图中

      MultipleControllers in one view

      在上面链接的代码中,只需在 RedView 和 BlueView 中的按钮按下函数中添加 (id)sender 即可。

      【讨论】:

      • 就像我在问题中所说:“我在几个地方读到 ViewController 仅用于管理全屏,即不是屏幕的一部分......”
      • 在您的问题中,您正在使用 xib 创建一个视图,并通过 addSubview 方法将其添加到您的主视图中。在我给你的链接中,我制作了两个控制器并将 redView 和 blueView 添加到同一个视图中。两者都由它们自己的控制器控制。您可以在自己的类中添加它们的逻辑。此外,您可以使用界面生成器来修改这两个视图。
      • 啊,是的,我现在明白了。但苹果似乎不支持这种方法。如果你在文档中查找它,它会说:“你不应该使用多个自定义视图控制器来管理同一视图层次结构的不同部分。同样,你不应该使用单个自定义视图控制器对象来管理多个屏幕价值内容。” developer.apple.com/library/ios/#featuredarticles/…
      • 哦,感谢您指出这一点。我不知道苹果不支持它:)。它虽然有效
      【解决方案6】:

      您将加载的 xib 的所有者定义为 nil。由于 xib 本身中的文件所有者已连接插座并被定义为 ShareView 的实例,因此您会收到有关未知键的异常(nil 没有您为 ShareView 定义的插座属性)。

      您应该将 xib 的加载器定义为所有者(即负责加载 xib 的视图控制器)。然后将单独的 UIView 对象添加到 xib 并将其定义为 ShareView 的实例。然后在加载xib时。

      ShareView *shareView = [[[[NSBundle mainBundle] loadNibNamed:@"ShareView" owner:self options:nil] objectAtIndex:0] retain];
      

      您还可以在视图控制器的界面中将shareView 定义为IBOutlet(并将文件所有者的出口连接到xib 本身的该视图)。然后,当您加载 xib 时,将不需要重新分配 shareView 实例变量,因为 xib 加载过程会将视图直接重新连接到实例变量。

      【讨论】:

      • 我做了你所说的(部分),但它仍然不起作用..我说部分,因为我不明白你到底是什么意思:“然后将单独的 UIView 对象添加到 xib 和将其定义为 ShareView 的实例。”我现在收到 NSUnknownException 错误,因为“MainController”没有在 ShareView 中实现插座。这就是为什么我没有得到你的答案。当shareView的逻辑位于ShareView.m中,并且Interface Builder中的ShareViews类标识设置为ShareView时,为什么“MainController”应该是shareView的所有者?希望以某种方式有意义..
      • 以下是创建 xib 的方法: 1) 从文件 -> 新建对话框窗口创建视图 xib。 2) 将 MainController 定义为文件所有者。 3) 选择xib中的视图对象并将其设置为ShareView的子类。 4) 双击该视图并将其编辑为您喜欢的内容。 5) 将该视图对象的出口连接到您添加的子视图。
      • 假设我采用你的方法,我在 ShareView.xib 的视图中放置了一个按钮,我将在哪里放置 -(IBAction) tappedMyButton {}... 它进入 ShareView.m文件?
      • 您将在 MainController 中放置一个操作并将 IBAction 出口从按钮连接到文件所有者。
      • 当然这是一个选项。但我想你和我一样都知道,不同视图控制器中的重复代码是不可取的......如果我想改变 IBAction 中的某些内容怎么办?
      猜你喜欢
      • 2015-07-05
      • 2011-10-30
      • 2016-09-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-12-12
      • 2015-02-04
      • 1970-01-01
      相关资源
      最近更新 更多