Make attributed titles work on iOS

This commit is contained in:
Nate Weaver 2020-04-15 16:34:16 -05:00
parent dc787620c5
commit 6aff83481f
4 changed files with 65 additions and 21 deletions

View File

@ -6,11 +6,29 @@
// Copyright © 2020 Ranchero Software. All rights reserved. // Copyright © 2020 Ranchero Software. All rights reserved.
// //
#if canImport(AppKit)
import AppKit import AppKit
typealias Font = NSFont
typealias FontDescriptor = NSFontDescriptor
typealias Color = NSColor
private let boldTrait = NSFontDescriptor.SymbolicTraits.bold
private let italicTrait = NSFontDescriptor.SymbolicTraits.italic
private let monoSpaceTrait = NSFontDescriptor.SymbolicTraits.monoSpace
#else
import UIKit
typealias Font = UIFont
typealias FontDescriptor = UIFontDescriptor
typealias Color = UIColor
private let boldTrait = UIFontDescriptor.SymbolicTraits.traitBold
private let italicTrait = UIFontDescriptor.SymbolicTraits.traitItalic
private let monoSpaceTrait = UIFontDescriptor.SymbolicTraits.traitMonoSpace
#endif
extension NSAttributedString { extension NSAttributedString {
func adding(font baseFont: NSFont, color: NSColor? = nil) -> NSAttributedString { func adding(font baseFont: Font, color: Color? = nil) -> NSAttributedString {
let mutable = self.mutableCopy() as! NSMutableAttributedString let mutable = self.mutableCopy() as! NSMutableAttributedString
let fullRange = NSRange(location: 0, length: mutable.length) let fullRange = NSRange(location: 0, length: mutable.length)
@ -22,60 +40,68 @@ extension NSAttributedString {
let baseDescriptor = baseFont.fontDescriptor let baseDescriptor = baseFont.fontDescriptor
let baseSymbolicTraits = baseDescriptor.symbolicTraits let baseSymbolicTraits = baseDescriptor.symbolicTraits
let baseTraits = baseDescriptor.object(forKey: .traits) as! [NSFontDescriptor.TraitKey: Any] let baseTraits = baseDescriptor.object(forKey: .traits) as! [FontDescriptor.TraitKey: Any]
let baseWeight = baseTraits[.weight] as! NSFont.Weight let baseWeight = baseTraits[.weight] as! Font.Weight
mutable.enumerateAttribute(.font, in: fullRange, options: []) { (font: Any?, range: NSRange, stop: UnsafeMutablePointer<ObjCBool>) in mutable.enumerateAttribute(.font, in: fullRange, options: []) { (font: Any?, range: NSRange, stop: UnsafeMutablePointer<ObjCBool>) in
guard let font = font as? NSFont else { return } guard let font = font as? Font else { return }
var newSymbolicTraits = baseSymbolicTraits var newSymbolicTraits = baseSymbolicTraits
let symbolicTraits = font.fontDescriptor.symbolicTraits let symbolicTraits = font.fontDescriptor.symbolicTraits
if symbolicTraits.contains(.italic) { if symbolicTraits.contains(italicTrait) {
newSymbolicTraits.insert(.italic) newSymbolicTraits.insert(italicTrait)
} }
if symbolicTraits.contains(.monoSpace) { if symbolicTraits.contains(monoSpaceTrait) {
newSymbolicTraits.insert(.monoSpace) newSymbolicTraits.insert(monoSpaceTrait)
} }
#if canImport(AppKit)
var descriptor = baseDescriptor.withSymbolicTraits(newSymbolicTraits) var descriptor = baseDescriptor.withSymbolicTraits(newSymbolicTraits)
#else
var descriptor = baseDescriptor.withSymbolicTraits(newSymbolicTraits)!
#endif
if symbolicTraits.contains(.bold) { if symbolicTraits.contains(boldTrait) {
// If the base font is semibold (as timeline titles are), make the "bold" // If the base font is semibold (as timeline titles are), make the "bold"
// text heavy for better contrast. // text heavy for better contrast.
if baseWeight == .semibold { if baseWeight == .semibold {
let traits: [NSFontDescriptor.TraitKey: Any] = [.weight: NSFont.Weight.heavy] let traits: [FontDescriptor.TraitKey: Any] = [.weight: Font.Weight.heavy]
let attributes: [NSFontDescriptor.AttributeName: Any] = [.traits: traits] let attributes: [FontDescriptor.AttributeName: Any] = [.traits: traits]
descriptor = descriptor.addingAttributes(attributes) descriptor = descriptor.addingAttributes(attributes)
} }
} }
let newFont = NSFont(descriptor: descriptor, size: size) let newFont = Font(descriptor: descriptor, size: size)
mutable.addAttribute(.font, value: newFont as Any, range: range) mutable.addAttribute(.font, value: newFont as Any, range: range)
} }
// make sup/sub smaller // make sup/sub smaller. `Key("NSSupeScript")` is used here because `.superscript`
// isn't defined in UIKit, for some reason.
mutable.enumerateAttributes(in: fullRange, options: []) { (attributes: [Key : Any], range: NSRange, stop: UnsafeMutablePointer<ObjCBool>) in mutable.enumerateAttributes(in: fullRange, options: []) { (attributes: [Key : Any], range: NSRange, stop: UnsafeMutablePointer<ObjCBool>) in
guard let superscript = attributes[.superscript] as? Int else { guard let superscript = attributes[Key("NSSuperScript")] as? Int else {
return return
} }
if superscript != 0 { if superscript != 0 {
let font = mutable.attribute(.font, at: range.location, effectiveRange: nil) as! NSFont let font = mutable.attribute(.font, at: range.location, effectiveRange: nil) as! Font
let features: [NSFontDescriptor.FeatureKey: Any] = [.typeIdentifier: kVerticalPositionType, .selectorIdentifier: superscript > 0 ? kSuperiorsSelector : kInferiorsSelector] #if canImport(AppKit)
let attributes: [NSFontDescriptor.AttributeName: Any] = [.featureSettings: [features]] let features: [FontDescriptor.FeatureKey: Any] = [.typeIdentifier: kVerticalPositionType, .selectorIdentifier: superscript > 0 ? kSuperiorsSelector : kInferiorsSelector]
#else
let features: [FontDescriptor.FeatureKey: Any] = [.featureIdentifier: kVerticalPositionType, .typeIdentifier: superscript > 0 ? kSuperiorsSelector : kInferiorsSelector]
#endif
let attributes: [FontDescriptor.AttributeName: Any] = [.featureSettings: [features]]
let descriptor = font.fontDescriptor.addingAttributes(attributes) let descriptor = font.fontDescriptor.addingAttributes(attributes)
let newFont = NSFont(descriptor: descriptor, size: font.pointSize) let newFont = Font(descriptor: descriptor, size: font.pointSize)
mutable.addAttribute(.font, value: newFont as Any, range: range) mutable.addAttribute(.font, value: newFont as Any, range: range)
mutable.addAttribute(.superscript, value: 0, range: range) mutable.addAttribute(Key("NSSuperScript"), value: 0, range: range)
} }
} }
return mutable.copy() as! NSAttributedString return mutable.copy() as! NSAttributedString

View File

@ -731,6 +731,7 @@
B2B8075E239C49D300F191E0 /* RSImage-AppIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */; }; B2B8075E239C49D300F191E0 /* RSImage-AppIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */; };
B2B80778239C4C7000F191E0 /* RSImage-AppIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */; }; B2B80778239C4C7000F191E0 /* RSImage-AppIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */; };
B2B80779239C4C7300F191E0 /* RSImage-AppIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */; }; B2B80779239C4C7300F191E0 /* RSImage-AppIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B8075D239C49D300F191E0 /* RSImage-AppIcons.swift */; };
B2C0FDEA2447A69100ADC150 /* NSAttributedString+NetNewsWire.swift in Sources */ = {isa = PBXBuildFile; fileRef = B26B9571243D176B0053EEF5 /* NSAttributedString+NetNewsWire.swift */; };
B528F81E23333C7E00E735DD /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = B528F81D23333C7E00E735DD /* page.html */; }; B528F81E23333C7E00E735DD /* page.html in Resources */ = {isa = PBXBuildFile; fileRef = B528F81D23333C7E00E735DD /* page.html */; };
BDCB516724282C8A00102A80 /* AccountsNewsBlur.xib in Resources */ = {isa = PBXBuildFile; fileRef = BDCB514D24282C8A00102A80 /* AccountsNewsBlur.xib */; }; BDCB516724282C8A00102A80 /* AccountsNewsBlur.xib in Resources */ = {isa = PBXBuildFile; fileRef = BDCB514D24282C8A00102A80 /* AccountsNewsBlur.xib */; };
BDCB516824282C8A00102A80 /* AccountsNewsBlur.xib in Resources */ = {isa = PBXBuildFile; fileRef = BDCB514D24282C8A00102A80 /* AccountsNewsBlur.xib */; }; BDCB516824282C8A00102A80 /* AccountsNewsBlur.xib in Resources */ = {isa = PBXBuildFile; fileRef = BDCB514D24282C8A00102A80 /* AccountsNewsBlur.xib */; };
@ -4307,6 +4308,7 @@
51F9F3F923DFB16300A314FD /* UITableView-Extensions.swift in Sources */, 51F9F3F923DFB16300A314FD /* UITableView-Extensions.swift in Sources */,
51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */, 51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */,
51C4526B226508F600C03939 /* MasterFeedViewController.swift in Sources */, 51C4526B226508F600C03939 /* MasterFeedViewController.swift in Sources */,
B2C0FDEA2447A69100ADC150 /* NSAttributedString+NetNewsWire.swift in Sources */,
5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */, 5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */,
84CAFCB022BC8C35007694F0 /* FetchRequestOperation.swift in Sources */, 84CAFCB022BC8C35007694F0 /* FetchRequestOperation.swift in Sources */,
51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */, 51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */,

View File

@ -12,6 +12,7 @@ import Articles
struct MasterTimelineCellData { struct MasterTimelineCellData {
let title: String let title: String
let attributedTitle: NSAttributedString
let summary: String let summary: String
let dateString: String let dateString: String
let feedName: String let feedName: String
@ -28,6 +29,7 @@ struct MasterTimelineCellData {
init(article: Article, showFeedName: ShowFeedName, feedName: String?, byline: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: UIImage?, numberOfLines: Int, iconSize: IconSize) { init(article: Article, showFeedName: ShowFeedName, feedName: String?, byline: String?, iconImage: IconImage?, showIcon: Bool, featuredImage: UIImage?, numberOfLines: Int, iconSize: IconSize) {
self.title = ArticleStringFormatter.truncatedTitle(article) self.title = ArticleStringFormatter.truncatedTitle(article)
self.attributedTitle = ArticleStringFormatter.attributedTruncatedTitle(article)
self.summary = ArticleStringFormatter.truncatedSummary(article) self.summary = ArticleStringFormatter.truncatedSummary(article)
self.dateString = ArticleStringFormatter.dateString(article.logicalDatePublished) self.dateString = ArticleStringFormatter.dateString(article.logicalDatePublished)
@ -60,6 +62,7 @@ struct MasterTimelineCellData {
init() { //Empty init() { //Empty
self.title = "" self.title = ""
self.attributedTitle = NSAttributedString()
self.summary = "" self.summary = ""
self.dateString = "" self.dateString = ""
self.feedName = "" self.feedName = ""

View File

@ -148,7 +148,7 @@ private extension MasterTimelineTableViewCell {
func updateTitleView() { func updateTitleView() {
titleView.font = MasterTimelineDefaultCellLayout.titleFont titleView.font = MasterTimelineDefaultCellLayout.titleFont
titleView.textColor = labelColor titleView.textColor = labelColor
updateTextFieldText(titleView, cellData?.title) updateTextFieldAttributedText(titleView, cellData?.attributedTitle)
} }
func updateSummaryView() { func updateSummaryView() {
@ -170,6 +170,19 @@ private extension MasterTimelineTableViewCell {
setNeedsLayout() setNeedsLayout()
} }
} }
func updateTextFieldAttributedText(_ label: UILabel, _ text: NSAttributedString?) {
var s = text ?? NSAttributedString(string: "")
if let fieldFont = label.font, let color = label.textColor {
s = s.adding(font: fieldFont, color: color)
}
if label.attributedText != s {
label.attributedText = s
setNeedsLayout()
}
}
func updateFeedNameView() { func updateFeedNameView() {
switch cellData.showFeedName { switch cellData.showFeedName {