2

I have a UITextField subclass where I'm overriding the drawing rectangles for the left view, text and caret. This is done using leftViewRect(forBounds:), leftViewRect(forBounds:), caretRect(for:), etc. Everything works as expected.

The problem is that because I'm moving the drawing rectangle (up) by -2 points in caretRect(for:), the drawing rectangle for selected text is 2 points lower than it should be. I tried overriding selectionRects(for:), but UITextSelectionRect's rect is a get-only property. What's the correct way to achieve this?

Code

public override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
    super.selectionRects(for: range).map {
        var selectionRect = $0
        selectionRect.rect.origin.y -= 2 // Left side of mutating operator isn't mutable: 'rect' is a get-only property
        return selectionRect
    }
}
JWK
  • 3,345
  • 2
  • 19
  • 27
  • 1
    From the documentation for `UITextSelectionRect`: *If you are implementing a custom text input view, you can subclass and use your custom class to return selection-related information. When subclassing, you should override and reimplement all properties. In your custom implementations, do not call super.*. – rmaddy Sep 27 '19 at 05:12
  • Thanks, I'd seen that. I'm still unsure how exactly to get what I want. Would I be initializing a `UITextSelectionRect` subclass with the mapped values from `super.selectionRects(for: range)` where the `rect` property's y value is modified? Some example code would really help. – JWK Sep 27 '19 at 05:24
  • That seems to be what you need. – rmaddy Sep 27 '19 at 05:24

1 Answers1

1

Per rmaddy's comment and this answer, I solved this by subclassing UITextSelectionRect and passing in the mapped values from super.selectionRects(for: range) with a modified rect. This is what I landed on:

public final class MyUITextField: UITextField {
    public override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] {
        super.selectionRects(for: range).map {
            MyTextSelectionRect(
                rect: CGRect(x: $0.rect.origin.x, y: $0.rect.origin.y - 2, width: $0.rect.width, height: $0.rect.height),
                writingDirection: $0.writingDirection,
                containsStart: $0.containsStart,
                containsEnd: $0.containsEnd,
                isVertical: $0.isVertical
            )
        }
    }

    //...
}
public final class MyTextSelectionRect: UITextSelectionRect {
    public override var rect: CGRect { _rect }
    public override var writingDirection: NSWritingDirection { _writingDirection }
    public override var containsStart: Bool { _containsStart }
    public override var containsEnd: Bool { _containsEnd }
    public override var isVertical: Bool { _isVertical }

    private let _rect: CGRect
    private let _writingDirection: NSWritingDirection
    private let _containsStart: Bool
    private let _containsEnd: Bool
    private let _isVertical: Bool

    public init(
        rect: CGRect,
        writingDirection: NSWritingDirection,
        containsStart: Bool,
        containsEnd: Bool,
        isVertical: Bool
    ) {
        _rect = rect
        _writingDirection = writingDirection
        _containsStart = containsStart
        _containsEnd = containsEnd
        _isVertical = isVertical
    }
}
JWK
  • 3,345
  • 2
  • 19
  • 27