iphone - framework - xcode tutorial




Como carregar um UIView usando um arquivo de nib criado com o Interface Builder (16)

@AVeryDev

6) Para anexar a visualização carregada à visão do controlador de exibição:

[self.view addSubview:myViewFromNib];

Presumivelmente, é necessário removê-lo da vista para evitar vazamentos de memória.

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

Dica: defina a opacidade da visualização principal na ponta carregada para zero, então ela não ocultará os itens da ponta original.

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

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

O jeito que estou fazendo é:

  1. Crie o objeto Question1View como uma subclasse UIView , definindo alguns IBOutlets .
  2. Crie (usando o Interface Builder) o Question1View.xib (AQUI ESTÁ ONDE MEU PROBLEMA É PROVAVELMENTE ). Eu defino o UIViewController e o UIView para serem da classe Question1View.
  3. Eu vinculo as saídas com o componente da visão (usando IB).
  4. Eu sobrescrevo o initWithNib do meu QuestionManagerViewController para 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:

2009-05-14 15: 05: 37.152 iMobiDines [17148: 20b] *** Terminar aplicativo devido a exceção não- NSInternalInconsistencyException ' NSInternalInconsistencyException ', razão: ' -[UIViewController _loadViewFromNibNamed:bundle:] carregou a ponta "Question1View", mas a saída de exibição não foi definido.

Tenho certeza de que há uma maneira de carregar a visualização usando o arquivo nib, sem precisar criar uma classe viewController.


A resposta anterior não leva em conta 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 todas as versões 2.1 e posteriores (são 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;

Nós usamos uma técnica semelhante a esta para a maioria das nossas aplicações.

Barney


Depois de passar muitas horas, eu saí seguindo a solução. Siga estas etapas para criar o UIView personalizado.

1) Crie a classe myCustomView herdada do UIView .

2) Crie o .xib com o nome myCustomView .

3) Mude a classe de UIView dentro do seu arquivo .xib, atribua a classe myCustomView lá.

4) Criar IBOutlets

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

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

Nota: Para aqueles que ainda enfrentam problema pode comentar, vou fornecer-lhes link de projeto de amostra com myCustomView


Esta é uma ótima pergunta (+1) e as respostas foram quase úteis;) Desculpe pessoal, mas eu tive um monte de tempo trabalhando duro, embora tanto Gonso quanto AVeryDev dessem boas dicas. Espero que esta resposta ajude os outros.

MyVC é o controlador de visualizaçã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 você desejar.
  • Em MyVC.h, tem

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

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

    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 truque para mim foi: as dicas nas outras respostas carregaram minha visão do xib, mas NÃO substitui a visão em MyVC (duh!) - eu tive que trocar isso sozinho.

Além disso, para obter acesso aos métodos do mySubview , a propriedade view no arquivo .xib deve ser definida como MySubview . Caso contrário, volta como um antigo UIView .

Se há uma maneira de carregar o mySubview diretamente de seu próprio xib, isso mySubview , mas isso me levou aonde eu precisava estar.


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, ligue assim:

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

Use um nome de bico se a sua ponta tiver um nome diferente do nome da sua classe.

Para substituí-lo em suas subclasses por comportamento adicional, ele pode ter esta aparência:

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

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

Eu tenho um controlador de visualização A (próprio de um controlador Nav) que carrega o VC B em um botão:

Em 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, eu quero ir para um 'modo de edição' que tem uma UI separada de uma ponta diferente, mas eu não quero um novo VC para o modo de edição, Eu quero que o novo bico seja associado ao meu B VC existente.

Então, no 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];

Então, 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 note que meu EditMode.nib tem apenas 1 obj de nível superior, um obj do UIView. Não importa se o proprietário do arquivo nesta ponta é definido como BViewController ou o padrão NSObject, mas verifique se a tomada de exibição no proprietário do arquivo não está definida para nada. Se for, então eu recebo uma falha exc_bad_access e o xcode continua carregando 6677 quadros de pilha mostrando um método UIView interno chamado repetidamente ... então parece um loop infinito. (The View Outlet está definido no meu Bnib original no entanto)

Espero que isto ajude.


Eu tinha motivos para fazer a mesma coisa (carregar programaticamente uma visão de um arquivo XIB), mas eu precisava fazer isso inteiramente a partir do contexto de uma subclasse de uma subclasse de um UIView (ou seja, sem envolver o controlador de visualização de forma alguma). Para fazer isso, criei este método de 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 do método initWithFrame da minha subclasse assim:

- (id)initWithFrame:(CGRect)frame {

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

Postado para interesse geral; Se alguém vir algum problema sem fazê-lo desta maneira, por favor me avise.


Eu usaria o UINib para instanciar um UIView personalizado para 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. Neste caso, é o primeiro elemento.


Isso é algo que deveria ser mais fácil. Acabei estendendo o UIViewController e adicionando um loadNib:inPlaceholder: selector. Agora eu posso dizer

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

Aqui está o código para a categoria (faz o mesmo rigamarole como 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

Não tenho certeza do que algumas das respostas estão falando, mas preciso colocar essa resposta aqui para quando eu 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 visã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];
} 

Isto supõe que você tem uma subclasse UIView chamada Blah , uma ponta chamada Blah que contém um UIView que tem sua classe definida como 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 Swift

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")
    }
}

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

  1. Crie seu UIView com o IBOutlet s que você precisa.
  2. Crie o xib em IB, projete para você gostando e faça um link assim: O dono do arquivo é da classe UIViewController (nenhuma subclasse customizada, mas a "real"). A visualização do proprietário do arquivo é conectada à visualização principal e sua classe é declarada como a da etapa 1).
  3. Conecte seus controles com os IBOutlet s.
  4. O DynamicViewController pode executar sua lógica para decidir qual view / xib carregar. Uma vez que tenha feito a decisão, no método loadView , coloque algo assim:

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

É isso aí!

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

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


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

  1. Crie uma subclasse personalizada do UIView e um arquivo xib , que nomearemos de acordo com o nome da nossa própria classe: no nosso caso, o MemeView. Dentro da classe Meme View, lembre-se de defini-lo como designável com o atributo @IBDesignable antes da declaração de classe
  2. Rember para definir o proprietário do arquivo no xib com nossa subclasse UIView personalizada no painel Indetity Inspector

  3. No arquivo xib, agora podemos construir nossa interface, fazer restrições, criar tomadas, ações etc.

  4. Precisamos implementar alguns métodos para nossa classe personalizada para abrir o xib depois de 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 de inspeção para ter controle total sobre elas a partir do construtor 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!

}

Alguns exemplos:

Se precisarmos adicionar mais informações à exibição enquanto é exibido dentro de um storyboard ou outro xib, para fazer isso, podemos implementar o prepareForInterfaceBuilder() , esse método será executado apenas durante a abertura do arquivo no construtor de interface. Se você fez tudo o que escrevi, mas nada está funcionando, é uma maneira de depurar uma visualização sigle adicionando pontos de interrupção em sua implementação.
Aqui está a hierarquia de visualizações.

Espero que isso ajude uma amostra completa pode ser baixada here


Versão mais curta:

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

Você não deve estar configurando a classe do seu controlador de visualização para ser uma subclasse do UIView no Interface Builder. Isso definitivamente é pelo menos parte do seu problema. Deixe isso como UIViewController , alguma subclasse dele ou alguma outra classe customizada que você tenha.

Quanto a carregar apenas uma visão de um xib, eu estava sob a suposição de que você tinha que ter algum tipo de view controller (mesmo se ele não estender o UIViewController , que pode ser muito pesado para suas necessidades) definido como o proprietário do arquivo em Interface Builder se você quiser usá-lo para definir sua interface. Eu fiz uma pequena pesquisa para confirmar isso também. Isso ocorre porque, do contrário, não haveria nenhuma maneira de acessar qualquer um dos elementos da interface no UIView , nem haveria uma maneira de ter seus próprios métodos no código acionados por eventos.

Se você usa um UIViewController como o proprietário do seu arquivo para suas views, você pode simplesmente usar initWithNibName:bundle: para carregá-lo e recuperar o objeto do view controller. No IB, certifique-se de definir a saída de view para a exibição com sua interface no xib. Se você usar algum outro tipo de objeto como proprietário do seu arquivo, você precisará usar o NSBundle loadNibNamed:owner:options: para carregar a ponta, passando uma instância do proprietário do arquivo para o método. Todas as suas propriedades serão definidas corretamente de acordo com as saídas definidas no IB.


Em rápida

Na verdade, a minha resolução para este problema foi, para carregar a visão em um viewDidLoad no meu CustonViewController onde eu queria usar a visão assim:

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

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


Acabei de adicionar 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: viewcontroller é menos visualização de carregamento no ios & mac





interface-builder