This commit is contained in:
DDIsFriend
2023-08-18 17:28:57 +08:00
commit f0e8a1709d
4282 changed files with 192396 additions and 0 deletions

View File

@@ -0,0 +1,171 @@
//
// EKAttributes+Animation.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/21/18.
// Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
import UIKit
// A protocol that describes an animation
protocol EKAnimation {
var delay: TimeInterval { get set }
var duration: TimeInterval { get set }
var spring: EKAttributes.Animation.Spring? { get set }
}
// A protocol that describes a range animation
protocol EKRangeAnimation: EKAnimation {
var start: CGFloat { get set }
var end: CGFloat { get set }
}
public extension EKAttributes {
/** Describes an animation that can be performed on the entry */
struct Animation: Equatable {
/** Describes properties for a spring animation that can be performed on the entry */
public struct Spring: Equatable {
/** The dampic of the spring animation */
public var damping: CGFloat
/** The initial velocity of the spring animation */
public var initialVelocity: CGFloat
/** Initializer */
public init(damping: CGFloat, initialVelocity: CGFloat) {
self.damping = damping
self.initialVelocity = initialVelocity
}
}
/** Describes an animation with range */
public struct RangeAnimation: EKRangeAnimation, Equatable {
/** The duration of the range animation */
public var duration: TimeInterval
/** The delay of the range animation */
public var delay: TimeInterval
/** The start value of the range animation (e.g. alpha, scale) */
public var start: CGFloat
/** The end value of the range animation (e.g. alpha, scale) */
public var end: CGFloat
/** The spring of the animation */
public var spring: Spring?
/** Initializer */
public init(from start: CGFloat, to end: CGFloat, duration: TimeInterval, delay: TimeInterval = 0, spring: Spring? = nil) {
self.start = start
self.end = end
self.delay = delay
self.duration = duration
self.spring = spring
}
}
/** Describes translation animation */
public struct Translate: EKAnimation, Equatable {
/** Describes the anchor position */
public enum AnchorPosition: Equatable {
/** Top position - the entry shows from top or exits towards the top */
case top
/** Bottom position - the entry shows from bottom or exits towards the bottom */
case bottom
/** Automatic position - the entry shows and exits according to EKAttributes.Position value. If the position of the entry is top, bottom, the entry's translation anchor is top, bottom - respectively.*/
case automatic
}
/** Animation duration */
public var duration: TimeInterval
/** Animation delay */
public var delay: TimeInterval
/** To where OR from the entry is animated */
public var anchorPosition: AnchorPosition
/** Optional translation spring */
public var spring: Spring?
/** Initializer */
public init(duration: TimeInterval, anchorPosition: AnchorPosition = .automatic, delay: TimeInterval = 0, spring: Spring? = nil) {
self.anchorPosition = anchorPosition
self.duration = duration
self.delay = delay
self.spring = spring
}
}
/** Translation animation prop */
public var translate: Translate?
/** Scale animation prop */
public var scale: RangeAnimation?
/** Fade animation prop */
public var fade: RangeAnimation?
/** Does the animation contains translation */
public var containsTranslation: Bool {
return translate != nil
}
/** Does the animation contains scale */
public var containsScale: Bool {
return scale != nil
}
/** Does the animation contains fade */
public var containsFade: Bool {
return fade != nil
}
/** Does the animation contains any animation whatsoever */
public var containsAnimation: Bool {
return containsTranslation || containsScale || containsFade
}
/** Returns the maximum delay amongst all animations */
public var maxDelay: TimeInterval {
return max(translate?.delay ?? 0, max(scale?.delay ?? 0, fade?.delay ?? 0))
}
/** Returns the maximum duration amongst all animations */
public var maxDuration: TimeInterval {
return max(translate?.duration ?? 0, max(scale?.duration ?? 0, fade?.duration ?? 0))
}
/** Returns the maximum (duration+delay) amongst all animations */
public var totalDuration: TimeInterval {
return maxDelay + maxDuration
}
/** Returns the maximum (duration+delay) amongst all animations */
public static var translation: Animation {
return Animation(translate: .init(duration: 0.3))
}
/** No animation at all */
public static var none: Animation {
return Animation()
}
/** Initializer */
public init(translate: Translate? = nil, scale: RangeAnimation? = nil, fade: RangeAnimation? = nil) {
self.translate = translate
self.scale = scale
self.fade = fade
}
}
}

View File

@@ -0,0 +1,138 @@
//
// EKAttributes+BackgroundStyle.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/21/18.
// Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
import UIKit
public extension EKAttributes {
/** The background style property */
enum BackgroundStyle: Equatable {
/** Blur style for light and dark modes */
public struct BlurStyle: Equatable {
public static var extra: BlurStyle {
return BlurStyle(light: .extraLight, dark: .dark)
}
public static var standard: BlurStyle {
return BlurStyle(light: .light, dark: .dark)
}
@available(iOS 10.0, *)
public static var prominent: BlurStyle {
return BlurStyle(light: .prominent, dark: .prominent)
}
public static var dark: BlurStyle {
return BlurStyle(light: .dark, dark: .dark)
}
let light: UIBlurEffect.Style
let dark: UIBlurEffect.Style
public init(style: UIBlurEffect.Style) {
self.light = style
self.dark = style
}
public init(light: UIBlurEffect.Style, dark: UIBlurEffect.Style) {
self.light = light
self.dark = dark
}
/** Computes a proper `UIBlurEffect.Style` instance */
public func blurStyle(for traits: UITraitCollection,
mode: EKAttributes.DisplayMode) -> UIBlurEffect.Style {
switch mode {
case .inferred:
if #available(iOS 13, *) {
switch traits.userInterfaceStyle {
case .light, .unspecified:
return light
case .dark:
return dark
@unknown default:
return light
}
} else {
return light
}
case .light:
return light
case .dark:
return dark
}
}
public func blurEffect(for traits: UITraitCollection,
mode: EKAttributes.DisplayMode) -> UIBlurEffect {
return UIBlurEffect(style: blurStyle(for: traits, mode: mode))
}
}
/** Gradient background style */
public struct Gradient {
public var colors: [EKColor]
public var startPoint: CGPoint
public var endPoint: CGPoint
public init(colors: [EKColor],
startPoint: CGPoint,
endPoint: CGPoint) {
self.colors = colors
self.startPoint = startPoint
self.endPoint = endPoint
}
}
/** Visual Effect (Blurred) background style */
case visualEffect(style: BlurStyle)
/** Color background style */
case color(color: EKColor)
/** Gradient background style */
case gradient(gradient: Gradient)
/** Image background style */
case image(image: UIImage)
/** Clear background style */
case clear
/** == operator overload */
public static func == (lhs: EKAttributes.BackgroundStyle,
rhs: EKAttributes.BackgroundStyle) -> Bool {
switch (lhs, rhs) {
case (visualEffect(style: let leftStyle),
visualEffect(style: let rightStyle)):
return leftStyle == rightStyle
case (color(color: let leftColor),
color(color: let rightColor)):
return leftColor == rightColor
case (image(image: let leftImage),
image(image: let rightImage)):
return leftImage == rightImage
case (gradient(gradient: let leftGradient),
gradient(gradient: let rightGradient)):
for (leftColor, rightColor) in zip(leftGradient.colors, rightGradient.colors) {
guard leftColor == rightColor else {
return false
}
}
return leftGradient.startPoint == rightGradient.startPoint &&
leftGradient.endPoint == rightGradient.endPoint
case (clear, clear):
return true
default:
return false
}
}
}
}

View File

@@ -0,0 +1,25 @@
//
// EKAttributes+DisplayMode.swift
// SwiftEntryKit
//
// Created by Daniel on 26/07/2019.
// Copyright © 2019 CocoaPods. All rights reserved.
//
import Foundation
public extension EKAttributes {
/** Display mode for the entry */
enum DisplayMode {
/** The display mode is inferred from the current user interface style */
case inferred
/** The display mode is light */
case light
/** The display mode is dark */
case dark
}
}

View File

@@ -0,0 +1,12 @@
//
// EKAttributes+Duration.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 5/4/18.
//
import Foundation
public extension EKAttributes {
typealias DisplayDuration = TimeInterval
}

View File

@@ -0,0 +1,79 @@
//
// EKAttributes+FrameStyle.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/28/18.
//
import Foundation
import CoreGraphics
import UIKit
public extension EKAttributes {
/** Corner radius of the entry - Specifies the corners */
enum RoundCorners {
/** *None* of the corners will be round */
case none
/** *All* of the corners will be round */
case all(radius: CGFloat)
/** Only the *top* left and right corners will be round */
case top(radius: CGFloat)
/** Only the *bottom* left and right corners will be round */
case bottom(radius: CGFloat)
var hasRoundCorners: Bool {
switch self {
case .none:
return false
default:
return true
}
}
var cornerValues: (value: UIRectCorner, radius: CGFloat)? {
switch self {
case .all(radius: let radius):
return (value: .allCorners, radius: radius)
case .top(radius: let radius):
return (value: .top, radius: radius)
case .bottom(radius: let radius):
return (value: .bottom, radius: radius)
case .none:
return nil
}
}
}
/** The border around the entry */
enum Border {
/** No border */
case none
/** Border wirh color and width */
case value(color: UIColor, width: CGFloat)
var hasBorder: Bool {
switch self {
case .none:
return false
default:
return true
}
}
var borderValues: (color: UIColor, width: CGFloat)? {
switch self {
case .value(color: let color, width: let width):
return(color: color, width: width)
case .none:
return nil
}
}
}
}

View File

@@ -0,0 +1,38 @@
//
// EKAttributes+HapticFeedback.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 5/1/18.
//
import UIKit
public extension EKAttributes {
/** Notification haptic feedback type. Adds an additional sensuous layer. Read more at UINotificationFeedbackType. Available from iOS 10, but you are not required to check the iOS version before using it. It's automatically handled by the kit.
*/
enum NotificationHapticFeedback {
case success
case warning
case error
case none
@available(iOS 10.0, *)
var value: UINotificationFeedbackGenerator.FeedbackType? {
switch self {
case .success:
return .success
case .warning:
return .warning
case .error:
return .error
case .none:
return nil
}
}
var isValid: Bool {
return self != .none
}
}
}

View File

@@ -0,0 +1,41 @@
//
// EKAttributes+LifecycleActions.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 6/16/18.
// Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
import Foundation
public extension EKAttributes {
/** Contains optionally injected events that take place during the entry lifecycle */
struct LifecycleEvents {
public typealias Event = () -> Void
/** Executed before the entry appears - before the animation starts.
Might not get called in case another entry with a higher display priority is displayed.
*/
public var willAppear: Event?
/** Executed after the animation ends.
Might not get called in case another entry with a higher display priority is displayed.
*/
public var didAppear: Event?
/** Executed before the entry disappears (Before the animation starts) */
public var willDisappear: Event?
/** Executed after the entry disappears (After the animation ends) */
public var didDisappear: Event?
public init(willAppear: Event? = nil, didAppear: Event? = nil, willDisappear: Event? = nil, didDisappear: Event? = nil) {
self.willAppear = willAppear
self.didAppear = didAppear
self.willDisappear = willDisappear
self.didDisappear = didDisappear
}
}
}

View File

@@ -0,0 +1,51 @@
//
// EKAttributes+PopBehavior.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/26/18.
//
import Foundation
public extension EKAttributes {
/** Describes the entry behavior when a new entry shows (with equal or higher display-priority) */
enum PopBehavior {
/** The entry disappears promptly (Does not animates out) when a new one shows */
case overridden
/** Animate the entry out - The entry rolls out when a new one shows */
case animated(animation: Animation)
public var isOverriden: Bool {
switch self {
case .overridden:
return true
case .animated:
return false
}
}
var animation: Animation? {
switch self {
case .animated(animation: let animation):
return animation
case .overridden:
return nil
}
}
func validate() {
#if DEBUG
guard let animation = animation else { return }
guard animation == .none else { return }
print("""
SwiftEntryKit warning: cannot associate value `EKAttributes.Animation()`
with `EKAttributes.PopBehavior.animated`. This may result in undefined behavior.
Please use `PopBehavior.overridden` instead.
""")
#endif
}
}
}

View File

@@ -0,0 +1,37 @@
//
// EKAttributes+Position.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/21/18.
// Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
import Foundation
public extension EKAttributes {
/** The position of the entry. */
enum Position {
/** The entry appears at the top of the screen. */
case top
/** The entry appears at the bottom of the screen. */
case bottom
/** The entry appears at the center of the screen. */
case center
public var isTop: Bool {
return self == .top
}
public var isCenter: Bool {
return self == .center
}
public var isBottom: Bool {
return self == .bottom
}
}
}

View File

@@ -0,0 +1,196 @@
//
// EKAttributes+Frame.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/21/18.
// Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
import UIKit
public extension EKAttributes {
/** Describes the frame of the entry. It's limitations, width and offset from the anchor (top / bottom of the screen) */
struct PositionConstraints {
/** Describes safe area relation */
public enum SafeArea {
/** Entry overrides safe area */
case overridden
/** The entry shows outs. But can optionally be colored */
case empty(fillSafeArea: Bool)
public var isOverridden: Bool {
switch self {
case .overridden:
return true
default:
return false
}
}
}
/** Describes an edge constraint of the entry */
public enum Edge {
/** Ratio constraint to screen edge */
case ratio(value: CGFloat)
/** Offset from each edge of the screen */
case offset(value: CGFloat)
/** Constant edge length */
case constant(value: CGFloat)
/** Unspecified edge length */
case intrinsic
/** Edge totally filled */
public static var fill: Edge {
return .offset(value: 0)
}
}
/** Describes the size of the entry */
public struct Size {
/** Describes a width constraint */
public var width: Edge
/** Describes a height constraint */
public var height: Edge
/** Initializer */
public init(width: Edge, height: Edge) {
self.width = width
self.height = height
}
/** The content's size. Entry's content view must have tight constraints */
public static var intrinsic: Size {
return Size(width: .intrinsic, height: .intrinsic)
}
/** The content's size. Entry's content view must have tight constraints */
public static var sizeToWidth: Size {
return Size(width: .offset(value: 0), height: .intrinsic)
}
/** Screen size, without horizontal or vertical offset */
public static var screen: Size {
return Size(width: .fill, height: .fill)
}
}
/** The relation to the keyboard's top and the screen's top while it is opened */
public enum KeyboardRelation {
/** Describes the offset when the keyboard is opened */
public struct Offset {
/** Describes top keyboard offset to the entry's bottom */
public var bottom: CGFloat
/** Describes top screen offset to the entry's top, useful to prevent the entry from exceeding the screen top bounds */
public var screenEdgeResistance: CGFloat?
public init(bottom: CGFloat = 0, screenEdgeResistance: CGFloat? = nil) {
self.bottom = bottom
self.screenEdgeResistance = screenEdgeResistance
}
/** None offset */
public static var none: Offset {
return Offset()
}
}
/** Bind the entry's bottom to the keyboard's top with an offset.
Additionally, the top edge of the screen can have a resistance offset which the entry isn't able to cross.
The resistance is mostly used when the device orientation changes and the entry's frame crosses the screen bounds.
Current isn't supported with center entry position.*/
case bind(offset: Offset)
/** Entry is unbound to the keyboard. It's location doesn't change. */
case unbind
/** Returns true if the entry is bound to the keyboard */
public var isBound: Bool {
switch self {
case .bind(offset: _):
return true
case .unbind:
return false
}
}
}
/** Rotation related position constraints */
public struct Rotation {
/** Attributes of supported interface orientations */
public enum SupportedInterfaceOrientation {
/** Uses standard supported interface orientation (target specification in general settings) */
case standard
/** Supports all orinetations */
case all
}
/** Autorotate the entry along with the device orientation */
public var isEnabled = true
/** The screen autorotates with accordance to this option */
public var supportedInterfaceOrientations = SupportedInterfaceOrientation.standard
public init() {}
}
/** The rotation attributes of the entry */
public var rotation = Rotation()
/** The entry can be bound to keyboard in case of appearance */
public var keyboardRelation = KeyboardRelation.unbind
/** The size of the entry */
public var size: Size
/** The maximum size of the entry */
public var maxSize: Size
/** The vertical offset from the top or bottom anchor */
public var verticalOffset: CGFloat
/** Can be used to display the content outside the safe area margins such as on the notch of the iPhone X or the status bar itself. */
public var safeArea = SafeArea.empty(fillSafeArea: false)
public var hasVerticalOffset: Bool {
return verticalOffset > 0
}
/** Returns a floating entry (float-like) */
public static var float: PositionConstraints {
return PositionConstraints(verticalOffset: 10, size: .init(width: .offset(value: 20), height: .intrinsic))
}
/** A full width entry (toast-like) */
public static var fullWidth: PositionConstraints {
return PositionConstraints(verticalOffset: 0, size: .sizeToWidth)
}
/** A full screen entry - fills the entire screen, modal-like */
public static var fullScreen: PositionConstraints {
return PositionConstraints(verticalOffset: 0, size: .screen)
}
/** Initialize with default parameters */
public init(verticalOffset: CGFloat = 0, size: Size = .sizeToWidth, maxSize: Size = .intrinsic) {
self.verticalOffset = verticalOffset
self.size = size
self.maxSize = maxSize
}
}
}

View File

@@ -0,0 +1,142 @@
//
// EKAttributes+Precedence.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/29/18.
//
import Foundation
fileprivate extension Int {
var isValidDisplayPriority: Bool {
return self >= EKAttributes.Precedence.Priority.minRawValue && self <= EKAttributes.Precedence.Priority.maxRawValue
}
}
public extension EKAttributes {
/**
Describes the manner on which the entry is pushed and displayed.
See the various values of more explanation.
*/
enum Precedence {
/**
The display priority of the entry - Determines whether is can be overriden by other entries.
Must be in range [0...1000]
*/
public struct Priority: Hashable, Equatable, RawRepresentable, Comparable {
public var rawValue: Int
public var hashValue: Int {
return rawValue
}
public init(_ rawValue: Int) {
assert(rawValue.isValidDisplayPriority, "Display Priority must be in range [\(Priority.minRawValue)...\(Priority.maxRawValue)]")
self.rawValue = rawValue
}
public init(rawValue: Int) {
assert(rawValue.isValidDisplayPriority, "Display Priority must be in range [\(Priority.minRawValue)...\(Priority.maxRawValue)]")
self.rawValue = rawValue
}
public static func == (lhs: Priority, rhs: Priority) -> Bool {
return lhs.rawValue == rhs.rawValue
}
public static func < (lhs: Priority, rhs: Priority) -> Bool {
return lhs.rawValue < rhs.rawValue
}
}
/**
Describes the queueing heoristic of entries.
*/
public enum QueueingHeuristic {
/** Determines the heuristic which the entry-queue is based on */
public static var value = QueueingHeuristic.priority
/** Chronological - FIFO */
case chronological
/** Ordered by priority */
case priority
/** Returns the caching heuristics mechanism that determines the priority in queue */
var heuristic: EntryCachingHeuristic {
switch self {
case .chronological:
return EKEntryChronologicalQueue()
case .priority:
return EKEntryPriorityQueue()
}
}
}
/**
Describes an *overriding* behavior for a new entry.
- In case no previous entry is currently presented, display the new entry.
- In case there is an entry that is currently presented - override it using the new entry. Also optionally drop all previously enqueued entries.
*/
case override(priority: Priority, dropEnqueuedEntries: Bool)
/**
Describes a FIFO behavior for an entry presentation.
- In case no previous entry is currently presented, display the new entry.
- In case there is an entry that is currently presented - enqueue the new entry, an present it just after the previous one is dismissed.
*/
case enqueue(priority: Priority)
var isEnqueue: Bool {
switch self {
case .enqueue:
return true
default:
return false
}
}
/** Setter / Getter for the display priority */
public var priority: Priority {
set {
switch self {
case .enqueue(priority: _):
self = .enqueue(priority: newValue)
case .override(priority: _, dropEnqueuedEntries: let dropEnqueuedEntries):
self = .override(priority: newValue, dropEnqueuedEntries: dropEnqueuedEntries)
}
}
get {
switch self {
case .enqueue(priority: let priority):
return priority
case .override(priority: let priority, dropEnqueuedEntries: _):
return priority
}
}
}
}
}
/** High priority entries can be overriden by other equal or higher priority entries only.
Entries are ignored as a higher priority entry is being displayed.
High priority entry overrides any other entry including another equal priority one.
You can you on of the values (.max, high, normal, low, min) and also set your own values. */
public extension EKAttributes.Precedence.Priority {
static let maxRawValue = 1000
static let highRawValue = 750
static let normalRawValue = 500
static let lowRawValue = 250
static let minRawValue = 0
/** Max - the highest possible priority of an entry. Can override only entries with *max* priority */
static let max = EKAttributes.Precedence.Priority(rawValue: maxRawValue)
static let high = EKAttributes.Precedence.Priority(rawValue: highRawValue)
static let normal = EKAttributes.Precedence.Priority(rawValue: normalRawValue)
static let low = EKAttributes.Precedence.Priority(rawValue: lowRawValue)
static let min = EKAttributes.Precedence.Priority(rawValue: minRawValue)
}

View File

@@ -0,0 +1,97 @@
//
// EKAttributes+Presets.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/23/18.
//
import Foundation
public extension EKAttributes {
/** Default attributes - Can be mutated according to the hosting application theme */
static var `default` = EKAttributes()
/** Toast preset - The frame fills margins and safe area is filled with background view */
static var toast: EKAttributes {
var attributes = EKAttributes()
attributes.positionConstraints = .fullWidth
attributes.positionConstraints.safeArea = .empty(fillSafeArea: true)
attributes.windowLevel = .statusBar
attributes.scroll = .edgeCrossingDisabled(swipeable: true)
attributes.popBehavior = .animated(animation: .translation)
return attributes
}
/** Float preset - The frame is margined and the safe area is left cleared */
static var float: EKAttributes {
var attributes = EKAttributes()
attributes.positionConstraints = .float
attributes.roundCorners = .all(radius: 10)
attributes.positionConstraints.safeArea = .empty(fillSafeArea: false)
attributes.windowLevel = .statusBar
return attributes
}
/** Preset for top float entry */
static var topFloat: EKAttributes {
var attributes = float
attributes.position = .top
return attributes
}
/** Preset for a bottom float entry */
static var bottomFloat: EKAttributes {
var attributes = float
attributes.position = .bottom
return attributes
}
/** Preset for a center float entry */
static var centerFloat: EKAttributes {
var attributes = float
attributes.position = .center
return attributes
}
/** Preset for a bottom toast entry */
static var bottomToast: EKAttributes {
var attributes = toast
attributes.position = .bottom
return attributes
}
/** Preset for a top toast entry */
static var topToast: EKAttributes {
var attributes = toast
attributes.position = .top
return attributes
}
/** Preset for a top note entry */
static var topNote: EKAttributes {
var attributes = topToast
attributes.scroll = .disabled
attributes.windowLevel = .normal
attributes.entryInteraction = .absorbTouches
return attributes
}
/** Preset for a bottom note entry */
static var bottomNote: EKAttributes {
var attributes = bottomToast
attributes.scroll = .disabled
attributes.windowLevel = .normal
attributes.entryInteraction = .absorbTouches
return attributes
}
/** Preset for a status bar entry - appears on top of the status bar */
static var statusBar: EKAttributes {
var attributes = topToast
attributes.windowLevel = .statusBar
attributes.entryInteraction = .absorbTouches
attributes.positionConstraints.safeArea = .overridden
return attributes
}
}

View File

@@ -0,0 +1,75 @@
//
// EKAttributes+Scroll.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/30/18.
//
import Foundation
import CoreGraphics
public extension EKAttributes {
/** Describes the event of scroll user interaction */
enum Scroll {
/** Describes the event when the user leaves the entry after rubber-banding it - How the entry behaves */
public struct PullbackAnimation {
public var duration: TimeInterval
public var damping: CGFloat
public var initialSpringVelocity: CGFloat
public init(duration: TimeInterval, damping: CGFloat, initialSpringVelocity: CGFloat) {
self.duration = duration
self.damping = damping
self.initialSpringVelocity = initialSpringVelocity
}
/** The entry is jolted when it's pulled back into the original position */
public static var jolt: PullbackAnimation {
return PullbackAnimation(duration: 0.5, damping: 0.3, initialSpringVelocity: 10)
}
/** The view eases out when it's pulled back into the original position */
public static var easeOut: PullbackAnimation {
return PullbackAnimation(duration: 0.3, damping: 1, initialSpringVelocity: 10)
}
}
/** The scroll ability is totally disabled */
case disabled
/** The scroll in the opposite direction to the edge is disabled */
case edgeCrossingDisabled(swipeable: Bool)
/** The scroll abiliby is enabled */
case enabled(swipeable: Bool, pullbackAnimation: PullbackAnimation)
var isEnabled: Bool {
switch self {
case .disabled:
return false
default:
return true
}
}
var isSwipeable: Bool {
switch self {
case .edgeCrossingDisabled(swipeable: let swipeable), .enabled(swipeable: let swipeable, pullbackAnimation: _):
return swipeable
default:
return false
}
}
var isEdgeCrossingEnabled: Bool {
switch self {
case .edgeCrossingDisabled:
return false
default:
return true
}
}
}
}

View File

@@ -0,0 +1,43 @@
//
// EKAttributes+Shadow.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/21/18.
// Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
import Foundation
import UIKit
public extension EKAttributes {
/** The shadow around the entry */
enum Shadow {
/** No shadow */
case none
/** Shadow with value */
case active(with: Value)
/** The shadow properties */
public struct Value {
public let radius: CGFloat
public let opacity: Float
public let color: EKColor
public let offset: CGSize
public init(color: EKColor = .black,
opacity: Float,
radius: CGFloat,
offset: CGSize = .zero) {
self.color = color
self.radius = radius
self.offset = offset
self.opacity = opacity
}
}
}
}

View File

@@ -0,0 +1,91 @@
//
// EKAttributes+StatusBar.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 5/25/18.
// Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
import UIKit
public extension EKAttributes {
/** Status bar appearance */
enum StatusBar {
/** The appearance of the status bar */
public typealias Appearance = (visible: Bool, style: UIStatusBarStyle)
/** Ignored. Status bar is ignored by entries with this apperance value*/
case ignored
/** Hidden. Doesn't apply to iPhone X */
case hidden
/** Visible with explicit dark style */
case dark
/** Visible with explicit light style */
case light
/** Keep previous state of status bar.
In case there is an already displayed entry, keep its status bar appearance.
In case the app is already displaying a status bar, keep its appearance */
case inferred
/** Returns the status bar appearance.
Note: See *Appearance* */
public var appearance: Appearance {
switch self {
case .dark:
if #available(iOS 13, *) {
return (true, .darkContent)
} else {
return (true, .default)
}
case .light:
return (true, .lightContent)
case .inferred:
return StatusBar.currentAppearance
case .hidden:
return (false, StatusBar.currentStyle)
case .ignored:
fatalError("There is no defined appearance for an ignored status bar")
}
}
/** Returns the status bar according to a given appearance */
public static func statusBar(by appearance: Appearance) -> StatusBar {
guard appearance.visible else {
return .hidden
}
switch appearance.style {
case .lightContent:
return .light
default:
return .dark
}
}
/** Returns the current appearance */
public static var currentAppearance: Appearance {
return (StatusBar.isCurrentVisible, StatusBar.currentStyle)
}
/** Returns the current status bar */
public static var currentStatusBar: StatusBar {
return statusBar(by: currentAppearance)
}
// Accessors
// TODO: Use `statusBarManager` of the window scene on iOS 13
private static var currentStyle: UIStatusBarStyle {
return UIApplication.shared.statusBarStyle
}
// TODO: Use `statusBarManager` of the window scene on iOS 13
private static var isCurrentVisible: Bool {
return !UIApplication.shared.isStatusBarHidden
}
}
}

View File

@@ -0,0 +1,84 @@
//
// EKAttributes+UserInteraction.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/21/18.
// Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
import Foundation
public extension EKAttributes {
/** Describes the user interaction events that are triggered as the user taps the entry / screen */
struct UserInteraction {
/** Code that is executed when the user taps the entry / screen */
public typealias Action = () -> ()
/** The default event that happens as the user interacts */
public enum Default {
/** Absorbs touches. The entry / screen does nothing (Swallows the touch) */
case absorbTouches
/** Touches delay the exit of the entry */
case delayExit(by: TimeInterval)
/** Taps dismiss the entry immediately */
case dismissEntry
/** Touches are forwarded to the lower window (In most cases it would be the application main window that will handle it */
case forward
}
var isResponsive: Bool {
switch defaultAction {
case .forward:
return false
default:
return true
}
}
var isDelayExit: Bool {
switch defaultAction {
case .delayExit:
return true
default:
return false
}
}
/** A default action that is executed when the entry or the screen are interacted by the user */
public var defaultAction: Default
/** Additional actions that can be customized by the user */
public var customTapActions: [Action]
public init(defaultAction: Default = .absorbTouches, customTapActions: [Action] = []) {
self.defaultAction = defaultAction
self.customTapActions = customTapActions
}
/** Dismiss action */
public static var dismiss: UserInteraction {
return UserInteraction(defaultAction: .dismissEntry)
}
/** Forward action */
public static var forward: UserInteraction {
return UserInteraction(defaultAction: .forward)
}
/** Absorb touches action */
public static var absorbTouches: UserInteraction {
return UserInteraction(defaultAction: .absorbTouches)
}
/** Delay exit action */
public static func delayExit(by delay: TimeInterval) -> UserInteraction {
return UserInteraction(defaultAction: .delayExit(by: delay))
}
}
}

View File

@@ -0,0 +1,30 @@
//
// EKAttributes+Validations.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 5/18/18.
//
import Foundation
extension EKAttributes {
private static var minDisplayDuration: DisplayDuration {
return 0
}
var validateDisplayDuration: Bool {
guard displayDuration >= EKAttributes.minDisplayDuration else {
return false
}
return true
}
var validateWindowLevel: Bool {
return windowLevel.value >= .normal
}
var isValid: Bool {
return validateDisplayDuration && validateWindowLevel
}
}

View File

@@ -0,0 +1,42 @@
//
// EKAttributes+WindowLevel.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/21/18.
// Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
import UIKit
public extension EKAttributes {
/** Describes the window level in which the entry would be displayed */
enum WindowLevel {
/** Above the alerts */
case alerts
/** Above the status bar */
case statusBar
/** Above the application window */
case normal
/** Custom level */
case custom(level: UIWindow.Level)
/** Returns the raw value - the window level itself */
public var value: UIWindow.Level {
switch self {
case .alerts:
return .alert
case .statusBar:
return .statusBar
case .normal:
return .normal
case .custom(level: let level):
return level
}
}
}
}

View File

@@ -0,0 +1,99 @@
//
// EKAttributes.swift
// SwiftEntryKit
//
// Created by Daniel Huri on 4/19/18.
// Copyright (c) 2018 huri000@gmail.com. All rights reserved.
//
import Foundation
import UIKit
public struct EKAttributes {
// MARK: Identification
/**
A settable **optional** name that matches the entry-attributes.
- Nameless entries cannot be inquired using *SwiftEntryKit.isCurrentlyDisplaying(entryNamed: _) -> Bool*
*/
public var name: String?
// MARK: Display Attributes
/** Entry presentation window level */
public var windowLevel = WindowLevel.statusBar
/** The position of the entry inside the screen */
public var position = Position.top
/** The display manner of the entry. */
public var precedence = Precedence.override(priority: .normal, dropEnqueuedEntries: false)
/** Describes how long the entry is displayed before it is dismissed */
public var displayDuration: DisplayDuration = 2 // Use .infinity for infinite duration
/** The frame attributes of the entry */
public var positionConstraints = PositionConstraints()
// MARK: User Interaction Attributes
/** Describes what happens when the user interacts the screen,
forwards the touch to the application window by default */
public var screenInteraction = UserInteraction.forward
/** Describes what happens when the user interacts the entry,
dismisses the content by default */
public var entryInteraction = UserInteraction.dismiss
/** Describes the scrolling behaviour of the entry.
The entry can be swiped out and in with an ability to spring back with a jolt */
public var scroll = Scroll.enabled(swipeable: true, pullbackAnimation: .jolt)
/** Generate haptic feedback once the entry is displayed */
public var hapticFeedbackType = NotificationHapticFeedback.none
/** Describes the actions that take place when the entry appears or is being dismissed */
public var lifecycleEvents = LifecycleEvents()
// MARK: Theme & Style Attributes
/** The display mode of the entry */
public var displayMode = DisplayMode.inferred
/** Describes the entry's background appearance while it shows */
public var entryBackground = BackgroundStyle.clear
/** Describes the background appearance while the entry shows */
public var screenBackground = BackgroundStyle.clear
/** The shadow around the entry */
public var shadow = Shadow.none
/** The corner attributes */
public var roundCorners = RoundCorners.none
/** The border around the entry */
public var border = Border.none
/** Preferred status bar style while the entry shows */
public var statusBar = StatusBar.inferred
// MARK: Animation Attributes
/** Describes how the entry animates in */
public var entranceAnimation = Animation.translation
/** Describes how the entry animates out */
public var exitAnimation = Animation.translation
/** Describes the previous entry behaviour when a new entry with higher display-priority shows */
public var popBehavior = PopBehavior.animated(animation: .translation) {
didSet {
popBehavior.validate()
}
}
/** Init with default attributes */
public init() {}
}