123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233 |
- import UIKit
- open class ToastView: UIView {
- // MARK: Properties
- open var text: String? {
- get { return self.textLabel.text }
- set { self.textLabel.text = newValue }
- }
- open var attributedText: NSAttributedString? {
- get { return self.textLabel.attributedText }
- set { self.textLabel.attributedText = newValue }
- }
-
- // MARK: Appearance
- /// The background view's color.
- override open dynamic var backgroundColor: UIColor? {
- get { return self.backgroundView.backgroundColor }
- set { self.backgroundView.backgroundColor = newValue }
- }
- /// The background view's corner radius.
- @objc open dynamic var cornerRadius: CGFloat {
- get { return self.backgroundView.layer.cornerRadius }
- set { self.backgroundView.layer.cornerRadius = newValue }
- }
- /// The inset of the text label.
- @objc open dynamic var textInsets = UIEdgeInsets(top: 6, left: 10, bottom: 6, right: 10)
- /// The color of the text label's text.
- @objc open dynamic var textColor: UIColor? {
- get { return self.textLabel.textColor }
- set { self.textLabel.textColor = newValue }
- }
- /// The font of the text label.
- @objc open dynamic var font: UIFont? {
- get { return self.textLabel.font }
- set { self.textLabel.font = newValue }
- }
- /// The bottom offset from the screen's bottom in portrait mode.
- @objc open dynamic var bottomOffsetPortrait: CGFloat = {
- switch UIDevice.current.userInterfaceIdiom {
- // specific values
- case .phone: return 30
- case .pad: return 60
- case .tv: return 90
- case .carPlay: return 30
- #if compiler(>=5.3)
- case .mac: return 60
- #endif
- // default values
- case .unspecified: fallthrough
- @unknown default: return 30
- }
- }()
- /// The bottom offset from the screen's bottom in landscape mode.
- @objc open dynamic var bottomOffsetLandscape: CGFloat = {
- switch UIDevice.current.userInterfaceIdiom {
- // specific values
- case .phone: return 20
- case .pad: return 40
- case .tv: return 60
- case .carPlay: return 20
- #if compiler(>=5.3)
- case .mac: return 40
- #endif
- // default values
- case .unspecified: fallthrough
- @unknown default: return 20
- }
- }()
-
- /// If this value is `true` and SafeArea is available,
- /// `safeAreaInsets.bottom` will be added to the `bottomOffsetPortrait` and `bottomOffsetLandscape`.
- /// Default value: false
- @objc open dynamic var useSafeAreaForBottomOffset: Bool = false
- /// The width ratio of toast view in window, specified as a value from 0.0 to 1.0.
- /// Default value: 0.875
- @objc open dynamic var maxWidthRatio: CGFloat = (280.0 / 320.0)
-
- /// The shape of the layer’s shadow.
- @objc open dynamic var shadowPath: CGPath? {
- get { return self.layer.shadowPath }
- set { self.layer.shadowPath = newValue }
- }
-
- /// The color of the layer’s shadow.
- @objc open dynamic var shadowColor: UIColor? {
- get { return self.layer.shadowColor.flatMap { UIColor(cgColor: $0) } }
- set { self.layer.shadowColor = newValue?.cgColor }
- }
-
- /// The opacity of the layer’s shadow.
- @objc open dynamic var shadowOpacity: Float {
- get { return self.layer.shadowOpacity }
- set { self.layer.shadowOpacity = newValue }
- }
-
- /// The offset (in points) of the layer’s shadow.
- @objc open dynamic var shadowOffset: CGSize {
- get { return self.layer.shadowOffset }
- set { self.layer.shadowOffset = newValue }
- }
-
- /// The blur radius (in points) used to render the layer’s shadow.
- @objc open dynamic var shadowRadius: CGFloat {
- get { return self.layer.shadowRadius }
- set { self.layer.shadowRadius = newValue }
- }
- // MARK: UI
- private let backgroundView: UIView = {
- let `self` = UIView()
- self.backgroundColor = UIColor(white: 0, alpha: 0.7)
- self.layer.cornerRadius = 5
- self.clipsToBounds = true
- return self
- }()
-
- private let textLabel: UILabel = {
- let `self` = UILabel()
- self.textColor = .white
- self.backgroundColor = .clear
- self.font = {
- switch UIDevice.current.userInterfaceIdiom {
- // specific values
- case .phone: return .systemFont(ofSize: 12)
- case .pad: return .systemFont(ofSize: 16)
- case .tv: return .systemFont(ofSize: 20)
- case .carPlay: return .systemFont(ofSize: 12)
- #if compiler(>=5.3)
- case .mac: return .systemFont(ofSize: 16)
- #endif
- // default values
- case .unspecified: fallthrough
- @unknown default: return .systemFont(ofSize: 12)
- }
- }()
- self.numberOfLines = 0
- self.textAlignment = .center
- return self
- }()
- // MARK: Initializing
- public init() {
- super.init(frame: .zero)
- self.isUserInteractionEnabled = false
- self.addSubview(self.backgroundView)
- self.addSubview(self.textLabel)
- }
- required convenience public init?(coder aDecoder: NSCoder) {
- self.init()
- }
- // MARK: Layout
- override open func layoutSubviews() {
- super.layoutSubviews()
- let containerSize = ToastWindow.shared.frame.size
- let constraintSize = CGSize(
- width: containerSize.width * maxWidthRatio - self.textInsets.left - self.textInsets.right,
- height: CGFloat.greatestFiniteMagnitude
- )
- let textLabelSize = self.textLabel.sizeThatFits(constraintSize)
- self.textLabel.frame = CGRect(
- x: self.textInsets.left,
- y: self.textInsets.top,
- width: textLabelSize.width,
- height: textLabelSize.height
- )
- self.backgroundView.frame = CGRect(
- x: 0,
- y: 0,
- width: self.textLabel.frame.size.width + self.textInsets.left + self.textInsets.right,
- height: self.textLabel.frame.size.height + self.textInsets.top + self.textInsets.bottom
- )
- var x: CGFloat
- var y: CGFloat
- var width: CGFloat
- var height: CGFloat
- let orientation = UIApplication.shared.statusBarOrientation
- if orientation.isPortrait || !ToastWindow.shared.shouldRotateManually {
- width = containerSize.width
- height = containerSize.height
- y = self.bottomOffsetPortrait
- } else {
- width = containerSize.height
- height = containerSize.width
- y = self.bottomOffsetLandscape
- }
- if #available(iOS 11.0, *), useSafeAreaForBottomOffset {
- y += ToastWindow.shared.safeAreaInsets.bottom
- }
- let backgroundViewSize = self.backgroundView.frame.size
- x = (width - backgroundViewSize.width) * 0.5
- y = height - (backgroundViewSize.height + y)
- self.frame = CGRect(
- x: x,
- y: y,
- width: backgroundViewSize.width,
- height: backgroundViewSize.height
- )
- }
- override open func hitTest(_ point: CGPoint, with event: UIEvent!) -> UIView? {
- if let superview = self.superview {
- let pointInWindow = self.convert(point, to: superview)
- let contains = self.frame.contains(pointInWindow)
- if contains && self.isUserInteractionEnabled {
- return self
- }
- }
- return nil
- }
- }
|