From da74ac3a3e921fa3c20d976672a5f34572b9709f Mon Sep 17 00:00:00 2001 From: MyunggiSon Date: Thu, 9 Nov 2023 14:59:26 +0900 Subject: [PATCH 1/4] feat: impl based constraints showToast --- Toast/Toast.swift | 73 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/Toast/Toast.swift b/Toast/Toast.swift index 49e64a8..7eff84b 100644 --- a/Toast/Toast.swift +++ b/Toast/Toast.swift @@ -28,7 +28,7 @@ import ObjectiveC /** Toast is a Swift extension that adds toast notifications to the `UIView` object class. - It is intended to be simple, lightweight, and easy to use. Most toast notifications + It is intended to be simple, lightweight, and easy to use. Most toast notifications can be triggered with a single line of code. The `makeToast` methods create a new view and then display it as toast. @@ -46,6 +46,7 @@ public extension UIView { static var duration = "com.toast-swift.duration" static var point = "com.toast-swift.point" static var completion = "com.toast-swift.completion" + static var constraints = "com.toast-swift.constraints" static var activeToasts = "com.toast-swift.activeToasts" static var activityView = "com.toast-swift.activityView" static var queue = "com.toast-swift.queue" @@ -64,6 +65,14 @@ public extension UIView { } } + private class ToastConstraintsWrapper { + let constraints: ((_ container: UIView, _ toast: UIView) -> Void)? + + init(_ constraints: ((_ container: UIView, _ toast: UIView) -> Void)?) { + self.constraints = constraints + } + } + private enum ToastError: Error { case missingParameters } @@ -177,6 +186,31 @@ public extension UIView { showToast(toast, duration: duration, point: point) } } + + /** + Displays any view as toast at a provided position and duration. The completion closure + executes when the toast view completes. `didTap` will be `true` if the toast view was + dismissed from a tap. + + @param toast The view to be displayed as toast + @param duration The notification duration + @param constarintsBlock The constraints block, executed after the toast view added + @param completion The completion block, executed after the toast view disappears. + didTap will be `true` if the toast view was dismissed from a tap. + */ + func showToast(_ toast: UIView, duration: TimeInterval = ToastManager.shared.duration, constraints: @escaping (_ container: UIView, _ toast: UIView) -> Void, completion: ((_ didTap: Bool) -> Void)? = nil) { + + objc_setAssociatedObject(toast, &ToastKeys.completion, ToastCompletionWrapper(completion), .OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + if ToastManager.shared.isQueueEnabled, activeToasts.count > 0 { + objc_setAssociatedObject(toast, &ToastKeys.duration, NSNumber(value: duration), .OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(toast, &ToastKeys.constraints, ToastConstraintsWrapper(constraints), .OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + queue.add(toast) + } else { + showToast(toast, duration: duration, constraints: constraints) + } + } // MARK: - Hide Toast Methods @@ -334,20 +368,44 @@ public extension UIView { // MARK: - Private Show/Hide Methods + + private func showToast(_ toast: UIView, duration: TimeInterval, constraints: (_ container: UIView, _ toast: UIView) -> Void) { + toast.alpha = 0.0 + + if ToastManager.shared.isTapToDismissEnabled { + let recognizer = UITapGestureRecognizer(target: self, action: #selector(UIView.handleToastTapped(_:))) + toast.addGestureRecognizer(recognizer) + toast.isUserInteractionEnabled = true + toast.isExclusiveTouch = true + } + activeToasts.add(toast) + self.addSubview(toast) + + constraints(self, toast) + + UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseOut, .allowUserInteraction], animations: { + toast.alpha = 1.0 + }) { _ in + let timer = Timer(timeInterval: duration, target: self, selector: #selector(UIView.toastTimerDidFinish(_:)), userInfo: toast, repeats: false) + RunLoop.main.add(timer, forMode: .common) + objc_setAssociatedObject(toast, &ToastKeys.timer, timer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + private func showToast(_ toast: UIView, duration: TimeInterval, point: CGPoint) { toast.center = point toast.alpha = 0.0 - + if ToastManager.shared.isTapToDismissEnabled { let recognizer = UITapGestureRecognizer(target: self, action: #selector(UIView.handleToastTapped(_:))) toast.addGestureRecognizer(recognizer) toast.isUserInteractionEnabled = true toast.isExclusiveTouch = true } - + activeToasts.add(toast) self.addSubview(toast) - + UIView.animate(withDuration: ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.curveEaseOut, .allowUserInteraction], animations: { toast.alpha = 1.0 }) { _ in @@ -375,6 +433,13 @@ public extension UIView { if let nextToast = self.queue.firstObject as? UIView, let duration = objc_getAssociatedObject(nextToast, &ToastKeys.duration) as? NSNumber, let point = objc_getAssociatedObject(nextToast, &ToastKeys.point) as? NSValue { self.queue.removeObject(at: 0) self.showToast(nextToast, duration: duration.doubleValue, point: point.cgPointValue) + return + } + + if let nextToast = self.queue.firstObject as? UIView, let duration = objc_getAssociatedObject(nextToast, &ToastKeys.duration) as? NSNumber, let wrapper = objc_getAssociatedObject(toast, &ToastKeys.constraints) as? ToastConstraintsWrapper, let constraints = wrapper.constraints { + self.queue.removeObject(at: 0) + self.showToast(nextToast, duration: duration.doubleValue, constraints: constraints) + return } } } From ccb6ce1e7a3dd73dcb43ed7fa6fdfe14c606faa5 Mon Sep 17 00:00:00 2001 From: MyunggiSon Date: Thu, 9 Nov 2023 15:00:26 +0900 Subject: [PATCH 2/4] feat: add based constraints showToast example --- Example/ViewController.swift | 43 +++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 88c4903..1d54cd7 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -77,7 +77,7 @@ extension ViewController { if section == 0 { return 2 } else { - return 11 + return 12 } } @@ -149,6 +149,7 @@ extension ViewController { case 8: cell.textLabel?.text = showingActivity ? "Hide toast activity" : "Show toast activity" case 9: cell.textLabel?.text = "Hide toast" case 10: cell.textLabel?.text = "Hide all toasts" + case 11: cell.textLabel?.text = "Make toast autolayout" default: cell.textLabel?.text = nil } @@ -219,8 +220,48 @@ extension ViewController { case 10: // Hide all toasts self.navigationController?.view.hideAllToasts() + + case 11: + self.navigationController?.view.showToast(CustomToast(), constraints: { container, toast in + toast.translatesAutoresizingMaskIntoConstraints = false + toast.leftAnchor.constraint(equalTo: container.leftAnchor, constant: 20).isActive = true + toast.rightAnchor.constraint(equalTo: container.rightAnchor, constant: -20).isActive = true + toast.bottomAnchor.constraint(equalTo: container.bottomAnchor, constant: -40).isActive = true + container.layoutIfNeeded() + }) default: break } } } + + +class CustomToast: UIView { + + let titleLabel = { + let lb = UILabel() + lb.text = "Hello World" + return lb + }() + + override var intrinsicContentSize: CGSize { + return .init(width: UIView.noIntrinsicMetric, height: 60) + } + + override init(frame: CGRect) { + super.init(frame: frame) + self.setup() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setup() { + self.backgroundColor = .red + self.addSubview(self.titleLabel) + self.titleLabel.translatesAutoresizingMaskIntoConstraints = false + self.titleLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true + self.titleLabel.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true + } +} From e44faa939e2bfbd13e7590559d81a66e222edc71 Mon Sep 17 00:00:00 2001 From: MyunggiSon Date: Thu, 9 Nov 2023 18:31:44 +0900 Subject: [PATCH 3/4] style: modify doc --- Toast/Toast.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Toast/Toast.swift b/Toast/Toast.swift index 7eff84b..fafdc9d 100644 --- a/Toast/Toast.swift +++ b/Toast/Toast.swift @@ -188,13 +188,13 @@ public extension UIView { } /** - Displays any view as toast at a provided position and duration. The completion closure + Displays any view as toast at a provided constraintsBlock and duration. The completion closure executes when the toast view completes. `didTap` will be `true` if the toast view was dismissed from a tap. @param toast The view to be displayed as toast @param duration The notification duration - @param constarintsBlock The constraints block, executed after the toast view added + @param constarints The constraints block, executed after the toast view added @param completion The completion block, executed after the toast view disappears. didTap will be `true` if the toast view was dismissed from a tap. */ From b09eb169bdfcc11b3214eb4f42ac199a45c36a9e Mon Sep 17 00:00:00 2001 From: MyunggiSon Date: Thu, 9 Nov 2023 18:33:09 +0900 Subject: [PATCH 4/4] style: modify title --- Example/ViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Example/ViewController.swift b/Example/ViewController.swift index 1d54cd7..09c244c 100644 --- a/Example/ViewController.swift +++ b/Example/ViewController.swift @@ -149,7 +149,7 @@ extension ViewController { case 8: cell.textLabel?.text = showingActivity ? "Hide toast activity" : "Show toast activity" case 9: cell.textLabel?.text = "Hide toast" case 10: cell.textLabel?.text = "Hide all toasts" - case 11: cell.textLabel?.text = "Make toast autolayout" + case 11: cell.textLabel?.text = "Make toast with autolayout" default: cell.textLabel?.text = nil }