XLActionController
Fully customizable and extensible action sheet controller written in Swift
Install / Use
/learn @xmartlabs/XLActionControllerREADME

By XMARTLABS.
XLActionController is an extensible library to quickly create any custom action sheet controller.
Examples
<table> <tr> <td> <img src="Media/demo_spotify.gif" width="300"/> </td> <td> <img src="Media/demo_twitter.gif" width="300"/> </td> <td> <img src="Media/demo_tweetbot.gif" width="300"/> </td> </tr> <tr> <td> <img src="Media/demo_periscope.gif" width="300"/> </td> <td> <img src="Media/demo_youtube.gif" width="300"/> </td> <td> <img src="Media/demo_skype.gif" width="300"/> </td> </tr> </table>The action sheet controllers shown above were entirely created using XLActionController and are included in the Examples. To run the Example project: clone XLActionController repository, open XLActionController workspace and run the Example project.
The code snippet below shows how to present the Tweetbot action sheet controller:
let actionController = TweetbotActionController()
actionController.addAction(Action("View Details", style: .default, handler: { action in
// do something useful
}))
actionController.addAction(Action("View Retweets", style: .default, handler: { action in
// do something useful
}))
actionController.addAction(Action("View in Favstar", style: .default, handler: { action in
// do something useful
}))
actionController.addAction(Action("Translate", style: .default, executeImmediatelyOnTouch: true, handler: { action in
// do something useful
}))
actionController.addSection(Section())
actionController.addAction(Action("Cancel", style: .cancel, handler:nil))
present(actionController, animated: true, completion: nil)
As you may have noticed, the library usage looks pretty similar to UIAlertController.
Actions' handlers are executed after the alert controller is dismissed from screen. If you want, you can change this passing true to the action's constructor to the argument executeImmediatelyOnTouch.
Behind the scenes XLActionController uses a UICollectionView to display the action sheet.
Usage
First create a custom action sheet view controller by extending from the ActionController generic class. For details on how to create a custom action sheet controller look at the Extensibility section.
For instance, let's suppose we've already created TwitterActionController.
// Instantiate custom action sheet controller
let actionSheet = TwitterActionController()
// set up a header title
actionSheet.headerData = "Accounts"
// Add some actions, note that the first parameter of `Action` initializer is `ActionData`.
actionSheet.addAction(Action(ActionData(title: "Xmartlabs", subtitle: "@xmartlabs", image: UIImage(named: "tw-xmartlabs")!), style: .default, handler: { action in
// do something useful
}))
actionSheet.addAction(Action(ActionData(title: "Miguel", subtitle: "@remer88", image: UIImage(named: "tw-remer")!), style: .default, handler: { action in
// do something useful
}))
// present actionSheet like any other view controller
present(actionSheet, animated: true, completion: nil)
<img src="Media/demo_twitter.gif" width="200"/>
As the code above illustrates, there are no relevant differences compared to the UIAlertController API.
The main difference is that XLActionController works with any header data type and not only the standard UIAlertController title and message properties.
Similarly XLActionController's Action works with any data Type and not only the title string.
// XLActionController:
xlActionController.headerData = SpotifyHeaderData(title: "The Fast And The Furious Soundtrack Collection", subtitle: "Various Artists", image: UIImage(named: "sp-header-icon")!)
// vs UIAlertController:
uiActionController.title = "The Fast And The Furious Soundtrack Collection" // no way to pass an image
uiActionController.message = "Various Artists"
// XLActionController:
let xlAction = Action(ActionData(title: "Save Full Album", image: UIImage(named: "sp-add-icon")!), style: .default, handler: { action in })
// notice that we are able to pass an image in addition to the title
xlActionController.addAction(xlAction)
// vs UIAlertController:
let uiAction = UIAlertAction(title: "Xmartlabs", style: .default, handler: { action in }))
uiActionController.addAction(uiAction)
This can be accomplished because XLActionController is a generic type.
Another important difference is that XLActionController provides a way to add action sections as illustrated in the code below:
actionController.addSection(Section())
and also each section has a data property. This property is generic, so that it can hold any type. This data will be used to create this section's header view.
let section = actionController.addSection(Section())
section.data = "String" // assuming section data Type is String
Each section contains a set of actions. We typically use sections to show a header view above a set of actions.
Extensibility
ActionController uses a UICollectionView to show actions and headers on screen. Actions will be rendered as instances of UICollectionViewCell. You can use your own subclass of UICollectionViewCell by specifying it in the action controller declaration. Additionally, ActionController allows you to specify a global header and a section header. Headers are shown as collection view's supplementary views.
The ActionController class is a generic type that works with any cell, header, section header type and its associated data types.
Create your custom action sheet controller
XLActionController provides extension points to specify a whole new look and feel to our custom sheet controller and to tweak present and dismiss animations. Let's see an example:
// As first step we should extend the ActionController generic type
public class PeriscopeActionController: ActionController<PeriscopeCell, String, PeriscopeHeader, String, UICollectionReusableView, Void> {
// override init in order to customize behavior and animations
public override init(nibName nibNameOrNil: String? = nil, bundle nibBundleOrNil: Bundle? = nil) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
// customizing behavior and present/dismiss animations
settings.behavior.hideOnScrollDown = false
settings.animation.scale = nil
settings.animation.present.duration = 0.6
settings.animation.dismiss.duration = 0.5
settings.animation.dismiss.options = .curveEaseIn
settings.animation.dismiss.offset = 30
// providing a specific collection view cell which will be used to display each action, height parameter expects a block that returns the cell height for a particular action.
cellSpec = .nibFile(nibName: "PeriscopeCell", bundle: Bundle(for: PeriscopeCell.self), height: { _ in 60})
// providing a specific view that will render each section header.
sectionHeaderSpec = .cellClass(height: { _ in 5 })
// providing a specific view that will render the action sheet header. We calculate its height according the text that should be displayed.
headerSpec = .cellClass(height: { [weak self] (headerData: String) in
guard let me = self else { return 0 }
let label = UILabel(frame: CGRect(x: 0, y: 0, width: me.view.frame.width - 40, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.font = .systemFontOfSize(17.0)
label.text = headerData
label.sizeToFit()
return label.frame.size.height + 20
})
// once we specify the views, we have to provide three blocks that will be used to set up these views.
// block used to setup the header. Header view and the header are passed as block parameters
onConfigureHeader = { [weak self] header, headerData in
guard let me = self else { return }
header.label.frame = CGRect(x: 0, y: 0, width: me.view.frame.size.width - 40, height: CGFloat.greatestFiniteMagnitude)
header.label.text = headerData
header.label.sizeToFit()
header.label.center = CGPoint(x: header.frame.size.width / 2, y: header.frame.size.height / 2)
}
// block used to setup the section header
onConfigureSectionHeader = { sectionHeader, sectionHeaderData in
sectionHeader.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
}
// block used to setup the collection view cell
onConfigureCellForAction = { [weak self] cell, action, indexPa
