Maneira correta de carregar um Nib para uma subclasse UIView

98

Estou ciente de que essa pergunta já foi feita antes, mas as respostas são contraditórias e estou confuso, então, por favor, não me xingue.

Eu quero ter uma UIViewsubclasse reutilizável em todo o meu aplicativo. Eu quero descrever a interface usando um arquivo nib.

Agora, digamos que seja uma visualização do indicador de carregamento com um indicador de atividade. Eu gostaria em algum evento para instanciar esta visão e animar em uma visão do controlador de visão. Eu poderia descrever a interface do modo de exibição sem problemas de forma programática, criando os elementos programaticamente e definindo seu quadro dentro de um método init etc.

Como posso fazer isso usando uma ponta? Manter o tamanho fornecido no construtor de interface sem ter que definir um quadro.

Consegui fazer assim, mas tenho certeza de que está errado (é apenas uma vista com um seletor):

 - (id)initWithDataSource:(NSDictionary *)dataSource {
        self = [super init];
        if (self){
            self = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@", [self class]] owner:self options:nil] objectAtIndex:0];
            self.pickerViewData = dataSource;
            [self configurePickerView];
        }
        return self;
    }

Mas estou sobrescrevendo a si mesmo e quando o instanciar:

FSASelectView *selectView = [[FSASelectView alloc] initWithDataSource:selectViewDictionary];
    selectView.delegate = self;

    selectView.frame = CGRectMake(0, self.view.bottom + 50, [FSASelectView width], [FSASelectView height]);

Tenho que definir manualmente o quadro em vez de retirá-lo do IB.

EDIT: Eu quero criar este modo de exibição personalizado em um controlador de exibição e ter acesso para controlar os elementos do modo de exibição. Eu não quero um novo controlador de visualização.

obrigado

EDITAR: Não sei se essa é a prática recomendada, tenho certeza que não, mas foi assim que fiz:

FSASelectView *selectView = [[[NSBundle mainBundle] loadNibNamed:[NSString stringWithFormat:@"%@",[FSASelectView class]] owner:self options:nil] objectAtIndex:0];
    selectView.delegate = self;
    [selectView configurePickerViewWithData:ds];
    selectView.frame = CGRectMake(0, self.view.bottom + 50, selectView.width, selectView.height);
    selectView.alpha = 0.9;
    [self.view addSubview:selectView];
    [UIView animateWithDuration: 0.25 delay: 0 options:UIViewAnimationOptionAllowUserInteraction |UIViewAnimationOptionCurveEaseInOut animations:^{
                            selectView.frame = CGRectMake(0, self.view.bottom - selectView.height, selectView.width, selectView.height);
                            selectView.alpha = 1;
                        } completion:^(BOOL finished) {
                        }];

A prática correta ainda é necessária

Isso deveria ter sido feito usando um controlador de visualização e init com o nome nib? Devo ter definido a ponta em algum método de inicialização UIView no código? Ou o que eu fiz está certo?

Adam Waite
fonte
Mas a primeira linha de código não é quase a mesma que você usou na pergunta original em initWithDataSource ? De qualquer forma, isso funcionará mesmo se você atribuir o proprietário como 'Nil'.
Rakesh de
É importante notar que hoje em dia em 99,99999% dos casos, seria apenas usar visualizações de contêiner , que agora são usadas em todos os lugares e sempre no iOS. artigo como fazer
Fattie
observação lateral que você pode substituir [NSString stringWithFormat: @ "% @", [FSASelectView class]] por NSStringFromClass ([FSASelectView class])
naomimichiko

Respostas:

132
MyViewClass *myViewObject = [[[NSBundle mainBundle] loadNibNamed:@"MyViewClassNib" owner:self options:nil] objectAtIndex:0]

Estou usando isso para inicializar as visualizações personalizadas reutilizáveis ​​que tenho.


Observe que você pode usar "firstObject" no final, é um pouco mais limpo. "firstObject" é um método útil para NSArray e NSMutableArray.

Aqui está um exemplo típico de carregamento de um xib para usar como cabeçalho de tabela. Em seu arquivo YourClass.m

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    return [[NSBundle mainBundle] loadNibNamed:@"TopArea" owner:self options:nil].firstObject;
}

Normalmente, no TopArea.xib, você clica em Proprietário do arquivo e define o proprietário do arquivo como SuaClasse . Então, na verdade, em YourClass.h você teria propriedades IBOutlet. NoTopArea.xib , você pode arrastar os controles para essas tomadas.

Não se esqueça disso TopArea.xib, você pode ter que clicar no próprio View e arrastá-lo para algum outlet, para ter controle sobre ele, se necessário. (Uma dica muito útil é que quando você está fazendo isso para linhas de células da tabela, você absolutamente tem que fazer isso - você deve conectar a própria visualização à propriedade relevante em seu código.)

Premsuraj
fonte
não há método initWithNibName: para um UIView, é apenas para UIViewController
meronix
Isso é para um controlador de visualização, quero usar um UIView e ter o controlador que o cria para possuí-lo.
Adam Waite
Desculpe, copiei o código errado na minha pressa, atualizei a resposta, dê uma olhada.
Premsuraj
Vou marcar isso correto. Não sei se é a melhor prática, mas funciona.
Adam Waite
@Joe Blow Desculpe pelo solavanco, mas por que haveria de fazer isso: "Não se esqueça que em TopArea.xib, você pode ter que clicar no próprio View e arrastá-lo para alguma saída, para ter controle sobre ele , se necessário." Em vez disso, você poderia apenas usar myViewObject para acessar a visualização subjacente, pois é o próprio UIView? Por que há necessidade de uma propriedade?
user1686342
39

Se você deseja manter seu CustomViewe seu xibindependente de File's Owner, siga estas etapas

  • Deixe o File's Ownercampo vazio.
  • Clique na visualização real no xibarquivo de seu CustomViewe defina Custom Classcomo CustomView(nome de sua classe de visualização personalizada)
  • Adicione IBOutletno .harquivo de sua visualização personalizada.
  • No .xibarquivo da sua visualização personalizada, clique em visualizar e entre Connection Inspector. Aqui você encontrará todos os seus IBOutlets que definir no .harquivo
  • Conecte-os com suas respectivas visualizações.

no .marquivo de sua CustomViewclasse, substitua o initmétodo conforme a seguir

-(CustomView *) init{
    CustomView *result = nil;
    NSArray* elements = [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner:self options: nil];
    for (id anObject in elements)
    {
        if ([anObject isKindOfClass:[self class]])
        {
            result = anObject;
            break;
        }
    }
    return result;
}

Agora, quando você quiser carregar o seu CustomView, use a seguinte linha de código [[CustomView alloc] init];

Resp
fonte
1
No iOS 9, esta é a única solução listada nesta página que realmente funciona para mim (as outras soluções causam travamento).
Lifjoy de
Observe que essa abordagem não é compatível com o carregamento de sua visualização personalizada de um XIB existente, pois ela não chama -init. Em vez disso, ele chamará initWithFrame:e -awakeFromNib. Se você escrever o mesmo código para carregar sua visualização como no -initmétodo, você entrará em um loop infinito.
Dustt
25

Siga os seguintes passos

  1. Crie uma classe chamada MyView .h / .m do tipo UIView.
  2. Crie um xib com o mesmo nome MyView.xib.
  3. Agora mude a classe File Owner UIViewControllerde NSObjectem xib. Veja a imagem abaixo insira a descrição da imagem aqui
  4. Conecte a Visualização do Proprietário do Arquivo à sua Visualização. Veja a imagem abaixo insira a descrição da imagem aqui

  5. Altere a classe da sua Visualização para MyView. O mesmo que 3.

  6. Os controles de posição criam IBOutlets.

Aqui está o código para carregar o View:

UIViewController *controller=[[UIViewController alloc] initWithNibName:@"MyView" bundle:nil];
MyView* view=(MyView*)controller.view;
[self.view addSubview:myview];

Espero que ajude.

Esclarecimento :

UIViewControlleré usado para carregar seu xib e a visualização que o UIViewControllerpossui é realmente a MyViewque você atribuiu no MyView xib.

Demo Fiz uma demonstração aqui

iphônico
fonte
Por que as pessoas estão votando errado nisso? Ainda não tentei, mas parece que não vai fazer o que eu quero.
Adam Waite
Eu tinha votado contra, mas isso é porque eu li mal sua resposta (pensei que você estava atribuindo uma subclasse UIView como o proprietário do arquivo). Me desculpe por isso.
Rakesh de
2
Em seu arquivo xib, ignore o proprietário dos arquivos. Então você pode evitar ter que criar um UIViewController apenas fazendo um MyView * view = [[UINib instanciarWithOwner: nil options: nil] lastObject];
LightningStryk
1
@LightningStryk Sim, claro, mas é apenas outra maneira de fazer isso.
iphonic
1
O objetivo é criar uma subclasse UIView reutilizável, não usar um UIViewController para esse propósito.
arielyz de
11

Respondendo minha própria pergunta cerca de 2 anos depois aqui, mas ...

Ele usa uma extensão de protocolo para que você possa fazer isso sem nenhum código extra para todas as classes.

/*

Prerequisites
-------------
- In IB set the view's class to the type hook up any IBOutlets
- In IB ensure the file's owner is blank

*/

public protocol CreatedFromNib {
    static func createFromNib() -> Self?
    static func nibName() -> String?
}

extension UIView: CreatedFromNib { }

public extension CreatedFromNib where Self: UIView {

    public static func createFromNib() -> Self? {
        guard let nibName = nibName() else { return nil }
        guard let view = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil).last as? Self else { return nil }
        return view
    }

    public static func nibName() -> String? {
        guard let n = NSStringFromClass(Self.self).componentsSeparatedByString(".").last else { return nil }
        return n
    }
}

// Usage:
let myView = MyView().createFromNib()
Adam Waite
fonte
8

Em Swift:

Por exemplo, o nome da sua classe personalizada é InfoView

No início, você cria arquivos InfoView.xibe InfoView.swiftassim:

import Foundation
import UIKit

class InfoView: UIView {
    class func instanceFromNib() -> UIView {
    return UINib(nibName: "InfoView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
}

Em seguida, defina File's Ownera UIViewControllergostar deste:

insira a descrição da imagem aqui

Renomeie seu Viewpara InfoView:

insira a descrição da imagem aqui

Clique com o botão direito File's Ownere conecte seu viewcampo a InfoView:

insira a descrição da imagem aqui

Certifique-se de que o nome da classe seja InfoView:

insira a descrição da imagem aqui

E depois disso, você pode adicionar a ação ao botão em sua classe personalizada sem nenhum problema:

insira a descrição da imagem aqui

E o uso desta classe personalizada em seu MainViewController:

func someMethod() {
    var v = InfoView.instanceFromNib()
    v.frame = self.view.bounds
    self.view.addSubview(v)
}
vkalit
fonte
2

Bem, você pode inicializar o xib usando um controlador de visualização e usar viewController.view. ou faça do jeito que você fez. Apenas fazendo uma UIViewsubclasse como o controlador paraUIView é uma má ideia.

Se você não tiver nenhuma saída de sua visualização personalizada, você pode usar diretamente um UIViewController classe para inicializá-la.

Atualização: No seu caso:

UIViewController *genericViewCon = [[UIViewController alloc] initWithNibName:@"CustomView"];
//Assuming you have a reference for the activity indicator in your custom view class
CustomView *myView = (CustomView *)genericViewCon.view;
[parentView addSubview:myView];
//And when necessary
[myView.activityIndicator startAnimating]; //or stop

Caso contrário, você terá que fazer um personalizado UIViewController(torná-lo o proprietário do arquivo para que os pontos de venda sejam devidamente conectados).

YourCustomController *yCustCon = [[YourCustomController alloc] initWithNibName:@"YourXibName"].

Onde você quiser adicionar a visualização, você pode usar.

[parentView addSubview:yCustCon.view];

No entanto, passar o outro controlador de visão (já sendo usado por outra visão) como o proprietário enquanto carrega o xib não é uma boa ideia, pois a propriedade de visão do controlador será alterada e quando você quiser acessar a visão original, você não tem uma referência a ele.

EDITAR: Você enfrentará esse problema se tiver configurado seu novo xib com o proprietário do arquivo como a mesma UIViewControllerclasse principal e vinculado a propriedade da visualização à nova visualização do xib.

ie;

  • YourMainViewController - gerencia - mainView
  • CustomView - precisa carregar do xib como e quando necessário.

O código abaixo irá causar confusão mais tarde, se você escrevê-lo dentro do view carregou YourMainViewController. Isso é porque a self.viewpartir deste ponto irá se referir à sua visão personalizada

-(void)viewDidLoad:(){
  UIView *childView= [[[NSBundle mainBundle] loadNibNamed:@"YourXibName" owner:self options:nil] objectAtIndex:0];
}
Rakesh
fonte
Ok, então basicamente, a prática recomendada afirma que cada UIView deve ter um controlador associado?
Adam Waite
Não necessariamente. Mas ao carregar uma visualização de um xib separado, essa seria a maneira sem problemas. A documentação da apple pré-IOS5 diz que um controlador de visualização não deve ser usado para controlar mais de uma cena (xib) e vice-versa.
Rakesh de
O xib é na verdade apenas do tamanho de uma visualização de alerta
Adam Waite,
Eu pessoalmente acho que depende da complexidade do code / xib se você tem que criar um novo controlador ou não. Se você conseguir lidar com os problemas causados ​​com sucesso e não estiver olhando para futuras modificações, isso não será um problema. Mas criar um controlador de visualização separado resolveria muitos problemas para você. E como no seu caso é apenas um indicador de atividade, você pode usar um objeto UIViewController (como mencionado na resposta) facilmente. Vou atualizar minha resposta para fazer isso de acordo.
Rakesh de
1
Portanto, posso desenhar a interface no construtor de interface em vez de fazê-lo programaticamente. Não é um problema fazer isso programaticamente, eu só quero saber como criar uma subclasse de um UIView e dar-lhe uma ponta! Isso não deve ser difícil.
Adam Waite