UITextView - Setting max lines hides the cursor - ios

This is what I am doing to set max number of lines :
self.text_description.textContainer.maximumNumberOfLines = 2
self.text_description.layoutManager.textContainerChangedGeometry(self.text_description.textContainer)
When 3rd line is about to get started, the cursor disappears. To put it back I have to tap backspace.

Assuming, in the text view cursor doesn't stays behind the keyboard. I have written a helper method that makes UITextField text scroll to end of text.
- (void)scrollToCaretInTextView:(UITextView *)textView animated:(BOOL)animated
{
CGRect rect = [textView caretRectForPosition:textView.selectedTextRange.end]; //Get the content size of textview
rect.size.height += textView.textContainerInset.bottom;
[textView scrollRectToVisible:rect animated:animated];
}

I don't quite sure what you are expecting. If you want two lines max, there should be no 3rd line.
Use this:
self.text_description.textContainer.maximumNumberOfLines = 2
self.text_description.textContainer.lineBreakMode = .byTruncatingTail
Limit the number of lines for UITextview
It seems what you actually want is a textview with two lines height? If that is true, try this in you view controller.
self.text_description = UITextView(frame: CGRect(x: 100.0, y: 100.0, width: 200.0, height: 28.0))
self.text_description.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
self.view.addSubview(self.text_description)

Related

clipsToBounds on UILabel not working

I have a UILabel, that I am setting up like this:
class someSuperclass {
var firstLetterLabel = UILabel(frame: CGRect(x: 58, y: 0, width: 81, height: 120))
func commonInit() {
firstLetterLabel.font = UIFont(name: "MuseoSans-500", size: 110.0)
firstLetterLabel.textColor = UIColor.museumRed
firstLetterLabel.textAlignment = .center
firstLetterLabel.numberOfLines = 1
firstLetterLabel.lineBreakMode = .byClipping
firstLetterLabel.clipsToBounds = false
addSubview(firstLetterLabel)
}
}
But it is still being clipped by its bounds
Since clipsToBounds does not seem to apply to the content of a label. How can I stop label content from being clipped by it's frame/bounds?
ClipsToBounds allows subviews or sub layers to spill out of a view or prevent that but it does not do that for drawn content by a view and in this case it is a UILabel. You cannot draw past the bounds of the view/label. That is why it is clipped. This difference always clips drawn content.
possible solutions
1) let intrinsic size of the single letter labels keep it from clipping. Place all the the labels in a horizontal stack view.
2) enable minimum font scale on the label to allow it to fit.
3) lastly it appears it is not drawing centered. Not really sure why as you have given very little to look at.
You can use UIButton instead, and setting button.userInteractionEnabled = false.

UIView border has different widths. Why?

Here is the picture :
I tried first with buttons, and then with UILabels on top of UIViews but the problem remained the same. Here's my code:
interView = UIView()
interView.backgroundColor = UIColor.clearColor()
interView.layer.borderWidth = 0.4
interView.layer.borderColor = UIColor.blackColor().colorWithAlphaComponent(0.4).CGColor
interView.frame = CGRectMake(0, 0, 130, 70)
interView.layer.shadowColor = UIColor.clearColor().CGColor
self.view.addSubview(interView)
So, I would like my UIView to have the same widths on every side of its border. Why is this happening?

Frame.size.width Not Returning Correct UIButton Width

I am trying to add a top and right border to my button but not the bottom or left. I looked it up and found a suggestion to do so by adding views to look like borders.
//Add borders
let topBorder = UIView(frame: CGRectMake(0, 0, editorBox.frame.size.width/3, 1))
let rightBorder = UIView(frame: CGRectMake(cancel.frame.size.width, 0, 1, cancel.frame.size.height))
topBorder.backgroundColor = UIColor(red: 220/255.0, green: 220/255.0, blue: 220/255.0, alpha: 1.0)
rightBorder.backgroundColor = UIColor(red: 220/255.0, green: 220/255.0, blue: 220/255.0, alpha: 1.0)
cancel.addSubview(topBorder)
cancel.addSubview(rightBorder)
But the resulting Simulator test looks like:
However, the button is definitely not that big because you can't click on the far right side of it. The auto layout constraints confirm that the button is not that big.
I just want each button to have a border around it's actual 1/3 width size, as set in the constraints. Can anyone tell me what's happening and how to fix it?
Looks to me to be an autolayout issue - your button is being generated before the layout changes its width. Your border views, on the other hand, do not get their width changed because they're outside of the constraint hierarchy.
For something this simple though, I think UIViews are a bit too much. I would add a few CALayers to the Button in question.
Here's a stack overflow question directly relating to the CALayer solution:
CALayer: add a border only at one side
It turns out that the other answer didn't solve my border problem by itself. Here's the code I'm using, based on the #tbondwilkinson answer:
//Add borders
let borderColor = UIColor(red: 200/255.0, green: 200/255.0, blue: 200/255.0, alpha: 1.0).CGColor
func formatTopBorder (buttonToFormat: UIButton,borderToFormat: CALayer) {
buttonToFormat.clipsToBounds = true
borderToFormat.borderColor = borderColor
borderToFormat.borderWidth = 2
borderToFormat.frame = CGRectMake(0, 0, CGRectGetWidth(cancel.frame), 2)
}
let topBorder: CALayer = CALayer()
let topBorder2: CALayer = CALayer()
let topBorder3: CALayer = CALayer()
let rightBorder: CALayer = CALayer()
let leftBorder: CALayer = CALayer()
formatTopBorder(cancel, borderToFormat: topBorder)
formatTopBorder(action, borderToFormat: topBorder2)
formatTopBorder(addSave, borderToFormat: topBorder3)
rightBorder.borderColor = borderColor
rightBorder.borderWidth = 2
rightBorder.frame = CGRectMake(CGRectGetWidth(cancel.frame)-2, 0, 2, CGRectGetHeight(cancel.frame))
leftBorder.borderColor = borderColor
leftBorder.borderWidth = 1
leftBorder.frame = CGRectMake(0, 0, 2, CGRectGetHeight(cancel.frame))
cancel.layer.addSublayer(topBorder)
cancel.layer.addSublayer(rightBorder)
action.layer.addSublayer(topBorder2)
addSave.layer.addSublayer(topBorder3)
addSave.layer.addSublayer(leftBorder)
But if placed in the viewDidLoad, it doesn't work perfectly. The top borders work and so does the left border. However, it was still prematurely getting the UIButton width, resulting in the right border not working:
rightBorder.frame = CGRectMake(CGRectGetWidth(cancel.frame)-2, 0, 2, CGRectGetHeight(cancel.frame))
The CGRectGetWidth(cancel.frame)-2 gets the correct right edge and gives space for the border, but it gets the width before the button has sized itself.
The solution:
Use the method and then put that code in the viewDidAppear. Unfortunately, the borders appear a fraction of a second after your view appears, but all the borders will be correct.
If anyone can post code that works for a right-edge border, that runs in the viewDidLoad, please post it and I'll mark it correct.

Tap area ridiculously small for UIButton

I have a UIButton that I created like this:
let closeButton = UIButton(type: .System)
closeButton.backgroundColor = UIColor(r: 92, g: 92, b: 118, alpha: 1)
closeButton.setImage(UIImage(named: "closeCross")?.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate), forState: .Normal)
closeButton.tintColor = UIColor.whiteColor()
closeButton.imageEdgeInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
closeButton.addTarget(self, action: "close", forControlEvents: .TouchUpInside)
closeButton.frame = CGRectMake(messageView.frame.maxX - 30, 0, 20, 20)
closeButton.center.y = messageView.bounds.midY
The problem is that the tap is triggered only if you are a pixel away from the center of the UIButton. The image I set is a png that is a cross X.
Even if I tap on the cross (but not exactly on the middle, thanks the simulator the precision), it doesn't work.
I don't understand. The background isn't clear. Whether or not I change the content/image edge insets doesn't change a thing. Whether or not I use an image rendering mode doesn't change a thing.
Its best practice to keep UIbuttons at least 44x44 in size. https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/LayoutandAppearance.html
Make it easy for people to interact with content and controls by giving each interactive element ample spacing. Give tappable controls a hit target of about 44 x 44 points.
So I would set your frame to be bigger then 20pts, also what size is your image 20pts? Could it be clipping outside of the button, check that clipToBounds is set to yes.
There also a more radical approach, and that's to create a subclass of UIButton with a touch area of at least 44.
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGFloat widthDelta = 44.0 - self.bounds.size.width;
CGFloat heightDelta = 44.0 - self.bounds.size.width;
CGRect largerBounds = CGRectInset(self.bounds, -0.5 * widthDelta, -0.5 * heightDelta);
return CGRectContainsPoint(largerBounds, point);
}
Another thing to do is investigate the button using reveal( http://revealapp.com/)
Here is actually how I fixed it:
closeButton.imageEdgeInsets = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
closeButton.frame = CGRectMake(noConnectionMessageView.frame.maxX - 40, 0, 40, 40)
It only happens when there is no title along with image on UIButton. If you don't want to add a title, there is a hack...
Just add a space as title text. Whole area of your button would start responding.
It miraculously worked for me.

Customize underline pattern in NSAttributedString (iOS7+)

I'm trying to add a dotted underline style to an NSAttributedString (on iOS7+/TextKit). Of course, I tried the built-in NSUnderlinePatternDot:
NSString *string = self.label.text;
NSRange underlineRange = [string rangeOfString:#"consetetur sadipscing elitr"];
NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:string];
[attString addAttributes:#{NSUnderlineStyleAttributeName: #(NSUnderlineStyleSingle | NSUnderlinePatternDot)} range:underlineRange];
self.label.attributedText = attString;
However, the style produced by this is actually rather dashed than dotted:
Am I missing something obvious here (NSUnderlinePatternReallyDotted? ;) ) or is there perhaps a way to customize the line-dot-pattern?
According to Eiko&Dave's answer, i made an example like this
i've try to finish that by using UILabel rather than UITextView, but could not find a solution after searching from stackoverflow or other websites. So, i used UITextView to do that.
let storage = NSTextStorage()
let layout = UnderlineLayout()
storage.addLayoutManager(layout)
let container = NSTextContainer()
container.widthTracksTextView = true
container.lineFragmentPadding = 0
container.maximumNumberOfLines = 2
container.lineBreakMode = .byTruncatingTail
layout.addTextContainer(container)
let textView = UITextView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width-40, height: 50), textContainer: container)
textView.isUserInteractionEnabled = true
textView.isEditable = false
textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
textView.text = "1sadasdasdasdasdsadasdfdsf"
textView.backgroundColor = UIColor.white
let rg = NSString(string: textView.text!).range(of: textView.text!)
let attributes = [NSAttributedString.Key.underlineStyle.rawValue: 0x11,
NSAttributedString.Key.underlineColor: UIColor.blue.withAlphaComponent(0.2),
NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17),
NSAttributedString.Key.baselineOffset:10] as! [NSAttributedString.Key : Any]
storage.addAttributes(attributes, range: rg)
view.addSubview(textView)
override LayoutManage Method
class UnderlineLayout: NSLayoutManager {
override func drawUnderline(forGlyphRange glyphRange: NSRange, underlineType underlineVal: NSUnderlineStyle, baselineOffset: CGFloat, lineFragmentRect lineRect: CGRect, lineFragmentGlyphRange lineGlyphRange: NSRange, containerOrigin: CGPoint) {
if let container = textContainer(forGlyphAt: glyphRange.location, effectiveRange: nil) {
let boundingRect = self.boundingRect(forGlyphRange: glyphRange, in: container)
let offsetRect = boundingRect.offsetBy(dx: containerOrigin.x, dy: containerOrigin.y)
let left = offsetRect.minX
let bottom = offsetRect.maxY-15
let width = offsetRect.width
let path = UIBezierPath()
path.lineWidth = 3
path.move(to: CGPoint(x: left, y: bottom))
path.addLine(to: CGPoint(x: left + width, y: bottom))
path.stroke()
}
}
in my solution, i've to expand lineSpacing also keep a customize underline by using NSAttributedString property NSMutableParagraphStyle().lineSpacing, but it seems that didn't work, but NSAttributedString.Key.baselineOffset is worked. hope can
I've spent a little bit of time playing around with Text Kit to get this and other similar scenarios to work. The actual Text Kit solution for this problem is to subclass NSLayoutManager and override drawUnderline(forGlyphRange:underlineType:baselineOffset:lineFragmentRect:lineFragmentGlyphRange:containerOrigin:)
This is the method that gets called to actually draw the underline that is requested by the attributes in the NSTextStorage. Here's a description of the parameters that get passed in:
glyphRange the index of the first glyph and the number of following glyphs to be underlined
underlineType the NSUnderlineStyle value of the NSUnderlineAttributeName attribute
baselineOffset the distance between the bottom of the bounding box and the baseline offset for the glyphs in the provided range
lineFragmentRect the rectangle that encloses the entire line that contains glyphRange
lineFragmentGlyphRange the glyph range that makes up the entire line that contains glyphRange
containerOrigin the offset of the container within the text view to which it belongs
Steps to draw underline:
Find the NSTextContainer that contains glyphRange using NSLayoutManager.textContainer(forGlyphAt:effectiveRange:).
Get the bounding rectangle for glyphRange using NSLayoutManager.boundingRect(forGlyphRange:in:)
Offset the bounding rectangle by the text container origin using CGRect.offsetBy(dx:dy:)
Draw your custom underline somewhere between the bottom of the offset bounding rectangle and the baseline (as determined by baselineOffset)
I know this is a 2 year old story but maybe it will help someone facing the same problem, like I did last week.
My workaround was to subclass UITextView, add a subview (the dotted lines container) and use the textView’s layoutManager method enumerateLineFragmentsForGlyphRange to get the number of lines and their frames.
Knowing the line’s frame I calculated the y where I wanted the dots. So I created a view (lineView in which I drawn dots), set clipsToBounds to YES, the width to that of the textView and then
added as a subview in the linesContainer at that y.
After that I set the lineView’s width to that returned by enumerateLineFragmentsForGlyphRange.
For multiple rows the same approach: update frames, add new lines or remove according to what enumerateLineFragmentsForGlyphRange returns.
This method is called in textViewDidChange after each text update.
Here is the code to get the array of lines and their frames
NSLayoutManager *layoutManager = [self layoutManager];
[layoutManager enumerateLineFragmentsForGlyphRange:NSMakeRange(0, [layoutManager numberOfGlyphs])
usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, NSRange glyphRange, BOOL *stop) {
[linesFramesArray addObject:NSStringFromCGRect(usedRect)];
}
];

Resources