Dynamic Typeに対応してユーザー設定に合わせたフォントを使用する
文字サイズ設定の変更に対応
テキストスタイルを指定して生成したフォントは、文字サイズ設定の変更には追従しません。そのため、ユーザーが文字サイズ設定を変更した場合は、新しい設定に合わせてフォントを生成し直す必要があります。
文字サイズ設定の変更は、UIContentSizeCategoryDidChangeNotification通知を利用して知ることができます。Dynamic Type対応のアプリでは、この通知を受けてフォントの再設定を行います。設定されている文字サイズは、通知のuserInfo(キーはUIContentSizeCategoryNewValueKey)より取得可能です。または、UIApplicationのpreferredContentSizeCategoryプロパティからも取得できます。
通知の登録は、viewDidLoadなど、適切なタイミングで行いましょう。
// 通知の登録 - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contentSizeCategoryDidChange:) name:UIContentSizeCategoryDidChangeNotification object:nil]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; if (self.isViewLoaded && [self.view window] == nil) { [[NSNotificationCenter defaultCenter] removeObserver:self]; self.view = nil; } } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }
通知を受けるハンドラでフォントを再設定します。このとき、フォントの設定にfontプロパティを用いているか、属性付きテキスト(NSAttributedString)のNSFontAttributeName属性を用いているかで、その処理の内容が異なります。
まずは、通常のテキスト(NSString)のフォントをfontプロパティで設定している場合の例を示します。この場合は、単純にfontプロパティの値を更新するだけです。
- (void)contentSizeCategoryDidChange:(NSNotification *)notification { // UIFontDescriptorからテキストスタイルを取得 UIFontDescriptor *fontDescriptor = [self.label.font fontDescriptor]; NSString *textStyle = [fontDescriptor objectForKey:UIFontDescriptorTextStyleAttribute]; if (textStyle) { // テキストスタイルが設定されていた場合はフォントを更新 UIFont *newFont = [UIFont preferredFontForTextStyle:textStyle]; self.label.font = newFont; [self.label invalidateIntrinsicContentSize]; } }
設定済みのUIFontオブジェクトからディスクリプタを取得し、テキストスタイルが設定されているかを確認します。テキストスタイルの設定がある場合には、フォントを更新します。
なお、フォントの再設定後にinvalidateIntrinsicContentSizeを呼んでいますが、これは、Auto Layoutでビューの固有サイズ(Intrinsicサイズ)を調整するための処理です。なお、ビューのサイズ変更に関しては、「2.1.6コンテンツモード」も参考にしてください※。
使用フォントが、テキストスタイルのみを指定して生成されたフォントであれば、上記コードで十分です。しかし、フォント属性をさらにカスタマイズし、他の属性を追加している場合は、上記だけでは付加的な属性が抜け落ちてしまうことがあります。
この問題を対処するには、テキストスタイルに無関係な属性を、元のディスクリプタからコピーする必要があります。その処理の一例を以下に示します。
※本連載では未掲載です。書籍をお持ちの方はP.021を参照してください。
- (void)contentSizeCategoryDidChange:(NSNotification *)notification { // UIFontDescriptorからテキストスタイルを取得 UIFontDescriptor *fontDescriptor = [self.label.font fontDescriptor]; NSString *textStyle = [fontDescriptor objectForKey:UIFontDescriptorTextStyleAttribute]; if (textStyle) { // テキストスタイルが設定されていた場合はフォントを更新 // テキストスタイルを元にディスクリプタを生成 // --- (1) UIFontDescriptor *newFontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:textStyle]; // スタイルに影響しない既存の属性をコピーする // --- (2) // 既存のディスクリプタが持つ属性を取得 NSMutableDictionary *attributes = fontDescriptor.fontAttributes.mutableCopy; // 新しいディスクリプタが持つ属性キー(スタイル依存のキー)を取得 NSArray *allNewKeys = [newFontDescriptor.fontAttributes allKeys]; // 既存の属性からスタイル依存の属性を削除 [attributes removeObjectsForKeys:allNewKeys]; // 新しいディスクリプタに残りの属性を追加 newFontDescriptor = [newFontDescriptor fontDescriptorByAddingAttributes:attributes]; // ディスクリプタからフォントを生成 // --- (3) UIFont *newFont = [UIFont fontWithDescriptor:newFontDescriptor size:0.0]; self.label.font = newFont; [self.label invalidateIntrinsicContentSize]; } }
上記コードの(2)で、付加的な属性をコピーしています。これで、テキストスタイルを採用したフォントにアフィン変換を適用(UIFontDescriptorMatrixAttribute属性を付加)している場合でも、付加的な属性を保持したままユーザーの文字サイズ設定に合わせたフォントを再生成することが可能です。
なお、公開されているテキストスタイルは6通りですが(冒頭参照)、実際にシステムが利用しているテキストスタイルはこれだけではありません。例えば、ソースコード18(1ページ目参照)では、Bodyスタイルにイタリック特性を追加すると、テキストスタイルはUICTFontTextStyleItalicBodyとなり*2、他の属性は追加されません。このような場合には、ソースコード20の方法でフォント更新が可能です。
続いて、属性付きテキストのNSFontAttributeNameに対してフォントを設定している場合の、フォント再設定例を示します(属性付きテキストの詳細は次回以降で解説)。このケースでは、テキストが持つ属性の中でも、フォントに関する属性のみを更新する必要があり、単純にfontプロパティを更新するよりも複雑な処理になります。
なお、下記の例では簡略化のため、ディスクリプタにはテキストスタイル属性以外に付加的な属性がないものとして説明します。
*2 iOS 7.1で確認
- (void)contentSizeCategoryDidChange:(NSNotification *)notification { NSMutableAttributedString *mutableAttrStr = self.textView.attributedText.mutableCopy; [mutableAttrStr beginEditing]; // フォント属性の数え上げ [mutableAttrStr enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, mutableAttrStr.length) options:0 usingBlock:^(id value, NSRange range, BOOL *stop){ // UIFontDescriptorからテキストスタイルを取得 UIFontDescriptor *fontDescriptor = [(UIFont *)value fontDescriptor]; NSString *textStyle = [fontDescriptor objectForKey:UIFontDescriptorTextStyleAttribute]; if (textStyle) { // テキストスタイルが設定されていた場合はフォントを更新 UIFont *newFont = [UIFont preferredFontForTextStyle:textStyle]; [mutableAttrStr removeAttribute:NSFontAttributeName range:range]; [mutableAttrStr addAttribute:NSFontAttributeName value:newFont range:range]; } }]; [mutableAttrStr endEditing]; self.textView.attributedText = mutableAttrStr; }
対象となる属性付きテキストが持つフォント属性(NSFontAttributeName)を数え上げ、テキストスタイルが設定されていたらフォントを生成し直します。
上記ではattributedTextを利用していますが、Text KitのNSTextStorageを用いている場合でも、これと同じ方法でフォントの再設定が可能です。
なお、執筆時(iOS 7.1)に確認されている問題として、日本語を含む属性付きテキストに、テキストスタイルを元に生成したフォントを設定すると、テキストスタイル属性が抜け落ちてしまう場合があります。そのため、日本語テキストに対しては、テキストスタイルを別に保持するなどの対処が必要となります。