I'm developing a messaging app and would like the predictive keyboard in iOS 8 to recognize that the user who is composing a message is replying to the previous message.
So I want to be able to feed a string to the keyboard to provide it context for prediction. So if a user is asked a question that can be interpreted as polar (yes / no), then the predictive keyboard should have the Yes | No | Maybe
Is this available to developers?
Note that I'm not talking about a custom keyboard, just feeding the standard keyboard some context for it's predictions. I'm also not concerned with actually customizing the quick type replies as in this question. I just want the keyboard to know what it's typing about.
Strictly inputting suggestions to the default keyboard is not possible. However, if you want to give the equivalent experience for the user, I would hide the iOS suggestion bar using myTextView.autocorrectionType = UITextAutocorrectionTypeNo;
and then replace that view with my own custom view that mimics the suggestion view until. Once the user types a character or selects an option, then hide the custom suggestion view and re-enable the iOS suggestion bar.
I Subclassed UIInputView just for this (the transparency and the transition is a little off, but everything else works pretty well).
#import <UIKit/UIKit.h>
@protocol SuggestionViewDelegate <NSObject>
@required
- (void)suggestionSelected:(NSString *)suggestion;
@end
@interface SuggestionView : UIInputView
- (instancetype)init;
- (instancetype)initWithFrame:(CGRect)frame;
/**
* The list of suggestions being displayed.
* The array contains 0-3 strings.
*
* @return Array of NSString's representing the current suggested strings
*/
- (NSArray *)suggestions;
/**
* Add a suggestion to display in the view.
* If there are already maxSuggestionCount suggestions, the added suggestion will push one of them out.
* If there are already maxSuggestionCount suggestions and the input is 'nil' then the last suggestion will be removed.
*
* @param suggestion String to suggest to the user
*/
- (void)addSuggestion:(NSString *)suggestion;
/**
* Removes the suggestion from the list of displayed suggestions.
* If the string is not in the set then there is no change made.
*
* @param suggestion NSString to remove from the suggested strings
*/
- (void)removeSuggestion:(NSString *)suggestion;
/**
* Takes in either NSArray or NSSet and replaces 'suggestions' with the input.
* Only the first three arguments are recognized.
* Objects should be strings. Undefined behavior otherwise.
*
* @param suggestions NSArray or NSSet with 0-3 NSStrings
*/
- (void)setSuggestions:(NSObject *)suggestions;
@property (weak) id <SuggestionViewDelegate> delegate;
/**
* The maximum number of suggestions allowed. Default is 3.
*/
@property (nonatomic) NSInteger maxSuggestionCount;
@end
#import "SuggestionView.h"
#define kScreenWidth [UIScreen mainScreen].bounds.size.width
@implementation SuggestionView {
NSMutableOrderedSet *_suggestions;
NSMutableArray *_suggestionButtons;
}
- (instancetype)init {
self = [self initWithFrame:CGRectMake(0.0f, 0.0f, kScreenWidth, 36.0f)];
if (self) {
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame inputViewStyle:UIInputViewStyleKeyboard];
if (self) {
_suggestions = [[NSMutableOrderedSet alloc] initWithCapacity:3];
self.maxSuggestionCount = 3;
_suggestionButtons = [[NSMutableArray alloc] init];
self.backgroundColor = [UIColor colorWithWhite:0.0f alpha:0.04f];
}
return self;
}
#pragma mark - Modifying Suggestions
- (void)addSuggestion:(NSString *)suggestion {
if (suggestion) {
[_suggestions addObject:suggestion];
}
while (_suggestions.count > self.maxSuggestionCount) {
[_suggestions removeObjectAtIndex:self.maxSuggestionCount];
}
}
- (void)removeSuggestion:(NSString *)suggestion {
[_suggestions removeObject:suggestion];
}
- (void)setSuggestions:(NSObject *)suggestions {
if ([suggestions respondsToSelector:@selector(countByEnumeratingWithState:objects:count:)]) {
[_suggestions removeAllObjects];
for (NSString *suggestion in (NSArray *)suggestions) {
if (_suggestions.count < self.maxSuggestionCount) {
[_suggestions addObject:suggestion];
} else {
break;
}
}
}
}
- (NSArray *)suggestions {
NSMutableArray *suggestionsArray = [[NSMutableArray alloc] initWithCapacity:_suggestions.count];
for (NSString *suggestion in _suggestions) {
[suggestionsArray addObject:suggestion];
}
return suggestionsArray;
}
#pragma mark - Visual Layout of Suggestions
- (void)layoutSubviews {
[self layoutSuggestions];
}
- (void)layoutSuggestions {
for (UIView *subview in _suggestionButtons) {
[subview removeFromSuperview];
}
[_suggestionButtons removeAllObjects];
for (int i = 0; i < _suggestions.count; i++) {
NSString *suggestion = _suggestions[i];
UIButton *suggestionButton = [[UIButton alloc] initWithFrame:CGRectMake(i * self.bounds.size.width/_suggestions.count, 0.0f, self.bounds.size.width/_suggestions.count, self.bounds.size.height)];
[suggestionButton setTitle:suggestion forState:UIControlStateNormal];
suggestionButton.titleLabel.adjustsFontSizeToFitWidth = YES;
suggestionButton.titleLabel.textAlignment = NSTextAlignmentCenter;
[suggestionButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[suggestionButton addTarget:self action:@selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:suggestionButton];
if (i > 0) {
UIView *whiteLine = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 0.5f, self.bounds.size.height)];
whiteLine.backgroundColor = [UIColor whiteColor];
[suggestionButton addSubview:whiteLine];
}
[_suggestionButtons addObject:suggestionButton];
}
}
#pragma mark - Selecting a Suggestion
- (void)buttonTouched:(UIButton *)button {
NSTimeInterval animationDuration = 0.09f;
[UIView animateWithDuration:animationDuration animations:^{
[button setBackgroundColor:[UIColor whiteColor]];
if ([self.delegate respondsToSelector:@selector(suggestionSelected:)]) {
[self performSelector:@selector(suggestionSelected:) withObject:button.currentTitle afterDelay:animationDuration * 0.9f];
}
[button performSelector:@selector(setBackgroundColor:) withObject:[UIColor clearColor] afterDelay:animationDuration];
}];
}
- (void)suggestionSelected:(NSString *)suggestion {
if ([self.delegate respondsToSelector:@selector(suggestionSelected:)]) {
[self.delegate suggestionSelected:suggestion];
}
}
@end
To implement this into a UITextField
or UITextView
that you've already subclassed, import the SuggestionView and implement the SuggestionViewDelegate. Then, in the UITextFieldDelegate
(or UITextViewDelegate
) methods, add:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
if ([textField isEqual:self.messageTextField]) {
if (self.suggestionView.suggestions.count > 0 && textField.text.length == 0) {
textField.inputAccessoryView = self.suggestionView;
textField.autocorrectionType = UITextAutocorrectionTypeNo;
[textField reloadInputViews];
}
}
return YES;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if ([textField isEqual:self.messageTextField]) {
if (string.length > 0) {
[self removeSuggestionView];
}
}
return YES;
}
- (void)removeSuggestionView {
self.messageTextField.inputAccessoryView = nil;
[self.messageTextField setInputAccessoryView:nil];
self.messageTextField.autocorrectionType = UITextAutocorrectionTypeYes;
[self.messageTextField reloadInputViews];
[self.messageTextField performSelector:@selector(resignFirstResponder) withObject:self afterDelay:0.0f];
[self.messageTextField performSelector:@selector(becomeFirstResponder) withObject:self afterDelay:0.0f];
}
Then, implement the SuggestionViewDelegate
:
- (void)suggestionSelected:(NSString *)suggestion {
[self.messageTextField setText:[NSString stringWithFormat:@"%@%@ ", self.messageTextField.text, suggestion]];
[self removeSuggestionView];
}
While this isn't a perfect solution, it does create the desired effect.