- Overview
- Features
- Example Project
- Example Project Installation
- Presets
- Playground
- Requirements
- Installation
- Usage
- Quick Usage
- Entry Attributes
- Window Level
- Display Position
- Display Priority
- Display Duration
- Position Constraints
- User Interaction
- Scroll Behavior
- Haptic Feedback
- Lifecycle Events
- Background Style
- Shadow
- Round Corners
- Border
- Animations
- Pop Behavior
- Status Bar
- Presets Usage Example
- Custom View Usage Example
- Displaying a View Controller
- Dismissing an Entry
- Swiping And Rubber Banding
- Dealing With Safe Area
- Dealing With Orientation Change
- Known Issues
- Author
- License
Overview
SwiftEntryKit is a simple and versatile pop-up presenter written in Swift.
Features
Banners or Pop-Ups are called Entries.
- The entries are displayed in a separated UIWindow (of type EKWindow), so the user is able to navigate the app freely while entries are being displayed in a non intrusive manner.
- The kit offers some beautiful presets that can be themed with your app colors and fonts.
- Customization: Entries are highly customizable
- Can be displayed either at the top, center, or the bottom of the screen.
- Can be displayed within or outside the screen’s safe area.
- Can be stylized: have a border, drop-shadow and round corners.
- Their content’s and the screen’s background can be blurred, dimmed, colored or have a gradient style.
- Transition animations are customizable – Entrance, Exit and Pop (by another entry).
- The user interactions with the entry or the screen can be intercepted.
- Entries have an optional rubber banding effect in panning.
- Entries can be optionally dismissed using a simple swipe gesture.
- Entries have display priority attribute. That means that an entry can be dismissed only be other entry with equal or higher priority.
- Entries can be optionally injected with lifecycle events: will and did appear/disappear.
- The status bar style is settable for the display duration of the entry.
- SwiftEntryKit supports custom views as well.
Example Project
The example project contains various presets and examples you can use and modify as your like.
Example Project Installation
You can either use the terminal or git client such as Source Tree. The zip file doesn’t contain a necessary dependency (QuickLayout).
Terminal Users
Run git clone
with --recurse-submodules
, to include QuickLayout as submodule, likewise:
$ git clone --recurse-submodules https://github.com/huri000/SwiftEntryKit.gitCopy the code
Git Client (Source Tree)
Cloning the from github.com/huri000/Swi… also setups QuickLayout as submodule.
Presets
Toasts | Notes | Floats | Popups | Alerts | Forms | Rating |
---|---|---|---|---|---|---|
Playground
Noun: A place where people can play 🏈
The example app contains a playground screen, an interface that allows you to customize your preferable entries. The playground screen has some limitations (allows to select constant values) but you can easily modify the code to suit your needs. Check it out!
The Playground Screen | Top Toast Sample |
---|---|
Requirements
- iOS 9 or any higher version.
- Xcode 9 or any higher version.
- Swift 4.0 or any higher version.
- The library has not been tested with iOS 8.x.y or a lower version.
- SwiftEntryKit leans heavily on QuickLayout – A lightwight library written in Swift that is used to easily layout views programmatically.
Installation
CocoaPods
CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:
$ gem install cocoapodsCopy the code
To integrate SwiftEntryKit into your Xcode project using CocoaPods, specify it in your Podfile
:
The source 'https://github.com/cocoapods/specs.git' platform: ios, '9.0' use_frameworks! Pod 'SwiftEntryKit', '0.4.2'Copy the code
Then, run the following command:
$ pod installCopy the code
Carthage
You can install Carthage with Homebrew using the following command:
$ brew update
$ brew install carthageCopy the code
To integrate SwiftEntryKit into your Xcode project using Carthage, specify the following in your Cartfile
:
Making "huri000 / SwiftEntryKit" = = 0.4.2
Copy the code
Usage
Quick Usage
No setup is needed! Each time you wish to display an entry, just create your view and initialize an EKAttributes struct. See also the preset usage example, and the example project. likewise:
// 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 */Copy the code
And then, just call:
SwiftEntryKit.display(entry: customView, using: attributes)Copy the code
The kit will replace the application main window with the EKWindow instance and display the entry.
Entry Attributes
EKAttributes is the entry’s descriptor. Each time an entry is displayed, an EKAttributes struct is necessary to describe the entry’s presentation, position inside the screen, the display duration, it’s frame constraints (if needed), it’s styling (corners, border and shadow), the user interaction events, the animations (in / out) and more.
Create a mutable EKAttributes structure likewise:
var attributes = EKAttributes()Copy the code
Below are the properties that can be modified in the EKAttributes:
Window Level
Entries can be displayed above the application main window, above the status bar, above the alerts window or even have a custom level (UIWindowLevel).
For example, set the window level to normal, likewise:
attributes.windowLevel = .normalCopy the code
This causes the entry to appear above the application key window and below the status bar.
Display Position
The entry can be displayed either at the top, center, or the bottom of the screen.
For example, set the display position to top, likewise:
attributes.position = .topCopy the code
Display Priority
The display priority of the entry determines whether it dismisses other entries or be dismissed by them. An entry can be dismissed only by an entry with an equal or a higher display priority.
let highPriorityAttributes = EKAttributes()
highPriorityAttributes.displayPriority = .high
let normalPriorityAttributes = EKAttributes()
normalPriorityAttributes.displayPriority = .normal
// Display high priority entry
SwiftEntryKit.display(entry: view1, using: highPriorityAttributes)
// Display normal priority entry (ignored!)
SwiftEntryKit.display(entry: view2, using: normalPriorityAttributes)Copy the code
view2 won’t be displayed!
Display Duration
The display duration of the entry (Counted from the moment the entry has finished it’s entrance animation and until the exit animation begins).
Display for 2 seconds:
attributes.displayDuration = 2Copy the code
Display for an infinate duration
attributes.displayDuration = .infinityCopy the code
Position Constraints
Constraints that tie the entry tightly to the screen contexts, for example: Height, Width, Max Width, Max Height, Additional Vertical Offset & Safe Area related info.
For example:
Ratio edge-so that the Ratio of the width edge has a Ratio of 0.9 of the screen’s width.
Let widthConstraint = EKAttributes. PositionConstraints. Edge. Thewire (value: 0.9)Copy the code
Intrinsic edge – signifies that the wanted height value is the content height – Decided by the entries vertical constraints
let heightConstraint = EKAttributes.PositionConstraints.Edge.intrinsicCopy the code
Create the entry size constraints likewise:
attributes.positionConstraints.size = .init(width: widthConstraint, height: heightConstraint)Copy the code
You can also set attributes.positionConstraints.maxSize in order to make sure the entry does not exceeds predefined limitations. This is useful on device orientation change.
Safe Area – can be used to override the safe area or to color it (More examples are in the example project) That snippet implies that the safe area insets should be kept and not be a part of the entry.
attributes.positionConstraints.safeArea = .empty(fillSafeArea: false)Copy the code
Vertical Offset – An additional offset that can be applied to the entry (Other than the safe area).
attributes.positionConstraints.verticalOffset = 10Copy the code
Keyboard Releation – used to bind an entry to the keyboard once the keyboard is displayed.
let offset = EKAttributes.PositionConstraints.KeyboardRelation.Offset(bottom: 10, screenEdgeResistance: 20)
let keyboardRelation = EKAttributes.PositionConstraints.KeyboardRelation.bind(offset: offset)
attributes.positionConstraints.keyboardRelation = keyboardRelationCopy the code
In the example above the entry’s bottom is tuned to have a 10pts offset from the top of the keyboard (while it shows) Because the entry’s frame might exceed the screen bounds, the user might not see all the entry – we wouldn’t want that. Therefore, an additional associated value has been added – screenEdgeResistance
with value of 20pts. That is, to make sure that the entry remains within the bounds of the screen, and always visible to the user. The extreme situation might occur as the device orientation is landscape and the keyboard shows up (See example project form presets for more information).
User Interaction
The entry and the screen can be interacted by the user. User interaction be can intercepted in various ways:
An interaction (Any touch whatsoever) with the entry delays it’s exit by 3s:
attributes.entryInteraction = .delayExit(by: 3)Copy the code
A tap on the entry / screen dismisses it immediately:
attributes.entryInteraction = .dismiss
attributes.screenInteraction = .dismissCopy the code
A tap on the entry is swallowed (ignored):
attributes.entryInteraction = .absorbTouchesCopy the code
A tap on the screen is forwarded to the lower level window, in most cases the receiver will be the application window. This is very useful when you want to display an unintrusive content like banners and push notification entries.
attributes.screenInteraction = .forwardCopy the code
Pass additional actions that are invokes when the user taps the entry:
let action = {
// Do something useful
}
attributes.entryInteraction.customTapActions.append(action)Copy the code
Scroll Behavior
Describes the entry behavior when it’s being scrolled, that is, dismissal by a swipe gesture and a rubber band effect much similar to a UIScrollView.
Disable the pan and swipe gestures on the entry:
attributes.scroll = .disabledCopy the code
Enable swipe and stretch and pullback with jolt effect:
attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .jolt)Copy the code
Enable swipe and stretch and pullback with an ease-out effect:
attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .easeOut)Copy the code
Enable swipe but disable stretch:
attributes.scroll = .edgeCrossingDisabled(swipeable: true)Copy the code
Haptic Feedback
The device can produce a haptic feedback, thus adding an additional sensory depth to each entry.
Lifecycle Events
Events can be injected to the entry so that they are to be called during its lifecycle.
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
}Copy the code
Background Style
The entry and the screen can have various background styles, such as blur, color, gradient and even an image.
The default value is .clear. This example implies clear background for both the entry and the screen:
attributes.entryBackground = .clear
attributes.screenBackground = .clearCopy the code
Colored entry background and dimmed screen background:
attributes.entryBackground = .color(color: .white)
attributes.screenBackground = .color(color: UIColor(white: 0.5, alpha: 0.5))Copy the code
Gradient entry background (diagonal vector):
let colors: [UIColor] = [.red, .green, .blue]
attributes.entryBackground = .gradient(gradient: .init(colors: colors, startPoint: .zero, endPoint: CGPoint(x: 1, y: 1)))Copy the code
Visual Effect entry background:
attributes.entryBackground = .visualEffect(style: .light)Copy the code
Shadow
The shadow that surrounds the entry.
Enable shadow around the entry:
Attributes. Shadow =.active(with:.init(color:.black, opacity: 0.3, radius: 10, offset:.zero))Copy the code
Disable shadow around the entry:
attributes.shadow = .noneCopy the code
Round Corners
Round corners around the entry.
Only top left and right corners with radius of 10:
attributes.roundCorners = .top(radius: 10)Copy the code
Only bottom left and right corners with radius of 10:
attributes.roundCorners = .bottom(radius: 10)Copy the code
All corners with radius of 10:
attributes.roundCorners = .all(radius: 10)Copy the code
No round corners:
attributes.roundCorners = .noneCopy the code
Border
The border around the entry.
Add a black border with thickness of 0.5 PTS:
Attributes. Border =.value(color:.black, width: 0.5)Copy the code
No border:
attributes.border = .noneCopy the code
Animations
Describes how the entry animates into and out of the screen.
- Each animation descriptor can have up to 3 types of animations at the same time. Those can be combined to a single complex one!
- Translation animation anchor can be explicitly set but it receives a default value according to position of the entry.
Example for translation from top with spring, scale in and even fade in as a single entrance animation:
Attributes. EntranceAnimation =. The init (translate: init (duration: 0.7, anchorPosition: top, spring: the init (damping: InitialVelocity: 0)), scale:.init(from: 0.6, to: 1, duration: 0.7), fade:.init(from: 0.8, to: 1, duration: 0.3))Copy the code
Pop Behavior
Describes the entry behavior when it’s being popped (dismissed by an entry with equal / higher display-priority.
The entry is being popped animatedly:
Attributes. PopBehavior =. Animation:.init(Translate:.init(duration: 0.2))Copy the code
The entry is being overriden (Disappears promptly):
attributes.popBehavior = .overriddenCopy the code
Status Bar
The status bar appearance can be modified during the display of the entry. In order to enable this feature, set View controller-based status bar appearance to NO in your project’s info.plist file.
Setting the status bar style is fairly simple:
Status bar becomes visible and gets a light style:
attributes.statusBar = .lightCopy the code
The status bar becomes hidden:
attributes.statusBar = .hiddenCopy the code
The status bar appearance is inferred from the previous context (won’t be changed):
attributes.statusBar = .inferredCopy the code
In case there is an already presenting entry with lower/equal display priority, the status bar will change it’s style When the entry is removed the status bar gets it’s initial style back.
EKAttributes’ interface is as follows:
public struct EKAttributes
// Display
public var windowLevel: WindowLevel
public var position: Position
public var displayPriority: DisplayPriority
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 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
}Copy the code
Presets Usage Example:
You can use one of the presets that come with SwiftEntryKit, doing these 4 simple steps:
- Create your EKAttributes struct and set your preferrable properties.
- Create EKNotificationMessage struct (The Content) and set the content.
- Create EKNotificationMessageView (The View) and inject EKNotificationMessage struct to it.
- Display the entry using SwiftEntryKit class method.
EKNotificationMessageView preset example:
// Generate top floating entry and set some properties var attributes = EKAttributes.topFloat attributes.entryBackground = .gradient(gradient: .init(colors: [.red, .green], startPoint: .zero, endPoint: CGPoint(x: 1, y: 1))) Attributes =.animated(animation:.init(Duration: 0.3), scale:.init(from: 1, to: 0, duration: 0))) Attributes =.active(with:.init(color:.black, opacity: 0.5, radius: 10, offset: 0) .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)Copy the code
Custom View Usage Example:
// Create a basic toast that appears at the top
var attributes = EKAttributes.topToast
// Set it's 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)Copy the code
Displaying a View Controller
As from version 0.4.0, View Controllers are supported As well
SwiftEntryKit.display(entry: customViewController, using: attributes)Copy the code
Dismissing an Entry
You can dismiss an entry by simply invoke dismiss in the SwiftEntryKit class, likewise:
SwiftEntryKit.dismiss()Copy the code
This dismisses the entry animatedly using its exitAnimation attribute and on comletion, the window would be removed as well.
Using a completion handler
Inject a trailing closure to be executed after the entry dismissal.
SwiftEntryKit.dismiss {
// Executed right after the entry has been dismissed
}Copy the code
Is Currently Displaying
Inquire whether an entry is currently displayed:
if SwiftEntryKit.isCurrentlyDisplaying {
/* Do your things */
}Copy the code
Swiping and Rubber Banding
Entries can be panned vertically (This ability can be enabled using the scroll attributes). Thefore it’s only natural that an entry can be dismissed using a swipe-like gesture.
Enable swipe gesture. When the swipe gesture fails (doesn’t pass the velocity threshold) ease it back.
attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .easeOut)Copy the code
Enable swipe gesture. When the swipe gesture fails throw it back out with a jolt.
attributes.scroll = .enabled(swipeable: true, pullbackAnimation: .jolt)Copy the code
The PullbackAnimation values (duration, damping & initialSpringVelocity) can be customized as well.
Swipe | Jolt |
---|---|
Dealing with safe area:
EKAttributes.PositionConstraints.SafeArea may be used to override the safe area with the entry’s content, or to fill the safe area with a background color (like Toasts do), or even leave the safe area empty (Like Floats do).
SwiftEntryKit supports iOS 11.x.y and is backward compatible to iOS 9.x.y, so the status bar area is treated as same as the safe area in earlier iOS versions.
Dealing with orientation change:
SwiftEntryKit identifies orientation changes and adjust the entry’s layout to those changes. Therefore, if you wish to limit the entries’s width, you are able to do so by giving it a maximum value, likewise:
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)Copy the code
Orientation Change Demonstration |
---|
Known Issues
Unable to find specification for SwiftEntryKit (=X.Y.Z) – In case you get this error please review this thread.
Unable to use example project – In case you are unable to install the example project please review this thread and the Example Project Installation section.
Author
Daniel Huri, [email protected]
Thank You
Thanks Lily Azar, [email protected] for those aweome preset icons.
Credits
License
SwiftEntryKit is available under the MIT license. See the LICENSE file for more info.
Exceptions
Please be aware that any use of the icons inside the project requires attribution to the creator. See credits for the creators list.