How to use SF Rounded in UIKit - swift

I have got a label and I spent quite a lot time trying to change the font of my UILabel to SF Rounded Bold.
Things like titleLabel.font = UIFont(name: "SFRounded-Bold", size: 34.0) or titleLabel.font = UIFont(name: "SanFranciscoRounded-Bold ", size: 34.0) don't work.
Is it even possible to use SF Rounded in UIKit? It is not listed in fonts list in scene editor and no one ever asked how to use it but in SwiftUI I can use SF Rounded without any problem.

Swift 5, iOS 13+
Here's an extension version of the other post's answer
import UIKit
extension UIFont {
class func rounded(ofSize size: CGFloat, weight: UIFont.Weight) -> UIFont {
let systemFont = UIFont.systemFont(ofSize: size, weight: weight)
let font: UIFont
if let descriptor = systemFont.fontDescriptor.withDesign(.rounded) {
font = UIFont(descriptor: descriptor, size: size)
} else {
font = systemFont
}
return font
}
}
To use it:
let label = UILabel()
label.text = "Hello world!"
label.font = .rounded(ofSize: 16, weight: .regular)

More concise version of Kevin's answer:
import UIKit
extension UIFont {
class func rounded(ofSize size: CGFloat, weight: UIFont.Weight) -> UIFont {
let systemFont = UIFont.systemFont(ofSize: size, weight: weight)
guard #available(iOS 13.0, *), let descriptor = systemFont.fontDescriptor.withDesign(.rounded) else { return systemFont }
return UIFont(descriptor: descriptor, size: size)
}
}
Usage:
let label = UILabel()
label.font = .rounded(ofSize: 16, weight: .regular)

Expanding on Artem's answer:
extension UIFont {
func rounded() -> UIFont {
guard let descriptor = fontDescriptor.withDesign(.rounded) else {
return self
}
return UIFont(descriptor: descriptor, size: pointSize)
}
}
Now you can add .rounded() to any font to get its rounded variant if it exists.

Related

How do I fix clipped ascenders and descenders in a custom font?

I have a custom font that I'm using in my app, Cassandra-Personal. It's a signature style font. Whenever I use it inside of a Text("someText") the font is clipped on the top and bottom due to ascenders and descenders present in the font. A couple of fixes that I've attempted are to change the .frame(), .baselineOffset(), and I also attempted to play around with the kerning/tracking to no avail. In short the text, regardless of the frame, is clipped. Whenever I .baselineOffset() it fixes the one side or the other, but not both sides. For obvious reasons I can't offset both sides simultaneously.
The View
struct GroupListHeaderView: View {
let headerTitle: String
var body: some View {
ZStack(alignment: .center) {
Rectangle().fill(Color(UIColor.yellow))
Text(headerTitle)
.font(Fonts.header)
.frame(height: 100)
}.frame(height: 100)
}
}
Addt'l Supplementary Code
struct Fonts {
// Other fonts removed, not relevant.
static let header = Font.custom(FontName.cassandra.rawValue, size: 30)
}
public enum FontName: String {
// Other cases removed, not relevant.
case cassandra = "CassandraPersonalUse-Regular"
}
Example #1
Baseline Offset of 30
Example #2
Baseline Offset of -30
Example #3
Frame height: 100
I faced with this problem today.
I have a workaround, but this is not the final solution. I add an invisible character '|' before and after the string.
func titleLabel(of title: String, fontName: String, fontSize: CGFloat, textColor: UIColor) -> AttributedString {
let result = NSMutableAttributedString()
var beginAttributes: [NSAttributedString.Key: Any] = [:]
beginAttributes[.font] = UIFont(name: fontName, size: fontSize)
beginAttributes[.foregroundColor] = UIColor.clear
beginAttributes[.baselineOffset] = -fontSize / 4
let beginAttributedString = NSAttributedString(string: "|", attributes: beginAttributes)
result.append(beginAttributedString)
var mainAttributes: [NSAttributedString.Key: Any] = [:]
mainAttributes[.font] = UIFont(name: fontName, size: fontSize)
mainAttributes[.foregroundColor] = textColor
let mainAttributedString = NSAttributedString(string: title, attributes: mainAttributes)
result.append(mainAttributedString)
var endAttributes: [NSAttributedString.Key: Any] = [:]
endAttributes[.font] = UIFont(name: fontName, size: fontSize)
endAttributes[.foregroundColor] = UIColor.clear
endAttributes[.baselineOffset] = fontSize / 4
let endAttributedString = NSAttributedString(string: "|", attributes: endAttributes)
result.append(endAttributedString)
let string = AttributedString(result)
return string
}
// useage:
Text(self.titleLabel(of: "Some Title", fontName: "CassandraPersonalUse-Regular", fontSize: 30, textColor: .black))
This down code should solve your issue:
Note: You can not give a fixed size to ZStack! because it would overridden the needed size for Text! it may get big or small size!
Also I think you would need use backgroundColor for Text instead of using Rectangle().
struct GroupListHeaderView: View {
let headerTitle: String
var body: some View {
Rectangle().fill(Color(UIColor.yellow))
.overlay(Text(headerTitle).font(Font.custom("CassandraPersonalUse-Regular", size: 30)))
}
}

Finding NSFont Size to Fit a particular width

I'm using the following code to find an NSFont size to fit a Width using a while loop.Is there an inbuilt function to make the computation easier?
let ratio = image.size.width/referenceimagesize!.width
let fcsize=fontsize.width*ratio
if(fontsize.width<fcsize)
{
while(fontsize.width==fcsize)
{
var newheight:CGFloat = 0;
font = NSFont(name: font!.fontName, size: font!.pointSize+1)
var fontAttributes = [NSAttributedStringKey.font: font]
fontsize = (text as NSString).size(withAttributes: fontAttributes)
}
}
No, there's no inbuilt function, but why do you use while(fontsize.width==fcsize)? Shouldn't the comparison be <? Try something like this:
func findFontSize(text: String, frameWidth: CGFloat, font: NSFont) -> CGFloat {
var textSize = text.size(withAttributes: [.font: font])
var newPointSize = font.pointSize
while textSize.width < frameWidth {
newPointSize += 1
let newFont = NSFont(name: font.fontName, size: newPointSize)!
textSize = text.size(withAttributes: [.font: newFont])
}
return newPointSize
}

Set font for UIButton with extension

I want to make it easy to change fonts and colors throughout my app. I have made a extension for UIButton like this:
extension UIButton {
func cancelAndSaveButtons() {
backgroundColor = Theme.tintColor
layer.shadowOpacity = 0.25
layer.shadowOffset = CGSize(width: 0, height: 10)
layer.shadowRadius = 5
layer.cornerRadius = frame.height / 2
setTitleColor(Theme.mainFontColor, for: .normal)
titleLabel?.font = UIFont(name: Theme.mainButtonFont, size: 25)
}
}
And I have a class for the Theme:
class Theme {
static let mainFontName = "BrushScriptMT"
static let mainButtonFont = "FredokaOne"
static let accentColor = UIColor(named: "Accent")
static let backgroundColor = UIColor(named: "Background")
static let tintColor = UIColor(named: "Tint")
static let mainFontColor = UIColor(named: "MainFontColor")
}
However, when I call myButton.cancelAndSaveButtons()in viewDidLoad the font or fontSize do not change. What am I missing here?
You might be using the wrong font name. Add this code to your AppDelegate and check if you're using the right name:
for fontFamilyName in UIFont.familyNames {
print("family: \(fontFamilyName)\n")
for fontName in UIFont.fontNames(forFamilyName: fontFamilyName) {
print("font: \(fontName)")
}
}
EDIT:
This is even cooler to print out the fonts:
dump(UIFont.familyNames)
Also, double check if you have imported your font files and added them to your plist. The font names on your plist must be the same as they will be printed on the code above.

Swift boundingRect returns different values for same input on playground and actual project

I'm not perfectly sure why boundingRect returns incorrect value in some case such as when a text ends closed to its maximum width. It works fine on all other cases though.
Here's example.
func snap(_ x: CGFloat) -> CGFloat {
let scale = UIScreen.main.scale
return ceil(x * scale) / scale
}
func snap(_ point: CGPoint) -> CGPoint {
return CGPoint(x: snap(point.x), y: snap(point.y))
}
func snap(_ size: CGSize) -> CGSize {
return CGSize(width: snap(size.width), height: snap(size.height))
}
func snap(_ rect: CGRect) -> CGRect {
return CGRect(origin: snap(rect.origin), size: snap(rect.size))
}
extension String {
func boundingRect(with size: CGSize, attributes: [String: AnyObject]) -> CGRect {
let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
let rect = self.boundingRect(with: size, options: options, attributes: attributes, context: nil)
return snap(rect)
}
func size(fits size: CGSize, font: UIFont, maximumNumberOfLines: Int = 0) -> CGSize {
let attributes = [NSFontAttributeName: font]
var size = self.boundingRect(with: size, attributes: attributes).size
if maximumNumberOfLines > 0 {
size.height = min(size.height, CGFloat(maximumNumberOfLines) * font.lineHeight)
}
return size
}
func width(with font: UIFont, maximumNumberOfLines: Int = 0) -> CGFloat {
let size = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
return self.size(fits: size, font: font, maximumNumberOfLines: maximumNumberOfLines).width
}
func height(fits width: CGFloat, font: UIFont, maximumNumberOfLines: Int = 0) -> CGFloat {
let size = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
return self.size(fits: size, font: font, maximumNumberOfLines: maximumNumberOfLines).height
}
func height(fits width: CGFloat, attributes: [String:AnyObject]) -> CGFloat {
let size = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)
return self.boundingRect(with: size, attributes: attributes).height
}
}
with above extension and methods, I ran below code in playground.
let str = "ありがとうございます。スマホ専用のページがなくても反映できるかはエンジンにご確認いたします。"
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byWordWrapping
paragraphStyle.alignment = .left
let attributes = [
NSFontAttributeName: UIFont.systemFont(ofSize: 15),
NSParagraphStyleAttributeName: paragraphStyle
]
let height = str.height(fits: 320, attributes: attributes)
print("calculated height: ", height)
and it returned 54.
However, when I ran same code in simple project (single view controller, copied and pasted above into viewDidLoad: in ViewController, it returned 36 (missing one line height). Any idea why this two behave differently with same input?
I have run your code on multiple simulators for iOS 10.3 and they all give me the value of 54.0 for calculated height.
You didn't specify which version of iOS you are using, so perhaps that is significant.
Also, print the font that is used in your app and in your playground. For UIFont.systemFont(ofSize: 15) I get .SFUIText in both.

How to get the height of a UILabel in Swift?

I am a beginner in Swift and I am trying to get the height of a label.
The label has multiple lines of text. I want to know the total height it occupies on the screen.
Swift 4 with extension
extension UILabel{
public var requiredHeight: CGFloat {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: frame.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.attributedText = attributedText
label.sizeToFit()
return label.frame.height
}
}
it's simple, just call
label.bounds.size.height
Updated for Swift 3
func estimatedHeightOfLabel(text: String) -> CGFloat {
let size = CGSize(width: view.frame.width - 16, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSFontAttributeName: UIFont.systemFont(ofSize: 10)]
let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height
return rectangleHeight
}
override func viewDidLoad() {
super.viewDidLoad()
guard let labelText = label1.text else { return }
let height = estimatedHeightOfLabel(text: labelText)
print(height)
}
Swift 5 ioS 13.2 tested 100%, best solution when the UILabel numberOfLines = 0
Note, result is rounded. Just remove ceil() if you don't want it.
If you want to get height -> give storyboard width of UILabel
If you want to get width -> give storyboard height of UILabel
let stringValue = ""//your label text
let width:CGFloat = 0//storybord width of UILabel
let height:CGFloat = 0//storyboard height of UILabel
let font = UIFont(name: "HelveticaNeue-Bold", size: 18)//font type and size
func getLableHeightRuntime() -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = stringValue.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.height)
}
func getLabelWidthRuntime() -> CGFloat {
let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
let boundingBox = stringValue.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
return ceil(boundingBox.width)
}
#iajmeri43's answer Updated for Swift 5
func estimatedLabelHeight(text: String, width: CGFloat, font: UIFont) -> CGFloat {
let size = CGSize(width: width, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
let attributes = [NSAttributedString.Key.font: font]
let rectangleHeight = String(text).boundingRect(with: size, options: options, attributes: attributes, context: nil).height
return rectangleHeight
}
To use it:
// 1. get the text from the label
guard let theLabelsText = myLabel.text else { return }
// 2. get the width of the view the label is in for example a cell
// Here I'm just stating that the cell is the same exact width of whatever the collection's width is which is usually based on the width of the view that collectionView is in
let widthOfCell = self.collectionView.frame.width
// 3. get the font that your using for the label. For this example the label's font is UIFont.systemFont(ofSize: 17)
let theLabelsFont = UIFont.systemFont(ofSize: 17)
// 4. Plug the 3 values from above into the function
let totalLabelHeight = estimatedLabelHeight(text: theLabelsText, width: widthOfCell, font: theLabelsFont)
// 5. Print out the label's height with decimal values eg. 95.46875
print(totalLabelHeight)
// 6. as #slashburn suggested in the comments, use the ceil() function to round out the totalLabelHeight
let ceilHeight = ceil(totalLabelHeight)
// 7. Print out the ceilHeight rounded off eg. 95.0
print(ceilHeight)