Como carregar um UIView usando um arquivo de ponta criado com o Interface Builder

241

Estou tentando fazer algo um pouco elaborado, mas algo que deve ser possível. Então, aqui está um desafio para todos os especialistas por aí (este fórum é um pacote para muitos de vocês :)).

Estou criando um "componente" do questionário, que quero carregar em um NavigationContoller(meu QuestionManagerViewController). O "componente" é um "vazio" UIViewController, que pode carregar visualizações diferentes, dependendo da pergunta que precisa ser respondida.

O jeito que eu faço é:

  1. Crie o objeto Question1View como uma UIViewsubclasse, definindo alguns IBOutlets.
  2. Crie (usando o Interface Builder) o Question1View.xib (AQUI ESTÁ ONDE É PROVAVELMENTE MEU PROBLEMA ). Defino o UIViewControllere o UIViewda classe Question1View.
  3. Eu vinculo as saídas com o componente da visualização (usando IB).
  4. Eu substituo o initWithNibmeu QuestionManagerViewControllerpara ficar assim:

    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
        if (self = [super initWithNibName:@"Question1View" bundle:nibBundleOrNil]) {
            // Custom initialization
        }
        return self;
    }

Quando executo o código, estou recebendo este erro:

14-05-2009 15: 05: 37.152 iMobiDines [17148: 20b] *** Finalizando o aplicativo devido a uma exceção não detectada ' NSInternalInconsistencyException', motivo: ' -[UIViewController _loadViewFromNibNamed:bundle:]carregou a ponta "Question1View", mas a saída de exibição não estava definida.'

Tenho certeza de que existe uma maneira de carregar a exibição usando o arquivo nib, sem a necessidade de criar uma classe viewController.

Alexander Farber
fonte

Respostas:

224

Também há uma maneira mais fácil de acessar a visualização em vez de lidar com a ponta como uma matriz.

1 ) Crie uma subclasse View personalizada com todas as saídas às quais deseja acessar mais tarde. --Minha visão

2 ) no UIViewController que você deseja carregar e manipular a ponta, crie uma propriedade IBOutlet que mantenha a visualização da ponta carregada, por exemplo

no MyViewController (uma subclasse UIViewController)

  @property (nonatomic, retain) IBOutlet UIView *myViewFromNib;

(não esqueça de sintetizá-lo e liberá-lo em seu arquivo .m)

3) abra sua ponta (chamaremos de 'myViewNib.xib') no IB, defina o proprietário do arquivo como MyViewController

4) conecte agora a saída do proprietário do arquivo myViewFromNib à visualização principal na ponta.

5) Agora no MyViewController, escreva a seguinte linha:

[[NSBundle mainBundle] loadNibNamed:@"myViewNib" owner:self options:nil];

Agora, assim que você fizer isso, chamar sua propriedade "self.myViewFromNib" dará acesso à visualização da sua ponta!

averydev
fonte
4
Funcionará para a visualização do controlador principal (self.view)? Por algumas razões, eu preciso carregar a vista principal da ponta após o método init do controlador padrão. Razão - estrutura complexa de sublassing
Lukasz
@Lukasz Quanto você consegue fazer? Até eu estou procurando algo como você.
Ajay Sharma
Desculpe por estar sendo grossa, mas estou confusa no primeiro ponto. Como crio pontos de venda em uma subclasse de exibição personalizada? Eu pensei que as tomadas eram apenas para UIViewControllers?
jowie
1
E um caso em que essa exibição aparece em várias visualizações principais? Então você propõe editar o xib para cada um deles manualmente? É MUITO RUIM !!!
user2083364
2
Isso funciona apenas se você desejar usar a ponta personalizada em um único controlador de exibição. No caso em que você deseja usá-lo em diferentes controladores de exibição, cada um criando uma instância da ponta personalizada dinamicamente, isso não funcionará.
thgc 24/02
120

Obrigado a todos. Eu encontrei uma maneira de fazer o que eu queria.

  1. Crie o seu UIViewcom o que IBOutletvocê precisa.
  2. Crie o xib no IB, projete-o como desejar e vincule-o desta maneira: O proprietário do arquivo é da classe UIViewController(nenhuma subclasse personalizada, mas a "real"). A visualização do proprietário do arquivo está conectada à visualização principal e sua classe é declarada como a da etapa 1).
  3. Conecte seus controles com os IBOutlets.
  4. O DynamicViewControllerpode executar sua lógica para decidir qual view / xib carregar. Uma vez feita a decisão, no loadViewmétodo coloque algo como isto:

    NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:@"QPickOneView"
                                                      owner:self
                                                    options:nil];
    
    QPickOneView* myView = [ nibViews objectAtIndex: 1];
    
    myView.question = question;

É isso aí!

O loadNibNamedmétodo do pacote principal cuidará de inicializar a exibição e criar as conexões.

Agora, o ViewController pode exibir uma visualização ou outra, dependendo dos dados da memória, e a tela "pai" não precisa se preocupar com essa lógica.

geedubb
fonte
2
Eu tenho um problema muito semelhante - eu simplesmente quero carregar um UIView a partir de um arquivo xib. Essas etapas não funcionam para mim - o aplicativo trava com "acesso ruim" logo após adicionar a visualização carregada como uma subvisualização.
27409 Justicle
2
Para o iPhone 3.0, use objectAtIndex: 0 para obter o primeiro elemento. Isso trava para mim exatamente como descrito por Justicle. Alguma idéia do porquê?
TBA
1
+1 funcionou perfeitamente para mim. Eu queria adicionar vários pontos de vista que tiveram o controlador de um ponto de vista (im usando um cenário vista aleta, onde uma parte da vista gira) eu tive que fazer o índice na matriz 0 (iPhone 3.0)
Aran Mulholland
42
Esta é a resposta correta, no entanto, o código deve ser modificado para percorrer o nibViews e fazer uma verificação de classe em cada objeto, para que você certamente esteja obtendo o objeto correto. objectAtIndex: 1 é uma suposição perigosa.
M. Ryan
2
Jasconius está certo. Você deve percorrer a matriz nibViews assim: gist.github.com/769539
leviatã
71

Não tenho certeza do que algumas das respostas estão falando, mas preciso colocar essa resposta aqui para quando pesquisar no Google na próxima vez. Palavras-chave: "Como carregar um UIView de uma ponta" ou "Como carregar um UIView de um NSBundle".

Aqui está o código quase 100% direto do livro Apress Beginning iPhone 3 (página 247, "Usando a nova célula de exibição de tabela"):

- (void)viewDidLoad {
    [super viewDidLoad];
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:@"Blah"
                                                 owner:self options:nil];
    Blah *blah;
    for (id object in bundle) {
        if ([object isKindOfClass:[Blah class]]) {
            blah = (Blah *)object;
            break;
        }
    }   
    assert(blah != nil && "blah can't be nil");
    [self.view addSubview: blah];
} 

Isso supõe que você tenha uma UIViewsubclasse chamada Blah, uma ponta chamada Blahque contém um UIViewcuja classe foi definida Blah.


Categoria: NSObject + LoadFromNib

#import "NSObject+LoadFromNib.h"

@implementation NSObject (LoadFromNib)

+ (id)loadFromNib:(NSString *)name classToLoad:(Class)classToLoad {
    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:name owner:self options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:classToLoad]) {
            return object;
        }
    }
    return nil;
}

@end

Extensão rápida

extension UIView {
    class func loadFromNib<T>(withName nibName: String) -> T? {
        let nib  = UINib.init(nibName: nibName, bundle: nil)
        let nibObjects = nib.instantiate(withOwner: nil, options: nil)
        for object in nibObjects {
            if let result = object as? T {
                return result
            }
        }
        return nil
    }
}

E um exemplo em uso:

class SomeView: UIView {
    class func loadFromNib() -> SomeView? {
        return self.loadFromNib(withName: "SomeView")
    }
}
Dan Rosenstark
fonte
2
Se você usar o, isKindOfvocê está protegido contra alterações na estrutura do Bundle.
Dan Rosenstark 07/07
1
Uma assertseria provavelmente uma solução melhor. Dessa forma, você apenas ocultará o problema.
Sulthan
1
@Sulthan uma afirmação é inerentemente diferente. Quero o objeto do tipo Blá onde quer que esteja na ponta . Não me importo se foi promovido ou rebaixado. No entanto, em resposta ao seu comentário, adicionei uma afirmação, mas ela não está no lugar do forloop. Isso complementa.
Dan Rosenstark
Você pode executar o loadFromNib sem parâmetros: + (id) loadFromNib {NSArray * bundle = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass ([classe própria]) owner: self options: nil]; for (objeto de identificação no pacote configurável) {if ([object isKindOfClass: [self class]]]) {return object; }} retornar nulo; }
JVC
22

Para todos aqueles que precisam gerenciar mais de uma instância da visualização personalizada, ou seja, uma coleção de saída , mesclei e personalizei as respostas @Gonso, @AVeryDev e @Olie desta maneira:

  1. Crie um personalizado MyView : UIViewe configure-o como " Classe Personalizada " da raiz UIViewno XIB desejado ; classe personalizada

  2. Crie todos os pontos de venda de que você precisa MyView(faça-o agora porque, após o ponto 3, o IB proporá que você conecte os pontos de UIViewControllervista à exibição personalizada, e não à vista personalizada); tomada de classe personalizada

  3. Defina seu UIViewControllercomo " Proprietário do arquivo " da visualização personalizada XIB ; insira a descrição da imagem aqui

  4. No UIViewController adicionar um novo UIViewspara cada instância MyViewque você quer, e conectá-los a UIViewControllercriar um Outlet Collection : esses pontos de vista irá funcionar como pontos de vista "wrapper" para as instâncias de exibição personalizado; insira a descrição da imagem aqui

  5. Finalmente, nas viewDidLoaddo seu UIViewControlleradd seguintes linhas os:

NSArray *bundleObjects;
MyView *currView;
NSMutableArray *myViews = [NSMutableArray arrayWithCapacity:myWrapperViews.count];
for (UIView *currWrapperView in myWrapperViews) {
    bundleObjects = [[NSBundle mainBundle] loadNibNamed:@"MyView" owner:self options:nil];
    for (id object in bundleObjects) {
        if ([object isKindOfClass:[MyView class]]){
            currView = (MyView *)object;
            break;
        }
    }

    [currView.myLabel setText:@"myText"];
    [currView.myButton setTitle:@"myTitle" forState:UIControlStateNormal];
    //...

    [currWrapperView addSubview:currView];
    [myViews addObject:currView];
}
//self.myViews = myViews; if need to access them later..
Francesco Vadicamo
fonte
Oi ... você sabe como fazer isso tudo programaticamente ou seja. não usando o arquivo xib?
O X-Coder
19

Eu usaria o UINib para instanciar um UIView personalizado a ser reutilizado

UINib *customNib = [UINib nibWithNibName:@"MyCustomView" bundle:nil];
MyCustomViewClass *customView = [[customNib instantiateWithOwner:self options:nil] objectAtIndex:0];
[self.view addSubview:customView];

Os arquivos necessários neste caso são MyCustomView.xib , MyCustomViewClass.h e MyCustomViewClass.m Observe que [UINib instantiateWithOwner]retorna uma matriz, portanto, você deve usar o elemento que reflete o UIView que deseja reutilizar. Nesse caso, é o primeiro elemento.

thgc
fonte
1
Não quer dizer "customNib" na linha 2 em vez de "rankingNib"
Danny
11

Você não deve definir a classe do seu controlador de exibição como uma subclasse UIViewno Interface Builder. Definitivamente, isso é pelo menos parte do seu problema. Deixe isso como uma UIViewControllersubclasse ou outra classe personalizada que você possui.

Como para carregar apenas uma visão de um xib, eu estava sob o pressuposto de que você tinha que ter algum tipo de controlador de vista (mesmo que não se estende UIViewController, o que pode ser muito pesado para as suas necessidades) Definir como proprietário do arquivo na interface Construtor, se você quiser usá-lo para definir sua interface. Eu fiz uma pequena pesquisa para confirmar isso também. Isso ocorre porque, caso contrário, não haveria maneira de acessar qualquer um dos elementos da interface noUIView , nem haveria uma maneira de ter seus próprios métodos no código acionados por eventos.

Se você usar a UIViewControllercomo Proprietário do arquivo para suas visualizações, poderá usá initWithNibName:bundle:-lo para carregá-lo e recuperar o objeto do controlador de visualização. No IB, certifique-se de definir a viewtomada para a visualização com sua interface no xib. Se você usar algum outro tipo de objeto como o Dono do arquivo, precisará usar NSBundleo loadNibNamed:owner:options:método de para carregar a ponta, passando uma instância do Dono do arquivo para o método. Todas as suas propriedades serão definidas corretamente de acordo com as saídas definidas no IB.

Marc W
fonte
Estou lendo o livro Apress, "Iniciando o desenvolvimento do iPhone: explorando o SDK do iPhone", e eles discutiram esse método no capítulo 9: Controladores de navegação e exibições de tabela. Não parecia muito complicado.
Lyndsey Ferguson
Eu ficaria curioso para ver como eles dizem que é feito. Eu não tenho uma cópia desse livro no momento.
Marc W
10

Você também pode usar o initWithNibName do UIViewController em vez de loadNibNamed. É mais simples, eu acho.

UIViewController *aViewController = [[UIViewController alloc] initWithNibName:@"MySubView" bundle:nil];
[self.subview addSubview:aViewController.view];
[aViewController release];  // release the VC

Agora você só precisa criar MySubView.xib e MySubView.h / m. No MySubView.xib, defina a classe Proprietário do arquivo como UIViewController e visualize a classe como MySubView.

Você pode posicionar e tamanho do subviewarquivo xib pai.

Ying
fonte
8

Esta é uma ótima pergunta (+1) e as respostas foram quase úteis;) Desculpe, pessoal, mas passei um tempo passando por isso, apesar de Gonso e AVeryDev terem dado boas dicas. Felizmente, esta resposta ajudará outras pessoas.

MyVC é o controlador de exibição que contém todas essas coisas.

MySubview é a visão que queremos carregar de um xib

  • No MyVC.xib, crie uma visualização do tipo MySubView com o tamanho e a forma corretos e posicionados onde desejar.
  • Em MyVC.h, tenha

    IBOutlet MySubview *mySubView
    // ...
    @property (nonatomic, retain) MySubview *mySubview;
  • No MyVC.m, @synthesize mySubView;e não se esqueça de liberá-lo nodealloc .

  • No MySubview.h, tenha uma saída / propriedade para UIView *view (pode ser desnecessária, mas funcionou para mim.) Sintetize e libere-a em .m
  • No MySubview.xib
    • defina o tipo de proprietário do arquivo como MySubviewe vincule a propriedade view à sua view.
    • Disponha todos os bits e conecte-os IBOutletaos desejados
  • De volta ao MyVC.m, tenha

    NSArray *xibviews = [[NSBundle mainBundle] loadNibNamed: @"MySubview" owner: mySubview options: NULL];
    MySubview *msView = [xibviews objectAtIndex: 0];
    msView.frame = mySubview.frame;
    UIView *oldView = mySubview;
    // Too simple: [self.view insertSubview: msView aboveSubview: mySubview];
    [[mySubview superview] insertSubview: msView aboveSubview: mySubview]; // allows nesting
    self.mySubview = msView;
    [oldCBView removeFromSuperview];

O mais complicado para mim foi: as dicas nas outras respostas carregaram minha visualização do xib, mas NÃO substituíram a visualização no MyVC (duh!) - tive que trocar isso sozinho.

Além disso, para obter acesso aos mySubviewmétodos de, a viewpropriedade no arquivo .xib deve ser definida como MySubview. Caso contrário, ele volta como simples UIView.

Se há uma maneira de carregar mySubviewdiretamente do seu próprio xib, isso seria ótimo, mas isso me levou aonde eu precisava estar.

Olie
fonte
1
Eu usei esse método, obrigado. Uma alteração que fiz para permitir o suporte a visualizações aninhadas foi alterar [ self.view insertSubview: msView aboveSubview: mySubview]; para [ mySubview.superview insertSubview: msView aboveSubview: mySubview];
Jason
8

Isso é algo que deveria ser mais fácil. Acabei estendendo UIViewControllere adicionando um loadNib:inPlaceholder:seletor. Agora eu posso dizer

self.mySubview = (MyView *)[self loadNib:@"MyView" inPlaceholder:mySubview];

Aqui está o código da categoria (ele faz o mesmo rigamarole descrito por Gonso):

@interface UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName;
- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder;

@end

@implementation UIViewController (nibSubviews)

- (UIView *)viewFromNib:(NSString *)nibName
{
  NSArray *xib = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil]; 
  for (id view in xib) { // have to iterate; index varies
    if ([view isKindOfClass:[UIView class]]) return view;
  }
  return nil;
}

- (UIView *)loadNib:(NSString *)nibName inPlaceholder:(UIView *)placeholder
{
  UIView *nibView = [self viewFromNib:nibName];
  [nibView setFrame:placeholder.frame];
  [self.view insertSubview:nibView aboveSubview:placeholder];
  [placeholder removeFromSuperview];
  return nibView;
}

@end
skot
fonte
7

Em rápido

Na verdade, minha resolução para esse problema foi carregar a visualização em um viewDidLoad no meu CustonViewController onde eu queria usar a visualização dessa maneira:

myAccessoryView = NSBundle.mainBundle().loadNibNamed("MyAccessoryView", owner: self, options: nil)[0] as! MyAccessoryView

Não carregue a visualização em um loadView()método! O método loadView serve para carregar a visualização do seu ViewController personalizado.

kalafun
fonte
6

Eu também queria fazer algo semelhante, foi o que encontrei: (SDK 3.1.3)

Eu tenho um controlador de exibição A (de propriedade de um controlador Nav) que carrega o VC B em um pressionamento de botão:

No AViewController.m

BViewController *bController = [[BViewController alloc] initWithNibName:@"Bnib" bundle:nil];
[self.navigationController pushViewController:bController animated:YES];
[bController release];

Agora, o VC B tem sua interface do Bnib, mas quando um botão é pressionado, quero ir para um 'modo de edição' que possui uma interface de usuário separada de uma ponta diferente, mas não quero um novo VC para o modo de edição, Quero que a nova ponta seja associada ao meu B VC existente.

Então, em BViewController.m (no método de pressionar o botão)

NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:@"EditMode" owner:self options:nil];
UIView *theEditView = [nibObjects objectAtIndex:0];
self.editView = theEditView;
[self.view addSubview:theEditView];

Em outro botão, pressione (para sair do modo de edição):

[editView removeFromSuperview];

e estou de volta ao meu Bnib original.

Isso funciona bem, mas observe que meu EditMode.nib possui apenas um objeto de nível superior, um objeto UIView. Não importa se o proprietário do arquivo nesta ponta está definido como BViewController ou o NSObject padrão, MAS certifique-se de que a saída de exibição no proprietário do arquivo NÃO esteja definida para nada. Se for, então eu recebo uma falha exc_bad_access e o xcode prossegue para carregar quadros de pilha 6677 mostrando um método UIView interno repetidamente chamado ... então parece um loop infinito. (No entanto, o View Outlet está definido no meu Bnib original)

Espero que isto ajude.

Brynjar
fonte
Ler nas entrelinhas também me ajudou.
precisa saber é o seguinte
De acordo com as informações no link, você não deve manipular os controladores de exibição dessa maneira.
5230
6

Eu fiz uma categoria que eu gosto:

UIView+NibInitializer.h

#import <UIKit/UIKit.h>

@interface UIView (NibInitializer)
- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil;
@end

UIView+NibInitializer.m

#import "UIView+NibInitializer.h"

@implementation UIView (NibInitializer)

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    if (!nibNameOrNil) {
        nibNameOrNil = NSStringFromClass([self class]);
    }
    NSArray *viewsInNib = [[NSBundle mainBundle] loadNibNamed:nibNameOrNil
                                                        owner:self
                                                      options:nil];
    for (id view in viewsInNib) {
        if ([view isKindOfClass:[self class]]) {
            self = view;
            break;
        }
    }
    return self;
}

@end

Então, chame assim:

MyCustomView *myCustomView = [[MyCustomView alloc] initWithNibNamed:nil];

Use um nome de ponta se sua ponta tiver outro nome que não seja o nome da sua classe.

Para substituí-lo em suas subclasses por comportamento adicional, pode ser assim:

- (instancetype)initWithNibNamed:(NSString *)nibNameOrNil
{
    self = [super initWithNibNamed:nibNameOrNil];
    if (self) {
        self.layer.cornerRadius = CGRectGetHeight(self.bounds) / 2.0;
    }
    return self;
}
Logan
fonte
5

Para usuário Swift com opção designável:

  1. Crie uma UIViewsubclasse personalizada e um arquivo xib , que nomearemos após o próprio nome da classe: no nosso caso, MemeView. Dentro da classe Meme View, lembre-se de defini-la como designável com o@IBDesignable atributo antes da declaração da classe
  2. Lembre-se de definir o Proprietário do arquivo no xib com nossa UIViewsubclasse personalizada no painel do Indetity Inspector

    insira a descrição da imagem aqui

  3. No arquivo xib agora podemos construir nossa interface, criar restrições, criar saídas, ações etc. insira a descrição da imagem aqui

  4. Precisamos implementar alguns métodos em nossa classe personalizada para abrir o xib uma vez inicializado

    class XibbedView: UIView {

    weak var nibView: UIView!
    
    override convenience init(frame: CGRect) {
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        self.init(nibName: nibName)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    init(nibName: String) {
        super.init(frame: CGRectZero)
        let nibName = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last!
        let nib = loadNib(nibName)
        nib.frame = bounds
        nib.translatesAutoresizingMaskIntoConstraints = false
        addSubview(nib)
        nibView = nib
        setUpConstraints()
    }
    
    func setUpConstraints() {
        ["V","H"].forEach { (quote) -> () in
            let format = String(format:"\(quote):|[nibView]|")
            addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: ["nibView" : nibView]))
        }
    }
    
    func loadNib(name: String) -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: name, bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
    
        return view
    }

    }

  5. Em nossa classe personalizada, também podemos definir algumas propriedades inspecionáveis ​​para ter controle total sobre elas a partir do criador de interface

    @IBDesignable class MemeView: XibbedView {

    @IBInspectable var memeImage: UIImage = UIImage() {
        didSet {
            imageView.image = memeImage
        }
    }
    @IBInspectable var textColor: UIColor = UIColor.whiteColor() {
        didSet {
            label.textColor = textColor
        }
    }
    @IBInspectable var text: String = "" {
        didSet {
            label.text = text
        }
    }
    @IBInspectable var roundedCorners: Bool = false {
        didSet {
            if roundedCorners {
                layer.cornerRadius = 20.0
                clipsToBounds = true
            }
            else {
                layer.cornerRadius = 0.0
                clipsToBounds = false
            }
        }
    }
    
    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var imageView: UIImageView!

}

Poucos exemplos:
insira a descrição da imagem aqui insira a descrição da imagem aqui

Se precisarmos adicionar mais informações, a exibição enquanto é exibida dentro de um storyboard ou outro xib, para fazer isso, podemos implementar prepareForInterfaceBuilder(), esse método será executado apenas ao abrir o arquivo no construtor de interfaces. Se você fez tudo o que escrevi, mas nada está funcionando, é uma maneira de depurar uma exibição de sigle adicionando pontos de interrupção em sua implementação. Aqui está a hierarquia de visualizações. insira a descrição da imagem aqui
Ver hiearchy

Espero que isso ajude uma amostra completa pode ser baixada aqui

Andrea
fonte
O artigo completo pode ser visto no meu blog cloudintouch.it/2016/04/21/designable-xib-in-xcode-hell-yeah, mas eu já publiquei a parte relevante.
Andrea
let nib = loadNib(nibName)esta é uma instância do XibbedView, se você chamar `` `addSubview (nib)` ``, estará adicionando uma instância do XibbedView a outra instância do XibbedView?
William Hu
Eu concordo com este ponto. Quando você tenta vê-lo em "Hierarquia de exibição de depuração", parece que o XibbedView contém um XibbedView. Você pode ver isso.
William Hu
@WilliamHu Se você carregar o exemplo de exemplo, poderá ver que o MemeView.xib é apenas uma instância do UIView com várias sub-visualizações, o proprietário do arquivo é um MemeView que é uma subclasse do XibbedView. Assim, a única instância de XibbedView é apenas MemeView que carrega um arquivo xib onde a vista principal é apenas uma opinião
Andrea
Oh ok então. Eu vou testar. Obrigado!
William Hu
4

Eu achei este post de Aaron Hillegass (autor, instrutor, Cocoa ninja) muito esclarecedor. Mesmo se você não adotar a abordagem modificada para carregar arquivos NIB por meio de um inicializador designado, provavelmente obterá pelo menos uma melhor compreensão do processo que está acontecendo. Ultimamente, tenho usado esse método com muito sucesso!

Meltemi
fonte
Link está morto. Eu acredito que agora está localizado em bignerdranch.com/blog/…
joelliusp
3

A resposta anterior não leva em consideração uma alteração na estrutura NIB (XIB) que ocorreu entre 2.0 e 2.1 do iPhone SDK. O conteúdo do usuário agora começa no índice 0 em vez de 1.

Você pode usar a macro 2.1 que é válida para toda a versão 2.1 e posterior (ou seja, dois sublinhados antes do IPHONE:

 // Cited from previous example
 NSArray* nibViews =  [[NSBundle mainBundle] loadNibNamed:@"QPickOneView" owner:self options:nil];
 int startIndex;
 #ifdef __IPHONE_2_1
 startIndex = 0;
 #else
 startIndex = 1;
 #endif
 QPickOneView* myView = [ nibViews objectAtIndex: startIndex];
 myView.question = question;

Usamos uma técnica semelhante a esta para a maioria de nossas aplicações.

Barney

Barney Mattox
fonte
2
Barney: "A resposta anterior" não faz sentido no Stack Overflow porque as respostas mudam de posição com base na votação. Melhor se referir à "resposta de Fred" ou "resposta de Barney" ou "resposta de olie".
Olie
3

Eu tinha motivos para fazer a mesma coisa (carregando programaticamente uma visualização de um arquivo XIB), mas precisava fazer isso inteiramente a partir do contexto de uma subclasse de uma subclasse de a UIView(isto é, sem envolver o controlador de exibição de nenhuma maneira). Para fazer isso, criei este método utilitário:

+ (id) initWithNibName:(NSString *)nibName withSelf:(id)myself {

    NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:nibName
                                                    owner:myself options:nil];
    for (id object in bundle) {
        if ([object isKindOfClass:[myself class]]) {
            return object;
        }
    }  

    return nil;
} 

Então eu chamo isso do initWithFramemétodo da minha subclasse da seguinte forma:

- (id)initWithFrame:(CGRect)frame {

    self = [Utilities initWithNibName:@"XIB1" withSelf:self];
    if (self) {
        // Initialization code.
    }
    return self;
}

Publicado para interesse geral; se alguém encontrar algum problema sem fazê-lo dessa maneira, informe-me.

MusiGenesis
fonte
Eu implementei seu código, mas ele continua fora do escopo no init. Eu criei seu método estático em uma classe Utilitários. Criei arquivos h / m chamados MyView (subclassificados do UIView) e um xib com o mesmo nome. No IB, defino o proprietário dos arquivos xib E a exibição como "MyView". No depurador, está fora do escopo. Estou faltando alguma coisa no IB?
TigerCoding
Por favor, dê uma olhada na minha nova pergunta SO aqui: stackoverflow.com/questions/9224857/…
TigerCoding
self = [Utilitários initWithNibName: @ "XIB1" withSelf: self]; Você está passando por si mesmo quando ainda não está definido. Esse código não é o mesmo: self = [Utilities initWithNibName: @ "XIB1" withSelf: nil]; ?
Ken Van Hoeylandt
@ Javy: meu exemplo de código aqui pode não funcionar com o ARC - não testei com o ARC ativado.
MusiGenesis
3

Aqui está uma maneira de fazê-lo no Swift (atualmente escrevendo o Swift 2.0 no XCode 7 beta 5).

Na sua UIViewsubclasse que você definiu como "Classe personalizada" no Interface Builder, crie um método como este (minha subclasse é chamada RecordingFooterView):

class func loadFromNib() -> RecordingFooterView? {
    let nib = UINib(nibName: "RecordingFooterView", bundle: nil)
    let nibObjects = nib.instantiateWithOwner(nil, options: nil)
    if nibObjects.count > 0 {
        let topObject = nibObjects[0]
        return topObject as? RecordingFooterView
    }
    return nil
}

Então você pode simplesmente chamar assim:

let recordingFooterView = RecordingFooterView.loadFromNib()

Johannes
fonte
2

Nenhuma das respostas explica como criar o XIB independente que é a raiz desta pergunta. Não há Xcode 4opção para "Criar novo arquivo XIB".

Para fazer isso

1) Choose "New File..."
2) Choose the "User Interface" category under the iOS section
3) Choose the "View" item
4) You will then be prompted to choose an iPhone or iPad format

Isso pode parecer simples, mas pode economizar alguns minutos, pois a palavra "XIB" não aparece em lugar algum.

Whitneyland
fonte
1

@AVeryDev

6) Para anexar a vista carregada à vista do seu controlador de vista:

[self.view addSubview:myViewFromNib];

Presumivelmente, é necessário removê-lo da exibição para evitar vazamentos de memória.

Para esclarecer: o controlador de exibição possui vários IBOutlets, alguns dos quais estão conectados aos itens no arquivo de ponta original (como de costume), e alguns estão conectados aos itens na ponta carregada. Ambas as pontas têm a mesma classe de proprietário. A vista carregada sobrepõe a vista original.

Dica: defina como zero a opacidade da vista principal na ponta carregada, para não ocultar os itens da ponta original.

Matt
fonte
É bom observar, mas adicionar uma visualização como uma subvisão a remove automaticamente de seu pai anterior, para que não vaze.
averydev
1

Depois de passar muitas horas, formei a seguinte solução. Siga estas etapas para criar personalizado UIView.

1) Crie a classe myCustomView herdada do UIView .
insira a descrição da imagem aqui

2) Crie .xib com o nome myCustomView .
insira a descrição da imagem aqui

3) Altere a classe do UIView dentro do seu arquivo .xib, atribua a classe myCustomView lá.
insira a descrição da imagem aqui

4) Crie IBOutlets
insira a descrição da imagem aqui

5) Carregue .xib no myCustomView * customView . Use o seguinte código de exemplo.

myCustomView * myView = [[[NSBundle mainBundle] loadNibNamed:@"myCustomView" owner:self options:nil] objectAtIndex:0];
[myView.description setText:@"description"];

Nota: Para aqueles que ainda enfrentam problemas podem comentar, fornecerei o link do exemplo de projeto com myCustomView

Aamir
fonte
1

Versão mais curta:

RSFavoritePlaceholderView *favoritePlaceholderView = [[[NSBundle mainBundle] loadNibNamed:@"RSFavoritePlaceholderView" owner:self options:nil] firstObject];
Denis Kutlubaev
fonte
0

Eu tenho uma convenção de nomear xibs com exibições nelas iguais à exibição. O mesmo que se faria com um controlador de exibição. Então, não preciso escrever nomes de classe no código. Carrego um UIView a partir de um arquivo de ponta com o mesmo nome.

Exemplo para uma classe chamada MyView.

  • Crie um arquivo de ponta chamado MyView.xib no Interface Builder
  • No Construtor de Interface, adicione um UIView. Defina sua classe como MyView. Personalize com o conteúdo do seu coração, conecte variáveis ​​de instância do MyView a subviews que você deseja acessar posteriormente.
  • No seu código, crie um novo MyView como este:

    MyView * myView = [MyView nib_viewFromNibWithOwner: owner];

Aqui está a categoria para isso:

@implementation UIView (nib)

+ (id) nib_viewFromNib {
    return [self nib_viewFromNibWithOwner:nil];
}

+ (id) nib_viewFromNibWithOwner:(id)owner {
    NSString *className = NSStringFromClass([self class]);
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:className owner:owner options:nil];
    UIView *view = nil;
    for(UIView *v in nib) {
        if ([v isKindOfClass:[self class]]) {
            view = v;
            break;
        }
    }
    assert(view != nil && "View for class not found in nib file");
    [view nib_viewDidLoad];
    return view;
}

// override this to do custom setup
-(void)nib_viewDidLoad {

}

Em seguida, conectava botões com ações do controlador que estou usando e definia as coisas nos rótulos usando as saídas na minha subclasse de exibição personalizada.

n13
fonte
0

Para carregar programaticamente uma visualização de uma ponta / ponta no Swift 4:

// Load a view from a Nib given a placeholder view subclass
//      Placeholder is an instance of the view to load.  Placeholder is discarded.
//      If no name is provided, the Nib name is the same as the subclass type name
//
public func loadViewFromNib<T>(placeholder placeholderView: T, name givenNibName: String? = nil) -> T {

    let nib = loadNib(givenNibName, placeholder: placeholderView)
    return instantiateView(fromNib: nib, placeholder: placeholderView)
}

// Step 1: Returns a Nib
//
public func loadNib<T>(_ givenNibName: String? = nil, placeholder placeholderView: T) -> UINib {
    //1. Load and unarchive nib file
    let nibName = givenNibName ?? String(describing: type(of: placeholderView))

    let nib = UINib(nibName: nibName, bundle: Bundle.main)
    return nib
}

// Step 2: Instantiate a view given a nib
//
public func instantiateView<T>(fromNib nib: UINib, placeholder placeholderView: T) -> T {
    //1. Get top level objects
    let topLevelObjects = nib.instantiate(withOwner: placeholderView, options: nil)

    //2. Have at least one top level object
    guard let firstObject = topLevelObjects.first else {
        fatalError("\(#function): no top level objects in nib")
    }

    //3. Return instantiated view, placeholderView is not used
    let instantiatedView = firstObject as! T
    return instantiatedView
}
Eng Yew
fonte
-1

Acabei adicionando uma categoria ao UIView para isso:

 #import "UIViewNibLoading.h"

 @implementation UIView (UIViewNibLoading)

 + (id) loadNibNamed:(NSString *) nibName {
    return [UIView loadNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1];
 }

 + (id) loadNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag {
    NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
    if(!nib) return nil;
    UIView * target = nil;
    for(UIView * view in nib) {
        if(view.tag == tag) {
            target = [view retain];
            break;
        }
    }
    if(target && [target respondsToSelector:@selector(viewDidLoad)]) {
        [target performSelector:@selector(viewDidLoad)];
    }
    return [target autorelease];
 }

 @end

explicação aqui: o viewcontroller é menos view loading no ios e no mac

gngrwzrd
fonte
Hrm. O que há com a tag?
n13 9/12/13
para saber qual objeto de nível superior manter. no momento em que era necessário, mas você provavelmente poderia retirar as reservas para cumprir com o ARC agora.
Gngrwzrd