WhatsNewKit
WhatsNewKit enables you to easily showcase your awesome new app features.
It's designed from the ground up to be fully customized to your needs.
Features
- [x] Customization and Configuration to your needs ?
- [x] Predefined Themes and Animations ?
- [x] Easily check if your Features have already been presented ?
- [x] Awesome UI ?
Example
The example Application is an excellent way to see WhatsNewKit
in action. You get a brief look of the available configuration options and how they affect the look and feel of the WhatsNewViewController
. Simply open the WhatsNewKit.xcodeproj
and run the WhatsNewKit-Example
scheme.
Installation
CocoaPods
WhatsNewKit is available through CocoaPods. To install
it, simply add the following line to your Podfile:
pod 'WhatsNewKit'
Carthage
Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.
To integrate WhatsNewKit into your Xcode project using Carthage, specify it in your Cartfile
:
github "SvenTiigi/WhatsNewKit"
Run carthage update --platform iOS
to build the framework and drag the built WhatsNewKit.framework
into your Xcode project.
On your application targets’ “Build Phases” settings tab, click the “+” icon and choose “New Run Script Phase” and add the Framework path as mentioned in Carthage Getting started Step 4, 5 and 6
Swift Package Manager
To integrate using Apple's Swift Package Manager, add the following as a dependency to your Package.swift
:
dependencies: [
.package(url: "https://github.com/SvenTiigi/WhatsNewKit.git", from: "1.3.0")
]
Manually
If you prefer not to use any of the aforementioned dependency managers, you can integrate WhatsNewKit into your project manually. Simply drag the Sources
Folder into your Xcode project.
Usage
The following first usage description shows the easiest way of presenting your new app features with WhatsNewKit
.
?? Please see the Advanced section for further configuration options and features.
import WhatsNewKit
// Initialize WhatsNew
let whatsNew = WhatsNew(
// The Title
title: "WhatsNewKit",
// The features you want to showcase
items: [
WhatsNew.Item(
title: "Installation",
subtitle: "You can install WhatsNewKit via CocoaPods or Carthage",
image: UIImage(named: "installation")
),
WhatsNew.Item(
title: "Open Source",
subtitle: "Contributions are very welcome ??",
image: UIImage(named: "openSource")
)
]
)
// Initialize WhatsNewViewController with WhatsNew
let whatsNewViewController = WhatsNewViewController(
whatsNew: whatsNew
)
// Present it ?
self.present(whatsNewViewController, animated: true)
Additionally, you can make use of the WhatsNewVersionStore
to show the WhatsNewViewController
only once for each version of your app.
// Initialize WhatsNewVersionStore
let versionStore: WhatsNewVersionStore = KeyValueWhatsNewVersionStore()
// Passing a WhatsNewVersionStore to the initializer
// will give you an optional WhatsNewViewController
let whatsNewViewController: WhatsNewViewController? = WhatsNewViewController(
whatsNew: whatsNew,
versionStore: versionStore
)
// Verify WhatsNewViewController is available
guard let viewController = whatsNewViewController else {
// The user has already seen the WhatsNew-Screen for the current Version of your app
return
}
// Present WhatsNewViewController
self.present(viewController, animated: true)
?? Head over to the WhatsNewVersionStore to learn more.
Advanced
As mentioned before WhatsNewKit
can be fully customized to your needs. The Advanced section will explain all configuration possibilities and features of WhatsNewKit
in detail. First off it's important to understand the components of the WhatsNewViewController
in order to customize the behaviour and UI
-Design.
WhatsNewViewController.Configuration
The WhatsNewViewController.Configuration struct enables you to customize the WhatsNewViewController
components to your needs. The configuration itself can be passed to the initializer of the WhatsNewViewController
.
// Initialize default Configuration
var configuration = WhatsNewViewController.Configuration()
// Customize Configuration to your needs
configuration.backgroundColor = .white
configuration.titleView.titleColor = .orange
configuration.itemsView.titleFont = .systemFont(ofSize: 17)
configuration.detailButton?.titleColor = .orange
configuration.completionButton.backgroundColor = .orange
// And many more configuration properties...
// Initialize WhatsNewViewController with custom configuration
let whatsNewViewController = WhatsNewViewController(
whatsNew: whatsNew,
configuration: configuration
)
Theme ?
A Theme allows you to group the customization of a WhatsNewViewController.Configuration
. WhatsNewKit
implemented predefined Themes which are available as static properties both in white and dark mode. Or you create your very own Theme to configure it to your needs.
.darkRed |
.whiteRed |
---|---|
|
|
// Configuration with predefined Dark Red Theme
let darkRed = WhatsNewViewController.Configuration(
theme: .darkRed
)
// Apply predefined White Red Theme to Configuration
var configuration = WhatsNewViewController.Configuration()
configuration.apply(theme: .whiteRed)
// Or create your own Theme and initialize a Configuration with your Theme
let myTheme = WhatsNewViewController.Theme { configuration in
configuration.backgroundColor = .white
configuration.titleView.titleColor = .orange
configuration.itemsView.titleFont = .systemFont(ofSize: 17)
configuration.detailButton?.titleColor = .orange
configuration.completionButton.backgroundColor = .orange
// ...
}
// Initialize a Configuration with your Theme
let configuration = WhatsNewViewController.Configuration(
theme: myTheme
)
For a full overview of the available predefined Themes check out the Example-Application.
iOS 13 Dark-Mode
Use the .red
Theme if you wish that a predefined Theme like .darkRed
and .whiteRed
automatically adapts to the current UserInterfaceStyle.
// Configuration with predefine `red` Theme which auto adapts to the UserInterfaceStyle
// in order to support iOS 13 Dark-Mode
let configuration = WhatsNewViewController.Configuration(
theme: .red
)
Dark-Mode compatible Themes: .blue
, .lightBlue
, .orange
, .purple
, .red
, .green
Layout ?
WhatsNewKit
comes with three predefined ItemsView.Layouts
.
Left | Centered | Right |
---|---|---|
// Left Layout
configuration.itemsView.layout = .left
// Centered Layout
configuration.itemsView.layout = .centered
// Right Layout
configuration.itemsView.layout = .right
☝️ In default the ItemsView layout is set to
.left
.
ContentMode ?
Setting the ContentMode
in the ItemsView
Configuration will adjust for how your features are arranged along the axis.
Top | Center | Fill |
---|---|---|
// ContentMode Top
configuration.itemsView.contentMode = .top
// ContentMode Center
configuration.itemsView.contentMode = .center
// ContentMode Fill
configuration.itemsView.contentMode = .fill
☝️ In default the ItemsView ContentMode is set to
top
.
Insets
Additionally, if you wish you can modify the layout insets of the WhatsNewViewController
components.
// Set TitleView Insets (Default values)
configuration.titleView.insets = UIEdgeInsets(top: 50, left: 20, bottom: 15, right: 20)
// Increase the CompletionButton Bottom Inset
configuration.completionButton.insets.bottom += 10
ImageSize
In order to define the size of your images for each of your feature you can set an ImageSize on the ItemsView
configuration.
// Use the original image size as it is
configuration.itemsView.imageSize = .original
// Use the preferred image size which fits perfectly :)
configuration.itemsView.imageSize = .preferred
// Use a custom height for each image
configuration.itemsView.imageSize = .fixed(height: 25)
☝️ In default the ItemsView ImageSize is set to
preferred
.
Image Tint-Color
In default WhatsNewKit auto tints the images of each WhatsNew.Item
in the given tint color of the configuration.
If you wish to disable this functionality simply set autoTintImage
to false
// Disable auto tinting images
configuration.itemsView.autoTintImage = false
☝️ In default the ItemsView AutoTintImage is set to
true
.
Animation ?
You can apply a Animation to all components of the WhatsNewViewController
via predefined animation types. In default all Animation properties are nil
indicating no animation should be perfomed.
// Set SlideUp Animation to TitleView
configuration.titleView.animation = .slideUp
// Set SlideRight Animation to ItemsView
configuration.itemsView.animation = .slideRight
// Set SlideLeft Animation to DetailButton
configuration.detailButton?.animation = .slideLeft
// Set SlideDown Animation to CompletionButton
configuration.completionButton.animation = .slideDown
If you wish to animate all views with the same type you can do so by simply applying it to the configuration.
// Global Animation-Type for all WhatsNewViewController components
configuration.apply(animation: .fade)
If you wish to define your custom animation, simply set the custom
enum and pass an animator closure.
// Custom Animation for DetailButton
configuration.detailButton?.animation = .custom(animator: { (view: UIView, settings: AnimatorSettings) in
// view: The View to perform animation on
// settings: Preferred duration and delay
})
Title Mode
In default the TitleView is sticked to the top.
If you wish that the TitleView scrolls with the ItemsView you can change the titleMode
on the TitleView configuration.
// TitleView scrolls alongside with the ItemsView
configuration.titleView.titleMode = .scrolls
// TitleView is fixed to top
configuration.titleView.titleMode = .fixed
☝️ In default the titleMode is set to
.fixed
.
Secondary Title Color
By setting a SecondaryColor on the TitleView you can change the color of certain characters.
// Set secondary color on TitleView Configuration
configuration.titleView.secondaryColor = .init(
// The start index
startIndex: 0,
// The length of characters
length: 5,
// The secondary color to apply
color: .whatsNewKitLightBlue
)
☝️ In default the secondaryColor is set to
nil
.
DetailButton
By setting an DetailButton struct on the WhatsNewViewController.Configuration
struct you can customize the title
and the corresponding action
of the displayed detail button on the WhatsNewViewController
. As the DetailButton
struct is declared as optional the WhatsNewViewController
will only display the button if a DetailButton
configuration is available
Action | Description |
---|---|
website |
When the user pressed the detail button a SFSafariViewController with the given URL will be presented |
custom |
After the detail button has been pressed by the user, your custom action will be invoked |
// Initialize DetailButton with title and open website at url
let detailButton = WhatsNewViewController.DetailButton(
title: "Read more",
action: .website(url: "https://github.com/SvenTiigi/WhatsNewKit")
)
// Initialize DetailButton with title and custom action
let detailButton = WhatsNewViewController.DetailButton(
title: "Read more",
action: .custom(action: { [weak self] whatsNewViewController in
// Perform custom action on detail button pressed
})
)
CompletionButton
The CompletionButton struct configures the displayed title and the action when the user pressed the completion button on the WhatsNewViewController
.
Action | Description |
---|---|
dismiss |
When the user pressed the completion button, the WhatsNewViewController will be dismissed. This is the default value |
custom |
After the completion button has been pressed by the user, your custom action will be invoked |
// Initialize CompletionButton with title and dismiss action
let completionButton = WhatsNewViewController.CompletionButton(
title: "Continue",
action: .dismiss
)
// Initialize CompletionButton with title and custom action
let completionButton = WhatsNewViewController.CompletionButton(
title: "Continue",
action: .custom(action: { [weak self] whatsNewViewController in
// Perform custom action on completion button pressed
})
)
HapticFeedback ?
You can enable on both DetailButton
and CompletionButton
haptic feedback when the user pressed one of these buttons. Either by setting the property or passing it to the initializer.
// Impact Feedback
button.hapticFeedback = .impact(.medium)
// Selection Feedback
button.hapticFeedback = .selection
// Notification Feedback with type
let completionButton = WhatsNewViewController.CompletionButton(
title: "Continue",
action: .dismiss,
hapticFeedback: .notification(.success)
)
☝️ In default the HapticFeedback is
nil
indicating no haptic feedback should be executed.
iPad Adjustments
If you wish to modify the layout of the WhatsNewViewController
when presenting it on an iPad you can set the padAdjustment
closure.
Currently the padAdjustment
closure will only look for changed insets
property of the WhatsNewViewController.Configuration
in order to update the layout. Therefore, changes to any other configuration property will have no effect.
// Set PadAdjustment closure
configuration.padAdjustment = { configuration in
// Invoke default PadAdjustments (Adjusts Insets for iPad)
WhatsNewViewController.Configuration.defaultPadAdjustment(&configuration)
// Adjust TitleView top insets
configuration.titleView.insets.top = 20
}
☝️ In default the
WhatsNewViewController.Configuration.defaultPadAdjustment
will be invoked.
WhatsNewVersionStore ?
If we speak about presenting awesome new app features we have to take care that this kind of UI
action only happens once if the user installed the app or opened it after an update. The WhatsNewKit
offers a protocol oriented solution for this kind of problem via the WhatsNewVersionStore protocol.
/// WhatsNewVersionStore typealias protocol composition
public typealias WhatsNewVersionStore = WriteableWhatsNewVersionStore & ReadableWhatsNewVersionStore
/// The WriteableWhatsNewVersionStore
public protocol WriteableWhatsNewVersionStore {
func set(version: WhatsNew.Version)
}
/// The ReadableWhatsNewVersionStore
public protocol ReadableWhatsNewVersionStore {
func has(version: WhatsNew.Version) -> Bool
}
The WhatsNewViewController
will use the APIs of the WhatsNewVersionStore
in the following way.
API | Description |
---|---|
has(version:) |
Checks if the Whatsnew.Version is available and will return nil during initialization. |
set(version:) |
The WhatsNew.Version will be set after the CompletionButton has been pressed. |
The WhatsNewVersionStore
can be passed as an parameter to the initializer. If you do so the initializer will become optional
.
// Initialize WhatsNewViewController with WhatsNewVersionStore
let whatsNewViewController: WhatsNewViewController? = WhatsNewViewController(
whatsNew: whatsNew,
versionStore: myVersionStore
)
// Check if WhatsNewViewController is available to present it.
if let controller = whatsNewViewController {
// Present it as WhatsNewViewController is available
// after init with WhatsNewVersionStore
self.present(controller, animated: true)
} else {
// WhatsNewViewController is `nil` this Version has already been presented
}
// Or invoke present on the WhatsNewViewController
// to avoid the need of unwrapping the optional
whatsNewViewController?.present(on: self)
☝️ Please keep in mind the
WhatsNewViewController
initializer will only becomeoptional
and checks if the Version has been already presented if you pass aWhatsNewVersionStore
object.
Implementation
If you already handled saving user settings in your app to something like Realm
, CoreData
or UserDefaults
you can conform that to the WhatsNewVersionStore
.
// Extend your existing App-Logic
extension MyUserSettingsDatabase: WhatsNewVersionStore {
// Implement me ??
}
Predefined Implementations
WhatsNewKit
brings along two predefined Implementations of the WhatsNewVersionStore
.
KeyValueWhatsNewVersionStore
The KeyValueWhatsNewVersionStore saves and retrieves the WhatsNew.Version
via a KeyValueable
protocol conform object. UserDefaults
and NSUbiquitousKeyValueStore
are already conform to that protocol ?
// Local KeyValueStore
let keyValueVersionStore = KeyValueWhatsNewVersionStore(
keyValueable: UserDefaults.standard
)
// iCloud KeyValueStore
let keyValueVersionStore = KeyValueWhatsNewVersionStore(
keyValueable: NSUbiquitousKeyValueStore.default
)
// Initialize WhatsNewViewController with KeyValueWhatsNewVersionStore
let whatsNewViewController: WhatsNewViewController? = WhatsNewViewController(
whatsNew: whatsNew,
versionStore: keyValueVersionStore
)
InMemoryWhatsNewVersionStore
The InMemoryWhatsNewVersionStore saves and retrieves the WhatsNew.Version
in memory. Perfect for development or testing phase ??
// Initialize WhatsNewViewController with InMemoryWhatsNewVersionStore
let whatsNewViewController: WhatsNewViewController? = WhatsNewViewController(
whatsNew: whatsNew,
versionStore: InMemoryWhatsNewVersionStore()
)
WhatsNew.Version
During the initialization of the WhatsNew
struct the WhatsNewKit
will automatically retrieve the current App-Version via the CFBundleShortVersionString and construct a WhatsNew.Version for you which is used by the WhatsNewVersionStore
protocol in order to persist the presented app versions. If you want to manually set the version you can do it like the following example.
// Initialize Version 1.0.0
let version = WhatsNew.Version(
major: 1,
minor: 0,
patch: 0
)
// Use a String literal
let version: WhatsNew.Version = "1.0.0"
// Current Version in Bundle (Default)
let version = WhatsNew.Version.current()
After you initialize a WhatsNew.Version
you can pass it to the initializer of a WhatsNew
struct.
// Initialize WhatsNew with Title and Items
let whatsNew = WhatsNew(
version: version,
title: "WhatsNewKit",
items: []
)
If you holding multiple WhatsNew
structs in an array you can make use of the following two functions to retrieve a WhatsNew
struct based on the WhatsNewVersion
.
let whatsNews: [WhatsNew] = [...]
// Retrieve WhatsNew from array based on Version 1.0.0
let whatsNewVersion1 = whatsNews.get(byVersion:
.init(major: 1, minor: 0, patch: 0)
)
// Or retrieve it via String as WhatsNew.Version is
// conform to the ExpressibleByStringLiteral protocol
let whatsNewVersion2 = whatsNews.get(byVersion: "2.0.0")
// If you want the WhatsNew for your current App-Version
// based on the CFBundleShortVersionString from Bundle.main
let currentWhatsNew = whatsNews.get()
Codable WhatsNew
The WhatsNew
struct is conform the Codable
protocol which allows you to initialize a WhatsNew
struct via JSON
.
{
"version": {
"major": 1,
"minor": 0,
"patch": 0
},
"title": "WhatsNewKit",
"items": [
{
"title": "Open Source",
"subtitle": "Contributions are very welcome ??",
"image": "iVBORw0KGgoA..."
}
]
}
The optional image
property of the WhatsNew.Item
will be decoded and encoded in Base64.
// Encode to JSON
let encoded = try? JSONEncoder().encode(whatsNew)
// Decode from JSON data
let decoded = try? JSONDecoder().decode(WhatsNew.self, from: data)
Featured on
- Awesome iOS Weekly
- Swift Weekly
- AppCoda Weekly
- iOS Goodies
- MyBridge - Open Source Swift Projects (June 2018)
- The iOS Times
- DZone
- Brian Advent
- 23 Amazing iOS UI Libraries written in Swift for the Past Year (v.2019)
- Indie iOS Focus Weekly
- 5 iOS Libraries That Will Inspire Your Creativity
Contributing
Contributions are very welcome ? ?
Credits
The WhatsNew.Item
images (icons8-github, icons8-puzzle, icons8-approval, icons8-picture) which are seen on the screenshots and inside the example application are taken from icons8.com which are licensed under Creative Commons Attribution-NoDerivs 3.0 Unported.
License
WhatsNewKit
Copyright (c) 2020 Sven Tiigi <[email protected]>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.