Files
AlertKit/Sources/SPAlert/SPAlertView.swift
T
2021-06-19 11:16:44 +03:00

253 lines
8.9 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// The MIT License (MIT)
// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.by)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
import UIKit
/**
SPAlert: Main view. Can be customisable if need.
For change duration, check method `present` and pass duration and other specific property if need customise.
Here available set window on which shoud be present.
If you have some windows, you shoud configure it. Check property `presentWindow`.
For disable dismiss by tap, check property `.dismissByTap`.
Recomended call `SPAlert` and choose style func.
*/
open class SPAlertView: UIView {
// MARK: - Properties
open var dismissByTap: Bool = true
open var completion: (() -> Void)? = nil
// MARK: - Views
open var titleLabel: UILabel?
open var subtitleLabel: UILabel?
open var iconView: UIView?
private lazy var backgroundView: UIVisualEffectView = {
let view: UIVisualEffectView = {
if #available(iOS 13.0, *) {
return UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial))
} else {
return UIVisualEffectView(effect: UIBlurEffect(style: .light))
}
}()
view.isUserInteractionEnabled = false
return view
}()
weak open var presentWindow: UIWindow?
// MARK: - Init
public init(title: String, message: String? = nil, preset: SPAlertIconPreset) {
super.init(frame: CGRect.zero)
commonInit()
layout = SPAlertLayout(for: preset)
setTitle(title)
if let message = message {
setMessage(message)
}
setIcon(for: preset)
}
public init(message: String) {
super.init(frame: CGRect.zero)
commonInit()
layout = SPAlertLayout.message()
setMessage(message)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
preservesSuperviewLayoutMargins = false
if #available(iOS 11.0, *) {
insetsLayoutMarginsFromSafeArea = false
}
layer.masksToBounds = true
layer.cornerRadius = SPAlertConfiguration.cornerRadius
backgroundColor = .clear
addSubview(backgroundView)
if dismissByTap {
let tapGesterRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismiss))
addGestureRecognizer(tapGesterRecognizer)
}
}
// MARK: - Configure
private func setTitle(_ text: String) {
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .title2, weight: .bold)
label.numberOfLines = 0
let style = NSMutableParagraphStyle()
style.lineSpacing = 3
style.alignment = .center
label.attributedText = NSAttributedString(string: text, attributes: [.paragraphStyle: style])
titleLabel = label
addSubview(label)
}
private func setMessage(_ text: String) {
let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .body)
label.numberOfLines = 0
let style = NSMutableParagraphStyle()
style.lineSpacing = 2
style.alignment = .center
label.attributedText = NSAttributedString(string: text, attributes: [.paragraphStyle: style])
subtitleLabel = label
addSubview(label)
}
private func setIcon(for preset: SPAlertIconPreset) {
let view = preset.createView()
self.iconView = view
addSubview(view)
}
// MARK: - Present
fileprivate var presentDismissDuration: TimeInterval = 0.2
fileprivate var presentDismissScale: CGFloat = 0.8
fileprivate var defaultContentColor: UIColor {
let darkColor = UIColor(red: 127 / 255, green: 127 / 255, blue: 129 / 255, alpha: 1)
let lightColor = UIColor(red: 88 / 255, green: 87 / 255, blue: 88 / 255, alpha: 1)
if #available(iOS 12.0, *) {
guard let interfaceStyle = self.window?.traitCollection.userInterfaceStyle else {
return lightColor
}
switch interfaceStyle {
case .light: return lightColor
case .dark: return darkColor
case .unspecified: return lightColor
@unknown default: return lightColor
}
} else {
return lightColor
}
}
open func present(duration: TimeInterval = SPAlertConfiguration.duration, haptic: SPAlertHaptic = .success, completion: (() -> Void)? = nil) {
if self.presentWindow == nil {
self.presentWindow = UIApplication.shared.keyWindow
}
guard let window = self.presentWindow else { return }
window.addSubview(self)
// Prepare for present
self.completion = completion
let contentСolor = defaultContentColor
titleLabel?.textColor = contentСolor
subtitleLabel?.textColor = contentСolor
iconView?.tintColor = contentСolor
alpha = 0
setFrame()
transform = transform.scaledBy(x: self.presentDismissScale, y: self.presentDismissScale)
// Present
haptic.impact()
UIView.animate(withDuration: presentDismissDuration, animations: {
self.alpha = 1
self.transform = CGAffineTransform.identity
}, completion: { finished in
if let iconView = self.iconView as? SPAlertIconAnimatable {
iconView.animate()
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration) {
self.dismiss()
}
})
}
@objc open func dismiss() {
UIView.animate(withDuration: presentDismissDuration, animations: {
self.alpha = 0
self.transform = self.transform.scaledBy(x: self.presentDismissScale, y: self.presentDismissScale)
}, completion: { [weak self] finished in
self?.removeFromSuperview()
self?.completion?()
})
}
// MARK: - Layout
open var layout: SPAlertLayout = .init()
fileprivate func setFrame() {
guard let window = self.presentWindow else { return }
frame = CGRect.init(x: 0, y: 0, width: 250, height: 100)
center = .init(x: window.frame.midX, y: window.frame.midY)
layoutMargins = layout.margins
sizeToFit()
center = .init(x: window.frame.midX, y: window.frame.midY)
}
open override func sizeThatFits(_ size: CGSize) -> CGSize {
layoutSubviews()
let bottomY = [subtitleLabel, titleLabel, iconView].first(where: { $0 != nil })??.frame.maxY ?? 150
return CGSize.init(width: frame.width, height: bottomY + layoutMargins.bottom)
}
open override func layoutSubviews() {
super.layoutSubviews()
backgroundView.frame = bounds
if let iconView = self.iconView {
iconView.frame = .init(origin: .init(x: 0, y: layoutMargins.top), size: layout.iconSize)
iconView.center.x = bounds.midX
}
if let titleLabel = self.titleLabel {
titleLabel.layoutDynamicHeight(
x: layoutMargins.left,
y: iconView == nil ? layoutMargins.top : (iconView?.frame.maxY ?? 0) + layout.spaceBetweenIconAndTitle,
width: frame.width - layoutMargins.left - layoutMargins.right)
}
if let subtitleLabel = self.subtitleLabel {
let yPosition: CGFloat = {
if let titleLabel = self.titleLabel {
return titleLabel.frame.maxY + 4
} else {
return layoutMargins.top
}
}()
subtitleLabel.layoutDynamicHeight(x: layoutMargins.left, y: yPosition, width: frame.width - layoutMargins.left - layoutMargins.right)
}
}
}