sendbird-uikit-ios/Sources/View/Common/SBUActionSheet.swift

427 lines
14 KiB
Swift

//
// SBUActionSheet.swift
// SendbirdUIKit
//
// Created by Tez Park on 16/02/2020.
// Copyright © 2020 Sendbird, Inc. All rights reserved.
//
import UIKit
public typealias SBUActionSheetHandler = () -> Void
public protocol SBUActionSheetDelegate: NSObjectProtocol {
func didSelectActionSheetItem(index: Int, identifier: Int)
func didDismissActionSheet()
}
extension SBUActionSheetDelegate {
public func didDismissActionSheet() {}
}
public class SBUActionSheetItem: SBUCommonItem {
var completionHandler: SBUActionSheetHandler?
public override init(
title: String? = nil,
color: UIColor? = SBUColorSet.onlight01,
image: UIImage? = nil,
font: UIFont? = nil,
tintColor: UIColor? = nil,
textAlignment: NSTextAlignment = .left,
tag: Int? = nil
) {
super.init(
title: title,
color: color,
image: image,
font: font,
tintColor: tintColor,
textAlignment: textAlignment,
tag: tag
)
self.completionHandler = nil
}
/// This function initializes actionSheet item.
/// - Parameters:
/// - title: Title text
/// - color: Title color
/// - image: Item image
/// - font: Title font
/// - textAlignment: Title alignment
/// - tag: Item tag
/// - completionHandler: Item's completion handler
public init(title: String? = nil,
color: UIColor? = nil,
image: UIImage? = nil,
font: UIFont? = nil,
textAlignment: NSTextAlignment = .left,
tag: Int? = nil,
completionHandler: SBUActionSheetHandler?) {
super.init(
title: title,
color: color,
image: image,
font: font,
textAlignment: textAlignment,
tag: tag
)
self.completionHandler = completionHandler
}
}
public class SBUActionSheet: NSObject {
@SBUThemeWrapper(theme: SBUTheme.componentTheme)
var theme: SBUComponentTheme
static private let shared = SBUActionSheet()
weak var delegate: SBUActionSheetDelegate?
private var items: [SBUActionSheetItem] = []
private var dismissHandler: (() -> Void)?
private var safeAreaInset: UIEdgeInsets {
self.window?.safeAreaInsets ?? .zero
}
var identifier: Int = -1
var window: UIWindow?
var baseView = UIView()
var backgroundView = UIButton()
let itemHeight: CGFloat = 56.0
let bottomMargin: CGFloat = 48.0
let sideMargin: CGFloat = 8.0
let insideMargin: CGFloat = 16.0
var prevOrientation: UIDeviceOrientation = .unknown
private override init() {
super.init()
}
/// This static function shows the actionSheet.
///
/// - Order
/// - item1
/// - item2
/// - item3
/// - cancel
///
/// See the example below for params generation.
/// ```
/// SBUActionSheet.show(
/// items: [
/// SBUActionSheetItem(title: TITLE1, image: IMAGE1),
/// SBUActionSheetItem(title: TITLE2, image: IMAGE2),
/// ],
/// cancelItem: SBUActionSheetItem(title: CANCEL_TITLE, image: CANCEL_IMAGE)
/// )
/// ```
/// - Parameters:
/// - items: Item array
/// - cancelItem: Cancel item
/// - identifier: ActionSheet identifier
/// - oneTimetheme: One-time theme setting
/// - delegate: ActionSheet delegate
public static func show(items: [SBUActionSheetItem],
cancelItem: SBUActionSheetItem,
identifier: Int = -1,
oneTimetheme: SBUComponentTheme? = nil,
delegate: SBUActionSheetDelegate? = nil,
dismissHandler: (() -> Void)? = nil) {
self.shared.show(
items: items,
cancelItem: cancelItem,
identifier: identifier,
oneTimetheme: oneTimetheme,
delegate: delegate,
dismissHandler: dismissHandler
)
}
/// This static function dismissed the actionSheet.
public static func dismiss() {
self.shared.dismiss()
}
private func show(items: [SBUActionSheetItem],
cancelItem: SBUActionSheetItem,
identifier: Int = -1,
oneTimetheme: SBUComponentTheme? = nil,
delegate: SBUActionSheetDelegate?,
dismissHandler: (() -> Void)?) {
self.handleDismiss(isUserInitiated: false)
self.prevOrientation = UIDevice.current.orientation
NotificationCenter.default.addObserver(
self,
selector: #selector(orientationChanged),
name: UIDevice.orientationDidChangeNotification,
object: nil
)
if let oneTimetheme = oneTimetheme {
self.theme = oneTimetheme
}
self.window = UIApplication.shared.currentWindow
guard let window = self.window else { return }
self.identifier = identifier
self.delegate = delegate
self.items = items
self.dismissHandler = dismissHandler
baseView = UIView()
backgroundView = UIButton()
// Set backgroundView
self.backgroundView.frame = window.bounds
self.backgroundView.backgroundColor = theme.overlayColor
self.backgroundView.addTarget(self, action: #selector(dismiss), for: .touchUpInside)
// Set items
let totalHeight = CGFloat(items.count + 1) * itemHeight + sideMargin + bottomMargin
let itemWidth = window.frame.width - (sideMargin * 2) - (self.safeAreaInset.left + self.safeAreaInset.right)
self.baseView.frame = CGRect(
origin: CGPoint(x: sideMargin + self.safeAreaInset.left, y: window.frame.height - totalHeight),
size: CGSize(width: itemWidth, height: totalHeight)
)
var itemOriginY: CGFloat = 0.0
for index in 0..<items.count {
let button = self.makeItems(
item: items[index],
separator: (index != items.count-1),
isTop: (index == 0),
isBottom: (index == items.count-1)
)
if let tag = items[index].tag {
button.tag = tag
} else {
items[index].tag = index
button.tag = index
}
var buttonFrame = button.frame
buttonFrame.origin = CGPoint(x: 0, y: itemOriginY)
button.frame = buttonFrame
self.baseView.addSubview(button)
itemOriginY += button.frame.height
}
itemOriginY += sideMargin
let cancelButton = self.makeCancelItem(item: cancelItem)
cancelButton.frame = CGRect(
origin: CGPoint(x: 0, y: itemOriginY),
size: cancelButton.frame.size
)
self.baseView.addSubview(cancelButton)
// Add to window
window.addSubview(self.backgroundView)
window.addSubview(self.baseView)
// Animation
let baseFrame = self.baseView.frame
self.baseView.frame = CGRect(
origin: CGPoint(x: baseFrame.origin.x, y: window.frame.height),
size: baseFrame.size
)
self.backgroundView.alpha = 0.0
UIView.animate(withDuration: 0.1, animations: {
self.backgroundView.alpha = 1.0
}) { _ in
UIView.animate(withDuration: 0.2, animations: {
self.baseView.frame = baseFrame
})
}
}
@objc private func dismiss() {
self.handleDismiss(isUserInitiated: true)
}
@objc private func handleDismiss(isUserInitiated: Bool = true) {
for subView in self.baseView.subviews {
subView.removeFromSuperview()
}
self.backgroundView.removeFromSuperview()
self.baseView.removeFromSuperview()
NotificationCenter.default.removeObserver(
self,
name: UIDevice.orientationDidChangeNotification,
object: nil)
if isUserInitiated {
self.delegate?.didDismissActionSheet()
let handler = self.dismissHandler
self.dismissHandler = nil
handler?()
}
}
// MARK: Make Buttons
private func makeItems(item: SBUActionSheetItem,
separator: Bool,
isTop: Bool,
isBottom: Bool) -> UIButton {
let width: CGFloat = (self.window?.bounds.width ?? self.baseView.frame.width)
let itemWidth: CGFloat = width - (self.sideMargin * 2) - (self.safeAreaInset.left + self.safeAreaInset.right)
let itemButton = UIButton(
frame: CGRect(
origin: .zero,
size: CGSize(width: itemWidth, height: self.itemHeight)
)
)
itemButton.setBackgroundImage(
UIImage.from(color: theme.backgroundColor),
for: .normal
)
itemButton.setBackgroundImage(
UIImage.from(color: theme.highlightedColor),
for: .highlighted
)
itemButton.addTarget(self, action: #selector(onClickActionSheetButton), for: .touchUpInside)
let titleLabel = UILabel()
let imageView = UIImageView()
var textImageMargin: CGFloat = (item.textAlignment == .left) ? self.insideMargin : 0.0
if let image = item.image {
let imageSize: CGFloat = 24.0
imageView.frame = CGRect(
origin: CGPoint(x: itemWidth - self.insideMargin - imageSize, y: self.insideMargin),
size: CGSize(width: imageSize, height: imageSize)
)
imageView.image = image
itemButton.addSubview(imageView)
textImageMargin = self.insideMargin
}
titleLabel.frame = CGRect(
origin: CGPoint(x: textImageMargin, y: 0),
size: CGSize(width: itemWidth - imageView.frame.width, height: self.itemHeight)
)
titleLabel.text = item.title
titleLabel.font = item.font ?? theme.actionSheetTextFont
titleLabel.textColor = item.color ?? theme.actionSheetTextColor
titleLabel.textAlignment = item.textAlignment
itemButton.addSubview(titleLabel)
if separator {
let separatorLine = UIView(
frame: CGRect(
origin: CGPoint(x: 0.0, y: itemHeight - 0.5),
size: CGSize(width: itemWidth, height: 0.5)
)
)
separatorLine.backgroundColor = theme.separatorColor
itemButton.addSubview(separatorLine)
}
let rectShape = CAShapeLayer()
rectShape.bounds = itemButton.frame
rectShape.position = itemButton.center
var corners: UIRectCorner = []
if isTop {
corners.update(with: [.topLeft, .topRight])
}
if isBottom {
corners.update(with: [.bottomLeft, .bottomRight])
}
rectShape.path = UIBezierPath(
roundedRect: itemButton.bounds,
byRoundingCorners: corners,
cornerRadii: CGSize(width: 10, height: 10))
.cgPath
itemButton.layer.mask = rectShape
return itemButton
}
private func makeCancelItem(item: SBUActionSheetItem) -> UIButton {
let width: CGFloat = (self.window?.bounds.width ?? self.baseView.frame.width)
let itemWidth: CGFloat = width - (self.sideMargin * 2) - (self.safeAreaInset.left + self.safeAreaInset.right)
let itemButton = UIButton(
frame: CGRect(
origin: .zero,
size: CGSize(width: itemWidth, height: self.itemHeight)
)
)
itemButton.setBackgroundImage(
UIImage.from(color: theme.backgroundColor),
for: .normal
)
itemButton.setBackgroundImage(
UIImage.from(color: theme.highlightedColor),
for: .highlighted
)
itemButton.addTarget(self, action: #selector(dismiss), for: .touchUpInside)
let titleLabel = UILabel()
titleLabel.frame = CGRect(origin: .zero, size: CGSize(width: itemWidth, height: itemHeight))
titleLabel.text = item.title
titleLabel.font = item.font ?? theme.actionSheetTextFont
titleLabel.textColor = item.color ?? theme.actionSheetItemColor
titleLabel.textAlignment = .center
itemButton.addSubview(titleLabel)
let rectShape = CAShapeLayer()
rectShape.bounds = itemButton.frame
rectShape.position = itemButton.center
rectShape.path = UIBezierPath(
roundedRect: itemButton.bounds,
byRoundingCorners: [.allCorners],
cornerRadii: CGSize(width: 10, height: 10)
).cgPath
itemButton.layer.mask = rectShape
return itemButton
}
// MARK: Button action
@objc private func onClickActionSheetButton(sender: UIButton) {
self.dismiss()
self.delegate?.didSelectActionSheetItem(
index: sender.tag,
identifier: self.identifier
)
let item = self.items.filter { $0.tag == sender.tag }.first
item?.completionHandler?()
}
// MARK: Orientation
@objc
func orientationChanged(_ notification: NSNotification) {
let currentOrientation = UIDevice.current.orientation
if prevOrientation.isPortrait && currentOrientation.isLandscape ||
prevOrientation.isLandscape && currentOrientation.isPortrait {
dismiss()
}
self.prevOrientation = currentOrientation
}
}