Mentions in Comments

Enable powerful user tagging in comments with mentions that automatically notify users, support custom rendering, and provide rich autocomplete experiences. Mentions enhance engagement by connecting users and facilitating targeted conversations.
Mentions automatically trigger push notifications to tagged users and support custom metadata for rich mention rendering with avatars, names, and custom styling.

Architecture Overview

Mention Components

ComponentDescriptionPurpose
DetectionIdentify @ symbols and potential mentionsTrigger autocomplete
SearchFind users matching typed textProvide selection options
MetadataStore mention position and user infoEnable custom rendering
RenderingDisplay mentions with custom stylingVisual differentiation
NotificationsAlert mentioned usersDrive engagement

Create Comments with Mentions

Integrate user mentions into comment creation with proper metadata for notifications and rendering.
import AmitySDK

struct MentionData {
    let userId: String
    let displayName: String
    let index: Int
    let length: Int
}

class CommentMentionManager {
    private let commentRepository: AmityCommentRepository
    private let userRepository: AmityUserRepository
    
    init(client: AmityClient) {
        self.commentRepository = AmityCommentRepository(client: client)
        self.userRepository = AmityUserRepository(client: client)
    }
    
    // Create comment with mentions
    func createCommentWithMentions(
        referenceId: String,
        referenceType: AmityCommentReferenceType,
        text: String,
        mentions: [MentionData],
        parentId: String? = nil,
        completion: @escaping (Result<AmityComment, Error>) -> Void
    ) {
        // Extract user IDs from mentions
        let mentionUserIds = mentions.map { $0.userId }
        
        // Create mention metadata for proper rendering
        let mentionMetadata = createMentionMetadata(from: mentions, in: text)
        
        let builder = AmityCommentCreationDataBuilder()
        builder.setText(text)
        builder.setMentionUsers(mentionUserIds)
        builder.setMetadata(mentionMetadata)
        builder.setParentId(parentId)
        
        commentRepository.createComment(
            for: referenceId,
            referenceType: referenceType,
            with: builder.build()
        ).observeOnce { result in
            completion(result)
        }
    }
    
    // Create mention metadata for custom rendering
    private func createMentionMetadata(from mentions: [MentionData], in text: String) -> [String: Any] {
        let mentionArray = mentions.map { mention in
            return [
                "userId": mention.userId,
                "displayName": mention.displayName,
                "type": "user",
                "index": mention.index,
                "length": mention.length
            ]
        }
        
        return [
            "mentions": mentionArray,
            "originalText": text
        ]
    }
    
    // Search users for autocomplete
    func searchUsers(
        query: String,
        completion: @escaping (Result<[AmityUser], Error>) -> Void
    ) {
        let userQuery = AmityUserQuery.Builder()
            .setDisplayName(query)
            .setLimit(10)
            .build()
        
        userRepository.getUsers(with: userQuery).observeOnce { result in
            switch result {
            case .success(let userCollection):
                let users = Array(userCollection.allObjects())
                completion(.success(users))
            case .failure(let error):
                completion(.failure(error))
            }
        }
    }
    
    // Update comment with mentions
    func updateCommentWithMentions(
        commentId: String,
        newText: String,
        mentions: [MentionData],
        completion: @escaping (Result<AmityComment, Error>) -> Void
    ) {
        let mentionUserIds = mentions.map { $0.userId }
        let mentionMetadata = createMentionMetadata(from: mentions, in: newText)
        
        let editor = AmityCommentEditor(client: commentRepository.client)
        editor.edit(commentId: commentId)
            .setText(newText)
            .setMentionUsers(mentionUserIds)
            .setMetadata(mentionMetadata)
            .update { result in
                completion(result)
            }
    }
    
    // Remove mentions from comment
    func removeMentionsFromComment(
        commentId: String,
        newText: String,
        completion: @escaping (Result<AmityComment, Error>) -> Void
    ) {
        let editor = AmityCommentEditor(client: commentRepository.client)
        editor.edit(commentId: commentId)
            .setText(newText)
            .setMentionUsers([]) // Empty array removes all mentions
            .setMetadata([:])    // Empty metadata
            .update { result in
                completion(result)
            }
    }
}

// Custom text view with mention support
class MentionTextView: UITextView {
    var onMentionTriggered: ((String) -> Void)?
    var onUserSelected: ((AmityUser) -> Void)?
    
    private var currentMentionRange: NSRange?
    private var mentionData: [MentionData] = []
    
    override func awakeFromNib() {
        super.awakeFromNib()
        delegate = self
    }
    
    // Parse text for mentions and apply styling
    func applyMentionStyling() {
        let attributedText = NSMutableAttributedString(string: text)
        
        // Reset styling
        attributedText.addAttribute(.foregroundColor, value: UIColor.label, range: NSRange(location: 0, length: text.count))
        
        // Apply mention styling
        for mention in mentionData {
            let range = NSRange(location: mention.index, length: mention.length)
            attributedText.addAttributes([
                .foregroundColor: UIColor.systemBlue,
                .font: UIFont.boldSystemFont(ofSize: font?.pointSize ?? 16)
            ], range: range)
        }
        
        self.attributedText = attributedText
    }
    
    // Insert mention at current position
    func insertMention(_ user: AmityUser) {
        guard let mentionRange = currentMentionRange else { return }
        
        let mentionText = "@\(user.displayName ?? user.userId)"
        let newText = (text as NSString).replacingCharacters(in: mentionRange, with: mentionText)
        
        // Update mention data
        let mention = MentionData(
            userId: user.userId,
            displayName: user.displayName ?? user.userId,
            index: mentionRange.location,
            length: mentionText.count
        )
        
        mentionData.append(mention)
        text = newText
        
        applyMentionStyling()
        currentMentionRange = nil
    }
    
    // Get current mentions for comment creation
    func getCurrentMentions() -> [MentionData] {
        return mentionData
    }
}

extension MentionTextView: UITextViewDelegate {
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        // Detect @ symbol for mention triggering
        if text == "@" {
            currentMentionRange = NSRange(location: range.location, length: 1)
            onMentionTriggered?("")
            return true
        }
        
        // Handle mention typing
        if let mentionRange = currentMentionRange, text != " " && text != "\n" {
            let currentQuery = (textView.text as NSString).substring(with: NSRange(
                location: mentionRange.location + 1,
                length: range.location - mentionRange.location - 1
            )) + text
            
            onMentionTriggered?(currentQuery)
        }
        
        return true
    }
}

// Usage in view controller
class CommentComposeViewController: UIViewController {
    @IBOutlet weak var mentionTextView: MentionTextView!
    @IBOutlet weak var userSuggestionsTableView: UITableView!
    
    private let mentionManager: CommentMentionManager
    private var suggestedUsers: [AmityUser] = []
    private let postId: String
    
    init(postId: String, client: AmityClient) {
        self.postId = postId
        self.mentionManager = CommentMentionManager(client: client)
        super.init(nibName: nil, bundle: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupMentionHandling()
    }
    
    private func setupMentionHandling() {
        mentionTextView.onMentionTriggered = { [weak self] query in
            self?.searchUsersForMention(query: query)
        }
        
        mentionTextView.onUserSelected = { [weak self] user in
            self?.mentionTextView.insertMention(user)
            self?.hideSuggestions()
        }
    }
    
    private func searchUsersForMention(query: String) {
        guard !query.isEmpty else {
            hideSuggestions()
            return
        }
        
        mentionManager.searchUsers(query: query) { [weak self] result in
            DispatchQueue.main.async {
                switch result {
                case .success(let users):
                    self?.showUserSuggestions(users)
                case .failure(let error):
                    print("Failed to search users: \(error)")
                    self?.hideSuggestions()
                }
            }
        }
    }
    
    private func showUserSuggestions(_ users: [AmityUser]) {
        suggestedUsers = users
        userSuggestionsTableView.reloadData()
        userSuggestionsTableView.isHidden = false
    }
    
    private func hideSuggestions() {
        userSuggestionsTableView.isHidden = true
        suggestedUsers.removeAll()
    }
    
    @IBAction func postComment() {
        let text = mentionTextView.text ?? ""
        let mentions = mentionTextView.getCurrentMentions()
        
        mentionManager.createCommentWithMentions(
            referenceId: postId,
            referenceType: .post,
            text: text,
            mentions: mentions
        ) { [weak self] result in
            DispatchQueue.main.async {
                switch result {
                case .success:
                    self?.navigationController?.popViewController(animated: true)
                case .failure(let error):
                    self?.showError("Failed to post comment: \(error.localizedDescription)")
                }
            }
        }
    }
}

Advanced Mention Features

Best Practices

Performance Optimization

  • Cache user search results to reduce API calls
  • Implement debounced search for autocomplete
  • Use virtual scrolling for large user lists
  • Optimize mention detection algorithms

User Experience

  • Provide visual feedback during mention typing
  • Show user avatars in autocomplete suggestions
  • Implement keyboard navigation for suggestions
  • Support both @ and # mention triggers

Accessibility

  • Provide proper screen reader support for mentions
  • Implement keyboard shortcuts for mention actions
  • Use semantic HTML for web implementations
  • Support high contrast mode for mention styling

Data Management

  • Validate mention data before sending
  • Handle deleted/deactivated user mentions gracefully
  • Implement proper mention metadata structure
  • Cache frequently mentioned users

Mention Data Structure

interface Mention {
    userId: string;
    displayName: string;
    type: 'user' | 'channel' | 'custom';
    index: number;        // Position in text
    length: number;       // Length of mention text
    metadata?: {
        avatar?: string;
        verified?: boolean;
        customData?: Record<string, any>;
    };
}

interface MentionMetadata {
    mentions: Mention[];
    originalText: string;
    renderingHints?: {
        showAvatars: boolean;
        customStyling: Record<string, any>;
    };
}

interface Comment {
    commentId: string;
    text: string;
    mentionUsers: string[];      // Array of mentioned user IDs
    metadata: MentionMetadata;   // Rich mention data
    // ...other comment properties
}

Error Handling

Error TypeDescriptionRecommended Action
USER_NOT_FOUNDMentioned user doesn’t existRemove mention or show placeholder
PERMISSION_DENIEDCannot mention userShow permission error message
MENTION_LIMIT_EXCEEDEDToo many mentions in commentEnforce mention limits
INVALID_MENTION_FORMATMalformed mention dataValidate and sanitize mentions
NETWORK_ERRORFailed to search usersImplement retry with offline cache

Use Cases

Mention Rendering

For comprehensive mention rendering support including custom styling, hover cards, and interactive elements, refer to our Core Concepts - Mentions documentation which covers:
  • Custom Mention Objects: Rich mention data structures
  • Rendering Patterns: Platform-specific rendering implementations
  • Interactive Features: Click handlers, hover cards, and user profiles
  • Styling Guidelines: Design system integration and theming
  • Accessibility Support: Screen reader compatibility and keyboard navigation
Mentions automatically trigger push notifications to tagged users. Ensure your app has proper notification permissions and handles notification preferences per user settings.