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

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))
}

Related

setValue:forUndefinedKey: this class is not key value coding-compliant for the key boldColor in iOS 12

I have this custom label I'm using across my app but when I run my app on devices on ios 12 it crashes with this error
NSUnknownKeyException', reason: '[<EFontRegular 0x10999e050> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key boldColor
but works fine in ios 13 and above. Any help will be appreciated. All the other solutions online didn't work for me
#IBDesignable
final class EFontRegular: BaseUILabel {
override var fontType: UIFont? {
return UIFont(name: EFontConstant.Name.regular.rawValue, size: fontSize)
}
}
#IBDesignable
class BaseUILabel: UILabel {
#IBInspectable var fontSize: CGFloat {
return self.font.pointSize
}
#IBInspectable var fontType: UIFont? {
return UIFont(name: EFontConstant.Name.regular.rawValue, size: EFontConstant.Size.regular.rawValue)
}
#IBInspectable var stringToColor: String = "" {
didSet {
let alignment: NSTextAlignment = alignCenter ? .center : .left
attributedText = text?.lineSpacedWithInlineColor(lineSpacing, alignment, stringToColor: stringToColor)
}
}
#IBInspectable var boldColor: UIColor {
traitCollection.userInterfaceStyle == .light ? .black : .white
}
#IBInspectable var stringToBold: String = "" {
didSet {
let alignment: NSTextAlignment = alignCenter ? .center : .left
attributedText = text?.lineSpacedWithInlineColor(lineSpacing, alignment, stringToColor: stringToBold, color: boldColor, bold: true, boldTextSize: fontSize)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
commonInit()
}
}
I had this same issue, which I resolved by removing unwanted or extra connections which you are not using in the storyboard. For me, this error generally occurred as I copied all the storyboards from one project and pasted them into the new project, and changed the component names and actions but forgot to remove the old names and actions for the UI Components in the storyboard.
Like this

Subclass UIButton with system font, bold, and kerning

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" !)

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

Swift UILabel subclass automatically call enum switch function

I have been creating a UILabel class called RPLabel, which is supposed to shorten all of my long list of programmatically set labels. This is the class code:
class RPLabel: UILabel {
// moneyTitle.frame = CGRect(x: 50, y: -50, width:XX, height: 35) //
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
self.setup()
self.testForLabelType()
}
enum labelTypeEnumeration {
case title, subtitle
}
var labelType = labelTypeEnumeration.title
override init(frame: CGRect){
super.init(frame: frame)
self.setup()
}
override func awakeFromNib() {
super.awakeFromNib()
self.setup()
}
func setup(){
self.text = self.text
self.textColor = self.textColor
self.font = self.font
self.layer.display()
}
func testForLabelType() {
switch labelType {
case .title:
setupTitle()
print("setupTitle")
case .subtitle:
setupSubtitle()
print("setupSubtitle")
}
}
func setupTitle(){
self.font = UIFont.boldSystemFont(ofSize: 12)
self.textColor = UIColor.secondaryColor()
self.textAlignment = .left
}
func setupSubtitle(){
self.font = UIFont.boldSystemFont(ofSize: 18)
self.textColor = UIColor.rgb(red: 200, green: 200, blue: 200)
self.textAlignment = .left
}
}
To make a label, I use the following piece of code:
var moneyTitle = RPLabel()
moneyTitle.labelType = .title
moneyTitle.testForLabelType()
moneyTitle.text = "MONEY"
moneyTitle.frame = CGRect(x: 50, y: -50, width:XX, height: 35)
The issue that I have is that I would like to set the labelType to .title without having to write moneyTitle.testForLabelType(). In other words, I would like for that function to run automatically in the class. An alternative I would not like would be inserting the parameter in the init, so please avoid telling me to do so, thanks.
You can use didSet:
didSet is called immediately after the new value is stored.
var labelType = labelTypeEnumeration.title {
didSet {
testForLabelType()
}
}

Why when using TextKit to render text does the second line end up shorter than the first?

Sample project: http://d.pr/f/1coXu
I'm using TextKit to render some text into what is essentially a very basic recreation of UILabel. The code is very simple and in this example just draws a hardcoded NSAttributedString into the view itself:
class TextKitView: UIView {
// TextKit Objects
var textStorage: NSTextStorage!
var textContainer: NSTextContainer!
var layoutManager: NSLayoutManager!
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = UIColor.clearColor()
layoutManager = NSLayoutManager()
textStorage = NSTextStorage(attributedString: createAttributedString())
textStorage.addLayoutManager(layoutManager)
textContainer = NSTextContainer(size: CGSize(width: bounds.width, height: bounds.height))
layoutManager.addTextContainer(textContainer)
}
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func drawRect(rect: CGRect) {
let glyphRange = layoutManager.glyphRangeForTextContainer(textContainer)
layoutManager.drawBackgroundForGlyphRange(glyphRange, atPoint: bounds.origin)
layoutManager.drawGlyphsForGlyphRange(glyphRange, atPoint: bounds.origin)
}
private func createAttributedString() -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: "Test string for testing", attributes: [NSForegroundColorAttributeName: UIColor.blackColor(), NSFontAttributeName: UIFont.systemFontOfSize(16.0)])
let labelAttributedString = NSAttributedString(string: "Foo bar\n testing", attributes: [NSForegroundColorAttributeName: UIColor.darkGrayColor(), NSBackgroundColorAttributeName: UIColor(white: 0.95, alpha: 1.0)])
attributedString.appendAttributedString(labelAttributedString)
return attributedString
}
}
However, when I create the view:
override func viewDidLoad() {
super.viewDidLoad()
let textKitView = TextKitView(frame: CGRect(x: 0.0, y: 100.0, width: 320.0, height: 320.0))
view.addSubview(textKitView)
}
It ends up looking like this:
Where you can see for some inexplicable reason the second line's background color and height is significantly shorter than the first, which looks really weird.
How do I prevent this? What's causing this? If I use UILabel and provide the same attributed string both lines are the same height. I've attached a very minimal sample project above if it helps.
Check out NSParagraphStyle: https://developer.apple.com/reference/uikit/nsparagraphstyle
There are line height properties on that class, i.e. minimumLineHeight and maximumLineHeight that may accomplish what you're trying to do. You can set the paragraph style for an NSAttributedString like so:
let paraStyle = NSMutableParagraphStyle()
paraStyle.setParagraphStyle = NSParagraphStyle.default
paraStyle.minimumLineHeight = 50 // for example
attributedString.addAttribute(NSParagraphStyleName, value: paraStyle, range:NSMakeRange(0, attributedString.length))

Resources