Subclass UIButton with system font, bold, and kerning - ios

You have to use attributed text to do kerning. But. On the other hand, you can't get at system fonts in the IB attributed text menu.
How do I make (in code) a UIButton which has
system font, weight bold
size 11
kern attribute set to -2
Ideally it would collect the text of the button from plain text Title in IB. But if the text has to be set in code that is fine.
class NiftyButton: UIButton {
????
}
Normally I initialize UIButton like this .. but I don't even know if that's the best place to do this? (You can't do it in layoutSubviews, since it will loop of course.)
class InitializeyButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
common()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
common()
}
func common() {
...
}
}
How to achieve in code ...
system font, weight bold
size 11
kern attribute set to -2

Here is the class you can use to get the desired result:
class InitializeyButton: UIButton {
#IBInspectable var spacing:CGFloat = 0 {
didSet {
updateTitleOfLabel()
}
}
override func setTitle(_ title: String?, for state: UIControl.State) {
let color = super.titleColor(for: state) ?? UIColor.black
let attributedTitle = NSAttributedString(
string: title ?? "",
attributes: [NSAttributedString.Key.kern: spacing,
NSAttributedString.Key.foregroundColor: color,
NSAttributedString.Key.font: UIFont.systemFont(ofSize: self.titleLabel?.font.pointSize ?? 11, weight: .bold) ])
super.setAttributedTitle(attributedTitle, for: state)
}
private func updateTitleOfLabel() {
let states:[UIControl.State] = [.normal, .highlighted, .selected, .disabled]
for state in states {
let currentText = super.title(for: state)
self.setTitle(currentText, for: state)
}
}
}

Copy-paste UIButton for systemFont + kerning:
I've tidied up Jawad's excellent information:
Firstly, at bringup time, you have to create the attributed title:
import UIKit
class SmallChatButton: UIIButton { // (note the extra "I" !!)
override func common() {
super.common()
backgroundColor = .your corporate color
contentEdgeInsets = UIEdgeInsets(top: 7, left: 11, bottom: 7, right: 11)
titles()
}
private func titles() {
let states: [UIControl.State] = [.normal, .highlighted, .selected, .disabled]
for state in states {
let currentText = super.title(for: state)
setTitle(currentText, for: state)
}
}
so to achieve that ..
override func setTitle(_ title: String?, for state: UIControl.State) {
let _f = UIFont.systemFont(ofSize: 10, weight: .heavy)
let attributedTitle = NSAttributedString(
string: title ?? "Click",
attributes: [
NSAttributedString.Key.kern: -0.5,
NSAttributedString.Key.foregroundColor: UIColor.white,
NSAttributedString.Key.font: _f
])
setAttributedTitle(attributedTitle, for: state)
}
override func layoutSubviews() { // example of rounded corners
super.layoutSubviews()
layer.cornerRadius = bounds.height / 2.0
clipsToBounds = true
}
}
Note that a kern of "-0.5" is about what most typographers want for typical "slightly tight type".
It looks good with say all caps, or a slightly bold font, or small type. The Apple measurement system is unlike anything used by typographers, so, you'll just have to vary it until the typographer on your app is satisfied.
What is UIIButton (note the extra "I" !!)
Inevitably in any project you will need a UIButton which has an "I" initializer, so you'll proabbly have:
import UIKit
class UIIButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
common()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
common()
}
func common() {
}
}
(It's quite amazing that in iOS one has to do all of the above to "kern the system font" !)

Related

How to test imported custom font in XCTest (Swift 5)

Hi everyone!
I imported custom font in project.
How can I test it with the text in the text field?
For example I have text field with text = "Stack overflow", with font = UIFont(name: "Roboto-Regular", size: 17).
How can I check that text = "Stack overflow" has font = UIFont(name: "Roboto-Regular", size: 17)? Have you any ideas, how to implement this in XCTest?
Extensions
import UIKit
extension UIFont {
static func robotoRegular (size: CGFloat) -> UIFont? {
return UIFont(name: "Roboto-Regular", size: size)
}
static func robotoBold (size: CGFloat) -> UIFont? {
return UIFont(name: "Roboto-Bold", size: size)
}
}
Will you be unit testing a class which uses those fonts?
I would suggest that you have a custom label subclass which you expect to be that font. The class and tests would look like this:
class CustomLabelWithFont: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
setStyle()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setStyle() {
self.font = .robotoBold(size: 20)
}
}
The tests file would look like so
class TestTestTests: XCTestCase {
func testFontSetting() throws {
let customLabel = CustomLabelWithFont(frame: .zero)
XCTAssertEqual(customLabel.font, .robotoBold(size: 20))
XCTAssertNotEqual(customLabel.font, .systemFont(ofSize: 20))
}
}
You can also test your UIFont extension directly if you wish. There wouldn't be much utility in this aside from checking that the nobody has accidentally changed the font logic
func testFontItself() throws {
let expectedFont: UIFont = .robotoBold(size: 20)!
XCTAssertEqual(expectedFont, UIFont(name: "Roboto-Regular", size: 20))
}

Custom UIButton class does not show title

I have created a custom UIButton class. But the title does not show up. I only get the background color and the rounded corners. But the title is not visible.
Also when I run it in simulator, iOS 13+, its showing the title white. But when I run in my device i.e. iOS 12.4, its not working.
Here is my code:
public class CornerButton : UIButton {
public override init(frame: CGRect) {
super.init(frame: frame)
setup()
self.layoutIfNeeded()
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
self.layoutIfNeeded()
}
func setup() {
self.titleLabel?.font = UIFont(name: "Poppins-SemiBold", size: 12)
self.backgroundColor = UIColor(named: "BarColor")
self.setTitleColor(.white, for: .normal) // this line is not working
self.clipsToBounds = true
}
public override func layoutSubviews() {
self.roundCorners(corners: [.topRight,.bottomLeft], radius: 10)
}
}
You need to call super.layoutSubviews()
public override func layoutSubviews() {
super.layoutSubviews()
self.roundCorners(corners: [.topRight,.bottomLeft], radius: 10)
}
BTW here you don't need layoutSubviews as you use it when you need to make something that depends on the actual measured bounds of the view and as you set a static radius you can omit it

UIButton TitleLabel - How to set UIButton's font when titleLabel is nil?

I am trying change the font of a button class (before the button is initiated), but it does not work. It seems that the "titleLabel" is the issue since it is nil.
guard let object = NSClassFromString(button.key) as? UIButton.Type else { return }
let buttonClass = object.self
buttonClass.appearance().titleLabel?.text = UIFont(name: "HelveticaNeue-Thin", size: 20)
Here the titleLabel is nil, so it won't work.
I have also tried setting the label's font in my class (MBButton is my class), but this does not work as well
UILabel.appearance(whenContainedInInstancesOf[MBButton.self]).font = UIFont(name: "HelveticaNeue-Thin", size: 20)
You can set the title label's font in your subclassed buttons setup:
#IBDesignable
class MMButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
func commonInit() -> Void {
titleLabel?.font = UIFont(name: "HelveticaNeue-Thin", size: 20)
// other customization here...
}
}
Alternatively, if you want to use the .appearance() method, add an extension:
class MMButton: UIButton {
// whatever you're doing for customization
}
extension MMButton {
#objc dynamic var titleLabelFont: UIFont! {
get { return self.titleLabel?.font }
set { self.titleLabel?.font = newValue }
}
}
then,
MMButton.appearance().titleLabelFont = UIFont(name: "HelveticaNeue-Thin", size: 20)
The best would be doing was #DonMag said. If you insist on using appearance, then you need to set titleFont on buttonClass.appearance() instead of titleLabel?.text

IBDesignable class don't seems to work properly

I'm building a new iOS app with swift and trying to make a custom IBDesignable UIButton class to use in the designer.
My code does not seems to work either in the builder or the app itself at runtime.
The default value i gave to the IBInspectable vars does not apply to them.
#IBDesignable class MyButton: UIButton {
#IBInspectable var backColor: UIColor = UIColor.red {
didSet {
self.backgroundColor = backColor
}
}
#IBInspectable var textColor: UIColor = UIColor.white {
didSet {
self.setTitleColor(textColor, for: .normal)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setup()
}
private func setup() {
titleLabel?.font = UIFont.systemFont(ofSize: 17)
contentEdgeInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
layer.cornerRadius = (frame.height / 2)
setTitle(title(for: .normal)?.uppercased(), for: .normal)
}
}
I expect to see the button in the designer/app with a red background and a white text and none of these happens.
I'm using Swift 4.2 and Xcode 10.1.
add this two func s
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
override func layoutSubviews() {
super.layoutSubviews()
self.setup()
}
no need for init
Setting a property default value does not invoke the didSet clause.
You need something like:
private func setup() {
titleLabel?.font = UIFont.systemFont(ofSize: 17)
contentEdgeInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
layer.cornerRadius = (frame.height / 2)
setTitle(title(for: .normal)?.uppercased(), for: .normal)
// I would do it differently, but this is just for demo purposes
// Also, can't assign a prop to itself, so using a temp
var tmp = self.backColor
self.backColor = tmp
tmp = self.textColor
self.textColor = tmp
}
Additionally, Xcode is not very good with IBDesignable, in swift or objective C.
Make sure the menu item: Editor -> Automatically Refresh Views is checked.
You can also explicitly do Editor -> Refresh All Views.
From my experience - this stops working after a while, and you need to quit Xcode and restart it to make it work again.
Make sure that your button is set to type .custom in your Interface Builder.
This is not obvious but the default button type is .system, which cannot be customized. I saw people having no problems with this but in my applications this was always a problem.
Another problem is that if you keep your inspectable values empty, the didSet won't be called and the default value won't be applied.
#IBInspectable var backColor: UIColor? = UIColor.red {
didSet {
updateBackground()
}
}
private func updateBackground() {
self.backgroundColor = backColor
}
func setup() {
...
updateBackground()
}

Adding NSMutableAttributedString into a TextField

I have a custom UITextfield which I want when the user finishes typing, a small "R$" must be added at the begging of the text with different size.
I call the method to add the "R$" like this:
self.addTarget(self, action: #selector(setCurrencyLabelPosition), for: .editingDidEnd)
and then I try to change the attributes and content like this:
func setCurrencyLabelPosition(){
let fullText:String = "R$\((self.text)!)"
self.text = fullText
var attribute:NSMutableAttributedString = NSMutableAttributedString(attributedString:self.attributedText!)
attribute.addAttribute(NSFontAttributeName, value:UIFont.systemFont(ofSize: 12), range: NSRange(location: 0, length: 2))
self.attributedText = attribute
}
the original text of this textfield is set for size 40.0, I want only the "R$" to be of size 12.0
The problem I'm facing is that the whole text gets the size 40.0
It prints the "R$" but the size of 40.
Is it possible to do what I'm trying to using NSMutableAttributedString?
I had been working on your question, I think there is a bug in UITextField because if you modify the font to bigger font it works but if you do so but for small font then don't work.
I have done a custom class and added some customizable Inspectable properties, hope this finally help you
This is how looks, Note: the glitch is because of my gif converter
import UIKit
#IBDesignable
class CustomTextField: UITextField {
#IBInspectable var prefix : String = ""
#IBInspectable var removePrefixOnEditing : Bool = true
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.addTarget(self, action: #selector(setCurrencyLabelPosition), for: .editingDidEnd)
self.addTarget(self, action: #selector(removePrefix), for: .editingDidBegin)
}
func removePrefix(){
if self.attributedText != nil
{
if(self.removePrefixOnEditing)
{
self.defaultTextAttributes = [NSFontAttributeName : UIFont.systemFont(ofSize: 20)]
let prefixRange = NSString(string: (self.attributedText?.string)!).range(of: prefix)
if(prefixRange.location != NSNotFound)
{
self.attributedText = NSAttributedString(string: (self.attributedText?.string.replacingOccurrences(of: prefix, with: ""))!, attributes: self.defaultTextAttributes)
}
}
}
}
func setCurrencyLabelPosition(){
if self.attributedText != nil
{
var fullText:String = "\((self.attributedText?.string)!)"
if(NSString(string: (self.attributedText?.string)!).range(of: prefix).location == NSNotFound)
{
fullText = "\(prefix)\((self.attributedText?.string)!)"
}
//hacky part, seems to be a bug in UITextField
self.defaultTextAttributes = [NSFontAttributeName : UIFont.systemFont(ofSize: 10)]
self.attributedText = NSAttributedString(attributedString: self.changeFontForText(originalText: fullText, text: prefix, basicFont: UIFont.systemFont(ofSize: 20), newFont: UIFont.systemFont(ofSize: 12)))
}
}
func changeFontForText(originalText:String,text:String,basicFont:UIFont, newFont:UIFont) -> NSMutableAttributedString
{
let resultAttributedString = NSMutableAttributedString(string: originalText, attributes: [NSFontAttributeName : basicFont])
let range = NSString(string: originalText).range(of: text)
if(range.location != NSNotFound)
{
resultAttributedString.setAttributes([NSFontAttributeName:newFont], range: range)
}
return resultAttributedString
}
}
Hope this helps

Resources