UI Components

The Atlas Libraries ship with a number of components to simplify application development. This section explores those components in more details.

The Conversation List

The ATLConversationListViewController class is a UITableViewController that contains a list of all the conversations that the authenticated user belongs to. By default, the cell will contain a title and will show the last message text from that conversation.

Initializing

Once you have connected to Layer and authenticated the user, you can present the Conversation List View by calling conversationListViewControllerWithLayerClient.

SubclassConversationListViewController *controller = [SubclassConversationListViewController conversationListViewControllerWithLayerClient:self.layerClient];
[self presentViewController:controller animated:YES completion:nil];

Configuring Conversation Details

You can configure how each conversation displays its details by implementing the ATLConversationListViewControllerDataSource protocol. For example, here is how you would configure the conversation title:

- (NSString *)conversationListViewController:(ATLConversationListViewController *)conversationListViewController titleForConversation:(LYRConversation *)conversation {
    return @"My conversation title";
}

Other optional DataSource methods:

- (id<ATLAvatarItem>)conversationListViewController:(ATLConversationListViewController *)conversationListViewController avatarItemForConversation:(LYRConversation *)conversation;
- (NSString *)reuseIdentifierForConversationListViewController:(ATLConversationListViewController *)conversationListViewController;
- (NSString *)conversationListViewController:(ATLConversationListViewController *)conversationListViewController textForButtonWithDeletionMode:(LYRDeletionMode)deletionMode;
- (UIColor *)conversationListViewController:(ATLConversationListViewController *)conversationListViewController colorForButtonWithDeletionMode:(LYRDeletionMode)deletionMode;
- (NSString *)conversationListViewController:(ATLConversationListViewController *)conversationListViewController lastMessageTextForConversation:(LYRConversation *)conversation;

Selecting a Conversation

When the user selects a conversation, the ATLConversationListViewController notifies the ATLConversationListViewControllerDelegate of the action. This is a great time to initiate the ATLConversationViewController for that conversation.

- (void)conversationListViewController:(ATLConversationListViewController *)conversationListViewController didSelectConversation:(LYRConversation *)conversation {
    SubclassConversationViewController *controller = [SubclassConversationViewController conversationViewControllerWithLayerClient:self.layerClient];
    controller.conversation = conversation;
    controller.displaysAddressBar = YES;
    [self.navigationController pushViewController:controller animated:YES];
}

Other optional Delegate methods:

- (void)conversationListViewController:(ATLConversationListViewController *)conversationListViewController didDeleteConversation:(LYRConversation *)conversation deletionMode:(LYRDeletionMode)deletionMode;
- (void)conversationListViewController:(ATLConversationListViewController *)conversationListViewController didFailDeletingConversation:(LYRConversation *)conversation deletionMode:(LYRDeletionMode)deletionMode error:(NSError *)error;
- (void)conversationListViewController:(ATLConversationListViewController *)conversationListViewController didSearchForText:(NSString *)searchText completion:(void (^)(NSSet *filteredParticipants))completion;

Searching for Conversations

The conversation view controller implements a UISearchController to display a subset of conversations based on the inputted search text. To enable this feature you’ll need to implement the didSearchForText delegate method and provide a filtered list of ATLParticipant objects. Any conversation containing one or more of these participants will be contained in the result.

- (void)conversationListViewController:(ATLConversationListViewController *)conversationListViewController didSearchForText:(nonnull NSString *)searchText completion:(nonnull void (^)(NSSet<id<ATLParticipant>> * _Nonnull))completion
{
    LYRQuery *query = [LYRQuery queryWithQueryableClass:[LYRIdentity class]];
    query.predicate = [LYRPredicate predicateWithProperty:@"displayName" predicateOperator:LYRPredicateOperatorLike value:[NSString stringWithFormat:@"%%%@%%", searchText]];
    [self.layerClient executeQuery:query completion:^(NSOrderedSet<id<ATLParticipant>> * _Nullable resultSet, NSError * _Nullable error) {
        if (resultSet) {
            completion(resultSet.set);
        } else {
            completion([NSSet set]);
        }
    }];
}

Note

The ATLParticipant protocol above must be adopted by objects wishing to represent Layer participants in the UI.

The Conversation View

The ATLConversationViewController is a UICollectionViewController that contains all the messages in the conversation. The area at the top where the participants are listed is called the Address Bar. The area at the bottom of the screen where the user can input text, select an image, or send a location is called the Message Input Toolbar.

Initializing

The following code shows how to initialize the ATLConversationViewController when someone taps on a conversation in the ATLConversationListViewController.

SubclassConversationViewController *controller = [SubclassConversationViewController conversationViewControllerWithLayerClient:self.layerClient];
controller.conversation = conversation;
controller.displaysAddressBar = YES;
[self.navigationController pushViewController:controller animated:YES];

Note

Currently ATLConversationViewController is designed to work as part of the UINavigationController navigation stack, and in tandem with ATLConversationListViewController. If you want to show the conversation view without a conversation list you can wrap ATLConversationViewController into a UINavigationController as the rootViewController. This is useful if your app is centered around a user only having a single conversation, such as with an Agent-Client business model.

SubclassConversationViewController *controller = [SubclassConversationViewController conversationViewControllerWithLayerClient:self.layerClient];
controller.conversation = conversation;
controller.displaysAddressBar = YES;
UINavigationController *conversationViewNavController = [[UINavigationController alloc] initWithRootViewController:controller];

Configuring Conversation Elements

You can configure various UI elements by implementing the ATLConversationViewControllerDataSource protocol.

You can configure the date shown aboe each group of messages.

- (NSAttributedString *)conversationViewController:(ATLConversationViewController *)conversationViewController attributedStringForDisplayOfDate:(NSDate *)date {
    NSDictionary *attributes = @{NSFontAttributeName : [UIFont systemFontOfSize:14],
    NSForegroundColorAttributeName : [UIColor grayColor] };
    return [[NSAttributedString alloc] initWithString:[self.dateFormatter stringFromDate:date] attributes:attributes];
}

You can configure the recipient status indicator underneath the message.

- (NSAttributedString *)conversationViewController:(ATLConversationViewController *)conversationViewController attributedStringForDisplayOfRecipientStatus:(NSDictionary *)recipientStatus {
    if (recipientStatus.count == 0) return nil;
    NSMutableAttributedString *mergedStatuses = [[NSMutableAttributedString alloc] init];

    [[recipientStatus allKeys] enumerateObjectsUsingBlock:^(NSString *participant, NSUInteger idx, BOOL *stop) {
        LYRRecipientStatus status = [recipientStatus[participant] unsignedIntegerValue];
        if ([participant isEqualToString:self.layerClient.authenticatedUser.userID]) {
            return;
        }

        NSString *checkmark = @"✔︎";
        UIColor *textColor = [UIColor lightGrayColor];
        if (status == LYRRecipientStatusSent) {
            textColor = [UIColor lightGrayColor];
        } else if (status == LYRRecipientStatusDelivered) {
            textColor = [UIColor orangeColor];
        } else if (status == LYRRecipientStatusRead) {
            textColor = [UIColor greenColor];
        }

        NSAttributedString *statusString = [[NSAttributedString alloc] initWithString:checkmark attributes:@{NSForegroundColorAttributeName: textColor}];
        [mergedStatuses appendAttributedString:statusString];
    }];
    return mergedStatuses;
}

Other optional DataSource methods.

- (NSString *)conversationViewController:(ATLConversationViewController *)viewController reuseIdentifierForMessage:(LYRMessage *)message;
- (LYRConversation *)conversationViewController:(ATLConversationViewController *)viewController conversationWithParticipants:(NSSet *)participants;

Conversation Actions

You can respond to various conversation actions by implementing the ATLConversationViewControllerDelegate protocol.

You can respond when a user sends a message.

- (void)conversationViewController:(ATLConversationViewController *)viewController didSendMessage:(LYRMessage *)message {
    NSLog(@"Message Was Sent!");
}

Atlas will automatically highlight links and phone numbers. You can control what happens when a link is pressed by listening to the ATLUserDidTapLinkNotification and ATLUserDidTapPhoneNumberNotification notifications.

- (void)viewDidLoad {
    // ...
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDidTapLink:) name:ATLUserDidTapLinkNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(userDidTapPhoneNumber:) name:ATLUserDidTapPhoneNumberNotification object:nil];
    // ...
}

- (void)userDidTapLink:(NSNotification *)notification {
    [[UIApplication sharedApplication] openURL:notification.object];
}

- (void)userDidTapPhoneNumber:(NSNotification *)notification {
    [[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithFormat:@"telprompt://%@", notification.object]]];
}

Other optional Delegate methods.

- (void)conversationViewController:(ATLConversationViewController *)viewController didFailSendingMessage:(LYRMessage *)message error:(NSError *)error;
- (void)conversationViewController:(ATLConversationViewController *)viewController didSelectMessage:(LYRMessage *)message;
- (CGFloat)conversationViewController:(ATLConversationViewController *)viewController heightForMessage:(LYRMessage *)message withCellWidth:(CGFloat)cellWidth;
- (NSOrderedSet *)conversationViewController:(ATLConversationViewController *)viewController messagesForMediaAttachments:(NSArray *)mediaAttachments;

The Participants List

ATLParticipant Protocol

Layer recognizes that you might already have a User Model in your app. Atlas can work with any User Model as long as it conforms to the ATLParticipant protocol.

// The first name of the participant as it should be presented in the user interface.
@property (nonatomic, readonly) NSString *firstName;
// The last name of the participant as it should be presented in the user interface.
@property (nonatomic, readonly) NSString *lastName;
// The full name of the participant as it should be presented in the user interface.
@property (nonatomic, readonly) NSString *fullName;
// The unique identifier of the participant as it should be used for Layer addressing. This identifier is issued by the Layer identity provider backend.
@property (nonatomic, readonly) NSString *participantIdentifier;

ATLAvatar Protocol

You can also have Avatar Bubbles appear alongside Conversations and Messages using the ATLAvatar protocol. ATLParticipant includes these as optional properties, and if they return nil the Avatar will be ignored.

// The first name of the participant as it should be presented in the user interface.
@property (nonatomic, readonly) NSString *firstName;
// The last name of the participant as it should be presented in the user interface.
@property (nonatomic, readonly) NSString *lastName;
// The full name of the participant as it should be presented in the user interface.
@property (nonatomic, readonly) NSString *fullName;
// The unique identifier of the participant as it should be used for Layer addressing. This identifier is issued by the Layer identity provider backend.
@property (nonatomic, readonly) NSString *participantIdentifier;

Controllers

There are two different view controllers that need to be populated from your list of users:

Address Bar View Controller

The ATLAddressBarViewController appears at the top of the ATLConversationViewController. When creating a new conversation you can add participants by typing the name and selecting from the available users, or tapping the + button to initiate the Participant Table View Controller and select from there.

Initialization

ATLConversationViewController includes an instance of ATLAddressBarViewController by default.

Searching for Participants

You can search for participants by implementing the searchForParticipantsMatchingText delegate method and returning a list of ATLParticipant objects.

- (void)addressBarViewController:(ATLAddressBarViewController *)addressBarViewController searchForParticipantsMatchingText:(NSString *)searchText completion:(void (^)(NSArray *participants))completion
{
    LYRQuery *query = [LYRQuery queryWithQueryableClass:[LYRIdentity class]];
    query.predicate = [LYRPredicate predicateWithProperty:@"displayName" predicateOperator:LYRPredicateOperatorLike value:[NSString stringWithFormat:@"%%%@%%", searchText]];
    [self.layerClient executeQuery:query completion:^(NSOrderedSet<id<ATLParticipant>> * _Nullable resultSet, NSError * _Nullable error) {
        if (resultSet) {
            completion(resultSet.array);
        } else {
            completion([NSArray array]);
        }
    }];
}

Other optional Delegate methods for searching.

- (void)addressBarViewControllerDidBeginSearching:(ATLAddressBarViewController *)addressBarViewController;
- (void)addressBarViewControllerDidEndSearching:(ATLAddressBarViewController *)addressBarViewController;

Adding and Removing Participants

When the user taps the + button in the Address Bar, the ATLAddressBarViewController notifies the ATLAddressBarViewControllerDelegate of the action. This is a great time to initialize a ATLParticipantTableViewController to present a table of users to select from.

- (void)addressBarViewController:(ATLAddressBarViewController *)addressBarViewController didTapAddContactsButton:(UIButton *)addContactsButton
{
    // On initialization, pass in a NSSet of Objects that adhere to the ATLParticipant Protocol
    SampleParticipantTableViewController *controller = [SampleParticipantTableViewController
    participantTableViewControllerWithParticipants:[NSSet setWithArray:users] sortType:ATLParticipantPickerSortTypeFirstName];
    controller.delegate = self;

    UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:controller];
    [self.navigationController presentViewController:navigationController animated:YES completion:nil];
}

By default, Atlas will update the presented conversation and messages in the view when the participants change in the Address Bar. This is useful in order to show the user a conversation already exists and their message will be continuing this conversation instead of starting a new one. You can configure this behavior with optional ATLAddressBarViewControllerDelegate methods.

- (void)addressBarViewController:(ATLAddressBarViewController *)addressBarViewController didSelectParticipant:(id<ATLParticipant>)participant;
- (void)addressBarViewController:(ATLAddressBarViewController *)addressBarViewController didRemoveParticipant:(id<ATLParticipant>)participant;
- (void)addressBarViewControllerDidSelectWhileDisabled:(ATLAddressBarViewController *)addressBarViewController;

Participant Table View Controller

ATLParticipantTableViewController is a UITableViewController that contains all the users the current authenticated user can message.

Selecting a Participant

When the user selects a participant, the ATLParticipantTableViewController notifies the ATLParticipantTableViewControllerDelegate of the action. This is a great time to update the Address Bar.

- (void)participantTableViewController:(ATLParticipantTableViewController *)participantTableViewController didSelectParticipant:(id<ATLParticipant>)participant {
    [self.addressBarController selectParticipant:participant];
    [self.navigationController dismissViewControllerAnimated:YES completion:nil];
}

Searching for a Participant

You can search for users with the didSearchWithString delegate method.

- (void)participantTableViewController:(ATLParticipantTableViewController *)participantTableViewController didSearchWithString:(NSString *)searchText completion:(void (^)(NSSet *))completion {
    NSLog(@"Search Text: %@",searchText);
    // Search your users here (Pseudocode)
    [[UserManager sharedManager] queryForUserWithName:searchText completion:^(NSArray *participants, NSError *error) {
        if (!error) {
            if (completion) completion([NSSet setWithArray:participants]);
        } else {
            NSLog(@"Error search for participants: %@", error);
        }
    }];
}

Other optional Delegate methods.

- (void)participantTableViewController:(ATLParticipantTableViewController *)participantTableViewController didDeselectParticipant:(id<ATLParticipant>)participant;
UI Concepts Customization