Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added dialogs #1173

Merged
merged 10 commits into from
Oct 16, 2018
20 changes: 20 additions & 0 deletions Material.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@
9D054A6620D175AC00D0528D /* Material+UILabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D054A6420D175AC00D0528D /* Material+UILabel.swift */; };
9D39A81B20FE8ED100BA8FA1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D39A81A20FE8ED100BA8FA1 /* ViewController.swift */; };
9D9089B92118914500605DC9 /* Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D9089B82118914500605DC9 /* Editor.swift */; };
9DE25DE02170D7AF000C04DF /* Dialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE25DDF2170D7AF000C04DF /* Dialog.swift */; };
9DE25DE22170D7C0000C04DF /* DialogController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE25DE12170D7C0000C04DF /* DialogController.swift */; };
9DE25DE42170D7FF000C04DF /* DialogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE25DE32170D7FF000C04DF /* DialogView.swift */; };
9DE84D721FF0252600586C8B /* RadioButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE84D6F1FF0252500586C8B /* RadioButtonGroup.swift */; };
9DE84D731FF0252600586C8B /* BaseButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE84D701FF0252500586C8B /* BaseButtonGroup.swift */; };
9DE84D741FF0252600586C8B /* CheckButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DE84D711FF0252500586C8B /* CheckButtonGroup.swift */; };
Expand Down Expand Up @@ -298,6 +301,9 @@
9D054A6420D175AC00D0528D /* Material+UILabel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+UILabel.swift"; sourceTree = "<group>"; };
9D39A81A20FE8ED100BA8FA1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
9D9089B82118914500605DC9 /* Editor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Editor.swift; sourceTree = "<group>"; };
9DE25DDF2170D7AF000C04DF /* Dialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dialog.swift; sourceTree = "<group>"; };
9DE25DE12170D7C0000C04DF /* DialogController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogController.swift; sourceTree = "<group>"; };
9DE25DE32170D7FF000C04DF /* DialogView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DialogView.swift; sourceTree = "<group>"; };
9DE84D6F1FF0252500586C8B /* RadioButtonGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioButtonGroup.swift; sourceTree = "<group>"; };
9DE84D701FF0252500586C8B /* BaseButtonGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseButtonGroup.swift; sourceTree = "<group>"; };
9DE84D711FF0252500586C8B /* CheckButtonGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckButtonGroup.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -550,6 +556,7 @@
96BCB8001CB40F0300C806FE /* Color */,
96328B9A1E05C135009A4C90 /* Data */,
96BCB80B1CB410CC00C806FE /* Device */,
9DE25DDE2170D779000C04DF /* Dialogs */,
96230AB61D6A51FD00AF47DC /* Divider */,
96BCB80A1CB410A100C806FE /* Extension */,
963FBF021D6696D0008F8512 /* FABMenu */,
Expand Down Expand Up @@ -771,6 +778,16 @@
name = Theme;
sourceTree = "<group>";
};
9DE25DDE2170D779000C04DF /* Dialogs */ = {
isa = PBXGroup;
children = (
9DE25DDF2170D7AF000C04DF /* Dialog.swift */,
9DE25DE12170D7C0000C04DF /* DialogController.swift */,
9DE25DE32170D7FF000C04DF /* DialogView.swift */,
);
name = Dialogs;
sourceTree = "<group>";
};
9DE84D6E1FF0250E00586C8B /* ButtonGroup */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -1015,9 +1032,12 @@
9DE84D721FF0252600586C8B /* RadioButtonGroup.swift in Sources */,
9D00EBB4216675FB00DBCD69 /* Theme.swift in Sources */,
965E80FE1DD4D59500D61E4B /* ToolbarController.swift in Sources */,
9DE25DE22170D7C0000C04DF /* DialogController.swift in Sources */,
9DE25DE42170D7FF000C04DF /* DialogView.swift in Sources */,
96328B971E05C0BB009A4C90 /* TableView.swift in Sources */,
965E80F81DD4D59500D61E4B /* ImageCard.swift in Sources */,
96328B991E05C0CE009A4C90 /* TableViewController.swift in Sources */,
9DE25DE02170D7AF000C04DF /* Dialog.swift in Sources */,
965E80F91DD4D59500D61E4B /* PresenterCard.swift in Sources */,
96E09DC81F2287E50000B121 /* TabsController.swift in Sources */,
961154CC1F32A7B100A78D74 /* ChipBar.swift in Sources */,
Expand Down
16 changes: 8 additions & 8 deletions Sources/iOS/Button.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,9 @@ open class Button: UIButton, Pulseable, PulseableLayer, Themeable {
/**
A convenience initializer that acceps an image and tint
- Parameter image: A UIImage.
- Parameter tintColor: A UI
- Parameter tintColor: A UIColor.
*/
public init(image: UIImage?, tintColor: UIColor = Color.blue.base) {
public init(image: UIImage?, tintColor: UIColor? = nil) {
super.init(frame: .zero)
prepare()
prepare(with: image, tintColor: tintColor)
Expand All @@ -202,9 +202,9 @@ open class Button: UIButton, Pulseable, PulseableLayer, Themeable {
/**
A convenience initializer that acceps a title and title
- Parameter title: A String.
- Parameter titleColor: A UI
- Parameter titleColor: A UIColor.
*/
public init(title: String?, titleColor: UIColor = Color.blue.base) {
public init(title: String?, titleColor: UIColor? = nil) {
super.init(frame: .zero)
prepare()
prepare(with: title, titleColor: titleColor)
Expand Down Expand Up @@ -309,19 +309,19 @@ extension Button {
- Parameter image: A UIImage.
- Parameter tintColor: A UI
*/
fileprivate func prepare(with image: UIImage?, tintColor: UIColor) {
fileprivate func prepare(with image: UIImage?, tintColor: UIColor?) {
self.image = image
self.tintColor = tintColor
self.tintColor = tintColor ?? self.tintColor
}

/**
Prepares the Button with a title and title
- Parameter title: A String.
- Parameter titleColor: A UI
*/
fileprivate func prepare(with title: String?, titleColor: UIColor) {
fileprivate func prepare(with title: String?, titleColor: UIColor?) {
self.title = title
self.titleColor = titleColor
self.titleColor = titleColor ?? self.titleColor
}
}

Expand Down
288 changes: 288 additions & 0 deletions Sources/iOS/Dialog.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
/*
* Copyright (C) 2018, Daniel Dahan and CosmicMind, Inc. <http://cosmicmind.com>.
* All rights reserved.
*
* Original Inspiration & Author
* Copyright (C) 2018 Orkhan Alikhanov <orkhan.alikhanov@gmail.com>
*
* 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
import Motion

@objc
public protocol DialogDelegate {
/**
A delegation method that is executed when the Dialog is cancelled through tapping background.
- Parameter _ dialog: A Dialog.
*/
@objc
optional func dialogDidCancel(_ dialog: Dialog)

/**
A delegation method that is executed when the Dialog will appear.
- Parameter _ dialog: A Dialog.
*/
@objc
optional func dialogWillAppear(_ dialog: Dialog)

/**
A delegation method that is executed when the Dialog did disappear.
- Parameter _ dialog: A Dialog.
*/
@objc
optional func dialogDidDisappear(_ dialog: Dialog)

/**
A delegation method that is executed to determine if the Dialog should be dismissed.
- Parameter _ dialog: A Dialog.
- Parameter shouldDismiss button: The tapped button. nil if dialog is being
cancelled through tapping background.
- Returns: A Boolean.
*/
@objc
optional func dialog(_ dialog: Dialog, shouldDismiss button: Button?) -> Bool

/**
A delegation method that is executed when the positive button of Dialog is tapped.
- Parameter _ dialog: A Dialog.
- Parameter didTapPositive button: A Button.
*/
@objc
optional func dialog(_ dialog: Dialog, didTapPositive button: Button)

/**
A delegation method that is executed when the negative button of Dialog is tapped.
- Parameter _ dialog: A Dialog.
- Parameter didTapNegative button: A Button.
*/
@objc
optional func dialog(_ dialog: Dialog, didTapNegative button: Button)

/**
A delegation method that is executed when the neutral button of Dialog is tapped.
- Parameter _ dialog: A Dialog.
- Parameter didTapNeutral button: A Button.
*/
@objc
optional func dialog(_ dialog: Dialog, didTapNeutral button: Button)
}

/// A builder for DialogController.
open class Dialog: NSObject {
/// A reference to dialog controller.
public let controller = DialogController<DialogView>()

/// A weak reference to DialogDelegate.
open weak var delegate: DialogDelegate?

/// An empty initializer.
public override init() {
super.init()

/// Set callbacks for delegate.
_ = shouldDismiss(handler: nil)
daniel-jonathan marked this conversation as resolved.
Show resolved Hide resolved
.positive(nil, handler: nil)
.negative(nil, handler: nil)
.neutral(nil, handler: nil)
.isCancelable(controller.isCancelable, handler: nil)
.willAppear(handler: nil)
.didDisappear(handler: nil)
}

/**
Sets title of the dialog.
- Parameter _ text: A string.
- Returns: Dialog itself to allow chaining.
*/
open func title(_ text: String?) -> Dialog {
dialogView.titleLabel.text = text
return self
}

/**
Sets details of the dialog.
- Parameter _ text: A string.
- Returns: Dialog itself to allow chaining.
*/
open func details(_ text: String?) -> Dialog {
dialogView.detailsLabel.text = text
return self
}

/**
Sets title and handler for positive button of dialog.
- Parameter _ title: A string.
- Parameter handler: A closure handling tap.
- Returns: Dialog itself to allow chaining.
*/
open func positive(_ title: String?, handler: (() -> Void)?) -> Dialog {
dialogView.positiveButton.title = title
controller.didTapPositiveButtonHandler = { [unowned self] in
self.delegate?.dialog?(self, didTapPositive: self.controller.dialogView.positiveButton)
handler?()
}
return self
}

/**
Sets title and handler for negative button of dialog.
- Parameter _ title: A string.
- Parameter handler: A closure handling tap.
- Returns: Dialog itself to allow chaining.
*/
open func negative(_ title: String?, handler: (() -> Void)?) -> Dialog {
daniel-jonathan marked this conversation as resolved.
Show resolved Hide resolved
dialogView.negativeButton.title = title
controller.didTapNegativeButtonHandler = { [unowned self] in
self.delegate?.dialog?(self, didTapNegative: self.controller.dialogView.negativeButton)
handler?()
}
return self
}

/**
Sets title and handler for neutral button of dialog.
- Parameter _ title: A string.
- Parameter handler: A closure handling tap.
- Returns: Dialog itself to allow chaining.
*/
open func neutral(_ title: String?, handler: (() -> Void)?) -> Dialog {
dialogView.neutralButton.title = title
controller.didTapNeutralButtonHandler = { [unowned self] in
self.delegate?.dialog?(self, didTapNeutral: self.controller.dialogView.neutralButton)
handler?()
}
return self
}

/**
Sets cancelability of dialog and handler for when it's cancelled.
- Parameter _ value: A Bool.
- Parameter handler: A closure handling cancellation.
- Returns: Dialog itself to allow chaining.
*/
open func isCancelable(_ value: Bool, handler: (() -> Void)? = nil) -> Dialog {
controller.isCancelable = value
controller.didCancelHandler = { [unowned self] in
self.delegate?.dialogDidCancel?(self)
handler?()
}
return self
}

/**
Sets should-dismiss handler of dialog which takes dialogView and tapped
button and returns a boolean indicating if dialog should be dismissed.
- Parameter handler: A closure handling if dialog can be dismissed.
- Returns: Dialog itself to allow chaining.
*/
open func shouldDismiss(handler: ((DialogView, Button?) -> Bool)?) -> Dialog {
controller.shouldDismissHandler = { [unowned self] dialogView, button in
let d = self.delegate?.dialog?(self, shouldDismiss: button) ?? true
let h = handler?(dialogView, button) ?? true
return d && h
}
return self
}

/**
Sets handler for when view controller will appear.
- Parameter handler: A closure handling the event.
- Returns: Dialog itself to allow chaining.
*/
open func willAppear(handler: (() -> Void)?) -> Dialog {
controller.willAppear = { [unowned self] in
self.delegate?.dialogWillAppear?(self)
handler?()
}
return self
}

/**
Sets handler for when view controller did disappear.
- Parameter handler: A closure handling the event.
- Returns: Dialog itself to allow chaining.
*/
open func didDisappear(handler: (() -> Void)?) -> Dialog {
controller.didDisappear = { [unowned self] in
self.delegate?.dialogDidDisappear?(self)
handler?()
self.controller.dialog = nil
}
return self
}

/**
Sets dialog delegate.
- Parameter delegate: A DialogDelegate.
- Returns: Dialog itself to allow chaining.
*/
open func delegate(_ delegate: DialogDelegate) -> Dialog {
self.delegate = delegate
return self
}

/**
Presents dialog modally from given viewController.
- Parameter _ viewController: A UIViewController.
- Returns: Dialog itself to allow chaining.
*/
@discardableResult
open func show(_ viewController: UIViewController) -> Dialog {
controller.dialog = self
viewController.present(controller, animated: true, completion: nil)
return self
}
}

private extension Dialog {
/// Returns dialogView of controller.
var dialogView: DialogView {
return controller.dialogView
}
}

/// A memory reference to companion Dialog instance.
private var DialogKey: UInt8 = 0

private extension DialogController {
/**
A Dialog instance attached to the dialog controller.
This is used to keep Dialog alive throughout the lifespan
of the controller.
*/
var dialog: Dialog? {
get {
return AssociatedObject.get(base: self, key: &DialogKey) {
return nil
}
}
set(value) {
AssociatedObject.set(base: self, key: &DialogKey, value: value)
}
}
}
Loading