• Stars
    star
    3,326
  • Rank 13,476 (Top 0.3 %)
  • Language
    Swift
  • License
    MIT License
  • Created about 9 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Fully customizable and extensible action sheet controller written in Swift

XLActionController

Build status Platform iOS Swift 5 compatible Carthage compatible CocoaPods compatible License: MIT

By XMARTLABS.

XLActionController is an extensible library to quickly create any custom action sheet controller.

Examples

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)

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, indexPath in
            cell.setup(action.data, detail: nil, image: nil)
            cell.separatorView?.isHidden = indexPath.item == self!.collectionView.numberOfItems(inSection: indexPath.section) - 1
            cell.alpha = action.enabled ? 1.0 : 0.5
            cell.actionTitleLabel?.textColor = action.style == .destructive ? UIColor(red: 210/255.0, green: 77/255.0, blue: 56/255.0, alpha: 1.0) : UIColor(red: 0.28, green: 0.64, blue: 0.76, alpha: 1.0)
        }
    }
}

ActionController type declaration:

public class ActionController<ActionViewType: UICollectionViewCell, ActionDataType, HeaderViewType: UICollectionReusableView, HeaderDataType, SectionHeaderViewType: UICollectionReusableView, SectionHeaderDataType>

When extending ActionController we must specify the following view types ActionViewType, HeaderViewType, SectionHeaderViewType. These types are the cell type used to render an action, the view used to render the action sheet header and the view used to render the section header.

Each view type has its associated data: ActionDataType, HeaderDataType, SectionHeaderDataType respectively.

If your custom action sheet doesn't have a header view we can use UICollectionReusableView as HeaderViewType and Void as HeaderDataType. If it doesn't have a section header view you can use UICollectionReusableView as SectionHeaderViewType and Void as SectionHeaderDataType.

The code below shows how we specify these types for the action controllers provided in the example project:

class PeriscopeActionController: ActionController<PeriscopeCell, String, PeriscopeHeader, String, UICollectionReusableView, Void> { ... } // doesn't need to show a section header
class SpotifyActionController: ActionController<SpotifyCell, ActionData, SpotifyHeaderView, SpotifyHeaderData, UICollectionReusableView, Void> { ... } // doesn't need to show a section header
class TwitterActionController: ActionController<TwitterCell, ActionData, TwitterActionControllerHeader, String, UICollectionReusableView, Void> { ... } // doesn't need to show a section header
class YoutubeActionController: ActionController<YoutubeCell, ActionData, UICollectionReusableView, Void, UICollectionReusableView, Void>

Tweaking default style and animation parameters

By following the previous section steps you should already be able to play with your custom action controller. It happens quite often that we need some other customization such as zooming out the presenting view, changing the status bar color or customizing the default present and dismiss animation.

ActionController class defines the settings property of type ActionSheetControllerSettings to tweak all these.

UICollectionView's behavior

// Indicates if the action controller must be dismissed when the user taps the background view. `true` by default.
settings.behavior.hideOnTap: Bool
// Indicates if the action controller must be dismissed when the user scrolls down the collection view. `true` by default.
settings.behavior.hideOnScrollDown: Bool
// Indicates if the collectionView's scroll is enabled. `false` by default.
settings.behavior.scrollEnabled: Bool
// Controls whether the collection view scroll bounces past the edge of content and back again. `false` by default.
settings.behavior.bounces: Bool
// Indicates if the collection view layout will use UIDynamics to animate its items. `false` by default.
settings.behavior.useDynamics: Bool
// Determines whether the navigation bar is hidden when action controller is being presented. `true` by default
settings.hideCollectionViewBehindCancelView: Bool

UICollectionView Style

// Margins between the collection view and the container view's margins. `0` by default
settings.collectionView.lateralMargin: CGFloat
// Cells height when UIDynamics is used to animate items. `50` by default.
settings.collectionView.cellHeightWhenDynamicsIsUsed: CGFloat

Animation settings

Struct that contains all properties related to presentation & dismissal animations

// Used to scale the presenting view controller when the action controller is being presented. If `nil` is set, then the presenting view controller won't be scaled. `(0.9, 0.9)` by default.
settings.animation.scale: CGSize? = CGSize(width: 0.9, height: 0.9)

Present animation settings

// damping value for the animation block. `1.0` by default.
settings.animation.present.damping: CGFloat
// delay for the animation block. `0.0` by default.
settings.animation.present.delay: TimeInterval
// Indicates the animation duration. `0.7` by default.
settings.animation.present.duration: TimeInterval
// Used as `springVelocity` for the animation block. `0.0` by default.
settings.animation.present.springVelocity: CGFloat
// Present animation options. `UIViewAnimationOptions.curveEaseOut` by default.
settings.animation.present.options: UIViewAnimationOptions

Dismiss animation settings

// damping value for the animation block. `1.0` by default.
settings.animation.dismiss.damping: CGFloat
// Used as delay for the animation block. `0.0` by default.
settings.animation.dismiss.delay: TimeInterval
// animation duration. `0.7` by default.
settings.animation.dismiss.duration: TimeInterval
// springVelocity for the animation block. `0.0` by default
settings.animation.dismiss.springVelocity: CGFloat
// dismiss animation options. `UIViewAnimationOptions.curveEaseIn` by default
settings.animation.dismiss.options: UIViewAnimationOptions

StatusBar Style

// Indicates if the status bar should be visible or hidden when the action controller is visible. Its default value is `true`
settings.statusBar.showStatusBar: Bool
// Determines the style of the device’s status bar when the action controller is visible. `UIStatusBarStyle.LightContent` by default.
settings.statusBar.style: UIStatusBarStyle
// Determines whether the action controller takes over control of status bar appearance from the presenting view controller. `true` by default.
settings.statusBar.modalPresentationCapturesStatusBarAppearance: Bool

Cancel view style

Sometimes we need to show a cancel view below the collection view. This is the case of the SpotifyActionController. These properties have nothing to do with the actions added to an action Controller nor with the actions with .Cancel as style value.

 // Indicates if the cancel view is shown. `false` by default.
settings.cancelView.showCancel: Bool
 // Cancel view's title. "Cancel" by default.
settings.cancelView.title: String?
 // Cancel view's height. `60` by default.
settings.cancelView.height: CGFloat
 // Cancel view's background color. `UIColor.black.withAlphaComponent(0.8)` by default.
settings.cancelView.backgroundColor: UIColor
// Indicates if the collection view is partially hidden by the cancelView when it is pulled down.
settings.cancelView.hideCollectionViewBehindCancelView: Bool

Advanced animations

If tweaking previous settings is not enough to make the animations work like you want, XLActionController allows you to change the present/dismiss animation by overriding some functions.

Presentation

open func presentView(_ presentedView: UIView, presentingView: UIView, animationDuration: Double, completion: ((_ completed: Bool) -> Void)?)

The function above is responsible for making the present animation. It encapsulates how the presentation is performed and invokes onWillPresentView, performCustomPresentationAnimation and onDidPresentView to allow you to change a specific point of the animation.

Typically we don't need to override presentView function because overriding either onWillPresentView, performCustomPresentationAnimation or onDidPresentView is enough.

open func onWillPresentView()

onWillPresentView is called before the animation block starts. Any change here won't be animated. It's intended to set the initial animated properties values.

open func performCustomPresentationAnimation(_ presentedView: UIView, presentingView: UIView)

performCustomPresentationAnimation is called from within the main animation block.

open func onDidPresentView()

After the present animation is completed, presentView calls onDidPresentView from within completion callback.

onWillPresentView, performCustomPresentationAnimation, onDidPresentView won't be invoked if you override presentView implementation.

Dismissal

Dismissal animation can be customized in the same way as presentation animation.

open func dismissView(_ presentedView: UIView, presentingView: UIView, animationDuration: Double, completion: ((_ completed: Bool) -> Void)?)

The function above is responsible for making the dismissal animation. It encapsulates how the dismissal animation is performed and invokes onWillDismissView, performCustomDismissingAnimation and onDidDismissView to allow you to change an specific point of the animation.

Typically we don't need to override dismissView method because overriding either onWillDismissView, performCustomDismissingAnimationoronDidDismissView` is enough.

open func onWillDismissView()

Overrides onWillDismissView to perform any set up before the animation begins.

open func performCustomDismissingAnimation(_ presentedView: UIView, presentingView: UIView)

performCustomDismissingAnimation function is invoked from within the main animation block.

open func onDidDismissView()

After the dismissal animation completes, dismissView calls onDidDismissView from within completion callback.

onWillDismissView, performCustomDismissingAnimation, onDidDismissView won't be invoked if you override dismissView implementation.

To show how simple and powerful XLActionController is and give several examples of how to extend ActionController we have mimicked the Skype, Tweetbot, Twitter, Youtube, Periscope and Spotify action controllers.

Requirements

  • iOS 9.3+
  • Xcode 10.2+
  • Swift 5.0+

Getting involved

  • If you want to contribute please feel free to submit pull requests.
  • If you have a feature request please open an issue.
  • If you found a bug or need help please check older issues before submitting an issue.

If you use XLActionController in your app we would love to hear about it! Drop us a line on twitter.

Installation

CocoaPods

CocoaPods is a dependency manager for Cocoa projects.

Specify XLActionController into your project's Podfile:

source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!

target '<Your App Target>' do
  # This will install just the library's core, won't include any examples
  pod 'XLActionController'

  # Uncomment depending on the examples that you want to install
  #pod 'XLActionController/Periscope'
  #pod 'XLActionController/Skype'
  #pod 'XLActionController/Spotify'
  #pod 'XLActionController/Tweetbot'
  #pod 'XLActionController/Twitter'
  #pod 'XLActionController/Youtube'
end

Then run the following command:

$ pod install

Carthage

Carthage is a simple, decentralized dependency manager for Cocoa.

Specify XLActionController into your project's Carthage:

github "xmartlabs/XLActionController" ~> 5.1.0

Manually as Embedded Framework

Clone XLActionController as a git submodule by running the following command from your project root git folder.

$ git submodule add https://github.com/xmartlabs/XLActionController.git

Open XLActionController folder that was created by the previous git submodule command and drag the XLActionController.xcodeproj into the Project Navigator of your application's Xcode project.

Select the XLActionController.xcodeproj in the Project Navigator and verify the deployment target matches with your application deployment target.

Select your project in the Xcode Navigation and then select your application target from the sidebar. Next select the "General" tab and click on the + button under the "Embedded Binaries" section.

Select XLActionController.framework and we are done!

Authors

License

XLActionController is released under MIT license and copyrighted by Xmartlabs SRL.

More Repositories

1

Eureka

Elegant iOS form builder in Swift
Swift
11,705
star
2

XLPagerTabStrip

Android PagerTabStrip for iOS.
Swift
6,880
star
3

XLForm

XLForm is the most flexible and powerful iOS library to create dynamic table-view forms. Fully compatible with Swift & Obj-C.
Objective-C
5,790
star
4

Bender

Easily craft fast Neural Networks on iOS! Use TensorFlow models. Metal under the hood.
Swift
1,795
star
5

PagerTabStripView

πŸš€ Elegant Pager View fully written in pure SwiftUI.
Swift
722
star
6

Xniffer

A swift network profiler built on top of URLSession.
Swift
502
star
7

XLRemoteImageView

UIImageView that shows a progress indicator while the image is loading from server. It makes use of AFNetworking. It looks like the Instagram loading indicator.
Objective-C
346
star
8

fountain

Android Kotlin paged endpoints made easy
Kotlin
169
star
9

XLData

Elegant and concise way to load and show data sets into table and collection view. It supports AFNetworking, Core Data and memory data stores.
Objective-C
165
star
10

XLSlidingContainer

XLSlidingContainer is a custom container controller that embeds two independent view controllers allowing to easily maximize any of them using gestures.
Swift
149
star
11

android-snapshot-publisher

Gradle plugin to deploy Android Snapshot Versions
Kotlin
145
star
12

Swift-Project-Template

Script to easily create an iOS project base code!
Swift
144
star
13

Swift-Framework-Template

Swift script to easily create Swift frameworks!
Swift
143
star
14

react-native-line

Line SDK wrapper for React Native πŸš€
TypeScript
119
star
15

Ecno

Ecno is a task state manager built on top of UserDefaults in pure Swift 4.
Swift
102
star
16

XLSwiftKit

Helpers and extensions for Swift
Swift
101
star
17

XLMediaZoom

UI controls to view an image or reproduce a video in fullscreen like Instagram does.
Swift
92
star
18

XLMailBoxContainer

Custom container view controller ala MailBox app.
Objective-C
90
star
19

gong

Xmartlabs' Android Base Project Template
Kotlin
89
star
20

cordova-plugin-market

Cordova Plugin that allows you to access native Marketplace app (aka Google Play, App Store) from your app
Java
87
star
21

stock

Dart package for Async Data Loading and Caching. Combine local (DB, cache) and network data simply and safely.
Dart
74
star
22

Opera

Protocol-Oriented Network abstraction layer written in Swift.
Swift
74
star
23

Ahoy

A lightweight swift library to build onboarding experiences.
Swift
52
star
24

bigbang

Android base project used by Xmartlabs team
Kotlin
50
star
25

MetalPerformanceShadersProxy

A proxy for MetalPerformanceShaders which takes to a stub on a simulator and to the real implementation on iOS devices.
Objective-C
45
star
26

Swift-Style-Guide

Swift language style guide & coding conventions followed by Xmartlabs.
44
star
27

RxSimpleNoSQL

Reactive extensions for SimpleNoSQL
Java
37
star
28

docker-jenkins-android

Jenkins docker image for Android development
36
star
29

docker-htpasswd

Docker image to create a htpasswd file
32
star
30

spoter-embeddings

Create embeddings from sign pose videos using Transformers
Python
30
star
31

TypedNavigation

A lightweight library to help you navigate in compose with well typed functions.
Kotlin
23
star
32

XLMapChat

A chat application running on Node.js, using Socket.IO, GMaps, and more...
JavaScript
22
star
33

flutter-template

Xmartlabs' Flutter Base Project
Dart
17
star
34

XLDataLoader

Objective-C
16
star
35

tf_tabular

Easily build TensorFlow models on tabular data
Python
16
star
36

dreamsnap

Real life through the eyes of an artist
CSS
15
star
37

bigbang-template

Android template used by Xmartlabs team
Kotlin
14
star
38

blog

Xmartlabs Blog
CSS
14
star
39

MLKitTest

Source code related to a blog post about ML Kit
Swift
13
star
40

benderthon

Set of utilities to work easier with Bender.
Python
13
star
41

XLiOSKit

Objective-C
13
star
42

python-template

Python
9
star
43

react-template-xmartlabs

This is an internal private project - aims to be at some point in the future our React base project
TypeScript
9
star
44

gpgpu-comparison

9
star
45

XmartRecyclerView

A smart, simple and fast RecyclerView library
Java
8
star
46

jared-landing

Landing page for Jared Bot.
HTML
8
star
47

Fastlane-CI-Files

Fastlane CI files
Ruby
8
star
48

fluttips

Flutter trips and tricks
JavaScript
8
star
49

Android-Style-Guide

Style guide for Android by Xmartlabs
7
star
50

XLMaterialCalendarView

MaterialCalendarView powered with reactive bindings and with the Java 8 Time API!
Java
6
star
51

rnx-cli

TypeScript
6
star
52

docker-android

Docker image for Android development.
6
star
53

gh-top-repos-users

Download the contributors of the top repos.
Python
5
star
54

BuildSlackNotifier

Jenkins plugin to send Android build results through a slack channel using incoming webhooks
Java
4
star
55

javascript-plugin

Source Code of blog Making a JS widget: a full-stack approach
Ruby
4
star
56

AndroidSwissKnife

Kotlin
3
star
57

projecthub-landing

ProjectHub lets you manage your GitHub Projects on the fly, from your mobile phone.
HTML
3
star
58

gh2s3

Download GitHub Archive data and upload it to an Amazon S3 bucket.
Python
3
star
59

xmartchat

Dart
3
star
60

pycon-es-workshop

Python
2
star
61

xl-blog

Gatsby XL's Blogpost
JavaScript
2
star
62

node-template

Base template for starting a new Node project
TypeScript
2
star
63

mPOS-SDK-iOS

Objective-C
1
star
64

terraform

HCL
1
star
65

FastlaneDemo

Demo project to show a basic fastlane configuration
Swift
1
star
66

xl-school-automation-web

This is the repository for web automation training for XL
Java
1
star
67

workshop-microservicios

Python
1
star
68

fountain-docs

Fountain documentation
1
star
69

client-side-widget-template

JavaScript templates for a client-side widget
HTML
1
star
70

rn-lightbox

JavaScript
1
star
71

malaria-detector

Jupyter Notebook
1
star
72

SQLiteDSL

1
star
73

terraform-basic-infra

HCL
1
star
74

docker-pcl-cmake

Docker image containing PCL and CMake.
1
star
75

cocoapods-specs

Ruby
1
star
76

simon-ai

This is the repository for the XL Says initiative
Dart
1
star