mirror of
https://github.com/SwiftKickMobile/SwiftMessages.git
synced 2026-04-07 19:17:33 +00:00
449 lines
16 KiB
Swift
449 lines
16 KiB
Swift
//
|
|
// MessageView.swift
|
|
// SwiftMessages
|
|
//
|
|
// Created by Timothy Moose on 7/30/16.
|
|
// Copyright © 2016 SwiftKick Mobile LLC. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
/*
|
|
*/
|
|
open class MessageView: BaseView, Identifiable, AccessibleMessage, HapticMessage {
|
|
|
|
/*
|
|
MARK: - Haptic feedback
|
|
*/
|
|
|
|
/// The default haptic feedback to be used when the message is presented.
|
|
open var defaultHaptic: SwiftMessages.Haptic?
|
|
|
|
/*
|
|
MARK: - Button tap handler
|
|
*/
|
|
|
|
/// An optional button tap handler. The `button` is automatically
|
|
/// configured to call this tap handler on `.TouchUpInside`.
|
|
open var buttonTapHandler: ((_ button: UIButton) -> Void)?
|
|
|
|
@objc func buttonTapped(_ button: UIButton) {
|
|
buttonTapHandler?(button)
|
|
}
|
|
|
|
/*
|
|
MARK: - Touch handling
|
|
*/
|
|
|
|
open override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
|
|
// Only accept touches within the background view. Anything outside of the
|
|
// background view's bounds should be transparent and does not need to receive
|
|
// touches. This helps with tap dismissal when using `DimMode.gray` and `DimMode.color`.
|
|
return backgroundView == self
|
|
? super.point(inside: point, with: event)
|
|
: backgroundView.point(inside: convert(point, to: backgroundView), with: event)
|
|
}
|
|
|
|
/*
|
|
MARK: - IB outlets
|
|
*/
|
|
|
|
/// An optional title label.
|
|
@IBOutlet open var titleLabel: UILabel?
|
|
|
|
/// An optional body text label.
|
|
@IBOutlet open var bodyLabel: UILabel?
|
|
|
|
/// An optional icon image view.
|
|
@IBOutlet open var iconImageView: UIImageView?
|
|
|
|
/// An optional icon label (e.g. for emoji character, icon font, etc.).
|
|
@IBOutlet open var iconLabel: UILabel?
|
|
|
|
/// An optional button. This buttons' `.TouchUpInside` event will automatically
|
|
/// invoke the optional `buttonTapHandler`, but its fine to add other target
|
|
/// action handlers can be added.
|
|
@IBOutlet open var button: UIButton? {
|
|
didSet {
|
|
if let old = oldValue {
|
|
old.removeTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
|
|
}
|
|
if let button = button {
|
|
button.addTarget(self, action: #selector(MessageView.buttonTapped(_:)), for: .touchUpInside)
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
MARK: - Identifiable
|
|
*/
|
|
|
|
open var id: String {
|
|
get {
|
|
return customId ?? "MessageView:title=\(String(describing: titleLabel?.text)), body=\(String(describing: bodyLabel?.text))"
|
|
}
|
|
set {
|
|
customId = newValue
|
|
}
|
|
}
|
|
|
|
private var customId: String?
|
|
|
|
/*
|
|
MARK: - AccessibleMessage
|
|
*/
|
|
|
|
/**
|
|
An optional prefix for the `accessibilityMessage` that can
|
|
be used to further clarify the message for VoiceOver. For example,
|
|
the view's background color or icon might convey that a message is
|
|
a warning, in which case one may specify the value "warning".
|
|
*/
|
|
open var accessibilityPrefix: String?
|
|
|
|
open var accessibilityMessage: String? {
|
|
#if swift(>=4.1)
|
|
let components = [accessibilityPrefix, titleLabel?.text, bodyLabel?.text].compactMap { $0 }
|
|
#else
|
|
let components = [accessibilityPrefix, titleLabel?.text, bodyLabel?.text].flatMap { $0 }
|
|
#endif
|
|
guard components.count > 0 else { return nil }
|
|
return components.joined(separator: ", ")
|
|
}
|
|
|
|
public var accessibilityElement: NSObject? {
|
|
return backgroundView
|
|
}
|
|
|
|
open var additionalAccessibilityElements: [NSObject]? {
|
|
var elements: [NSObject] = []
|
|
func getAccessibleSubviews(view: UIView) {
|
|
for subview in view.subviews {
|
|
if subview.isAccessibilityElement {
|
|
elements.append(subview)
|
|
} else {
|
|
// Only doing this for non-accessible `subviews`, which avoids
|
|
// including button labels, etc.
|
|
getAccessibleSubviews(view: subview)
|
|
}
|
|
}
|
|
}
|
|
getAccessibleSubviews(view: self.backgroundView)
|
|
return elements
|
|
}
|
|
}
|
|
|
|
/*
|
|
MARK: - Creating message views
|
|
|
|
This extension provides several convenience functions for instantiating
|
|
`MessageView` from the included nib files in a type-safe way. These nib
|
|
files can be found in the Resources folder and can be drag-and-dropped
|
|
into a project and modified. You may still use these APIs if you've
|
|
copied the nib files because SwiftMessages looks for them in the main
|
|
bundle first. See `SwiftMessages` for additional nib loading options.
|
|
*/
|
|
|
|
extension MessageView {
|
|
|
|
/**
|
|
Specifies one of the nib files included in the Resources folders.
|
|
*/
|
|
public enum Layout: String {
|
|
|
|
/**
|
|
The standard message view that stretches across the full width of the
|
|
container view.
|
|
*/
|
|
case messageView = "MessageView"
|
|
|
|
/**
|
|
A floating card-style view with rounded corners.
|
|
*/
|
|
case cardView = "CardView"
|
|
|
|
/**
|
|
Like `CardView` with one end attached to the super view.
|
|
*/
|
|
case tabView = "TabView"
|
|
|
|
/**
|
|
A 20pt tall view that can be used to overlay the status bar.
|
|
Note that this layout will automatically grow taller if displayed
|
|
directly under the status bar (see the `ContentInsetting` protocol).
|
|
*/
|
|
case statusLine = "StatusLine"
|
|
|
|
/**
|
|
A floating card-style view with elements centered and arranged vertically.
|
|
This view is typically used with `.center` presentation style.
|
|
*/
|
|
case centeredView = "CenteredView"
|
|
}
|
|
|
|
/**
|
|
Loads the nib file associated with the given `Layout` and returns the first
|
|
view found in the nib file with the matching type `T: MessageView`.
|
|
|
|
- Parameter layout: The `Layout` option to use.
|
|
- Parameter filesOwner: An optional files owner.
|
|
|
|
- Returns: An instance of generic view type `T: MessageView`.
|
|
*/
|
|
public static func viewFromNib<T: MessageView>(layout: Layout, filesOwner: AnyObject = NSNull.init()) -> T {
|
|
return try! SwiftMessages.viewFromNib(named: layout.rawValue)
|
|
}
|
|
|
|
/**
|
|
Loads the nib file associated with the given `Layout` from
|
|
the given bundle and returns the first view found in the nib
|
|
file with the matching type `T: MessageView`.
|
|
|
|
- Parameter layout: The `Layout` option to use.
|
|
- Parameter bundle: The name of the bundle containing the nib file.
|
|
- Parameter filesOwner: An optional files owner.
|
|
|
|
- Returns: An instance of generic view type `T: MessageView`.
|
|
*/
|
|
public static func viewFromNib<T: MessageView>(layout: Layout, bundle: Bundle, filesOwner: AnyObject = NSNull.init()) -> T {
|
|
return try! SwiftMessages.viewFromNib(named: layout.rawValue, bundle: bundle, filesOwner: filesOwner)
|
|
}
|
|
}
|
|
|
|
/*
|
|
MARK: - Layout adjustments
|
|
|
|
This extension provides a few convenience functions for adjusting the layout.
|
|
*/
|
|
|
|
extension MessageView {
|
|
/**
|
|
Constrains the image view to a specified size. By default, the size of the
|
|
image view is determined by its `intrinsicContentSize`.
|
|
|
|
- Parameter size: The size to be translated into Auto Layout constraints.
|
|
- Parameter contentMode: The optional content mode to apply.
|
|
*/
|
|
public func configureIcon(withSize size: CGSize, contentMode: UIView.ContentMode? = nil) {
|
|
var views: [UIView] = []
|
|
if let iconImageView = iconImageView { views.append(iconImageView) }
|
|
if let iconLabel = iconLabel { views.append(iconLabel) }
|
|
views.forEach {
|
|
let constraints = [$0.heightAnchor.constraint(equalToConstant: size.height),
|
|
$0.widthAnchor.constraint(equalToConstant: size.width)]
|
|
constraints.forEach { $0.priority = UILayoutPriority(999.0) }
|
|
$0.addConstraints(constraints)
|
|
if let contentMode = contentMode {
|
|
$0.contentMode = contentMode
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
MARK: - Theming
|
|
|
|
This extension provides a few convenience functions for setting styles,
|
|
colors and icons. You are encouraged to write your own such functions
|
|
if these don't exactly meet your needs.
|
|
*/
|
|
|
|
extension MessageView {
|
|
|
|
/**
|
|
A convenience function for setting some pre-defined colors and icons.
|
|
|
|
- Parameter theme: The theme type to use.
|
|
- Parameter iconStyle: The icon style to use. Defaults to `.Default`.
|
|
- Parameter useHaptics: If `true`, configures an appropriate haptic based on theme. Defaults to `false`.
|
|
*/
|
|
public func configureTheme(_ theme: Theme, iconStyle: IconStyle = .default, includeHaptic: Bool = false) {
|
|
let iconImage = iconStyle.image(theme: theme)
|
|
let backgroundColor: UIColor
|
|
let foregroundColor: UIColor
|
|
let defaultBackgroundColor: UIColor
|
|
switch theme {
|
|
case .info:
|
|
defaultBackgroundColor = UIColor(red: 225.0/255.0, green: 225.0/255.0, blue: 225.0/255.0, alpha: 1.0)
|
|
case .success:
|
|
defaultBackgroundColor = UIColor(red: 97.0/255.0, green: 161.0/255.0, blue: 23.0/255.0, alpha: 1.0)
|
|
case .warning:
|
|
defaultBackgroundColor = UIColor(red: 246.0/255.0, green: 197.0/255.0, blue: 44.0/255.0, alpha: 1.0)
|
|
case .error:
|
|
defaultBackgroundColor = UIColor(red: 249.0/255.0, green: 66.0/255.0, blue: 47.0/255.0, alpha: 1.0)
|
|
}
|
|
if includeHaptic {
|
|
switch theme {
|
|
case .success, .info:
|
|
defaultHaptic = SwiftMessages.Haptic.success
|
|
case .warning:
|
|
defaultHaptic = SwiftMessages.Haptic.warning
|
|
case .error:
|
|
defaultHaptic = SwiftMessages.Haptic.error
|
|
}
|
|
}
|
|
switch theme {
|
|
case .info:
|
|
backgroundColor = UIColor {
|
|
switch $0.userInterfaceStyle {
|
|
case .dark, .unspecified: return UIColor(red: 125/255.0, green: 125/255.0, blue: 125/255.0, alpha: 1.0)
|
|
case .light: fallthrough
|
|
@unknown default:
|
|
return defaultBackgroundColor
|
|
}
|
|
}
|
|
foregroundColor = .label
|
|
case .success:
|
|
backgroundColor = UIColor {
|
|
switch $0.userInterfaceStyle {
|
|
case .dark, .unspecified: return UIColor(red: 55/255.0, green: 122/255.0, blue: 0/255.0, alpha: 1.0)
|
|
case .light: fallthrough
|
|
@unknown default:
|
|
return defaultBackgroundColor
|
|
}
|
|
}
|
|
foregroundColor = .white
|
|
case .warning:
|
|
backgroundColor = UIColor {
|
|
switch $0.userInterfaceStyle {
|
|
case .dark, .unspecified: return UIColor(red: 239/255.0, green: 184/255.0, blue: 10/255.0, alpha: 1.0)
|
|
case .light: fallthrough
|
|
@unknown default:
|
|
return defaultBackgroundColor
|
|
}
|
|
}
|
|
foregroundColor = .white
|
|
case .error:
|
|
backgroundColor = UIColor {
|
|
switch $0.userInterfaceStyle {
|
|
case .dark, .unspecified: return UIColor(red: 195/255.0, green: 12/255.0, blue: 12/255.0, alpha: 1.0)
|
|
case .light: fallthrough
|
|
@unknown default:
|
|
return defaultBackgroundColor
|
|
}
|
|
}
|
|
foregroundColor = .white
|
|
}
|
|
configureTheme(backgroundColor: backgroundColor, foregroundColor: foregroundColor, iconImage: iconImage)
|
|
}
|
|
|
|
/**
|
|
A convenience function for setting a foreground and background color.
|
|
Note that images will only display the foreground color if they're
|
|
configured with UIImageRenderingMode.AlwaysTemplate.
|
|
|
|
- Parameter backgroundColor: The background color to use.
|
|
- Parameter foregroundColor: The foreground color to use.
|
|
*/
|
|
public func configureTheme(backgroundColor: UIColor, foregroundColor: UIColor, iconImage: UIImage? = nil, iconText: String? = nil) {
|
|
iconImageView?.image = iconImage
|
|
iconLabel?.text = iconText
|
|
iconImageView?.tintColor = foregroundColor
|
|
let backgroundView = self.backgroundView ?? self
|
|
backgroundView.backgroundColor = backgroundColor
|
|
iconLabel?.textColor = foregroundColor
|
|
titleLabel?.textColor = foregroundColor
|
|
bodyLabel?.textColor = foregroundColor
|
|
button?.backgroundColor = foregroundColor
|
|
button?.tintColor = backgroundColor
|
|
button?.contentEdgeInsets = UIEdgeInsets(top: 7.0, left: 7.0, bottom: 7.0, right: 7.0)
|
|
button?.layer.cornerRadius = 5.0
|
|
iconImageView?.isHidden = iconImageView?.image == nil
|
|
iconLabel?.isHidden = iconLabel?.text == nil
|
|
}
|
|
}
|
|
|
|
/*
|
|
MARK: - Configuring the content
|
|
|
|
This extension provides a few convenience functions for configuring the
|
|
message content. You are encouraged to write your own such functions
|
|
if these don't exactly meet your needs.
|
|
|
|
SwiftMessages does not try to be clever by adjusting the layout based on
|
|
what content you configure. All message elements are optional and it is
|
|
up to you to hide or remove elements you don't need. The easiest way to
|
|
remove unwanted elements is to drag-and-drop one of the included nib
|
|
files into your project as a starting point and make changes.
|
|
*/
|
|
|
|
extension MessageView {
|
|
|
|
/**
|
|
Sets the message body text.
|
|
|
|
- Parameter body: The message body text to use.
|
|
*/
|
|
public func configureContent(body: String) {
|
|
bodyLabel?.text = body
|
|
}
|
|
|
|
/**
|
|
Sets the message title and body text.
|
|
|
|
- Parameter title: The message title to use.
|
|
- Parameter body: The message body text to use.
|
|
*/
|
|
public func configureContent(title: String, body: String) {
|
|
configureContent(body: body)
|
|
titleLabel?.text = title
|
|
}
|
|
|
|
/**
|
|
Sets the message title, body text and icon image. Also hides the
|
|
`iconLabel`.
|
|
|
|
- Parameter title: The message title to use.
|
|
- Parameter body: The message body text to use.
|
|
- Parameter iconImage: The icon image to use.
|
|
*/
|
|
public func configureContent(title: String, body: String, iconImage: UIImage) {
|
|
configureContent(title: title, body: body)
|
|
iconImageView?.image = iconImage
|
|
iconImageView?.isHidden = false
|
|
iconLabel?.text = nil
|
|
iconLabel?.isHidden = true
|
|
}
|
|
|
|
/**
|
|
Sets the message title, body text and icon text (e.g. an emoji).
|
|
Also hides the `iconImageView`.
|
|
|
|
- Parameter title: The message title to use.
|
|
- Parameter body: The message body text to use.
|
|
- Parameter iconText: The icon text to use (e.g. an emoji).
|
|
*/
|
|
public func configureContent(title: String, body: String, iconText: String) {
|
|
configureContent(title: title, body: body)
|
|
iconLabel?.text = iconText
|
|
iconLabel?.isHidden = false
|
|
iconImageView?.isHidden = true
|
|
iconImageView?.image = nil
|
|
}
|
|
|
|
/**
|
|
Sets all configurable elements.
|
|
|
|
- Parameter title: The message title to use.
|
|
- Parameter body: The message body text to use.
|
|
- Parameter iconImage: The icon image to use.
|
|
- Parameter iconText: The icon text to use (e.g. an emoji).
|
|
- Parameter buttonImage: The button image to use.
|
|
- Parameter buttonTitle: The button title to use.
|
|
- Parameter buttonTapHandler: The button tap handler block to use.
|
|
*/
|
|
public func configureContent(title: String?, body: String?, iconImage: UIImage?, iconText: String?, buttonImage: UIImage?, buttonTitle: String?, buttonTapHandler: ((_ button: UIButton) -> Void)?) {
|
|
titleLabel?.text = title
|
|
bodyLabel?.text = body
|
|
iconImageView?.image = iconImage
|
|
iconLabel?.text = iconText
|
|
button?.setImage(buttonImage, for: .normal)
|
|
button?.setTitle(buttonTitle, for: .normal)
|
|
self.buttonTapHandler = buttonTapHandler
|
|
iconImageView?.isHidden = iconImageView?.image == nil
|
|
iconLabel?.isHidden = iconLabel?.text == nil
|
|
}
|
|
}
|
|
|
|
|