From 6b0099ad400fe0261a2c82180df70ba1df479ccf Mon Sep 17 00:00:00 2001 From: Tijme Gommers Date: Mon, 18 Nov 2019 19:30:42 +0100 Subject: [PATCH 1/2] Fixed #7. Made `keyWindow` public so users can set their own parent view. This allows anyone to apply keyboard constraints as they want to. --- Source/SPAlert/Views/SPAlertView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Source/SPAlert/Views/SPAlertView.swift b/Source/SPAlert/Views/SPAlertView.swift index ea31696..3002aa1 100644 --- a/Source/SPAlert/Views/SPAlertView.swift +++ b/Source/SPAlert/Views/SPAlertView.swift @@ -142,6 +142,7 @@ open class SPAlertView: UIView { haptic.impact() keyWindow.addSubview(self) layoutIfNeeded() + layoutSubviews() alpha = 0 transform = transform.scaledBy(x: 0.8, y: 0.8) @@ -226,5 +227,5 @@ open class SPAlertView: UIView { } } - private var keyWindow: UIWindow { return UIApplication.shared.keyWindow ?? UIWindow() } + public var keyWindow: UIView = (UIApplication.shared.keyWindow ?? UIWindow()) } From f6290f15cfda64bef5800814d67f25e902590ee8 Mon Sep 17 00:00:00 2001 From: Tijme Gommers Date: Mon, 18 Nov 2019 20:37:01 +0100 Subject: [PATCH 2/2] Added a "cross" animation/icon, as specified in issue #9. --- Example/Controllers/ViewController.swift | 5 +- SPAlert.xcodeproj/project.pbxproj | 4 + Source/SPAlert/Models/SPAlertHaptic.swift | 6 +- Source/SPAlert/Models/SPAlertPreset.swift | 13 +++ .../Views/Icons/SPAlertIconErrorView.swift | 84 +++++++++++++++++++ 5 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 Source/SPAlert/Views/Icons/SPAlertIconErrorView.swift diff --git a/Example/Controllers/ViewController.swift b/Example/Controllers/ViewController.swift index 04bab25..c4db94d 100644 --- a/Example/Controllers/ViewController.swift +++ b/Example/Controllers/ViewController.swift @@ -5,8 +5,9 @@ class ViewController: UITableViewController { let data: [Alert] = [ Alert(key: "Done", preset: .done, title: "Added to Library", subtitle: nil), - Alert(key: "Heart", preset: .heart, title: "Love", subtitle: "We'll recommend more like this in for You"), - Alert(key: "Message", preset: nil, title: nil, subtitle: "Email requerid") + Alert(key: "Heart", preset: .heart, title: "Love", subtitle: "We'll recommend more like this for you"), + Alert(key: "Message", preset: nil, title: nil, subtitle: "Email required"), + Alert(key: "Error", preset: .error, title: "Oops", subtitle: "Please try again later") ] var selectedIndexPath: IndexPath { diff --git a/SPAlert.xcodeproj/project.pbxproj b/SPAlert.xcodeproj/project.pbxproj index 388ad7d..40923fb 100644 --- a/SPAlert.xcodeproj/project.pbxproj +++ b/SPAlert.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 706F894A2383277500ECF5D1 /* SPAlertIconErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 706F89492383277500ECF5D1 /* SPAlertIconErrorView.swift */; }; F41204E92382AC9B009C2AC7 /* SPAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41204DF2382AC9B009C2AC7 /* SPAlertView.swift */; }; F41204EA2382AC9B009C2AC7 /* SPAlertPreset.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41204E02382AC9B009C2AC7 /* SPAlertPreset.swift */; }; F41204EB2382AC9B009C2AC7 /* SPAlertIconHeartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41204E22382AC9B009C2AC7 /* SPAlertIconHeartView.swift */; }; @@ -49,6 +50,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 706F89492383277500ECF5D1 /* SPAlertIconErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPAlertIconErrorView.swift; sourceTree = ""; }; F41204D02382ABE9009C2AC7 /* SPAlert.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SPAlert.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F41204DF2382AC9B009C2AC7 /* SPAlertView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPAlertView.swift; sourceTree = ""; }; F41204E02382AC9B009C2AC7 /* SPAlertPreset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPAlertPreset.swift; sourceTree = ""; }; @@ -151,6 +153,7 @@ children = ( F41204E22382AC9B009C2AC7 /* SPAlertIconHeartView.swift */, F41204E32382AC9B009C2AC7 /* SPAlertIconDoneView.swift */, + 706F89492383277500ECF5D1 /* SPAlertIconErrorView.swift */, ); path = Icons; sourceTree = ""; @@ -351,6 +354,7 @@ F41204EA2382AC9B009C2AC7 /* SPAlertPreset.swift in Sources */, F41204EC2382AC9B009C2AC7 /* SPAlertIconDoneView.swift in Sources */, F41204E92382AC9B009C2AC7 /* SPAlertView.swift in Sources */, + 706F894A2383277500ECF5D1 /* SPAlertIconErrorView.swift in Sources */, F41205292382AF5C009C2AC7 /* SPAlertIconAnimatable.swift in Sources */, F41204EB2382AC9B009C2AC7 /* SPAlertIconHeartView.swift in Sources */, F412052B2382AF64009C2AC7 /* SPAlertLayout.swift in Sources */, diff --git a/Source/SPAlert/Models/SPAlertHaptic.swift b/Source/SPAlert/Models/SPAlertHaptic.swift index 4e4928e..0af1e95 100644 --- a/Source/SPAlert/Models/SPAlertHaptic.swift +++ b/Source/SPAlert/Models/SPAlertHaptic.swift @@ -24,13 +24,17 @@ import UIKit public enum SPAlertHaptic { case success + case error case none func impact() { + let generator = UINotificationFeedbackGenerator() + switch self { case .success: - let generator = UINotificationFeedbackGenerator() generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.success) + case .error: + generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.error) case .none: break } diff --git a/Source/SPAlert/Models/SPAlertPreset.swift b/Source/SPAlert/Models/SPAlertPreset.swift index ca4b7ff..3d11d81 100644 --- a/Source/SPAlert/Models/SPAlertPreset.swift +++ b/Source/SPAlert/Models/SPAlertPreset.swift @@ -25,6 +25,7 @@ public enum SPAlertPreset { case done case heart + case error var iconView: UIView { switch self { @@ -32,6 +33,8 @@ public enum SPAlertPreset { return SPAlertIconDoneView() case .heart: return SPAlertIconHeartView() + case .error: + return SPAlertIconErrorView() } } @@ -53,6 +56,14 @@ public enum SPAlertPreset { layout.iconHeight = 77 layout.bottomIconSpace = 35 return layout + case .error: + var layout = SPAlertLayout() + layout.topSpace = 63 + layout.bottomSpace = 29 + layout.iconWidth = 112 + layout.iconHeight = 112 + layout.bottomIconSpace = 35 + return layout } } @@ -62,6 +73,8 @@ public enum SPAlertPreset { return .success case .heart: return .success + case .error: + return .error } } } diff --git a/Source/SPAlert/Views/Icons/SPAlertIconErrorView.swift b/Source/SPAlert/Views/Icons/SPAlertIconErrorView.swift new file mode 100644 index 0000000..018d80c --- /dev/null +++ b/Source/SPAlert/Views/Icons/SPAlertIconErrorView.swift @@ -0,0 +1,84 @@ +// The MIT License (MIT) +// Copyright © 2019 Ivan Vorobei (ivanvorobei@icloud.com) +// +// 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 + +public class SPAlertIconErrorView: UIView, SPAlertIconAnimatable { + + public func animate() { + animateTopToBottomLine() + animateBottomToTopLine() + } + + private func animateTopToBottomLine() { + let length = frame.width + + let topToBottomLine = UIBezierPath() + topToBottomLine.move(to: CGPoint(x: length * 0.1, y: length * 0.1)) + topToBottomLine.addLine(to: CGPoint(x: length * 0.9, y: length * 0.9)) + + let animatableLayer = CAShapeLayer() + animatableLayer.path = topToBottomLine.cgPath + animatableLayer.fillColor = UIColor.clear.cgColor + animatableLayer.strokeColor = tintColor?.cgColor + animatableLayer.lineWidth = 9 + animatableLayer.lineCap = .round + animatableLayer.lineJoin = .round + animatableLayer.strokeEnd = 0 + self.layer.addSublayer(animatableLayer) + + let animation = CABasicAnimation(keyPath: "strokeEnd") + animation.duration = 0.3 + animation.fromValue = 0 + animation.toValue = 1 + animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + + animatableLayer.strokeEnd = 1 + animatableLayer.add(animation, forKey: "animation") + } + + private func animateBottomToTopLine() { + let length = frame.width + + let bottomToTopLine = UIBezierPath() + bottomToTopLine.move(to: CGPoint(x: length * 0.1, y: length * 0.9)) + bottomToTopLine.addLine(to: CGPoint(x: length * 0.9, y: length * 0.1)) + + let animatableLayer = CAShapeLayer() + animatableLayer.path = bottomToTopLine.cgPath + animatableLayer.fillColor = UIColor.clear.cgColor + animatableLayer.strokeColor = tintColor?.cgColor + animatableLayer.lineWidth = 9 + animatableLayer.lineCap = .round + animatableLayer.lineJoin = .round + animatableLayer.strokeEnd = 0 + self.layer.addSublayer(animatableLayer) + + let animation = CABasicAnimation(keyPath: "strokeEnd") + animation.duration = 0.3 + animation.fromValue = 0 + animation.toValue = 1 + animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) + + animatableLayer.strokeEnd = 1 + animatableLayer.add(animation, forKey: "animation") + } +}