iosios8quicktype

Possible to programmatically feed the iOS predictive keyboard context/source text?


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.


Solution

  • 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.