目录
概述
SwiftEntryKit 是一个简单且功能丰富的 Swift 编写的内容呈现器。
功能
横幅或弹出窗口称为入口。
- 入口在单独的UIWindow(类型为EKWindow)内显示,因此用户可以在入口以非侵入的方式显示时自由地导航应用程序。
- 该套件提供了美丽的预置,可以使用您自己的颜色和字体进行主题设置。
- 定制:入口可高度自定义
- 可以 Positioned 在屏幕的顶部、中心或底部。
- 可以在屏幕安全区域内或之外显示。
- 可以风格化:有 边框、阴影和圆角。
- 其内容及周围背景可以被模糊、变暗、上色或具有样式的渐变。
- 过渡 动画 可自定义 - 进入、退出和弹出(由另一个入口)。
- 可以拦截入口或屏幕的用户交互。
- 可以使用 优先级 属性将入口排队或覆盖之前的入口。
- 每个入口都有一个 显示优先级 属性。这意味着只有具有相等或更高优先级的其他入口才能将其忽略。
- 预置支持无障碍访问。
- 在平移时,入口可以有可选的弹性效果。
- 可以使用简单的滑动手势选择性地忽略入口。
- 可以内嵌生命周期事件:将和消失的 will 和 did。
- 可以为入口的显示持续时间设置状态栏样式。
- 支持导航控制器和自定义视图!
示例项目
示例项目包含各种预置和示例供您使用和修改。
示例项目安装
您可以使用终端或源树等git客户端。
终端用户
$ git clone https://github.com/huri000/SwiftEntryKit.git
Git客户端(源码树)
克隆 https://github.com/huri000/SwiftEntryKit.git
预设
Toast消息 | 备注 | 浮动 | 弹出窗口 |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
警告框 | 表单 | 评分 | 更多... |
---|---|---|---|
![]() |
![]() |
![]() |
![]() |
游乐场
名词:人们可以玩耍的地方
示例应用程序包含一个游乐场界面,允许您自定义首选条目。游乐场界面有一些限制(允许选择常量值),但是您可以轻松修改代码以满足您的需求。看看吧!
游乐场界面 | 顶部Toast示例 |
---|---|
![]() |
![]() |
需求
- iOS 9 或更高版本。
- Xcode 9 或更高版本。
- Swift 4.0 或更高版本。
- 该库尚未在iOS 8.x.y 或更低版本上进行测试。
安装
- SwiftEntryKit自1.0.0版本起与Swift 5兼容。
- SwiftEntryKit自0.8.1版本起与Swift 4.2兼容。
- 使用较低Swift版本的开发者应安装发布版0.7.2。
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 实例替换应用程序主窗口并显示服务项。
入口属性
入口属性 是入口的描述符。每次显示入口时,都需要一个 EKAttributes 结构来描述入口的显示方式、屏幕内的位置、显示时长、框架约束(如果有需要)、样式(角落、边框和阴影)、用户交互事件、动画(进入/退出)等。
类似地创建一个可变的 EKAttributes 结构
var attributes = EKAttributes()
以下是可以修改的 入口属性 属性
入口名称
入口可以有名称。当实例化 EKAttributes 结构时,它是无名的,意味着 name
属性是 nil
。建议为入口设置一个有意义的名称。
attributes.name = "Top Note"
可以特别引用具有名称的入口,例如,可以查询是否 特定 的入口正在显示
if SwiftEntryKit.isCurrentlyDisplaying(entryNamed: "Top Note") {
/* Do your things */
}
窗口级别
入口可以在应用程序主窗口上方、状态栏上方、警报窗口上方甚至有自定义级别(UIWindowLevel)显示。
例如,将窗口级别设置为 正常,类似地
attributes.windowLevel = .normal
这将导致入口在应用程序主窗口上方、状态栏下方显示。
windowLevel
的默认值是 .statusBar
。
显示位置
入口可以在屏幕的 顶部、居中 或 底部 显示。
例如,将显示位置设置为 底部,类似地
attributes.position = .bottom
position
的默认值是 .top
。
优先级
条目的优先级属性描述了条目插入的方式。它提供了两种管理多个同时条目显示优先级的途径。
覆盖
如果显示优先级等于或高于当前显示的条目,则覆盖它。
以下示例设置了具有.max
显示优先级的.override
优先级,同时忽略已入队的条目(在新的条目消失后显示它们)。
attributes.precedence = .override(priority: .max, dropEnqueuedEntries: false)
您可以取消队列中条目的队列。
如果dropEnqueuedEntries
为false
,则入队条目将保留在队列中。第一个入队条目将显示在新条目弹出后。如果dropEnqueuedEntries
为true
,则在新的条目显示时,条目队列将被清空。
入队
如果队列为空,则立即显示条目,否则,将条目插入队列,直到其显示的时刻到来。
以下示例设置了具有.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
。
位置约束
将条目与屏幕上下文紧密关联的限制,例如:高度、宽度、最大宽度、最大高度、额外的垂直偏移和安全区相关信息。
- 支持自动布局的条目 - 它们的高度由应用于它们的所有约束推断。
- 不包括自动布局的条目 - 必须显式使用
positionConstraints
的size
属性来设置它们的精确大小。
例如
比率边 - 表示宽度边的比率是屏幕宽度的0.9。
let widthConstraint = EKAttributes.PositionConstraints.Edge.ratio(value: 0.9)
内在边 - 表示期望的高度值是内容高度 - 由条目的垂直约束决定。
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偏移。由于条目的框架可能超出屏幕边界,用户可能看不到整个条目——我们不想这样。因此,添加了一个额外的关联值——值为20pt的screenEdgeResistance
。也就是说,为了确保条目保持在屏幕边界内,并且始终对用户可见。在设备方向为横向并且键盘显示的情况下,可能出现极端情况(更多信息请参阅示例项目预设)。
用户交互
用户可以交互条目和屏幕。用户交互可以通过各种方式拦截
与条目的任何交互(任何触摸)都会延迟其退出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
。
滚动行为
描述条目在滚动时的行为,即,通过滑动手势和类似于UIScrollView的橡皮筋效果进行关闭。
禁用条目上的拖拽和滑动手势
attributes.scroll = .disabled
启用带有震动效果的滑动、拉伸和回弹
attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .jolt)
启用带有平稳出效果的滑动、拉伸和回弹
attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .easeOut)
启用滑动但禁用拉伸
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
引入了两种专用类型。
EKColor
描述了亮暗模式下的颜色。EKAttributes.BackgroundStyle.BlurStyle
描述了亮暗模式下的模糊效果。
以下设置将使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)
entryBackground
和screenBackground
的默认值为.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
。
动画
描述入口如何进入和退出屏幕。
- 每个动画描述符可以同时具有最多3种类型的动画。它们可以组合成单个复杂动画!
- 平移动画锚点可以显式设置,但它的默认值根据入口位置确定。
例如,从顶部使用弹跳,缩放进入,甚至淡入作为单个进入动画
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))
entranceAnimation
和exitAnimation
的默认值是.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
。
ECAttributes的接口如下
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个简单步骤:
- 创建您的 EKAttributes 结构体并设置您希望使用的属性。
- 创建 EKNotificationMessage 结构体(内容)并设置内容。
- 创建 EKNotificationMessageView (视图)并将 EKNotificationMessage 结构体注入其中。
- 使用 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完成显示后,应用程序代理持有的窗口再次变为关键窗口。这可以通过使用rollbackWindow
参数来更改此行为。
SwiftEntryKit.display(entry: view, using: attributes, rollbackWindow: .custom(window: alternativeWindow))
在条目被关闭后,所提供的alternativeWindow
将变为关键窗口,而不是应用程序代理持有的窗口。
关闭条目
您可以通过在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 属性开启)。因此,使用类似于滑动的手势删除条目也变得很自然。
启用滑动手势。当滑动手势失败(未超过速度阈值)时,使其缓慢返回。
attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .easeOut)
启用滑动手势。当滑动手势失败时,将它猛地弹回。
attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .jolt)
还可以自定义 PullbackAnimation 的值(持续时间、阻尼和初始弹簧速度)。
滑动 | 猛冲 |
---|---|
![]() |
![]() |
处理安全区域
EKAttributes.PositionConstraints.SafeArea 可以用来覆盖项目内容的安全区域,或者用背景颜色(如 Toast 一样)填充安全区域,或者甚至使安全区域保持为空(如 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)
方向变化演示 |
---|
![]() |
示例项目中的暗黑模式
您可以使用预设屏幕上的分段控制器来调整显示模式,强制启用浅色和暗黑模式。所有预设均已针对暗黑模式做好准备,但仅在示例项目中展示了一些暗黑模式的功能。
Swift和Objective-C的互操作性
SwiftEntryKit的API使用Swift语言的专用语法(枚举、关联值等)。因此,不能直接从Objective-C文件(.m、.h或.mm)中引用SwiftEntryKit
。
然而,使用一个简单的.swift类(它充当SwiftEntryKit
和您的Objective-C代码之间的适配器)相当容易地将SwiftEntryKit集成到Objective-C项目中。
此项目展示了如何使用Carthage和CocoaPods。
作者
Daniel Huri,[email protected]
捐赠
可通过将Bitcoin或Ether发送到以下地址进行捐赠。
BTC | ETH |
---|---|
134TiBiUvVNt7Na5KXEFBSChLdgVDw1Hnr | 0xAe6616181FCdde4793AE749Ce21Cd5Af9333A3E2 |
![]() |
![]() |
感谢
感谢Lily Azar,[email protected]提供了那些极好的预设图标。
致谢
许可
SwiftEntryKit 遵循 MIT 许可协议。更多详情请参阅 LICENSE 文件。
例外
请注意,项目内使用的图标需要向创作者致谢。创作者名单请参阅 致谢 文件。