Skip to content

Commit

Permalink
Show custom swipe action
Browse files Browse the repository at this point in the history
Add swipe actions
  • Loading branch information
hartlco committed May 4, 2020
1 parent 35404d5 commit 8fc56fd
Show file tree
Hide file tree
Showing 29 changed files with 3,250 additions and 25 deletions.
17 changes: 14 additions & 3 deletions CHANGELOG.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
upcoming:
version: 2.2
version: 2.2.1
date: TBD
dev:
-
user_facing:
- Add Swipe actions for reply/conversation
- Add conversation item to context menu
- Don’t dismiss keyboard when entering micropub information
- Accept micropub access token without requiring the micropub endpoint to be filled first

releases:
version: 2.2
date: 2020-04-30
dev:
- Introduce CHANGELOG.yml
- Integrate danger.js for pull request checks
user_facing:
- Reset Application notification badge after opening the App

releases:
- Add actions menu to cells
- Add indieauth support
81 changes: 81 additions & 0 deletions External/SwipeCellKit/Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//
// Extensions.swift
//
// Created by Jeremy Koch
// Copyright © 2017 Jeremy Koch. All rights reserved.
//

import UIKit

extension UITableView {
var swipeCells: [SwipeTableViewCell] {
return visibleCells.compactMap({ $0 as? SwipeTableViewCell })
}

func hideSwipeCell() {
swipeCells.forEach { $0.hideSwipe(animated: true) }
}
}

extension UICollectionView {
var swipeCells: [SwipeCollectionViewCell] {
return visibleCells.compactMap({ $0 as? SwipeCollectionViewCell })
}

func hideSwipeCell() {
swipeCells.forEach { $0.hideSwipe(animated: true) }
}

func setGestureEnabled(_ enabled: Bool) {
gestureRecognizers?.forEach {
guard $0 != panGestureRecognizer else { return }

$0.isEnabled = enabled
}
}
}

extension UIScrollView {
var swipeables: [Swipeable] {
switch self {
case let tableView as UITableView:
return tableView.swipeCells
case let collectionView as UICollectionView:
return collectionView.swipeCells
default:
return []
}
}

func hideSwipeables() {
switch self {
case let tableView as UITableView:
tableView.hideSwipeCell()
case let collectionView as UICollectionView:
collectionView.hideSwipeCell()
default:
return
}
}
}

extension UIPanGestureRecognizer {
func elasticTranslation(in view: UIView?, withLimit limit: CGSize, fromOriginalCenter center: CGPoint, applyingRatio ratio: CGFloat = 0.20) -> CGPoint {
let translation = self.translation(in: view)

guard let sourceView = self.view else {
return translation
}

let updatedCenter = CGPoint(x: center.x + translation.x, y: center.y + translation.y)
let distanceFromCenter = CGSize(width: abs(updatedCenter.x - sourceView.bounds.midX),
height: abs(updatedCenter.y - sourceView.bounds.midY))

let inverseRatio = 1.0 - ratio
let scale: (x: CGFloat, y: CGFloat) = (updatedCenter.x < sourceView.bounds.midX ? -1 : 1, updatedCenter.y < sourceView.bounds.midY ? -1 : 1)
let x = updatedCenter.x - (distanceFromCenter.width > limit.width ? inverseRatio * (distanceFromCenter.width - limit.width) * scale.x : 0)
let y = updatedCenter.y - (distanceFromCenter.height > limit.height ? inverseRatio * (distanceFromCenter.height - limit.height) * scale.y : 0)

return CGPoint(x: x, y: y)
}
}
28 changes: 28 additions & 0 deletions External/SwipeCellKit/SwipeAccessibilityCustomAction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// SwipeAccessibilityCustomAction.swift
// SwipeCellKit
//
// Created by Jeremy Koch
// Copyright © 2017 Jeremy Koch. All rights reserved.
//

import UIKit

class SwipeAccessibilityCustomAction: UIAccessibilityCustomAction {
let action: SwipeAction
let indexPath: IndexPath

init?(action: SwipeAction, indexPath: IndexPath, target: Any, selector: Selector) {

self.action = action
self.indexPath = indexPath

let name = action.accessibilityLabel ?? action.title ?? action.image?.accessibilityIdentifier ?? nil

if let name = name {
super.init(name: name, target: target, selector: selector)
} else {
return nil
}
}
}
131 changes: 131 additions & 0 deletions External/SwipeCellKit/SwipeAction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//
// SwipeAction.swift
//
// Created by Jeremy Koch
// Copyright © 2017 Jeremy Koch. All rights reserved.
//

import UIKit

/// Constants that help define the appearance of action buttons.
public enum SwipeActionStyle: Int {
/// Apply a style that reflects standard non-destructive actions.
case `default`

/// Apply a style that reflects destructive actions.
case destructive
}

/**
The `SwipeAction` object defines a single action to present when the user swipes horizontally in a table/collection item.
This class lets you define one or more custom actions to display for a given item in your table/collection. Each instance of this class represents a single action to perform and includes the text, formatting information, and behavior for the corresponding button.
*/
public class SwipeAction: NSObject {
/// An optional unique action identifier.
public var identifier: String?

/// The title of the action button.
///
/// - note: You must specify a title or an image.
public var title: String?

/// The style applied to the action button.
public var style: SwipeActionStyle

/// The object that is notified as transitioning occurs.
public var transitionDelegate: SwipeActionTransitioning?

/// The font to use for the title of the action button.
///
/// - note: If you do not specify a font, a 15pt system font is used.
public var font: UIFont?

/// The text color of the action button.
///
/// - note: If you do not specify a color, white is used.
public var textColor: UIColor?

/// The highlighted text color of the action button.
///
/// - note: If you do not specify a color, `textColor` is used.
public var highlightedTextColor: UIColor?

/// The image used for the action button.
///
/// - note: You must specify a title or an image.
public var image: UIImage?

/// The highlighted image used for the action button.
///
/// - note: If you do not specify a highlight image, the default `image` is used for the highlighted state.
public var highlightedImage: UIImage?

/// The closure to execute when the user taps the button associated with this action.
public var handler: ((SwipeAction, IndexPath) -> Void)?

/// The background color of the action button.
///
/// - note: Use this property to specify the background color for your button. If you do not specify a value for this property, the framework assigns a default color based on the value in the style property.
public var backgroundColor: UIColor?

/// The highlighted background color of the action button.
///
/// - note: Use this property to specify the highlighted background color for your button.
public var highlightedBackgroundColor: UIColor?

/// The visual effect to apply to the action button.
///
/// - note: Assigning a visual effect object to this property adds that effect to the background of the action button.
public var backgroundEffect: UIVisualEffect?

/// A Boolean value that determines whether the actions menu is automatically hidden upon selection.
///
/// - note: When set to `true`, the actions menu is automatically hidden when the action is selected. The default value is `false`.
public var hidesWhenSelected = false

/**
Constructs a new `SwipeAction` instance.
- parameter style: The style of the action button.
- parameter title: The title of the action button.
- parameter handler: The closure to execute when the user taps the button associated with this action.
*/
public init(style: SwipeActionStyle, title: String?, handler: ((SwipeAction, IndexPath) -> Void)?) {
self.title = title
self.style = style
self.handler = handler
}

/**
Calling this method performs the configured expansion completion animation including deletion, if necessary. Calling this method more than once has no effect.
You should only call this method from the implementation of your action `handler` method.
- parameter style: The desired style for completing the expansion action.
*/
public func fulfill(with style: ExpansionFulfillmentStyle) {
completionHandler?(style)
}

// MARK: - Internal

internal var completionHandler: ((ExpansionFulfillmentStyle) -> Void)?
}

/// Describes how expansion should be resolved once the action has been fulfilled.
public enum ExpansionFulfillmentStyle {
/// Implies the item will be deleted upon action fulfillment.
case delete

/// Implies the item will be reset and the actions view hidden upon action fulfillment.
case reset
}

// MARK: - Internal

internal extension SwipeAction {
var hasBackgroundColor: Bool {
return backgroundColor != .clear && backgroundEffect == nil
}
}
108 changes: 108 additions & 0 deletions External/SwipeCellKit/SwipeActionButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//
// SwipeActionButton.swift
//
// Created by Jeremy Koch.
// Copyright © 2017 Jeremy Koch. All rights reserved.
//

import UIKit

class SwipeActionButton: UIButton {
var spacing: CGFloat = 8
var shouldHighlight = true
var highlightedBackgroundColor: UIColor?

var maximumImageHeight: CGFloat = 0
var verticalAlignment: SwipeVerticalAlignment = .centerFirstBaseline


var currentSpacing: CGFloat {
return (currentTitle?.isEmpty == false && imageHeight > 0) ? spacing : 0
}

var alignmentRect: CGRect {
let contentRect = self.contentRect(forBounds: bounds)
let titleHeight = titleBoundingRect(with: verticalAlignment == .centerFirstBaseline ? CGRect.infinite.size : contentRect.size).integral.height
let totalHeight = imageHeight + titleHeight + currentSpacing

return contentRect.center(size: CGSize(width: contentRect.width, height: totalHeight))
}

private var imageHeight: CGFloat {
get {
return currentImage == nil ? 0 : maximumImageHeight
}
}

override var intrinsicContentSize: CGSize {
return CGSize(width: UIView.noIntrinsicMetric, height: contentEdgeInsets.top + alignmentRect.height + contentEdgeInsets.bottom)
}

convenience init(action: SwipeAction) {
self.init(frame: .zero)

contentHorizontalAlignment = .center

tintColor = action.textColor ?? .white
let highlightedTextColor = action.highlightedTextColor ?? tintColor
highlightedBackgroundColor = action.highlightedBackgroundColor ?? UIColor.black.withAlphaComponent(0.1)

titleLabel?.font = action.font ?? UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.medium)
titleLabel?.textAlignment = .center
titleLabel?.lineBreakMode = .byWordWrapping
titleLabel?.numberOfLines = 0

accessibilityLabel = action.accessibilityLabel

setTitle(action.title, for: .normal)
setTitleColor(tintColor, for: .normal)
setTitleColor(highlightedTextColor, for: .highlighted)
setImage(action.image, for: .normal)
setImage(action.highlightedImage ?? action.image, for: .highlighted)
}

override var isHighlighted: Bool {
didSet {
guard shouldHighlight else { return }

backgroundColor = isHighlighted ? highlightedBackgroundColor : .clear
}
}

func preferredWidth(maximum: CGFloat) -> CGFloat {
let width = maximum > 0 ? maximum : CGFloat.greatestFiniteMagnitude
let textWidth = titleBoundingRect(with: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)).width
let imageWidth = currentImage?.size.width ?? 0

return min(width, max(textWidth, imageWidth) + contentEdgeInsets.left + contentEdgeInsets.right)
}

func titleBoundingRect(with size: CGSize) -> CGRect {
guard let title = currentTitle, let font = titleLabel?.font else { return .zero }

return title.boundingRect(with: size,
options: [.usesLineFragmentOrigin],
attributes: [NSAttributedString.Key.font: font],
context: nil).integral
}

override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
var rect = contentRect.center(size: titleBoundingRect(with: contentRect.size).size)
rect.origin.y = alignmentRect.minY + imageHeight + currentSpacing
return rect.integral
}

override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
var rect = contentRect.center(size: currentImage?.size ?? .zero)
rect.origin.y = alignmentRect.minY + (imageHeight - rect.height) / 2
return rect
}
}

extension CGRect {
func center(size: CGSize) -> CGRect {
let dx = width - size.width
let dy = height - size.height
return CGRect(x: origin.x + dx * 0.5, y: origin.y + dy * 0.5, width: size.width, height: size.height)
}
}
Loading

0 comments on commit 8fc56fd

Please sign in to comment.