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

# Recommendations

> Intelligent user recommendations and suggestions

# Recommendations

The ContactsManager SDK provides intelligent recommendation features to enhance your app's social experience. These recommendations help users discover connections in different contexts, like finding users already using your app or suggesting people they might know.

## Types of Recommendations

The SDK offers several recommendation types:

1. **Contacts to Invite**: Identify contacts who would benefit from using your app
2. **App Users**: Find contacts who are already using your app
3. **People You Might Know**: Discover potential connections based on shared contacts

## Contacts to Invite

Get a list of contacts that would be good to invite to your app:

<CodeGroup>
  ```swift Swift theme={null}
  do {
      let recommendations = try await ContactsService.shared
          .getSharedContactsByUsersToInvite(limit: 10)
      
      for recommendation in recommendations {
          let contact = recommendation.contact
          let score = recommendation.score
          let reason = recommendation.reason
          
          print("\(contact.displayName ?? "Unknown") - Score: \(score) - " + 
                "Reason: \(reason)")
      }
  } catch {
      print("Error getting invitation recommendations: \(error.localizedDescription)")
  }
  ```

  ```jsx React Native theme={null}
  import { getInviteRecommendations } from '@contactsmanager/rn';

  try {
    // Get invite recommendations with a limit of 10
    const recommendations = await getInviteRecommendations(10);
    
    // Display the recommendations
    recommendations.forEach(recommendation => {
      const { contact, score, reason } = recommendation;
      console.log(`${contact.displayName || 'Unknown'} - Score: ${score} - Reason: ${reason}`);
    });
  } catch (error) {
    console.error('Error getting invitation recommendations:', error);
  }
  ```

  ```kotlin Kotlin theme={null}
  try {
      val recommendations = RecommendationService.getInstance(context)
          .getSharedContactsByUsersToInvite(limit = 10)
          .getOrThrow()
      
      recommendations.forEach { recommendation ->
          val contact = recommendation.contact
          val score = recommendation.score
          val reason = recommendation.reason
          
          println("${contact.displayName ?: "Unknown"} - Score: $score - Reason: $reason")
      }
  } catch (e: Exception) {
      println("Error getting invitation recommendations: ${e.localizedDescription}")
  }
  ```

  ```objectivec Objective-C theme={null}
  CMRecommendationService *recommendationService = [[CMContactService sharedInstance] recommendationService];
  [recommendationService getSharedContactsByUsersToInviteWithLimit:10
                                                       completion:^(NSArray<CMContactRecommendation *> * _Nullable recommendations, 
                                                                  NSError * _Nullable error) {
      if (error) {
          NSLog(@"Error getting invitation recommendations: %@", error.localizedDescription);
          return;
      }
      
      for (CMContactRecommendation *recommendation in recommendations) {
          CMContact *contact = recommendation.contact;
          double score = recommendation.score;
          NSString *reason = recommendation.reason;
          
          NSLog(@"%@ - Score: %.2f - Reason: %@", 
                contact.displayName ?: @"Unknown",
                score,
                reason);
      }
  }];
  ```
</CodeGroup>

### Recommendation Scores

Each recommendation includes a score between 0.0 and 1.0 that indicates how relevant the recommendation is. Higher scores indicate stronger recommendations.

## App Users

Find contacts who are already using your app:

<CodeGroup>
  ```swift Swift theme={null}
  do {
      let appUsers = try await ContactsService.shared.getContactsUsingApp(limit = 20)
      
      print("Found \(appUsers.count) contacts using the app")
      
      for appUser in appUsers {
          let contact = appUser.contact
          let organizationUserId = appUser.canonicalContact.organizationUserId
          
          print("\(contact?.displayName ?? "Unknown") - " + 
                "User ID: \(organizationUserId)")
      }
  } catch {
      print("Error getting app users: \(error.localizedDescription)")
  }
  ```

  ```jsx React Native theme={null}
  import { getContactsUsingApp } from '@contactsmanager/rn';

  try {
    // Get contacts who are already using the app with a limit of 20
    const appUsers = await getContactsUsingApp(20);
    
    console.log(`Found ${appUsers.length} contacts using the app`);
    
    // Display the app users
    appUsers.forEach(appUser => {
      const { contact, canonicalContact } = appUser;
      const organizationUserId = canonicalContact.organizationUserId;
      
      console.log(`${contact?.displayName || 'Unknown'} - User ID: ${organizationUserId}`);
    });
  } catch (error) {
    console.error('Error getting app users:', error);
  }
  ```

  ```kotlin Kotlin theme={null}
  try {
      val appUsers = RecommendationService.getInstance(context)
          .getContactsUsingApp(limit = 20)
          .getOrThrow()
      
      println("Found ${appUsers.size} contacts using the app")
      
      appUsers.forEach { appUser ->
          val contact = appUser.contact
          val organizationUserId = appUser.canonicalContact.organizationUserId
          
          println("${contact?.displayName ?: "Unknown"} - User ID: $organizationUserId")
      }
  } catch (e: Exception) {
      println("Error getting app users: ${e.localizedDescription}")
  }
  ```

  ```objectivec Objective-C theme={null}
  CMRecommendationService *recommendationService = [[CMContactService sharedInstance] recommendationService];
  [recommendationService getContactsUsingAppWithLimit:20
                                         completion:^(NSArray<CMLocalCanonicalContact *> * _Nullable contacts, 
                                                    NSError * _Nullable error) {
      if (error) {
          NSLog(@"Error getting app users: %@", error.localizedDescription);
          return;
      }
      
      NSLog(@"Found %lu contacts using the app", (unsigned long)contacts.count);
      
      for (CMLocalCanonicalContact *appUser in contacts) {
          CMContact *contact = appUser.contact;
          NSString *organizationUserId = appUser.canonicalContact.organizationUserId;
          
          NSLog(@"%@ - User ID: %@", 
                contact.displayName ?: @"Unknown",
                organizationUserId);
      }
  }];
  ```
</CodeGroup>

### Using Organization User IDs

The `organizationUserId` property allows you to connect the local contact with their server-side identity, which is useful for social features.

## People You Might Know

Discover potential connections based on mutual contact information:

<CodeGroup>
  ```swift Swift theme={null}
  do {
      let connections = try await ContactsService.shared.getUsersYouMightKnow(limit: 15)
      
      print("Found \(connections.count) people you might know")
      
      for user in connections {
          print("\(user.fullName) - " + 
                "\(user.email ?? user.phone ?? "No contact info")")
      }
  } catch {
      print("Error getting connection recommendations: \(error.localizedDescription)")
  }
  ```

  ```jsx React Native theme={null}
  import { getUsersYouMightKnow } from '@contactsmanager/rn';

  try {
    // Get users you might know with a limit of 15
    const connections = await getUsersYouMightKnow(15);
    
    console.log(`Found ${connections.length} people you might know`);
    
    // Display the people you might know
    connections.forEach(user => {
      console.log(`${user.fullName} - ${user.email || user.phone || 'No contact info'}`);
    });
  } catch (error) {
    console.error('Error getting connection recommendations:', error);
  }
  ```

  ```kotlin Kotlin theme={null}
  try {
      val connections = RecommendationService.getInstance(context)
          .getUsersYouMightKnow(limit = 15)
          .getOrThrow()
      
      println("Found ${connections.size} people you might know")
      
      connections.forEach { user ->
          println("${user.fullName} - ${user.email ?: user.phone ?: "No contact info"}")
      }
  } catch (e: Exception) {
      println("Error getting connection recommendations: ${e.localizedDescription}")
  }
  ```

  ```objectivec Objective-C theme={null}
  CMRecommendationService *recommendationService = [[CMContactService sharedInstance] recommendationService];
  [recommendationService getUsersYouMightKnowWithLimit:15
                                          completion:^(NSArray<CMCanonicalContact *> * _Nullable contacts, 
                                                     NSError * _Nullable error) {
      if (error) {
          NSLog(@"Error getting connection recommendations: %@", error.localizedDescription);
          return;
      }
      
      NSLog(@"Found %lu people you might know", (unsigned long)contacts.count);
      
      for (CMCanonicalContact *user in contacts) {
          NSString *contactInfo = user.email ?: user.phone ?: @"No contact info";
          NSLog(@"%@ - %@", user.fullName, contactInfo);
      }
  }];
  ```
</CodeGroup>

## Building a Recommendation UI

Here's an example of how to build a recommendations UI:

<CodeGroup>
  ```swift Swift theme={null}
  struct RecommendationsView: View {
      @State private var inviteRecommendations: [ContactRecommendation] = []
      @State private var appUsers: [LocalCanonicalContact] = []
      @State private var peopleYouMightKnow: [CanonicalContact] = []
      @State private var isLoading = true
      @State private var error: Error?
      
      var body: some View {
          ScrollView {
              if isLoading {
                  ProgressView("Loading recommendations...")
                      .padding()
              } else if let error = error {
                  VStack {
                      Text("Error loading recommendations")
                          .font(.headline)
                      Text(error.localizedDescription)
                          .foregroundColor(.red)
                      Button("Try Again") {
                          loadRecommendations()
                      }
                      .padding()
                  }
              } else {
                  VStack(alignment: .leading, spacing: 20) {
                      // App Users Section
                      if !appUsers.isEmpty {
                          Text("People you know using the app")
                              .font(.headline)
                              .padding(.horizontal)
                          
                          ScrollView(.horizontal, showsIndicators: false) {
                              HStack(spacing: 12) {
                                  ForEach(appUsers, id: \.canonicalContact.id) { user in
                                      AppUserCard(user: user)
                                  }
                              }
                              .padding(.horizontal)
                          }
                      }
                      
                      // People You Might Know Section
                      if !peopleYouMightKnow.isEmpty {
                          Text("People you might know")
                              .font(.headline)
                              .padding(.horizontal)
                          
                          ScrollView(.horizontal, showsIndicators: false) {
                              HStack(spacing: 12) {
                                  ForEach(peopleYouMightKnow, id: \.id) { user in
                                      PeopleYouMightKnowCard(user: user)
                                  }
                              }
                              .padding(.horizontal)
                          }
                      }
                      
                      // Invite Recommendations Section
                      if !inviteRecommendations.isEmpty {
                          Text("Invite to the app")
                              .font(.headline)
                              .padding(.horizontal)
                          
                          ScrollView(.horizontal, showsIndicators: false) {
                              HStack(spacing: 12) {
                                  ForEach(inviteRecommendations, id: \.contact.id) { 
                                      recommendation in
                                      InviteRecommendationCard(
                                          recommendation: recommendation
                                      )
                                  }
                              }
                              .padding(.horizontal)
                          }
                      }
                  }
              }
          }
          .navigationTitle("Recommendations")
          .onAppear {
              loadRecommendations()
          }
      }
  }
  ```

  ```kotlin Kotlin theme={null}
  class RecommendationsViewModel : ViewModel() {
      private val recommendationService = RecommendationService.getInstance(context)
      
      fun loadRecommendations(
          onLoading: (Boolean) -> Unit,
          onError: (String) -> Unit,
          onSuccess: (List<ContactRecommendation>, List<LocalCanonicalContact>, List<CanonicalContact>) -> Unit
      ) {
          viewModelScope.launch {
              onLoading(true)
              try {
                  // Load all recommendation types in parallel
                  val inviteDeferred = async { recommendationService.getSharedContactsByUsersToInvite(10).getOrThrow() }
                  val appUsersDeferred = async { recommendationService.getContactsUsingApp(20).getOrThrow() }
                  val peopleDeferred = async { recommendationService.getUsersYouMightKnow(15).getOrThrow() }
                  
                  val (invite, users, people) = awaitAll(inviteDeferred, appUsersDeferred, peopleDeferred)
                  onSuccess(invite, users, people)
              } catch (e: Exception) {
                  onError(e.localizedDescription)
              } finally {
                  onLoading(false)
              }
          }
      }
  }

  @Composable
  fun RecommendationsScreen(
      viewModel: RecommendationsViewModel = viewModel()
  ) {
      var isLoading by remember { mutableStateOf(true) }
      var error by remember { mutableStateOf<String?>(null) }
      var inviteRecommendations by remember { mutableStateOf<List<ContactRecommendation>>(emptyList()) }
      var appUsers by remember { mutableStateOf<List<LocalCanonicalContact>>(emptyList()) }
      var peopleYouMightKnow by remember { mutableStateOf<List<CanonicalContact>>(emptyList()) }

      LaunchedEffect(Unit) {
          viewModel.loadRecommendations(
              onLoading = { isLoading = it },
              onError = { error = it },
              onSuccess = { invite, users, people ->
                  inviteRecommendations = invite
                  appUsers = users
                  peopleYouMightKnow = people
              }
          )
      }

      Column(
          modifier = Modifier
              .fillMaxSize()
              .verticalScroll(rememberScrollState())
      ) {
          if (isLoading) {
              Box(
                  modifier = Modifier.fillMaxSize(),
                  contentAlignment = Alignment.Center
              ) {
                  CircularProgressIndicator()
              }
          } else if (error != null) {
              ErrorView(
                  error = error!!,
                  onRetry = {
                      error = null
                      isLoading = true
                      viewModel.loadRecommendations(
                          onLoading = { isLoading = it },
                          onError = { error = it },
                          onSuccess = { invite, users, people ->
                              inviteRecommendations = invite
                              appUsers = users
                              peopleYouMightKnow = people
                          }
                      )
                  }
              )
          } else {
              // App Users Section
              if (appUsers.isNotEmpty()) {
                  RecommendationSection(
                      title = "People you know using the app",
                      items = appUsers
                  ) { user ->
                      AppUserCard(user = user)
                  }
              }

              // People You Might Know Section
              if (peopleYouMightKnow.isNotEmpty()) {
                  RecommendationSection(
                      title = "People you might know",
                      items = peopleYouMightKnow
                  ) { user ->
                      PeopleYouMightKnowCard(user = user)
                  }
              }

              // Invite Recommendations Section
              if (inviteRecommendations.isNotEmpty()) {
                  RecommendationSection(
                      title = "Invite to the app",
                      items = inviteRecommendations
                  ) { recommendation ->
                      InviteRecommendationCard(recommendation = recommendation)
                  }
              }
          }
      }
  }
  ```

  ```objectivec Objective-C theme={null}
  @interface CMRecommendationsViewController : UIViewController

  @property (nonatomic, strong) CMRecommendationService *recommendationService;
  @property (nonatomic, strong) UIScrollView *scrollView;
  @property (nonatomic, strong) UIStackView *contentStackView;
  @property (nonatomic, strong) UIActivityIndicatorView *loadingIndicator;
  @property (nonatomic, strong) UIView *errorView;
  @property (nonatomic, strong) NSArray<CMContactRecommendation *> *inviteRecommendations;
  @property (nonatomic, strong) NSArray<CMLocalCanonicalContact *> *appUsers;
  @property (nonatomic, strong) NSArray<CMCanonicalContact *> *peopleYouMightKnow;

  @end

  @implementation CMRecommendationsViewController

  - (void)viewDidLoad {
      [super viewDidLoad];
      
      self.title = @"Recommendations";
      self.view.backgroundColor = UIColor.systemBackgroundColor;
      
      // Initialize recommendation service
      self.recommendationService = [[CMContactService sharedInstance] recommendationService];
      
      // Setup UI
      [self setupUI];
      
      // Load recommendations
      [self loadRecommendations];
  }

  - (void)setupUI {
      // Setup scroll view
      self.scrollView = [[UIScrollView alloc] init];
      self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
      [self.view addSubview:self.scrollView];
      
      // Setup content stack view
      self.contentStackView = [[UIStackView alloc] init];
      self.contentStackView.axis = UILayoutConstraintAxisVertical;
      self.contentStackView.spacing = 20;
      self.contentStackView.translatesAutoresizingMaskIntoConstraints = NO;
      [self.scrollView addSubview:self.contentStackView];
      
      // Setup loading indicator
      self.loadingIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleLarge];
      self.loadingIndicator.translatesAutoresizingMaskIntoConstraints = NO;
      self.loadingIndicator.hidesWhenStopped = YES;
      [self.view addSubview:self.loadingIndicator];
      
      // Setup constraints
      [NSLayoutConstraint activateConstraints:@[
          [self.scrollView.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
          [self.scrollView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
          [self.scrollView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
          [self.scrollView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor],
          
          [self.contentStackView.topAnchor constraintEqualToAnchor:self.scrollView.topAnchor],
          [self.contentStackView.leadingAnchor constraintEqualToAnchor:self.scrollView.leadingAnchor],
          [self.contentStackView.trailingAnchor constraintEqualToAnchor:self.scrollView.trailingAnchor],
          [self.contentStackView.bottomAnchor constraintEqualToAnchor:self.scrollView.bottomAnchor],
          [self.contentStackView.widthAnchor constraintEqualToAnchor:self.scrollView.widthAnchor],
          
          [self.loadingIndicator.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
          [self.loadingIndicator.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor]
      ]];
  }

  - (void)loadRecommendations {
      [self.loadingIndicator startAnimating];
      self.contentStackView.hidden = YES;
      
      dispatch_group_t group = dispatch_group_create();
      
      // Load invite recommendations
      dispatch_group_enter(group);
      [self.recommendationService getSharedContactsByUsersToInviteWithLimit:10 completion:^(NSArray<CMContactRecommendation *> * _Nullable recommendations, NSError * _Nullable error) {
          if (!error) {
              self.inviteRecommendations = recommendations;
          }
          dispatch_group_leave(group);
      }];
      
      // Load app users
      dispatch_group_enter(group);
      [self.recommendationService getContactsUsingAppWithLimit:20 completion:^(NSArray<CMLocalCanonicalContact *> * _Nullable contacts, NSError * _Nullable error) {
          if (!error) {
              self.appUsers = contacts;
          }
          dispatch_group_leave(group);
      }];
      
      // Load people you might know
      dispatch_group_enter(group);
      [self.recommendationService getUsersYouMightKnowWithLimit:15 completion:^(NSArray<CMCanonicalContact *> * _Nullable contacts, NSError * _Nullable error) {
          if (!error) {
              self.peopleYouMightKnow = contacts;
          }
          dispatch_group_leave(group);
      }];
      
      // Update UI when all requests complete
      dispatch_group_notify(group, dispatch_get_main_queue(), ^{
          [self.loadingIndicator stopAnimating];
          [self updateUI];
      });
  }

  - (void)updateUI {
      // Clear existing content
      for (UIView *view in self.contentStackView.arrangedSubviews) {
          [view removeFromSuperview];
      }
      
      // Add sections
      if (self.appUsers.count > 0) {
          [self addSectionWithTitle:@"People you know using the app" items:self.appUsers type:CMRecommendationTypeAppUsers];
      }
      
      if (self.peopleYouMightKnow.count > 0) {
          [self addSectionWithTitle:@"People you might know" items:self.peopleYouMightKnow type:CMRecommendationTypeUsersYouMightKnow];
      }
      
      if (self.inviteRecommendations.count > 0) {
          [self addSectionWithTitle:@"Invite to the app" items:self.inviteRecommendations type:CMRecommendationTypeInviteRecommendations];
      }
      
      self.contentStackView.hidden = NO;
  }

  - (void)addSectionWithTitle:(NSString *)title items:(NSArray *)items type:(CMRecommendationType)type {
      // Add section title
      UILabel *titleLabel = [[UILabel alloc] init];
      titleLabel.text = title;
      titleLabel.font = [UIFont systemFontOfSize:20 weight:UIFontWeightSemibold];
      titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
      [titleLabel setContentHuggingPriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
      
      UIView *titleContainer = [[UIView alloc] init];
      [titleContainer addSubview:titleLabel];
      
      [NSLayoutConstraint activateConstraints:@[
          [titleLabel.topAnchor constraintEqualToAnchor:titleContainer.topAnchor constant:16],
          [titleLabel.leadingAnchor constraintEqualToAnchor:titleContainer.leadingAnchor constant:16],
          [titleLabel.trailingAnchor constraintEqualToAnchor:titleContainer.trailingAnchor constant:-16],
          [titleLabel.bottomAnchor constraintEqualToAnchor:titleContainer.bottomAnchor]
      ]];
      
      [self.contentStackView addArrangedSubview:titleContainer];
      
      // Add horizontal scroll view for items
      UIScrollView *horizontalScrollView = [[UIScrollView alloc] init];
      horizontalScrollView.showsHorizontalScrollIndicator = NO;
      horizontalScrollView.translatesAutoresizingMaskIntoConstraints = NO;
      
      UIStackView *itemsStackView = [[UIStackView alloc] init];
      itemsStackView.axis = UILayoutConstraintAxisHorizontal;
      itemsStackView.spacing = 12;
      itemsStackView.translatesAutoresizingMaskIntoConstraints = NO;
      [horizontalScrollView addSubview:itemsStackView];
      
      [NSLayoutConstraint activateConstraints:@[
          [itemsStackView.topAnchor constraintEqualToAnchor:horizontalScrollView.topAnchor],
          [itemsStackView.leadingAnchor constraintEqualToAnchor:horizontalScrollView.leadingAnchor constant:16],
          [itemsStackView.trailingAnchor constraintEqualToAnchor:horizontalScrollView.trailingAnchor constant:-16],
          [itemsStackView.bottomAnchor constraintEqualToAnchor:horizontalScrollView.bottomAnchor],
          [itemsStackView.heightAnchor constraintEqualToAnchor:horizontalScrollView.heightAnchor]
      ]];
      
      // Add items
      for (id item in items) {
          UIView *card = [self createCardViewForItem:item type:type];
          [itemsStackView addArrangedSubview:card];
      }
      
      [self.contentStackView addArrangedSubview:horizontalScrollView];
      [horizontalScrollView.heightAnchor constraintEqualToConstant:160].active = YES;
  }

  - (UIView *)createCardViewForItem:(id)item type:(CMRecommendationType)type {
      UIView *card = [[UIView alloc] init];
      card.backgroundColor = UIColor.systemBackgroundColor;
      card.layer.cornerRadius = 8;
      card.layer.shadowColor = UIColor.blackColor.CGColor;
      card.layer.shadowOffset = CGSizeMake(0, 2);
      card.layer.shadowRadius = 4;
      card.layer.shadowOpacity = 0.1;
      
      [card.widthAnchor constraintEqualToConstant:100].active = YES;
      
      // Avatar container
      UIView *avatarContainer = [[UIView alloc] init];
      avatarContainer.translatesAutoresizingMaskIntoConstraints = NO;
      avatarContainer.backgroundColor = UIColor.systemGray5Color;
      avatarContainer.layer.cornerRadius = 40;
      avatarContainer.clipsToBounds = YES;
      [card addSubview:avatarContainer];
      
      // Name label
      UILabel *nameLabel = [[UILabel alloc] init];
      nameLabel.translatesAutoresizingMaskIntoConstraints = NO;
      nameLabel.textAlignment = NSTextAlignmentCenter;
      nameLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
      nameLabel.numberOfLines = 1;
      [card addSubview:nameLabel];
      
      // Action button
      UIButton *actionButton = [UIButton buttonWithType:UIButtonTypeSystem];
      actionButton.translatesAutoresizingMaskIntoConstraints = NO;
      actionButton.layer.cornerRadius = 12;
      actionButton.titleLabel.font = [UIFont systemFontOfSize:12 weight:UIFontWeightSemibold];
      [card addSubview:actionButton];
      
      // Configure based on type
      switch (type) {
          case CMRecommendationTypeAppUsers: {
              CMLocalCanonicalContact *user = (CMLocalCanonicalContact *)item;
              nameLabel.text = user.contact.displayName ?: @"Unknown";
              [actionButton setTitle:@"Follow" forState:UIControlStateNormal];
              actionButton.backgroundColor = UIColor.systemBlueColor;
              [actionButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
              break;
          }
          case CMRecommendationTypeUsersYouMightKnow: {
              CMCanonicalContact *user = (CMCanonicalContact *)item;
              nameLabel.text = user.fullName;
              [actionButton setTitle:@"Connect" forState:UIControlStateNormal];
              actionButton.backgroundColor = UIColor.systemBlueColor;
              [actionButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
              break;
          }
          case CMRecommendationTypeInviteRecommendations: {
              CMContactRecommendation *recommendation = (CMContactRecommendation *)item;
              nameLabel.text = recommendation.contact.displayName ?: @"Unknown";
              [actionButton setTitle:@"Invite" forState:UIControlStateNormal];
              actionButton.backgroundColor = UIColor.systemGreenColor;
              [actionButton setTitleColor:UIColor.whiteColor forState:UIControlStateNormal];
              break;
          }
      }
      
      // Layout constraints
      [NSLayoutConstraint activateConstraints:@[
          [avatarContainer.topAnchor constraintEqualToAnchor:card.topAnchor constant:8],
          [avatarContainer.centerXAnchor constraintEqualToAnchor:card.centerXAnchor],
          [avatarContainer.widthAnchor constraintEqualToConstant:80],
          [avatarContainer.heightAnchor constraintEqualToConstant:80],
          
          [nameLabel.topAnchor constraintEqualToAnchor:avatarContainer.bottomAnchor constant:8],
          [nameLabel.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:4],
          [nameLabel.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-4],
          
          [actionButton.topAnchor constraintEqualToAnchor:nameLabel.bottomAnchor constant:8],
          [actionButton.leadingAnchor constraintEqualToAnchor:card.leadingAnchor constant:8],
          [actionButton.trailingAnchor constraintEqualToAnchor:card.trailingAnchor constant:-8],
          [actionButton.heightAnchor constraintEqualToConstant:32],
          [actionButton.bottomAnchor constraintEqualToAnchor:card.bottomAnchor constant:-8]
      ]];
      
      return card;
  }

  @end
  ```
</CodeGroup>

## Best Practices

1. **Load Recommendations Asynchronously**: Use async/await to load recommendations without blocking the UI
2. **Implement Pagination**: For large contact lists, use the `limit` parameter to paginate results
3. **Handle Permission Changes**: Reload recommendations when contact permissions change
4. **Cache Results Temporarily**: Avoid excessive API calls by caching results for a short period
5. **Show Loading States**: Always show appropriate loading indicators during network operations

## Troubleshooting

### Common Issues

1. **Empty Recommendations**
   * Ensure the user has granted contacts access
   * Verify that contacts have been synced with `syncContacts()`
   * Check if the user has enough contacts for meaningful recommendations

2. **Missing User Information**
   * Make sure contacts have proper phone numbers or email addresses
   * Ensure the server has up-to-date user information

3. **Poor Recommendation Quality**
   * Increase the contacts sync frequency
   * Encourage users to complete their profiles
   * Consider implementing a feedback mechanism for recommendations
