Creazione di un client Jabber per iOS visualizzazione chat ed emoticon personalizzate

In questa parte della serie, creeremo una visualizzazione personalizzata per rendere i messaggi di chat più professionali. Inoltre, aggiungeremo anche delle emoticon reali da mostrare al posto delle loro controparti testuali.

Correzione di piccoli bug

Prima di proseguire abbiamo notato un piccolo bug introdotto nella parte 3 della serie. Quando riceviamo una notifica che un nuovo amico è online, lo aggiungiamo alla schiera di persone online e aggiorniamo la vista.

 - (void) newBuddyOnline: (NSString *) buddyName [onlineBuddies addObject: buddyName]; [self.tView reloadData]; 

Questo potrebbe funzionare se abbiamo ricevuto una notifica online solo una volta. In realtà, tale notifica viene inviata periodicamente. Ciò potrebbe essere dovuto alla natura del protocollo XMPP o dell'implementazione ejabber che stiamo utilizzando. In ogni caso, per evitare duplicati, dovremmo controllare se abbiamo già aggiunto alla matrice il compagno trasportato nella notifica. Quindi, ci rifattiamo in questo modo:

 - (void) newBuddyOnline: (NSString *) buddyName if (! [onlineBuddies containsObject: buddyName]) [onlineBuddies addObject: buddyName]; [self.tView reloadData]; 

E il bug è stato risolto.

Costruire messaggi di chat personalizzati

Durante la serie abbiamo creato un controller di visualizzazione chat che visualizza i messaggi utilizzando componenti visivi standard inclusi nell'SDK di iOS. Il nostro obiettivo è creare qualcosa di più carino, che mostra il mittente e l'ora di un messaggio. Ci ispiriamo all'applicazione SMS in bundle in iOS, che mostra il contenuto del messaggio avvolto da una bolla simile a un fumetto. Il risultato che vogliamo raggiungere è mostrato nella figura seguente:

I componenti per l'input sono in alto, come nell'attuale implementazione. Dobbiamo creare una vista personalizzata per le celle del tavolo. Questa è la lista dei requisiti:

  • Ogni cella mostra il mittente e l'ora del messaggio per mezzo di un'etichetta in alto
  • Ogni messaggio è avvolto da un'immagine a palloncino con qualche padding
  • Le immagini di sfondo per il messaggio sono diverse a seconda del mittente
  • L'altezza del messaggio (e la sua immagine di sfondo) può variare in base alla lunghezza del testo

Salvataggio del timestamp di un messaggio

L'attuale implementazione non salva il tempo in cui un messaggio è stato inviato / ricevuto. Poiché dobbiamo eseguire questa operazione in più di una posizione, creiamo un metodo di utilità che restituisce la data e l'ora correnti sotto forma di stringa. Lo facciamo per mezzo di una categoria, estendendo il NSString classe.
Seguendo la convenzione suggerita da Apple, creiamo due file sorgente nominati NSString + Utils.h e NSString + Utils.m. Il file di intestazione contiene il seguente codice:

 @interface NSString (Utils) + (NSString *) getCurrentTime; @fine

Nell'implementazione, definiamo il metodo statico getCurrentTime come segue

 @implementation NSString (Utils) + (NSString *) getCurrentTime NSDate * nowUTC = [Data NSDate]; NSDateFormatter * dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setTimeZone: [NSTimeZone localTimeZone]]; [dateFormatter setDateStyle: NSDateFormatterMediumStyle]; [dateFormatter setTimeStyle: NSDateFormatterMediumStyle]; return [dateFormatter stringFromDate: nowUTC];  @fine

Tale metodo restituirà stringhe come le seguenti: 12 settembre 2011 7:34:21 PM

Se si desidera personalizzare il formato della data è possibile consultare la documentazione di NSFormatter.
Ora che abbiamo il metodo di utilità pronto, dobbiamo salvare la data e l'ora dei messaggi inviati e ricevuti. Entrambe le modifiche riguardano SMChatViewController quando inviamo un messaggio:

 - (IBAction) sendMessage NSString * messageStr = self.messageField.text; if ([messageStr length]> 0) ? NSMutableDictionary * m = [[NSMutableDictionary alloc] init]; [m setObject: @ "you" forKey: @ "sender"]; [m setObject: [NSString getCurrentTime] forKey: @ "time"] ;? ? 

E quando lo riceviamo:

 - (void) newMessageReceived: (NSDictionary *) messageContent NSString * m = [messageContent objectForKey: @ "msg"] ;? [messageContent setObject: [NSString getCurrentTime] forKey: @ "time"] ;? 

Ora disponiamo di tutte le strutture dati necessarie per creare la nostra interfaccia personalizzata, quindi iniziamo personalizzando la nostra visualizzazione cella.

The Balloon View

La maggior parte delle modifiche che introdurremo sono correlate a SMChatViewController e in particolare al metodo -(UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath, che è dove viene disegnato il contenuto di ogni cella.
L'implementazione corrente utilizza un UITableViewCell generico, ma ciò non è sufficiente per i nostri requisiti, quindi è necessario suddividerlo in sottoclassi. Chiamiamo la nostra nuova classe SMMessageViewTableCell.

La classe ha bisogno di tre elementi visivi:

  • Un'etichetta per mostrare la data e l'ora
  • Una vista testuale per mostrare il messaggio
  • Una vista immagine per visualizzare una vista personalizzata a forma di palloncino

Ecco il file di interfaccia corrispondente:

 @interface SMMessageViewTableCell: UITableViewCell UILabel * senderAndTimeLabel; UITextView * messageContentView; UIImageView * bgImageView;  @property (nonatomic, assign) UILabel * senderAndTimeLabel; @property (nonatomic, assegna) UITextView * messageContentView; @property (nonatomic, assign) UIImageView * bgImageView; @fine

Il primo passo dell'implementazione è sintetizzare le proprietà e impostare la deallocazione delle istanze.

 @implementation SMMessageViewTableCell @synthesize senderAndTimeLabel, messageContentView, bgImageView; - (void) dealloc [senderAndTimeLabel release]; [versione di MessageContentView]; [versione bgImageView]; [super dealloc];  @fine

Quindi possiamo sovrascrivere il costruttore per aggiungere gli elementi visivi al contentView della cella. Il senderAndTimeLabel è l'unico elemento con una posizione fissa in modo che possiamo impostare la sua cornice e aspetto proprio nel costruttore.

 - (id) initWithStyle: (UITableViewCellStyle) style reuseIdentifier: (NSString *) reuseIdentifier if (self = [super initWithStyle: style reuseIdentifier: reuseIdentifier]) senderAndTimeLabel = [[UILabel alloc] initWithFrame: CGRectMake (10, 5, 300, 20 )]; senderAndTimeLabel.textAlignment = UITextAlignmentCenter; senderAndTimeLabel.font = [UIFont systemFontOfSize: 11.0]; senderAndTimeLabel.textColor = [UIColor lightGrayColor]; [self.contentView addSubview: senderAndTimeLabel];  return self; 

La visualizzazione dell'immagine e il campo del messaggio non necessitano di alcun posizionamento. Questo verrà gestito nel metodo di visualizzazione tabella, poiché è necessario conoscere la lunghezza del messaggio per calcolarne il frame. Quindi l'implementazione finale del costruttore è la seguente.

 - (id) initWithStyle: (UITableViewCellStyle) style reuseIdentifier: (NSString *) reuseIdentifier if (self = [super initWithStyle: style reuseIdentifier: reuseIdentifier]) senderAndTimeLabel = [[UILabel alloc] initWithFrame: CGRectMake (10, 5, 300, 20 )]; senderAndTimeLabel.textAlignment = UITextAlignmentCenter; senderAndTimeLabel.font = [UIFont systemFontOfSize: 11.0]; senderAndTimeLabel.textColor = [UIColor lightGrayColor]; [self.contentView addSubview: senderAndTimeLabel]; bgImageView = [[UIImageView alloc] initWithFrame: CGRectZero]; [self.contentView addSubview: bgImageView]; messageContentView = [[UITextView alloc] init]; messageContentView.backgroundColor = [UIColor clearColor]; messageContentView.editable = NO; messageContentView.scrollEnabled = NO; [messageContentView sizeToFit]; [self.contentView addSubview: messageContentView];  return self; 

Ora riscriviamo il -(UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath metodo usando la nuova cella personalizzata che abbiamo costruito. Per prima cosa, dobbiamo sostituire la vecchia classe di celle con quella nuova.

 - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath NSDictionary * s = (NSDictionary *) [messages objectAtIndex: indexPath.row]; static NSString * CellIdentifier = @ "MessageCellIdentifier"; SMMessageViewTableCell * cell = (SMMessageViewTableCell *) [tableView dequeueReusableCellWithIdentifier: CellIdentifier]; if (cell == nil) cell = [[[SMMessageViewTableCell alloc] initWithFrame: CGRectZero reuseIdentifier: CellIdentifier] autorelease]; 

Poiché non ha senso assegnare quote geometriche nel costruttore, iniziamo con zero. Ecco un passaggio cruciale. Dobbiamo calcolare la dimensione del testo in base alla lunghezza della stringa inviata o ricevuta. Fortunatamente l'SDK fornisce un metodo pratico chiamato sizeWithFont: constrainedToSize: lineBreakMode: che calcola l'altezza e la larghezza di una stringa come renderizzata in base ai vincoli che passiamo come parametro. Il nostro unico vincolo è la larghezza del dispositivo che è 320 pixel logici in larghezza. Dato che vogliamo un po 'di padding, impostiamo il vincolo a 260, mentre l'altezza non è un problema, quindi possiamo impostare un numero molto più alto.

 CGSize textSize = 260.0, 10000.0; Dimensione CGSize = [dimensione messaggioWithFont: [UIFont boldSystemFontOfSize: 13] constrainedToSize: textSize lineBreakMode: UILineBreakModeWordWrap];

Ora, la dimensione è un parametro che useremo per disegnare entrambi messageContentView e la vista a palloncino. Vogliamo che i messaggi inviati vengano visualizzati allineati a sinistra e che i messaggi ricevuti appaiano allineati a destra. Quindi la posizione di messageContentView cambia in base al mittente del messaggio, come segue:

 NSString * sender = [s objectForKey: @ "sender"]; NSString * message = [s objectForKey: @ "msg"]; NSString * time = [s objectForKey: @ "time"]; CGSize textSize = 260.0, 10000.0; Dimensione CGSize = [dimensione messaggioWithFont: [UIFont boldSystemFontOfSize: 13] constrainedToSize: textSize lineBreakMode: UILineBreakModeWordWrap]; cell.messageContentView.text = message; cell.accessoryType = UITableViewCellAccessoryNone; cell.userInteractionEnabled = NO; if ([sender isEqualToString: @ "you"]) // ha inviato messaggi [cell.messageContentView setFrame: CGRectMake (padding, padding * 2, size.width, size.height)];  else [cell.messageContentView setFrame: CGRectMake (320 - size.width - padding, padding * 2, size.width, size.height)]; ? 

Ora dobbiamo visualizzare l'immagine del fumetto come wrapper per la visualizzazione dei messaggi. Innanzitutto, abbiamo bisogno di ottenere risorse grafiche. Puoi costruire il tuo o usare i seguenti.

Il primo, con la "freccia" a sinistra, verrà utilizzato per i messaggi inviati e l'altro per quelli ricevuti. Potresti chiederti perché le risorse sono così piccole. Non avremo bisogno di grandi dimensioni per adattarle alle dimensioni, ma estenderemo tali risorse per adattarle al frame della visualizzazione dei messaggi. Lo stretching distribuirà solo la parte centrale delle risorse, che è fatta di un colore solido, quindi non ci sarà alcun effetto di deformazione indesiderato. Per riuscirci usiamo un metodo pratico [[UIImage imageNamed: @ "orange.png"] stretchableImageWithLeftCapWidth: 24 topCapHeight: 15];. I parametri rappresentano il limite (dai bordi) in cui può iniziare lo stretching. Ora la nostra immagine è pronta per essere posizionata.

L'implementazione finale è la seguente:

 - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath NSDictionary * s = (NSDictionary *) [messages objectAtIndex: indexPath.row]; static NSString * CellIdentifier = @ "MessageCellIdentifier"; SMMessageViewTableCell * cell = (SMMessageViewTableCell *) [tableView dequeueReusableCellWithIdentifier: CellIdentifier]; if (cell == nil) cell = [[[SMMessageViewTableCell alloc] initWithFrame: CGRectZero reuseIdentifier: CellIdentifier] autorelease];  NSString * sender = [s objectForKey: @ "sender"]; NSString * message = [s objectForKey: @ "msg"]; NSString * time = [s objectForKey: @ "time"]; CGSize textSize = 260.0, 10000.0; Dimensione CGSize = [dimensione messaggioWithFont: [UIFont boldSystemFontOfSize: 13] constrainedToSize: textSize lineBreakMode: UILineBreakModeWordWrap]; size.width + = (padding / 2); cell.messageContentView.text = message; cell.accessoryType = UITableViewCellAccessoryNone; cell.userInteractionEnabled = NO; UIImage * bgImage = nil; if ([sender isEqualToString: @ "you"]) // left aligned bgImage = [[UIImage imageNamed: @ "orange.png"] stretchableImageWithLeftCapWidth: 24 topCapHeight: 15]; [cell.messageContentView setFrame: CGRectMake (padding, padding * 2, size.width, size.height)]; [cell.bgImageView setFrame: CGRectMake (cell.messageContentView.frame.origin.x - padding / 2, cell.messageContentView.frame.origin.y - padding / 2, size.width + padding, size.height + padding)];  else bgImage = [[UIImage imageNamed: @ "aqua.png"] stretchableImageWithLeftCapWidth: 24 topCapHeight: 15]; [cell.messageContentView setFrame: CGRectMake (320 - size.width - padding, padding * 2, size.width, size.height)]; [cell.bgImageView setFrame: CGRectMake (cell.messageContentView.frame.origin.x - padding / 2, cell.messageContentView.frame.origin.y - padding / 2, size.width + padding, size.height + padding)];  cell.bgImageView.image = bgImage; cell.senderAndTimeLabel.text = [NSString stringWithFormat: @ "% @% @", mittente, ora]; cella di ritorno; 

Non dobbiamo dimenticare che l'altezza dell'intera cella è dinamica, quindi dovremmo anche aggiornare il seguente metodo:

 - (CGFloat) tableView: (UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath *) indexPath NSDictionary * dict = (NSDictionary *) [messages objectAtIndex: indexPath.row]; NSString * msg = [dict objectForKey: @ "msg"]; CGSize textSize = 260.0, 10000.0; Dimensione CGSize = [msg sizeWithFont: [UIFont boldSystemFontOfSize: 13] constrainedToSize: textSize lineBreakMode: UILineBreakModeWordWrap]; size.height + = padding * 2; Altezza CGFloat = altezza < 65 ? 65 : size.height; return height; 

Ora siamo pronti per eseguire la nostra nuova implementazione di celle di visualizzazione personalizzate. Ecco il risultato:

Emoticons

Molti programmi di chat come iChat, Adium o anche chat basate sul Web come Facebook Chat, emoticon di supporto, ovvero espressioni fatte di lettere e punteggiatura che rappresentano un'emozione del tipo :) per la felicità, :( per la tristezza e così via. è personalizzare la visualizzazione dei messaggi in modo che le immagini vengano visualizzate al posto delle lettere e della punteggiatura.Per abilitare questo comportamento è necessario analizzare ogni messaggio e sostituire le occorrenze delle emoticon con i corrispondenti caratteri Unicode. Per un elenco di emoticon disponibili su iPhone è possibile controlla questa tabella Possiamo aggiungere il metodo di sostituzione nella categoria Utils che abbiamo già utilizzato per calcolare la data corrente. Questa è l'implementazione:

 - (NSString *) sostitutoEmoticons // Vedi http://www.easyapns.com/iphone-emoji-alerts per un elenco di emoticon disponibili NSString * res = [self stringByReplacingOccurrencesOfString: @ ":)" withString: @ "\ ue415" ]; res = [res StringByReplacingOccurrencesOfString: @ ":(" withString: @ "\ ue403"]; res = [res StringByReplacingOccurrencesOfString: @ ";-)" withString: @ "\ ue405"]; res = [res StringByReplacingOccurrencesOfString: @ ": - x" withString: @ "\ ue418"]; ritorno; 

Qui sostituiamo solo tre emoticon solo per darti un'idea di come funziona il metodo. Tale metodo deve essere chiamato prima di memorizzare i messaggi nella matrice che popola il SMChatViewController. Quando inviamo un messaggio:

 - (IBAction) sendMessage NSString * messageStr = self.messageField.text; if ([messageStr length]> 0) ? NSMutableDictionary * m = [[NSMutableDictionary alloc] init]; [m setObject: [messageStr substituteEmoticons] forKey: @ "msg"] ;? [messages addObject: m];]? 

Quando lo riceviamo:

 - (void) newMessageReceived: (NSDictionary *) messageContent NSString * m = [messageContent objectForKey: @ "msg"]; [messageContent setObject: [m sostitutoEmoticons] forKey: @ "msg"]; [messaggi addObject: messageContent] ;? 

Il nostro client Jabber è ora completo. Ecco uno screenshot dell'implementazione finale:

Pronto per chattare?

Codice sorgente

Il codice sorgente completo per questo progetto può essere trovato su GitHub qui.