2010-10-27 10 views
40

Voglio creare un componente riutilizzabile (un controllo personalizzato) per l'iPhone. Consiste di diversi controlli standard predisposti su una vista e quindi del codice associato. I miei obiettivi sono:iPhone: crea un componente riutilizzabile (controllo) con alcuni componenti di Interface Builder e un altro codice

  1. Desidero poter utilizzare Interface Builder per disporre le sottoview nel mio controllo personalizzato;
  2. Desidero in qualche modo impacchettare il tutto in modo tale da poter trascinare abbastanza facilmente il componente personalizzato risultante in altre viste, senza dover ricablare manualmente un sacco di prese e così via. (Un po 'di ricablaggio manuale va bene, io non ne voglio fare tonnellate e tonnellate.)

Lasciatemi essere più concreto, e dirti in modo specifico che cosa dovrebbe fare il mio controllo. Nella mia app, a volte ho bisogno di colpire un servizio web per convalidare i dati che l'utente ha inserito. In attesa di una risposta dal servizio web, voglio visualizzare uno spinner (un indicatore di attività). Se i servizi Web rispondono con un codice di successo, voglio visualizzare un segno di spunta "successo". Se il servizio Web risponde con un codice di errore, voglio visualizzare un'icona di errore e un messaggio di errore.

Il modo monouso per eseguire questa operazione è piuttosto semplice: creo solo un UIView che contiene UIActivityIndicatorView, due UIImages (uno per l'icona di successo e uno per l'icona di errore) e un UILabel per il messaggio di errore. Ecco uno screenshot, rispetto alle corrispondenti parti contrassegnate in rosso:

alt text

Ho quindi cablare i pezzi alle prese, e ho messo un po 'di codice nel mio controller.

Ma come posso impacchettare quei pezzi - il codice e la piccola collezione di viste - in modo che io possa riutilizzarli? Qui ci sono alcune cose che ho trovato che mi fanno da parte, ma non sono grandioso:

  • Posso trascinare la raccolta di viste e controlli nella sezione Oggetti personalizzati della Libreria; poi, più tardi, posso trascinarli di nuovo su altre viste. Ma (a) dimentica quali immagini sono state associate ai due UIImages, (b) c'è un sacco di ricablaggio manuale di quattro o cinque prese, e (c) soprattutto, questo non porta il codice. (Forse c'è un modo semplice per cablare il codice?)
  • Penso che potrei creare un IBPlugin; Non sono sicuro se ciò sarebbe di aiuto, e sembra un sacco di lavoro, e inoltre non mi è del tutto chiaro se IBPlugins funzioni per lo sviluppo di iPhone.
  • Ho pensato, "Hmm, c'è il codice associato a questo - che odora di controller", così ho provato a creare un controller personalizzato (ad esempio WebServiceValidatorController) con il file XIB associato. In realtà ciò sembra davvero promettente, ma a quel punto non riesco a capire come, in Interface Builder, trascinare questo componente su altre viste. Il WebServiceValidatorController è un controller, non una vista, quindi posso trascinarlo in una finestra del documento, ma non in una vista.

Ho la sensazione che mi manca qualcosa ovvio ...

+0

Ho trovato questo collegamento per fornire alcune buone informazioni: http://www.bartlettpublishing.com/site/bartpub/blog/3/entry/327 –

risposta

12

Ho creato costrutti simili, tranne che io non uso il risultato in IB, ma un'istanza di utilizzando il codice. Descriverò come funziona, e alla fine ti darò un suggerimento su come può essere usato per ottenere ciò che cerchi.

Inizio da un file XIB vuoto in cui aggiungo una vista personalizzata al livello superiore. Configuro che la vista personalizzata sia la mia classe.Sotto nella gerarchia di vista, creo e configuro le subview come richiesto.

Creo tutti gli IBOutlet nella mia classe di visualizzazione personalizzata e li connetto lì. In questo esercizio ignoro completamente il "Proprietario del file".

Ora, quando ho bisogno di creare la vista (di solito in controller come parte di while/for-loop per creare tanto di loro, se necessario), io uso la funzionalità di NSBundle come questo:

- (void)viewDidLoad 
{ 
    CGRect fooBarViewFrame = CGRectMake(0, 0, self.view.bounds.size.width, FOOBARVIEW_HEIGHT); 
    for (MBSomeData *data in self.dataArray) { 
     FooBarView *fooBarView = [self loadFooBarViewForData:data]; 
     fooBarView.frame = fooBarViewFrame; 
     [self.view addSubview:fooBarView]; 

     fooBarViewFrame = CGRectOffset(fooBarViewFrame, 0, FOOBARVIEW_HEIGHT); 
    } 
} 

- (FooBarView*)loadFooBarViewForData:(MBSomeData*)data 
{ 
    NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"FooBarView" owner:self options:nil]; 
    FooBarView *fooBarView = [topLevelObjects objectAtIndex:0]; 
    fooBarView.barView.amountInEuro = data.amountInEuro; 
    fooBarView.barView.gradientStartColor = data.color1; 
    fooBarView.barView.gradientMidColor = data.color2; 
    fooBarView.titleLabel.text = data.name; 
    fooBarView.titleLabel.textColor = data.nameColor; 
    return fooBarView; 
} 

Avviso , come ho impostato il proprietario del pennino su self - non c'è nulla di male in quanto non ho collegato "proprietario del file" a nulla. L'unico risultato valido del caricamento di questo pennino è il suo primo elemento: il mio punto di vista.

Se si desidera adattare questo approccio per la creazione di viste in IB, è piuttosto semplice. Implementare il caricamento della sottoview in una vista personalizzata principale. Imposta il frame della vista secondaria sui limiti della vista principale, in modo che abbiano le stesse dimensioni. La vista principale diventerà il contenitore per la tua vista personalizzata reale e anche un'interfaccia per il codice esterno: apri solo le proprietà necessarie delle sue sottoview, il resto è incapsulato. Quindi basta rilasciare la visualizzazione personalizzata in IB, configurarla come classe e usarla come al solito.

+1

Chiaramente scritto, intelligente, e funziona! Grazie Michal. –

+1

Non dovresti dipendere dall'ordine degli oggetti restituiti da loadNibNamed: owner: options :, invece, dovresti controllare i valori di classe/tag sugli oggetti restituiti. Uno dei vantaggi dell'utilizzo del proprietario e delle uscite del file come descritto nella mia risposta è che non è necessario eseguire il ciclo degli oggetti restituiti da questo metodo. – Bogatyr

+0

beh, sto usando il fatto che ho aggiunto solo un oggetto di primo livello all'XIB - e poiché ce n'è solo uno, è abbastanza sicuro non iterare ma riprendere il primo. – Michal

21

Ecco come sto risolvendo un problema simile: volevo:

  • creare riutilizzabili, self-contained "classi di moduli di widget" che attua una vista complesso costruito da più componenti UIKit (e altri nidificato classi del modulo widget!). Le classi cliente di livello superiore di queste classi di widget non si preoccupano di cosa sia un UILabel, cos'è un UIImageView all'interno del widget, le classi del cliente si preoccupano solo di concetti come "visualizzazione del punteggio" o "visualizzazione del logo del team".

  • all'interno di una classe modulo widget lay out l'interfaccia utente per il widget e sbocchi ai collegamenti con interface builder

  • per maggiori clienti di livello di un widget, volevo essere in grado di posizionare il telaio del widget all'interno la vista del cliente in costruttore di interfaccia, senza dover progettare plug-in personalizzati per IB, ecc

Questi widget non sono controllori di primo livello: così non ha senso per loro di essere sottoclassi di UIViewController. Poi c'è anche il consiglio di Apple di non avere più di un VC su uno schermo visibile alla volta. Inoltre, ci sono molti consigli e opinioni che vagano come "MVC! MVC! Devi separare il tuo View e il tuo controllo! MVC!" che le persone sono così fortemente scoraggiate dalla sottoclasse di UIView o dal mettere sempre la logica dell'app all'interno di una sottoclasse UIView.

Ma ho deciso di farlo comunque (sottoclasse UIView). Sono stato in giro per il blocco alcune volte (ma ancora abbastanza nuovo per l'iPhone SDK/UIKit), e sono molto sensibile al design che è brutto, e che può causare problemi, e francamente non vedo il problema con la creazione di sottoclassi di UIView e l'aggiunta di punti vendita e azioni. In realtà ci sono molti vantaggi a fare il tuo widget riutilizzabili sia una sottoclasse di UIView piuttosto che essere a base UIViewController:

  • È possibile inserire un'istanza diretta della classe widget di nel layout costruttore di interfaccia di una vista del cliente, e la il frame UIView della vista widget verrà inizializzato correttamente quando la classe cliente viene caricata dallo xib. Questo è molto più pulito per me che inserire una "vista segnaposto" nel cliente, quindi istanziare il modulo del widget a livello di programmazione e impostare la cornice del widget su quella del segnaposto e quindi scambiare la vista segnaposto per la vista del widget.

  • È possibile creare un file xib pulito e semplice per disporre i componenti del widget nel builder dell'interfaccia. Il proprietario del file è impostato sulla classe del tuo widget. Il file pennino contiene un UIView root (vaniglia) in cui è presente tutta la GUI e quindi nel metodo awakeFromNib: il widget, questa vista del pennino viene aggiunta al widget stesso come sottoview. Qualsiasi modifica alle dimensioni del fotogramma può essere gestita qui, interamente all'interno del codice del widget, se necessario. In questo modo:

- (void) awakeFromNib 
{ 
    [[NSBundle mainBundle] loadNibNamed:@"MyWidgetView" owner:self options:nil]; 
    [self addSubview:self.view]; 
} 
  • Il widget di auto-initialzes sua interfaccia utente utilizzando loadNibNamed di NSBundle: metodo di opzioni nel suo proprio metodo awakeFromNib, quindi l'integrazione del widget in classi di clienti: proprietario è pulito: basta rilasciare un UIView nella visualizzazione del cliente in IB, modificare la classe di UIView in MyWidgetView e in init o viewDidLoad del cliente impostare i valori predefiniti nella visualizzazione del widget utilizzando l'API del widget che scrivi tu stesso (setScore: setTeamImage: etc .)

Mi sembra che non ci sia sostanzialmente alcuna differenza nel codice risultante tra la sottoclassi di un UIView in questo modo per creare un "widget" riutilizzabile e l'utilizzo di un UIViewController. In entrambi i casi i file .h contengono membri della classe widget, outlets, dichiarazioni di azioni e dichiarazioni API speciali. In entrambi i casi il file .m contiene implementazioni di azioni e un'implementazione API speciale. E la vista è separata dal controllo: la vista è nel file xib!

Così, in sintesi, questo è quello che faccio per il confezionamento fino complesse riutilizzabili vista widget:

  • rendere la classe widget di una sottoclasse di UIView
  • istanziare il widget di dove si vuole, in altre viste in la tua app in IB direttamente trascinando un UIView dalla libreria degli strumenti e cambiando la classe in "MyWidgetClass".
  • Progetta l'interfaccia utente del tuo widget in IB in un pennino all'interno di un UIView di root vanilla.
  • nel awakeFromNib della classe widget di: metodo, caricare l'interfaccia utente del widget dal XI ter e fare [self addSubview:self.theXibRootView]

  • Codice il widget, proprio come si farebbe con un UIViewController: punti vendita, le azioni, le API speciali

Sarei interessato a conoscere altri sistemi strutturali per la creazione di widget riutilizzabili e quali sono i vantaggi rispetto a questo approccio.

Se il widget deve segnalare eventi alla classe cliente, basta utilizzare un protocollo delegato in cui il cliente imposta il delegato del widget su sé stesso, proprio come in un UIViewController.

+2

Grazie. Questo è proprio quello che stavo cercando. – jlstrecker

Problemi correlati