implement dynamic type for master feed list

This commit is contained in:
Maurice Parker 2019-04-28 10:31:35 -05:00
parent 5fc3fee12d
commit d7391b208d
8 changed files with 140 additions and 66 deletions

View File

@ -64,7 +64,6 @@
51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452702265091600C03939 /* MultilineUILabelSizer.swift */; };
51C452782265091600C03939 /* MasterTimelineCellData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452712265091600C03939 /* MasterTimelineCellData.swift */; };
51C452792265091600C03939 /* MasterTimelineTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452722265091600C03939 /* MasterTimelineTableViewCell.swift */; };
51C4527A2265091600C03939 /* SingleLineUILabelSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452732265091600C03939 /* SingleLineUILabelSizer.swift */; };
51C4527B2265091600C03939 /* MasterUnreadIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452742265091600C03939 /* MasterUnreadIndicatorView.swift */; };
51C4527C2265091600C03939 /* MasterTimelineCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C452752265091600C03939 /* MasterTimelineCellLayout.swift */; };
51C4527F2265092C00C03939 /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51C4527E2265092C00C03939 /* DetailViewController.swift */; };
@ -117,6 +116,8 @@
51F85BF52273625800C787DC /* Bundle-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BF42273625800C787DC /* Bundle-Extensions.swift */; };
51F85BF722749FA100C787DC /* UIFont-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BF622749FA100C787DC /* UIFont-Extensions.swift */; };
51F85BF92274AA7B00C787DC /* UIBarButtonItem-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */; };
51F85BFB2275D85000C787DC /* Array-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BFA2275D85000C787DC /* Array-Extensions.swift */; };
51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */; };
6581C73820CED60100F4AD34 /* SafariExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */; };
6581C73A20CED60100F4AD34 /* SafariExtensionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6581C73920CED60100F4AD34 /* SafariExtensionViewController.swift */; };
6581C73D20CED60100F4AD34 /* SafariExtensionViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6581C73B20CED60100F4AD34 /* SafariExtensionViewController.xib */; };
@ -651,7 +652,6 @@
51C452702265091600C03939 /* MultilineUILabelSizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultilineUILabelSizer.swift; sourceTree = "<group>"; };
51C452712265091600C03939 /* MasterTimelineCellData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterTimelineCellData.swift; sourceTree = "<group>"; };
51C452722265091600C03939 /* MasterTimelineTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterTimelineTableViewCell.swift; sourceTree = "<group>"; };
51C452732265091600C03939 /* SingleLineUILabelSizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleLineUILabelSizer.swift; sourceTree = "<group>"; };
51C452742265091600C03939 /* MasterUnreadIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterUnreadIndicatorView.swift; sourceTree = "<group>"; };
51C452752265091600C03939 /* MasterTimelineCellLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MasterTimelineCellLayout.swift; sourceTree = "<group>"; };
51C4527E2265092C00C03939 /* DetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailViewController.swift; sourceTree = "<group>"; };
@ -672,6 +672,8 @@
51F85BF42273625800C787DC /* Bundle-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle-Extensions.swift"; sourceTree = "<group>"; };
51F85BF622749FA100C787DC /* UIFont-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont-Extensions.swift"; sourceTree = "<group>"; };
51F85BF82274AA7B00C787DC /* UIBarButtonItem-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIBarButtonItem-Extensions.swift"; sourceTree = "<group>"; };
51F85BFA2275D85000C787DC /* Array-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array-Extensions.swift"; sourceTree = "<group>"; };
51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleLineUILabelSizer.swift; sourceTree = "<group>"; };
6581C73320CED60000F4AD34 /* Subscribe to Feed.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Subscribe to Feed.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
6581C73420CED60100F4AD34 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
6581C73720CED60100F4AD34 /* SafariExtensionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SafariExtensionHandler.swift; sourceTree = "<group>"; };
@ -972,6 +974,7 @@
51C45245226506C800C03939 /* Extensions */ = {
isa = PBXGroup;
children = (
51F85BFA2275D85000C787DC /* Array-Extensions.swift */,
51F85BF42273625800C787DC /* Bundle-Extensions.swift */,
5183CCD9226E31A50010922C /* NonIntrinsicImageView.swift */,
5183CCCF226E1E880010922C /* NonIntrinsicLabel.swift */,
@ -1021,7 +1024,7 @@
51C452712265091600C03939 /* MasterTimelineCellData.swift */,
51C452752265091600C03939 /* MasterTimelineCellLayout.swift */,
51C452742265091600C03939 /* MasterUnreadIndicatorView.swift */,
51C452732265091600C03939 /* SingleLineUILabelSizer.swift */,
51F85BFC2275DCA800C787DC /* SingleLineUILabelSizer.swift */,
51C452702265091600C03939 /* MultilineUILabelSizer.swift */,
);
path = Cell;
@ -2233,6 +2236,7 @@
51C45291226509C800C03939 /* SmartFeed.swift in Sources */,
51C452A722650A3D00C03939 /* RSImage-Extensions.swift in Sources */,
51C45269226508F600C03939 /* MasterFeedTableViewCell.swift in Sources */,
51F85BFD2275DCA800C787DC /* SingleLineUILabelSizer.swift in Sources */,
51C4528F226509BD00C03939 /* UnreadFeed.swift in Sources */,
5183CCDD226F1F5C0010922C /* NavigationProgressView.swift in Sources */,
51C452772265091600C03939 /* MultilineUILabelSizer.swift in Sources */,
@ -2278,11 +2282,11 @@
5115CAF42266301400B21BCE /* AddContainerViewController.swift in Sources */,
51C45297226509E300C03939 /* DefaultFeedsImporter.swift in Sources */,
512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */,
51F85BFB2275D85000C787DC /* Array-Extensions.swift in Sources */,
51C452AC22650FD200C03939 /* AppNotifications.swift in Sources */,
51C452762265091600C03939 /* MasterTimelineViewController.swift in Sources */,
5183CCE9226F68D90010922C /* RefreshTimer.swift in Sources */,
51C452882265093600C03939 /* AddFeedViewController.swift in Sources */,
51C4527A2265091600C03939 /* SingleLineUILabelSizer.swift in Sources */,
51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */,
5183CCE3226F314C0010922C /* ProgressTableViewController.swift in Sources */,
512E09012268907400BDCFDD /* MasterFeedTableViewSectionHeader.swift in Sources */,

View File

@ -0,0 +1,20 @@
//
// Array-Extensions.swift
// NetNewsWire-iOS
//
// Created by Maurice Parker on 4/28/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
import UIKit
extension Array where Element == CGRect {
func maxY() -> CGFloat {
var y: CGFloat = 0.0
self.forEach { y = Swift.max(y, $0.maxY) }
return y
}
}

View File

@ -19,6 +19,7 @@ class MasterFeedTableViewCell : UITableViewCell {
weak var delegate: MasterFeedTableViewCellDelegate?
var allowDisclosureSelection = false
private var layout: MasterFeedTableViewCellLayout?
override var accessibilityLabel: String? {
set {}
@ -52,6 +53,7 @@ class MasterFeedTableViewCell : UITableViewCell {
var shouldShowImage = false {
didSet {
if shouldShowImage != oldValue {
resetLayout()
setNeedsLayout()
}
faviconImageView.image = shouldShowImage ? faviconImage : nil
@ -66,6 +68,7 @@ class MasterFeedTableViewCell : UITableViewCell {
if unreadCountView.unreadCount != newValue {
unreadCountView.unreadCount = newValue
unreadCountView.isHidden = (newValue < 1)
resetLayout()
setNeedsLayout()
}
}
@ -78,6 +81,7 @@ class MasterFeedTableViewCell : UITableViewCell {
set {
if titleView.text != newValue {
titleView.text = newValue
resetLayout()
setNeedsDisplay()
setNeedsLayout()
}
@ -87,7 +91,6 @@ class MasterFeedTableViewCell : UITableViewCell {
private let titleView: UILabel = {
let label = NonIntrinsicLabel()
label.numberOfLines = 0
label.lineBreakMode = .byTruncatingTail
label.allowsDefaultTighteningForTruncation = false
label.adjustsFontForContentSizeCategory = true
label.font = .preferredFont(forTextStyle: .body)
@ -98,7 +101,7 @@ class MasterFeedTableViewCell : UITableViewCell {
return UIImageView(image: AppAssets.feedImage)
}()
private let unreadCountView = MasterFeedUnreadCountView(frame: CGRect.zero)
private var unreadCountView = MasterFeedUnreadCountView(frame: CGRect.zero)
private var showingEditControl = false
private var disclosureButton: UIButton?
@ -107,16 +110,30 @@ class MasterFeedTableViewCell : UITableViewCell {
commonInit()
}
override func prepareForReuse() {
layout = nil
unreadCountView.setNeedsLayout()
unreadCountView.setNeedsDisplay()
}
override func willTransition(to state: UITableViewCell.StateMask) {
super.willTransition(to: state)
showingEditControl = state.contains(.showingEditControl)
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
if layout == nil {
resetLayout()
}
return CGSize(width: bounds.width, height: layout!.height)
}
override func layoutSubviews() {
super.layoutSubviews()
let shouldShowDisclosure = !(showingEditControl && showsReorderControl)
let layout = MasterFeedTableViewCellLayout(cellSize: bounds.size, insets: safeAreaInsets, shouldShowImage: shouldShowImage, label: titleView, unreadCountView: unreadCountView, showingEditingControl: showingEditControl, indent: indentationLevel == 1, shouldShowDisclosure: shouldShowDisclosure)
layoutWith(layout)
if layout == nil {
resetLayout()
}
layoutWith(layout!)
}
@objc func buttonPressed(_ sender: UIButton) {
@ -168,6 +185,11 @@ private extension MasterFeedTableViewCell {
view.translatesAutoresizingMaskIntoConstraints = false
}
func resetLayout() {
let shouldShowDisclosure = !(showingEditControl && showsReorderControl)
layout = MasterFeedTableViewCellLayout(cellSize: bounds.size, insets: safeAreaInsets, shouldShowImage: shouldShowImage, label: titleView, unreadCountView: unreadCountView, showingEditingControl: showingEditControl, indent: indentationLevel == 1, shouldShowDisclosure: shouldShowDisclosure)
}
func layoutWith(_ layout: MasterFeedTableViewCellLayout) {
faviconImageView.setFrameIfNotEqual(layout.faviconRect)
titleView.setFrameIfNotEqual(layout.titleRect)

View File

@ -25,6 +25,8 @@ struct MasterFeedTableViewCellLayout {
let unreadCountRect: CGRect
let disclosureButtonRect: CGRect
let height: CGFloat
init(cellSize: CGSize, insets: UIEdgeInsets, shouldShowImage: Bool, label: UILabel, unreadCountView: MasterFeedUnreadCountView, showingEditingControl: Bool, indent: Bool, shouldShowDisclosure: Bool) {
var initialIndent = MasterFeedTableViewCellLayout.marginLeft + insets.left
@ -41,21 +43,7 @@ struct MasterFeedTableViewCellLayout {
var rFavicon = CGRect.zero
if shouldShowImage {
rFavicon = CGRect(x: bounds.origin.x, y: 0.0, width: MasterFeedTableViewCellLayout.imageSize.width, height: MasterFeedTableViewCellLayout.imageSize.height)
rFavicon = MasterFeedTableViewCellLayout.centerVertically(rFavicon, bounds)
}
self.faviconRect = rFavicon
// Title
let labelSize = SingleLineUILabelSizer.size(for: label.text ?? "", font: label.font!)
var rLabel = CGRect(x: 0.0, y: 0.0, width: labelSize.width, height: labelSize.height)
if shouldShowImage {
rLabel.origin.x = rFavicon.maxX + MasterFeedTableViewCellLayout.imageMarginRight
} else {
rLabel.origin.x = bounds.minX
}
rLabel = MasterFeedTableViewCellLayout.centerVertically(rLabel, bounds)
// Unread Count
let unreadCountSize = unreadCountView.intrinsicContentSize
@ -63,44 +51,54 @@ struct MasterFeedTableViewCellLayout {
var rUnread = CGRect.zero
if !unreadCountIsHidden {
rUnread.size = unreadCountSize
rUnread.origin.x = bounds.maxX -
(unreadCountSize.width + MasterFeedTableViewCellLayout.unreadCountMarginRight + MasterFeedTableViewCellLayout.disclosureButtonSize.width)
rUnread = MasterFeedTableViewCellLayout.centerVertically(rUnread, bounds)
// Cap the Title width based on the unread indicator button
let labelMaxX = rUnread.minX - MasterFeedTableViewCellLayout.unreadCountMarginLeft
if rLabel.maxX > labelMaxX {
rLabel.size.width = labelMaxX - rLabel.minX
}
}
self.unreadCountRect = rUnread
// Disclosure Button
var rDisclosure = CGRect.zero
if shouldShowDisclosure {
rDisclosure.size = MasterFeedTableViewCellLayout.disclosureButtonSize
rDisclosure.origin.x = bounds.maxX - MasterFeedTableViewCellLayout.disclosureButtonSize.width
rDisclosure = MasterFeedTableViewCellLayout.centerVertically(rDisclosure, bounds)
// Cap the Title width based on the disclosure button
let labelMaxX = rDisclosure.minX
if rLabel.maxX > labelMaxX {
rLabel.size.width = labelMaxX - rLabel.minX
}
}
// Title
let labelWidth = bounds.width - (rFavicon.width + MasterFeedTableViewCellLayout.imageMarginRight + MasterFeedTableViewCellLayout.unreadCountMarginLeft + rUnread.width + MasterFeedTableViewCellLayout.unreadCountMarginRight + rDisclosure.width)
let labelSizeInfo = MultilineUILabelSizer.size(for: label.text ?? "", font: label.font, numberOfLines: 0, width: Int(floor(labelWidth)))
var rLabel = CGRect(x: 0.0, y: 0.0, width: labelSizeInfo.size.width, height: labelSizeInfo.size.height)
if shouldShowImage {
rLabel.origin.x = rFavicon.maxX + MasterFeedTableViewCellLayout.imageMarginRight
} else {
rLabel.origin.x = bounds.minX
}
// Determine cell height
let cellHeight = [rFavicon, rLabel, rUnread, rDisclosure].maxY()
// Center in Cell
let newBounds = CGRect(x: bounds.origin.x, y: bounds.origin.y, width: bounds.width, height: cellHeight)
if shouldShowImage {
rFavicon = MasterFeedTableViewCellLayout.centerVertically(rFavicon, newBounds)
}
if !unreadCountIsHidden {
rUnread = MasterFeedTableViewCellLayout.centerVertically(rUnread, newBounds)
}
if shouldShowDisclosure {
rDisclosure = MasterFeedTableViewCellLayout.centerVertically(rDisclosure, newBounds)
}
rLabel = MasterFeedTableViewCellLayout.centerVertically(rLabel, newBounds)
// Assign the properties
self.height = cellHeight
self.faviconRect = rFavicon
self.unreadCountRect = rUnread
self.disclosureButtonRect = rDisclosure
// Cap the Title width based on total width
if rLabel.maxX > bounds.maxX {
rLabel.size.width = bounds.maxX - rLabel.minX
}
self.titleRect = rLabel
}

View File

@ -10,6 +10,8 @@ import UIKit
class MasterFeedTableViewSectionHeader: UITableViewHeaderFooterView {
private var layout: MasterFeedTableViewCellLayout?
override var accessibilityLabel: String? {
set {}
get {
@ -57,7 +59,6 @@ class MasterFeedTableViewSectionHeader: UITableViewHeaderFooterView {
private let titleView: UILabel = {
let label = NonIntrinsicLabel()
label.numberOfLines = 0
label.lineBreakMode = .byTruncatingTail
label.allowsDefaultTighteningForTruncation = false
label.adjustsFontForContentSizeCategory = true
label.font = .preferredFont(forTextStyle: .body)
@ -81,12 +82,20 @@ class MasterFeedTableViewSectionHeader: UITableViewHeaderFooterView {
commonInit()
}
override func layoutSubviews() {
super.layoutSubviews()
let layout = MasterFeedTableViewCellLayout(cellSize: bounds.size, insets: safeAreaInsets, shouldShowImage: false, label: titleView, unreadCountView: unreadCountView, showingEditingControl: false, indent: true, shouldShowDisclosure: true)
layoutWith(layout)
override func sizeThatFits(_ size: CGSize) -> CGSize {
if layout == nil {
resetLayout()
}
return CGSize(width: bounds.width, height: layout!.height)
}
override func layoutSubviews() {
super.layoutSubviews()
if layout == nil {
resetLayout()
}
layoutWith(layout!)
}
}
@ -115,6 +124,10 @@ private extension MasterFeedTableViewSectionHeader {
view.translatesAutoresizingMaskIntoConstraints = false
}
func resetLayout() {
layout = MasterFeedTableViewCellLayout(cellSize: bounds.size, insets: safeAreaInsets, shouldShowImage: false, label: titleView, unreadCountView: unreadCountView, showingEditingControl: false, indent: true, shouldShowDisclosure: true)
}
func layoutWith(_ layout: MasterFeedTableViewCellLayout) {
titleView.setFrameIfNotEqual(layout.titleRect)
unreadCountView.setFrameIfNotEqual(layout.unreadCountRect)

View File

@ -38,6 +38,7 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn
NotificationCenter.default.addObserver(self, selector: #selector(backingStoresDidRebuild(_:)), name: .BackingStoresDidRebuild, object: navState)
NotificationCenter.default.addObserver(self, selector: #selector(masterSelectionDidChange(_:)), name: .MasterSelectionDidChange, object: navState)
NotificationCenter.default.addObserver(self, selector: #selector(contentSizeCategoryDidChange), name: UIContentSizeCategory.didChangeNotification, object: nil)
refreshControl = UIRefreshControl()
refreshControl!.addTarget(self, action: #selector(refreshAccounts(_:)), for: .valueChanged)
@ -141,6 +142,10 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn
}
}
@objc func contentSizeCategoryDidChange(_ note: Notification) {
tableView.reloadData()
}
// MARK: Table View
override func numberOfSections(in tableView: UITableView) -> Int {
@ -152,7 +157,27 @@ class MasterFeedViewController: ProgressTableViewController, UndoableCommandRunn
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return CGFloat(integerLiteral: 44)
guard let nameProvider = navState.rootNode.childAtIndex(section)?.representedObject as? DisplayNameProvider else {
return 44
}
let headerView = MasterFeedTableViewSectionHeader()
headerView.name = nameProvider.nameForDisplay
guard let sectionNode = navState.rootNode.childAtIndex(section) else {
return 44
}
if let account = sectionNode.representedObject as? Account {
headerView.unreadCount = account.unreadCount
} else {
headerView.unreadCount = 0
}
let size = headerView.sizeThatFits(CGSize.zero)
return size.height
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

View File

@ -243,14 +243,3 @@ private extension MasterTimelineCellLayout {
return r
}
}
private extension Array where Element == CGRect {
func maxY() -> CGFloat {
var y: CGFloat = 0.0
self.forEach { y = Swift.max(y, $0.maxY) }
return y
}
}

View File

@ -98,9 +98,12 @@ private extension MultilineUILabelSizer {
}
var height = MultilineUILabelSizer.calculateHeight(string, width, font)
let maxHeight = singleLineHeightEstimate * numberOfLines
if height > maxHeight {
height = maxHeight
if numberOfLines != 0 {
let maxHeight = singleLineHeightEstimate * numberOfLines
if height > maxHeight {
height = maxHeight
}
}
cache[string]![width] = height