Modo corretto per caricare un pennino per una sottoclasse UIView


98

Sono consapevole che questa domanda è stata posta in passato, ma le risposte sono contraddittorie e sono confuso, quindi per favore non incendiarmi.

Voglio avere una UIViewsottoclasse riutilizzabile in tutta la mia app. Voglio descrivere l'interfaccia usando un file pennino.

Supponiamo ora che sia una visualizzazione dell'indicatore di caricamento con un indicatore di attività. Vorrei su un evento per creare un'istanza di questa vista e animarla nella vista di un controller di visualizzazione. Potrei descrivere l'interfaccia della vista senza problemi a livello di codice, creando gli elementi in modo programmatico e impostando la loro cornice all'interno di un metodo di inizializzazione ecc.

Come posso farlo usando un pennino però? Mantenere le dimensioni fornite in Interface Builder senza dover impostare un frame.

Sono riuscito a farlo in questo modo, ma sono sicuro che sia sbagliato (è solo una vista con un selettore al suo interno):

 - (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;
    }

Ma sovrascrivo me stesso e quando ne creo un'istanza:

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

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

Devo impostare manualmente la cornice piuttosto che averla ripresa da IB.

EDIT: voglio creare questa visualizzazione personalizzata in un controller di visualizzazione e avere accesso per controllare gli elementi della visualizzazione. Non voglio un nuovo controller di visualizzazione.

Grazie

EDIT: Non so se questa sia la migliore pratica, sono sicuro che non lo sia, ma è così che l'ho fatto:

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) {
                        }];

La pratica corretta voleva ancora

Avrebbe dovuto essere fatto usando un controller di visualizzazione e un init con il nome del pennino? Avrei dovuto impostare il pennino in qualche metodo di inizializzazione UIView nel codice? O va bene quello che ho fatto?


Ma la prima riga di codice non è quasi la stessa di quella che hai usato nella domanda originale initWithDataSource? Comunque questo funzionerà anche se dai il proprietario come "Nullo".
Rakesh

Vale la pena notare che in questi giorni nel 99,99999% dei casi, si utilizzerebbero solo le visualizzazioni del contenitore , che ora vengono utilizzate ovunque e sempre in iOS. articolo
illustrativo

nota a margine che puoi sostituire [NSString stringWithFormat: @ "% @", [FSASelectView class]] con NSStringFromClass ([FSASelectView class])
naomimichiko

Risposte:


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

Lo sto usando per inizializzare le visualizzazioni personalizzate riutilizzabili che ho.


Nota che puoi usare "firstObject" alla fine, è un po 'più pulito. "firstObject" è un metodo pratico per NSArray e NSMutableArray.

Ecco un tipico esempio di caricamento di un xib da utilizzare come intestazione di tabella. Nel tuo file YourClass.m

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

Normalmente, in TopArea.xib, fai clic su Proprietario del file e imposta il proprietario del file su YourClass . Quindi in realtà in YourClass.h avresti le proprietà IBOutlet. In TopArea.xib, puoi trascinare i controlli in tali punti vendita.

Non dimenticare che in TopArea.xib, potresti dover fare clic su Visualizza stesso e trascinarlo in qualche presa, in modo da averne il controllo, se necessario. (Un suggerimento molto utile è che quando lo fai per le righe delle celle di una tabella, devi assolutamente farlo: devi connettere la vista stessa alla proprietà pertinente nel tuo codice.)


non c'è initWithNibName: metodo per un UIView, è solo per UIViewController
meronix

Questo è per un controller di visualizzazione, voglio usare un UIView e avere il controller che lo crea per possederlo.
Adam Waite

Mi dispiace, ho copiato in fretta il codice sbagliato, ho aggiornato la risposta, dai un'occhiata.
Premsuraj

Lo contrassegno come corretto. Non so se sia la migliore pratica ma funziona.
Adam Waite

@Joe Blow Scusa per l'urto, ma perché dovresti farlo: "Non dimenticare che in TopArea.xib, potresti dover fare clic sulla vista stessa e trascinarla in una presa, in modo da averne il controllo , se necessario." Potresti invece usare semplicemente myViewObject per accedere alla vista sottostante in quanto è una UIView stessa? Perché c'è bisogno di una proprietà?
user1686342

39

Se vuoi mantenere il tuo CustomViewe il suo xibindipendente File's Owner, segui questi passaggi

  • Lascia il File's Ownercampo vuoto.
  • Fai clic sulla visualizzazione effettiva nel xibtuo file CustomViewe impostala Custom Classcome CustomView(nome della classe di visualizzazione personalizzata)
  • Aggiungere IBOutletnel .hfile della visualizzazione personalizzata.
  • Nel .xibfile della tua visualizzazione personalizzata, fai clic su Visualizza ed entra Connection Inspector. Qui troverai tutti i tuoi IBOutlet che definisci nel .hfile
  • Collegali con la loro rispettiva vista.

nel .mfile della tua CustomViewclasse, sovrascrivi il initmetodo come segue

-(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;
}

Ora quando vuoi caricare il tuo CustomView, usa la seguente riga di codice [[CustomView alloc] init];


1
A partire da iOS 9, questa è l'unica soluzione elencata in questa pagina che funziona effettivamente per me (le altre soluzioni causano arresti anomali).
lifjoy

Tieni presente che questo approccio non è compatibile con il caricamento della visualizzazione personalizzata da un XIB esistente poiché non chiama -init. Invece chiamerà initWithFrame:e -awakeFromNib. Se scrivi lo stesso codice per caricare la tua vista come nel -initmetodo, entrerai in un ciclo infinito.
Dustt

25

Segui i passaggi seguenti

  1. Crea una classe denominata MyView .h / .m di tipo UIView.
  2. Crea un xib con lo stesso nome MyView.xib.
  3. Ora cambia la classe del proprietario del file in UIViewControllerda NSObjectin xib. Guarda l'immagine qui sotto inserisci qui la descrizione dell'immagine
  4. Collega la visualizzazione del proprietario del file alla visualizzazione. Guarda l'immagine qui sotto inserisci qui la descrizione dell'immagine

  5. Cambia la classe della tua vista in MyView. Uguale a 3.

  6. I controlli di posizione creano IBOutlet.

Ecco il codice per caricare la vista:

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

Spero che sia d'aiuto.

Chiarimento :

UIViewControllerviene utilizzato per caricare il tuo xib e la vista che UIViewControllerha è effettivamente quella MyViewche hai assegnato in MyView xib ..

Demo Ho fatto una demo grab qui


Perché le persone votano questo, sbagliato? Non ho ancora provato, ma sembra che non farà quello che voglio.
Adam Waite

Avevo votato negativamente, ma questo è perché ho letto male la tua risposta (pensavo stessi assegnando una sottoclasse UIView come proprietario del file). Mi dispiace per quello.
Rakesh

2
Nel tuo file xib ignora il proprietario del file. Quindi puoi evitare di dover creare un UIViewController semplicemente eseguendo un MyView * view = [[UINib instantiateWithOwner: nil options: nil] lastObject];
LightningStryk

1
@LightningStryk Sì, certo, ma è solo un altro modo per farlo.
iphonic

1
È creare una sottoclasse UIView riutilizzabile, non utilizzare un UIViewController a tale scopo.
arielyz

11

Rispondendo alla mia domanda su 2 o qualcosa anni dopo qui ma ...

Utilizza un'estensione di protocollo in modo da poterlo fare senza alcun codice aggiuntivo per tutte le classi.

/*

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()

8

In Swift:

Ad esempio, il nome della tua classe personalizzata è InfoView

All'inizio, crei file InfoView.xibe in InfoView.swiftquesto modo:

import Foundation
import UIKit

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

Quindi impostare File's Ownerin UIViewControllerquesto modo:

inserisci qui la descrizione dell'immagine

Rinomina il tuo Viewin InfoView:

inserisci qui la descrizione dell'immagine

Fai clic con il pulsante destro del mouse File's Ownere collega il tuo viewcampo con InfoView:

inserisci qui la descrizione dell'immagine

Assicurati che il nome della classe sia InfoView:

inserisci qui la descrizione dell'immagine

E dopo questo puoi aggiungere l'azione al pulsante nella tua classe personalizzata senza alcun problema:

inserisci qui la descrizione dell'immagine

E l'utilizzo di questa classe personalizzata nel tuo MainViewController:

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

2

Bene, potresti inizializzare xib usando un controller di visualizzazione e usare viewController.view. o fallo nel modo in cui l'hai fatto. Solo creare una UIViewsottoclasse come controller per UIViewè una cattiva idea.

Se non si dispone di punti vendita dalla visualizzazione personalizzata, è possibile utilizzare direttamente una UIViewControllerclasse per inizializzarla.

Aggiornamento: nel tuo 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

Altrimenti devi fare un custom UIViewController(per renderlo come proprietario del file in modo che le prese siano correttamente cablate).

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

Ovunque tu voglia aggiungere la vista puoi usare.

[parentView addSubview:yCustCon.view];

Tuttavia, il passaggio di un altro controller di visualizzazione (già utilizzato per un'altra visualizzazione) come proprietario durante il caricamento di xib non è una buona idea poiché la proprietà di visualizzazione del controller verrà modificata e quando si desidera accedere alla visualizzazione originale, non lo farai avere un riferimento ad esso.

EDIT: Affronterai questo problema se hai impostato il tuo nuovo xib con il proprietario del file come la stessa UIViewControllerclasse principale e hai collegato la proprietà view alla nuova vista xib.

ie;

  • YourMainViewController - gestisce - mainView
  • CustomView: deve essere caricato da xib come e quando richiesto.

Il codice seguente causerà confusione in seguito, se lo scrivi all'interno della vista è stato caricato YourMainViewController. Questo perché self.viewda questo punto in poi farà riferimento alla tua visualizzazione personalizzata

-(void)viewDidLoad:(){
  UIView *childView= [[[NSBundle mainBundle] loadNibNamed:@"YourXibName" owner:self options:nil] objectAtIndex:0];
}

Ok, quindi fondamentalmente, la migliore pratica afferma che ogni UIView dovrebbe avere un controller associato?
Adam Waite

Non necessariamente. Ma quando si carica una vista da un xib separato, questo sarebbe il modo senza problemi. I documenti Apple pre-IOS5 dicono che un controller di visualizzazione non dovrebbe essere usato per controllare più di una scena (xib) e viceversa.
Rakesh

Lo xib è in realtà solo la dimensione di una visualizzazione di allerta
Adam Waite

Personalmente ritengo che dipenda dalla complessità del codice / xib se è necessario creare un nuovo controller o meno. Se riesci a gestire i problemi causati con successo e se non stai guardando le modifiche future, non è un problema. Ma la creazione di un controller di visualizzazione separato risolverebbe molti problemi per te. E poiché nel tuo caso è solo un indicatore di attività, puoi utilizzare facilmente un oggetto UIViewController (come menzionato nella risposta). Aggiornerò la mia risposta per farlo di conseguenza.
Rakesh

1
Quindi posso disegnare l'interfaccia nel generatore di interfacce piuttosto che farlo a livello di programmazione. Non è un problema farlo in modo programmatico, voglio solo sapere come sottoclassare una UIView e dargli un pennino! Non dovrebbe essere difficile.
Adam Waite
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.