-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add swipe actions
- Loading branch information
Showing
29 changed files
with
3,250 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
28
External/SwipeCellKit/SwipeAccessibilityCustomAction.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.