How to update font family of complete ios app globally - swift4

I am working on an app and it very painful and time consuming to update font family for each label and other text elements used in xcode.
Is there any way we can update the font family of all label, text fields etc globally without changing the font-size.
I used this code in swift 3 and worked perfectly
Set a default font for whole iOS app?
extension UILabel {
var substituteFontName : String {
get { return self.font.fontName }
set { self.font = UIFont(name: newValue, size: self.font.pointSize) }
}
}
but in swift 4 it shows this error
Fatal error: Unexpectedly found nil while unwrapping an Optional value
on line
set { self.font = UIFont(name: newValue, size: self.font.pointSize) }
Thanks

Related

Can't subclass UIFont

I use custom fonts in my iOS application and have setup the fonts like so:
private enum MalloryProWeight: String {
case book = "MalloryMPCompact-Book"
case medium = "MalloryMPCompact-Medium"
case bold = "MalloryMPCompact-Bold"}
extension UIFont {
enum Caption {
private static var bookFont: UIFont {
UIFont(name: MalloryProWeight.book.rawValue, size: 1)!
}
private static var mediumFont: UIFont {
UIFont(name: MalloryProWeight.medium.rawValue, size: 1)!
}
private static var boldFont: UIFont {
UIFont(name: MalloryProWeight.bold.rawValue, size: 1)!
}
static var book: UIFont {
return bookFont.withSize(10)
}
static var medium: UIFont {
mediumFont.withSize(10)
}
static var bold: UIFont {
boldFont.withSize(10)
}
}
So that at the call site I can do the following:
UIFont.Caption.bold
This works well; I have an NSAttributed extension that takes in. UIFont and color and returns an attributed string = so it all fits nicely.
However, I now have a requirement to set the LetterSpacing and LineHeight on each of my fonts.
I don't want to go and update the NSAttributed extension to take in these values to set them - I ideally want them accessible from UIFont
So, I tried to subclass UIFont to add my own properties to it - like so:
class MrDMyCustomFontFont: UIFont {
var letterSpacing: Double?
}
And use it like so
private static var boldFont: UIFont {
MrDMyCustomFontFont(name: MalloryProWeight.bold.rawValue, size: 1)!
}
However the compiler complains and I am unsure how to resolve it:
Argument passed to call that takes no arguments
So my question is two part:
How can I add my own custom property (and set it on a per-instance base) on UIFont
Else how do I properly subclass UIFont so that I can add my own properties there?
Thanks!
You can't subclass UIFont because it is bridged to CTFont via UICTFont. That's why the init methods are marked "not inherited" in the header. It's not a normal kind of class.
You can easily add a new property to UIFont, but it won't work the way you want it to. It'll be exactly what you asked for: per-instance. But it won't be copied, so the instance returned from boldFont.withSize(10) won't have the same value as boldFont. If you want the code, this is how you do it:
private var letterSpacingKey: String? = nil
extension UIFont {
var letterSpacing: Double? {
get {
(objc_getAssociatedObject(self, &letterSpacingKey) as? NSNumber)?.doubleValue
}
set {
objc_setAssociatedObject(self, &letterSpacingKey, newValue.map(NSNumber.init(value:)),
.OBJC_ASSOCIATION_RETAIN)
}
}
}
And then you can set it:
let font = UIFont.boldSystemFont(ofSize: 1)
font.letterSpacing = 1
print(font.letterSpacing) // Optional(1)
But you'll lose it anytime a derived font is created:
let newFont = font.withSize(10)
print(newFont.letterSpacing) // nil
So I don't think you want that.
But most of this doesn't really make sense. What would you do with these properties? "Letter spacing" isn't a font characteristic; it's a layout/style characteristic. Lying about the font's height metric is probably the wrong tool as well; configuring that is also generally a paragraph characteristic.
What you likely want is a "Style" that tracks all the things in question (font, spacing, paragraph styles, etc) and can be applied to an AttributedString. Luckily that already exists in iOS 15+: AttributeContainer. Prior to iOS 15, you can just use a [NSAttributedString.Key: Any].
Then, instead of an (NS)AttributedString extension to merge your font in, you can just merge your Container/Dictionary directly (which is exactly how it's designed to work).
extension AttributeContainer {
enum Caption {
private static var boldAttributes: AttributeContainer {
var container = AttributeContainer()
container.font = UIFont(name: MalloryProWeight.bold.rawValue, size: 1)!
container.expansion = 1
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 1.5
container.paragraphStyle = paragraphStyle
return container
}
static var bold: AttributeContainer {
var attributes = boldAttributes
attributes.font = boldAttributes.font.withsize(10)
return attributes
}
}
}

Use SwiftUI's Font.largeTitle when NSFont is expected

I am still learning SwiftUI & dev in general. Anything NSWhatever related still throws me off.
And I know that my issue is just a lack of understanding :D
Rn, I am building a SwiftUI macOS 11 app & it works pretty well... but SwiftUI has its limits.
Now, I want to use a NSTextField instead of SwiftUI's TextField(), bc custom UI etc...
My code:
func makeNSView(context: Context) -> NSTextField {
let textField = FocusAwareTextField()
textField.placeholderAttributedString = NSAttributedString(
string: placeholder,
attributes: [
NSAttributedString.Key.foregroundColor: NSColor(Color.secondary),
NSAttributedString.Key.font: NSFont(name: "SF Pro", size: 32)!
]
)
textField.isBordered = false
textField.delegate = context.coordinator
textField.backgroundColor = NSColor.clear
...
...
...
My issue:
NSAttributedString.Key.font: expects a NSFont.
And the above code builds fine but I'd rather stick to SwiftUI's Dynamic Font System and use Font.largeTitle.bold() instead of manually defining to use SF Pro and a font size.
I know how to convert Color.black to NSColor(Color.black) but didn't find a working example for Fonts.
Also, I'd appreciate it if someone could actually explain what is going on so I can understand, instead of just doing copy & paste.
Thank you so much!

Setting global font in swift

I create an extension where I set custom font
extension UILabel {
var substituteFontName : String {
get { return self.font.fontName }
set {
self.font = UIFont(name: newValue, size: self.font.pointSize)
}
}
}
but I have a problem: I get a error Unexpectedly found nil while unwrapping an Optional value, but if I set static size like this
self.font = UIFont(name: newValue, size: 13)
I didn't get a error and fonts is changing. How can I set exactly size which was automatically
The font attribute is actually implicitly unwrapped UIFont!, so you should change your set to:
self.font = UIFont(name: newValue, size: self.font?.pointSize ?? UIFont.labelFontSize)

How to use SF Rounded font in SwiftUI?

I am trying to use the SF rounded font in my SwiftUI project, how would you set it?
I already tried messing around with the .font() but it didn't work (I wasn't able to set it to this rounded font)
Text("Your Text").font(.system(.body, design: .rounded))
I know the question is about SwiftUI, but I thought it could be helpful to also include an UIKit answer.
let fontSize: CGFloat = 24
// Here we get San Francisco with the desired weight
let systemFont = UIFont.systemFont(ofSize: fontSize, weight: .regular)
// Will be SF Compact or standard SF in case of failure.
let font: UIFont
if let descriptor = systemFont.fontDescriptor.withDesign(.rounded) {
font = UIFont(descriptor: descriptor, size: fontSize)
} else {
font = systemFont
}
Doing it this way works for me on a Text object in SwiftUI:
.font(.system(size: 13.0, weight: .regular, design: .rounded))
Converted the UIKit answer by #Pomme2Poule into a function for easy use, should anyone need it. Function uses dynamic type too, so it'll scale with the font size.
func roundedFont(ofSize style: UIFont.TextStyle, weight: UIFont.Weight) -> UIFont {
// Will be SF Compact or standard SF in case of failure.
let fontSize = UIFont.preferredFont(forTextStyle: style).pointSize
if let descriptor = UIFont.systemFont(ofSize: fontSize, weight: weight).fontDescriptor.withDesign(.rounded) {
return UIFont(descriptor: descriptor, size: fontSize)
} else {
return UIFont.preferredFont(forTextStyle: style)
}
}
Thought I'd add an extra use case: if you would like to apply a custom font weight to your rounded SF font on something like a text field, while still maintaining dynamic font scaling, you can do the following:
TextField("test", $test)
.font(Font.system(.largeTitle, design: .rounded).weight(.heavy))
This is required since one cannot call .fontWeight() directly on a TextField (at least as of iOS 13).

How to add custom fonts to an iPhone app?

What must I do to get an UIFont object with a custom font? I remember there was also something going on in the Info.plist file.
What font file formats are supported?
To add a custom font to your application, you can add them to the XCode project. Then, modify the application-info.plist file. Add the key Fonts provided by application to a new row. Add one item for each font you have added.
It supports both TTF and OpenType fonts. One caveat is that it loads and parses all fonts in the startup of your app, so it will slow down the initial load time.
I believe this is only available in iOS 3.2 or later.
Programming way (Swift)
Add the fonts to your project at the resource folder
And add this function:
func loadFont(filePath: String) {
let fontData = NSData(contentsOfFile: filePath)!
let dataProvider = CGDataProviderCreateWithCFData(fontData)
let cgFont = CGFontCreateWithDataProvider(dataProvider)!
var error: Unmanaged<CFError>?
if !CTFontManagerRegisterGraphicsFont(cgFont, &error) {
let errorDescription: CFStringRef = CFErrorCopyDescription(error!.takeUnretainedValue())
print("Unable to load font: %#", errorDescription)
}
}
Use example:
if let fontPath = NSBundle.mainBundle().pathForResource("MyCustomFont", ofType: "ttf") {
loadFont(fontPath)
}
let myFont = UIFont(name: "MyCustomFont", size: CGFloat(18))