iOS 7 Tutorial Series: Dynamic Type

本文深入探讨了iOS 7中引入的动态字体类型功能,包括系统内容大小设置、响应内容大小变化的方法、使用自定义字体与动态字体结合的实践,以及在应用中适配动态字体的技巧。同时,文章还指出了一些限制,如UITextField和MKAnnotationView中动态字体的不适用情况,并提供了应对策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

from :http://captechconsulting.com/blog/john-szumski/ios-7-tutorial-series-dynamic-type

by John Szumski on Oct 11, 2013

Apple debuted many new typographic APIs with iOS 7 and has made its new text options very apparent to end users by changing the default system font. Text Kit brings many powerful features of CoreText to a higher level of abstraction and dramatically expands the approachability of the text layout features that have been available to developers for some time. Along with the new system font, Apple has also added Dynamic Type, a feature that gives users the ability to choose a base text size that is used throughout the system in both first- and third-party applications. Changing font sizes poses some new challenges for developers, however if you are already using Apple-suggested technologies like Auto Layout and localized strings your app may already have some of the necessary flexibility. The sample application included with this post demonstrates how to adapt to changes in the base text size, how to use custom fonts with Dynamic Type, and best practices to use in your application.

 

Using the System Content Size

The system content size is exposed to users in Settings > General > Text Size as a slider with seven different base sizes where the default size roughly matches the font sizes previously recommended for iOS 6 and below.

Text Size in Settings

The NSString value for the current setting is available to developers as the preferredContentSizeCategory property of UIApplication. Typically you won't need to use this value directly unless you are using a custom interface font, which is discussed in a later section of this post. Instead, you should use a new UIFont method preferredFontForTextStyle:where you previously would have used systemFontOfSize: or boldSystemFontOfSize: to create a UIFont instance. This method takes one of six text style options that will allow Apple to choose an appropriate font, weight, and size adjusted for the user's current content size:

  • UIFontTextStyleHeadline
  • UIFontTextStyleSubheadline
  • UIFontTextStyleBody
  • UIFontTextStyleFootnote
  • UIFontTextStyleCaption1
  • UIFontTextStyleCaption2

These six style options combined with the seven content size options yield a large number of font permutations, each of which has been meticulously tweaked by Apple designers for optimal display. Because of the font size variances possible at runtime, apps must be structured to have fluid layouts and avoid hardcoded width or size values.

 

Responding to Content Size Changes

When an iOS application is loaded for the first time, its user interface will be drawn with the system's current content size at launch time, however developers can't rely on this value staying constant throughout the lifetime of the application. Users can use the multitasking features of iOS to switch to Settings and update the text size setting at any time. To respond to these changes at runtime, your controllers and views will need to listen for theUIContentSizeCategoryDidChangeNotification:

[[NSNotificationCenter defaultCenter] 
	addObserver:self
	   selector:@selector(userTextSizeDidChange)
		   name:UIContentSizeCategoryDidChangeNotification
		 object:nil];

Your implementation of userTextSizeDidChange will need to update fonts on all visible UI elements and recalculate a layout for the updated sizes of those elements. It's important to note that you must apply a new UIFont instance withpreferredFontForTextStyle: to get an updated size. Simply calling invalidateIntrinsicContentSize orsetNeedsLayout will not automatically apply the new content size because UIFont instances are immutable.

A table view at different text sizes

For UITableViewController subclasses, both steps can be accomplished with a call to reloadData orreloadRowsAtIndexPaths: for the visible cells. The table controller will recalculate row heights for the new text size andtableView:cellForRowAtIndexPath: will apply the new fonts. Note that this approach requires fonts to be set incellForRowAtIndexPath: and not in an init method. An alternative is to have each cell instance listen forUIContentSizeCategoryDidChangeNotification and adjust its fonts internally, and then the controller is only required to update the layout.

For UIViewControllers, UIScrollViews, or plain UIViews, apply the new fonts to your interface elements. If you are using Auto Layout, adjusting the fonts will be enough to activate the layout engine and will also calculate a newcontentSize for UIScrollViews. For manual layouts, call setNeedsLayout to tell iOS to update the positions of all subviews. The sample application uses Auto Layout, and I highly encourage you to adopt it if you haven't already.

It is also important to note that these adaptive fonts can give non-optimal layout values if used directly. For example, the sample application's table view implements boundingRectWithSize:options:attributes:context: to determine the appropriate height for the row:

- (CGFloat)tableView:(UITableView *)tableView 
		   heightForRowAtIndexPath:(NSIndexPath *)indexPath {
 
	NSString *codename = self.codenames[indexPath.row];
	CGRect codenameRect = [codename 
		boundingRectWithSize:CGSizeMake(
			CGRectGetWidth(CGRectIntegral(tableView.bounds)) - 40, 
			MAXFLOAT) // 40 = 20pt horizontal padding on each side
		options:NSStringDrawingUsesLineFragmentOrigin
		attributes:@{NSFontAttributeName: [self fontForBodyTextStyle]}
		context:nil];
 
	return MAX(44.0f, CGRectGetHeight(CGRectIntegral(codenameRect)) + 20);
		// 20 = 10pt vertical padding on each end
}

If you put a breakpoint in this method and examine codenameRect, you'll notice that it has a size that includes fractional points:

(lldb) p codenameRect
(CGRect) $1 = origin=(x=0, y=0) size=(width=101.43001, height=16.702)

These values can be used for layout, however using fractional point values forces the device to interpolate and blend pixels when drawing to the screen. This extra processing can have noticeable effects if done across many subviews, especially while scrolling. The fix is easy: simply call CGRectIntegral() on any CGRect before you use it for layout calculations. If you are using Auto Layout, the fix is even easier; the layout system takes care of fractional points automatically.

 

Using Custom Fonts with Dynamic Type

Custom fonts are a great way to have your app stand out from the crowd, and fortunately it's very easy to support Dynamic Type with a simple category on UIFont. If you need a refresher on using custom fonts on iOS, this Stack Overflow post covers the basics and iOS Fonts lists all the available fonts included with the system. When implementing Dynamic Type with a custom font, the basic idea is to have a mapping between the UIContentSizeCategory string constants and a font size. In the sample project I created a category called AvenirContentSize that addspreferredAvenirFontForTextStyle: to UIFont:

+ (UIFont *)preferredAvenirFontForTextStyle:(NSString *)textStyle {
	// choose the font size
	CGFloat fontSize = 16.0;
	NSString *contentSize = [UIApplication sharedApplication].preferredContentSizeCategory;
 
	if ([contentSize isEqualToString:UIContentSizeCategoryExtraSmall]) {
		fontSize = 12.0;
 
	} else if ([contentSize isEqualToString:UIContentSizeCategorySmall]) {
		fontSize = 14.0;
 
	} else if ([contentSize isEqualToString:UIContentSizeCategoryMedium]) {
		fontSize = 16.0;
 
	} else if ([contentSize isEqualToString:UIContentSizeCategoryLarge]) {
		fontSize = 18.0;
 
	} else if ([contentSize isEqualToString:UIContentSizeCategoryExtraLarge]) {
		fontSize = 20.0;
 
	} else if ([contentSize isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
		fontSize = 22.0;
 
	} else if ([contentSize isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
		fontSize = 24.0;
	}
 
 
	// choose the font weight
	if ([textStyle isEqualToString:UIFontTextStyleHeadline] ||
		[textStyle isEqualToString:UIFontTextStyleSubheadline]) {
 
		return [UIFont fontWithName:@"Avenir-Black" size:fontSize];
 
	} else {
		return [UIFont fontWithName:@"Avenir-Medium" size:fontSize];
	}
}

Now wherever I want to use Avenir in my application I can use that method just as I wouldpreferredFontForTextStyle: to use the system font. You'll notice that I'm scaling the fontSize linearly, however you can certainly get fancy by adjusting along a curve or interchanging different weights within the same font family. Similar adjustments can be made for the textStyle argument to make headlines bold or use a different font variant.

 

Notable Limitations

I've come across two limitations of the Dynamic Type system:

  • UITextField: The placeholder font is not exposed in the public API and doesn't use the value of the font property, therefore you can't adjust its size as the content size changes. This leads to a weird effect where the text adjusts normally but if the field is cleared it jumps back to the original font size when displaying the placeholder. You can most likely fix this by subclassingUITextField and implementing placeholderRectForBounds: and drawPlaceholderInRect: with adaptive fonts yourself, however this is left as an exercise for the reader.
     
  • MKAnnotationView: The text in the system-provided callout does not adjust as the content size changes. Unfortunately, fixing this requires you to implement the entire callout yourself.

Even with these relatively minor limitations, the Dynamic Type system is great for improving the accessibility of your application to users with vision impairments. Implementation only requires an easy UIFont category and oneNSNotification, so developers have little reason to skip Dynamic Type support.

The sample application is available on GitHub.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值