refactor collapse cells (#1406)

This commit is contained in:
Ryan Nystrom
2018-01-13 17:21:55 -05:00
committed by GitHub
parent 6075edcee5
commit 878a9d98b7
10 changed files with 67 additions and 108 deletions

View File

@@ -9,7 +9,7 @@
import UIKit
import IGListKit
final class IssueCommentCodeBlockCell: IssueCommentBaseCell, ListBindable, CollapsibleCell {
final class IssueCommentCodeBlockCell: IssueCommentBaseCell, ListBindable {
static let scrollViewInset = UIEdgeInsets(
top: Styles.Sizes.rowSpacing,
@@ -26,7 +26,6 @@ final class IssueCommentCodeBlockCell: IssueCommentBaseCell, ListBindable, Colla
let textView = AttributedStringView()
let scrollView = UIScrollView()
let overlay = CreateCollapsibleOverlay()
override init(frame: CGRect) {
super.init(frame: frame)
@@ -58,7 +57,6 @@ final class IssueCommentCodeBlockCell: IssueCommentBaseCell, ListBindable, Colla
width: contentView.bounds.width - inset.left - inset.right,
height: scrollView.contentSize.height
)
LayoutCollapsible(layer: overlay, view: contentView)
}
// MARK: ListBindable
@@ -72,10 +70,4 @@ final class IssueCommentCodeBlockCell: IssueCommentBaseCell, ListBindable, Colla
textView.configureAndSizeToFit(text: viewModel.code, width: 0)
}
// MARK: CollapsibleCell
func setCollapse(visible: Bool) {
overlay.isHidden = !visible
}
}

View File

@@ -1,46 +0,0 @@
//
// CollapsibleCell.swift
// Freetime
//
// Created by Ryan Nystrom on 5/28/17.
// Copyright © 2017 Ryan Nystrom. All rights reserved.
//
import UIKit
let CollapseCellMinHeight: CGFloat = 20
func CreateCollapsibleOverlay() -> CALayer {
let layer = CAGradientLayer()
layer.isHidden = true
layer.colors = [
UIColor(white: 1, alpha: 0).cgColor,
UIColor(white: 0, alpha: 0.1).cgColor
]
return layer
}
func LayoutCollapsible(layer: CALayer, view: UIView) {
if layer.superlayer != view.layer {
view.layer.addSublayer(layer)
}
let bounds = view.bounds
// disable implicit CALayer animations
CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
layer.frame = CGRect(
x: 0,
y: bounds.height - CollapseCellMinHeight,
width: bounds.width,
height: CollapseCellMinHeight
)
CATransaction.commit()
}
protocol CollapsibleCell {
func setCollapse(visible: Bool)
}

View File

@@ -19,9 +19,7 @@ protocol IssueCommentImageHeightCellDelegate: class {
func imageDidFinishLoad(cell: IssueCommentImageCell, url: URL, size: CGSize)
}
final class IssueCommentImageCell: IssueCommentBaseCell,
ListBindable,
CollapsibleCell {
final class IssueCommentImageCell: IssueCommentBaseCell, ListBindable {
weak var delegate: IssueCommentImageCellDelegate?
weak var heightDelegate: IssueCommentImageHeightCellDelegate?
@@ -29,7 +27,6 @@ CollapsibleCell {
let imageView = FLAnimatedImageView()
private let spinner = UIActivityIndicatorView(activityIndicatorStyle: .gray)
private let overlay = CreateCollapsibleOverlay()
private var tapGesture: UITapGestureRecognizer!
override init(frame: CGRect) {
@@ -59,7 +56,6 @@ CollapsibleCell {
override func layoutSubviews() {
super.layoutSubviews()
LayoutCollapsible(layer: overlay, view: contentView)
var frame = bounds
if let size = imageView.image?.size {
@@ -105,12 +101,6 @@ CollapsibleCell {
}
}
// MARK: CollapsibleCell
func setCollapse(visible: Bool) {
overlay.isHidden = !visible
}
// MARK: UIGestureRecognizerDelegate
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
@@ -120,7 +110,7 @@ CollapsibleCell {
// only start the image tap gesture when an image exists
// and the tap is within the actual image's bounds
guard let image = imageView.image,
overlay.isHidden
collapsed == false
else { return false }
let imageSize = image.size

View File

@@ -20,7 +20,7 @@ private func bodyIsCollapsible(body: Any) -> Bool {
func IssueCollapsedBodies(bodies: [AnyObject], width: CGFloat) -> (AnyObject, CGFloat)? {
let cap: CGFloat = 300
// minimum height to collapse so expanding shows significant amount of content
let minDelta = CollapseCellMinHeight * 3
let minDelta = IssueCommentBaseCell.collapseCellMinHeight * 3
var totalHeight: CGFloat = 0
for body in bodies {
@@ -34,7 +34,7 @@ func IssueCollapsedBodies(bodies: [AnyObject], width: CGFloat) -> (AnyObject, CG
if bodyIsCollapsible(body: body),
totalHeight > cap,
totalHeight - cap > minDelta {
let collapsedBodyHeight = max(cap - (totalHeight - height), CollapseCellMinHeight)
let collapsedBodyHeight = max(cap - (totalHeight - height), IssueCommentBaseCell.collapseCellMinHeight)
return (body, collapsedBodyHeight)
}
}

View File

@@ -14,6 +14,8 @@ protocol IssueCommentDoubleTapDelegate: class {
class IssueCommentBaseCell: UICollectionViewCell, UIGestureRecognizerDelegate {
static let collapseCellMinHeight: CGFloat = 20
enum BorderType {
case head
case neck
@@ -28,6 +30,8 @@ class IssueCommentBaseCell: UICollectionViewCell, UIGestureRecognizerDelegate {
private let borderLayer = CAShapeLayer()
private let backgroundLayer = CAShapeLayer()
private let collapseLayer = CAGradientLayer()
private let collapseButton = UIButton()
override init(frame: CGRect) {
super.init(frame: frame)
@@ -50,6 +54,26 @@ class IssueCommentBaseCell: UICollectionViewCell, UIGestureRecognizerDelegate {
backgroundLayer.strokeColor = nil
backgroundLayer.fillColor = UIColor.white.cgColor
layer.insertSublayer(backgroundLayer, at: 0)
collapseLayer.isHidden = true
collapseLayer.colors = [
UIColor(white: 1, alpha: 0).cgColor,
UIColor(white: 1, alpha: 1).cgColor
]
collapseButton.setImage(UIImage(named: "bullets")?.withRenderingMode(.alwaysTemplate), for: .normal)
collapseButton.backgroundColor = Styles.Colors.Blue.medium.color
collapseButton.accessibilityTraits = UIAccessibilityTraitNone
collapseButton.tintColor = .white
collapseButton.titleLabel?.font = Styles.Fonts.smallTitle
collapseButton.clipsToBounds = true
collapseButton.isHidden = true
collapseButton.contentEdgeInsets = UIEdgeInsets(top: -2, left: 8, bottom: -2, right: 8)
collapseButton.imageEdgeInsets = .zero
collapseButton.sizeToFit()
collapseButton.layer.cornerRadius = collapseButton.bounds.height / 2
collapseButton.isUserInteractionEnabled = false // allow tap to pass through to cell
contentView.addSubview(collapseButton)
}
required init?(coder aDecoder: NSCoder) {
@@ -114,6 +138,29 @@ class IssueCommentBaseCell: UICollectionViewCell, UIGestureRecognizerDelegate {
borderLayer.frame = self.bounds
backgroundLayer.frame = self.bounds
if collapseLayer.isHidden == false {
contentView.layer.addSublayer(collapseLayer)
contentView.bringSubview(toFront: collapseButton)
let collapseFrame = CGRect(
x: bounds.minX,
y: bounds.height - IssueCommentBaseCell.collapseCellMinHeight,
width: bounds.width,
height: IssueCommentBaseCell.collapseCellMinHeight
)
// disable implicit CALayer animations
CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
collapseLayer.frame = collapseFrame
CATransaction.commit()
collapseButton.center = CGPoint(
x: collapseFrame.width / 2,
y: collapseFrame.minY + collapseButton.bounds.height / 2 - 3
)
}
}
override var backgroundColor: UIColor? {
@@ -130,6 +177,14 @@ class IssueCommentBaseCell: UICollectionViewCell, UIGestureRecognizerDelegate {
doubleTapDelegate?.didDoubleTap(cell: self)
}
var collapsed: Bool = false {
didSet {
collapseButton.isHidden = !collapsed
collapseLayer.isHidden = !collapsed
setNeedsLayout()
}
}
// MARK: UIGestureRecognizerDelegate
func gestureRecognizer(

View File

@@ -123,8 +123,8 @@ IssueCommentDoubleTapDelegate {
private func clearCollapseCells() {
// clear any collapse state before updating so we don't have a dangling overlay
for cell in collectionContext?.visibleCells(for: self) ?? [] {
if let cell = cell as? CollapsibleCell {
cell.setCollapse(visible: false)
if let cell = cell as? IssueCommentBaseCell {
cell.collapsed = false
}
}
}
@@ -284,8 +284,8 @@ IssueCommentDoubleTapDelegate {
else { fatalError("Cell not bindable") }
// extra config outside of bind API. applies to multiple cell types.
if let cell = cell as? CollapsibleCell {
cell.setCollapse(visible: collapsed && (viewModel as AnyObject) === object?.collapse?.model)
if let cell = cell as? IssueCommentBaseCell {
cell.collapsed = collapsed && (viewModel as AnyObject) === object?.collapse?.model
}
// connect specific cell delegates

View File

@@ -9,7 +9,7 @@
import UIKit
import IGListKit
final class IssueCommentQuoteCell: IssueCommentBaseCell, ListBindable, CollapsibleCell {
final class IssueCommentQuoteCell: IssueCommentBaseCell, ListBindable {
static let borderWidth: CGFloat = 2
static func inset(quoteLevel: Int) -> UIEdgeInsets {
@@ -23,7 +23,6 @@ final class IssueCommentQuoteCell: IssueCommentBaseCell, ListBindable, Collapsib
let textView = AttributedStringView()
private var borders = [UIView]()
private let overlay = CreateCollapsibleOverlay()
override init(frame: CGRect) {
super.init(frame: frame)
@@ -37,7 +36,6 @@ final class IssueCommentQuoteCell: IssueCommentBaseCell, ListBindable, Collapsib
override func layoutSubviews() {
super.layoutSubviews()
LayoutCollapsible(layer: overlay, view: contentView)
textView.reposition(width: contentView.bounds.width)
for (i, border) in borders.enumerated() {
border.frame = CGRect(
@@ -71,10 +69,4 @@ final class IssueCommentQuoteCell: IssueCommentBaseCell, ListBindable, Collapsib
setNeedsLayout()
}
// MARK: CollapsibleCell
func setCollapse(visible: Bool) {
overlay.isHidden = !visible
}
}

View File

@@ -10,10 +10,9 @@ import UIKit
import SnapKit
import IGListKit
final class IssueCommentSummaryCell: IssueCommentBaseCell, ListBindable, CollapsibleCell {
final class IssueCommentSummaryCell: IssueCommentBaseCell, ListBindable {
let label = UILabel()
let overlay = CreateCollapsibleOverlay()
override init(frame: CGRect) {
super.init(frame: frame)
@@ -31,11 +30,6 @@ final class IssueCommentSummaryCell: IssueCommentBaseCell, ListBindable, Collaps
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
LayoutCollapsible(layer: overlay, view: contentView)
}
// MARK: ListBindable
func bindViewModel(_ viewModel: Any) {
@@ -43,10 +37,4 @@ final class IssueCommentSummaryCell: IssueCommentBaseCell, ListBindable, Collaps
label.text = "\(viewModel.summary)"
}
// MARK: CollapsibleCell
func setCollapse(visible: Bool) {
overlay.isHidden = !visible
}
}

View File

@@ -9,7 +9,7 @@
import UIKit
import IGListKit
final class IssueCommentTextCell: IssueCommentBaseCell, ListBindable, CollapsibleCell {
final class IssueCommentTextCell: IssueCommentBaseCell, ListBindable {
static let inset = UIEdgeInsets(
top: 0,
@@ -19,7 +19,6 @@ final class IssueCommentTextCell: IssueCommentBaseCell, ListBindable, Collapsibl
)
let textView = AttributedStringView()
let overlay = CreateCollapsibleOverlay()
override init(frame: CGRect) {
super.init(frame: frame)
@@ -35,7 +34,6 @@ final class IssueCommentTextCell: IssueCommentBaseCell, ListBindable, Collapsibl
override func layoutSubviews() {
super.layoutSubviews()
LayoutCollapsible(layer: overlay, view: contentView)
textView.reposition(width: contentView.bounds.width)
}
@@ -55,10 +53,4 @@ final class IssueCommentTextCell: IssueCommentBaseCell, ListBindable, Collapsibl
textView.configureAndSizeToFit(text: viewModel, width: contentView.bounds.width)
}
// MARK: CollapsibleCell
func setCollapse(visible: Bool) {
overlay.isHidden = !visible
}
}

View File

@@ -77,7 +77,6 @@
292FA33F1FBCE4D000BBB0BB /* GithubClient+Milestones.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292FA33E1FBCE4D000BBB0BB /* GithubClient+Milestones.swift */; };
292FCAF61EDFCC510026635E /* IssueCommentCodeBlockCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292FCACA1EDFCC510026635E /* IssueCommentCodeBlockCell.swift */; };
292FCAF71EDFCC510026635E /* IssueCommentCodeBlockModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292FCACB1EDFCC510026635E /* IssueCommentCodeBlockModel.swift */; };
292FCAF81EDFCC510026635E /* CollapsibleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292FCACC1EDFCC510026635E /* CollapsibleCell.swift */; };
292FCAF91EDFCC510026635E /* IssueCommentDetailCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292FCACE1EDFCC510026635E /* IssueCommentDetailCell.swift */; };
292FCAFA1EDFCC510026635E /* IssueCommentDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292FCACF1EDFCC510026635E /* IssueCommentDetailsViewModel.swift */; };
292FCAFB1EDFCC510026635E /* IssueCommentImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 292FCAD11EDFCC510026635E /* IssueCommentImageCell.swift */; };
@@ -504,7 +503,6 @@
292FA33E1FBCE4D000BBB0BB /* GithubClient+Milestones.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GithubClient+Milestones.swift"; sourceTree = "<group>"; };
292FCACA1EDFCC510026635E /* IssueCommentCodeBlockCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueCommentCodeBlockCell.swift; sourceTree = "<group>"; };
292FCACB1EDFCC510026635E /* IssueCommentCodeBlockModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueCommentCodeBlockModel.swift; sourceTree = "<group>"; };
292FCACC1EDFCC510026635E /* CollapsibleCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollapsibleCell.swift; sourceTree = "<group>"; };
292FCACE1EDFCC510026635E /* IssueCommentDetailCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueCommentDetailCell.swift; sourceTree = "<group>"; };
292FCACF1EDFCC510026635E /* IssueCommentDetailsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueCommentDetailsViewModel.swift; sourceTree = "<group>"; };
292FCAD11EDFCC510026635E /* IssueCommentImageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IssueCommentImageCell.swift; sourceTree = "<group>"; };
@@ -1021,7 +1019,6 @@
isa = PBXGroup;
children = (
292FCAC91EDFCC510026635E /* CodeBlock */,
292FCACC1EDFCC510026635E /* CollapsibleCell.swift */,
292FCACD1EDFCC510026635E /* Details */,
2949674A1EF9714900B1CF1A /* Hr */,
2949674F1EFC1E9200B1CF1A /* Html */,
@@ -2270,7 +2267,6 @@
29CEA5CD1F84DB1B009827DB /* BaseListViewController.swift in Sources */,
98835BD41F1A17EE005BA24F /* Bundle+Version.swift in Sources */,
29316DB51ECC7DEB007CAE3F /* ButtonCell.swift in Sources */,
292FCAF81EDFCC510026635E /* CollapsibleCell.swift in Sources */,
295F52AD1EF1BE83000B53CF /* CommentModelsFromMarkdown.swift in Sources */,
D8BAD0661FDF224600C41071 /* WrappingStaticSpacingFlowLayout.swift in Sources */,
2919295A1F3FB1D20012067B /* Content.swift in Sources */,