mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2024-12-24 16:51:19 +01:00
Make attributed titles work on iOS
This commit is contained in:
parent
dc787620c5
commit
6aff83481f
@ -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
|
||||||
|
@ -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 */,
|
||||||
|
@ -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 = ""
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user