# Manage chat messages After a chat message arrives, you can read its metadata, delete it, and control who can chat through host privileges. ## Read message metadata Every `onChatNewMessageNotify` callback receives a `ZMVideoSDKChatMessage`. In addition to the sender and content shown in [Core features](/docs/video-sdk/macos/chat/), the message exposes the following fields. ```swift let messageID = chatMessage.messageID // String — unique ID; required to delete the message. let timeMessageWasSent = chatMessage.timeStamp // time_t — when the message was sent. let isChatToAll = chatMessage.isChatToAll // Bool — true for public messages, false for private. let currentUserIsSender = chatMessage.isSelfSend // Bool — true if the current user sent the message. let recipient = chatMessage.receiverUser // ZMVideoSDKUser? — the recipient of a private message; nil when isChatToAll is true. ``` ```objectivec chatMessage.messageID; // NSString — unique ID; required to delete the message. chatMessage.timeStamp; // time_t — when the message was sent. chatMessage.isChatToAll; // BOOL — true for public messages, false for private. chatMessage.isSelfSend; // BOOL — true if the current user sent the message. chatMessage.receiverUser; // ZMVideoSDKUser — the recipient of a private message; nil when isChatToAll is true. ``` ## Delete a chat message A user can delete a message they sent, and a host can delete any message in the session. A compliance system can also delete a message under a data-loss-prevention policy. Before exposing a delete control for a given message, ask the SDK whether it can be deleted in the current context. ```swift guard let chatHelper = ZMVideoSDK.shared()?.getChatHelper() else { return } let messageID = chatMessage.messageID if chatHelper.canChatMessageBeDeleted(messageID) { let result = chatHelper.deleteChatMessage(messageID) if result != .ZMVideoSDKErrors_Success { // Surface the error to the user. } } ``` ```objectivec ZMVideoSDKChatHelper *chatHelper = [[ZMVideoSDK sharedVideoSDK] getChatHelper]; NSString *messageID = chatMessage.messageID; if ([chatHelper canChatMessageBeDeleted:messageID]) { ZMVideoSDKErrors result = [chatHelper deleteChatMessage:messageID]; if (result != ZMVideoSDKErrors_Success) { // Surface the error to the user. } } ``` ### Listen for deletions When any message is deleted, the SDK fires `onChatMsgDeleteNotification` on every user's device. Remove the message from your UI in this callback so it stays in sync regardless of who initiated the delete. ```swift func onChatMsgDeleteNotification(_ chatHelper: ZMVideoSDKChatHelper!, messageID msgID: String!, deleteBy type: ZMVideoSDKChatMessageDeleteType) { // Remove the message with id msgID from your UI. // Optionally show who removed it using type. } ``` ```objectivec - (void)onChatMsgDeleteNotification:(ZMVideoSDKChatHelper *)chatHelper messageID:(NSString *)msgID deleteBy:(ZMVideoSDKChatMessageDeleteType)type { // Remove the message with id msgID from your UI. // Optionally show who removed it using type. } ``` ### Delete-initiator values `ZMVideoSDKChatMessageDeleteType` tells you who deleted the message. | Value | Meaning | | ---------------------------------------- | --------------------------------------------------------------------------------------- | | `ZMVideoSDKChatMessageDeleteType_None` | Initialization value, not used in a real callback. | | `ZMVideoSDKChatMessageDeleteType_BySelf` | The message author deleted their own message. | | `ZMVideoSDKChatMessageDeleteType_ByHost` | The session host deleted the message. | | `ZMVideoSDKChatMessageDeleteType_ByDlp` | A Data Loss Prevention (DLP) policy removed the message for violating compliance rules. | ## Host control: chat privileges The session host (or a session manager) can change what kind of chat participants are allowed using `changeChatPrivilege`. Privilege changes fire `onChatPrivilegeChanged` on every user's device. Use that callback to enable or disable your composer. ```swift guard let chatHelper = ZMVideoSDK.shared()?.getChatHelper() else { return } // Read the current privilege (any user). let privilege = chatHelper.getChatPrivilege() // Change the privilege (host or manager only). let result = chatHelper.changeChatPrivilege(.publicly) if result != .ZMVideoSDKErrors_Success { // Privilege change was rejected — most commonly because the local // user isn't the host or manager. } ``` ```objectivec ZMVideoSDKChatHelper *chatHelper = [[ZMVideoSDK sharedVideoSDK] getChatHelper]; // Read the current privilege (any user). ZMVideoSDKChatPrivilegeType privilege = [chatHelper getChatPrivilege]; // Change the privilege (host or manager only). ZMVideoSDKErrors result = [chatHelper changeChatPrivilege:ZMVideoSDKChatPrivilegeType_Publicly]; if (result != ZMVideoSDKErrors_Success) { // Privilege change was rejected — most commonly because the local // user isn't the host or manager. } ``` ### Privilege values `ZMVideoSDKChatPrivilegeType` controls what kinds of chat participants can send. | Value | Effect | | ---------------------------------------------------- | ----------------------------------------------------------------- | | `ZMVideoSDKChatPrivilegeType_Unknown` | Uninitialized. Should not appear in a real session. | | `ZMVideoSDKChatPrivilegeType_Publicly_And_Privately` | Participants can send public and private chat. | | `ZMVideoSDKChatPrivilegeType_Publicly` | Participants can send public chat only. Private messaging is off. | | `ZMVideoSDKChatPrivilegeType_No_One` | Chat is fully disabled for non-host participants. | ### React to privilege changes Handle `onChatPrivilegeChanged` and update your composer to match the new privilege. ```swift func onChatPrivilegeChanged(_ chatHelper: ZMVideoSDKChatHelper!, chatPrivilegeType privilege: ZMVideoSDKChatPrivilegeType) { switch privilege { case .no_One: hideChatComposer() case .publicly: showComposerWithoutPrivateOption() case .publicly_And_Privately: showFullComposer() default: break } } ``` ```objectivec - (void)onChatPrivilegeChanged:(ZMVideoSDKChatHelper *)chatHelper chatPrivilegeType:(ZMVideoSDKChatPrivilegeType)privilege { switch (privilege) { case ZMVideoSDKChatPrivilegeType_No_One: [self hideChatComposer]; break; case ZMVideoSDKChatPrivilegeType_Publicly: [self showComposerWithoutPrivateOption]; break; case ZMVideoSDKChatPrivilegeType_Publicly_And_Privately: [self showFullComposer]; break; default: break; } } ``` If you only need a boolean "is chat available right now" check rather than the specific privilege value, the `isChatDisabled` and `isPrivateChatDisabled` helpers on `ZMVideoSDKChatHelper` are easier to read at the call site.