Commit 787aacb9 by Daniel Dahan

development: Material 2.5.0 release

parent 14ca5674
Pod::Spec.new do |s|
s.name = 'Material'
s.version = '2.4.19'
s.version = '2.5.0'
s.license = 'BSD-3-Clause'
s.summary = 'An animation and graphics framework for Material Design in Swift.'
s.homepage = 'http://materialswift.com'
......
......@@ -7,12 +7,10 @@
objects = {
/* Begin PBXBuildFile section */
961409AF1E43D15C00E7BA99 /* BottomSheetController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961730711E16F00C00A9A297 /* BottomSheetController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
961409B01E43D15C00E7BA99 /* CollectionViewCard.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961730591E145DE900A9A297 /* CollectionViewCard.swift */; settings = {ATTRIBUTES = (Public, ); }; };
961409B11E43D15C00E7BA99 /* FABMenu.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96A183621E0C6CE200083C30 /* FABMenu.swift */; settings = {ATTRIBUTES = (Public, ); }; };
961409B21E43D15C00E7BA99 /* FABMenuController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96A183641E0C6DD400083C30 /* FABMenuController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617305A1E145DE900A9A297 /* CollectionViewCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961730591E145DE900A9A297 /* CollectionViewCard.swift */; };
961730721E16F00C00A9A297 /* BottomSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 961730711E16F00C00A9A297 /* BottomSheetController.swift */; };
9617B07D1DFCA8CF00410F8F /* Application.swift in Headers */ = {isa = PBXBuildFile; fileRef = 961E6BDE1DDA2A95004E6C93 /* Application.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B07E1DFCA8CF00410F8F /* Card.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB75D1CB40DC500C806FE /* Card.swift */; settings = {ATTRIBUTES = (Public, ); }; };
9617B07F1DFCA8CF00410F8F /* ImageCard.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7621CB40DC500C806FE /* ImageCard.swift */; settings = {ATTRIBUTES = (Public, ); }; };
......@@ -100,7 +98,6 @@
965E810A1DD4D5C800D61E4B /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB77B1CB40DC500C806FE /* Font.swift */; };
965E810B1DD4D5C800D61E4B /* RobotoFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7941CB40DC500C806FE /* RobotoFont.swift */; };
965E810C1DD4D5C800D61E4B /* DynamicFontType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9628645E1D540AF300690B69 /* DynamicFontType.swift */; };
965E810D1DD4D5C800D61E4B /* Menu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB78E1CB40DC500C806FE /* Menu.swift */; };
965E81101DD4D5C800D61E4B /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7901CB40DC500C806FE /* NavigationBar.swift */; };
965E81111DD4D5C800D61E4B /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7911CB40DC500C806FE /* NavigationController.swift */; };
965E81121DD4D5C800D61E4B /* NavigationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96BCB7921CB40DC500C806FE /* NavigationItem.swift */; };
......@@ -162,7 +159,6 @@
96BCB83B1CB4115200C806FE /* NavigationDrawerController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7971CB40DC500C806FE /* NavigationDrawerController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB83C1CB4115200C806FE /* Bar.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7981CB40DC500C806FE /* Bar.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB83D1CB4115200C806FE /* RootController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7991CB40DC500C806FE /* RootController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB83F1CB4115200C806FE /* Menu.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB78E1CB40DC500C806FE /* Menu.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB8441CB4115200C806FE /* BottomNavigationController.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7581CB40DC500C806FE /* BottomNavigationController.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB8451CB4115200C806FE /* BottomTabBar.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7591CB40DC500C806FE /* BottomTabBar.swift */; settings = {ATTRIBUTES = (Public, ); }; };
96BCB8461CB4115200C806FE /* NavigationBar.swift in Headers */ = {isa = PBXBuildFile; fileRef = 96BCB7901CB40DC500C806FE /* NavigationBar.swift */; settings = {ATTRIBUTES = (Public, ); }; };
......@@ -199,7 +195,6 @@
/* Begin PBXFileReference section */
961276621DCD8B1800A7D920 /* CharacterAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterAttribute.swift; sourceTree = "<group>"; };
961730591E145DE900A9A297 /* CollectionViewCard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewCard.swift; sourceTree = "<group>"; };
961730711E16F00C00A9A297 /* BottomSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BottomSheetController.swift; sourceTree = "<group>"; };
961DED451DCC40C500F425B6 /* Editor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Editor.swift; sourceTree = "<group>"; };
961DED4A1DCC546100F425B6 /* EditorController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditorController.swift; sourceTree = "<group>"; };
961E6BDE1DDA2A95004E6C93 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
......@@ -267,7 +262,6 @@
96BCB7881CB40DC500C806FE /* Switch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Switch.swift; sourceTree = "<group>"; };
96BCB7891CB40DC500C806FE /* TableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = "<group>"; };
96BCB78C1CB40DC500C806FE /* View.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = "<group>"; };
96BCB78E1CB40DC500C806FE /* Menu.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = Menu.swift; sourceTree = "<group>"; tabWidth = 4; };
96BCB7901CB40DC500C806FE /* NavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = "<group>"; };
96BCB7911CB40DC500C806FE /* NavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = "<group>"; };
96BCB7921CB40DC500C806FE /* NavigationItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItem.swift; sourceTree = "<group>"; };
......@@ -317,14 +311,6 @@
name = TextField;
sourceTree = "<group>";
};
9617306E1E16EFE300A9A297 /* BottomSheet */ = {
isa = PBXGroup;
children = (
961730711E16F00C00A9A297 /* BottomSheetController.swift */,
);
name = BottomSheet;
sourceTree = "<group>";
};
961DED441DCC40AC00F425B6 /* Editor */ = {
isa = PBXGroup;
children = (
......@@ -470,7 +456,6 @@
963FBF021D6696D0008F8512 /* Menu */ = {
isa = PBXGroup;
children = (
96BCB78E1CB40DC500C806FE /* Menu.swift */,
96A183621E0C6CE200083C30 /* FABMenu.swift */,
96A183641E0C6DD400083C30 /* FABMenuController.swift */,
);
......@@ -536,7 +521,6 @@
children = (
961E6BDD1DDA2A7E004E6C93 /* Application */,
96264BE41D833C8400576F37 /* Bar */,
9617306E1E16EFE300A9A297 /* BottomSheet */,
962DDD081D6FBBD0001C307C /* BottomTabBar */,
96BCB8031CB40F4B00C806FE /* Button */,
96BCB8021CB40F3B00C806FE /* Card */,
......@@ -799,7 +783,6 @@
96BCB83B1CB4115200C806FE /* NavigationDrawerController.swift in Headers */,
96BCB83C1CB4115200C806FE /* Bar.swift in Headers */,
96BCB83D1CB4115200C806FE /* RootController.swift in Headers */,
96BCB83F1CB4115200C806FE /* Menu.swift in Headers */,
96BCB8441CB4115200C806FE /* BottomNavigationController.swift in Headers */,
96BCB8451CB4115200C806FE /* BottomTabBar.swift in Headers */,
96BCB8461CB4115200C806FE /* NavigationBar.swift in Headers */,
......@@ -857,7 +840,6 @@
96328B9D1E05C24E009A4C90 /* RemindersController.swift in Headers */,
96328B9E1E05C24E009A4C90 /* TableView.swift in Headers */,
96328B9F1E05C24E009A4C90 /* TableViewController.swift in Headers */,
961409AF1E43D15C00E7BA99 /* BottomSheetController.swift in Headers */,
961409B01E43D15C00E7BA99 /* CollectionViewCard.swift in Headers */,
961409B11E43D15C00E7BA99 /* FABMenu.swift in Headers */,
961409B21E43D15C00E7BA99 /* FABMenuController.swift in Headers */,
......@@ -963,7 +945,6 @@
965E810A1DD4D5C800D61E4B /* Font.swift in Sources */,
965E810B1DD4D5C800D61E4B /* RobotoFont.swift in Sources */,
965E810C1DD4D5C800D61E4B /* DynamicFontType.swift in Sources */,
965E810D1DD4D5C800D61E4B /* Menu.swift in Sources */,
96A183651E0C6DD400083C30 /* FABMenuController.swift in Sources */,
965E81101DD4D5C800D61E4B /* NavigationBar.swift in Sources */,
965E81111DD4D5C800D61E4B /* NavigationController.swift in Sources */,
......@@ -1018,7 +999,6 @@
965E80D51DD4C50600D61E4B /* Grid.swift in Sources */,
965E80D61DD4C50600D61E4B /* HeightPreset.swift in Sources */,
961E6BDF1DDA2A95004E6C93 /* Application.swift in Sources */,
961730721E16F00C00A9A297 /* BottomSheetController.swift in Sources */,
965E80D71DD4C50600D61E4B /* Icon.swift in Sources */,
965E80FC1DD4D59500D61E4B /* SearchBarController.swift in Sources */,
965E80D81DD4C50600D61E4B /* Layer.swift in Sources */,
......
......@@ -6,7 +6,7 @@ Material is an animation and graphics framework for Material Design in Swift.
![Material Sample](http://cosmicmind.com/samples/github/page-tab-bar-controller-2.png)
* [Download the latest sample](https://github.com/CosmicMind/Samples/tree/master/Graph/CardTableView).
* [Download the latest sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/CardTableView).
## Use Motion With Material
......@@ -14,7 +14,7 @@ Motion is a new tool used to create transition animations between view controlle
## Sample
Take a look at a sample [Photo Collection](https://github.com/CosmicMind/Samples/tree/master/Motion/PhotoCollection) project.
Take a look at a sample [Photo Collection](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/PhotoCollection) project.
![Motion Photo Collection Sample](http://www.cosmicmind.com/motion/cosmicmind_motion_sample.gif)
......@@ -28,7 +28,7 @@ Take a look at a sample [Photo Collection](https://github.com/CosmicMind/Samples
- [x] Photo Library
- [x] Photo & Video
- [x] Cards
- [x] Menus
- [x] FABMenu
- [x] Icons
- [x] TextField
- [X] Snackbar
......@@ -93,7 +93,7 @@ A TextField is an excellent way to improve UX. It allows for a placeholder and a
![TextField](http://www.cosmicmind.com/gifs/white/text-field.gif)
* Download the complete [TextField sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/TextField).
* Download the complete [TextField sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/TextField).
* Learn more about [TextField](http://cosmicmind.com/material/textfield).
......@@ -103,7 +103,7 @@ A button is used to trigger an action through a touch event. Material comes with
![Material Image](http://www.cosmicmind.com/material/white/button.gif)
* Download the complete [Button sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/Button).
* Download the complete [Button sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/Button).
* Learn more about [Button](http://cosmicmind.com/material/button).
......@@ -113,7 +113,7 @@ A switch is a control component that toggles between on and off states.
![Material Image](http://www.cosmicmind.com/material/white/switch.gif)
* Download the complete [Switch sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/Switch).
* Download the complete [Switch sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/Switch).
* Learn more about [Switch](http://cosmicmind.com/material/switch).
......@@ -123,7 +123,7 @@ A Card is a flexible component that may be configured in any way you like. It ha
![Material Image](http://www.cosmicmind.com/gifs/white/card.gif)
* Download the complete [Card sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/Card).
* Download the complete [Card sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/Card).
* Learn more about [Card](http://cosmicmind.com/material/card).
......@@ -133,7 +133,7 @@ An ImageCard is an expansion of the base Card. The Toolbar overlays an image are
![Material Image](http://www.cosmicmind.com/gifs/white/image-card.gif)
* Download the complete [ImageCard sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/ImageCard).
* Download the complete [ImageCard sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/ImageCard).
* Learn more about [ImageCard](http://cosmicmind.com/material/imagecard).
* Learn how to make the ImageCard data-driven with [Graph's ImageCard sample](https://github.com/CosmicMind/Samples/tree/master/Graph/ImageCard).
......@@ -144,18 +144,18 @@ The PresenterCard is a completely new card style. It allows for a primary presen
![Material Image](http://www.cosmicmind.com/gifs/white/presenter-card.gif)
* Download the complete [PresenterCard sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/PresenterCard).
* Download the complete [PresenterCard sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/PresenterCard).
* Learn more about [PresenterCard](http://cosmicmind.com/material/presentercard).
## Menu
## FABMenu
A Menu manages a collection of views. A new MenuItem type has been added that manages a title and button to improve UX and visual beauty.
A FABMenu manages a collection of views. A new MenuItem type has been added that manages a title and button to improve UX and visual beauty.
![Material Image](http://www.cosmicmind.com/material/white/menu-controller.gif)
* Download the complete [Menu sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/MenuController).
* Learn more about [Menu](http://cosmicmind.com/material/menu).
* Download the complete [FABMenu sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/FABMenuController).
* Learn more about [FABMenu](http://cosmicmind.com/material/fabmenu).
## Toolbar
......@@ -164,7 +164,7 @@ Toolbars are super flexible and add excellent control to your navigation flow. T
![Material Image](http://www.cosmicmind.com/gifs/white/toolbar-controller.gif)
* Download the complete [Toolbar sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/ToolbarController).
* Download the complete [Toolbar sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/ToolbarController).
* Learn more about [Toolbar](http://cosmicmind.com/material/toolbar).
......@@ -174,9 +174,9 @@ A SearchBar is a powerful navigation tool that allows for user's input with an i
![Material Image](http://www.cosmicmind.com/gifs/shared/search-bar-controller.gif)
* Download the complete [SearchBar sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/SearchBarController).
* Download the complete [SearchBar sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/SearchBarController).
* Learn more about [SearchBar](http://cosmicmind.com/material/searchbar).
* Learn how to make the SearchBar data-driven with [Graph's Search sample](https://github.com/CosmicMind/Samples/tree/master/Graph/Search).
* Learn how to make the SearchBar data-driven with [Graph's Search sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/Search).
## PageTabBar
......@@ -185,7 +185,7 @@ A PageTabBar is a new component that links a customizable TabBar to a UIPageView
![Material Image](http://www.cosmicmind.com/material/white/page-tab-bar-controller.gif)
* Download the complete [PageTabBar sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/PageTabBarController).
* Download the complete [PageTabBar sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/PageTabBarController).
* Learn more about [PageTabBar](http://cosmicmind.com/material/pagetabbar).
......@@ -195,7 +195,7 @@ A NavigationController is a specialized view controller that manages a hierarchy
![Material Image](http://www.cosmicmind.com/gifs/white/navigation-controller.gif)
* Download the complete [NavigationController sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/NavigationController).
* Download the complete [NavigationController sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/NavigationController).
* Learn more about [NavigationController](http://cosmicmind.com/material/navigationcontroller).
......@@ -205,7 +205,7 @@ A NavigationDrawer slides in from the left or right and contains the navigation
![Material Image](http://www.cosmicmind.com/material/shared/navigation-drawer-controller.gif)
* Download the complete [NavigationDrawer sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/NavigationDrawerController).
* Download the complete [NavigationDrawer sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/NavigationDrawerController).
* Learn more about [NavigationDrawer](http://cosmicmind.com/material/navigationdrawer).
......@@ -215,7 +215,7 @@ A Snackbar is a new component that is very simple in its behavior and very power
![Material Image](http://www.cosmicmind.com/material/white/snackbar-controller.gif)
* Download the complete [Snackbar sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/SnackbarController).
* Download the complete [Snackbar sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/SnackbarController).
* Learn more about [Snackbar](http://cosmicmind.com/material/snackbar).
......@@ -225,7 +225,7 @@ PhotoLibrary is a new component that simplifies the Photos framework and allows
![Material Image](http://www.cosmicmind.com/material/shared/photolibrary-controller.png)
* Download the complete [PhotoLibrary sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/PhotoLibraryController).
* Download the complete [PhotoLibrary sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/PhotoLibraryController).
* Learn more about [PhotoLibrary](http://cosmicmind.com/material/photolibrary).
......@@ -235,7 +235,7 @@ Capture is an API that simplifies iOS' AVFoundation framework. It allows for pho
![Material Image](http://www.cosmicmind.com/material/shared/capture-controller.png)
* Download the complete [Capture sample](https://github.com/CosmicMind/Samples/tree/master/Material/Programmatic/CaptureController).
* Download the complete [Capture sample](https://github.com/CosmicMind/Samples/tree/master/Projects/Programmatic/CaptureController).
* Learn more about [Capture](http://cosmicmind.com/material/capture).
......
......@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.4.19</string>
<string>2.5.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
......
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@objc(BottomSheetFABButtonPosition)
public enum BottomSheetFABButtonPosition: Int {
case left
case right
case center
}
@objc(BottomSheetStyle)
public enum BottomSheetStyle: Int {
case modal
case persistent
}
open class BottomSheet: View {
/// Handler for when the fabButton is set.
fileprivate var fabButtonWasSet: ((FABButton?) -> Void)?
/// A reference to a FABButton.
open var fabButton: FABButton? {
didSet {
fabButtonWasSet?(fabButton)
fabButton?.zPosition = 6000
layoutSubviews()
}
}
/// A reference to the BottomSheetFABButtonPosition.
open var fabButtonPostion = BottomSheetFABButtonPosition.right
/// A reference to the fabButtonEdgeInsetsPreset.
open var fabButtonEdgeInsetsPreset = EdgeInsetsPreset.none {
didSet {
fabButtonEdgeInsets = EdgeInsetsPresetToValue(preset: fabButtonEdgeInsetsPreset)
}
}
/// A reference to the fabButtonEdgeInsets.
open var fabButtonEdgeInsets = EdgeInsets.zero {
didSet {
layoutSubviews()
}
}
open override func layoutSubviews() {
super.layoutSubviews()
if let v = fabButton {
if nil == v.superview {
v.removeFromSuperview()
addSubview(v)
}
var point = center
point.y = fabButtonEdgeInsets.top - fabButtonEdgeInsets.bottom
switch fabButtonPostion {
case .left:
point.x = v.bounds.width / 2 + fabButtonEdgeInsets.left
case .right:
point.x = bounds.width - v.bounds.width / 2 - fabButtonEdgeInsets.right
case .center:break
}
v.center = point
}
}
open override func prepare() {
super.prepare()
fabButtonEdgeInsetsPreset = .horizontally5
}
}
extension BottomSheet {
/**
Handles the hit test for the fabButton.
- Parameter _ point: A CGPoint.
- Parameter with event: An optional UIEvent.
- Returns: An optional UIView.
*/
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard let v = fabButton else {
return super.hitTest(point, with: event)
}
let p = v.convert(point, from: self)
if v.bounds.contains(p) {
return v.hitTest(p, with: event)
}
return super.hitTest(point, with: event)
}
}
extension UIViewController {
/**
A convenience property that provides access to the BottomSheetController.
This is the recommended method of accessing the BottomSheetController
through child UIViewControllers.
*/
public var bottomSheetController: BottomSheetController? {
var viewController: UIViewController? = self
while nil != viewController {
if viewController is BottomSheetController {
return viewController as? BottomSheetController
}
viewController = viewController?.parent
}
return nil
}
}
@objc(BottomSheetControllerDelegate)
public protocol BottomSheetControllerDelegate {
/**
An optional delegation method that is fired before the
BottomSheetController opens.
- Parameter bottomViewController: A BottomSheetController.
*/
@objc
optional func bottomViewControllerWillOpen(bottomViewController: BottomSheetController)
/**
An optional delegation method that is fired after the
BottomSheetController opened.
- Parameter bottomViewController: A BottomSheetController.
*/
@objc
optional func bottomViewControllerDidOpen(bottomViewController: BottomSheetController)
/**
An optional delegation method that is fired before the
BottomSheetController closes.
- Parameter bottomViewController: A BottomSheetController.
*/
@objc
optional func bottomViewControllerWillClose(bottomViewController: BottomSheetController)
/**
An optional delegation method that is fired after the
BottomSheetController closed.
- Parameter bottomViewController: A BottomSheetController.
*/
@objc
optional func bottomViewControllerDidClose(bottomViewController: BottomSheetController)
/**
An optional delegation method that is fired when the
BottomSheetController pan gesture begins.
- Parameter bottomViewController: A BottomSheetController.
- Parameter didBeginPanAt point: A CGPoint.
*/
@objc
optional func bottomViewController(bottomViewController: BottomSheetController, didBeginPanAt point: CGPoint)
/**
An optional delegation method that is fired when the
BottomSheetController pan gesture changes position.
- Parameter bottomViewController: A BottomSheetController.
- Parameter didChangePanAt point: A CGPoint.
*/
@objc
optional func bottomViewController(bottomViewController: BottomSheetController, didChangePanAt point: CGPoint)
/**
An optional delegation method that is fired when the
BottomSheetController pan gesture ends.
- Parameter bottomViewController: A BottomSheetController.
- Parameter didEndPanAt point: A CGPoint.
*/
@objc
optional func bottomViewController(bottomViewController: BottomSheetController, didEndPanAt point: CGPoint)
/**
An optional delegation method that is fired when the
BottomSheetController tap gesture executes.
- Parameter bottomViewController: A BottomSheetController.
- Parameter didTapAt point: A CGPoint.
*/
@objc
optional func bottomViewController(bottomViewController: BottomSheetController, didTapAt point: CGPoint)
}
@objc(BottomSheetController)
open class BottomSheetController: RootController {
/**
A CGFloat property that is used internally to track
the original (x) position of the container view when panning.
*/
fileprivate var originalY: CGFloat = 0
/**
A UIPanGestureRecognizer property internally used for the
bottomSheet pan gesture.
*/
internal fileprivate(set) var bottomPanGesture: UIPanGestureRecognizer?
/**
A UITapGestureRecognizer property internally used for the
bottomSheet tap gesture.
*/
internal fileprivate(set) var bottomTapGesture: UITapGestureRecognizer?
/**
A CGFloat property that accesses the bottomSheet threshold of
the BottomSheetController. When the panning gesture has
ended, if the position is beyond the threshold,
the bottomSheet is opened, if it is below the threshold, the
bottomSheet is closed.
*/
@IBInspectable
open var bottomThreshold: CGFloat = 64
fileprivate var bottomSheetThreshold: CGFloat = 0
/// A preset for bottomSheetClosedThreshold.
open var bottomSheetClosedThresholdPreset = HeightPreset.none {
didSet {
bottomSheetClosedThreshold = CGFloat(bottomSheetClosedThresholdPreset.rawValue)
}
}
/// The height the BottomSheet should leave open when a FABButton exists.
open var bottomSheetClosedThreshold: CGFloat = 0 {
didSet {
layoutSubviews()
}
}
/**
A BottomSheetControllerDelegate property used to bind
the delegation object.
*/
open weak var delegate: BottomSheetControllerDelegate?
/**
A CGFloat property that sets the animation duration of the
bottomSheet when closing and opening. Defaults to 0.25.
*/
@IBInspectable
open var animationDuration: TimeInterval = 0.25
/**
A Boolean property that enables and disables the bottomSheet from
opening and closing. Defaults to true.
*/
@IBInspectable
open var isEnabled: Bool {
get {
return isBottomSheetEnabled
}
set(value) {
isBottomSheetEnabled = value
}
}
/**
A Boolean property that enables and disables the bottomSheet from
opening and closing. Defaults to true.
*/
@IBInspectable
open var isBottomSheetEnabled = false {
didSet {
isBottomPanGestureEnabled = isBottomSheetEnabled
isBottomTapGestureEnabled = isBottomSheetEnabled
}
}
/// Enables the left pan gesture.
@IBInspectable
open var isBottomPanGestureEnabled = false {
didSet {
if isBottomPanGestureEnabled {
prepareBottomPanGesture()
} else {
removeBottomPanGesture()
}
}
}
/// Enables the left tap gesture.
@IBInspectable
open var isBottomTapGestureEnabled = false {
didSet {
if isBottomTapGestureEnabled {
prepareBottomTapGesture()
} else {
removeBottomTapGesture()
}
}
}
/**
A DepthPreset property that is used to set the depth of the
bottomSheet when opened.
*/
open var depthPreset = DepthPreset.depth1
/**
A UIView property that is used to hide and reveal the
bottomViewController. It is very rare that this property will
need to be accessed externally.
*/
open let bottomSheet = BottomSheet()
/// Indicates whether the bottomSheet or rightView is opened.
open var isOpened: Bool {
return isBottomSheetOpened
}
/// indicates if the bottomSheet is opened.
open var isBottomSheetOpened: Bool {
return bottomSheet.y != Screen.height
}
/**
A UIViewController property that references the
active left UIViewController.
*/
open fileprivate(set) var bottomViewController: UIViewController?
/**
A CGFloat property to access the width that the bottomSheet
opens up to.
*/
@IBInspectable
open fileprivate(set) var bottomSheetHeight: CGFloat = 0
/// Determines the layout style for the bottomSheet.
open var bottomSheetStyle = BottomSheetStyle.modal {
didSet {
switch bottomSheetStyle {
case .modal:
depthPreset = .depth1
isBottomPanGestureEnabled = true
isBottomTapGestureEnabled = true
case .persistent:
depthPreset = .none
isBottomPanGestureEnabled = false
isBottomTapGestureEnabled = false
}
}
}
/**
An initializer that initializes the object with a NSCoder object.
- Parameter aDecoder: A NSCoder instance.
*/
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepare()
}
/**
An initializer that initializes the object with an Optional nib and bundle.
- Parameter nibNameOrNil: An Optional String for the nib.
- Parameter bundle: An Optional NSBundle where the nib is located.
*/
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
prepare()
}
/**
An initializer for the BottomSheetController.
- Parameter rootViewController: The main UIViewController.
- Parameter bottomViewController: An Optional left UIViewController.
*/
public init(rootViewController: UIViewController, bottomViewController: UIViewController? = nil) {
super.init(rootViewController: rootViewController)
self.bottomViewController = bottomViewController
prepare()
}
/// Layout subviews.
open override func layoutSubviews() {
super.layoutSubviews()
bottomSheet.width = view.bounds.width
bottomSheet.height = bottomSheetHeight
bottomSheetThreshold = view.bounds.height - bottomSheetHeight / 2
if .persistent == bottomSheetStyle {
rootViewController.view.height = view.bounds.height - bottomSheetHeight
}
guard let vc = bottomViewController else {
return
}
vc.view.width = bottomSheet.bounds.width
vc.view.height = bottomSheetHeight
vc.view.center = CGPoint(x: bottomSheet.bounds.width / 2, y: bottomSheetHeight / 2)
}
/**
Prepares the view instance when intialized. When subclassing,
it is recommended to override the prepare method
to initialize property values and other setup operations.
The super.prepare method should always be called immediately
when subclassing.
*/
open override func prepare() {
super.prepare()
prepareBottomSheet()
bottomSheetClosedThresholdPreset = .large
bottomSheet.fabButtonWasSet = handleFABButtonWasSet
}
/**
A method that is used to set the width of the bottomSheet when
opened. This is the recommended method of setting the bottomSheet
width.
- Parameter width: A CGFloat value to set as the new width.
- Parameter isHidden: A Boolean value of whether the bottomSheet
should be isHidden after the width has been updated or not.
- Parameter animated: A Boolean value that indicates to animate
the bottomSheet width change.
*/
open func setBottomSheetHeight(height: CGFloat, isHidden: Bool, animated: Bool, duration: TimeInterval = 0.5) {
bottomSheetHeight = height
if animated {
bottomSheet.isShadowPathAutoSizing = false
if isHidden {
UIView.animate(withDuration: duration,
animations: { [weak self, v = bottomSheet, h = bottomSheetHeight] in
guard let s = self else {
return
}
v.bounds.size.height = height
v.position.y = s.view.bounds.height - h / 2
}) { [weak self, v = bottomSheet] _ in
guard let s = self else {
return
}
v.isShadowPathAutoSizing = true
s.layoutSubviews()
s.hideView(container: v)
}
} else {
UIView.animate(withDuration: duration,
animations: { [weak self, v = bottomSheet, h = bottomSheetHeight] in
guard let s = self else {
return
}
v.bounds.size.height = h
v.position.y = s.view.bounds.height - h / 2
}) { [weak self, v = bottomSheet] _ in
guard let s = self else {
return
}
v.isShadowPathAutoSizing = true
s.layoutSubviews()
s.showView(container: v)
}
}
} else {
bottomSheet.bounds.size.height = bottomSheetHeight
if isHidden {
hideView(container: bottomSheet)
bottomSheet.position.y = view.bounds.height - bottomSheetHeight / 2
} else {
bottomSheet.isShadowPathAutoSizing = false
showView(container: bottomSheet)
bottomSheet.position.y = view.bounds.height - bottomSheetHeight / 2
bottomSheet.isShadowPathAutoSizing = true
}
layoutSubviews()
}
}
/**
A method that toggles the bottomSheet opened if previously closed,
or closed if previously opened.
- Parameter velocity: A CGFloat value that sets the
velocity of the user interaction when animating the
bottomSheet. Defaults to 0.
*/
open func toggleBottomSheet(velocity: CGFloat = 0) {
isBottomSheetOpened ? closeBottomSheet(velocity: velocity) : openBottomSheet(velocity: velocity)
}
/**
A method that opens the bottomSheet.
- Parameter velocity: A CGFloat value that sets the
velocity of the user interaction when animating the
bottomSheet. Defaults to 0.
*/
open func openBottomSheet(velocity: CGFloat = 0) {
guard isBottomSheetEnabled else {
return
}
showView(container: bottomSheet)
isUserInteractionEnabled = false
delegate?.bottomViewControllerWillOpen?(bottomViewController: self)
UIView.animate(withDuration: TimeInterval(0 == velocity ? animationDuration : fmax(0.1, fmin(1, Double(bottomSheet.y / velocity)))),
animations: { [weak self, v = bottomSheet] in
guard let s = self else {
return
}
v.position.y = s.view.bounds.height - s.bottomSheetHeight / 2
if .modal == s.bottomSheetStyle {
s.rootViewController.view.alpha = 0.5
}
}) { [weak self] _ in
guard let s = self else {
return
}
s.delegate?.bottomViewControllerDidOpen?(bottomViewController: s)
}
}
/**
A method that closes the bottomSheet.
- Parameter velocity: A CGFloat value that sets the
velocity of the user interaction when animating the
bottomSheet. Defaults to 0.
*/
open func closeBottomSheet(velocity: CGFloat = 0) {
guard isBottomSheetEnabled else {
return
}
isUserInteractionEnabled = true
delegate?.bottomViewControllerWillClose?(bottomViewController: self)
UIView.animate(withDuration: TimeInterval(0 == velocity ? animationDuration : fmax(0.1, fmin(1, Double(bottomSheet.y / velocity)))),
animations: { [weak self, v = bottomSheet] in
guard let s = self else {
return
}
v.position.y = s.view.bounds.height + s.bottomSheetHeight / 2 - (nil == s.bottomSheet.fabButton ? 0 : s.bottomSheetClosedThreshold)
if .modal == s.bottomSheetStyle {
s.rootViewController.view.alpha = 1
}
}) { [weak self, v = bottomSheet] _ in
guard let s = self else {
return
}
if nil == s.bottomSheet.fabButton {
s.hideView(container: v)
}
s.delegate?.bottomViewControllerDidClose?(bottomViewController: s)
}
}
/// A method that removes the passed in pan and bottomSheet tap gesture recognizers.
fileprivate func removeBottomSheetGestures() {
removeBottomPanGesture()
removeBottomTapGesture()
}
/// Removes the left pan gesture.
fileprivate func removeBottomPanGesture() {
guard let v = bottomPanGesture else {
return
}
view.removeGestureRecognizer(v)
bottomPanGesture = nil
}
/// Removes the left tap gesture.
fileprivate func removeBottomTapGesture() {
guard let v = bottomTapGesture else {
return
}
view.removeGestureRecognizer(v)
bottomTapGesture = nil
}
/**
A method that determines whether the passed point is
contained within the bounds of the bottomSheetThreshold
and height of the BottomSheetController view frame
property.
- Parameter point: A CGPoint to test against.
- Returns: A Boolean of the result, true if yes, false
otherwise.
*/
fileprivate func isPointContainedWithinBottomThreshold(point: CGPoint) -> Bool {
return point.y >= view.bounds.height - bottomThreshold
}
/**
A method that determines whether the passed in point is
contained within the bounds of the passed in container view.
- Parameter container: A UIView that sets the bounds to test
against.
- Parameter point: A CGPoint to test whether or not it is
within the bounds of the container parameter.
- Returns: A Boolean of the result, true if yes, false
otherwise.
*/
fileprivate func isPointContainedWithinView(container: UIView, point: CGPoint) -> Bool {
let result = container.bounds.contains(point)
guard false == result, let v = bottomSheet.fabButton else {
return result
}
return v.bounds.contains(v.convert(point, from: container))
}
/**
A method that shows a view.
- Parameter container: A container view.
*/
fileprivate func showView(container: UIView) {
container.isHidden = false
container.depthPreset = depthPreset
}
/**
A method that hides a view.
- Parameter container: A container view.
*/
fileprivate func hideView(container: UIView) {
container.isHidden = true
container.depthPreset = .none
}
}
extension BottomSheetController {
/// A method that prepares the bottomViewController.
fileprivate func prepareBottomSheetController() {
prepare(viewController: bottomViewController, withContainer: bottomSheet)
}
/// A method that prepares the bottomSheet.
fileprivate func prepareBottomSheet() {
bottomSheetHeight = .phone == Device.userInterfaceIdiom ? 280 : 320
bottomSheet.isHidden = true
bottomSheet.width = view.bounds.width
bottomSheet.height = bottomSheetHeight
bottomSheet.position.y = view.bounds.height + bottomSheetHeight / 2
bottomSheet.zPosition = 2000
view.addSubview(bottomSheet)
guard nil != bottomViewController else {
return
}
isBottomSheetEnabled = true
prepareBottomSheetController()
}
/// Prepare the left pan gesture.
fileprivate func prepareBottomPanGesture() {
guard nil == bottomPanGesture else {
return
}
bottomPanGesture = UIPanGestureRecognizer(target: self, action: #selector(handleBottomSheetPanGesture(recognizer:)))
bottomPanGesture!.delegate = self
view.addGestureRecognizer(bottomPanGesture!)
}
/// Prepare the left tap gesture.
fileprivate func prepareBottomTapGesture() {
guard nil == bottomTapGesture else {
return
}
bottomTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleBottomSheetTapGesture(recognizer:)))
bottomTapGesture!.delegate = self
bottomTapGesture!.cancelsTouchesInView = false
view.addGestureRecognizer(bottomTapGesture!)
}
}
extension BottomSheetController: UIGestureRecognizerDelegate {
/**
Detects the gesture recognizer being used.
- Parameter gestureRecognizer: A UIGestureRecognizer to detect.
- Parameter touch: The UITouch event.
- Returns: A Boolean of whether to continue the gesture or not.
*/
@objc
open func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if gestureRecognizer == bottomPanGesture && (isBottomSheetOpened || isPointContainedWithinBottomThreshold(point: touch.location(in: view))) {
return true
}
if isBottomSheetOpened && gestureRecognizer == bottomTapGesture {
return true
}
return false
}
/**
A method that is fired when the pan gesture is recognized
for the bottomSheet.
- Parameter recognizer: A UIPanGestureRecognizer that is
passed to the handler when recognized.
*/
@objc
fileprivate func handleBottomSheetPanGesture(recognizer: UIPanGestureRecognizer) {
guard isBottomSheetEnabled && (isBottomSheetOpened || isPointContainedWithinBottomThreshold(point: recognizer.location(in: view))) else {
return
}
// Animate the panel.
switch recognizer.state {
case .began:
originalY = bottomSheet.position.y
showView(container: bottomSheet)
delegate?.bottomViewController?(bottomViewController: self, didBeginPanAt: recognizer.location(in: view))
case .changed:
let y = originalY + recognizer.translation(in: bottomSheet).y
let h = view.bounds.height
let b = bottomSheetHeight / 2
let p = h - b
let q = h + b - bottomSheetClosedThreshold
bottomSheet.position.y = y < p ? p : y > q && nil != bottomSheet.fabButton ? q : y
let a = 1 - (h - y) / bottomSheetHeight
rootViewController.view.alpha = 0.5 < a && y >= b ? a : 0.5
delegate?.bottomViewController?(bottomViewController: self, didChangePanAt: recognizer.location(in: view))
case .ended, .cancelled, .failed:
let p = recognizer.velocity(in: recognizer.view)
let v = p.y >= 500 || p.y <= -500 ? p.y : 0
delegate?.bottomViewController?(bottomViewController: self, didEndPanAt: recognizer.location(in: view))
if bottomSheet.y >= bottomSheetThreshold || v > 500 {
closeBottomSheet(velocity: v)
} else {
openBottomSheet(velocity: v)
}
case .possible:break
}
}
/**
A method that is fired when the tap gesture is recognized
for the bottomSheet.
- Parameter recognizer: A UITapGestureRecognizer that is
passed to the handler when recognized.
*/
@objc
fileprivate func handleBottomSheetTapGesture(recognizer: UITapGestureRecognizer) {
guard isBottomSheetOpened else {
return
}
delegate?.bottomViewController?(bottomViewController: self, didTapAt: recognizer.location(in: view))
guard isBottomSheetEnabled && isBottomSheetOpened && !isPointContainedWithinView(container: bottomSheet, point: recognizer.location(in: bottomSheet)) else {
return
}
closeBottomSheet()
}
}
extension BottomSheetController {
/**
A handler that is executed when the bottomSheet.fabButton
has been set.
- Parameter fabButton: An optional FABButton.
*/
fileprivate func handleFABButtonWasSet(fabButton: FABButton?) {
guard nil == fabButton else {
if !isOpened {
showView(container: bottomSheet)
closeBottomSheet()
}
return
}
if !isOpened {
closeBottomSheet()
}
}
}
/*
* Copyright (C) 2015 - 2017, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of CosmicMind nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import UIKit
@objc(MenuDirection)
public enum MenuDirection: Int {
case up
case down
case topLeft
case topRight
case bottomLeft
case bottomRight
}
open class MenuItem: Toolbar {
open override func prepare() {
super.prepare()
heightPreset = .normal
titleLabel.textAlignment = .left
detailLabel.textAlignment = .left
contentViewAlignment = .center
}
}
class MenuCollectionViewCell: CollectionViewCell {
/// A reference to the MenuItem.
open var menuItem: MenuItem? {
didSet {
oldValue?.removeFromSuperview()
if let v = menuItem {
contentView.addSubview(v)
}
}
}
}
@objc(MenuDelegate)
public protocol MenuDelegate {
/**
A delegation method that is execited when the menu will open.
- Parameter menu: A Menu.
*/
@objc
optional func menuWillOpen(menu: Menu)
/**
A delegation method that is execited when the menu did open.
- Parameter menu: A Menu.
*/
@objc
optional func menuDidOpen(menu: Menu)
/**
A delegation method that is execited when the menu will close.
- Parameter menu: A Menu.
*/
@objc
optional func menuWillClose(menu: Menu)
/**
A delegation method that is execited when the menu did close.
- Parameter menu: A Menu.
*/
@objc
optional func menuDidClose(menu: Menu)
/**
A delegation method that is executed when the user taps while
the menu is opened.
- Parameter menu: A Menu.
- Parameter tappedAt point: A CGPoint.
- Parameter isOutside: A boolean indicating whether the tap
was outside the menu button area.
*/
@objc
optional func menu(menu: Menu, tappedAt point: CGPoint, isOutside: Bool)
@objc
optional func menu(menu: Menu, didSelect menuItem: MenuItem, at indexPath: IndexPath)
}
@objc(Menu)
open class Menu: Button {
/// The direction in which the animation opens the menu.
open var direction = MenuDirection.up {
didSet {
layoutSubviews()
}
}
/// A reference to the collectionViewCard.
@IBInspectable
open let collectionViewCard = CollectionViewCard()
/// A preset wrapper around collectionViewCardEdgeInsets.
open var collectionViewCardEdgeInsetsPreset = EdgeInsetsPreset.none {
didSet {
collectionViewCardEdgeInsets = EdgeInsetsPresetToValue(preset: collectionViewCardEdgeInsetsPreset)
}
}
/// A reference to collectionViewCardEdgeInsets.
@IBInspectable
open var collectionViewCardEdgeInsets = EdgeInsets.zero {
didSet {
layoutSubviews()
}
}
/**
Retrieves the data source items for the collectionView.
- Returns: An Array of DataSourceItem objects.
*/
open fileprivate(set) var dataSourceItems: [DataSourceItem] {
get {
return collectionViewCard.dataSourceItems
}
set(value) {
collectionViewCard.dataSourceItems = value
}
}
/// A reference to the MenuItems.
open var items = [MenuItem]() {
didSet {
dataSourceItems.removeAll()
for item in items {
dataSourceItems.append(DataSourceItem(data: item, width: item.width, height: item.height))
}
layoutSubviews()
}
}
/// A boolean indicating if the menu is open or not.
open var isOpened = false
/// An optional delegation handler.
open weak var delegate: MenuDelegate?
open override func layoutSubviews() {
super.layoutSubviews()
reload()
}
open override func prepare() {
super.prepare()
prepareCollectionViewCard()
prepareHandler()
}
open func reload() {
if 0 == collectionViewCard.width {
collectionViewCard.width = Screen.bounds.width - collectionViewCardEdgeInsets.left - collectionViewCardEdgeInsets.right
}
}
}
extension Menu {
/// Prepares the collectionViewCard.
fileprivate func prepareCollectionViewCard() {
collectionViewCard.collectionView.delegate = self
collectionViewCard.collectionView.dataSource = self
collectionViewCard.collectionView.register(MenuCollectionViewCell.self, forCellWithReuseIdentifier: "MenuCollectionViewCell")
}
/// Prepares the handler.
fileprivate func prepareHandler() {
addTarget(self, action: #selector(handleToggleMenu), for: .touchUpInside)
}
}
extension Menu {
/**
Handles the hit test for the Menu and views outside of the Menu bounds.
- Parameter _ point: A CGPoint.
- Parameter with event: An optional UIEvent.
- Returns: An optional UIView.
*/
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard isOpened, isEnabled else {
return super.hitTest(point, with: event)
}
for v in subviews {
let p = v.convert(point, from: self)
if v.bounds.contains(p) {
delegate?.menu?(menu: self, tappedAt: point, isOutside: false)
return v.hitTest(p, with: event)
}
}
delegate?.menu?(menu: self, tappedAt: point, isOutside: true)
close()
return self.hitTest(point, with: event)
}
}
extension Menu {
open func open() {
guard !isOpened, isEnabled else {
return
}
guard nil == collectionViewCard.superview else {
return
}
delegate?.menuWillOpen?(menu: self)
switch direction {
case .up:
layout(collectionViewCard).bottom().centerHorizontally()
case .down:
layout(collectionViewCard).top().centerHorizontally()
case .topLeft:
layout(collectionViewCard).topLeft()
case .topRight:
layout(collectionViewCard).topRight()
case .bottomLeft:
layout(collectionViewCard).bottomLeft()
case .bottomRight:
layout(collectionViewCard).bottomRight()
}
isOpened = true
delegate?.menuDidOpen?(menu: self)
}
open func close() {
guard isOpened, isEnabled else {
return
}
delegate?.menuWillClose?(menu: self)
collectionViewCard.removeFromSuperview()
isOpened = false
delegate?.menuDidClose?(menu: self)
}
}
extension Menu {
/**
Handler to toggle the Menu opened or closed.
- Parameter button: A UIButton.
*/
@objc
fileprivate func handleToggleMenu(button: UIButton) {
guard isOpened else {
open()
return
}
close()
}
}
extension Menu: CollectionViewDelegate {
open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let menuItem = collectionViewCard.dataSourceItemsIndexPaths[indexPath] as? MenuItem else {
return
}
delegate?.menu?(menu: self, didSelect: menuItem, at: indexPath)
}
}
extension Menu: CollectionViewDataSource {
@objc
open func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
@objc
open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dataSourceItems.count
}
@objc
open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MenuCollectionViewCell", for: indexPath) as! MenuCollectionViewCell
guard let menuItem = dataSourceItems[indexPath.item].data as? MenuItem else {
return cell
}
collectionViewCard.dataSourceItemsIndexPaths[indexPath] = menuItem
cell.menuItem = menuItem
cell.menuItem?.width = cell.width
return cell
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment