0

I'm writing an iOS app where the user can add text fields, then drag them around the screen to reposition them, layout-style, sort of like Keynote.

I'm currently appending the user-added UITextFields to an @IBOutlet Collection and defaulting to .borderStyle = .roundedRect to get a faint border around the selected text, indicating the field is selected. Any UITextField will be set to .roundedRect border style when textFieldDidBeginEditing is called, and switch to textField.borderStyle = .none when textFieldDidEndEditing is called.

All seems to work with one problem: when switching border style to .none, the text field loses indentation that was around the border, shifting text outward and putting it in a spot where the user hadn't intended (graphic adds a background color red, just to show the shift, but I'll eventually allow the user to set background colors, so just shifting the UITextField isn't an option).

enter image description here

I've also tried adapting the answer at: Create space at the beginning of a UITextField setting a no-padding inset for the TextView when it's a .roundedRect, but adding padding when .borderStyle is .none. This seems to have no effect.

Other answers have suggested setting textField.layer.borderColor = UIColor.clear.cgColor or textField.layer.borderWidth = 0.0 but these don't seem to have any effect, either

I'm eventually going to allow the user to change fonts & sizes of each TextField, so I'd like any indentation to be consistent whether the UITextField is selected or nots elected, and regardless of font choices.

Code is below. Recommendations are most welcome, as well as setting me on a new approach, if I'm missing a better solution.

Thanks! John

class ViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var screenView: UIView! // a 320 x 240 view
    @IBOutlet var fieldCollection: [UITextField]! // Not connected, fields created programmatically

    // below are used in .inset(by:) but seems to have no effect
    let padding = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)
    let noPadding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

    override func viewDidLoad() {
        super.viewDidLoad()
        // hide keyboard if we tap outside of a field
        let tap = UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing(_:)))
        tap.cancelsTouchesInView = false
        self.view.addGestureRecognizer(tap)
        createNewField()
    }

    // Select / deselect text fields
    func textFieldDidBeginEditing(_ textField: UITextField) {
        textField.borderStyle = .roundedRect
        // textField.bounds.inset(by: noPadding) // effect is the same if left out
    }

    func textFieldDidEndEditing(_ textField: UITextField) {
        textField.borderStyle = .none
        // textField.bounds.inset(by: padding) // effect is the same if left out
    }

    // UITextField created & added to fieldCollection
    func createNewField() {
        let newFieldRect = CGRect(x: 0, y: 0, width: 320, height: 30)
        let newField = UITextField(frame: newFieldRect)
        newField.borderStyle = .roundedRect
        newField.isUserInteractionEnabled = true
        newField.addGestureRecognizer(addGestureToField())
        screenView.addSubview(newField)
        if fieldCollection == nil {
            fieldCollection = [newField]
        } else {
            fieldCollection.append(newField)
        }
        newField.delegate = self
        newField.becomeFirstResponder()
    }

    func addGestureToField() -> UIPanGestureRecognizer {
        var panGesture = UIPanGestureRecognizer()
        panGesture = UIPanGestureRecognizer(target: self, action: #selector(draggedView(_:)))
        return panGesture
    }

    // event handler when a field(view) is dragged
    @objc func draggedView(_ sender:UIPanGestureRecognizer){
        sender.view!.becomeFirstResponder()
        let selectedView = sender.view as! UITextField
        selectedView.bringSubviewToFront(selectedView)
        let translation = sender.translation(in: screenView)
        selectedView.center = CGPoint(x: selectedView.center.x + translation.x, y: selectedView.center.y + translation.y)
        sender.setTranslation(CGPoint.zero, in: screenView)
    }

    @IBAction func addFieldPressed(_ sender: UIButton) {
        createNewField()
    }
}
Gallaugher
  • 1,593
  • 16
  • 27

1 Answers1

0

I was able to work around the problem by subclassing UITextField:

class PaddedTextField: UITextField {
    let padding = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)
    let noPadding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

    override open func textRect(forBounds bounds: CGRect) -> CGRect {
        if self.borderStyle == .none {
            let content = bounds.inset(by: padding)
            return content
        } else {
            return bounds.inset(by: noPadding)
        }
    }
}

I then changed the newField object creation from using UITextField to: let newField = PaddedTextField(frame: newFieldRect)

One more change. The height needed to be more appropriately calculated. Since all of my text fields can start out the full length of the enclosing superview (320 points), I modified the original newFieldRect, used .sizeToFit() to create a textbox with the appropriate height. The other dimensions won't be correct b/c I don't have anything in the text view, but I extract the .height and reuse this with my original initliazation parameters.

newField.sizeToFit()
let newFieldHeight = newField.frame.height
newFieldRect = CGRect(x: 0, y: 0, width: 320, height: newFieldHeight)
newField.frame = newFieldRect

Here's hoping it helps save someone time.

Gallaugher
  • 1,593
  • 16
  • 27