KeyboardLayoutGuide issue - keyboard

I can't seem to get the constraints working so the keyboard does not cover the textfield so the user can see while typing. Can someone help? I'm coding in Xcode using UIKit.
Here is my current code.
import UIKit
class AdoptViewController: UIViewController {
//idenfiing the textfield.
let nameInput: UITextField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
title = "Adopt a Cat"
findCat()
UpdateScreen()
//sets up the textfield. Appears as a square in the center of the screen
nameInput.borderStyle = UITextField.BorderStyle.line
self.view.addSubview(nameInput)
nameInput.translatesAutoresizingMaskIntoConstraints = false
let horizontalConstraint = NSLayoutConstraint(item: nameInput, attribute: NSLayoutConstraint.Attribute.centerX, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.centerX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: nameInput, attribute: NSLayoutConstraint.Attribute.centerY, relatedBy: NSLayoutConstraint.Relation.equal, toItem: view, attribute: NSLayoutConstraint.Attribute.centerY, multiplier: 1, constant: 10)
let widthConstraint = NSLayoutConstraint(item: nameInput, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 100)
let heightConstraint = NSLayoutConstraint(item: nameInput, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 100)
NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
As you can see I have nothing to position the textfield out of the way of the keyboard. I don't understand how to use NSLayoutConstraints are there any good beginner tutorials for this?
Here's what I have done.
At first I was connecting the UITextField through the storyboard and a UI outlet but whenever I tried to add constraints programmatically, it would give me an error about not having that method or type in UITextView. I thought that if I added the TextField programmatically it might allow me to add constraints programmatically. With that, I was able to add these basic constraints which I found online but I can't figure out how to change them.
I tried to move my textfield before getting into how to add .keyboardLayoutGuide by alining it to the button of my UIimageView. With both of these examples the build would crash.
nameInput.topAnchor.constraint(equalTo: AboptCatImage.bottomAnchor, constant: 10)
NSLayoutConstraint(item: nameInput,
attribute: .leading,
relatedBy: .equal,
toItem: AboptCatImage,
attribute: .trailing,
multiplier: 1.0,
constant: 2.0).isActive = true

Related

CALayerInvalidGeometry: CALayer position contains NaN when trying to size a small, remote svg with variable size and aspect ratio

What I want to achieve:
Display small, variable sized svgs in my app with auto layout.
The svgs contain pre-rendered latex formulas. The svgs are loaded asynchronously so I don't know upfront which size or aspect ratio they are.
The hardest pre-conditions I could imagine.
I tried several svg displaying libraries, and no one turned out to work with our svgs, so I decided to use a UIWebView.
The problem here is, that the svgs should not be scrollable and the UIWebView of minimal size.
The svgs should span all available width and have a height based on the aspect ratio.
What I tried:
class FormulaView: UIView, UIWebViewDelegate, XMLParserDelegate {
private var view: UIView!
#IBOutlet weak var webView: UIWebView!
#IBOutlet weak var topMargin: NSLayoutConstraint!
#IBOutlet weak var bottomMargin: NSLayoutConstraint!
private var width = -1.0
private var height = -1.0
// MARK: Initialisation
override init(frame: CGRect) {
super.init(frame: frame)
setUpView()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setUpView()
}
// MARK: Set Up
// Performs the initial setup.
// Loads a XIB file into a view and returns this view.
func loadSVG(fromURL url: URL) {
let request = NSURLRequest(url: url)
webView.delegate = self
webView.loadRequest(request as URLRequest)
}
func loadSVG(fromString string: String) {
let url = URL(string: string)
if url == nil {
self.isHidden = true
return
}
self.loadSVG(fromURL: url!)
}
var tableView: UITableView?
private func viewFromNibForClass() -> UIView {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
let view = nib.instantiate(withOwner: self, options: nil).first as! UIView
return view
}
private func setUpView() {
view = viewFromNibForClass()
view.frame = bounds
// Auto-layout stuff.
view.autoresizingMask = [
UIViewAutoresizing.flexibleWidth,
UIViewAutoresizing.flexibleHeight
]
addSubview(view)
// Formula should take no space if no formula is loaded
topMargin.constant = 0
bottomMargin.constant = 0
self.translatesAutoresizingMaskIntoConstraints = false
self.addConstraints([
NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal,
toItem: view, attribute: .top, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: self, attribute: .right, relatedBy: .equal,
toItem: view, attribute: .right, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal,
toItem: view, attribute: .bottom, multiplier: 1.0, constant: 0),
NSLayoutConstraint(item: self, attribute: .left, relatedBy: .equal,
toItem: view, attribute: .left, multiplier: 1.0, constant: 0)
])
}
// MARK: - UIWebView delegate
func webViewDidFinishLoad(_ webView: UIWebView) {
// Get xml of svg file
let xml = webView.stringByEvaluatingJavaScript(from: "document.documentElement.outerHTML")
let parser = XMLParser(data: (xml?.data(using: .utf8))!)
parser.delegate = self
parser.parse()
}
// MARK: - XMLParser delegate
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
// Get width and height of svg
if elementName == "svg" {
self.height = Double(attributeDict["height"]?
.replacingOccurrences(of: "pt", with: "") ?? "")
?? -1.0
self.width = Double(attributeDict["width"]?
.replacingOccurrences(of: "pt", with: "") ?? "")
?? -1.0
let aspectRatio = self.width / self.height
tableView?.beginUpdates()
// Calculate height the webview should have based on the width and the normal aspect ratio of the svg
let webviewHeight = self.webView.bounds.size.width / CGFloat(aspectRatio)
if let constraint = (webView.constraints.filter {
$0.firstAttribute == .height && $0.secondAttribute == .notAnAttribute
}.first) {
constraint.constant = webviewHeight
} else {
webView.heightAnchor.constraint(equalToConstant: webviewHeight).isActive = true
}
// Apply a 24 margin as a svg is now loaded
topMargin.constant = 24
bottomMargin.constant = 24
tableView?.endUpdates()
self.layoutIfNeeded()
self.invalidateIntrinsicContentSize()
// Zoom webview appropriately that the svg is completely visible and no scrolling is neccessary or even possible
let scrollView = webView.scrollView
let zoom = webView.bounds.size.width / scrollView.contentSize.width
scrollView.maximumZoomScale = zoom
scrollView.setZoomScale(zoom, animated: true) // Error occurs here
// prevent scrolling
scrollView.isScrollEnabled = false
scrollView.isPagingEnabled = false
scrollView.isUserInteractionEnabled = false
scrollView.isDirectionalLockEnabled = true
}
}
}
This version seems to work fine as I tested it on the simulator,
but in some cases (EDIT: I only see these crashes in our Bug reporting tool), I get an error (CALayerInvalidGeometry: CALayer position contains NaN: [nan nan]) at this line scrollView.setZoomScale(zoom, animated: true).
It seems the calculating the zoom results in CGFloat.nan,
(1) but when does this occur,
(2) how can I prevent this and
(3) if it occurs, how can I calculate the zoom in these cases?
EDIT: Or in other words, why are webView.bounds.size.width and/or scrollView.contentSize.width non positiv values?
If I could reproduce it, I could say more, but unluckily I can't...
Thanks in advance,
Dennis

Xamarin iOS : Center UILabel inside UICollectionView Header created programmatically

The solution for centering any subview within a parent is usually simple, however, it doesn't seem to work in my case.
I'm working with a UICollectionView and have added a Header class programmatically. I have this constructor, where I also try to center the label within the screen:
[Export("initWithFrame:")]
public Header(System.Drawing.RectangleF frame) : base(frame)
{
label = new UILabel
{
Frame = new System.Drawing.RectangleF(frame.Size.Width / 2, 50, 200, 50),
BackgroundColor = UIColor.Clear,
TextColor = UIColor.White,
Font = UIFont.FromName("HelveticaNeueLTStd-ThCn", 35f),
Text = DateTime.Now.ToString("Y")
};
AddSubview(label);
}
And I initialize the class inside the UICollectionViewSource 's constructor like this:
public MyCollectionViewDataSource(MainController mainController, DateTime currentDate)
{
try
{
controller = mainController;
new Header(new RectangleF(0, 0, (float)mainController.View.Frame.Size.Width, 200));
}
catch (Exception ex)
{
Console.WriteLine(ex.Message + ex.StackTrace);
}
}
What exactly am I missing because this usually works in other instances but seems to fail here?
This is what it looks like :
I found an explanation here iOS Layout Gotchas by Adam Kemp which helped me resolve this issue.
The first solution
One very common mistake I made was adding the layout definition code in the constructor, instead of doing it in the rightful place : the LayoutSubviews override in this case.
Giving the label the frame size in the constructor assumes a static size set at the time of construction, which may later change depending on the screen size.
The second solution
He explains that :
Frame sets the position of a view within its parent while Bounds is in the coordinate system of the view itself (not its parent).
So, to center the UILabel, I used bounds and center together and this worked for me.
[Export("initWithFrame:")]
public Header(CGRect bounds) : base(bounds)
{
label = new UILabel
{
BackgroundColor = UIColor.Clear,
TextColor = UIColor.White,
Font = UIFont.FromName("HelveticaNeueLTStd-ThCn", 35f),
Text = DateTime.Now.ToString("Y"),
TextAlignment = UITextAlignment.Center
};
rectangle = bounds;
AddSubview(label);
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
label.Bounds = new CGRect (rectangle.Size.Width / 2, 50, 200, 50);
label.Center = new PointF((float)rectangle.Size.Width/2,50);
}

iOS7 - Change UINavigationBar border color

Is it possible to change the grey border-bottom color of the UINavigationBar in iOS7?
I already tried to remove to border, but this is not working:
[[UINavigationBar appearance] setShadowImage:[[UIImage alloc] init]];
Thanks!
You are removing the shadow but not the border, you need to do the following:
[[UINavigationBar appearance] setBackgroundImage:[[UIImage alloc] init] forBarMetrics:UIBarMetricsDefault];
[[UINavigationBar appearance] setShadowImage:[[UIImage alloc] init]];
To change the border use an image of 2 pixels width line:
[[UINavigationBar appearance] setShadowImage:[UIImage imageNamed:#"2pxWidthLineImage"]];
Here is a category to change bottom color with height:
[self.navigationController.navigationBar setBottomBorderColor:[UIColor redColor] height:1];
Objective C:
UINavigationBar+Helper.h
#import <UIKit/UIKit.h>
#interface UINavigationBar (Helper)
- (void)setBottomBorderColor:(UIColor *)color height:(CGFloat)height;
#end
UINavigationBar+Helper.m
#import "UINavigationBar+Helper.h"
#implementation UINavigationBar (Helper)
- (void)setBottomBorderColor:(UIColor *)color height:(CGFloat)height {
CGRect bottomBorderRect = CGRectMake(0, CGRectGetHeight(self.frame), CGRectGetWidth(self.frame), height);
UIView *bottomBorder = [[UIView alloc] initWithFrame:bottomBorderRect];
[bottomBorder setBackgroundColor:color];
[self addSubview:bottomBorder];
}
#end
Swift:
extension UINavigationBar {
func setBottomBorderColor(color: UIColor, height: CGFloat) {
let bottomBorderRect = CGRect(x: 0, y: frame.height, width: frame.width, height: height)
let bottomBorderView = UIView(frame: bottomBorderRect)
bottomBorderView.backgroundColor = color
addSubview(bottomBorderView)
}
}
Here is another way:
CALayer *border = [CALayer layer];
border.borderColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"border"]].CGColor;
border.borderWidth = 1;
CALayer *layer = self.navigationController.navigationBar.layer;
border.frame = CGRectMake(0, layer.bounds.size.height, layer.bounds.size.width, 1);
[layer addSublayer:border];
The only way I found to change color is:
override func viewDidLoad() {
super.viewDidLoad()
if let navigationController = self.navigationController {
let navigationBar = navigationController.navigationBar
let navigationSeparator = UIView(frame: CGRectMake(0, navigationBar.frame.size.height - 1, navigationBar.frame.size.width, 0.5))
navigationSeparator.backgroundColor = UIColor.redColor() // Here your custom color
navigationSeparator.opaque = true
self.navigationController?.navigationBar.addSubview(navigationSeparator)
}
}
I wrote an extension based on the other answers for easier usage in Swift:
extension UINavigationBar {
func setBottomBorderColor(color: UIColor) {
let navigationSeparator = UIView(frame: CGRectMake(0, self.frame.size.height - 0.5, self.frame.size.width, 0.5))
navigationSeparator.backgroundColor = color
navigationSeparator.opaque = true
navigationSeparator.tag = 123
if let oldView = self.viewWithTag(123) {
oldView.removeFromSuperview()
}
self.addSubview(navigationSeparator)
}
}
You can use this extension with calling the method in a context like that:
self.navigationController?.navigationBar.setBottomBorderColor(UIColor.whiteColor())
I found that pretty useful as I had to deal with that colored-border-problem.
I solved this problem with the use of autolayouts. The solution works on different screen sizes and with orientation change.
extension UINavigationBar {
#IBInspectable var bottomBorderColor: UIColor {
get {
return self.bottomBorderColor;
}
set {
let bottomBorderRect = CGRect.zero;
let bottomBorderView = UIView(frame: bottomBorderRect);
bottomBorderView.backgroundColor = newValue;
addSubview(bottomBorderView);
bottomBorderView.translatesAutoresizingMaskIntoConstraints = false;
self.addConstraint(NSLayoutConstraint(item: bottomBorderView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: 0));
self.addConstraint(NSLayoutConstraint(item: bottomBorderView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0));
self.addConstraint(NSLayoutConstraint(item: bottomBorderView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0));
self.addConstraint(NSLayoutConstraint(item: bottomBorderView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute,multiplier: 1, constant: 1));
}
}
}
Based on the answer from #sash I made an extension in Swift using Autolayout, explained right here.
In essence, the other solutions have the following pitfalls:
Cannot add dropshadow if using the UIImage solution
The subview added doesn't resize upon rotation of the view
extension UINavigationBar {
func setBottomBorderColor(color: UIColor, height: CGFloat) -> UIView {
let bottomBorderView = UIView(frame: CGRectZero)
bottomBorderView.translatesAutoresizingMaskIntoConstraints = false
bottomBorderView.backgroundColor = color
self.addSubview(bottomBorderView)
let views = ["border": bottomBorderView]
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[border]|", options: [], metrics: nil, views: views))
self.addConstraint(NSLayoutConstraint(item: bottomBorderView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: height))
self.addConstraint(NSLayoutConstraint(item: bottomBorderView, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1.0, constant: height))
return bottomBorderView
}
}
This let you still add a drop shadow if you need to, and this handles rotation nicely !
If you like simple and hacky solutions like I do, create a view that covers the default border:
UIView *navBarLineView = [[UIView alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.navigationController.navigationBar.frame),
CGRectGetWidth(self.navigationController.navigationBar.frame), 1)];
navBarLineView.backgroundColor = [UIColor redColor];
[self.navigationController.navigationBar addSubview:navBarLineView];
budidino solutions works very well. Here it is for Swift:
let navBarLineView = UIView(frame: CGRectMake(0,
CGRectGetHeight((navigationController?.navigationBar.frame)!),
CGRectGetWidth((self.navigationController?.navigationBar.frame)!),
1))
navBarLineView.backgroundColor = UIColor.whiteColor()
navigationController?.navigationBar.addSubview(navBarLineView)
Well, if you want to remove bottom border you set shadow image to empty image
[navigationBar setShadowImage:[UIImage new]];
so if you want to set it to another color just create image with that color, I use a helper function to create image from color below (original source http://jslim.net/blog/2014/05/05/ios-customize-uitabbar-appearance/)
+ (UIImage *)imageFromColor:(UIColor *)color forSize:(CGSize)size
{
return [UIImage imageFromColor:color forSize:size withCornerRadius:0];
}
+ (UIImage *)imageFromColor:(UIColor *)color forSize:(CGSize)size withCornerRadius:(CGFloat)radius
{
CGRect rect = CGRectMake(0, 0, size.width, size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Begin a new image that will be the new image with the rounded corners
// (here with the size of an UIImageView)
UIGraphicsBeginImageContext(size);
// Add a clip before drawing anything, in the shape of an rounded rect
[[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius] addClip];
// Draw your image
[image drawInRect:rect];
// Get the image, here setting the UIImageView image
image = UIGraphicsGetImageFromCurrentImageContext();
// Lets forget about that we were drawing
UIGraphicsEndImageContext();
return image;
}
and in my navbar
[navigationBar setShadowImage:[UIImage imageFromColor:[UIColor redColor] forSize:CGSizeMake(CGRectGetWidth(self.tableView.frame), 1)]];
that's it, It's working for me, hope this help. Please consider changing the accepted answer because its not working and can be confusing
To build on #sash's Swift implementation you can make the border responsive to rotation/trait changes by using constraints:
extension UINavigationBar {
func setBottomBorderColor(color: UIColor, height: CGFloat) {
let bottomBorderView = UIView()
bottomBorderView.backgroundColor = color
bottomBorderView.translatesAutoresizingMaskIntoConstraints = false
addSubview(bottomBorderView)
// Add constraints to make the bar always stay at the bottom of the nav bar and change size with rotation/trait changes
let horizontalConstraint = NSLayoutConstraint(item: bottomBorderView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
let verticalConstraint = NSLayoutConstraint(item: bottomBorderView, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0)
let widthConstraint = NSLayoutConstraint(item: bottomBorderView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: self, attribute: .width, multiplier: 1, constant: 0)
let heightConstraint = NSLayoutConstraint(item: bottomBorderView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: height)
self.addConstraints([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
}
Here's the method for creating image with clear color:
+ (UIImage*)imageFromColor:(UIColor *)color withSize:(CGSize)sizeImage
{
UIImage *resultImage = nil;
UIGraphicsBeginImageContext(sizeImage);
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), color.CGColor);
CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0.0f, 0.0f, sizeImage.width, sizeImage.height));
resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return resultImage;
}
Here's it's usage for removing annoying bottom line:
navigationBar.shadowImage = [UIImage imageFromColor:[UIColor clearColor] withSize:CGSizeMake(1.0f, 1.0f)];
Picture 1
you can use Reveal to see the border color is the UIImageView's backgroundColor.
so directly modifying the imageView's backgroundColor or hide it.
the code: i write in #interface QdtTabBarController : UITabBarController
Class backGroundClass = NSClassFromString(#"_UIBarBackground");
for (UIView *view in self.tabBar.subviews) {
if ([view isKindOfClass:backGroundClass]) {
for (UIView *view2 in view.subviews) {
if ([view2 isKindOfClass:[UIImageView class]]) {
dispatch_async(dispatch_get_main_queue(), ^{
view2.backgroundColor = [UIColor redColor];
});
};
};
break;
}
}
Picture 2
I'm using RubyMotion with the RedPotion gem, which includes a StandardAppearance class. This is what I did!
Put this line at the top of your app_delegate.rb, just before the on_load method:
ApplicationStylesheet.new(nil).application_setup
Then, in your application_stylesheet.rb, put this as the last line in the application_setup method:
StandardAppearance.apply app.window
And then this is my StandardAppearance class:
class StandardAppearance
def self.apply(window)
Dispatch.once do
UINavigationBar.appearance.tap do |o|
o.setBackgroundImage(UIImage.alloc.init, forBarMetrics: UIBarMetricsDefault)
o.shadowImage = UIImage.alloc.init
end
end
end
end
This will help you :)
[self.navigationController.navigationBar.layer setBorderWidth:2.0];// Just to make sure its working
[self.navigationController.navigationBar.layer setBorderColor:[[UIColor redColor] CGColor]];

iphone:How to use index number on map pin in MKAnnotation View?

Hello friends,
I am developing a functionality to display index number on map pin and set image on mapview of iPhone so please tell me any link or any idea to develop this functionality.
Thanks in advance.
What is an index number in the context? Is it just a digit that your code is displaying? If so your question is how to display text on a map. Use a map overlay. Same for images. Search for MKMapOverlay and go from there.
I have use this method for index number in annotation view.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
guard !(annotation is MKUserLocation) else {
return nil
}
// Better to make this class property
let annotationIdentifier = "AnnotationIdentifier"
var annotationView = MKAnnotationView()
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
annotationView.frame = CGRect(x: annotationView.frame.origin.x, y: annotationView.frame.origin.y, width: 80, height: 200)
annotationView.annotation = annotation
annotationView.tag = index
index += 1
let imageViewPin = UIImageView(frame: CGRect(x: 0, y: 0, width: 30, height: 40))
imageViewPin.center = CGPoint(x: annotationView.center.x, y: annotationView.center.y - 11)
imageViewPin.image = UIImage(named: "green_pin")
annotationView.addSubview(imageViewPin)
return annotationView
}
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
debugPrint(view.tag)
}

Underlining text in UIButton

Can anyone suggest how to underline the title of a UIButton ? I have a UIButton of Custom type, and I want the Title to be underlined, but the Interface Builder does not provide any option to do so.
In Interface Builder when you select the Font Option for a Button, it provides option to select None, Single, Double, Color but none of these provide any changes to the Title on the Button.
Any help appreciated.
To use interface builder to underline, one has to:
Change it to attributed
Highlight the text in the Attributes inspector
Right click, choose Font and then Underline
Video someone else made
https://www.youtube.com/watch?v=5-ZnV3jQd9I
From iOS6 it is now possible to use an NSAttributedString to perform underlining (and anything else attributed strings support) in a much more flexible way:
NSMutableAttributedString *commentString = [[NSMutableAttributedString alloc] initWithString:#"The Quick Brown Fox"];
[commentString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0, [commentString length])];
[button setAttributedTitle:commentString forState:UIControlStateNormal];
Note: added this as another answer - as its a totally different solution to my previous one.
Edit:
oddly (in iOS8 at least) you have to underline the first character otherwise it doesn't work!
so as a workaround, set the first char underlined with clear colour!
// underline Terms and condidtions
NSMutableAttributedString* tncString = [[NSMutableAttributedString alloc] initWithString:#"View Terms and Conditions"];
// workaround for bug in UIButton - first char needs to be underlined for some reason!
[tncString addAttribute:NSUnderlineStyleAttributeName
value:#(NSUnderlineStyleSingle)
range:(NSRange){0,1}];
[tncString addAttribute:NSUnderlineColorAttributeName value:[UIColor clearColor] range:NSMakeRange(0, 1)];
[tncString addAttribute:NSUnderlineStyleAttributeName
value:#(NSUnderlineStyleSingle)
range:(NSRange){5,[tncString length] - 5}];
[tncBtn setAttributedTitle:tncString forState:UIControlStateNormal];
UIUnderlinedButton.h
#interface UIUnderlinedButton : UIButton {
}
+ (UIUnderlinedButton*) underlinedButton;
#end
UIUnderlinedButton.m
#implementation UIUnderlinedButton
+ (UIUnderlinedButton*) underlinedButton {
UIUnderlinedButton* button = [[UIUnderlinedButton alloc] init];
return [button autorelease];
}
- (void) drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;
// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// set to same colour as text
CGContextSetStrokeColorWithColor(contextRef, self.titleLabel.textColor.CGColor);
CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender);
CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);
CGContextClosePath(contextRef);
CGContextDrawPath(contextRef, kCGPathStroke);
}
#end
You can do it in the interface builder itself.
Select the attribute inspector
Change the title type from plain to attributed
Set appropriate font size and text alignment
Then select the title text and set the font as underlined
It is very simple with attributed string
Creates a dictionary with set attributes and apply to the attributed string. Then you can set the attributed string as attibutedtitle in uibutton or attributedtext in uilabel.
NSDictionary *attrDict = #{NSFontAttributeName : [UIFont
systemFontOfSize:14.0],NSForegroundColorAttributeName : [UIColor
whiteColor]};
NSMutableAttributedString *title =[[NSMutableAttributedString alloc] initWithString:#"mybutton" attributes: attrDict];
[title addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInteger:NSUnderlineStyleSingle] range:NSMakeRange(0,[commentString length])]; [btnRegLater setAttributedTitle:title forState:UIControlStateNormal];
Here is my function, works in Swift 1.2.
func underlineButton(button : UIButton, text: String) {
var titleString : NSMutableAttributedString = NSMutableAttributedString(string: text)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, count(text.utf8)))
button.setAttributedTitle(titleString, forState: .Normal)
}
UPDATE Swift 3.0 extension:
extension UIButton {
func underlineButton(text: String) {
let titleString = NSMutableAttributedString(string: text)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0, text.characters.count))
self.setAttributedTitle(titleString, for: .normal)
}
}
The Swift 5.0 version that works as of September 2019 in Xcode 10.3:
extension UIButton {
func underlineText() {
guard let title = title(for: .normal) else { return }
let titleString = NSMutableAttributedString(string: title)
titleString.addAttribute(
.underlineStyle,
value: NSUnderlineStyle.single.rawValue,
range: NSRange(location: 0, length: title.count)
)
setAttributedTitle(titleString, for: .normal)
}
}
To use it, set your button title first with button.setTitle("Button Title", for: .normal) and then call button.underlineText() to make that title underlined.
Nick's answer is a great, quick way to do this.
I added support in drawRect for shadows.
Nick's answer doesn't take into account if your button title has a shadow below the text:
But you can move the underline down by the height of the shadow like so:
CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
CGFloat shadowHeight = self.titleLabel.shadowOffset.height;
descender += shadowHeight;
Then you'll get something like this:
For Swift 3 the following extension can be used:
extension UIButton {
func underlineButton(text: String) {
let titleString = NSMutableAttributedString(string: text)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.styleSingle.rawValue, range: NSMakeRange(0, text.characters.count))
self.setAttributedTitle(titleString, for: .normal)
}
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;
// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
UIColor *colr;
// set to same colour as text
if (self.isHighlighted || self.isSelected) {
colr=self.titleLabel.highlightedTextColor;
}
else{
colr= self.titleLabel.textColor;
}
CGContextSetStrokeColorWithColor(contextRef, colr.CGColor);
CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender);
CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);
CGContextClosePath(contextRef);
CGContextDrawPath(contextRef, kCGPathStroke);
}
//Override this to change the underline color to highlighted color
-(void)setHighlighted:(BOOL)highlighted
{
[super setHighlighted:highlighted];
// [self setNeedsDisplay];
}
Expanding on the answer by #Nick H247, I experienced an issue where firstly the underline was not redrawing when the button resized on rotation; this can be solved by setting your button to redraw like so:
myButton.contentMode = UIViewContentModeRedraw;
This forces the button to redraw when the bounds change.
Secondly, the original code assumed you only had 1 line of text in the button (my button wraps to 2 lines on rotation) and the underline only appears on the last line of text. The drawRect code can be modified to first calculate the number of lines in the button, then put an underline on every line rather than just the bottom, like so:
- (void) drawRect:(CGRect)rect {
CGRect textRect = self.titleLabel.frame;
// need to put the line at top of descenders (negative value)
CGFloat descender = self.titleLabel.font.descender;
CGContextRef contextRef = UIGraphicsGetCurrentContext();
// set to same colour as text
CGContextSetStrokeColorWithColor(contextRef, self.titleLabel.textColor.CGColor);
CGSize labelSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font
constrainedToSize:self.titleLabel.frame.size
lineBreakMode:UILineBreakModeWordWrap];
CGSize labelSizeNoWrap = [self.titleLabel.text sizeWithFont:self.titleLabel.font forWidth:self.titleLabel.frame.size.width lineBreakMode:UILineBreakModeMiddleTruncation ];
int numberOfLines = abs(labelSize.height/labelSizeNoWrap.height);
for(int i = 1; i<=numberOfLines;i++) {
// Original code
// CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender + PADDING);
//
// CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender);
CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + (labelSizeNoWrap.height*i) + descender + PADDING);
CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + (labelSizeNoWrap.height*i) + descender);
CGContextClosePath(contextRef);
CGContextDrawPath(contextRef, kCGPathStroke);
}
}
Hope this code helps someone else!
In swift
func underlineButton(button : UIButton) {
var titleString : NSMutableAttributedString = NSMutableAttributedString(string: button.titleLabel!.text!)
titleString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: NSMakeRange(0, button.titleLabel!.text!.utf16Count))
button.setAttributedTitle(titleString, forState: .Normal)}
You can use this code to add underline with spacing in button.
When I tried to draw an underline from interface builder. It look like below image.
1 - Interface builder reference
And after using below code I achieved the result as I wanted.
2 - using described code
public func setTextUnderline()
{
let dummyButton: UIButton = UIButton.init()
dummyButton.setTitle(self.titleLabel?.text, for: .normal)
dummyButton.titleLabel?.font = self.titleLabel?.font
dummyButton.sizeToFit()
let dummyHeight = dummyButton.frame.size.height + 3
let bottomLine = CALayer()
bottomLine.frame = CGRect.init(x: (self.frame.size.width - dummyButton.frame.size.width)/2, y: -(self.frame.size.height - dummyHeight), width: dummyButton.frame.size.width, height: 1.0)
bottomLine.backgroundColor = self.titleLabel?.textColor.cgColor
self.layer.addSublayer(bottomLine)
}
How will one handle the case when we keep a button underlined pressed? In that case the button's textcolor changes according to highlighted color but line remains of original color. Let say if button text color in normal state is black then its underline will also have black color. The button's highlighted color is white. Keeping button pressed changes button text color from black to white but underline color remains black.
I believe it's some bug in font editor in XCode. If you using interface builder you have to change title from Plain to Attributed, open TextEdit create underlined text and copy-paste to textbox in XCode
Nick H247's answer but Swift approach:
import UIKit
class UnderlineUIButton: UIButton {
override func drawRect(rect: CGRect) {
super.drawRect(rect)
let textRect = self.titleLabel!.frame
var descender = self.titleLabel?.font.descender
var contextRef: CGContextRef = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(contextRef, self.titleLabel?.textColor.CGColor);
CGContextMoveToPoint(contextRef, textRect.origin.x, textRect.origin.y + textRect.size.height + descender!);
CGContextAddLineToPoint(contextRef, textRect.origin.x + textRect.size.width, textRect.origin.y + textRect.size.height + descender!);
CGContextClosePath(contextRef);
CGContextDrawPath(contextRef, kCGPathStroke);
}
}
func underline(text: String, state: UIControlState = .normal, color:UIColor? = nil) {
var titleString = NSMutableAttributedString(string: text)
if let color = color {
titleString = NSMutableAttributedString(string: text,
attributes: [NSForegroundColorAttributeName: color])
}
let stringRange = NSMakeRange(0, text.characters.count)
titleString.addAttribute(NSUnderlineStyleAttributeName,
value: NSUnderlineStyle.styleSingle.rawValue,
range: stringRange)
self.setAttributedTitle(titleString, for: state)
}
Swift 3 version for #NickH247's answer with custom underline color, linewidth and gap:
import Foundation
class UnderlinedButton: UIButton {
private let underlineColor: UIColor
private let thickness: CGFloat
private let gap: CGFloat
init(underlineColor: UIColor, thickness: CGFloat, gap: CGFloat, frame: CGRect? = nil) {
self.underlineColor = underlineColor
self.thickness = thickness
self.gap = gap
super.init(frame: frame ?? .zero)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
guard let textRect = titleLabel?.frame,
let decender = titleLabel?.font.descender,
let context = UIGraphicsGetCurrentContext() else { return }
context.setStrokeColor(underlineColor.cgColor)
context.move(to: CGPoint(x: textRect.origin.x, y: textRect.origin.y + textRect.height + decender + gap))
context.setLineWidth(thickness)
context.addLine(to: CGPoint(x: textRect.origin.x + textRect.width, y: textRect.origin.y + textRect.height + decender + gap))
context.closePath()
context.drawPath(using: .stroke)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Resources