iOS微信QQ聊天介面的UI框架以及Socket簡單實現群聊功能
7.1日更新 Python3 TCP Demo相關
https://blog.csdn.net/Deft_MKJing/article/details/80851879
2.2日更新,socket簡易群聊通訊,之前實現的是靜態本地聊天模擬
1.需要的先下載下來,先開啟SocketSeverce 2
這個伺服器程式碼,已經封裝好了Socket建立和連線
2.開啟工程,自動會連上伺服器,已經寫好了socket的生成和連線
3.再開啟一個終端,模擬第二個客戶端telnet 192.168.31.150 3667
輸入之後就能進行簡單的群聊功能
// 客戶端示例程式碼
// 連線到聊天伺服器
GCDAsyncSocket *socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
[socket connectToHost:@"127.0.0.1" onPort:3667 error:nil];
self.clientSocket = socket;
// 服務端部分示例程式碼
- (instancetype)init
{
if (self = [super init]) {
/**
注意:這裡的服務端socket,只負責socket(),bind(),lisence(),accept(),他的任務到底結束,只負責監聽是否有客戶端socket來連線
*/
self.serviceSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
}
return self;
}
- (void)connected
{
NSError *error = nil;
// 給一個需要連線的埠,0-1024是系統的
[self.serviceSocket acceptOnPort:3667 error:&error];
if (error) {
NSLog(@"3666伺服器開啟失敗。。。。。%@",error);
}
else
{
NSLog(@"開啟成功,並開始監聽");
}
}
個人非常喜歡老外寫的框架,而且是還是不斷更新的,這個簡直是不斷學習的好資料啊,人家那物件導向封裝的,值得學習,那麼花時間來簡單介紹下該框架的應用場景
核心類名介紹
Mode資料類
- JSQAudioMediaItem.h 語音
- JSQLocationMediaItem.h 定位
- JSQMediaItem.h 非檔案的Media基類
- JSQMessage.h 所有訊息都由該類包裝,因此,最外層 collectionView用到的就是陣列包含該類
- JSQMessageAvatarImageDataSource.h 頭像資料代理
- JSQMessageBubbleImageDataSource.h 氣泡資料代理
- JSQMessageData.h 傳送訊息ID date代理
- JSQMessageMediaData.h 非文字訊息資料代理
- JSQMessagesAvatarImage.h 頭像類
- JSQMessagesBubbleImage.h 氣泡類
- JSQMessagesCollectionViewDataSource.h
- JSQMessagesCollectionViewDelegateFlowLayout.h
- JSQPhotoMediaItem.h 圖片
- JSQVideoMediaItem.h 視訊
View類
- JSQMessagesCellTextView.m 純文字TextView
- JSQMessagesCollectionView.m 核心collectionView繼承原生的
- JSQMessagesCollectionViewCell.m 核心cell
- JSQMessagesCollectionViewCellIncoming.xib 收到訊息cell
- JSQMessagesCollectionViewCellOutgoing.xib 傳送訊息cell
- JSQMessagesComposerTextView.m 貼上文字
- JSQMessagesInputToolbar.m 底部的toolBar
- JSQMessagesLabel.m 頭部時間或者底部文字Label
- JSQMessagesLoadEarlierHeaderView.xib 更多載入View
- JSQMessagesMediaPlaceholderView.m MediaPlaceHolderView
- JSQMessagesTypingIndicatorFooterView.xib 預載入指示Bubble
類雖然很多,但是肯定越多越好,說明功能越強大啊
1.萬事開頭難,第一步
建立一個ViewController繼承與JSQMessagesViewController,然後來一個資料model,來存放所有接受和發出去的訊息,各種型別上面已經介紹了,直接放h檔案的程式碼
// VC
@interface MKJChatViewcontroller : JSQMessagesViewController<UIActionSheetDelegate, JSQMessagesComposerTextViewPasteDelegate>
@property (strong, nonatomic) DemoModelData *demoData; //!< 訊息模型
- (void)receiveMessagePressed:(UIBarButtonItem *)sender;
// Model
**
* This is for demo/testing purposes only.
* This object sets up some fake model data.
* Do not actually do anything like this.
* 假資料,用來展示玩玩的,別當真
*/
static NSString * const kJSQDemoAvatarDisplayNameSquires = @"Jesse Squires";
static NSString * const kJSQDemoAvatarDisplayNameCook = @"Tim Cook";
static NSString * const kJSQDemoAvatarDisplayNameJobs = @"Jobs";
static NSString * const kJSQDemoAvatarDisplayNameWoz = @"Steve Wozniak";
static NSString * const kJSQDemoAvatarIdSquires = @"053496-4509-289";
static NSString * const kJSQDemoAvatarIdCook = @"468-768355-23123";
static NSString * const kJSQDemoAvatarIdJobs = @"707-8956784-57";
static NSString * const kJSQDemoAvatarIdWoz = @"309-41802-93823";
@interface DemoModelData : NSObject
/*
* 這裡放的都是JSQMessage物件 該物件有兩個初始化方式 1.media or noMedia
*/
@property (strong, nonatomic) NSMutableArray *messages; // message陣列
@property (strong, nonatomic) NSDictionary *avatars; // 聊天人所有頭像
@property (strong, nonatomic) JSQMessagesBubbleImage *outgoingBubbleImageData; // 發出去的氣泡顏色
@property (strong, nonatomic) JSQMessagesBubbleImage *incomingBubbleImageData; // 收到的氣泡顏色
@property (strong, nonatomic) NSDictionary *users; // 使用者名稱字資訊
- (void)addPhotoMediaMessage;//!< 圖片訊息
- (void)addLocationMediaMessageCompletion:(JSQLocationMediaItemCompletionBlock)completion; //!< 定位小心
- (void)addVideoMediaMessage; //!< 視訊 無底圖
- (void)addVideoMediaMessageWithThumbnail; //!< 視訊帶底圖
- (void)addAudioMediaMessage; //!< 音訊
首先注意的,這裡的資料都是faker,沒錯,就是faker大魔王,拿來玩玩而已,具體需要根據業務邏輯來,那麼再來看看偽造的實現資料
2.搞事情,搞資料啊
// 純文字JSQMessage物件建立
self.messages = [[NSMutableArray alloc] initWithObjects:
[[JSQMessage alloc] initWithSenderId:kJSQDemoAvatarIdSquires
senderDisplayName:kJSQDemoAvatarDisplayNameSquires
date:[NSDate distantPast]
text:NSLocalizedString(@"Welcome to JSQMessages: A messaging UI framework for iOS.", nil)]
// 非純文字JSQMessage物件建立之圖片
JSQPhotoMediaItem *photoItem = [[JSQPhotoMediaItem alloc] initWithImage:[UIImage imageNamed:@"goldengate"]];
JSQMessage *photoMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdSquires
displayName:kJSQDemoAvatarDisplayNameSquires
media:photoItem];
// 非純文字JSQMessage物件建立之Location定位
CLLocation *ferryBuildingInSF = [[CLLocation alloc] initWithLatitude:37.795313 longitude:-122.393757];
JSQLocationMediaItem *locationItem = [[JSQLocationMediaItem alloc] init];
[locationItem setLocation:ferryBuildingInSF withCompletionHandler:completion];
JSQMessage *locationMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdSquires
displayName:kJSQDemoAvatarDisplayNameSquires
media:locationItem];
// 非純文字JSQMessage物件建立之視訊
NSURL *videoURL = [NSURL URLWithString:@"http://qingdan.img.iwala.net/v/twt/twt1612_720P.mp4"];
JSQVideoMediaItem *videoItem = [[JSQVideoMediaItem alloc] initWithFileURL:videoURL isReadyToPlay:YES];
JSQMessage *videoMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdSquires
displayName:kJSQDemoAvatarDisplayNameSquires
media:videoItem];
// 非純文字JSQMessage物件建立之語音
NSString * sample = [[NSBundle mainBundle] pathForResource:@"jsq_messages_sample" ofType:@"m4a"];
NSData * audioData = [NSData dataWithContentsOfFile:sample];
JSQAudioMediaItem *audioItem = [[JSQAudioMediaItem alloc] initWithData:audioData];
JSQMessage *audioMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdSquires
displayName:kJSQDemoAvatarDisplayNameSquires
media:audioItem];
// 最後都加到Model的資料裡面
[self.messages addObject:JSQMessage物件];
3.搞頭像和氣泡
// 頭像圖片製作工具類
// 新方法
// 通過文字和顏色建立頭像
JSQMessagesAvatarImage *jsqImage = [JSQMessagesAvatarImageFactory avatarImageWithUserInitials:@"MKJ"
backgroundColor:[UIColor colorWithWhite:0.85f alpha:1.0f]
textColor:[UIColor colorWithWhite:0.60f alpha:1.0f]
font:[UIFont systemFontOfSize:14.0f]
diameter:kJSQMessagesCollectionViewAvatarSizeDefault+10];
// 通過image建立頭像
JSQMessagesAvatarImage *cookImage = [JSQMessagesAvatarImageFactory avatarImageWithImage:[UIImage imageNamed:@"demo_avatar_cook"] diameter:kJSQMessagesCollectionViewAvatarSizeDefault];;
// 氣泡圖片製作工具類
// [UIImage jsq_bubbleRegularImage]這個方法有很多種氣泡模式,圓的,尖的以及邊框形式的
JSQMessagesBubbleImageFactory *bubbleFactory = [[JSQMessagesBubbleImageFactory alloc] initWithBubbleImage:[UIImage jsq_bubbleRegularImage] capInsets:UIEdgeInsetsZero];
// 發出去的氣泡顏色
self.outgoingBubbleImageData = [bubbleFactory outgoingMessagesBubbleImageWithColor:[UIColor jsq_messageBubbleLightGrayColor]];
// self.outgoingBubbleImageData = [bubbleFactory outgoingMessagesBubbleImageWithColor:[UIColor whiteColor]];
// 收到的氣泡顏色
self.incomingBubbleImageData = [bubbleFactory incomingMessagesBubbleImageWithColor:[UIColor jsq_messageBubbleGreenColor]];
4.控制器寫資料邏輯代理
先提一下那個滾動動畫,慎用,有點不可控
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
/**
* Enable/disable springy bubbles, default is NO.
* You must set this from `viewDidAppear:`
* Note: this feature is mostly stable, but still experimental
* 注意啊,這個有時候會蹦掉,玩玩就好了
*/
// 一個bubbles的移動動畫效果
self.collectionView.collectionViewLayout.springinessEnabled = [[[NSUserDefaults standardUserDefaults] valueForKey:@"kDynamic"] boolValue];
}
模擬個右上角的按鈕,來接受訊息,最核心程式碼
// 收到別人發的訊息了
- (void)receiveMessagePressed:(UIBarButtonItem *)sender
{
// 這僅僅是模擬Demo
/**
* Show the typing indicator to be shown
* 是否需要一個載入指示
*/
self.showTypingIndicator = YES;
/**
* Scroll to actually view the indicator 滾動到最後
*/
[self scrollToBottomAnimated:YES];
/**
* Copy last sent message, this will be the new "received" message
* 來一份上一次的資料
*/
JSQMessage *copyMessage = [[self.demoData.messages lastObject] copy];
if (!copyMessage) {
copyMessage = [JSQMessage messageWithSenderId:kJSQDemoAvatarIdJobs
displayName:kJSQDemoAvatarDisplayNameJobs
text:@"First received!"];
}
/**
* Allow typing indicator to show
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSMutableArray *userIds = [[self.demoData.users allKeys] mutableCopy];
[userIds removeObject:self.senderId];
NSString *randomUserId = userIds[arc4random_uniform((int)[userIds count])];
JSQMessage *newMessage = nil;
id<JSQMessageMediaData> newMediaData = nil;
id newMediaAttachmentCopy = nil;
// JSQMessage對應的BOOL isMediaMessage = NO就是text,YES就是圖片,音訊,視訊,定位
if (copyMessage.isMediaMessage) {
/**
* Last message was a media message
*/
// 先把代理儲存下
id<JSQMessageMediaData> copyMediaData = copyMessage.media;
// 如果是圖片
if ([copyMediaData isKindOfClass:[JSQPhotoMediaItem class]]) {
JSQPhotoMediaItem *photoItemCopy = [((JSQPhotoMediaItem *)copyMediaData) copy];
// 預設都是YES的,這句話的意思是氣泡的小尖尖朝哪個方向,YES是發出去的,就朝右,反之
photoItemCopy.appliesMediaViewMaskAsOutgoing = NO;
newMediaAttachmentCopy = [UIImage imageWithCGImage:photoItemCopy.image.CGImage];
/**
* Set image to nil to simulate "downloading" the image
* and show the placeholder view
* 代表發出去的訊息會進行短暫的loading
*/
photoItemCopy.image = nil;
newMediaData = photoItemCopy;
}
else if ([copyMediaData isKindOfClass:[JSQLocationMediaItem class]]) {
// 座標訊息 同上
JSQLocationMediaItem *locationItemCopy = [((JSQLocationMediaItem *)copyMediaData) copy];
locationItemCopy.appliesMediaViewMaskAsOutgoing = NO;
newMediaAttachmentCopy = [locationItemCopy.location copy];
/**
* Set location to nil to simulate "downloading" the location data
*/
locationItemCopy.location = nil;
newMediaData = locationItemCopy;
}
else if ([copyMediaData isKindOfClass:[JSQVideoMediaItem class]]) {
// 視訊訊息 同上
JSQVideoMediaItem *videoItemCopy = [((JSQVideoMediaItem *)copyMediaData) copy];
videoItemCopy.appliesMediaViewMaskAsOutgoing = NO;
newMediaAttachmentCopy = [videoItemCopy.fileURL copy];
/**
* Reset video item to simulate "downloading" the video
*/
videoItemCopy.fileURL = nil;
videoItemCopy.isReadyToPlay = NO;
newMediaData = videoItemCopy;
}
else if ([copyMediaData isKindOfClass:[JSQAudioMediaItem class]]) {
// 同上
JSQAudioMediaItem *audioItemCopy = [((JSQAudioMediaItem *)copyMediaData) copy];
audioItemCopy.appliesMediaViewMaskAsOutgoing = NO;
newMediaAttachmentCopy = [audioItemCopy.audioData copy];
/**
* Reset audio item to simulate "downloading" the audio
*/
audioItemCopy.audioData = nil;
newMediaData = audioItemCopy;
}
else {
NSLog(@"%s error: unrecognized media item", __PRETTY_FUNCTION__);
}
// 除開Text外的訊息類
newMessage = [JSQMessage messageWithSenderId:randomUserId
displayName:self.demoData.users[randomUserId]
media:newMediaData];
}
else {
/**
* Last message was a text message 純文字訊息類
*/
newMessage = [JSQMessage messageWithSenderId:randomUserId
displayName:self.demoData.users[randomUserId]
text:copyMessage.text];
}
/**
* Upon receiving a message, you should:
*
* 1. Play sound (optional)
* 2. Add new id<JSQMessageData> object to your data source
* 3. Call `finishReceivingMessage`
*/
// [JSQSystemSoundPlayer jsq_playMessageReceivedSound];
// 播放聲音
[self.demoData.messages addObject:newMessage];
[self finishReceivingMessageAnimated:YES];
// 如果訊息型別是Media 非文字形式
if (newMessage.isMediaMessage) {
/**
* Simulate "downloading" media 模擬下載
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
/**
* 模擬下載,下載完之後重新刷
*/
if ([newMediaData isKindOfClass:[JSQPhotoMediaItem class]]) {
((JSQPhotoMediaItem *)newMediaData).image = newMediaAttachmentCopy;
[self.collectionView reloadData];
}
else if ([newMediaData isKindOfClass:[JSQLocationMediaItem class]]) {
[((JSQLocationMediaItem *)newMediaData)setLocation:newMediaAttachmentCopy withCompletionHandler:^{
[self.collectionView reloadData];
}];
}
else if ([newMediaData isKindOfClass:[JSQVideoMediaItem class]]) {
((JSQVideoMediaItem *)newMediaData).fileURL = newMediaAttachmentCopy;
((JSQVideoMediaItem *)newMediaData).isReadyToPlay = YES;
[self.collectionView reloadData];
}
else if ([newMediaData isKindOfClass:[JSQAudioMediaItem class]]) {
((JSQAudioMediaItem *)newMediaData).audioData = newMediaAttachmentCopy;
[self.collectionView reloadData];
}
else {
NSLog(@"%s error: unrecognized media item", __PRETTY_FUNCTION__);
}
});
}
});
}
注意:收到訊息三步驟
1.playSound 可選
2.Add new id object to your data source 把資料來源加入
3. Call finishReceivingMessage
,告訴完成了,直接重新整理資料
傳送純文字訊息,步驟和上面一樣
// 純文字傳送
- (void)didPressSendButton:(UIButton *)button
withMessageText:(NSString *)text
senderId:(NSString *)senderId
senderDisplayName:(NSString *)senderDisplayName
date:(NSDate *)date
{
/**
* Sending a message. Your implementation of this method should do *at least* the following:
*
* 1. Play sound (optional)
* 2. Add new id<JSQMessageData> object to your data source
* 3. Call `finishSendingMessage`
*/
// 套路三部曲 直接完成組裝
// [JSQSystemSoundPlayer jsq_playMessageSentSound];
JSQMessage *message = [[JSQMessage alloc] initWithSenderId:senderId
senderDisplayName:senderDisplayName
date:date
text:text];
[self.demoData.messages addObject:message];
[self finishSendingMessageAnimated:YES];
}
傳送非文字訊息,ActionSheet選擇器
// 點選左側accessory按鈕啟動actionSheet
- (void)didPressAccessoryButton:(UIButton *)sender
{
[self.inputToolbar.contentView.textView resignFirstResponder];
UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"Media messages", nil)
delegate:self
cancelButtonTitle:NSLocalizedString(@"Cancel", nil)
destructiveButtonTitle:nil
otherButtonTitles:NSLocalizedString(@"Send photo", nil), NSLocalizedString(@"Send location", nil), NSLocalizedString(@"Send video", nil), NSLocalizedString(@"Send video thumbnail", nil), NSLocalizedString(@"Send audio", nil), nil];
[sheet showFromToolbar:self.inputToolbar];
}
// 點選左側accessory按鈕彈出sheet,選擇需要傳送的事件新增到資料來源
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if (buttonIndex == actionSheet.cancelButtonIndex) {
[self.inputToolbar.contentView.textView becomeFirstResponder];
return;
}
switch (buttonIndex) {
case 0:
[self.demoData addPhotoMediaMessage];
break;
case 1:
{
__weak UICollectionView *weakView = self.collectionView;
[self.demoData addLocationMediaMessageCompletion:^{
[weakView reloadData];
}];
}
break;
case 2:
[self.demoData addVideoMediaMessage];
break;
case 3:
[self.demoData addVideoMediaMessageWithThumbnail];
break;
case 4:
[self.demoData addAudioMediaMessage];
break;
}
// [JSQSystemSoundPlayer jsq_playMessageSentSound];
[self finishSendingMessageAnimated:YES];
}
注意:傳送訊息三步驟和上面的收到的步驟一模一樣的
剩下的都是一些修飾的代理資料UI以及一些附帶事件的實現
// 傳送的人ID
- (NSString *)senderId
// 傳送人名字
- (NSString *)senderDisplayName
// 根據index返回需要載入的message物件
- (id<JSQMessageData>)collectionView:(JSQMessagesCollectionView *)collectionView messageDataForItemAtIndexPath:(NSIndexPath *)indexPath
// 刪除訊息
- (void)collectionView:(JSQMessagesCollectionView *)collectionView didDeleteMessageAtIndexPath:(NSIndexPath *)indexPath
// 聊天氣泡,根據ID判斷是傳送的還是接受的
- (id<JSQMessageBubbleImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView messageBubbleImageDataForItemAtIndexPath:(NSIndexPath *)indexPath
// 頭像
- (id<JSQMessageAvatarImageDataSource>)collectionView:(JSQMessagesCollectionView *)collectionView avatarImageDataForItemAtIndexPath:(NSIndexPath *)indexPath
// 時間UI
- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellTopLabelAtIndexPath:(NSIndexPath *)indexPath
// 除本人以外顯示bubble cell上面的名字
- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForMessageBubbleTopLabelAtIndexPath:(NSIndexPath *)indexPath
// 氣泡cell底部文字
- (NSAttributedString *)collectionView:(JSQMessagesCollectionView *)collectionView attributedTextForCellBottomLabelAtIndexPath:(NSIndexPath *)indexPath
基本上的邏輯就已經完成了,只要根據業務載入實際的邏輯就能自己做一套聊天的了,但是細節還需要完善很多,畢竟一套成熟的聊天框架是需要不斷完善的,這裡拋磚引玉,覺得可以的同學可以順手給個贊,有問題及時留言,多交流多學習總是不會錯的
相關文章
- socket實現聊天功能(二)
- WinForm的Socket實現簡單的聊天室 IMORM
- UNIX Domain Socket實現簡易聊天AI
- 微信小程式-實現實時聊天功能 前端部分微信小程式前端
- 朝花夕拾之socket的基本使用以及mina框架簡單介紹框架
- ReactNative 聊天 App 實戰|RN 仿微信介面群聊|朋友圈ReactAPP
- netty無縫切換rabbitmq、activemq、rocketmq實現聊天室單聊、群聊功能NettyMQ
- Android通過輔助功能實現搶微信紅包原理簡單介紹Android
- 基於Java的Socket類Tcp網路程式設計實現實時聊天互動程式(一):QQ聊天介面的搭建JavaTCP程式設計
- 基於Socket.IO實現Android聊天功能Android
- 如何實現仿微信介面[我的+首頁聊天列表+長按選單功能+新增選單功能]
- 企業微信JS-SDK實現會話聊天功能JS會話
- 超簡單實現iOS列表的索引功能iOS索引
- Flutter | 超簡單仿微信QQ側滑選單元件Flutter元件
- C# 簡單的聊天大廳功能及原始碼(socket通訊)C#原始碼
- SpringBoot2 整合 WebSocket 簡單實現聊天室功能Spring BootWeb
- 微信小程式 簡易搜尋功能實現微信小程式
- 微信聊天趣味玩法,把“你被移出群聊”發給好友
- RPC模式的介紹以及簡單的實現RPC模式
- Python基於Socket實現簡易多人聊天室Python
- 實現微信分享功能
- socket實現簡單ssh服務
- iOS換膚功能的簡單處理框架iOS框架
- QQ群頭像 微信群頭像 多圖合併框架實現框架
- 簡單介紹SpringMVC RESTFul實現列表功能SpringMVCREST
- vs2010編寫的簡單socket聊天
- Python使用Socket寫一個簡單聊天程式Python
- Java用UDP實現簡單聊天JavaUDP
- LeNet簡介以及Caffe實現
- 基於環信實現線上聊天功能
- php實現一個簡單的socketPHP
- 花裡胡俏地用Dart+Flutter實現簡單聊天功能DartFlutter
- 用Java程式碼實現一個簡單的聊天室功能Java
- node.js 用socket實現聊天Node.js
- 一種簡單地實現 SAP UI5 Master detail 頁面的方法UIASTAI
- RSA加密演算法簡單介紹以及python實現加密演算法Python
- phpStudy啟動介面的功能簡介PHP
- 【SpringMVC】RESTFul簡介以及案例實現SpringMVCREST
- 深入解析dio(一) Socket 程式設計實現本地多端群聊程式設計