6

I've spent hours trying to figure out how to create/then get a custom inputView to work. I have a grid of TextInputs (think scrabble board) that when pressed should load a custom inputView to insert text.

I've created a .xib file containing the UI elements for the custom inputView. I was able to create a CustomInputViewController and have the inputView appear but never able to get the actual TextInput to update it's value/text.

Apple documentation has seemed light on how to get this to work and the many tutorials I've have seen have been using Obj-C (which I have been unable to convert over due to small things that seem unable to now be done in swift).

What is the overarching architecture and necessary pieces of code that should be implemented to create a customInputView for multiple textInputs (delegate chain, controller, .xibs, views etc)?

codycoats
  • 77
  • 1
  • 7
  • Create a custom class file and set this as the class for your xib in InterfaceBuilder. Then you can ctrl-drag from UI elements to your class file (assistant editor) to create IBOutlets. Use this outlets in your code to update the UI elements. – zisoft Oct 28 '14 at 08:05
  • I've been able to create IBOutlets/IBActions for the buttons in the customView. This has been necessary but not sufficient to get buttons in the customView to communicate textInput/keyboardFunctionality to the view of TextInputs. – codycoats Oct 28 '14 at 13:25
  • don't your views have to implement/conform to the proper protocol? (UITextInput?). you might also have to tell them to become first responder when they are tapped. I'd try subclassing UIControl vs. UIView – nielsbot Feb 14 '15 at 08:42
  • hav a looke at this: http://stackoverflow.com/questions/26539849/swift-proper-way-to-load-xib-file. – Abdullah Md. Zubair Feb 17 '15 at 17:33

2 Answers2

9

Set up a nib file with the appropriate inputView layout and items. In my case I set each button to an action on File Owner of inputViewButtonPressed.

Set up a storyboard (or nib if you prefer) for a view controller.

Then using the following code, you should get what you're looking for:

class ViewController: UIViewController, UITextFieldDelegate {
    var myInputView : UIView!
    var activeTextField : UITextField?

    override func viewDidLoad() {
        super.viewDidLoad()

        // load input view from nib
        if let objects = NSBundle.mainBundle().loadNibNamed("InputView", owner: self, options: nil) {
            myInputView = objects[0] as UIView
        }

        // Set up all the text fields with us as the delegate and
        // using our input view
        for view in self.view.subviews {
            if let textField = view as? UITextField {
                textField.inputView = myInputView
                textField.delegate = self
            }
        }
    }

    func textFieldDidBeginEditing(textField: UITextField) {
        activeTextField = textField
    }

    func textFieldDidEndEditing(textField: UITextField) {
        activeTextField = nil
    }

    @IBAction func inputViewButtonPressed(button:UIButton) {
        // Update the text field with the text from the button pressed
        activeTextField?.text = button.titleLabel?.text

        // Close the text field
        activeTextField?.resignFirstResponder()
    }
}

Alternatively, if you're wanting to be more keyboard-like, you can use this function as your action (it uses the new let syntax from Swift 1.2), break it up if you need 1.1 compatibility:

    @IBAction func insertButtonText(button:UIButton) {
        if let textField = activeTextField, title = button.titleLabel?.text, range = textField.selectedTextRange {
            // Update the text field with the text from the button pressed
            textField.replaceRange(range, withText: title)
        }
    }

This uses the UITextInput protocol to update the text field as appropriate. Handling delete is a little more complicated, but still not too bad:

    @IBAction func deleteText(button:UIButton) {
        if let textField = activeTextField, range = textField.selectedTextRange {
            if range.empty {
                // If the selection is empty, delete the character behind the cursor
                let start = textField.positionFromPosition(range.start, inDirection: .Left, offset: 1)
                let deleteRange = textField.textRangeFromPosition(start, toPosition: range.end)
                textField.replaceRange(deleteRange, withText: "")
            }
            else {
                // If the selection isn't empty, delete the chars in the selection
                textField.replaceRange(range, withText: "")
            }
        }
    }
David Berry
  • 40,941
  • 12
  • 84
  • 95
  • Thanks so much for this answer, David. If I wanted to re-use the keyboard across a few places, what would you recommend as the best way of doing that? – Richard Burton Mar 14 '15 at 12:59
  • You could subclass `UIViewController` or you could instantiate this code in either a `UITextField` subclass or a separate controller class. – David Berry Mar 14 '15 at 17:14
8

You shouldn't go through all that hassle. There's a new class in iOS 8 called: UIAlertController where you can add TextFields for the user to input data. You can style it as an Alert or an ActionSheet.

Example:

let alertAnswer = UIAlertController(title: "Input your scrabble Answer", message: nil, preferredStyle: .Alert) // or .ActionSheet

Now that you have the controller, add fields to it:

alertAnswer.addTextFieldWithConfigurationHandler { (get) -> Void in
            getAnswer.placeholder = "Answer"
            getAnswer.keyboardType = .Default
            getAnswer.clearsOnBeginEditing = true
            getAnswer.borderStyle = .RoundedRect
        } // add as many fields as you want with custom tweaks

Add action buttons:

let submitAnswer = UIAlertAction(title: "Submit", style: .Default, handler: nil)
let cancel = UIAlertAction(title: "Cancel", style: .Cancel, handler: nil)
alertAnswer.addAction(submitAnswer)
alertAnswer.addAction(cancel)

Present the controller whenever you want with:

self.presentViewController(alertAnswer, animated: true, completion: nil)

As you see, you have various handlers to pass custom code at any step.

As example, this is how it would look: An example of how it could look

leonardo
  • 1,686
  • 15
  • 15