SwiftEntryKit

Platform Language Version Carthage compatible Accio: Supported License

🤗 您可以在此进行捐赠。

目录

概述

SwiftEntryKit 是一个用 Swift 编写的简单而通用的内容呈现器。

特性

横幅或弹出窗口被称为 Entries(条目)。

示例项目

示例项目包含各种预设和示例,您可以根据自己的喜好使用和修改。

示例项目安装

您可以使用终端或像 Source Tree 这样的 Git 客户端。

终端用户

$ git clone https://github.com/huri000/SwiftEntryKit.git

Git 客户端 (Source Tree)

克隆 https://github.com/huri000/SwiftEntryKit.git

预设

Toasts(吐司) Notes(通知) Floats(浮动窗口) Popups(弹出窗口)
toasts_example notes_example floats_example popup_example
Alerts(警报) Forms(表单) Rating(评分) 更多...
alert_example form_example rating_example custom_example

Playground

名词:人们可以玩橄榄球的地方 🏈

该示例应用程序包含一个 playground 屏幕,它是一个允许您自定义您喜欢的条目的界面。 playground 屏幕有一些限制(允许选择常量值),但您可以轻松地修改代码以满足您的需求。 快来查看一下!

Playground 屏幕 顶部 Toast 示例
playground_example playground-sample-1

要求

安装

CocoaPods

CocoaPods 是 Cocoa 项目的依赖管理器。 您可以使用以下命令安装它

$ gem install cocoapods

要使用 CocoaPods 将 SwiftEntryKit 集成到您的 Xcode 项目中,请在您的 Podfile 中指定它

source 'https://github.com/cocoapods/specs.git'
platform :ios, '9.0'
use_frameworks!

pod 'SwiftEntryKit', '2.0.0'

然后,运行以下命令

$ pod install

Carthage

Carthage 是一个去中心化的依赖管理器,它构建您的依赖项并为您提供二进制框架。

您可以使用 Homebrew 使用以下命令安装 Carthage

$ brew update
$ brew install carthage

要使用 Carthage 将 SwiftEntryKit 集成到您的 Xcode 项目中,请在您的 Cartfile 中指定以下内容

github "huri000/SwiftEntryKit" == 2.0.0

Accio

Accio 是一个由 SwiftPM 驱动的去中心化依赖管理器,适用于 iOS/tvOS/watchOS/macOS 项目。

您可以使用 Homebrew 使用以下命令安装 Accio

$ brew tap JamitLabs/Accio https://github.com/JamitLabs/Accio.git
$ brew install accio

要使用 Accio 将 SwiftEntryKit 集成到您的 Xcode 项目中,请在您的 Package.swift 清单中指定以下内容

.package(url: "https://github.com/huri000/SwiftEntryKit", .exact("2.0.0"))

在您想要使用的目标中指定 "SwiftEntryKit" 作为依赖项后,运行 accio install

使用

快速使用

无需设置! 每次您希望显示一个条目时,只需创建您的视图并初始化一个 EKAttributes 结构体。 另请参阅预设使用示例和示例项目。 同样如此

// Customized view
let customView = SomeCustomView()
/*
Do some customization on customView
*/

// Attributes struct that describes the display, style, user interaction and animations of customView.
var attributes = EKAttributes()
/*
Adjust preferable attributes
*/

然后,只需调用

SwiftEntryKit.display(entry: customView, using: attributes)

该工具包会将应用程序主窗口替换为 EKWindow 实例并显示该条目。

Entry 属性

EKAttributes 是条目的描述符。 每次显示一个条目时,都需要一个 EKAttributes 结构体来描述条目的呈现方式、屏幕内的位置、显示时长、其框架约束(如果需要)、其样式(圆角、边框和阴影)、用户交互事件、动画(进入/退出)等等。

同样创建可变的 EKAttributes 结构

var attributes = EKAttributes()

以下是可以在 EKAttributes 中修改的属性

Entry 名称

条目可以有名称。 当实例化 EKAttributes 结构体时,它是无名的,这意味着 name 属性为 nil。 建议为条目设置一个有意义的名称。

attributes.name = "Top Note"

稍后可以专门引用具有名称的条目,例如,您可以询问当前是否显示了特定条目

if SwiftEntryKit.isCurrentlyDisplaying(entryNamed: "Top Note") {
    /* Do your things */
}

窗口层级

Entries 可以显示在应用程序主窗口之上、状态栏之上、警报窗口之上,甚至具有自定义级别 (UIWindowLevel)。

例如,将窗口级别设置为正常,同样如此

attributes.windowLevel = .normal

这会导致条目出现在应用程序主窗口之上和状态栏之下。

windowLevel 的默认值为 .statusBar

显示位置

该条目可以显示在屏幕的顶部中心底部

例如,将显示位置设置为底部,同样如此

attributes.position = .bottom

position 的默认值为 .top

优先级

条目的优先级属性描述了条目推入的方式。 它提供了两种管理多个同时条目的呈现优先级的方法。

覆盖

如果显示优先级等于或高于当前显示的条目,则覆盖它。

使用 .max 显示优先级设置 .override 优先级的示例,同时忽略已排队的条目(使其在新条目关闭后显示)。

attributes.precedence = .override(priority: .max, dropEnqueuedEntries: false)

您可以选择性地刷新队列中的条目。

如果 dropEnqueuedEntriesfalse,则排队的条目将保留在队列中。 第一个排队的条目将在新条目弹出后立即显示。 如果 dropEnqueuedEntriestrue,则当显示新条目时,条目队列将被刷新。

入队

如果队列为空,则立即显示该条目,否则,将该条目插入队列,直到轮到它显示。

使用 .normal 显示优先级设置 .enqueue 优先级的示例

attributes.precedence = .enqueue(priority: .normal)
启发式

队列中条目优先级排序有两种可能的启发式方法

在使用 SwiftEntryKit 显示条目之前,仅执行一次以下操作,以选择最适合您的启发式方法。

EKAttributes.Precedence.QueueingHeuristic.value = .priority

或者

EKAttributes.Precedence.QueueingHeuristic.value = .chronological

EKAttributes.Precedence.QueueingHeuristic.value 的默认值为 .priority

优先级的默认值为 .override(priority: .normal, dropEnqueuedEntries: false)

显示优先级

条目的显示优先级确定它是否关闭其他条目或被它们关闭。 一个条目只能被具有相等或更高显示优先级的条目关闭。

let highPriorityAttributes = EKAttributes()
highPriorityAttributes.precedence.priority = .high

let normalPriorityAttributes = EKAttributes()
normalPriorityAttributes.precedence.priority = .normal

// Display high priority entry
SwiftEntryKit.display(entry: view1, using: highPriorityAttributes)

// Display normal priority entry (ignored!)
SwiftEntryKit.display(entry: view2, using: normalPriorityAttributes)

view2 不会被显示!

显示时长

条目的显示时长(从条目完成其进入动画的那一刻算起,直到退出动画开始)。

显示 4 秒

attributes.displayDuration = 4

无限时长显示

attributes.displayDuration = .infinity

displayDuration 的默认值为 2

位置约束

将条目紧密绑定到屏幕上下文的约束,例如:高度、宽度、最大宽度、最大高度、附加垂直偏移和安全区域相关信息。

例如

Ratio edge - 表示宽度边的比率是屏幕宽度的 0.9。

let widthConstraint = EKAttributes.PositionConstraints.Edge.ratio(value: 0.9)

Intrinsic edge - 表示所需的高度值是内容高度 - 由条目的垂直约束决定

let heightConstraint = EKAttributes.PositionConstraints.Edge.intrinsic

同样创建条目大小约束

attributes.positionConstraints.size = .init(width: widthConstraint, height: heightConstraint)

您还可以设置 attributes.positionConstraints.maxSize,以确保条目不超过预定义的限制。 这在设备方向改变时很有用。

安全区域 - 可用于覆盖安全区域或为其着色(更多示例在示例项目中)。该代码段意味着应保留安全区域插图,而不应成为条目的一部分。

attributes.positionConstraints.safeArea = .empty(fillSafeArea: false)

垂直偏移 - 可以应用于条目的附加偏移(安全区域之外)。

attributes.positionConstraints.verticalOffset = 10

自动旋转 - 条目是否随设备的方向自动旋转。 默认为 true

attributes.positionConstraints.rotation.isEnabled = false

键盘关系 - 用于在显示键盘后将条目绑定到键盘。

let offset = EKAttributes.PositionConstraints.KeyboardRelation.Offset(bottom: 10, screenEdgeResistance: 20)
let keyboardRelation = EKAttributes.PositionConstraints.KeyboardRelation.bind(offset: offset)
attributes.positionConstraints.keyboardRelation = keyboardRelation

在上面的例子中,条目的底部被调整为距离键盘顶部(在显示时)有 10pt 的偏移量。由于条目的 frame 可能会超出屏幕边界,用户可能无法看到条目的全部内容 - 我们不希望出现这种情况。因此,添加了一个额外的关联值 screenEdgeResistance,其值为 20pt。 也就是说,为了确保条目保持在屏幕范围内,并且始终对用户可见。 在设备方向为横向并且键盘弹出时,可能会出现极端情况(有关更多信息,请参见示例项目表单预设)。

用户交互

用户可以与条目和屏幕进行交互。 用户交互可以通过多种方式进行拦截

与条目的交互(任何触摸)都会将其退出延迟 3 秒

attributes.entryInteraction = .delayExit(by: 3)

点击条目/屏幕会立即将其关闭

attributes.entryInteraction = .dismiss
attributes.screenInteraction = .dismiss

点击条目会被吞噬(忽略)

attributes.entryInteraction = .absorbTouches

点击屏幕会被转发到较低级别的窗口,在大多数情况下,接收者将是应用程序窗口。 当您想要显示不引人注意的内容(如横幅和推送通知条目)时,这非常有用。

attributes.screenInteraction = .forward

传递用户点击条目时调用的其他操作

let action = {
    // Do something useful
}
attributes.entryInteraction.customTapActions.append(action)

screenInteraction 的默认值为 .forward

entryInteraction 的默认值为 .dismiss

滚动行为

描述条目在滚动时的行为,即通过滑动(swipe)手势进行关闭以及类似于 UIScrollView 的橡皮筋效果。

禁用条目上的平移(pan)和滑动(swipe)手势

attributes.scroll = .disabled

启用滑动(swipe)和拉伸(stretch),以及具有急动(jolt)效果的回弹(pullback)

attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .jolt)

启用滑动(swipe)和拉伸(stretch),以及具有缓出(ease-out)效果的回弹(pullback)

attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .easeOut)

启用滑动(swipe)但禁用拉伸(stretch)

attributes.scroll = .edgeCrossingDisabled(swipeable: true)

scroll 的默认值为 .enabled(swipeable: true, pullbackAnimation: .jolt)

触觉反馈

设备可以产生触觉反馈,从而为每个条目添加额外的感官深度。

hapticFeedbackType 的默认值为 .none

生命周期事件

可以将事件注入到条目中,以便在条目的生命周期中调用它们。

attributes.lifecycleEvents.willAppear = {
    // Executed before the entry animates inside 
}

attributes.lifecycleEvents.didAppear = {
    // Executed after the entry animates inside
}

attributes.lifecycleEvents.willDisappear = {
    // Executed before the entry animates outside
}

attributes.lifecycleEvents.didDisappear = {
    // Executed after the entry animates outside
}

显示模式

为了让您完全支持任何用户界面样式,SwiftEntryKit 引入了两种专门的类型

以下代码强制 SwiftEntryKit 在深色模式下显示条目。

attributes.displayMode = .dark

可能的值为:.light.dark.inferred。 默认值为 .inferred,这意味着条目将以当前用户界面样式显示。

背景样式

条目和屏幕可以具有各种背景样式,例如模糊、颜色、渐变甚至图像。

以下示例表示条目和屏幕都具有清晰的背景

attributes.entryBackground = .clear
attributes.screenBackground = .clear

彩色条目背景和变暗的屏幕背景

attributes.entryBackground = .color(color: .standardContent)
attributes.screenBackground = .color(color: EKColor(UIColor(white: 0.5, alpha: 0.5)))

渐变条目背景(对角线向量)

let colors: [EKColor] = ...
attributes.entryBackground = .gradient(gradient: .init(colors: colors, startPoint: .zero, endPoint: CGPoint(x: 1, y: 1)))

视觉效果条目背景

attributes.entryBackground = .visualEffect(style: .dark)

entryBackgroundscreenBackground 的默认值为 .clear

阴影

环绕条目的阴影。

在条目周围启用阴影

attributes.shadow = .active(with: .init(color: .black, opacity: 0.3, radius: 10, offset: .zero))

在条目周围禁用阴影

attributes.shadow = .none

shadow 的默认值为 .none

圆角

条目周围的圆角。

仅左上角和右上角,半径为 10

attributes.roundCorners = .top(radius: 10)

仅左下角和右下角,半径为 10

attributes.roundCorners = .bottom(radius: 10)

所有角,半径为 10

attributes.roundCorners = .all(radius: 10)

没有圆角

attributes.roundCorners = .none

roundCorners 的默认值为 .none

边框

条目周围的边框。

添加厚度为 0.5pt 的黑色边框

attributes.border = .value(color: .black, width: 0.5)

没有边框

attributes.border = .none

border 的默认值为 .none

动画

描述条目如何动画式地进入和退出屏幕。

例如,从顶部进行平移(translation),带有弹簧(spring)效果、缩放(scale)进入甚至淡入(fade in)作为单个进入动画

attributes.entranceAnimation = .init(
                 translate: .init(duration: 0.7, anchorPosition: .top, spring: .init(damping: 1, initialVelocity: 0)), 
                 scale: .init(from: 0.6, to: 1, duration: 0.7), 
                 fade: .init(from: 0.8, to: 1, duration: 0.3))

entranceAnimationexitAnimation 的默认值为 .translation - 条目分别以 0.3 秒的持续时间平移进出。

弹出行为

描述当条目被弹出(被具有相等/更高显示优先级的条目关闭)时的行为。

条目正在以动画方式弹出

attributes.popBehavior = .animated(animation: .init(translate: .init(duration: 0.2)))

条目被覆盖(迅速消失)

attributes.popBehavior = .overridden

popBehavior 的默认值为 .animated(animation: .translation) - 它以 0.3 秒的持续时间平移出去。

状态栏

在显示条目期间,可以修改状态栏外观。 SwiftEntryKit 同时支持基于视图控制器的状态栏外观和手动设置。

设置状态栏样式非常简单 -

状态栏变为可见并获得浅色样式

attributes.statusBar = .light

状态栏变为隐藏

attributes.statusBar = .hidden

状态栏外观是从先前的上下文推断出来的(不会更改)

attributes.statusBar = .inferred

如果已经存在一个显示优先级较低/相等的条目,则状态栏将更改其样式。 删除条目后,状态栏将恢复其初始样式。

statusBar 的默认值为 .inferred

EKAttributes 的接口如下

public struct EKAttributes

    // Identification
    public var name: String?

    // Display
    public var windowLevel: WindowLevel
    public var position: Position
    public var precedence: Precedence
    public var displayDuration: DisplayDuration
    public var positionConstraints: PositionConstraints

    // User Interaction
    public var screenInteraction: UserInteraction
    public var entryInteraction: UserInteraction
    public var scroll: Scroll
    public var hapticFeedbackType: NotificationHapticFeedback
    public var lifecycleEvents: LifecycleEvents

    // Theme & Style
    public var displayMode = DisplayMode.inferred
    public var entryBackground: BackgroundStyle
    public var screenBackground: BackgroundStyle
    public var shadow: Shadow
    public var roundCorners: RoundCorners
    public var border: Border
    public var statusBar: StatusBar
    
    // Animations
    public var entranceAnimation: Animation
    public var exitAnimation: Animation
    public var popBehavior: PopBehavior
}

预设使用示例

您可以使用 SwiftEntryKit 附带的预设之一,只需执行以下 4 个简单步骤

  1. 创建您的 EKAttributes 结构并设置您喜欢的属性。
  2. 创建 EKNotificationMessage 结构(内容)并设置内容。
  3. 创建 EKNotificationMessageView(视图)并将 EKNotificationMessage 结构注入其中。
  4. 使用 SwiftEntryKit 类方法显示条目。

EKNotificationMessageView 预设示例

// Generate top floating entry and set some properties
var attributes = EKAttributes.topFloat
attributes.entryBackground = .gradient(gradient: .init(colors: [EKColor(.red), EKColor(.green)], startPoint: .zero, endPoint: CGPoint(x: 1, y: 1)))
attributes.popBehavior = .animated(animation: .init(translate: .init(duration: 0.3), scale: .init(from: 1, to: 0.7, duration: 0.7)))
attributes.shadow = .active(with: .init(color: .black, opacity: 0.5, radius: 10, offset: .zero))
attributes.statusBar = .dark
attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .jolt)
attributes.positionConstraints.maxSize = .init(width: .constant(value: UIScreen.main.minEdge), height: .intrinsic)

let title = EKProperty.LabelContent(text: titleText, style: .init(font: titleFont, color: textColor))
let description = EKProperty.LabelContent(text: descText, style: .init(font: descFont, color: textColor))
let image = EKProperty.ImageContent(image: UIImage(named: imageName)!, size: CGSize(width: 35, height: 35))
let simpleMessage = EKSimpleMessage(image: image, title: title, description: description)
let notificationMessage = EKNotificationMessage(simpleMessage: simpleMessage)

let contentView = EKNotificationMessageView(with: notificationMessage)
SwiftEntryKit.display(entry: contentView, using: attributes)

自定义视图使用示例

// Create a basic toast that appears at the top
var attributes = EKAttributes.topToast

// Set its background to white
attributes.entryBackground = .color(color: .white)

// Animate in and out using default translation
attributes.entranceAnimation = .translation
attributes.exitAnimation = .translation

let customView = UIView()
/*
... Customize the view as you like ...
*/

// Display the view with the configuration
SwiftEntryKit.display(entry: customView, using: attributes)

显示一个视图控制器

从 0.4.0 版本开始,也支持视图控制器。

SwiftEntryKit.display(entry: customViewController, using: attributes)

备用回滚窗口

默认情况下,在 SwiftEntryKit 完成条目的显示后,应用程序委托持有的窗口会再次成为 key。 可以使用 rollbackWindow 参数更改此行为。

SwiftEntryKit.display(entry: view, using: attributes, rollbackWindow: .custom(window: alternativeWindow))

条目被关闭后,给定的窗口 alternativeWindow 将成为 key,而不是应用程序委托持有的窗口。

关闭一个 Entry

您可以通过简单地在 SwiftEntryKit 类中调用 dismiss 来关闭当前显示的条目,如下所示

SwiftEntryKit.dismiss()

或者

SwiftEntryKit.dismiss(.displayed)

这将使用其 exitAnimation 属性以动画方式关闭条目,并在完成后,窗口也将被删除。

您也可以关闭当前显示的条目并刷新队列,如下所示

SwiftEntryKit.dismiss(.all)

仅刷新队列,将任何当前显示的条目留给其自然生命周期

SwiftEntryKit.dismiss(.queue)

按名称关闭特定条目 - 无论是当前显示的还是排队的。 所有具有给定名称的条目都会被关闭。

SwiftEntryKit.dismiss(.specific(entryName: "Entry Name"))

关闭任何显示优先级低于或等于 .normal 的条目。

SwiftEntryKit.dismiss(.prioritizedLowerOrEqualTo(priority: .normal))

使用完成处理程序

注入一个尾随闭包,以便在条目关闭后执行。

SwiftEntryKit.dismiss {
    // Executed right after the entry has been dismissed
}

当前是否正在显示

查询当前是否正在显示条目

if SwiftEntryKit.isCurrentlyDisplaying {
    /* Do your things */
}

使用 EKAttributes 内的 name 属性查询是否正在显示特定条目。

if SwiftEntryKit.isCurrentlyDisplaying(entryNamed: "Top Note") {
/* Do your things */
}

队列包含

查询条目队列是否不为空

if SwiftEntryKit.isQueueEmpty {
    /* Do your things */
}

查询条目队列是否包含具有名称的条目

if SwiftEntryKit.queueContains(entryNamed: "Custom-Name") {
    /* Do your things */
}

滑动和橡皮筋效果

条目可以垂直平移(可以使用 scroll 属性启用此功能)。 因此,使用类似滑动的动作关闭条目是很自然的。

启用滑动(swipe)手势。 当滑动(swipe)手势失败(未通过速度阈值)时,将其缓回。

attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .easeOut)

启用滑动(swipe)手势。 当滑动(swipe)手势失败时,将其以急动(jolt)效果扔回去。

attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .jolt)

PullbackAnimation 值(duration、damping 和 initialSpringVelocity)也可以自定义。

滑动(Swipe) 急动(Jolt)
swipe_example band_example

处理安全区域

EKAttributes.PositionConstraints.SafeArea 可用于覆盖条目内容的的安全区域,或者用背景颜色填充安全区域(如 Toasts 所做的那样),甚至将安全区域留空(如 Floats 所做的那样)。

SwiftEntryKit 支持 iOS 11.x.y,并向后兼容 iOS 9.x.y,因此在早期 iOS 版本中,状态栏区域的处理方式与安全区域相同。

处理方向改变

SwiftEntryKit 会识别方向改变,并将条目的布局调整为这些改变。 因此,如果您希望限制条目的宽度,可以通过为其指定最大值来实现,如下所示

var attributes = EKAttributes.topFloat

// Give the entry the width of the screen minus 20pts from each side, the height is decided by the content's contraint's
attributes.positionConstraints.size = .init(width: .offset(value: 20), height: .intrinsic)

// Give the entry maximum width of the screen minimum edge - thus the entry won't grow much when the device orientation changes from portrait to landscape mode.
let edgeWidth = min(UIScreen.main.bounds.width, UIScreen.main.bounds.height)
attributes.positionConstraints.maxSize = .init(width: .constant(value: edgeWidth), height: .intrinsic)

let customView = UIView()
/*
... Customize the view as you like ...
*/

// Use class method of SwiftEntryKit to display the view using the desired attributes
SwiftEntryKit.display(entry: customView, using: attributes)
方向改变演示
orientation_change

示例项目中的黑暗模式

您可以使用预设屏幕上的分段控件来调整显示模式,强制浅色和深色模式。 所有预设都已准备好黑暗模式,但示例项目中的只有一些演示了黑暗模式功能。

light_dark

Swift 和 Objective-C 互操作性

SwiftEntryKit 的 API 使用 Swift 语言独有的语法(枚举、关联值等)。 因此,无法从 Objective-C 文件(.m.h.mm)直接引用 SwiftEntryKit

但是,使用一个简单的 .swift 类(它是 SwiftEntryKit 和您的 Objective-C 代码之间的适配器)将 SwiftEntryKit 集成到 Objective-C 项目中非常容易。

这个项目 使用 Carthage 和 CocoaPods 演示了这一点。

作者

Daniel Huri, huri000@gmail.com

捐赠

可以通过向以下地址发送比特币或以太币进行捐赠。

BTC ETH
134TiBiUvVNt7Na5KXEFBSChLdgVDw1Hnr 0xAe6616181FCdde4793AE749Ce21Cd5Af9333A3E2
btc_address eth_address

谢谢

感谢 Lily Azar, lilushkaa@gmail.com 制作了这些很棒的预设图标。

鸣谢

图标鸣谢

许可

SwiftEntryKit 在 MIT 许可下可用。 有关更多信息,请参见 LICENSE 文件。

例外情况

请注意,使用项目中的任何图标都需要注明创作者。 有关创作者列表,请参见 credits