Een Jabber Client voor iOS bouwen aangepaste chatweergave en emoticons

In dit deel van de serie zullen we een aangepaste weergave maken om ervoor te zorgen dat chatberichten er professioneler uitzien. Bovendien zullen we ook echte emoticons toevoegen om te laten zien in plaats van hun tekstuele tegenhangers.

Kleine bugfix

Voordat we verder gaan, hebben we een kleine bug ontdekt in deel 3 van de serie. Wanneer we een melding ontvangen dat een nieuwe buddy online is, voegen we deze toe aan de reeks online mensen en vernieuwen we de weergave.

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

Dit zou kunnen werken als we een online melding slechts eenmaal ontvangen. In werkelijkheid wordt een dergelijke kennisgeving periodiek verzonden. Dit kan te wijten zijn aan de aard van het XMPP-protocol of de geëjabberde implementatie die we gebruiken. Om duplicaten te voorkomen, moeten we in ieder geval controleren of we de buddy in de melding al hebben toegevoegd aan de array. Dus we refactoren als volgt:

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

En de bug is opgelost.

Gebouw aangepaste chatberichten

Tijdens de serie hebben we een chatview-controller gebouwd die berichten weergeeft met behulp van standaard visuele componenten die zijn opgenomen in de iOS SDK. Ons doel is om iets mooiers te bouwen, waarin de afzender en de tijd van een bericht worden weergegeven. We halen inspiratie uit de sms-toepassing gebundeld in iOS, die de inhoud van het bericht weergeeft, gewikkeld door een ballon als een bubbel. Het resultaat dat we willen bereiken, wordt weergegeven in de volgende afbeelding:

De componenten voor de invoer staan ​​bovenaan, zoals in de huidige implementatie. We moeten een aangepaste weergave maken voor de cellen van de tabel. Dit is de lijst met vereisten:

  • Elke cel toont de afzender en de tijd van het bericht door middel van een label bovenaan
  • Elk bericht is omwikkeld met een ballonafbeelding met wat opvulling
  • Achtergrondafbeeldingen voor het bericht verschillen per afzender
  • De hoogte van het bericht (en de achtergrondafbeelding) kan variëren afhankelijk van de lengte van de tekst

De tijdstempel van een bericht opslaan

De huidige implementatie slaat niet het tijdstip op waarop een bericht is verzonden / ontvangen. Omdat we deze bewerking op meer dan één plaats moeten uitvoeren, maken we een gebruiksmethode die de huidige datum en tijd als een string teruggeeft. We doen het door middel van een categorie, het uitbreiden van de NSString klasse.
Na de door Apple voorgestelde conventie creëren we twee genoemde bronbestanden NSString + Utils.h en NSString + Utils.m. Het header-bestand bevat de volgende code:

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

In de implementatie definiëren we de statische methode getCurrentTime als volgt

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

Zo'n methode geeft tekenreeksen als het volgende terug: 12 september 2011 19:34:21 PM

Als u het formaat van de datum wilt aanpassen, kunt u de documentatie van NSFormatter raadplegen.
Nu we de utility-methode gereed hebben, moeten we de datum en tijd van verzonden en ontvangen berichten opslaan. Beide wijzigingen hebben betrekking op de SMChatViewController wanneer we een bericht verzenden:

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

En wanneer we het ontvangen:

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

Nu hebben we alle datastructuren die we nodig hebben om onze aangepaste interface te bouwen, dus laten we beginnen met het aanpassen van onze celweergave.

De ballonweergave

De meeste van de wijzigingen die we gaan introduceren, hebben betrekking op de SMChatViewController en met name op de methode -(UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath, dat is waar de inhoud van elke cel wordt getekend.
De huidige implementatie maakt gebruik van een generieke UITableViewCell, maar dat is niet genoeg voor onze vereisten, dus we moeten deze subclasseren. We noemen onze nieuwe klas SMMessageViewTableCell.

De klas heeft drie visuele elementen nodig:

  • Een label om datum en tijd te tonen
  • Een tekstuele weergave om het bericht te tonen
  • Een afbeeldingsweergave om een ​​aangepaste weergave in de vorm van een ballon weer te geven

Hier is het bijbehorende interfacebestand:

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

De eerste stap van de implementatie is om eigenschappen te synthetiseren en de deallocatie van instanties in te stellen.

 @implementation SMMessageViewTableCell @synthesize senderAndTimeLabel, messageContentView, bgImageView; - (void) dealloc [senderAndTimeLabel release]; [messageContentView release]; [bgImageView release]; [super dealloc];  @end

Dan kunnen we de constructor overschrijven om de visuele elementen toe te voegen aan de contentView van de cel. De senderAndTimeLabel is het enige element met een vaste positie, zodat we het frame en uiterlijk recht in de constructor kunnen instellen.

 - (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];  terugkeer zelf; 

De beeldweergave en het berichtveld hebben geen positionering nodig. Dat wordt beheerd in de tabelweergavemethode, want we moeten de lengte van het bericht weten om het frame te berekenen. Dus de uiteindelijke implementatie van de constructor is de volgende.

 - (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];  terugkeer zelf; 

Laten we nu het -(UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath methode met behulp van de nieuwe aangepaste cel die we hebben gebouwd. Ten eerste moeten we de oude celklasse vervangen door de nieuwe.

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

Omdat het geen zin heeft om geometrische dimensies in de constructor toe te wijzen, beginnen we met nul. Hier is een cruciale stap. We moeten de grootte van de tekst berekenen aan de hand van de lengte van de verzonden of ontvangen string. Gelukkig biedt de SDK een handige methode genaamd sizeWithFont: constrainedToSize: lineBreakMode: die de hoogte en breedte van een string berekent zoals weergegeven in overeenstemming met de beperkingen die we als parameter doorgeven. Onze enige beperking is de breedte van het apparaat met 320 logische pixels in de breedte. Omdat we wat opvulling willen, stellen we de beperking in op 260, terwijl de hoogte geen probleem is, dus we kunnen een veel hoger aantal instellen.

 CGSize textSize = 260.0, 10000.0; CGSize size = [message sizeWithFont: [UIFont boldSystemFontOfSize: 13] constrainedToSize: textSize lineBreakMode: UILineBreakModeWordWrap];

Nu is de grootte een parameter die we zullen gebruiken om beide te tekenen messageContentView en de ballonweergave. We willen dat verzonden berichten links uitgelijnd verschijnen en dat de ontvangen berichten rechts uitgelijnd verschijnen. Dus de positie van messageContentView verandert volgens de afzender van het bericht, als volgt:

 NSString * sender = [s objectForKey: @ "afzender"]; NSString * message = [s objectForKey: @ "msg"]; NSString * time = [s objectForKey: @ "time"]; CGSize textSize = 260.0, 10000.0; CGSize size = [message sizeWithFont: [UIFont boldSystemFontOfSize: 13] constrainedToSize: textSize lineBreakMode: UILineBreakModeWordWrap]; cell.messageContentView.text = bericht; cell.accessoryType = UITableViewCellAccessoryNone; cell.userInteractionEnabled = NO; if ([verzender isEqualToString: @ "u"]) // verzonden berichten [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)]; ? 

Nu moeten we de ballonafbeelding als een wrapper voor de berichtweergave weergeven. Ten eerste moeten we grafische middelen krijgen. U kunt uw eigen bouwen of de volgende gebruiken.

De eerste, met de "pijl" aan de linkerkant, zal worden gebruikt voor verzonden berichten, en de andere voor ontvangen berichten. Je vraagt ​​je misschien af ​​waarom de activa zo klein zijn. We hebben geen grote afbeeldingen nodig om in grootte te worden aangepast, maar we zullen die items rekken om zich aan te passen aan het frame van de berichtweergave. Het uitrekken spreidt alleen het centrale deel van de elementen, dat is gemaakt van een effen kleur, dus er zal geen ongewenst vervormingseffect zijn. Om dat te bereiken gebruiken we een handige methode [[UIImage imageNamed: @ "orange.png"] stretchableImageWithLeftCapWidth: 24 topCapHeight: 15];. De parameters vertegenwoordigen de limiet (vanaf randen) waar het uitrekken kan beginnen. Nu is ons beeld klaar om gepositioneerd te worden.

De uiteindelijke implementatie is de volgende:

 - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath NSDictionary * s = (NSDictionary *) [berichten 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: @ "afzender"]; NSString * message = [s objectForKey: @ "msg"]; NSString * time = [s objectForKey: @ "time"]; CGSize textSize = 260.0, 10000.0; CGSize size = [message sizeWithFont: [UIFont boldSystemFontOfSize: 13] constrainedToSize: textSize lineBreakMode: UILineBreakModeWordWrap]; size.width + = (opvulling / 2); cell.messageContentView.text = bericht; cell.accessoryType = UITableViewCellAccessoryNone; cell.userInteractionEnabled = NO; UIImage * bgImage = nil; if ([verzender isEqualToString: @ "u"]) // links uitgelijnd bgImage = [[UIImage imageNamed: @ "orange.png"] stretchableImageWithLeftCapWidth: 24 topCapHeight: 15]; [cel.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: @ "% @% @", afzender, tijd]; terugkeer cel; 

We moeten niet vergeten dat de hoogte van de hele cel dynamisch is, dus we zouden ook de volgende methode moeten bijwerken:

 - (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; CGSize size = [msg sizeWithFont: [UIFont boldSystemFontOfSize: 13] constrainedToSize: textSize lineBreakMode: UILineBreakModeWordWrap]; size.height + = padding * 2; CGFloat hoogte = maathoogte < 65 ? 65 : size.height; return height; 

Nu zijn we klaar om onze nieuwe implementatie van aangepaste weergavecellen uit te voeren. Hier is het resultaat:

emoticons

Veel chatprogramma's zoals iChat, Adium of zelfs op het web gebaseerde chats zoals Facebook Chat ondersteunen emoticons, dat zijn uitdrukkingen gemaakt van letters en interpunctie die een emotie vertegenwoordigen zoals :) voor blijheid, :( voor verdriet, enzovoort. is om de berichtweergave aan te passen, zodat afbeeldingen worden weergegeven in plaats van letters en interpunctie. Om dit gedrag mogelijk te maken, moeten we elk bericht parseren en voorvallen van emoticons vervangen door de overeenkomstige Unicode-tekens. Bekijk deze tabel. We kunnen de substitutiemethode toevoegen in de categorie Utils die we al hebben gebruikt om de huidige datum te berekenen. Dit is de implementatie:

 - (NSString *) substituteEmoticons // Zie http://www.easyapns.com/iphone-emoji-alerts voor een lijst met beschikbare emoticons NSString * res = [self stringByReplacingOccurrencesOfString: @ ":)" withString: @ "\ ue415" ]; res = [res stringByReplacingOccurrencesOfString: @ ":(" withString: @ "\ ue403"]; res = [res stringByReplacingOccurrencesOfString: @ ";-)" withString: @ "\ ue405"]; res = [res stringByReplacingOccurrencesOfString: @ ": - x" withString: @ "\ ue418"]; return res; 

Hier vervangen we slechts drie emoticons om u een idee te geven van hoe de methode werkt. Zo'n methode moet worden aangeroepen voordat berichten in de array worden opgeslagen die de SMChatViewController. Wanneer we een bericht verzenden:

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

Wanneer we het ontvangen:

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

Onze Jabber-client is nu voltooid. Hier is een screenshot van de uiteindelijke implementatie:

Klaar om te chatten?

Broncode

De volledige broncode voor dit project is hier te vinden op GitHub.