Reputation: 1549
Xcode 6 has a new feature where fonts and font sizes in UILabel
, UITextField
, and UIButton
can be set automatically based on the size class of the current device configuration, right in the storyboard. For example, you can set a UILabel
to use font size 12 on "any width, compact height" (such as on iPhones in landscape) configurations and size 18 on "regular width, regular height" configurations (such as on iPads). More information is available here:
developer.apple.com/size_class
This is a great feature in theory because it could make it unnecessary to programmatically set different fonts on UI features based on the device configuration. Right now, I have some conditional code that sets the fonts based on the device type, but obviously, that means I have to set the fonts programmatically everywhere throughout the app. So I was initially really excited about this feature, but I found that it has a severe problem of actual usage for me (perhaps a bug). Note that I am building against SDK 8 and setting a minimum deployment target of iOS 8, so this has nothing to do with compatibility with old versions of iOS.
The problem is this: If I set different font sizes for different size classes and use the "System" font provided by iOS, everything works as expected, and the font sizes change based on the size class. If I use a custom font supplied by my application (yes, I have it set up correctly in my application bundle, as it works programmatically) and set the custom font to a label in an XCode 6 storyboard, that also works as expected. But when I try to use different sizes of the custom font for different size classes, in the storyboard, it suddenly doesn't work. The only difference in configuration is the font I've chosen (a custom one vs. the System font). Instead, all of the fonts show up on the device and simulator as the default system font at the default size, regardless of size class (and I verified via the debugger that it is substituting the system font for the actual one specified in the storyboard). So basically, the size class feature appears to be broken for custom fonts. Also, interestingly, the custom fonts actually display and adjust size properly in the XCode 6 "Preview" pane for the view controller: it stops working only when running on the actual iOS system (which makes me think that I'm configuring it correctly).
I tried multiple different custom fonts, and it doesn't seem to work for any of them, but it always works if I use "System" instead.
Anyway, has anyone else seen this problem in Xcode 6?
Any ideas on whether this is a bug in iOS 8, Xcode, or something
Am I doing wrong?
The only workaround I've found, as I said, is to continue to programmatically set the fonts like I have been for about three versions of iOS because that does work.
But I'd love to be able to use this feature if I could get it to work with custom fonts. Using the System font is not acceptable for our design.
ADDITIONAL INFO: As of Xcode 8.0, the bug is fixed.
Upvotes: 104
Views: 16721
Reputation: 1
A combination of some of the later answers above were helpful. Here's how I solved the IB bug via a Swift UILabel extension:
import UIKit
// This extension is only required as a work-around to an interface builder bug in XCode 7.3.1
// When custom fonts are set per size class, they are reset to a small system font
// In order for this extension to work, you must set the fonts in IB to System
// We are switching any instances of ".SFUIDisplay-Bold" to "MuseoSans-700" and ".SFUIDisplay-Regular" to "MuseoSans-300" and keeping the same point size as specified in IB
extension UILabel {
override public func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if ((traitCollection.verticalSizeClass != previousTraitCollection?.verticalSizeClass) || traitCollection.horizontalSizeClass != previousTraitCollection?.horizontalSizeClass) {
//let oldFontName = "\(font.fontName)-\(font.pointSize)"
if (font.fontName == systemFontRegular) {
font = UIFont(name: customFontRegular, size: (font?.pointSize)!)
//xlog.debug("Old font: \(oldFontName) -> new Font: \(font.fontName) - \(font.pointSize)")
}
else if (font.fontName == systemFontBold) {
font = UIFont(name: customFontBold, size: (font?.pointSize)!)
//xlog.debug("Old font: \(oldFontName) -> new Font: \(font.fontName) - \(font.pointSize)")
}
}
}
}
Upvotes: 0
Reputation: 33
Still no signed right answer. This code works fine for me. You must disable font size for size classes in interface builder first. In IB you can use custom font.
- (void) traitCollectionDidChange: (UITraitCollection *) previousTraitCollection {
[super traitCollectionDidChange: previousTraitCollection];
if ((self.traitCollection.verticalSizeClass != previousTraitCollection.verticalSizeClass)
|| self.traitCollection.horizontalSizeClass != previousTraitCollection.horizontalSizeClass) {
self.textField.font = [UIFont fontWithName:textField.font.fontName size:17.f];
}
}
Upvotes: 0
Reputation: 995
I've come up with even faster fix (I assume you always use your one custom font).
Create a category for UILabel and include in files using buggy storyboard with size classes and dedicated font setting for various classes:
@implementation UILabel (FontFixForSizeClassesAppleBug)
- (void)layoutSubviews
{
[super layoutSubviews];
if([[UIFont systemFontOfSize:10].familyName isEqualToString:self.font.familyName]) {
//workaround for interface builder size classes bug which ignores custom font if various classes defined for label: http://stackoverflow.com/questions/26166737/custom-font-sizing-in-xcode6-size-classes-not-working-properly-w-custom-fonts
self.font = [UIFont fontWithName:@"YOUR_CUSTOM_FONT_NAME" size:self.font.pointSize];
}
}
@end
Just use your custom fonts in storyboard. When buggy interpreter will use system font instead your own this category will switch it to your custom font.
Upvotes: -1
Reputation: 5353
After trying everything, I eventually settled on a combination of the above solutions. Using Xcode 7.2, Swift 2.
import UIKit
class LabelDeviceClass : UILabel {
@IBInspectable var iPhoneSize:CGFloat = 0 {
didSet {
if isPhone() {
overrideFontSize(iPhoneSize)
}
}
}
@IBInspectable var iPadSize:CGFloat = 0 {
didSet {
if isPad() {
overrideFontSize(iPadSize)
}
}
}
func isPhone() -> Bool {
// return UIDevice.currentDevice().userInterfaceIdiom == .Phone
return !isPad()
}
func isPad() -> Bool {
// return UIDevice.currentDevice().userInterfaceIdiom == .Pad
switch (UIScreen.mainScreen().traitCollection.horizontalSizeClass, UIScreen.mainScreen().traitCollection.verticalSizeClass) {
case (.Regular, .Regular):
return true
default:
return false
}
}
func overrideFontSize(fontSize:CGFloat){
let currentFontName = self.font.fontName
if let calculatedFont = UIFont(name: currentFontName, size: fontSize) {
self.font = calculatedFont
}
}
}
@IBInspectable
lets you set the font size in the StoryboarddidSet
observer, to avoid the pitfalls from layoutSubviews()
(infinite loop for dynamic table view row heights) and awakeFromNib()
(see @cocoaNoob's comment)@IBDesignable
@IBDesignable
doesn't work with traitCollection
according to this other stack articleUIScreen.mainScreen()
rather than self
per this stack articleUpvotes: 17
Reputation: 1077
Fast fix:
1) Set fonts as System for size classes
2) Subclass UILabel and override "layoutSubviews" method like:
- (void)layoutSubviews
{
[super layoutSubviews];
// Implement font logic depending on screen size
if ([self.font.fontName rangeOfString:@"bold" options:NSCaseInsensitiveSearch].location == NSNotFound) {
NSLog(@"font is not bold");
self.font = [UIFont fontWithName:@"Custom regular Font" size:self.font.pointSize];
} else {
NSLog(@"font is bold");
self.font = [UIFont fontWithName:@"Custom bold Font" size:self.font.pointSize];
}
}
By the way, it is a very convenient technique for iconic fonts
Upvotes: 41
Reputation: 7774
None of these worked for me, but this did. You also need to use the system font in IB
#import <UIKit/UIKit.h>
@interface UILabelEx : UILabel
@end
#import "UILabelEx.h"
#import "Constants.h"
@implementation UILabelEx
- (void) traitCollectionDidChange: (UITraitCollection *) previousTraitCollection {
[super traitCollectionDidChange: previousTraitCollection];
self.font = [UIFont fontWithName:APP_FONT size:self.font.pointSize];
}
@end
Upvotes: 0
Reputation: 1502
The problem is still there that you cannot use the feature to set Fonts for different size classes from interface builder.
Just set font based on the device you want just like below:
if (Your Device is iPAd) //For iPad
{
[yourLabel setFont:[UIFont fontWithName:@"FontName" size:FontSize]];
}
else //For Other Devices then iPad
{
[yourLabel setFont:[UIFont fontWithName:@"FontName" size:FontSize]];
}
This works perfectly on all devices.
Upvotes: 0
Reputation: 5095
I am using Swift, XCode 6.4. So this is what I did
import Foundation
import UIKit
@IBDesignable class ExUILabel: UILabel {
@IBInspectable var fontName: String = "Default-Font" {
didSet {
self.font = UIFont(name: fontName, size:self.font.pointSize)
}
}
override func layoutSubviews() {
super.layoutSubviews()
self.font = UIFont(name: fontName, size:self.font.pointSize)
}
}
Goto Designer -> Identity Inspector -> Set the class to ExUILabel
Then go to Attribute inspector in designer and set the font name.
Upvotes: -1
Reputation: 1371
The bug is still valid in XCode 7.0 GM.
Razor28's solution causes infinite loops in some cases. My experience has been with using it in conjunction with SwipeView.
Instead, I suggest that you:
1) Subclass UILabel and override setFont:
- (void)setFont:(UIFont *)font
{
font = [UIFont fontWithName:(@"Montserrat") size:font.pointSize];
[super setFont:font];
}
2) Set the custom class of your UILabels and then set the font size classes by using System font
Upvotes: 3
Reputation: 563
I had the same problem and found one not really clear, but fine solution!
override func viewDidLayoutSubviews() { for i in 100...110 { if let label = view.viewWithTag(i) as? UILabel { label.font = UIFont(name: "RotondaC-Bold", size: label.font.pointSize) } } }
Upvotes: -6
Reputation: 4101
This (and other Xcode - Size Classes related) bug caused me some serious grief recently as I had to go through a huge storyboard file hacking things away.
For anyone else in this position, I'd like to add something on top of @razor28's answer to ease the pain.
In the header file of your custom subclass, use IBInspectable
for your runtime attributes. This will make these attributes accessible from the "Attributes Inspector", visually right above the default position for font settings.
Example use:
@interface MyCustomLabel : UILabel
@property (nonatomic) IBInspectable NSString *fontType;
@property (nonatomic) IBInspectable CGFloat iphoneFontSize;
@property (nonatomic) IBInspectable CGFloat ipadFontSize;
@end
This will very helpfully produce this output:
An added benefit is that now we don't have to add the runtime attributes manually for each label. This is the closest I could get to XCode's intended behaviour. Hopefully a proper fix is on its way with iOS 9 this summer.
Upvotes: 8
Reputation: 2794
Workaround for UILabel
: keep the same font size on all Size Classes, but instead change your label height accordingly in each Size Class. Your label must have autoshrink enabled. It worked nicely in my case.
Upvotes: 9
Reputation: 515
Similar solution to @razor28's one but I think a little more universal. Also, works fine on older iOS versions
https://gist.github.com/softmaxsg/8a3ce30331f9f07f023e
Upvotes: 3
Reputation: 1
Upvotes: -8