// // TimelineTableViewCell.swift // NetNewsWire // // Created by Brent Simmons on 8/31/15. // Copyright © 2015 Ranchero Software, LLC. All rights reserved. // import UIKit import UIKitExtras import Images class TimelineTableViewCell: VibrantTableViewCell { private let titleView = TimelineTableViewCell.multiLineUILabel() private let summaryView = TimelineTableViewCell.multiLineUILabel() private let unreadIndicatorView = UnreadIndicatorView(frame: CGRect.zero) private let dateView = TimelineTableViewCell.singleLineUILabel() private let feedNameView = TimelineTableViewCell.singleLineUILabel() private lazy var iconView = IconView() private lazy var starView = { return NonIntrinsicImageView(image: AppAssets.timelineStarImage) }() private var unreadIndicatorPropertyAnimator: UIViewPropertyAnimator? private var starViewPropertyAnimator: UIViewPropertyAnimator? var cellData: TimelineCellData! { didSet { updateSubviews() } } required init?(coder: NSCoder) { super.init(coder: coder) commonInit() } override func prepareForReuse() { unreadIndicatorPropertyAnimator?.stopAnimation(true) unreadIndicatorPropertyAnimator = nil unreadIndicatorView.isHidden = true starViewPropertyAnimator?.stopAnimation(true) starViewPropertyAnimator = nil starView.isHidden = true } override var frame: CGRect { didSet { setNeedsLayout() } } override func updateVibrancy(animated: Bool) { updateLabelVibrancy(titleView, color: labelColor, animated: animated) updateLabelVibrancy(summaryView, color: labelColor, animated: animated) updateLabelVibrancy(dateView, color: secondaryLabelColor, animated: animated) updateLabelVibrancy(feedNameView, color: secondaryLabelColor, animated: animated) if animated { UIView.animate(withDuration: Self.duration) { if self.isHighlighted || self.isSelected { self.unreadIndicatorView.backgroundColor = AppAssets.vibrantTextColor } else { self.unreadIndicatorView.backgroundColor = AppAssets.secondaryAccentColor } } } else { if self.isHighlighted || self.isSelected { self.unreadIndicatorView.backgroundColor = AppAssets.vibrantTextColor } else { self.unreadIndicatorView.backgroundColor = AppAssets.secondaryAccentColor } } } override func sizeThatFits(_ size: CGSize) -> CGSize { let layout = updatedLayout(width: size.width) return CGSize(width: size.width, height: layout.height) } override func layoutSubviews() { super.layoutSubviews() let layout = updatedLayout(width: bounds.width) unreadIndicatorView.setFrameIfNotEqual(layout.unreadIndicatorRect) starView.setFrameIfNotEqual(layout.starRect) iconView.setFrameIfNotEqual(layout.iconImageRect) setFrame(for: titleView, rect: layout.titleRect) setFrame(for: summaryView, rect: layout.summaryRect) feedNameView.setFrameIfNotEqual(layout.feedNameRect) dateView.setFrameIfNotEqual(layout.dateRect) separatorInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) } func setIconImage(_ image: IconImage) { iconView.iconImage = image } } // MARK: - Private private extension TimelineTableViewCell { static func singleLineUILabel() -> UILabel { let label = NonIntrinsicLabel() label.lineBreakMode = .byTruncatingTail label.allowsDefaultTighteningForTruncation = false label.adjustsFontForContentSizeCategory = true return label } static func multiLineUILabel() -> UILabel { let label = NonIntrinsicLabel() label.numberOfLines = 0 label.lineBreakMode = .byTruncatingTail label.allowsDefaultTighteningForTruncation = false label.adjustsFontForContentSizeCategory = true return label } func setFrame(for label: UILabel, rect: CGRect) { if Int(floor(rect.height)) == 0 || Int(floor(rect.width)) == 0 { hideView(label) } else { showView(label) label.setFrameIfNotEqual(rect) } } func addSubviewAtInit(_ view: UIView, hidden: Bool) { addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false view.isHidden = hidden } func commonInit() { addSubviewAtInit(titleView, hidden: false) addSubviewAtInit(summaryView, hidden: true) addSubviewAtInit(unreadIndicatorView, hidden: true) addSubviewAtInit(dateView, hidden: false) addSubviewAtInit(feedNameView, hidden: true) addSubviewAtInit(iconView, hidden: true) addSubviewAtInit(starView, hidden: true) } func updatedLayout(width: CGFloat) -> TimelineCellLayout { if UIApplication.shared.preferredContentSizeCategory.isAccessibilityCategory { return TimelineAccessibilityCellLayout(width: width, insets: safeAreaInsets, cellData: cellData) } else { return TimelineDefaultCellLayout(width: width, insets: safeAreaInsets, cellData: cellData) } } func updateTitleView() { titleView.font = TimelineDefaultCellLayout.titleFont titleView.textColor = labelColor updateTextFieldAttributedText(titleView, cellData?.attributedTitle) } func updateSummaryView() { summaryView.font = TimelineDefaultCellLayout.summaryFont summaryView.textColor = labelColor updateTextFieldText(summaryView, cellData?.summary) } func updateDateView() { dateView.font = TimelineDefaultCellLayout.dateFont dateView.textColor = secondaryLabelColor updateTextFieldText(dateView, cellData.dateString) } func updateTextFieldText(_ label: UILabel, _ text: String?) { let s = text ?? "" if label.text != s { label.text = s setNeedsLayout() } } func updateTextFieldAttributedText(_ label: UILabel, _ text: NSAttributedString?) { var s = text ?? NSAttributedString(string: "") if let fieldFont = label.font { s = s.adding(font: fieldFont) } if label.attributedText != s { label.attributedText = s setNeedsLayout() } } func updateFeedNameView() { switch cellData.showFeedName { case .feed: showView(feedNameView) feedNameView.font = TimelineDefaultCellLayout.feedNameFont feedNameView.textColor = secondaryLabelColor updateTextFieldText(feedNameView, cellData.feedName) case .byline: showView(feedNameView) feedNameView.font = TimelineDefaultCellLayout.feedNameFont feedNameView.textColor = secondaryLabelColor updateTextFieldText(feedNameView, cellData.byline) case .none: hideView(feedNameView) } } func updateUnreadIndicator() { if !unreadIndicatorView.isHidden && cellData.read && !cellData.starred { unreadIndicatorPropertyAnimator = UIViewPropertyAnimator(duration: 0.66, curve: .easeInOut) { [weak self] in self?.unreadIndicatorView.alpha = 0 } unreadIndicatorPropertyAnimator?.addCompletion { [weak self] _ in self?.unreadIndicatorView.isHidden = true self?.unreadIndicatorView.alpha = 1 self?.unreadIndicatorPropertyAnimator = nil } unreadIndicatorPropertyAnimator?.startAnimation() } else { unreadIndicatorView.alpha = 1 showOrHideView(unreadIndicatorView, cellData.read || cellData.starred) } } func updateStarView() { if !starView.isHidden && cellData.read && !cellData.starred { starViewPropertyAnimator = UIViewPropertyAnimator(duration: 0.66, curve: .easeInOut) { [weak self] in self?.starView.alpha = 0 } starViewPropertyAnimator?.addCompletion { [weak self] _ in self?.starView.isHidden = true self?.starView.alpha = 1 self?.starViewPropertyAnimator = nil } starViewPropertyAnimator?.startAnimation() } else { starView.alpha = 1 showOrHideView(starView, !cellData.starred) } } func updateIconImage() { guard let image = cellData.iconImage, cellData.showIcon else { makeIconEmpty() return } showView(iconView) if iconView.iconImage !== cellData.iconImage { iconView.iconImage = image setNeedsLayout() } } func updateAccessiblityLabel() { let starredStatus = cellData.starred ? "\(NSLocalizedString("Starred", comment: "Starred article for accessibility")), " : "" let unreadStatus = cellData.read ? "" : "\(NSLocalizedString("Unread", comment: "Unread")), " let label = starredStatus + unreadStatus + "\(cellData.feedName), \(cellData.title), \(cellData.summary), \(cellData.dateString)" accessibilityLabel = label } func makeIconEmpty() { if iconView.iconImage != nil { iconView.iconImage = nil setNeedsLayout() } hideView(iconView) } func hideView(_ view: UIView) { if !view.isHidden { view.isHidden = true } } func showView(_ view: UIView) { if view.isHidden { view.isHidden = false } } func showOrHideView(_ view: UIView, _ shouldHide: Bool) { shouldHide ? hideView(view) : showView(view) } func updateSubviews() { updateTitleView() updateSummaryView() updateDateView() updateFeedNameView() updateUnreadIndicator() updateStarView() updateIconImage() updateAccessiblityLabel() } }