iOS7 - Change UINavigationBar border color - colors

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]];

Related

Set BackgroundColor of Second View and Supplementary View of UISplitViewController

As you can see, in a 3 lines SplitViewController, on the top, there are while space that are not match the background color.
Any way to set them the same as the blue background?
The red background color was set by self.navigationController?.navigationBar.backgroundColor = .red. But set color there can't match the background color.
At last I have to create an image from color and set the image as background.
Thanks to this post.
Creating a UIImage from a UIColor to use as a background image for UIButton
extension UIImage {
static func from(color: UIColor) -> UIImage {
let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor)
context!.fill(rect)
let img = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return img!
}
}
if let nav = self.navigationController {
nav.navigationBar.setBackgroundImage(UIImage.from(color: UIColor(named: "Blue")!), for: .default)
}

SwiftUI Add top line to TabBar

How can I add a line/divider on the top of my TabBar for adding some kind of a splitter between my view and TabBar?
I forgot that I have added this code for making the TabBar completely white.
But my shadow color was white, so I just changed it to Gray
extension UITabBarController {
override open func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .white
appearance.shadowImage = UIImage()
appearance.shadowColor = .gray
appearance.stackedLayoutAppearance.normal.iconColor = .black
appearance.stackedLayoutAppearance.normal.titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black]
self.tabBar.standardAppearance = appearance
}
}
You can change color of TabBar line
init(){
UITabBar.appearance().separatorColor = .red
UITabBar.appearance().separatorInset = UIEdgeInsets(top: 0, left: -10, bottom: 0, right: 0)
}

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

UITextView to Mimic UITextField [for basic round corner text field]

I want to have UITextField with multiple lines, after a quick google on this issue I found that I should use TextView so I did switch my code to use UITextView when I want multiple lines. My View still have other one line textField that I want to keep.
To make my TextView looks like TextField, I had to add code to set border and radius, but they look a little bit different on iOS7. Does anyone know:
what is the color for the UITextField border? when both enabled and disabled so I can sent my textview to match it.
what is radius of the corner of TextField.
what is the background color for UITextField when it is disabled[attached picture shows the text field has lighter shade of grey when it is disabled]? so i can set my text view to the same color when i disable user interaction.
If there is away to keep using textfield for multiline text, I am all ears and i switch to use it.
Best Regards,
I use this:
textView.layer.borderColor = [[UIColor colorWithRed:215.0 / 255.0 green:215.0 / 255.0 blue:215.0 / 255.0 alpha:1] CGColor];
textView.layer.borderWidth = 0.6f;
textView.layer.cornerRadius = 6.0f;
little differences in the params make it looks more like UITextField(I hope).
I use this:
#import <QuartzCore/QuartzCore.h>
-(void) textViewLikeTextField:(UITextView*)textView
{
[textView.layer setBorderColor:[[UIColor colorWithRed:212.0/255.0
green:212.0/255.0
blue:212.0/255.0
alpha:1] CGColor]];
[textView.layer setBorderWidth:1.0f];
[textView.layer setCornerRadius:7.0f];
[textView.layer setMasksToBounds:YES];
}
and get a good resut.
I have a small subclass of UITextView that gives in iOS 7 the same look as the UITextField
The interface is empty:
#interface MyTextView : UITextView
#end
The implementation overwrites the initialization and the setter of the 'editable' property:
#implementation MyTextView
//================================================================================
- (id) initWithFrame: (CGRect) frame
{
self = [super initWithFrame:frame];
if (self)
{
[self commonInit];
}
return self;
}
//================================================================================
- (id) initWithCoder: (NSCoder *) aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self commonInit];
}
return self;
}
//================================================================================
- (void) commonInit
{
self.layer.borderWidth = 1.0f;
self.layer.borderColor = [[UIColor colorWithRed:232.0/255.0
green:232.0/255.0 blue:232.0/255.0 alpha:1] CGColor];
self.layer.cornerRadius = 6;
}
//================================================================================
- (void) setEditable: (BOOL) editable
{
[super setEditable:editable];
if (editable)
{
self.backgroundColor = [UIColor whiteColor];
}
else
{
self.backgroundColor = [UIColor colorWithRed:250.0/255.0
green:250.0/255.0 blue:250.0/255.0 alpha:1];
}
}
//================================================================================
#end
This is the closest I got with enabled UITextView
[yourTextView.layer setBorderColor:[[[UIColor lightGrayColor] colorWithAlphaComponent:0.2] CGColor]];
[yourTextView.layer setBorderWidth:2.0];
yourTextView.layer.cornerRadius = 5;
yourTextView.clipsToBounds = YES;
yourTextView.textColor = [UIColor lightGrayColor];

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