> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quickblox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Messaging

> Learn how to send and receive messages, mark messages as delivered or read, etc.

## Before you begin

1. Register a [QuickBlox account](https://admin.quickblox.com/signin). This is a matter of a few minutes and you will be able to use this account to build your apps.
2. Configure QuickBlox SDK for your app. Check out [Setup](/sdks/ios-setup) page for more details.
3. Create a user session to be able to use QuickBlox functionality. See [Authentication](/sdks/ios-authentication) page to learn how to do it.
4. Connect to the Chat server. See [Connection](/sdks/ios-chat-connection) page to learn how to do it.
5. Create a dialog. See [Dialogs](/sdks/ios-chat-dialogs) page to learn how to do it.

Visit [Key Concepts](/docs/key-concepts) page to learn the most important QuickBlox concepts.

## Subscribe message events

Add the event delegate to receive messages in real-time using the `addDelegate()` method. The event delegate enables the app to listen to message events associated with receiving a message, delivery receipts, and read receipts.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    QBChat.instance.addDelegate(self)
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    [QBChat.instance addDelegate: self];
    ```
  </Tab>
</Tabs>

You should also implement the `QBChatDelegate` methods in your chat controller to track events in your chat.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    /// MARK: - QBChatDelegate
    extension YourViewController: QBChatDelegate {
        // MARK: - Manage chat receive message callback's
        func chatRoomDidReceive(_ message: QBChatMessage, fromDialogID dialogID: String) {
            // Called whenever group chat dialog did receive a message.
            // !!!note Will be called on both recipients' and senders' device (with corrected time from server)
        }
        func chatDidReceive(_ message: QBChatMessage) {
            // Called whenever new private message was received from QBChat.
            // !!!note Will be called only on recipient device
        }
    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    /// MARK: - QBChatDelegate
    // MARK: - Manage chat receive message callback's
    - (void)chatDidReceiveMessage:(QBChatMessage *)message {
        // Called whenever new private message was received from QBChat.
        // !!!note Will be called only on recipient device
    }
    - (void)chatRoomDidReceiveMessage:(QBChatMessage *)message fromDialogID (NSString*)dialogID {
        // Called whenever group chat dialog did receive a message.
        // !!!note Will be called on both recepients' and senders' device (with corrected time from server)
    }
    ```
  </Tab>
</Tabs>

## Send text message

To send a message to a **private** dialog, use the code snippet below.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    let message = QBChatMessage()
    message.text = "How are you today?"
    message.customParameters["save_to_history"] = true
    let privateDialog = ...
    privateDialog.send(message) { (error) in
    }

    //MARK: ChatDelegate
    func chatDidReceive(_ message: QBChatMessage) {
     }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    QBChatMessage *message = [[QBChatMessage alloc] init];
    message.text = @"How are you today?";
    message.customParameters[@"save_to_history"] = @"1";
    QBChatDialog *privateDialog = ...;
    [privateDialog sendMessage:message completionBlock:^(NSError * _Nullable error) {
    }];

    //MARK: ChatDelegate
    - (void)chatDidReceiveMessage:(QBChatMessage *)message {
    } (edited)
    ```
  </Tab>
</Tabs>

To send a message to a **group** or **public** dialog, use the code snippet below.

<Tip>
  You need to join the **group** and **public** dialog by calling the `join()` method before you start chatting in a dialog. Once the dialog is joined, you can receive/send messages. See [this section](/sdks/ios-chat-dialogs#join-dialog) to learn how to join the dialog.
</Tip>

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    let message = QBChatMessage()
    message.text = "How are you today?"
    message.customParameters["save_to_history"] = true
    let groupDialog = ...
    groupDialog.send(message) { (error) in
    }

    //MARK: ChatDelegate
    func chatRoomDidReceive(_ message: QBChatMessage, fromDialogID dialogID: String) {
     }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    QBChatMessage *message = [[QBChatMessage alloc] init];
    message.text = @"How are you today?";
    message.customParameters[@"save_to_history"] = @"1";
    QBChatDialog *groupDialog = ...;
    [groupDialog sendMessage:message completionBlock:^(NSError * _Nullable error) {
    }];

    //MARK: QBChatDelegate
    - (void)chatRoomDidReceiveMessage:(QBChatMessage *)message fromDialogID:(NSString *)dialogID {
    }
    ```
  </Tab>
</Tabs>

<Tip>
  Make sure to set the `save_to_history` as `true` to save the message on the server. If the `save_to_history` is set as `false`, the message won't be saved on the server. However, the message will be delivered to the user in either case.
</Tip>

## Send message with attachment

Chat attachments are supported by the [content API](/sdks/ios-content). In order to send a chat attachment, you need to upload the file to QuickBlox cloud storage and obtain a link to the file (file UID). Then you need to include this UID into the chat message and send it.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    let url = URL(fileURLWithPath:"file_path")
    //for image
    QBRequest.uploadFile(with: url, fileName: fileName, contentType: "image/png", isPublic: true, successBlock: { (response, uploadedBlob) in

        let attachment = QBChatAttachment()
        attachment.id = uploadedBlob.uid
        attachment.name = uploadedBlob.name
        //for image
        attachment.type = "image"
        attachment.url = uploadedBlob.publicUrl()

        let message = QBChatMessage()
        message.text = "Image Attachment"
        //Set attachment
        message.attachments = [attachment]
        //Send message with attachment
        chatDialog.send(message, completionBlock: { (error) in
        })
    }, statusBlock: { (request, status)  in
        //Update UI with upload progress
    }, errorBlock: { (response) in
        //show upload error
    })
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    NSURL *fileUrl = [NSURL fileURLWithPath:@"file_path"]; //Local file url
    //for image
    [QBRequest uploadFileWithUrl:fileUrl fileName:@"image.png" contentType:@"image/png" isPublic:YES successBlock:^(QBResponse * _Nonnull response, QBCBlob * _Nonnull blob) {
        //Create attachment
        QBChatAttachment *attachment = [[QBChatAttachment alloc] init];
        attachment.ID = blob.UID;
        //for image
        attachment.type = @"image";
        attachment.url = blob.publicUrl;
        //Create message
        QBChatMessage *message = [[QBChatMessage alloc] init];
        message.text = @"Image attachment";
        //Set attachment
        message.attachments = @[attachment];
        //Send message with attachment
        [chatDialog sendMessage:message completionBlock:^(NSError * _Nullable error) {}];
    } statusBlock:^(QBRequest * _Nonnull request, QBRequestStatus * _Nonnull status) {
        //Update UI with upload progress
    } errorBlock:^(QBResponse * _Nonnull response) {
        //show upload error
    }];
    ```
  </Tab>
</Tabs>

The same flow is supported on the message receiver's side. When you receive a message with an attachment, you need to get the file UID, and then download the file from the cloud storage.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    func chatDidReceive(_ message: QBChatMessage) {
        if let attachment = message.attachments?.first,
            let uid = attachment.id {
            QBRequest.downloadFile(withUID: uid, successBlock: { (response, data) in
                //process file data
            }, statusBlock: { (response, status) in
                //Update UI with dowonload progress
            }, errorBlock: { (response) in
                //show download error
            })
        }
    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    - (void)chatDidReceiveMessage:(QBChatMessage *)message {
        QBChatAttachment *attachment = message.attachments.firstObject;
        [QBRequest downloadFileWithUID:attachment.ID successBlock:^(QBResponse * _Nonnull response, NSData * _Nonnull fileData) {
            //process file data
        } statusBlock:^(QBRequest * _Nonnull request, QBRequestStatus * _Nonnull status) {
            //Update UI with download progress
        } errorBlock:^(QBResponse * _Nonnull response) {
            //show download error
        }];
    }
    ```
  </Tab>
</Tabs>

## Send message with extra data

You have an option to extend the message with additional fields. Specify one or more **key-value** items in the `message`. Using these items, you can implement the ability for a user to send self-location information to another user or notification messages signifying that a user has left a group, etc.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    let message = QBChatMessage()
    message.text = "How are you today?"
    message.customParameters["save_to_history"] = "1"
    message.customParameters["customParam1"] = "book"
    message.customParameters["customParam2"] = "21"

    let dialog = ...
    dialog.send(message) { (error) in

    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    QBChatMessage *message = [[QBChatMessage alloc] init];
    message.text = @"How are you today?";
    message.customParameters[@"save_to_history"] = @"1";
    message.customParameters[@"customParam1"] = @"book";
    message.customParameters[@"customParam2"] = @"21";

    QBChatDialog *dialog = ...;
    [dialog sendMessage:message completionBlock:^(NSError * _Nullable error) {

    }];
    ```
  </Tab>
</Tabs>

| Argument | Required | Description                                  |
| -------- | -------- | -------------------------------------------- |
| message  | yes      | Specifies message fields that should be set. |

Set the following fields of the `message`:

| Field            | Required | Description                                                                                                                                                       |
| ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| text             | no       | Message text.                                                                                                                                                     |
| customParameters | no       | Extra data. Specify any key-value pairs. In each pair, the key and value are both string values. Set the save\_to\_history as 1 to save a message to the history. |

## Retrieve chat history

Every dialog stores its chat history that you can retrieve using the `messages(withDialogID:extendedRequest:)` method. The request below will return messages for a specific dialog, sorted by the `date_sent` field in descending order, limited to 50 dialogs on the page.

<Tabs>
  <Tab title="Swift Concurrency">
    ```Swift theme={null}
    let page = QBResponsePage(limit: 50, skip: 100)
    let extendedRequest = ["sort_desc": "date_sent", "mark_as_read": "0"]
    let result = try await QBRequest.messages(withDialogID: "5356c64ab35c12bd3b108a41", extendedRequest: extendedRequest, for: page)
    ```
  </Tab>

  <Tab title="Swift">
    ```Swift theme={null}
    let page = QBResponsePage(limit: 50, skip: 100)
    let extendedRequest = ["sort_desc": "date_sent", "mark_as_read": "0"]
    QBRequest.messages(withDialogID: "5356c64ab35c12bd3b108a41", extendedRequest: extendedRequest, for: page, successBlock: { (response, messages, page) in

    }, errorBlock: { (response) in

    })
    ```
  </Tab>

  <Tab title="Objective-C">
    ```objc theme={null}
    QBResponsePage *responsePage = [QBResponsePage responsePageWithLimit:50 skip:0];
    NSDictionary *extReq = @{@"sort_desc": @"date_sent", @"mark_as_read": @"0"};
    [QBRequest messagesWithDialogID:@"5356c64ab35c12bd3b108a41" extendedRequest:extReq forPage:responsePage successBlock:^(QBResponse *response, NSArray *messages, QBResponsePage *page) {

    } errorBlock:^(QBResponse *response) {

    }];
    ```
  </Tab>
</Tabs>

<Warning>
  If you want to mark all retrieved chat messages as a read, set the `mark_as_read` parameter as `1` in the `extendedRequest`. If you decide not to mark chat messages as read, just set the `mark_as_read` parameter as `0`.
</Warning>

If you want to retrieve only messages updated after some specific date time and order the search results, you can apply operators. This is useful if you cache messages somehow and do not want to obtain the whole list of messages on every app start. Thus, you can apply [search](/sdks/ios-chat-messaging#search-operators) and [sort](/sdks/ios-chat-messaging#sort-operators) operators to list messages on the page so that it is easier to view specific messages. The operators are set as key-value parameters in the `extendedRequest` dictionary.

If you want to get a paginated list of messages from the server, you can set the key-value parameters in the `extendedRequest` dictionary.

| Pagination parameter | Description                                                                                 |
| -------------------- | ------------------------------------------------------------------------------------------- |
| skip                 | Skip N records in search results. Useful for pagination. Default (if not specified): **0**. |
| limit                | Limit search results to N records. Useful for pagination. Default value: **100**.           |

### Search operators

You can use search operators to get more specific search results.

| Search operators | Applicable to types  | Applicable to fields                                 | Description                                          |
| ---------------- | -------------------- | ---------------------------------------------------- | ---------------------------------------------------- |
| lt               | number, string, date | date\_sent, sender\_id, recipient\_id, updated\_at   | **Less Than** operator.                              |
| lte              | number, string, date | date\_sent, sender\_id, recipient\_id, updated\_at   | **Less Than** or **Equal** to operator.              |
| gt               | number, string, date | date\_sent, sender\_id, recipient\_id, updated\_at   | **Greater Than** operator.                           |
| gte              | number, string, date | date\_sent, sender\_id, recipient\_id, updated\_at   | **Greater Than** or **Equal** to operator.           |
| ne               | number, string, date | \_id, message, date\_sent, sender\_id, recipient\_id | **Not Equal** to operator.                           |
| in               | number, string, date | date\_sent, sender\_id, recipient\_id                | **IN** array operator.                               |
| nin              | number, string, date | date\_sent, sender\_id, recipient\_id                | Not **IN** array operator.                           |
| or               | number, string, date | date\_sent, sender\_id, recipient\_id                | All records that contain a value 1 **or** value 2.   |
| ctn              | string               | message                                              | All records that **contain** a particular substring. |

### Sort operators

You can use sort operators to order the search results.

| Sort operator | Applicable to types | Description                                                               |
| ------------- | ------------------- | ------------------------------------------------------------------------- |
| sort\_asc     | All types           | Search results will be sorted in ascending order by the specified field.  |
| sort\_desc    | All types           | Search results will be sorted in descending order by the specified field. |

## Update message

Update the message text using the `updateMessage(withID:text:dialogID:)` method below.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    let messageId = "5b23aa4f5d0b0be0900041aa"
    let text = "Edited text"
    let dialogId = "5356c64ab35c12bd3b108a41"
    QBRequest.updateMessage(withID: messageId, text: text, dialogID: dialogId, successBlock: { (response) in

    }, errorBlock: { (response) in

    })
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    NSString *messageId = @"5b23aa4f5d0b0be0900041aa";
    NSString *text = @"Edited text";
    NSString *dialogId = @"5356c64ab35c12bd3b108a41";
    [QBRequest updateMessageWithID: messageId text: text dialogID: dialogId successBlock:^(QBResponse * _Nonnull response) {

    } errorBlock:^(QBResponse * _Nonnull response) {

    }];
    ```
  </Tab>
</Tabs>

## Delete message

Any user in the `occupantIDs` can delete a message from the dialog. As a result, the message will be deleted from the current user history, without affecting the histories of other users.

The owner of the dialog can completely remove messages from all users' histories. This is achieved by setting the `forAllUsers` parameter to `true`.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    let messages: Set<String> = ["5b23aa4f5d0b0be0900041aa", "bc23aa4f5d0b0be0900041ad"]
    QBRequest.deleteMessages(withIDs: messages, forAllUsers: forAllUsers, successBlock: { (response) in

    }, errorBlock: { (response) in

    })
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    NSSet *messages = [NSSet setWithArray:@[@"5b23aa4f5d0b0be0900041aa", @"bc23aa4f5d0b0be0900041ad"]];
    [QBRequest deleteMessagesWithIDs:messages forAllUsers:NO successBlock:^(QBResponse * _Nonnull response) {

    } errorBlock:^(QBResponse * _Nonnull response) {

    }];
    ```
  </Tab>
</Tabs>

| Argument   | Required | Description                                                                                            |
| ---------- | -------- | ------------------------------------------------------------------------------------------------------ |
| messages   | yes      | A set of strings with messages IDs                                                                     |
| forAllUser | yes      | A boolean parameter. Delete message for everyone. Set it as true to perform. Only the owner can do it. |

## Check if a message is sent

The message is considered as **sent** if it has been delivered to the server. To get to know that a message has been delivered to the server, make sure to enable a stream management before connecting to the Chat server. See [this section](/sdks/ios-setup#stream-management) to learn how to enable the stream management.

Thus, you send a message to the server and if no error is returned, it is considered as **sent** (by default). There is no field for a sent status in the message model.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    chatDialog.send(message) { (error) in
        if error == nil {
            // status - 'sent'
        }
    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    QBChatDialog *chatDialog = ...;
    [chatDialog sendMessage:message completionBlock:^(NSError * _Nullable error) {
        if(!error) {
            //status - 'sent'
        }
    }];
    ```
  </Tab>
</Tabs>

<Warning>
  You should enable Stream Management before you do the `login()` because the Stream Management is initialized while Chat login is performed.

  The Stream Management defines an extension for active management of a stream between a client and server, including features for stanza acknowledgments.
</Warning>

## Mark message as delivered

As a sender, you may want to be informed that a message has been successfully delivered to the recipient. The mark-as-delivered functionality allows to notify the sender about message delivery.

To track the event when the message has been delivered to the user, use the `chatDidDeliverMessage(messageID:dialogID:)` method of the `QBChatDelegate` delegate.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    // ...
    QBChat.instance.addDelegate(self)
    // ...

    //MARK: QBChatDelegate
    func chatDidDeliverMessage(withID messageID: String, dialogID: String, toUserID userID: UInt) {

    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    // ...
    [QBChat.instance addDelegate:self];
    // ...

    //MARK: QBChatDelegate
    - (void)chatDidDeliverMessageWithID:(NSString *)messageID dialogID:(NSString *)dialogID toUserID:(NSUInteger)userID {

    }
    ```
  </Tab>
</Tabs>

Use the `mark(asDelivered:)` method to mark a message as delivered. As a result, the server will notify a sender about the delivery receipt.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    let receivedMessage: QBChatMessage = ...

    QBChat.instance.mark(asDelivered: receivedMessage) { (error) in

    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    QBChatMessage *receivedMessage = ...;

    [QBChat.instance markAsDelivered:receivedMessage completion:^(NSError * _Nullable error){

    }];
    ```
  </Tab>
</Tabs>

| Argument        | Required | Description                         |
| --------------- | -------- | ----------------------------------- |
| receivedMessage | yes      | A message received from the sender. |

A message can be marked as delivered automatically by the server once a message is successfully delivered to the recipient. Set the `markable` as `true` using the `send()` method if you want, as a sender, to receive message delivery receipts from other recipients. Thus, the `markable` parameter enables the sender to request the delivery receipt. It also enables the recipient to confirm the message delivery. However, if `markable` is `false` or omitted, then you can notify a sender about the delivery receipt using the `mark(asDelivered:)` method.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    let message = QBChatMessage()
    message.markable = true
    message.text = "How are you today?"

    chatDialog.send(message) { (error) in

    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    QBChatMessage *message = [QBChatMessage new];
    message.markable = YES;
    message.text = @"How are you today?";

    [chatDialog sendMessage:message completionBlock:^(NSError * _Nullable error) {

    }];

    ```
  </Tab>
</Tabs>

<Tip>
  Make sure to understand, that marking-as-delivered operation just confirms the fact of message delivery. The message acquires the **delivered** status when the message delivered event is received.

  When a message is marked as delivered, the IDs of users who have received the message are stored in the message model, on the server. Thus, you can request a chat history from the server to get to know who received the message using the `messages(withDialogID:)` method. See [this section](/sdks/ios-chat-messaging#retrieve-chat-history) to learn how to retrieve chat history.
</Tip>

You can also update the `delivered_ids` field in the message model, on the server, by using the method below. As a result, the ID of the user who has received the message will be added to the array of `delivered_ids` field. However, the server won't notify the user about the change in the message model.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    let dialog = ...
    let dialogID = dialog.id
    let receivedMessage: QBChatMessage = ...
    let receivedMessageID: String = receivedMessage.id
    let markMessagesAsDeliveredSet = Set<String>([receivedMessageID])

    QBRequest.markMessages(asDelivered: markMessagesAsDeliveredSet, dialogID: dialogID, successBlock: { (response) in

    }, errorBlock: { (response) in

    })
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    QBChatDialog *dialog = ...;
    NSString *dialogID = dialog.ID;
    QBChatMessage *receivedMessage = ...;
    NSString *receivedMessageID = receivedMessage.ID;
    NSSet<NSString *> *markMessagesAsDeliveredSet = [NSSet setWithArray:@[receivedMessageID]

    [QBRequest markMessagesAsDelivered:markMessagesAsDeliveredSet dialogID:dialogID successBlock:^(QBResponse * _Nonnull response) {

    } errorBlock:^(QBResponse * _Nonnull response) {

    }];
    ```
  </Tab>
</Tabs>

| Argument                   | Required | Description                         |
| -------------------------- | -------- | ----------------------------------- |
| markMessagesAsDeliveredSet | yes      | A set of strings with messages IDs. |
| dialogID                   | yes      | The ID of the dialog.               |

## Mark message as read

As a sender, you may want to be informed that a message has been read by the recipient. The mark-as-read functionality allows to notify the sender that a message has been read.

To track the event when the message has been read by the user, the `chatDidReadMessage(messageID:dialogID:readerID:)` method of the `QBChatDelegate` is used.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    //MARK: QBChatDelegate
    func chatDidReadMessage(withID messageID: String, dialogID: String, readerID: UInt) {

    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    //MARK: QBChatDelegate
    - (void)chatDidReadMessageWithID:(NSString *)messageID dialogID:(NSString *)dialogID readerID:(NSUInteger)readerID {

    }
    ```
  </Tab>
</Tabs>

Use the `read()` method to mark a message as read. As a result, the server will notify a sender about the read receipt.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    let receivedMessage: QBChatMessage = ...

    QBChat.instance.read(receivedMessage) { (error) in

    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    QBChatMessage *receivedMessage = ...;

    [QBChat.instance readMessage:receivedMessage completion:^(NSError * _Nullable error) {

    }
    ```
  </Tab>
</Tabs>

| Argument        | Required | Description                         |
| --------------- | -------- | ----------------------------------- |
| receivedMessage | yes      | A message received from the sender. |

<Check>
  When a message is marked as read, the IDs of users who have read the message are stored in the message model, on the server. Thus, you can request a chat history from the server to get to know who read the message using the `messages(withDialogID:)` method. See [this section](/sdks/ios-chat-messaging#retrieve-chat-history) to learn how to retrieve chat history.
</Check>

You can also update the `read_ids` field in the message model, on the server, by using the method below. As a result, the ID of the user who has read the message will be added to the array of `read_ids` field. However, the server won't notify the user about the change in the message model.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    let dialog = ...
    let dialogID = dialog.id
    let receivedMessage: QBChatMessage = ...
    let receivedMessageID: String = receivedMessage.id
    let markMessagesAsReadSet = Set<String>([receivedMessageID])

    QBRequest.markMessages(asRead: markMessagesAsReadSet, dialogID: dialogID, successBlock: { (response) in

    }, errorBlock: { (response) in

    })
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    QBChatDialog *dialog = ...;
    NSString *dialogID = dialog.ID;
    QBChatMessage *receivedMessage = ...;
    NSString *receivedMessageID = receivedMessage.ID;
    NSSet<NSString *> *markMessagesAsReadSet = [NSSet setWithArray:@[receivedMessageID]

    [QBRequest markMessagesAsRead:markMessagesAsReadSet dialogID:dialogID successBlock:^(QBResponse * _Nonnull response) {

    } errorBlock:^(QBResponse * _Nonnull response) {

    }];
    ```
  </Tab>
</Tabs>

| Argument              | Required | Description                                  |
| --------------------- | -------- | -------------------------------------------- |
| markMessagesAsReadSet | yes      | A set of strings with received messages IDs. |
| dialogID              | yes      | The ID of the dialog that received messages. |

## Send typing indicators

You may want, as a sender, to let the recipient know that you are typing the message or have stopped typing the message. Use typing indicators as a form of chat-specific presence. Typing indicators allow to indicate if users are typing messages in a dialog at the moment.

There are the following **typing** notifications supported.

* **typing**. The user is composing a message. The user is actively interacting with a message input interface specific to this chat session (for example, by typing in the input area of a chat window).
* **stopped**. The user had been composing but now has stopped. The user has been composing but has not interacted with the message input interface for a short period of time (for example, 30 seconds).

To track the event when the sender is typing the message, use the `onUserIsTyping(userID:)` method of the delegate.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    chatDialog.onUserIsTyping = { (userID: UInt) in

    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    chatDialog.onUserIsTyping = ^(NSUInteger userID) {

    };
    ```
  </Tab>
</Tabs>

To track the event when the sender has stopped typing, use the `onUserStoppedTyping(userID:)` method of the delegate.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    chatDialog.onUserStoppedTyping = { (userID: UInt) in

    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    chatDialog.onUserStoppedTyping = ^(NSUInteger userID) {

    };
    ```
  </Tab>
</Tabs>

To notify a recipient that a sender is typing the message, use the `sendUserIsTyping()` method. As a result, the server will notify a recipient about the event.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    chatDialog.sendUserIsTyping()
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    [chatDialog sendUserIsTyping];
    ```
  </Tab>
</Tabs>

To notify a recipient that a sender had been composing a message but now has stopped, use the `sendUserStopTyping()` method. As a result, the server will notify a recipient about the event.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    chatDialog.sendUserStopTyping()
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    [chatDialog sendUserStoppedTyping];
    ```
  </Tab>
</Tabs>

## Send system messages

There is a way to send system messages to other users about some events. For example, a system message can be sent when a user has joined or left a group dialog. System messages are handled over a separate channel and are not mixed up with regular chat messages. Thus, they are handled by `chatDidReceiveSystemMessage()` callback of `QBChatDelegate`. See [this section](/sdks/ios-chat-messaging#subscribe-message-events) to learn how to add `QBChatDelegate`.

System messages are also not shown in the dialog history and, consequently, are not stored on the server. This means that these messages will be delivered **only** to online users. Send system messages using the `sendSystemMessage()` method.

<Tabs>
  <Tab title="Swift">
    ```Swift theme={null}
    enum MessageType : String {
        case createGroupDialog = "1"
        case addUsersToGroupDialog = "2"
        case leaveGroupDialog = "3"
    }

    var message: QBChatMessage = QBChatMessage()
    let params = NSMutableDictionary()
    params["notification_type"] = MessageType.leaveGroupDialog.rawValue
    params["dialog_id"] = "5356c64ab35c12bd3b108a41"
    message.customParameters = params
    message.recipientID = 344


    QBChat.instance.sendSystemMessage(message) { (error) in
    }


    #pragma mark -
    #pragma mark QBChatDelegate

    func chatDidReceiveSystemMessage(message: QBChatMessage!) {

    }
    ```
  </Tab>

  <Tab title="Objective-C">
    ```Objective-C theme={null}
    typedef NS_ENUM(NSUInteger, NotificationMessageType) {
        NotificationMessageTypeCreate = 1,
        NotificationMessageTypeAdding = 2,
        NotificationMessageTypeLeave = 3
    };

    QBChatMessage *message = [QBChatMessage message];

    NSMutableDictionary *params = [NSMutableDictionary dictionary];
    params[@"notification_type"] = [NSString stringWithFormat:@"%@", @(NotificationMessageTypeLeave)];
    params[@"dialog_id"] = @"5356c64ab35c12bd3b108a41";
    [message setCustomParameters:params];

    [message setRecipientID:344];

    [[QBChat instance] sendSystemMessage:message completion:^(NSError * _Nullable error) {

    }];


    #pragma mark -
    #pragma mark QBChatDelegate

    - (void)chatDidReceiveSystemMessage:(QBChatMessage *)message{

    }
    ```
  </Tab>
</Tabs>

| Argument | Description                                         |
| -------- | --------------------------------------------------- |
| message  | Specifies system message fields that should be set. |

Set the following fields of the `message`:

| Field            | Required | Description                                                                                              |
| ---------------- | -------- | -------------------------------------------------------------------------------------------------------- |
| recipientID      | yes      | ID of the recipient.                                                                                     |
| customParameters | no       | Extra data. Specify one or more key-value pairs. In each pair, the key and value are both string values. |
