How to get the CGRect of the present caret - ios

I want to obtain the location of the present caret in UITextView as CGRect, but there seems to be no information. Even the official documentation, I can't imagine how to utilize this method and find the explanation. Now I could know the way of getting the offset in the UITextView, not CGRect. But, I really want to know the CGRect.
Any comments should be highly appreciated.
Thanks.

Try this:
extension UITextView {
var caret: CGRect? {
guard let selectedTextRange = self.selectedTextRange else { return nil }
return self.caretRect(for: selectedTextRange.end)
}
}
and use it like this:
let textView: UITextView = ...
if let caret = textView.caret {
// Do your thing here.
} else {
// Caret is undefined.
}
By the way, you were on the right track :-) The above solution is based on the UITextInput method you just mentioned:
func caretRect(for position: UITextPosition) -> CGRect
Return a rectangle used to draw the caret at a given insertion point.
The UITextPosition parameter represents a position in a text container; in other words, it is an index into the backing string in a text-displaying view.

//rect is an object of CGRect
var rect = TextView.frame

Related

Why is my CGPoint returning other characters?

Hi there I am trying to add a label to the centre of the thumb on a custom UIControl. I need to find the centre on the thumb.
I have a function that I'd like to return the thumb centre as CGPoint.
The function is returning really strange coordinates that look like:
thumb center coordinates: (5.534284385e-315, 5.356796015e-315)
Why am I getting these values?
Does UIControl have a way to return thumbRect like UISlider? Thank you!!
open func getThumbCenterRect() -> CGPoint {
return thumbCenter
}
fileprivate var thumbCenter: CGPoint {
var thumbCenter = viewCenter
let angle = rtlAwareAngleRadians(thumbAngle)
thumbCenter.x += CGFloat(cos(angle)) * controlRadius
thumbCenter.y += CGFloat(sin(angle)) * controlRadius
return thumbCenter
}
#IBAction func onSlideChange(_ sender: MTCircularSlider) {
let thumbCenter = knobWithLabelView.getThumbCenterRect()
print("thumb centre coordinates: \(thumbCenter)")
}
output:
thumb coordinates: (5.534284385e-315, 5.356796015e-315)
It's scientific notation. 5.534284385e-315 means 5.534284385 * 10^-315, which is a really small number (a 0., followed by 314 zeroes, followed by 55342...`).
In other words, it's a really small number, that's really close to 0 but not quite. Most probably, you'll want to do some rounding to get values that snap to more reasonable accepted values.

How do you get correct caret size AND position when using NSMutableParagraphStyle and paragraphSpacingBefore in Swift 4

I've been playing around with attributed text in a UITextView (Swift 4.2 and noticed that once I introduced "paragraphSpacingBefore" into my design, the Caret becmae too large on the first line of each new paragraph.
I found this suggested fix on Stackoverflow which seemed to work ok to fix the caret size. The problem I found was the caret itself floats above the target line when that line was the start of a new paragraph.
UITextView lineSpacing make cursor height not same
Caret Floats above the target line
I tried solving it, maintaining the core idea of the original solution and adding some offset logic. During debugging I noticed that the original answer for caret size always adjusts the size even when not required so I added a variance filter (only adjust if variance > 10%). Did this because I think adjusting every time will interfere with my soln. to the floating caret problem.
If someone can take a look at my proposed approach, suggest improvements or a better way etc i'd be grateful:
override func caretRect(for position: UITextPosition) -> CGRect {
var superRect = super.caretRect(for: position)
guard let isFont = self.font else {
return superRect
}
let proposedHeight: CGFloat = isFont.pointSize - isFont.descender
var delta: CGFloat = superRect.size.height - proposedHeight
delta = (delta * delta).squareRoot()
//If the delta is < 10% of the original height just return the original rect
if delta / superRect.size.height < 0.1 {
return superRect
}
superRect.size.height = isFont.pointSize - isFont.descender
// "descender" is expressed as a negative value,
// so to add its height you must subtract its value
superRect.origin.y = superRect.origin.y + delta
// delta is used to correct for resized caret floating above the target line
return superRect
}
I got a solution:
// Fix long cursor height when at the end of paragraph with paragraphspacing and wrong cursor position in titles with paragraph spacing before
override public func caretRect(for position: UITextPosition) -> CGRect {
var superRect = super.caretRect(for: position)
guard let isFont = self.font else { return superRect }
let location = self.offset(from: self.beginningOfDocument, to: position)
if let paragrahStyle = self.storage.attribute(.paragraphStyle, at: location, effectiveRange: nil) as? NSParagraphStyle {
superRect.origin.y += paragrahStyle.paragraphSpacingBefore
}
superRect.size.height = isFont.pointSize - isFont.descender
return superRect
}
The real problem paragraphSpacingBefore. So all you have to do is to get the paragraph styling attributes, get the spacing and move the cursor by that spacing. This works well with all the text.

getting consistent sizing for glyph backgrounds

Hey folks – I'm new to TextKit and trying to draw backgrounds & borders around specific attributes. I've gotten fairly close, but haven't yet found methods that don't generate very inconsistent sizing, which tends to look bad. Here's my first crack at it:
class MyLayout: NSLayoutManager {
override func drawBackground(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
super.drawBackground(forGlyphRange: glyphsToShow, at: origin)
guard let storage = textStorage else {
return
}
guard let context = UIGraphicsGetCurrentContext() else {
return
}
var codeBlockRect: CGRect? = nil
enumerateLineFragments(forGlyphRange: glyphsToShow) { (rect, usedRect, container, subRange, stop) in
var effectiveRange = NSRange()
let attributes = storage.attributes(at: subRange.location, effectiveRange: &effectiveRange)
storage.enumerateAttribute(.inlineCodeBlock, in: subRange) { (value, attributeRange, stop) in
guard value != nil else {
return
}
var background = self.boundingRect(forGlyphRange: attributeRange, in: container)
background.origin.x += origin.x
background.origin.y += origin.y
context.setFillColor(UIColor.lightGrey.cgColor)
context.setStrokeColor(UIColor.mediumGrey.cgColor)
context.stroke(background)
context.fill(background)
}
}
}
}
That produces these results:
Single line of text:
Multiple lines of text:
As you can see, there's about 3 pixels of difference between the sizes there. I imagine it's because boundingRect, as the documentation says:
Returns the smallest bounding rect which completely encloses the glyphs in the given glyphRange
But I haven't found a method that gives me a number closer to what I'm looking for. My ideal scenario is that every rectangle will have the exact same height.
Let me know if any more information is needed.
Update
It crossed my mind that this could be related to the proprietary font we're using, so I changed everything to use UIFont.systemFont, which didn't make any difference.
I found a workaround here – instead of defining my inlineCodeBlock as a background, i defined it as a custom underline style.
Doing that let me override drawUnderline(forGlyphRange:underlineType:baselineOffset:lineFragmentRect:lineFragmentGlyphRange:containerOrigin:).
Once I had that, I was able to use baselineOffset to get a consistent positioning.

Converting two UIViews to the same coordinate system when UIViews are in hierarchy

I have 2 UIImageViews in a ViewController and I'm trying to work out when they intersect.
imageViewA: is in my storyboard view, with constraints, and is in a hierarchy of views as follows:
- Background
-- Images
--- imageViewA
imageViewB: is created dynamically and is dragged around the screen using a UIPanGestureRecognizer.
When the drag ends, I want to check if imageViewB intersects with imageViewB. I used the intersects function, but don't get the results I expect, I suppose because imageViewA is in a hierachy of views, which means it's in a different coordinate system. So I want to convert both views to the same coordinate system. How I can do this?
I've tried the following:
let frameA = imageViewA.convert(imageViewA.frame, to: self.view)
let frameB = imageViewB.convert(imageViewB.frame, to: self.view)
But it doesn't give me the results I expect, which frameB having a much larger Y coordinate.
Do I need to do something like this:
let frameA = imageViewA.superview?.superview?.convert(imageViewA.superview?.superview?.frame, to: self.view)
There are other questions covering conversion to coordinate systems, but they don't seem to tackle what to do when the view is in a hierarchy.
Your problem is that imageViewA.frame is in the geometry (coordinate system) of imageViewA.superview, but UIView.convert(_ rect: to view:) expects rect to be in the geometry of imageViewA.
UPDATE
The simplest solution is to convert imageViewA.bounds (which is in imageViewA's geometry) directly to imageViewB's geometry, and then see if it intersects imageViewB.bounds, which is also in imageViewB's geometry:
let aInB = imageViewA.convert(imageViewA.bounds, to: imageViewB)
if aInB.intersects(imageViewB.bounds) {
...
ORIGINAL
The simplest solution is to convert imageViewA.bounds, which is in imageViewA's own geometry:
let frameA = imageViewA.convert(imageViewA.bounds, to: self.view)
let frameB = imageViewB.convert(imageViewB.bounds, to: self.view)
I misunderstood the convert function. Just in case it's useful for anyone else, the solution looks like this:
let convertedFrameB = frameA.superview?.convert(frameA.frame, from self.view)
if(convertedFrameB.intersects(frameA.frame) {...
...
}
Here's an extension that might be useful:
extension UIView {
func intersectsIgnoringCoordinateSpace(_ view2: UIView) -> Bool {
let frameOne = self.convert(self.bounds, to: self.topLevelView)
let frameTwo = view2.convert(self.bounds, to: self.topLevelView)
return frameOne.intersects(frameTwo)
}
var topLevelView: UIView? {
get {
var topView = self.superview
while(topView?.superview != nil) {
topView = topView?.superview
}
return topView
}
}

UITextView will shorten text?

How to know wether UITextView will shorten text because lack of space? I know I can calculate with boundingRectWithSize and sizeThatFit, but what if exclusionPaths of the UITextView is changed. I want lay out string in a polygon, and increase polygon size, until string laid out without shortening. Any idea, how can get a bool return, wether current setup will shorten?
self.tv.textContainer.exclusionPaths = myArrayOfBezierPaths;
func isSizeFitForTextView() -> Bool{
let layoutManager = self.textView.layoutManager
let glyphIndex = layoutManager.glyphIndexForCharacter(at:( self.textView.text as NSString).length)
let range = layoutManager.truncatedGlyphRange(inLineFragmentForGlyphAt: glyphIndex)
return range.location != NSNotFound
}

Resources