From 16a65bbabaff2dd4b034f94ab6c578d5e1683b32 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Fri, 30 Jun 2023 11:31:23 +0300 Subject: [PATCH 01/37] Added source code of v5. --- .github/ISSUE_TEMPLATE/bug_report.md | 8 - .github/ISSUE_TEMPLATE/feature_request.md | 4 - .github/ISSUE_TEMPLATE/question.md | 11 - .github/PULL_REQUEST_TEMPLATE.md | 5 - .gitignore | 5 +- .mailmap | 3 - CONTRIBUTING.md | 5 +- Example App/SPAlert.xcodeproj/project.pbxproj | 413 ------------------ .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../xcshareddata/swiftpm/Package.resolved | 25 -- .../xcschemes/iOS Example.xcscheme | 78 ---- .../xcdebugger/Breakpoints_v2.xcbkptlist | 6 - .../xcschemes/xcschememanagement.plist | 22 - Example App/iOS Example/App/AppDelegate.swift | 39 -- .../AppIcon.appiconset/Contents.json | 116 ----- .../AppIcon.appiconset/Icon.png | Bin 36557 -> 0 bytes .../AppIcon.appiconset/icon_20pt.png | Bin 410 -> 0 bytes .../AppIcon.appiconset/icon_20pt@2x-1.png | Bin 767 -> 0 bytes .../AppIcon.appiconset/icon_20pt@2x.png | Bin 767 -> 0 bytes .../AppIcon.appiconset/icon_20pt@3x.png | Bin 1121 -> 0 bytes .../AppIcon.appiconset/icon_29pt.png | Bin 570 -> 0 bytes .../AppIcon.appiconset/icon_29pt@2x-1.png | Bin 1069 -> 0 bytes .../AppIcon.appiconset/icon_29pt@2x.png | Bin 1069 -> 0 bytes .../AppIcon.appiconset/icon_29pt@3x.png | Bin 1612 -> 0 bytes .../AppIcon.appiconset/icon_40pt.png | Bin 767 -> 0 bytes .../AppIcon.appiconset/icon_40pt@2x-1.png | Bin 1498 -> 0 bytes .../AppIcon.appiconset/icon_40pt@2x.png | Bin 1498 -> 0 bytes .../AppIcon.appiconset/icon_40pt@3x.png | Bin 2274 -> 0 bytes .../AppIcon.appiconset/icon_60pt@2x.png | Bin 2274 -> 0 bytes .../AppIcon.appiconset/icon_60pt@3x.png | Bin 3505 -> 0 bytes .../AppIcon.appiconset/icon_76pt.png | Bin 1407 -> 0 bytes .../AppIcon.appiconset/icon_76pt@2x.png | Bin 2969 -> 0 bytes .../AppIcon.appiconset/icon_83.5@2x.png | Bin 3284 -> 0 bytes .../App/Assets.xcassets/Contents.json | 6 - .../App/Base.lproj/LaunchScreen.storyboard | 25 -- .../Controllers/PresetsController.swift | 141 ------ Example App/iOS Example/Info.plist | 47 -- .../iOS Example/Models/AlertPresetModel.swift | 38 -- Package.swift | 12 +- README.md | 228 ++-------- SPAlert.podspec | 10 +- Sources/AlertKit/AlertHaptic.swift | 23 + Sources/AlertKit/AlertIcon.swift | 31 ++ Sources/AlertKit/AlertKitAPI.swift | 19 + Sources/AlertKit/AlertViewStyle.swift | 7 + .../Extensions/UIFontExtension.swift | 0 .../Extensions/UILabelExtension.swift | 12 + .../AlertKit/Icons/AlertIconDoneView.swift | 42 ++ .../Icons/AlertIconErrorView.swift} | 38 +- .../Icons/AlertIconHeartView.swift} | 23 +- Sources/AlertKit/Icons/AlertSpinnerView.swift | 25 ++ .../Views/AlertAppleMusic16View.swift | 315 +++++++++++++ .../Views/AlertAppleMusic17View.swift | 266 +++++++++++ .../SPAlert/Extensions/SwiftUIExtension.swift | 82 ---- .../SPAlert/Extensions/UILabelExtension.swift | 33 -- Sources/SPAlert/Models/SPAlertLayout.swift | 49 --- .../Preset Views/SPAlertIconDoneView.swift | 51 --- .../Preset Views/SPAlertSpinnerView.swift | 51 --- .../Protocols/SPAlertIconAnimatable.swift | 33 -- Sources/SPAlert/SPAlert.swift | 105 ----- Sources/SPAlert/SPAlertIconPreset.swift | 156 ------- Sources/SPAlert/SPAlertView.swift | 302 ------------- Sources/SPAlert/Services/SPAlertHaptic.swift | 47 -- TODO.md | 5 - 65 files changed, 814 insertions(+), 2163 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/question.md delete mode 100644 .mailmap delete mode 100644 Example App/SPAlert.xcodeproj/project.pbxproj delete mode 100644 Example App/SPAlert.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 Example App/SPAlert.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 Example App/SPAlert.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100644 Example App/SPAlert.xcodeproj/xcshareddata/xcschemes/iOS Example.xcscheme delete mode 100644 Example App/SPAlert.xcodeproj/xcuserdata/ivanvorobei.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist delete mode 100644 Example App/SPAlert.xcodeproj/xcuserdata/ivanvorobei.xcuserdatad/xcschemes/xcschememanagement.plist delete mode 100644 Example App/iOS Example/App/AppDelegate.swift delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/Contents.json delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/Icon.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_76pt.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png delete mode 100644 Example App/iOS Example/App/Assets.xcassets/Contents.json delete mode 100644 Example App/iOS Example/App/Base.lproj/LaunchScreen.storyboard delete mode 100644 Example App/iOS Example/Controllers/PresetsController.swift delete mode 100644 Example App/iOS Example/Info.plist delete mode 100644 Example App/iOS Example/Models/AlertPresetModel.swift create mode 100644 Sources/AlertKit/AlertHaptic.swift create mode 100644 Sources/AlertKit/AlertIcon.swift create mode 100644 Sources/AlertKit/AlertKitAPI.swift create mode 100644 Sources/AlertKit/AlertViewStyle.swift rename Sources/{SPAlert => AlertKit}/Extensions/UIFontExtension.swift (100%) create mode 100644 Sources/AlertKit/Extensions/UILabelExtension.swift create mode 100644 Sources/AlertKit/Icons/AlertIconDoneView.swift rename Sources/{SPAlert/Preset Views/SPAlertIconErrorView.swift => AlertKit/Icons/AlertIconErrorView.swift} (62%) rename Sources/{SPAlert/Preset Views/SPAlertIconHeartView.swift => AlertKit/Icons/AlertIconHeartView.swift} (76%) create mode 100644 Sources/AlertKit/Icons/AlertSpinnerView.swift create mode 100644 Sources/AlertKit/Views/AlertAppleMusic16View.swift create mode 100644 Sources/AlertKit/Views/AlertAppleMusic17View.swift delete mode 100644 Sources/SPAlert/Extensions/SwiftUIExtension.swift delete mode 100644 Sources/SPAlert/Extensions/UILabelExtension.swift delete mode 100644 Sources/SPAlert/Models/SPAlertLayout.swift delete mode 100644 Sources/SPAlert/Preset Views/SPAlertIconDoneView.swift delete mode 100644 Sources/SPAlert/Preset Views/SPAlertSpinnerView.swift delete mode 100644 Sources/SPAlert/Protocols/SPAlertIconAnimatable.swift delete mode 100644 Sources/SPAlert/SPAlert.swift delete mode 100644 Sources/SPAlert/SPAlertIconPreset.swift delete mode 100644 Sources/SPAlert/SPAlertView.swift delete mode 100644 Sources/SPAlert/Services/SPAlertHaptic.swift delete mode 100644 TODO.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b28f7b0..9f2e97f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -5,11 +5,3 @@ title: '' labels: bug assignees: ivanvorobei --- - -**Details** - - iOS Version [e.g. 15.2] - - Framework Version [e.g. 1.0.2] - - Installed via [e.g. SPM, Cocoapods, Manually] - -**Describe the Bug** -A clear and concise description of what the bug is. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 0a6e21d..6efc626 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,8 +4,4 @@ about: Suggest an idea for this project title: '' labels: enhancement assignees: ivanvorobei - --- - -**Feature Description** -Describe what functionality you want to see. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 62725cd..0000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -name: Question -about: Something is not clear with the project -title: '' -labels: question -assignees: ivanvorobei - ---- - -**Describe the Problem** -A clear and concise description of what you want to do. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8e2d91c..66bffd1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,2 @@ ## Goal - -## Checklist - -- [] Testing in compability platforms -- [] Installed correct via `Swift Package Manager` and `Cocoapods` diff --git a/.gitignore b/.gitignore index 736b1ea..4f40064 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ -# osX files +# macOS Files .DS_Store .Trashes # Swift Package Manager .swiftpm - -# User Interface -*UserInterfaceState.xcuserstate diff --git a/.mailmap b/.mailmap deleted file mode 100644 index be4d3fa..0000000 --- a/.mailmap +++ /dev/null @@ -1,3 +0,0 @@ -Ivan Vorobei -Ivan Vorobei -Ivan Vorobei \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3391492..1a8eb1b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,6 @@ # Contributing -Here provided more info about project, contribution process and recomended changes. -Please, read it before pull request or create issue. +Here provided info about contribution process and recommendations. ## Codestyle @@ -28,5 +27,3 @@ Here you find all which using in project: - // MARK: - Internal - // MARK: - Models - // MARK: - Ovveride - -If you can't find valid, add new to codestyle agreements please. Other can be use if class is large and need struct it even without adding to codestyle agreements. diff --git a/Example App/SPAlert.xcodeproj/project.pbxproj b/Example App/SPAlert.xcodeproj/project.pbxproj deleted file mode 100644 index 0cac476..0000000 --- a/Example App/SPAlert.xcodeproj/project.pbxproj +++ /dev/null @@ -1,413 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 52; - objects = { - -/* Begin PBXBuildFile section */ - F43A587426564C71009098ED /* SparrowKit in Frameworks */ = {isa = PBXBuildFile; productRef = F43A587326564C71009098ED /* SparrowKit */; }; - F43A587726564C7D009098ED /* SPDiffable in Frameworks */ = {isa = PBXBuildFile; productRef = F43A587626564C7D009098ED /* SPDiffable */; }; - F43A587A26564DF7009098ED /* SPAlert in Frameworks */ = {isa = PBXBuildFile; productRef = F43A587926564DF7009098ED /* SPAlert */; }; - F47E1E1A26564BB3008D901C /* AlertPresetModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47E1E1226564BB3008D901C /* AlertPresetModel.swift */; }; - F47E1E1B26564BB3008D901C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F47E1E1426564BB3008D901C /* Assets.xcassets */; }; - F47E1E1C26564BB3008D901C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F47E1E1526564BB3008D901C /* LaunchScreen.storyboard */; }; - F47E1E1D26564BB3008D901C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47E1E1726564BB3008D901C /* AppDelegate.swift */; }; - F47E1E1E26564BB3008D901C /* PresetsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F47E1E1926564BB3008D901C /* PresetsController.swift */; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - F47E1DFA26564B6A008D901C /* iOS Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "iOS Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; - F47E1E0B26564B6C008D901C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F47E1E1226564BB3008D901C /* AlertPresetModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertPresetModel.swift; sourceTree = ""; }; - F47E1E1426564BB3008D901C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - F47E1E1626564BB3008D901C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - F47E1E1726564BB3008D901C /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - F47E1E1926564BB3008D901C /* PresetsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresetsController.swift; sourceTree = ""; }; - F482FBF42756A77100E40FDA /* SPAlert */ = {isa = PBXFileReference; lastKnownFileType = folder; name = SPAlert; path = ..; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - F47E1DF726564B6A008D901C /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - F43A587A26564DF7009098ED /* SPAlert in Frameworks */, - F43A587726564C7D009098ED /* SPDiffable in Frameworks */, - F43A587426564C71009098ED /* SparrowKit in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - F47E1DF126564B6A008D901C = { - isa = PBXGroup; - children = ( - F482FBF42756A77100E40FDA /* SPAlert */, - F47E1DFC26564B6A008D901C /* iOS Example */, - F47E1DFB26564B6A008D901C /* Products */, - ); - sourceTree = ""; - }; - F47E1DFB26564B6A008D901C /* Products */ = { - isa = PBXGroup; - children = ( - F47E1DFA26564B6A008D901C /* iOS Example.app */, - ); - name = Products; - sourceTree = ""; - }; - F47E1DFC26564B6A008D901C /* iOS Example */ = { - isa = PBXGroup; - children = ( - F47E1E1326564BB3008D901C /* App */, - F47E1E1826564BB3008D901C /* Controllers */, - F47E1E1126564BB3008D901C /* Models */, - F47E1E0B26564B6C008D901C /* Info.plist */, - ); - path = "iOS Example"; - sourceTree = ""; - }; - F47E1E1126564BB3008D901C /* Models */ = { - isa = PBXGroup; - children = ( - F47E1E1226564BB3008D901C /* AlertPresetModel.swift */, - ); - path = Models; - sourceTree = ""; - }; - F47E1E1326564BB3008D901C /* App */ = { - isa = PBXGroup; - children = ( - F47E1E1726564BB3008D901C /* AppDelegate.swift */, - F47E1E1426564BB3008D901C /* Assets.xcassets */, - F47E1E1526564BB3008D901C /* LaunchScreen.storyboard */, - ); - path = App; - sourceTree = ""; - }; - F47E1E1826564BB3008D901C /* Controllers */ = { - isa = PBXGroup; - children = ( - F47E1E1926564BB3008D901C /* PresetsController.swift */, - ); - path = Controllers; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - F47E1DF926564B6A008D901C /* iOS Example */ = { - isa = PBXNativeTarget; - buildConfigurationList = F47E1E0E26564B6C008D901C /* Build configuration list for PBXNativeTarget "iOS Example" */; - buildPhases = ( - F47E1DF626564B6A008D901C /* Sources */, - F47E1DF726564B6A008D901C /* Frameworks */, - F47E1DF826564B6A008D901C /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = "iOS Example"; - packageProductDependencies = ( - F43A587326564C71009098ED /* SparrowKit */, - F43A587626564C7D009098ED /* SPDiffable */, - F43A587926564DF7009098ED /* SPAlert */, - ); - productName = SPAlert; - productReference = F47E1DFA26564B6A008D901C /* iOS Example.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - F47E1DF226564B6A008D901C /* Project object */ = { - isa = PBXProject; - attributes = { - LastSwiftUpdateCheck = 1250; - LastUpgradeCheck = 1250; - TargetAttributes = { - F47E1DF926564B6A008D901C = { - CreatedOnToolsVersion = 12.5; - }; - }; - }; - buildConfigurationList = F47E1DF526564B6A008D901C /* Build configuration list for PBXProject "SPAlert" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = F47E1DF126564B6A008D901C; - packageReferences = ( - F43A587226564C71009098ED /* XCRemoteSwiftPackageReference "SparrowKit" */, - F43A587526564C7D009098ED /* XCRemoteSwiftPackageReference "SPDiffable" */, - ); - productRefGroup = F47E1DFB26564B6A008D901C /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - F47E1DF926564B6A008D901C /* iOS Example */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - F47E1DF826564B6A008D901C /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F47E1E1B26564BB3008D901C /* Assets.xcassets in Resources */, - F47E1E1C26564BB3008D901C /* LaunchScreen.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - F47E1DF626564B6A008D901C /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F47E1E1E26564BB3008D901C /* PresetsController.swift in Sources */, - F47E1E1D26564BB3008D901C /* AppDelegate.swift in Sources */, - F47E1E1A26564BB3008D901C /* AlertPresetModel.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXVariantGroup section */ - F47E1E1526564BB3008D901C /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - F47E1E1626564BB3008D901C /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - F47E1E0C26564B6C008D901C /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.5; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - F47E1E0D26564B6C008D901C /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 14.5; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - F47E1E0F26564B6C008D901C /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = T9Z76HLJ3T; - INFOPLIST_FILE = "iOS Example/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = by.ivanvorobei.opensource.spalert; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - F47E1E1026564B6C008D901C /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = ""; - CODE_SIGN_STYLE = Automatic; - DEVELOPMENT_TEAM = T9Z76HLJ3T; - INFOPLIST_FILE = "iOS Example/Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 14.1; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = by.ivanvorobei.opensource.spalert; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - F47E1DF526564B6A008D901C /* Build configuration list for PBXProject "SPAlert" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F47E1E0C26564B6C008D901C /* Debug */, - F47E1E0D26564B6C008D901C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - F47E1E0E26564B6C008D901C /* Build configuration list for PBXNativeTarget "iOS Example" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F47E1E0F26564B6C008D901C /* Debug */, - F47E1E1026564B6C008D901C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - -/* Begin XCRemoteSwiftPackageReference section */ - F43A587226564C71009098ED /* XCRemoteSwiftPackageReference "SparrowKit" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/ivanvorobei/SparrowKit"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 3.5.0; - }; - }; - F43A587526564C7D009098ED /* XCRemoteSwiftPackageReference "SPDiffable" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/ivanvorobei/SPDiffable"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 4.0.0; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - -/* Begin XCSwiftPackageProductDependency section */ - F43A587326564C71009098ED /* SparrowKit */ = { - isa = XCSwiftPackageProductDependency; - package = F43A587226564C71009098ED /* XCRemoteSwiftPackageReference "SparrowKit" */; - productName = SparrowKit; - }; - F43A587626564C7D009098ED /* SPDiffable */ = { - isa = XCSwiftPackageProductDependency; - package = F43A587526564C7D009098ED /* XCRemoteSwiftPackageReference "SPDiffable" */; - productName = SPDiffable; - }; - F43A587926564DF7009098ED /* SPAlert */ = { - isa = XCSwiftPackageProductDependency; - productName = SPAlert; - }; -/* End XCSwiftPackageProductDependency section */ - }; - rootObject = F47E1DF226564B6A008D901C /* Project object */; -} diff --git a/Example App/SPAlert.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Example App/SPAlert.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a..0000000 --- a/Example App/SPAlert.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/Example App/SPAlert.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example App/SPAlert.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d9810..0000000 --- a/Example App/SPAlert.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/Example App/SPAlert.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example App/SPAlert.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved deleted file mode 100644 index afc95fd..0000000 --- a/Example App/SPAlert.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ /dev/null @@ -1,25 +0,0 @@ -{ - "object": { - "pins": [ - { - "package": "SparrowKit", - "repositoryURL": "https://github.com/ivanvorobei/SparrowKit", - "state": { - "branch": null, - "revision": "ec3da50d713713d43b26a3e191f33f9930fd0e82", - "version": "3.5.0" - } - }, - { - "package": "SPDiffable", - "repositoryURL": "https://github.com/ivanvorobei/SPDiffable", - "state": { - "branch": null, - "revision": "d09a126517371bc34080d0f2dad70ffd724f862e", - "version": "4.0.0" - } - } - ] - }, - "version": 1 -} diff --git a/Example App/SPAlert.xcodeproj/xcshareddata/xcschemes/iOS Example.xcscheme b/Example App/SPAlert.xcodeproj/xcshareddata/xcschemes/iOS Example.xcscheme deleted file mode 100644 index bef2b64..0000000 --- a/Example App/SPAlert.xcodeproj/xcshareddata/xcschemes/iOS Example.xcscheme +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example App/SPAlert.xcodeproj/xcuserdata/ivanvorobei.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/Example App/SPAlert.xcodeproj/xcuserdata/ivanvorobei.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist deleted file mode 100644 index d74bb93..0000000 --- a/Example App/SPAlert.xcodeproj/xcuserdata/ivanvorobei.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/Example App/SPAlert.xcodeproj/xcuserdata/ivanvorobei.xcuserdatad/xcschemes/xcschememanagement.plist b/Example App/SPAlert.xcodeproj/xcuserdata/ivanvorobei.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index bbab66a..0000000 --- a/Example App/SPAlert.xcodeproj/xcuserdata/ivanvorobei.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - SchemeUserState - - iOS Example.xcscheme_^#shared#^_ - - orderHint - 0 - - - SuppressBuildableAutocreation - - F47E1DF926564B6A008D901C - - primary - - - - - diff --git a/Example App/iOS Example/App/AppDelegate.swift b/Example App/iOS Example/App/AppDelegate.swift deleted file mode 100644 index e69915b..0000000 --- a/Example App/iOS Example/App/AppDelegate.swift +++ /dev/null @@ -1,39 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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 -import SparrowKit -import SPAlert - -@UIApplicationMain -class AppDelegate: SPAppWindowDelegate { - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - let rootController = PresetsController().wrapToNavigationController(prefersLargeTitles: false) - makeKeyAndVisible(viewController: rootController, tint: .systemBlue) - - // If need to change for all alerts. - // SPAlertView.appearance().duration = 2 - // SPAlertView.appearance().cornerRadius = 8 - - return true - } -} diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/Contents.json b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 4e4cb5b..0000000 --- a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "images" : [ - { - "filename" : "icon_20pt@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "icon_20pt@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "20x20" - }, - { - "filename" : "icon_29pt@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "icon_29pt@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "29x29" - }, - { - "filename" : "icon_40pt@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "icon_40pt@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "40x40" - }, - { - "filename" : "icon_60pt@2x.png", - "idiom" : "iphone", - "scale" : "2x", - "size" : "60x60" - }, - { - "filename" : "icon_60pt@3x.png", - "idiom" : "iphone", - "scale" : "3x", - "size" : "60x60" - }, - { - "filename" : "icon_20pt.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "20x20" - }, - { - "filename" : "icon_20pt@2x-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "20x20" - }, - { - "filename" : "icon_29pt.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "29x29" - }, - { - "filename" : "icon_29pt@2x-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "29x29" - }, - { - "filename" : "icon_40pt.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "40x40" - }, - { - "filename" : "icon_40pt@2x-1.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "40x40" - }, - { - "filename" : "icon_76pt.png", - "idiom" : "ipad", - "scale" : "1x", - "size" : "76x76" - }, - { - "filename" : "icon_76pt@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "76x76" - }, - { - "filename" : "icon_83.5@2x.png", - "idiom" : "ipad", - "scale" : "2x", - "size" : "83.5x83.5" - }, - { - "filename" : "Icon.png", - "idiom" : "ios-marketing", - "scale" : "1x", - "size" : "1024x1024" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/Icon.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/Icon.png deleted file mode 100644 index 85fc719f88db30c65b2b4ef3488b06a29b8adc78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36557 zcmeEu`9IX{_dnApv{|y2DEnTKwV0Arvb0EKE6Gkmc4O);$=V`iZ;?XwHOow8FC;rL zvhVvgjG6CwS$e$P`i;Wuvh zkA?;nkJ|hTnkIhBe||>(af+={n1%*JbN1AU%kH#eo&JTFo0=55>BJr#w2`=)F8@ZT z+$!j3`TmCUGN*%&E^K`?ptIx?a8Z5Fmxo821-Re7xo{!l#)&-{@$rvP7ycYX{ql8A z<&6uzGigz+Xo-JTG1#T-T-#+^Y`aogJn-Yy-8k7AlYRTq^h|shKN?!p`VW|obnOvJ z%2mw&{YUuM8WcATJ8JW(^~cX(FkAR=2Q{DkeS7T(E1IiY{&!VATH2j{=*JT38y~Xq zd_1k$&j0yv8XOueOT!|PxrgC@{_eNc@92N+5qX`3=_k#Z_{xg{|ND1tn&$uA4AZ}x z{O_CmSCX6R@vlz)$LK*O{~FGQ%lg*^{g0FX*O_ldqJJaKeXKTinhkMJ*jpajfS_}%O10MO&;C?e&(e&lYqWCvm zhe#HoZ&9dXG!4(3hL!alre~^5BRm(q)blYhF2pj{LrH}^^(}H+`=@Jpt-ae+AMujSLaUol zqb=SltW11ij!?<*Zqac0Ti~JNxSDQuBFM8Rz`gH@#*LXix4kcFNPaVA8p$o^#1*;f-0A9)vKsopJY)B zU0#z{?Q@#$uts3$nLZTzZN-cS*KEGZ7Zyf%qaN7v+JFD!LaM8q_wsOJL5+8(YfCV3 zb)s@0l)g{y433#kR}VIvbcZ|WFaEH=wTQ{j7Lwja^%RRvvOTeD73dlx%fBjcL=n|=^3FXOBwr{DAK3+=nSxxx5T0Gx;t-?Anmc6Zo!IOzk zR|PgX({%at-@=G^_nPIP^;8rDFgS9p>kuD?7V|GAKj=I|WDX&XRUFDP3jm8fboGN(SvKM44 zSoy+aKvdCerxgB*io{~&JBTIty*zZFriS{|=C>QwXPU5Ow*;v{2Zd%7iKo>iykU6t z_XsMeHf3sr(UC)4(Nnj4gnp1D@;ZI51$ns|)xO=cpBD9<i|5g4i8I*Gr{MqiwL8J zJe&}*>#=?XzWt}d<0Bq-@0VV4EX)!4^Y|GJ?y=uiKa0OQ5mrRFN4LG_*O&X^Zuxm6 z$C?|Q$30zwcvVY|CMvw3!EFU6B+Eo>{44%ojwSC|taZ@d#dE#)Ma|H8L?o7dwVgvM=u3n#ds@+rSza(CVHljZ||?#I7D zafbhA5eZMubSL#^kQkEUpZxYB7SjbAwW5E40M8_MWeRR?`vd*Wh*eXSzD&-QUz*2~ z_`*83ne~(g=y6stq3Ihh!tK2HT>jVXRK{QyYN=tb)o%RYjH24GTlQ6IBr8T|fIi|t zA9w2RJiB=_VLCYVK=v{={mXJx>5wW*Ke~_+5=X3UN#+}ZD18iO7!G;P^acaVrsKbf zigTo-J>oF&Wz}kfe*tM2jx3b zO5lgT3xfpgPy6rO%l_7$k{n;qb6}u0X74j0EyHPC2p=Z?06f3*y1eM$wLj5C5=r=x zYWKTySvj%QOQtOD<2fxQa}lNtOqI_-Cb3Be*#8=I68+66YI0hMSggmutE3>yuov5l z7RpB@Ra>8cw2_Uc{ucThr9R|fW(TN|wn%_Uoxfae`j}8SS(NqM^0>MmWN}M|U^QRy>L- z9OwRK|D{*vil~P7TE?}>CI>O`7vPY=Pvxcm9#W6);cmRt>0oZXro%g%<#6DPbH-0u zLX5ZsJ|%L7JNmDW*pF&4#`+vn8OiWGZxO;yf0K916v!Ei$T>OS(O)?m9=QE&XG?{7 z=<)W)GW!f|af;4^jh>9Xtb9MW!kTjNiVrr|k0Dp# z&+DtO`}L#iqKJW+RAFbfxr}~lpTA!id?eCJTILV#ufd;A7Vl8GW64lE6qKU26q~<{forke{ElqJ%4kkJ?9%Xq{dXQe4neXQrTK)gMB`0jZEya%VJ%i~k(P#0Fz`^Y(LAKj; zn6ul(B{z4-jGmyhbUL*dyRjX(d^i_l7RpPGr_)gU2X37q9~FOLW?<9V3P>G-8@Hoq zW^es`cbq%Qu|z%K$Wun#4Dy`!(QxLCE%Gg*+AOFL4m2iu_n3XBTWEK#d%-()28HH6 z*v;toFg@z|=AlywYP*l$oHfmyaU1%1V1Y=j5d3~l30*A=vAZO)`me07;Sg1Kpmfv} z1nZdDv$nd$`Md#KX0P040E&d5Hit%`gBwxdo#Yf)kW)jAd_u79*RhTqTMh)1GRQn* zK{mS$L$VR$-xRXa-pXSHJ-$JRNbY%>;ux>PZ5w@Q8~*STN}32pR0 z^7wfhCmhSk)?y-G^v8)lUJfwX^JbS(Xq$r|Le?wyH{;VUHusq- z;okN?a#RZwT8({9r|hz7nk}?1IH1po!lTuh=N@m|%1=&QVyvfFYQaL})gpH%bJQW* z8oJMJll%RteN&Nj<{*kB>F)hBP4Y(%ZeICW+`xB=IAS*ud1TwPth>>7W<={#jSFXl zL}gHd?4Wf4f#OFug;)_ zZRCiwOw*gsjVG|xaoPbqxx_z#!mFqKFbC*BsFj%vk(uMQzwm z2;G+qYE+6YmSobv{dub<-GSQPM?ICwK68s!JGVe~?u?+ZTlrj&Z*@ zRxlV#^!_3P2ob>`VHT{io3A*bq(eLor>2t}jc7+?XV1b#g2w5`+>+S|-V2)BKKN%U zZh9|4W=@%b1kNKj&qx^MrweeMHMd3y@5i1TD_&I5^r~`(^{*1NXto<2d%Y>F5z(n% zn&v$-2O>JiEv&_~&OBK1bVgmtik#W4ODuf4w%{ci4O^EsoGFflK8QzIPO9+t03MxS zYKseJNC05&4eAEcTTfW%NyF{#lCq7;mq}%IXIv z+Ly0raFN@sXd0ti(VNTnFc!^Ji#rWl+&BMnkFHGXWS{sRg@Kpm=G4_i(?3qT0F<gd%g*Fdh(wSs-?JX`+nZz=Q-(z;eyy%t*frM8R?Ql4*8OoIAyKEYOrit zvkHwD@Ty|Fl!oGuJroiG%Mk!`;J^B9({dh*Otp`umBnk?Qv$CRHkW$$J;8?}rn!!b zSs*w)kAHM=69-;a>zxHY^wf;)Ip>O?NS~~59j(?fLt)#0HKUkY4ZlSQI6W~GFx};G zV!KqyiA~(KkWcM_G$TI#0ju1`N0?dAbpeyWdFbZ=)`Dl&m%^;Ek6CqPxNc_u`z3IR z_aVyhglV1Fz+#nAgbv5z&LUErvYSKKNmd;;I$u(tX=xpHVOA`}N(2QBpdiw|k${*@ zQ`?FQU===1#jTpxMl0GYbcm`mSISZFOJ&l5EU5|r_x|rk1QOl_JbPnG^_8P~h@J`V zsgxqsNv$q|&LbHax_W(vdW$ct%q6w3%H zc?O>Yw}%e+x0116{>Y=yFAe|(+rg=`X}gc54v~th>eEc|OMTCD9>+djir~LJ^<`Vd z6Ag;5qK10_$S)2Kah2h}jv(J(Jk*ls_#=}feccs*W;p)-uE%eEv5g!$OTqB`cV@_H zcZEc3x)k+2FOOK9n;SQuH7#w=5gORCTY-;qyOGz>`LQ_z(-|X3BB$y2eAdq#Z-kn} zPWjH+L!y19faO_@K6=;EbZGC;+fY)qGX)XA0^q6$S(?_72nHXvK!dCPc1 zS9^b%&PjkmUV)N9I*LzvQ${mVhg?S*hb|S2;Xhj6>QZAPo})+nmD6vhich8V+JYE9 zSDfCZZDFdL22+XT27!SqCi`S8+sPaSA>xmH;(Qi^dqY)y)%`sXrTh-Ch;YZF=**@{ zrxE6c8+vZ4|HX((4-RpQPqxKWu)*?%uv<+PNgh z<1sSyOjkpIHLV%j zrblFitZ{i|Zk*MCmGHI*RXADV6@91rBWSo}#t(W~#67$w3kh$)tCXvJD=FBxYW-D< zxYb3GG{QvTID2A@XJif5LsLQqwUgr~jg>nCdNW7Anv1y`K^;q`yw}MindV-cHfa~z+)3?rfn)0$#Al3qC3A~1Ie_*2OwVw$WHhy~+&VF4|9HoW&MdlG zQXRFE?P|{Jtf;?9o7qQ{jOCRsQkv0%s(pKQLditc9eQuah{>{HIDyD}3Hm+IuO3gM z&awel!d_r5HTOGxA2q2{b%?> zLE$`79=}|-dfZL)EuWdQkc|WJDfrC!aRB65Ax^n9*vAPGGB_g!;Fpv3-8-;;V;5PL z1UkrzqkEqbqoOaiSM`7H&BOByM*C=|=o@?TL99P%KZI*c{1HyYiGvd-4{FEs-J{Oybw$OwxC=Jk89>k z3(%s1!D(3q6&kEV0^BJ&gijf8qY!;6V0BG1|M9%g*;-|3C6^)H0oi^)b6>y>F70aX))1TbKm$`}maePVCD z6=e@xVXPeW(+g)9n3xBy>O(}f$Ho;1&>>XJmaV%UG z_>8VuSCPlz+uekhhpwNz*OvHf=E^mDu42!*$J#MU3D2_6Gcbvyz!|P<3g6y1bs9=j zj*6g(v;&Wm8By2l3e@LDJW2o-N$iA_*9T-e(rdTD|L`%xrD3ZCBTDx=mOMFAZvDQX zjQv_ENsemGZrY4yL}Gk*55q=`uZ$o7KxefhSxT=;8qfP20%Cg z-k_6;{kfqW-t-b}{h$tY|f zjt0Q?|A562@$}n&zUHT7lCb^#5u?f(Z6jjoRpsF|SddbVQkq_iht_ETE{0&Bjpv&4 zH)4$_@VZzaGFvmuHDATOQ{iZJe;XVrPc?2HxrZDy0jx(vOH^=!ENe96>mZw|yn{~} zPB7H5Aa7-~yWB=Lz5Ru*J|VCMyXCu?4ZvQAv$|U@t8ExiKWn8NRqf%9fWzj%An12W z9~o)Su{F|#u&Wu7gLnVs2%8ZJAS4M^sc~HVR zpQ{}u;%l!c3VjYqXrG=uw*hKDlmNMiT_;eJRr8sh`Qeq(iU%AT6I}K5uWb3 zxN$7ZJQ`^9a6ke>^>35qKRmf3oDT5#D1O#)Hm>j&{Ub<9ko%!;PT#nn!wB!Lr*F(w zcWlM(Fe{U5AR52{e*TkV&XD^yMCo^c-dKEivv0#U{D)Wd9T-MT|{r!`x%Q3iOM0;9x;N@aHN2=PjZuC9e4w^9*Lmd;RLJ5m6>o3c&wkaQF&kP$GL4tamZnB~rZ0pxIiGWGGB6vjuJ|Q2wmLY} z-ZX%(oqNxZ;j@HA6~!OjhKIXr7#zzx-8$KNO?+}<8>F05!ONCG<&sBLaZI>oare~$ zoCyL9!_f!@bZ*OBy;$(Am&X`y@+uZi-P;b4BGqGu&UsLo56R$_jSI9+15?Z@tQ~|U z&+<-Ji>LMR$ET@hwA&+<;`jAjs_O|;+chnT z3N}(05Yjz~+v`|#U_X~)=0C5_S_fbvq>Ov+RS#f$gXK)ts$EQHOaSGO1Znu#aKdrk zM;r>~U%Wi-b@$K3E}LY@-gnQm7kHf>BuD!(^j+$HALaNMA?@P61SE-}z%{WHOAqlx z*%h7DE0;lRvl=nz7w4+)6ONz(T=k6Drq+)4Q@+!$?A5X4-M-YstZ_MYt3qQF4e{qR znieI=mQQk&uK>cZ@z*yVeGCp9WD~g-5{FhYN4ob(hz#X$KS#I99RtF;Xmz19e{AN< zVe|5!(+@8+6}YuqCn~IFH}Dr;k4H*mhWrRE9*EH0&Cxxr_ZU)|3Y?>fg>Eh#jV<5V zQ(V3ZV&b)7LDze7J8_=dZxSti)cR#ox3f!pSZq8R=h$0a6J4t~XhiHca<*n=5(y=I z%IoCbeQXNj!Sb|99<8J=M=h>{#ILZ8jNMX!u-6-R-Vspb!{;oRY-=Sn#fZOfyVZV@>f3iT&j?PhCi z4;LLqK!h0hx{KkLj(pty&fRkK?s$6{Lj@1-%2=V}NPh&RP^PT;0JM6$Rwh>4vnUeJ zAbzUiTSytCfoso2B@2)scndXdE0>9btUVdZYuLx8wmI(8J^2ft|0GZ=)r>iwRnb4k zbWdWbg*8})k^G`P%~#EXhngA&upXyPpjt#|K{9x$9!C`29!}EG+bW|4e9Fwt2vV1C z#rS$5R6t;j#HaX7;#sQ|iZ5q5-WzVp(znZa;6`akE(wAfHy0cvyxv&~w~syuz`$>p zhG4}<@1_s@#!^>n^tas9r*j9t^PXS~HoN$HHcj@Kj=(5D^uY@7F|W>)|AK*1aP_h@ zNtV_EC|8b`Dn6b&xQ+<0OB!BYESOdL^Pz$_RCC*Iss0w;fhHp(^vuEAfL_Zwsh^dR zBE(h{6vjN~#_DANB!A})asa-Y{<$e69^|7huAimLbSp^vyqtvC%UKyaFGJ~KSs{eP zL0Ie+zFGlHye_J(kmA;9KWqBoP0o>4*jw?9(NB2}{jYW*sTWQVlu=WFETDS``~fF3 ztI&CotBv#9Ik^lILp2_MFCU?3{WpVO?2`+?3`E=<1<2R!0A&OtK zc=cc85GI@sGIbwnMhB|l>@%SCt2_&*(*F7WfUXwq&o}=HKy1m1H?1oom0WGecsveV^QIvjQE z*3QnChT=c{+mtj?r#!S#U8|3TY;(L#Uffn_N#eGVZ z8i174^#&F4uOe`|({0Ekg`f^4ejEzW-k$g~Ck4V7Zcb8&P`y2L<)Yc2uMgJ1&Rm9u z{vCltRz6)vod14L5r=|)$NAW1Z`VB#I|rkkwY>{h^43k5FATDKj8e`E>o0WWAwHr= zr_MdgC!teWx2oq`OnDcHSS+AlI$D(Y9`_|^9~r=R7JcpAr)z{8sG+u5R)i3{G9V2? zk_$!1&MQ4||7e&65Wa;58O9ORmhIAO8GZNJN2AvrMetwN|EqUVQ@1jASW;n#7 z)T+#;v^rJ9aPJEkM!bdtT-%)%{kww9h?x|TXIh`$e3mAg85LmcQ`3}TRsY7DPKX`dbG*R1yJ(O zi#UKEWmA7za^`MVu<0VWYWY>hcPXH8CE~&$*-D4CaDl>evS<@;>+{?J0|MXOh(5v; z&w5jz-jmuR2ZH9%vEDraoW7k?b1XU{y^vlli|HRqq1pH@fMLs0gqV1pl5h^R$s4q( z@9nNPeCg+LE#;x61GXj~f(hBy4Lw-Ovg&l#^FX9shWRv{U2u)=B1t=bLF#Z&Sh5*|=B5pKi$H%ohW4>QsSBM@aqj>&IF+)V zHJWlG*!iv6VO88k#+y_JW7j6I(u9a$>zGR|e;%St8r;GV-uSvYE?C0wY?*ylQJJ*#RilScRINm{y z^PY*j0mVQO*Vv~J#xPGHcs#x=nIR;6$b2BE?@q1PS2xP)q|+?Xq1NkV4}X{~ZqU?S z<>8goB!g_L3#q+(j5_EbZ4-{!<3*YBx92T=1qb5Obp@+Pdd3|F(dbq!a0s!t=1+SL zz4jN2kTm6L)7Sz4+A4;yUAl-#&A*+Tr~T=whGi7>nYO~=h2PF;H|V4cA@T@7Iv0j{ zl5o`sXOLydA;0ugBepZ=SfQfX-DeoUyM{uDmGnM-t#1{Z6O^2;5p$c&kkQiR#Uba5 z1Fkjx2bhyZYU*!B>GanAjDpnb3-f-fD~q9Hquj@VZY&|ZJS<%nc0djCv%1Sm`Z@}K zmRjNN7~}lc4sitzk$L5cNdnW=A}c4+84+g|glLc|S?YN??7P!KD(g4+!ZA(hvVJwG zERyNjilR7QvQtG_$;w#7;-NAPP_lP`3!X~h`(~xm6`)(h~sO6@VnTVIiNS$(_e@>w+SSwY8oL|*o+p3p?y+e*Kgl14fA!noQWq?)sbO{p& z{neRSAO_pIn%GvQ`Lbi;t*=}%x=d0a03BlBXZ7Grd@icDy!aPDQfQ>IQGQatSM2tW z`ij!fMI!$7;g@U7jv*M{&pYB8EFTI9hQ>gzgp@pGt$ihvc*mUJ`^0CU?LH<6RtXLS zq!9wGVTtF$PIYNe{|qT~`)zqzsrM97d4v!&5EBi($mlZsE8)3E`0;0G* zE9R9n!#}kX2R&bgkmTFUQg^<(bKI%%0`}3C-N(`)5+j@)12_`_-J7E9`erdXEus!n z0U9MAX4pdXjeYcvO%b*PXr56O#7*~BR6w&Cl>ANRh`gi~g{AoW=VBA)5G~b4LO`?L z6+z~q+~N0vRRW!|4#nfm`75SwiQD;RZ=y}SR&zLAK7vTM`}R$S1gY&(d^cuY9f}YX zY(P-hTMJ&P(2FkV@mTIygEm1(H zVfH2@jJRh$LaJ^Jky1DQ7;?>SmfP}XB>a)O1ZF>I>daNP!^}%h2@q?iHL;|eAygaB zuNA!#q1!->s_Uqe1Gb-kdFac4D(>i1GJnXUZ@a11s8{EZ?@+OZ+jw`u(t!PHw=%J< zfKb2P#~S5WJb%T=Wm+A{U=oVQ6j22nmCaLrxGgs(eisyk>iOAoEcHucxl~u$>N-^X zWDq)x&kd}$Ks>;Y-doQM^0p9YH=Rt@Sx%lpU3z~$08gHmKI}S@{dMplE*stDJ8B?891D`77{zYOPJ*q@-gOPX-o&RvimAw2cRpSN6{WA&(zR%CY3 zx1!U0@@vT|Wy?whwDR6zeTGSro|>&Lq~OU9YOwPb!B9)Vl|z`2E!ldIFa0FWXQ9pV zL4`v6OO#et)r2=gDZ?s+$)N*KV8(NWuUuAHLdV<3l?qmn-9 zx*miAY+U{rUg`O1pv5t%aKU$s_kUqtI%x(j{}CknKGXjY&k3g^sm?&VEz-1^@LlU< z*&c+`e&aaXa>a8H|B}tlpb@~`RiprH-}WWsAc9m+f>ic=_P(cc7Kl0CJCz4S6Qb=> zk!IB29)mGe1Jcvp^yg!bDK5`X@@84q)oNhgdrk1>IEBE=s!0f*;Zc`-p#@|g2Ue-$+OqNZ}2 zcbpf6#OYo|cq#8_lDU;7Ee>fvr?}0kCi^~%{5XNyw*NK;sogjFn6M&@9i|tg&TYDd znaL^~_DMn66%uGw zDNg%*<__EQ64s-t6`1(R2%V{qjAKyy$XM)Jc^B#X)I%p);jmQ6P}=l#1+VZc{5xCm zDatn9Q*kOjZ$4HH;B$Wk^Y}okLa2F&Vs6HXmuu`cw-`8aUEgP{R)vJg&|^nFyBHLX zZ&Ap)jcOAb$5LigpRCUD=}O{u+fkC$UyVBXW)nkuLMteTTMjdZfUMCr(m;K9naeQ2 zwMN>eav_SF?Ko-L8%Zy{!El zZgrtkIz)LQ|4MBC8kH~d)-OhlD_Jb@COVhs;N|9|{l48)AhI)NwWowbDM)-C&ZdUWOmEOgV6A^nqO0s{j`PK_i5Ul}htenN^IL0r&o^U3&02+=mJ zxTlm=`w_4ZY@&77Dqyq2&K+7~kePs<-_EtcWBj!Htr;XtiWD63|15JJ?xAF} zMA%26DJw*(95&&d<1;H()9c#Mc6Pk?9jgwg>G?7=0-=`?p0=+2JlaNVErpycn!V9s zpZjU}*w(s4f_)xwf1B!l^b0}6*~vLlRF{X|TJZFcSm?>OLwU=qgk$VxP45|=Lnmf@w}`n^HP@- zF|a{fSu!*9p*vG$*L0m6wXGk?%v*gHJA1P)>0|-E4zNIgc@kXW<3K7uMrZo z*YR`x&Yl;30&~tWuFU}^#hztCdmW$ZHV?m7avlh(wxIglfKv}26bSRUn-(fltH6(R z%b_#F*Q&yoHDNi!r{xE9X>f3v72*2ET#!ddV6so9yk9fYy}J8H&W_ zzVa#0ygMFu*lS^oyj3(a5*mRYUZS|1yJ^ZbaA800y#bKGWj??!FMfaa{CvrQshCM` z;aZhZyGb%ut$eMC_UA>oix=kkq7pV#^FHZ8d#e6;fi^ZxX)p(!RkBki9CKJQm z&t}!gQ6YZ}7QOWTxvZMC1xk6m$T|8_8yOjcfax5>OGkl;wx1t7g(~A%n)SzM(+7+Mkf3b4#75;?oON=ze~_3yr7D5@YT@cCJ-{s;7L{ZMf`SJshsmFE+n*+qfzAUnwMW95Objc-a}6_SH9G7*)$Kk$-%<^dF_b`B zF3OyEJ9lSmp{_JhIJt+<@IbC!yw~D{ zQ&+(t$u0AfUoFxjk4u0qFp2Q8XXsyh*2+?N2lpWEqheZqrCD7L9rograDEf3^J84bpIy!#TJcUK4=2W*#sjneOJH%d(kPJF>s{6*5>u_cp=Ahdh2*loSgv}FyS}lk+h1?r;9xBn z`^_#Z%4Gp)bE2=;ja_e9FXo^#L%ZjO>Kg{|hVkV{kHUW>M=!yQM5Q&r5BNk-hxFEvOTkQ4zD5<%oWiBowx)+?;GF9YswF;9lz%eF41;=@MLZ zP>8~!(n7E~BwG}4eT&#SlHg9skR0AcX(k(hq|w(N;3Osrj0ETJ-=?-m%fJeF?#^>8CJT%mV&_ zp`#St?$zBYk0{}VVbI!HA%3ixwx3l2X_F*u*(G9nL-D&v;jdc+KicVmf8?FnU)@d; zO>eq#?TXZjDsccG`JsFbgtCG5rG!<|LJPbBz)(vqzZ9n_Et$fq_v_g|y1=J_yL4w#r_)qwY$wWm0niqOItz~ z`_3M67t%^Vz0#q$*7cEu;7FfK{48g#m_h#G)G?kX7=^74NQ3tE@HE*`He@cMdWU2! zuvNQD>~15ixRNRrxH8ttNb_Rpe8{-m6Ic&}^EijJpS1rv7lbiK$GYFRY^3b%ev^0S z)EUiq%?PRbERE1gmC>;b$gH}!B0yUsOidO}n1~h;gre+NIAk3uEn?nM2w+2+B%#M4 z@x_q?VS90qqH(JE?+P9>)(*`^BVFF$avkjQ#_w7Sc73(ukTPlox&-LIYM!NIJWeOsf>Ex7fI{pc&t>u-4DXW5IZS?7f*C)2pBMnJT%hte? z7JBE=W(~Sk1GCrGA5ozx9bDxYcJP`tE?B<(14M}jPmGy+*E?2cgn$~$d;G{B6M*jW zu)MfqpZi&)SMkD?i%|7kSs3pH5E=v>e940T5tNs(A}Q?qjUO)~&c)eQGdLiv6Ia*U zATHesQ${>*AN1(*Rg^Jvxr5;UC8q41t;j@%$rHM!xL*y z@!!G4(xzd4tXlkMj%~|84c_sah-2^?dOL#1?)Bq4d|}SW{L?8aW!1cT<Jk z(oc{{UsWrVMpK^MSiZ9s6z&0l-jibXRZ#aPGOl@)h_?T&K=d)`Q_4NM(COA8sjb-G zh&0i)E1kMtYYUNKFL&MXMWgFC*sVtC$za&x$XFKjW_ee6g^-X#Jcx~I%Vj*N+`Rf4R6x;Tjaq~#Z zU`z$(7C~$m0I@wbRCHn$c|`zA2{@o{#5_A#Cv9-+m%)>@v6i(Yf%tmm@vfTAQ8-Tf zpKrYZ!l(PCrmMi( z_hvU}hZ^G-i%%v%p#Nsx*c-|rcI!*7wi&>5Yv?Qfwo~4&GazJL;)-y_%Ig>YImKsz zt9nKZrfk`I3b1kYjV`e}fK%rJQfPDOgBj-lyGp`G_Fw2w~{sg#J*_fQ&?iAn_+kamtCxdIXaO2;bI ze9!M{hLqw3C8raUT^aSK_kQ|~S;In5G=pBg$_t18Iq65>fM(O$N3>hEmILra%Jol~ z6@V%#o%CJlFdFberUEN9Aa)o;i_tYDd~bL9LdQ{BE=Sd4ISL)(|1+jbWJ0RJJ3hPU z0x|e!9pkP{WSQ>6lAWIT$nr^tZZu>ZSuZdHej^cUsO3jFJHwFkVd66&G6m1egA)>G zLS~g=>_e>w++S+<8R>`%X27WiH)tQ1%#1?IEhF<#W#4wI{vJB?!&D6?W$PNy#W5G6 zCD--tVHW556wH4H_2VC`4echU6(}LYp6s!I(vL8>Om-cIOk%9EL@ND4D86$hZ>bJT zY4|W)b8XvNJ4O+S=<-Z!^iY)Ox}uo_?BnirxPn5r^WApt=V|pX+S0MLpeHU9S_e-- zVcPstZOmxw56QePmfb2#W}SmaudkvrU0r?qA7Y_1?+X5G&3Z>OH`FtJ(zw6`%g?*G z!if^)iISDn+}+(jzB1l)GoOTJ0B^)}ukC_i9orx%@qVtce6zgWCDo4N6=&K;cFQ8A z;Y+yIqHq^TZGG-gToO|CFyoyF@cBHNy0_SKKIwN%@q)ZnAykNg(_3j!(-!H4cA2b9 zlBz0vyTR-~nJ?C+z28B$SMw&eAw; zJGn56p-TJwnXiI(s$w+hPoDg0%%{%xPcGlHzz>+Ol18YT!fgi_c#;znSL?WF>-ldS zFT0-o_EGz*Ub@ljZ>@&4LC5*`r(2$8R`xgCCoSSXgp=(X3%?{iJQik`DQ#O^XSu4D zxnMZC;-x~2>n*DD&AL(pj;Zw?b|Ir;WV!->#IC}vyPo5vg44-)`;lZW8DnxNMK%R- zDd^MZFtmp*{;E~;BZO_wF5!xw-%0z)k#VY$1)0J5WQs3gk1jH+ypNr5j!Txt_J3FN zHQHv!i)${U4roQ$4*W0}&k_+|Zye_5i>KkFy_v{|FR$Pllr||!hojP!wQK(- z2}?R$uMozVwbI2>BL+uHRwmL+$vuPLe5Hh~q;aXKsh=0xZAZb~9vgb#^CyjxRI*e? zz5BDm$!~#eXn447Hj4U(Jjg#D)m6Q&li?_W6qpV(N|jWlGqPpTH{O@r%PW5{l<7X} z)r=eJ#wFyLFU7-dCgUA~zT4iu#@ZQ^9Y;DZI}g-aTb)EwduUmq(CwjFk)L5ip_NW` zCq$Se0p*$&i=|-QDj!Qai_pC=_O$+FvKn%N_g5J;zDj9gXKzI)ahB~Vw0n%_=Uh94 zxB@25bF_2>T^l8s?|&;b1w%PWNOQvS#A1o^h;4@76Ap#NqS=w0Lqyz{_31PEUQC?|Vl5sTi31>j105^gpblkP@ae<5%_%)h|cln_E_q#ayR}9Ua$% z_hC;utDRc|2;1%B=jytJOT;#pb@V&lZ)s zU?jsN&Sxyk>-eB_4T}{BzBT=|p?cVQ9Oaw32J&*0BnQyo9x^Vub?xLCqb*e$`S5nZ zd%OcUyOO~sg!Uh;uT$})nVfs7&4&2+MI&;YOV8OD?k}Ga^Yzt@mHccV zw3#J30x=)@>>NK-{HF?YCp7(z1cE%CRI@xAO7#FtWh=6K^ePYE&*e{3&=}NCE1pv- zF9!F9hk=HXK9VrLUd?@d^4Y2g)|lpJ52L{m*_LYEVn0%6o`g1vrxnc<>a57nqIe{b z_hv(sZDE9tC;IEJ^PH{ zRMGoB$t`OGG&lv^VJDc7!tj}x)&5q@aUE@rgmIuEIvP}r#O=xh{*63&X>QZRDz)sp zYZ`h1B>w1*G8Zjs{2)@zY+SJKuw9-T9?ujQ1w&^-eF~(`gXJa`^-JD&kBC^$0qkh$ zFbm`aVJ69+%QpGVF*Fb}AKzt)#UwJgxVg0~cQ~ZMpnp!o3y0#hKxTFs?CEXwWFMM7 zD3<`bV5@0rN~)czkNszsRpAigZpQOI!8^+1gsyJAwRijoj_5r!_}5e0Q0R+vo}Qjb zd3ky4l19(Z@yX*tm_T`mcRTEJZodwshH$=AM>dA(U|3@J%iSA5$IN%g5LM_lnd+}P z9k5(3Pw~xu(Orb5A5#Mj#zJb}G#t3cb%k$ceQvWg%}<$TL9>qzx`XIlTwR-XwwRiJ ze<7(EAR!E&#VK>^eCM6-l2R1q8;0y_ea+(%@ibP0!&hN$`|ZJW&!!Q>j2mSiqC3tq zFtuJYt1F{^zGgE$9JolqZBC2bXV<0K5&2^;J<}Pcd~Z^J&aIkAn@+aw6Mlpt`fSQ# z-o$`w&dsW)^#*=f>r#FQddw}S3ls~d5^j4f-mDI<=-@-l2W#O+Tm-&=+CvSJspV{A zRbHPTw@L?9`|^z~mIanCRK8e8e%M9klFmB?^}@ zb|^Le^nuT#M&#W)6W${3g$B3En7_pge+qdS=d*pRtGIB#N8gj>WulJ{QF?|Ih2FX+ zf1!QUX7~Zet|b|rwacVMsY!S5@_Q}s3WvIfHsm$Rh5I-3ZJUX2e8ZyPj)VCJu!1i# zO6y!7=W{=lnXZuuq{uiN-IY`$gJk_r+)Mtkf*3PtoEv_$bKXlcVq!iTzqTh@)HiAH z!oKE~z}cKKYs>sTvP?-kY`d;s1k|MGVByG{K&kZ=)^6{i9>J1khj`k3`0gE%sZWU| zdb^L$>5l&{kU3oP1HB-6d%kHhMN_K8Jx+CT%f>~xBjPP8f%}L0{F?`Xh)IHFq8b9v zo0;L)(HK`aEEd8;#nh{4h@mK*L_Xw!CcfD@sVP>_s@i4+ZB*)s!xF zA;ft+UGud}oQkwmv{_$D4ETQxj(2rP!*$YUu~`SZ+(4K+$oj!B=Z>E7-8Ku6wHQr< zlViHQ4YPO07Z&i4smMT|?Mm*rD>yM(`FA(b3kT9~lreHpSJJ1~jhu5Qts70y!_UBL zF$t%GAN@?lt&A2-s&Ygacb`XmQ2%%7x*gI-*J8KTC3re}*h~WEd2cbCk56-3$84F3ph8PLjs!67^;Zs?Cf;?4ju#AJ!%*P8E{w*D9MVCK2|Nd zw-FkDzDKESiPzd@4w;XvY4>*1rIS9>nx=NXx(xhiLk_CG$)6t88?7hT++YPLXuP?G zBqa9nlU2>^-t5P4(fRF2I}y>|=-q4(UP&lXwD$+oo z5!;$wV#fS*hH-PmaymidHCi-@M`aPeVaSB%T>0i%Z}z8R}8B zANqgoUHd=O`5K?TQB=pJ)@5C4T3M--PKgvowaK*Y;9XjMPp?|TBJ3*bQwW1qP5y)BhnE^Cfj_Uhc@AXJ7<2Us{jT&9Tq!>>9&R0L zC;$v=QNOWX>XeQuLcp*xbBiG?qO!9qNSsjWoN4r>+mh?9Q?7II5~LpJ=)tLx70Cn~-^Sm5K5`dxu(ahM*_SuFA)bw|hL-WSZKo$X!XA$peV8cqBKbVK zYFNsyX2ws;b+nRC^MiBH=xpl);Ig30&c+=Inw*qF{i5e$yEWI{^9ZJw)?6NmiZf^R zo5)z~t0=M|*CwzZqth;AflBfc1Uw$_NMER!9v?>nI-YI(rc1%WyCgSwP+CuhgYD{k*MhGSM=}7Vi`UMA;ah6j z2f7CGxo)lu@!(u(WAR?V6VhE^?oR5j(%ig-G8ZteIs7#tDoTi=6CLB>swIxiMmB{VE zOOL~dA$^ON5Y^Tc`aCjjDa$MP*5Q`+-7D(iYfcU*m4qU;6$XXZWYn@^Q5I-8iot3d zch8Kci_h4P>7r&pK3U_fe#_+dyQVB;9IF7HVO}5hhG!I%^-l2DBfFGhEzZqs{(7%t zI{A16ZFtA~9!Nkm@oE$$z2D=oF5(;p zDsD+z+xYUO6N8}}*!1|jw55ip#8fOt0EDYrZEPh?HIDj7G0%0(65k1lcb^7+CMPLS z0wut4rqcbK{?fgZ%QfEEtuYMSGnzWR1d$P^5Ogwgt+RiH)I{r>AGw69I>F z->=Rxcrb6Fb+@qBQH)_ z)XC2-d^@NS-ul5!%=`q|mX}izSR_QH8$7^HxzPe5>$6|sa6`Ihcu0qfQ;viFzs7Ph zPPX24D~;^!OMwnw%X6ZsJ)vA(1#9pCo?cmTjGxLqIB}Gj<1Q6wZ-z^RBdyu-sRu=g zdh`vl5V!_N(Anbt2$nPDW7I7K_i(Hv*lt2Q7F{{GaI@>pLVK#ZZIU-2ia{jewf0Qw zn~y(ue8pqo+zvCcM0DG#Fl*58*qhJ|0b|)eu9Zye8_~kaAa(h`$<5E|N7`cF^?hLlwwrD0&pXy0!T{j+ZyJ0^2&z2(sI16A#)0;WE8cS3JRgG6@j|0iD~?Ar-7X_QyYs2I++Y`|;0* zp%ceFsN;UA%9f+gE4|+u$s`5n1&nCX^6c$M5?CRMhuOEB+i5}hFc42NZclDAFb(ZV z9Z390&rwP3p^-h#z!$@#5O*uGp1N&8!wq^AvK=!z$+th-nFY0VhrXWBh!6=w1(g$( zW!XQBq)KDHj!d6=YAHN5{J;`NOGOnV-nIz_SyUS)RJW^2g`#a5w!4YagODE>Gq+0%Q}3^1KY=g!eG*D_Tr~t*wDr<-v|4CZGIOY z_P9$ONtyV4hNwRvW;CWPtD>UfBmGEJeaiaPCgqUX;|5+AGUhga?d!mGO7_B%wZBL| zI7;sxesQyjpUZs4DIb(t4MjTErEKTEaFelzYYgqxNHyMG?C5jw8`n7pz6M zPY>kf=)^ac^ST*BG2Z%r(xf{Z+$<2oh>gae#}3KY!jS8sV2Lr~MU&56nSZc;cD~@4 zO6%_-KmJ8I{)N$3vLzZLk=mywYk&wroG2CANQ$h-ixOMl%0nk95O4zCcza054@+%v zG9VTQUZt$JLnwP=6Sj`Q-^YM~A_ZEp{B3sq{xLO=0I~Maxo1JJcSOoehBXee zomauXCx2&Y0scBU+UuRi@rt7Mw%R}s@jyqT2aJjw_ySK7uRvh(^ky+~cki?*THet` zOQVMk1mga5-bJ%S7!BHbuq>Osb?imLfdU+0fn%Y=GH+h*M%qHt6atj#u}mr?Q&G)Z z5S`u1`OFynt*qsabAH?L7)`wN6uw=U2v12@_6*_7bHN_8S=&=3w(zWfCm*7#HB>G^W;VHf^KXIg z2L|rIlt$WhZ`wmP3}jNbDoeucAaMRX0>AfPO~4h%;9F=xYH0Q*59SL#Or%p?y(0Y% zu-hkhNV5{Q-j{O6Gn@hN$!`sj$S%=<)HJ|&-o1VA2G=jp6xR?tUOYMEXzkVz-Jd+k z(eldG-{J)aIB&uPqjHBQE)V79z07WYv-t!xsI%{Z<9<06<|}Q4jWp(lF2FZpLV?+D zEvhiD<+S8eKd)Mg?8Tg9GVhYx>BXlQwEXf4)jVaZB>~Tyjm?4vN&)~>fF>Z;G*LQ` z=lhE;D$44g5I=Dv?(9pPdiCY4dSuthHH(o*%TyMB0U6DI|B9zAOP`?Zbba>EefB^g zGvYy*@g#6z1qkNi>@@~csw4Z}DoA~M%HT&(zbp+ecB$ML-suQ diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt.png deleted file mode 100644 index dcdc09886be31a0f0bc6dae68bbed4b700f68309..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 410 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 diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt.png deleted file mode 100644 index 6eeab635b7edaf035e3da033d81dba4c2b7acbe7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 570 zcmeAS@N?(olHy`uVBq!ia0vp^vLMXC1|-8Kr}G0TmUKs7M+SzC{oH>NS%G|u)CkWs zUtb0-Ae)1Mu|1Q41;}CqVksbIU|?Fn1Q(HAz>HvnB>!$U$p$K3>*?Yc;=%iNsehE9j<^FqR zxqjs`QRm%Pr?KYqdL3w~==Iv#bTFaeU3U5Q0KODXkD%`6J#zYIjz7EWxKewXh@z<~ z-^U%cAAbMcb9y$BLfWB~nj68J}Nl{-Ut3uJg>@w}154e#$S~ zExT~@gd_#lwJnnlmsq)~&34`R!$YmaO4e<$VEpYhW-W&w_BM#@5IeQ`{PH`se63E2 ziHEW_DhPGv@hoB4l>Te(-|UCh9}m7PD(HyI58z!=(q$bh;#ibz(v=!lvI6-QsS%!O zzP=1vKsE;hV|yk83y{SK#8N=az`(SC2`*x_fEmFCNj4;Ro?~EOUf}8C7?Q#IcBZ$# zaG=QX|4+{{b!?izp*HPP-BeNS$PJU0E|9*-_3)z8Z5H!Si~(igeSG`MR&+gEQP7mQx{GU>n!9y~YjKFh!41Vu z7f+w|{&VjxYwXc!OdI*rUhOnpFTQ#6W(%911$*`|xit$$y*(_w`+Sz`^*z}IyJHPK zl$3Ai$J^hRm?U>1D&YMtak+kWadC0Z1onr`EmfY5J@fwi_QRRaTXV9NPi)*Wr6=7_+lT;E-shW6FbCJ4}X`xFT}IHHqHG z6Qm}|bTX~o7vDR5^5i>;yXSCD6Pf#P3hxhQpLh+CrnBexZ{E1jQ8-gUQ^42ifB(C; zZ;zTy{`tVs5}D|JP?fpX0SUq$=L) zCfxkM`s0=BR|_Yk-Bi?6^K>{VQ0uq%{}0B~-rga5x4kH_ia0Z^$05jIk(sg`S5*At zJJ;*A<_B8E>YkAdGLl=JvLI!`J{`7ro<+--tLM$%nU%fOz;}k@>`=AtB@09>)}Cy9 z&RlWl_UVENW$vt@YVSW)hKf$r-+n40Oq3vRuW8pm YR$U&sj78g~7=rSwr>mdKI;Vst05`?q3;+NC diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png deleted file mode 100644 index 5b5611a737f0689f38acf06bfebbb19e7a30c08d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1069 zcmeAS@N?(olHy`uVBq!ia0vp^Rv^s51|%Qu_R;`SEa{HEjtmSN`?>!lvI6-QsS%!O zzP=1vKsE;hV|yk83y{SK#8N=az`(SC2`*x_fEmFCNj4;Ro?~EOUf}8C7?Q#IcBZ$# zaG=QX|4+{{b!?izp*HPP-BeNS$PJU0E|9*-_3)z8Z5H!Si~(igeSG`MR&+gEQP7mQx{GU>n!9y~YjKFh!41Vu z7f+w|{&VjxYwXc!OdI*rUhOnpFTQ#6W(%911$*`|xit$$y*(_w`+Sz`^*z}IyJHPK zl$3Ai$J^hRm?U>1D&YMtak+kWadC0Z1onr`EmfY5J@fwi_QRRaTXV9NPi)*Wr6=7_+lT;E-shW6FbCJ4}X`xFT}IHHqHG z6Qm}|bTX~o7vDR5^5i>;yXSCD6Pf#P3hxhQpLh+CrnBexZ{E1jQ8-gUQ^42ifB(C; zZ;zTy{`tVs5}D|JP?fpX0SUq$=L) zCfxkM`s0=BR|_Yk-Bi?6^K>{VQ0uq%{}0B~-rga5x4kH_ia0Z^$05jIk(sg`S5*At zJJ;*A<_B8E>YkAdGLl=JvLI!`J{`7ro<+--tLM$%nU%fOz;}k@>`=AtB@09>)}Cy9 z&RlWl_UVENW$vt@YVSW)hKf$r-+n40Oq3vRuW8pm YR$U&sj78g~7=rSwr>mdKI;Vst05`?q3;+NC diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png deleted file mode 100644 index f3cf00c94c49a5bd0c16dfbb4bfd512e9f431965..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1612 zcmb7^eLT|%9LIkf!<-?PVnm+WX&Jg6JgkN|+a%0&o7>M?rn*cT>!h?ZTb_=}!{CYGf-jjFgkK^W_!HM>2GnI{n+Di(Lv=_x z23Zxe^3!WZ#y@92S!nmX@;teZ)wk-Qm{t6V;(hZmatyZ4S$B87nUFsUcLTH1lq}0? zf2i3W_T*MDOV=@ds_T&4430Na3%glPX1NxZ(S;hK<3{&wWK5e7?-TW1HJFahI|FBcH6%F z`;r;SwY%37sOB|%J|E+GbBk;9Kywv#9V_0_Vx!XDgOmO^@(0W-9h*36viCS5C%><^ zI4vh zZDN|{vb=@6VndFb+*5srg-zxwC&nTAO9b-z6H6xw=!kM7`nx7;l}d#lD?{q81<#HS zE}V@%=oY;vtpciQYa>wC@VTkk-2g^k5Pbwq?^gG>M%j>h*VWh8D-YMT;*LM@JKbcQ zkmEUUHEllWsmjX2DIy}mkg{xEHC5{QNGx`eNTnG&-y9!sAMYs&(YzF*P0h_06d_e6 zA@AP<4WJr&b(d~k5ErndIyhzSb=TjS9&+dTKz z8YLJ8$;-XNI$0S-yR3?& zMzTGJ4^7f_(MgGkL*fl$Ya|ki%`Eko4)Z%=FV^2N?hy@8=2)w5@!g5Ldi zXB`~48NUl&oballxUfhngaDZ=)v_%-aIUr(n!M&@jt=f=A|mADft(Arl9SWcrxuND zuy56}v5K2P;mU7KrwOQQ<=(vL)gbQc^^E%?BlEtv4;tIDnz`&f&v$!y87`&#k|Lq) z7m2U)2ET;mkSy&VpSUr{Olio)cJ!M52+_&i3~F`r>W5 z#_Lghs_UIp&A!z9M~}cSnrV6|ch10ZJ3sgpq3H#s{Un&XOc5@N>9nhSbTi^&!wVe zrox;pbmmDaE1`V5F`;Ee(B64YjR*XKssr*mx+kh$g(?qW{C13h$Je;R6rk*-2-~zp z!k;idup#EH46}?j@O56YC@r14Bc@fhh%(H3ye3tAp`G;^^?K z2|}GR<@r+N%K4t)S#6Z@_;K9G!-ukrERykouHa4TCs%gGoS%7j>Q(yG<_ASZmhUy$ zEiO9e*?$q_=LzqaW2hl8B`t7L&2sjGo=!&t7ZMK1*V;jLwV87R8@GZ#=5mWQhRQO~ z5)**;>0`mh!>Wx6?Hx;w$f0vzkkfjZDIrX3(IN%+a=VH&g#w^kpY{@ cuK$7k2!lvI6-QsS%!O zzP=1vKsE;hV|yk83y{SK#8N=az`(SC2`-|sfEmFCNk;0qOam$@@^oe4FQ`6w^c7s!0tkS!+HMG25onjL0%Db^~ z^EEf#jb<&OYt0fJFP%I5q)cq-nLUAzlJ31aGjr$ne?@7}&PwRj|NGS&z31JXq-}Du z_*Q)7d&&8VGt1W@d+$FUC?hbNdF$7sNrLL?$7jr}WZQn*cEcTmGxJ-^XYzdeR#qM0 zw_LcJyPKJ@{hL_&=K6;cQ%|NyaU9o^;Ck?>NNnrf)lY5&%s$(;^Mke;`#Nd%n3HQC z1g~{d2wN?>Xu^RTH<&;DQ_mAI*gng7Yt+)8V%@BqhK&8keeWqOe4%Uir>bM^_h<>p zo$WVoJ~x&Hw-P zkColKHy3_tTL=n2e*8oe7#tsC+nayM%E~Iw^m+8>xMgM0#QP@}$LLKyda3U?D`#Gl zjj5Ahf`twH^wX}j=l9J|tV@)8TQ=K9&CJX^P;2UmizZT6Vu8N zuCr;A(=3*x2rufmc=5M}2-l5&8+@PNuv7OqS-CgvQ$aUO2ahHh{*e25;<4x|hMN=ews$Yx?ww&G^?%OI=5DV7ySD{74U5tP{E|$h z1S@3^mVD^E^DeVY@0VItZo!(BSq@<-1`+~XdmRic&hlltDhK}fTW7V8Z()Y~8}k>b z8Y;D4dT-qSuk+W=_UM(zE9<5g{a!WS@txvG9u%O&SbOwD0Rb=I46Uiaen{Jzigeg7m<9Z$(4m5~4d$lH-^&q$j3 zJu=dgnT-BeE-6@`wSzSPRHex7_`xOn(ExY5iw+Keo`hupNc?30CLxF<%8~$pRH4*= z7iuB&fBr5NbZB7!0C%vnwRQm^tJ(cdF6UIbTT_dg3tn*4vBP&d$2Hlv5_KbUxFl~^ zuRV%Oc$4)bj~r}kUHqwwMPhfPH&K&x8nogna4KQuQnFaId+PdM$IlAe{MS@6w`Y~^ z_6kGR@+nd_O4*5A|IT`CG9pK@uV+PE|tvG3EX8&JOp`=tJ(u3 z!V<2BW1$)f*uJUQACP3&C9EJNjzOcLL?V%qa{5&PcR~h%7%>nQN;kH&u*U1%qQH#5FWECAPO)GrYtJ z8yj-D2D;WD*hpvROT(M*kDuDFHpe+WCQ!$%#DLDu&Ypg=0-KFbJ&dR*)rl)!-J_#y zb&KMWgnPkzdIkpQT<71_bk{ozG;rG0)xKrs>Xw$CSN2f}#x`;w&u%%_ zT?`Biv#YPGTP1>8V~Eyv#q?2me*ErlK{jtDC)>)OpU=$78cD*`SC_Y_#>k_{IXU)P z?gL-1$S8LM-2dhG>YejTT>Wp3jk;$jUPZ+X0g zjjA_%bf8T*m^VF9zVfWGw_z$oT+_-k(Y^#$qD+56D5Bv*M%Z+CKs_`nNe&e!iN= zS-6j0kbYf)(^1dmGP_Sd7w-4>yM_+y3*!p}k1b2ynVXyM4Gs>@#@88}xyA;TTibMu z5S(81E(2Xe91i!0;~h_emo#WNHnhLbV&Xj5vG9u%O&SbOwD0Rb=I46Uiaen{Jzigeg7m<9Z$(4m5~4d$lH-^&q$j3 zJu=dgnT-BeE-6@`wSzSPRHex7_`xOn(ExY5iw+Keo`hupNc?30CLxF<%8~$pRH4*= z7iuB&fBr5NbZB7!0C%vnwRQm^tJ(cdF6UIbTT_dg3tn*4vBP&d$2Hlv5_KbUxFl~^ zuRV%Oc$4)bj~r}kUHqwwMPhfPH&K&x8nogna4KQuQnFaId+PdM$IlAe{MS@6w`Y~^ z_6kGR@+nd_O4*5A|IT`CG9pK@uV+PE|tvG3EX8&JOp`=tJ(u3 z!V<2BW1$)f*uJUQACP3&C9EJNjzOcLL?V%qa{5&PcR~h%7%>nQN;kH&u*U1%qQH#5FWECAPO)GrYtJ z8yj-D2D;WD*hpvROT(M*kDuDFHpe+WCQ!$%#DLDu&Ypg=0-KFbJ&dR*)rl)!-J_#y zb&KMWgnPkzdIkpQT<71_bk{ozG;rG0)xKrs>Xw$CSN2f}#x`;w&u%%_ zT?`Biv#YPGTP1>8V~Eyv#q?2me*ErlK{jtDC)>)OpU=$78cD*`SC_Y_#>k_{IXU)P z?gL-1$S8LM-2dhG>YejTT>Wp3jk;$jUPZ+X0g zjjA_%bf8T*m^VF9zVfWGw_z$oT+_-k(Y^#$qD+56D5Bv*M%Z+CKs_`nNe&e!iN= zS-6j0kbYf)(^1dmGP_Sd7w-4>yM_+y3*!p}k1b2ynVXyM4Gs>@#@88}xyA;TTibMu z5S(81E(2Xe91i!0;~h_emo#WNHnhLbV&Xj5hP9Y<^QP#kKMRBB!|+KSNz(V|vu5?YZ_YIRsekE*CWVv~~8 z2udhDW>p*sMbjFoa_!TjU+(<}?w9-Fd7tNf-{13mdOy4=P7c+DdQ}3kszc2A{2v)6PNpTZ}r9#w*sn% zyifb>k09gLkt6XO&g@Qs*~$;YIn%Zc*0s} zaQv0fnXXJtrPw&TE zfdQxW*Zb^+N@`N95ytB6Fu0zlopx`>A^t`Pb$Y;Oj&={7oScqo439Yv^mFDO9qec@ zllu9s>~Nne{7)MDs5QONJ!7B4~MRmtyck<&7I(-Fb=A7~#Tn`ljUpRZ=RLC4PV5wq_{5o7<(%rvIiX zGd(&YNUdeh^8b~$DUar4st(7zxQjAmO-;ENhA;3IT#P;uwoRN$y06FU(%;*KRhmzSFh9W|K;A~*F9DW)18&|M(w6PCJ;arm+*IiAqol#5{fdRyJOPO5UheT z0^$B>$Yo_XU(b8SaVS_coTGEOF2zuILK5i=)Qt0Dn?>7aU9DmneHY%8CmhhzW#M#7 zQBgi5!o4zWHsh5w`$NO+_I_hMo5Vj}isu+F>cXbeIpf}Yo6uzsIMx8d?Q|^sEX|(a zTz7J#bk4=!Xlra!1^@P^3ur^>R28 zdAjLEwWtVRx)NwBh!;#Es9iu26@dLjzHgFQGQyIn~;QBwx7n?7!|Kz){Cxrsq8 zz>37p1-QGLkVqMK*>WGTb8Xte%shTiPftov+nTlp!7f+pqx>BsK&ZN4wG>lYYk5(( z3lo7<$Bz^BTUN)3lsAU@`p{KV3RFhMoMS1#awq@`3I;Ld$V0%}fL&HoUA+WjXecbV zlC3*YQqm%E9!o`Z@Cc-`;Q^JCw_A;g1cFkyeeo>4@3hB_?&V(;jp8iwi_ht7jBU$q zJZfOrrAjOvRK}WMJ}VW#68@svO|J;zrLtkqe^qDQ>gfUWqqw5u-xl9~Q;AGVO(k$& zv#aJv^oK0w>&Rg<5D#cVlTht-Q&%8_eo3wCs*$Jz$^E@k$oEUkZB1WsPe?B%g z)|KqFu=nJYz~BiNa#_n$Xbs;qlUZ{}82z*bDP10oFo|KhxxJ(+a08H2XMFgAZY^5$ zYu`-Zn`(1}P8MWh^6vyH2W&z(QxJd{i1Oe41nsIH6V=alOb|oGz1P z4AB->X#=JY{(F7y)L3?|$l_70qy5Jdz0=dKv~y@xO2xyQOyY0d?89iuH_Gib8{04I zo`?s3;OcFy&Ww-cY#oFky5&R*68a~hB+}Z-%a+CES$tukhqTrg)-#+wu0f~ayt03B z>p@?uS0|SHHnbBx$e8R68?tMbrnc775W;0-h2r0Q;lbm943NqUk12bS%!N9uE#lHk}PnsRq93BpmR!)LS;!*VW80GG=MNT t@s15|Uiov-{x4l1I9v9_|BhP9Y<^QP#kKMRBB!|+KSNz(V|vu5?YZ_YIRsekE*CWVv~~8 z2udhDW>p*sMbjFoa_!TjU+(<}?w9-Fd7tNf-{13mdOy4=P7c+DdQ}3kszc2A{2v)6PNpTZ}r9#w*sn% zyifb>k09gLkt6XO&g@Qs*~$;YIn%Zc*0s} zaQv0fnXXJtrPw&TE zfdQxW*Zb^+N@`N95ytB6Fu0zlopx`>A^t`Pb$Y;Oj&={7oScqo439Yv^mFDO9qec@ zllu9s>~Nne{7)MDs5QONJ!7B4~MRmtyck<&7I(-Fb=A7~#Tn`ljUpRZ=RLC4PV5wq_{5o7<(%rvIiX zGd(&YNUdeh^8b~$DUar4st(7zxQjAmO-;ENhA;3IT#P;uwoRN$y06FU(%;*KRhmzSFh9W|K;A~*F9DW)18&|M(w6PCJ;arm+*IiAqol#5{fdRyJOPO5UheT z0^$B>$Yo_XU(b8SaVS_coTGEOF2zuILK5i=)Qt0Dn?>7aU9DmneHY%8CmhhzW#M#7 zQBgi5!o4zWHsh5w`$NO+_I_hMo5Vj}isu+F>cXbeIpf}Yo6uzsIMx8d?Q|^sEX|(a zTz7J#bk4=!Xlra!1^@P^3ur^>R28 zdAjLEwWtVRx)NwBh!;#Es9iu26@dLjzHgFQGQyIn~;QBwx7n?7!|Kz){Cxrsq8 zz>37p1-QGLkVqMK*>WGTb8Xte%shTiPftov+nTlp!7f+pqx>BsK&ZN4wG>lYYk5(( z3lo7<$Bz^BTUN)3lsAU@`p{KV3RFhMoMS1#awq@`3I;Ld$V0%}fL&HoUA+WjXecbV zlC3*YQqm%E9!o`Z@Cc-`;Q^JCw_A;g1cFkyeeo>4@3hB_?&V(;jp8iwi_ht7jBU$q zJZfOrrAjOvRK}WMJ}VW#68@svO|J;zrLtkqe^qDQ>gfUWqqw5u-xl9~Q;AGVO(k$& zv#aJv^oK0w>&Rg<5D#cVlTht-Q&%8_eo3wCs*$Jz$^E@k$oEUkZB1WsPe?B%g z)|KqFu=nJYz~BiNa#_n$Xbs;qlUZ{}82z*bDP10oFo|KhxxJ(+a08H2XMFgAZY^5$ zYu`-Zn`(1}P8MWh^6vyH2W&z(QxJd{i1Oe41nsIH6V=alOb|oGz1P z4AB->X#=JY{(F7y)L3?|$l_70qy5Jdz0=dKv~y@xO2xyQOyY0d?89iuH_Gib8{04I zo`?s3;OcFy&Ww-cY#oFky5&R*68a~hB+}Z-%a+CES$tukhqTrg)-#+wu0f~ayt03B z>p@?uS0|SHHnbBx$e8R68?tMbrnc775W;0-h2r0Q;lbm943NqUk12bS%!N9uE#lHk}PnsRq93BpmR!)LS;!*VW80GG=MNT t@s15|Uiov-{x4l1I9v9_|BYj_qgJuo8-tPKEwhIICmJ7?(gt1wq{xV1GviOzulhGbs=Go3NeAxsAV zz*x-qZ>71I=|BAM#W2xkL%NrYxsicmB*R9b?>#4D{(erB>~pR?<2{(ShDz!sX4Q%j zjLDl#CJmc-V2VB?N`^Tzo^x{wjWPRjHUs5kmvl+$i~FUxI0?BpDaiond+6c&dfA_Y zv$U6QY|YJr<|r}yd&d#k*^!}C$}iI4gD%Sml5n}^fEe#tpdhsPwdAS3rv7_F0MwAV z6sYf0I{J7>-{SyMY3&>EYMF3|AWcx1}o-@AuVjP zJ+ZIOa*Dq$;Wb9?4#ZZ%&45^+xu;Un(lxPB4$p)&0{YJ_F=$u>zqoeZo5?O>9>~YX zPmuS&j#;0q&7Dah4r5>6U+lAA87dq+?cmjf`pCnL4fC(WbZ1-%_-5y8ElCv8k%NPG z*JoNs9~IU!)nW>tpTP&Z{+U8JWuo(UH-8uo;61EzK^~*C18YvKnCgB!)^yA5!Px8J zKzBFQm4Cf=>O(AZ zh*ZlrwNA*}@;{6X`)Lc@_%{AUBVaHG-#dx_ol`wodnf`u+~RWAT6phVA2{QDwEyN+ zs(MLy&9u~gN0xYk5{2dJmBE6Zph8z=cdVZxzonA|uqHJiZZU||?cqpn4_m*{8uXoM zwtApCnUD<$`7Wjt{<9c+=x;o8dbA!}HjTaiRr2hG4@&a&p({4-v^tG!s?Xi@^P<{> zSET5%KJNIL3y9VC4FYv&7`Z_xv)uRzKc9feN@^2iUj%9i*NbZi-($=hnmriDti#sf)1<`|L+P z6Q(6t^}y+IZAgbLJPCj?b|Q!bP`kUDLzXQ@EZpGNA)3CUM(VY-x4GN}e1AmkQD;n* zo!@dj8{lcesbFuiqr&fyyH7&&RJ|0HAzsQI9&j63R5+Tzdj4WDY%q6hu|EW|(h1h| zC*ol0ML{4`Oy1+WtrvA8pXY#XDk+7Qnmp4@Cm(PZvYml;$UN~9Jxa|{@sLu2=y{Rf z2%T*AzW57v?$-0KhRNscJw1!w;y6U?8+S*`V3wiYWXFYb8}~3hpo6>+T8J?$EoB}; zv$BM*aI#|a#ejjiFZ)g(e1AH7%NH%Jps4tmeoN&I@<&x6yR!Ye>yxX*4IMr)F%yoD zBCDmZF>;=GpRI+@JF6qo4bxJ>Mg*&M+(`ij3{jY5!$Z<4p zN4~DTq)h<&4SLO7@fX#BS*&rw4c?E z_u6QAsBy>DsN;-MQ$=}11|nXdGU*DC^9`B^Gs!0gmO-_zr1_)Bi24VX+f~e&2xlWx zQwP%ZU#{H)GnK(!pzcAf9;*t*Q}dA|=2b#)cQ zn^`q`2MMHr&b+m80eZMbwoS(o$tz>pA1Y5Kg3Qja#_Wp@7N|y{x%r(*(xDyU8_E?E zv-mWDyjOm+DqR#8mKOJQhfRFhQk+{ZWI7VZ!= ztMlSoRul@QrLMnxd45>W8xG?kby{T0gNKXLM0G3@k+6#^?15ce&kGFhmmDzT^Yy4M zYA__cwyo_Y7;@oD!E>`h?XdBw3#P>7NDO+qn8eAG=n~S6BA?R4rzdqlEjgw5FF3Ai zkmRJK8_*Nv+W5OH1?g6n&_u$gBo1jYNjbNe6N>!NJXM_Q&$zu!k+!jrkkD}Spl_>a z=iKZLicl=Q%cwV7G*O6-28 ztBy6>k`hMfD3M(@M}mS}By)^h9ItYEB4<-zq}Fv%;yT-e7{0Ogm!my;eZ10eYm05X z#^0qOJdf{77!-0cn5*{C=*MNT%*38NZ8%OaYB$^E^!&rUpZm=cs@cD0-z`R{1Osxk zK%lhtj@yOs;QX*Z$a2CtO5Sc)`uMnG`%dY}KF#E*ph55n<(c2g!tDNv!bGOq_GpSJ zS6}~Uhy7neUdYbrdFJ!T8p7;;VpyYfc|sm`c6Qmify2qcx30Gcq=_+_JUyyKeffQeO2JTTfSVBiIdnICadfW@Hi3zZ%&G z+`x|>?XLS~X*_mr-@J!Ip&}R1W8KROpV`&*5@8_P_n(tgNl8iKlQP%Trzl*wfv~(f z(|n!2dF<8X#qEzOy1HXJu4~ESpeS$9F8a~X@u7n&xzgda9d+)}2^(*7zp$b@f;Rtw ziTE*T&?uL2zOA$EPKgGVGV;mvMJCt_IW9J+D+@onOJPT&!@^PUS1J+$CVbHN#*&bqou9Q0%kYR@bn?~IvgT~^Q>)dVv1lz z>gM74FP;n3a`5s&L&GCCI;NW7_A^(8Tn$o4*KG=}W*qg5ld#)Y8-5r_tA7giQQgK{ zehsJad^6ax^)50}+mD@kY}3|m-ACff$rbhdc^`u8Qu?%2^f1GqJ4IYAdX< zS3#OJOLc1_^E3=$@8cNetleW8an$r!aj2*0ucl*;Wpi31d#Ksoq&uwJg={8@=H}3X* zp>TsYNOqb>OgCmTQL#cVg){a5SE7Bvc}zlyj=PH!JB)X9Y! zu3x`f=k>eS7HrZ3Im+cYu-0L?W$417)uUu=zUxF(nrFRMzc;V10=b=lS@V#jSNxQ^Y(OmryI=yVV$=7T zKyiC1GXQHYXev2a1EAG%PY>Mba!s2(+K-XTUE@ygqy+%#|M}T~BX1m`icPxPnJy~_ zIlj&Vpq#A*x3D~M1o!);;0gLy`CrLV#8i>&N;v?=iPt2$hAunLR#<7!J@PIs_{#$R fZ~b2nd_Gp@js3#N>DI@;`zCW^8>3q2?Z5sBtHpUq diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_76pt.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_76pt.png deleted file mode 100644 index 93ce2c69ffdead96d8d51439a7ad073e81a2226a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1407 zcmeAS@N?(olHy`uVBq!ia0vp^J|N7&1|*M957Y)yEa{HEjtmSN`?>!lvI6-QsS%!O zzP=1vKsE;hV|yk83y{SK#8N=az`(SC2`=KZfEmFCNtzZuy#+M#lc$SgNCo5DnbH0s z;UdTX-*NKr($1gYpk^JwDHJ*m2!U(29e@AWf0Ofbp2hfhpdtoCa!)%VM;A8VorNo(3f{^>{XDPUjL% z@L}@V88U+*St#Mu(nBl~lNo~bMAZ(=V3|?05=E=cq?P=fKd)UAle7QFygqLKv5ES9 z3zjTlDJ(3UbwsZ0#=q$?k&%rjue5cZJSpp7XlS^j^0T_ik>l@g@KqJ9xMt9lYO-zD z0VRXer%zY>_^`d%sA%WA3k=E;xw*N0nJ*?i{4YEE?fbuHa&9kV*tTm|*8Da#pMAd! zc)w5T@b|cpZ53euq|q|b*T+wT(Z$vE;hpm5Q|kJZmo%0krx$zi9v9qnKxTv0EyFz7@MqG3f{E>=vd8e`d7I+BxavM(4e$SaG8PjP z-@}!*z9Zr8-t2=HK3s^q?q{!~!}Im)*AwSX-P`!{vx{)T#*Oo}lmBE_N$KitH#9e2 ze&x;c8+Y!wgolgI)ikMT>RBTXV08@bnWf!37c<*2#N1z z4l5U*XIuX)>Hgh?r&g_A?VRWLAmQSVSq$D@UPV6K{ul0@-S2#Qu9%On@7r^?-_Ei7 zSNgpqxuD>}+2YqeQ|Ri|u61?$E{j}PyzGF+C-dO^1QW@PTed9mY0;Xv?DD&<#~EM8 z^*?^OYS)B>mswc_pPpz=J@UTnY-8Y&ZS^?@5YguyD)FL$CLEz*3|?&uJ`wxGJSe-*|+#F-~Ye#YI%!Dm*xo~e;)mI-Lg%}(Dv`O@)xN$z|q9ek-no{=HWARHivw{PU*^*Q_~nR?erbVQu1XRa-N=KKK3h zOtr@}HMQK*!mibATCk}$D8bS7(CGxblT*GJZCfyf)8N^8r2}{}J}G6!r~eGTRTWGA Tt&u(sDg`}V{an^LB{Ts58-aX% diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png deleted file mode 100644 index fcb1a1f4787d4224ab8985cd8a063ecbc69fff07..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2969 zcmd^B=|2<-7oNe4ElV<(+_EIHlg21TBpO?{VVWV!cw?e$al7_0A%scAWGpFJl94Q7 zlx1Wa>$PtqhU^9r$}r}-?}zvO7vA6R{qUUUIp_SI^Wl6rzw`KaTMH4P<3a!cKm>8k z40*`pe;@!pj51&36%L8-j){#408o}Jyzd4&%ndv+2%L=#KV1nb-#dS6_|=+P6VklSk1C;_}VDG2T+LPov9@ zC}?AOOUfx(d{>YjF{L0+SB1!jcK5wNS4^VL#KmFXM!DwIO8B?jcgs>?_x{&cmO_99*n+nO5A<$>d8qfppcE4W=87v=P6Kk!HxdBZ`q+V`vInaQ$mZF8)#K%kH86QTgd zz+Uve9Fb^@xC&u@g4oB{aSb7ZJNj^Cto{%zo=9mxpX-CN#zTaQNPK zBTAI&`^S%aOxJE#9pmprO+N%pL-;q(Ush?_Ry9G6ENb6TPm*u7m5mK4M7wKcr}|nr zf~J_mw%g-#^6>EJ?{fpA!^3O6CrUToEk5I1qbX7v&cWgT_AYwDJKVp-2$tu%$0eP- zG=9c55&_Yj}eiI-+YiUHcddBycBWv)S z(C;L}AsK0SPxQSw>+Fj)1J!GF9&#E<*-mw#m66Qj(kh0vMrhM0hQEp9$;K~yWI3%Q zQO+!^o6bW?=Oi3in{;2-Ilc72!q|fE1zjtDFUo; z=l3;XevH2*q3ppT>mu!)lk1xv3_hhpT5V8za#{j?1z=oigj%GMpzZiBeXU zmI;qt-QFc;zBSOC!4)B(ywrpQ5BA3;Wj95~ks61q!Qla~+1mXBh1N-u^wA83#S|)L zoWZCa_*eQmG#7ynE{LbJEDd~xG9Z>=Sv;(AcoDP77=I>ceR8!@lN#u3k5M|NV#|8) z*`FKFKP&@z*mqPJpUl`&y{A2O=SNI zq?BZPS9(R#4Ap)NO3TQIf-mf>GA6Scg?112))fm>W8u6d1y(lZ=dtWaHEH#cBTZU!y~5&r?!}49?Qqxcq{u zVFO!Npw7UjiA>4F_A+IU=J=z}U!mm#=eu+Tzy0BS2hij{CG8sCjwv1=KYpI|5J&mN z++;_Phy9SHJhpgzz0&Ec+D^S~P})=LP&TeH9yk!tWr#XiXw>T=qB%e=6r+}k3rZ>J zsv0+;9z2ixGHAA*N<5jLy$(3pDp~4_R4|K#iY+_AS=~$Ng63FO{G0B zS59kjcJO6*!~I2u{Y@0~`kZNPUY=5WcRMVj=fOhHiK*46Y<6g5ff34g3o4mxBBil7B3$M86;R2&0iQGgk(S zSF{8j*-Q)Wh^Gx_whnnV;2qRg!)`GJ8`}O^fgxWgjz^V$XUN^Lpyd3 z!XROQU;@~R*kSEr@E4E$x}BFD(*!chsgCuMKPCB{R@bo-m|h|!Kn(~8aBtiPUCM_H zO&N;G3`TxuK4krnqH)$3e#CAX(I%_t`NB^4lnCWKI>e#}n@#*PjVxBe@^&c2x&;}F z#jo1~z$&GM<0_FHGutc!H}4}!Q}sTX8k=ApBWJ*f*7`@!(B|LHy2sDDFVMd{;+59& zq=0pd1zvGDdgnv83c0uGAELn`&$qfWPs6q=RdfL(hi89oEBWoKaOn8H)y2>_aQA{Se@y?cXs7|Tq6^%DiY-H!>DpbzR-AVFVwR( z^6t*FoWdB&xrxif!45nXGy3U)`t)++#ce{4v&{9%z&6{)=jPKIM%1%a9i6J$ae>uE zJNXBaV-7wVT2TwJNUED4Mz{$h! z!|e3yerCh*N(OEH{rc?*5cMcZ^ZQq4bg+OciRhAjQc#hVm`pi*xcHfC31-(!qh8SE zArK@p>#+8K9oeuEe?03C!<%OqMVUYql}rYzj&oedrGja`oq8atIUEI^r`G@Ld~*u>MV@O z#@?98%)p83z-}&CrxKR7Y;1=4tcj=#$4+A}MA)UkfOfY@J!XPr+o?f!8`>ZyU+)G_ j!2lfQaP0rVvdslTQs!SnJ1o=x`HvA-ZOuwdu~B~m3y6Kk diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png deleted file mode 100644 index 088c52c1226809cb86f3a8b10746d472e8874ad0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3284 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 diff --git a/Example App/iOS Example/App/Assets.xcassets/Contents.json b/Example App/iOS Example/App/Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/Example App/iOS Example/App/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Example App/iOS Example/App/Base.lproj/LaunchScreen.storyboard b/Example App/iOS Example/App/Base.lproj/LaunchScreen.storyboard deleted file mode 100644 index 865e932..0000000 --- a/Example App/iOS Example/App/Base.lproj/LaunchScreen.storyboard +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Example App/iOS Example/Controllers/PresetsController.swift b/Example App/iOS Example/Controllers/PresetsController.swift deleted file mode 100644 index 3a58d7d..0000000 --- a/Example App/iOS Example/Controllers/PresetsController.swift +++ /dev/null @@ -1,141 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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 -import SparrowKit -import SPDiffable -import SPAlert - -class PresetsController: SPDiffableTableController { - - // MARK: - Init - - init() { - super.init(style: .insetGrouped) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - Lifecycle - - override func viewDidLoad() { - super.viewDidLoad() - navigationItem.title = "SPAlert Presets" - - currentPreset = presets.first! - configureDiffable(sections: content, cellProviders: SPDiffableTableDataSource.CellProvider.default) - - navigationController?.isToolbarHidden = false - toolbarItems = [ - .init(image: .system("chevron.down.circle"), primaryAction: .init(handler: { (action) in - guard let currentPreset = self.currentPreset else { - self.currentPreset = self.presets.first - return - } - guard let index = self.presets.firstIndex(where: { $0.id == currentPreset.id }) else { return } - self.currentPreset = self.presets[safe: index + 1] - if self.currentPreset == nil { - self.currentPreset = self.presets.first - } - })), - .init(barButtonSystemItem: .flexibleSpace, target: nil, action: nil), - .init(systemItem: .play, primaryAction: .init(handler: { [weak self] (action) in - guard let preset = self?.currentPreset else { return } - SPAlert.present( - title: preset.title, - message: preset.message, - preset: preset.preset, - completion: nil - ) - - if preset.preset == SPAlertIconPreset.spinner { - delay(2, closure: { - SPAlert.dismiss() - }) - } - - }), menu: nil), - .init(barButtonSystemItem: .flexibleSpace, target: nil, action: nil), - ] - } - - // MARK: - Data - - fileprivate var presets: [AlertPresetModel] { - return [ - .init( - name: "Done", - title: "Added to Library", - message: nil, - preset: .done - ), - .init( - name: "Error", - title: "Oops", - message: "Please try again later", - preset: .error - ), - .init( - name: "Heart", - title: "Love", - message: "We'll recommend more like this for you", - preset: .heart - ), - .init( - name: "Spinner (Loading)", - title: "Please, wait", - message: "It take some time", - preset: .spinner - ), - .init( - name: "Custom Image", - title: "Custom Image", - message: "Passed UIImage object for preset with style custom.", - preset: .custom(UIImage.init(systemName: "pencil.and.outline")!.withRenderingMode(.alwaysTemplate)) - ), - ] - } - - // MARK: - Diffable - - var currentPreset: AlertPresetModel? { - didSet { - self.diffableDataSource?.set(content, animated: false) - } - } - - var content: [SPDiffableSection] { - let items = presets.map { (preset) -> SPDiffableTableRow in - return SPDiffableTableRow( - text: preset.name, - accessoryType: (preset.id == currentPreset?.id) ? .checkmark : .none, - selectionStyle: .none) { [weak self] _,_ in - guard let self = self else { return } - self.currentPreset = preset - } - } - return [ - SPDiffableSection(id: "presets", header: nil, footer: nil, items: items) - ] - } -} diff --git a/Example App/iOS Example/Info.plist b/Example App/iOS Example/Info.plist deleted file mode 100644 index 58e1ce4..0000000 --- a/Example App/iOS Example/Info.plist +++ /dev/null @@ -1,47 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - SPAlert - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - LSRequiresIPhoneOS - - UIApplicationSupportsIndirectInputEvents - - UILaunchStoryboardName - LaunchScreen - UIRequiredDeviceCapabilities - - armv7 - - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - - diff --git a/Example App/iOS Example/Models/AlertPresetModel.swift b/Example App/iOS Example/Models/AlertPresetModel.swift deleted file mode 100644 index 834f902..0000000 --- a/Example App/iOS Example/Models/AlertPresetModel.swift +++ /dev/null @@ -1,38 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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 -import SPAlert - -/** - Example wrapper model. - */ -struct AlertPresetModel { - - var name: String - var title: String - var message: String? - var preset: SPAlertIconPreset - - var id: String { - return name - } -} diff --git a/Package.swift b/Package.swift index 746938c..9996a56 100644 --- a/Package.swift +++ b/Package.swift @@ -3,22 +3,22 @@ import PackageDescription let package = Package( - name: "SPAlert", + name: "AlertKit", platforms: [ - .iOS(.v11) + .iOS(.v13) ], products: [ .library( - name: "SPAlert", - targets: ["SPAlert"] + name: "AlertKit", + targets: ["AlertKit"] ) ], dependencies: [], targets: [ .target( - name: "SPAlert", + name: "AlertKit", swiftSettings: [ - .define("SPPALERT_SPM") + .define("ALERTKIT_SPM") ] ) ], diff --git a/README.md b/README.md index fdf48bd..4bb6f8c 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,30 @@ -# SPAlert +# AlertKit -

- - - -

- -**Popup from Apple Music & Feedback in AppStore**. Contains `Done`, `Heart`, `Error` and other. Supports Dark Mode and `SwiftUI`. +**Popup from Apple Music & Feedback in AppStore**. Contains `Done`, `Heart`, `Error` and other. Supports Dark Mode. I tried to recreate Apple's alerts as much as possible. You can find these alerts in the AppStore after feedback and after you add a song to your library in Apple Music. -For get alert from switch silent mode, check library [SPIndicator](https://github.com/ivanvorobei/SPIndicator). +![Alert Kit v5](https://cdn.sparrowcode.io/github/alertkit/v5/preview-v1.png) + +For run alert just call this: + +```swift +AlertKitAPI.present(title: "Added to Library", icon: .done, style: .iOS17AppleMusic, haptic: .success) +``` + +Available 2 styles: + +```swift +public enum AlertViewStyle { + + case iOS16AppleMusic + case iOS17AppleMusic +} +``` ### Community
-## Navigate - -- [Installation](#installation) - - [Swift Package Manager](#swift-package-manager) - - [CocoaPods](#cocoapods) - - [Manually](#manually) -- [Quick Start](#quick-start) -- [Usage](#usage) - - [Duration](#duration) - - [Dismiss](#dismiss) - - [Layout](#layout) - - [Haptic](#haptic) - - [Spinner](#spinner) - - [Colors](#colors) - - [Global Appearance](#global-appearance) -- [SwiftUI](#swiftui) -- [Russian Community](#russian-community) - ## Installation -Ready for use on iOS 11+. +Ready to use on iOS 13+. ### Swift Package Manager -The [Swift Package Manager](https://swift.org/package-manager/) is a tool for automating the distribution of Swift code and is integrated into the `swift` compiler. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies. +In Xcode go to Project -> Your Project Name -> `Package Dependencies` -> Tap *Plus*. Insert url: -Once you have your Swift package set up, adding as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`. +``` +https://github.com/sparrowcode/AlertKit +``` + +or adding it to the `dependencies` of your `Package.swift`: ```swift dependencies: [ - .package(url: "https://github.com/ivanvorobei/SPAlert", .upToNextMajor(from: "4.2.0")) + .package(url: "https://github.com/sparrowcode/AlertKit", .upToNextMajor(from: "5.0.0")) ] ``` ### CocoaPods: +This is an outdated way of doing things. I advise you to use [SPM](#swift-package-manager). However, I will continue to support Cocoapods for some time. + +
Cocoapods Instalation + [CocoaPods](https://cocoapods.org) is a dependency manager. For usage and installation instructions, visit their website. To integrate using CocoaPods, specify it in your `Podfile`: ```ruby pod 'SPAlert' ``` +
### Manually -If you prefer not to use any of the dependency managers, you can integrate manually. Put `Sources/SPAlert` folder in your Xcode project. Make sure to enable `Copy items if needed` and `Create groups`. - -## Quick Start +If you prefer not to use any of dependency managers, you can integrate manually. Put `Sources/AlertKit` folder in your Xcode project. Make sure to enable `Copy items if needed` and `Create groups`. -For the best experience, I recommend presenting alerts by calling the class functions `SPAlert`. These functions are updated regularly and show the alerts as Apple way: - -```swift -SPAlert.present(title: "Added to Library", preset: .done) -``` - -For using a custom image: - -```swift -SPAlert.present(title: "Love", message: "We'll recommend more like this in For You", preset: .custom(UIImage.init(named: "heart")!)) -``` - -For showing a simple text message: - -```swift -SPAlert.present(message: "Something going wrong", haptic: .error) -``` - -## Usage - -### Duration - -To change the duration of present time, create an alert view and call `present` method with custom duration: - -```swift -let alertView = SPAlertView(title: "Complete", preset: .done) -alertView.duration = 4 -alertView.present() -``` - -If you don't want to dismiss alert in time, disable `dismissInTime`. After it, `duration` property will be ignored. - -```swift -alertView.dismissInTime = false -``` - -In this case, you should dismiss the alert manually. - -### Dismiss - -If you tap the alert, it will disappear. This can be disabled: - -```swift -alertView.dismissByTap = false -``` - -Also, you can manually dismiss all alerts, simply call this: - -```swift -SPAlert.dismiss() -``` +## Apps Using -### Layout - -For customise layout and margins, use `layout` property. You can manage margins for each side, icon size and space between image and titles: - -```swift -alertView.layout.iconSize = .init(width: 24, height: 24) -alertView.layout.margins.top = 12 -alertView.layout.spaceBetweenIconAndTitle = 8 -``` - -### Haptic - -To manage haptic, you should pass it in present method: - -```swift -alertView.present(duration: 1.5, haptic: .success, completion: nil) -``` - -You can remove duration and completion, because they have default values. - -### Spinner - -I added the preset `.spinner`, to use it simply call this: - -```swift -let alertView = SPAlertView(title: "Please, wait", preset: .spinner) -alertView.present() -``` - -By default this preset `dismissInTime` is disabled and the alert needs to be manually dismissed. You can do it only for one view or dismiss all alerts: - -```swift -// For one alert -alertView.dismiss() - -// For all alerts -SPAlert.dismiss() -``` - -### Colors - -For change color of icon, simple set tint color for any preset: - -```swift -alertView.iconView?.tintColor = .systemRed - -//If you set custom image, don't forget set rendering mode: -UIImage(systemName: "pencil.and.outline")!.withRenderingMode(.alwaysTemplate) -``` - -### Global Appearance - -Also you can change some default values for alerts. For example you can change default duration and corner radius for alert with next code: - -```swift -SPAlertView.appearance().duration = 2 -SPAlertView.appearance().cornerRadius = 12 -``` - -It will apply to all alerts. I recommend setting it in the app delegate, but you can change it in runtime. - -## SwiftUI - -Use like system alert only show message tips: - -```swift -Button("Show alert") { - showAlert = true -}.SPAlert(isPresent: $showAlert, message: "this is message only") -``` - -or show message, title, image and other configuration: - -```swift -Button("Show alert") { - showAlert = true -}.SPAlert( - isPresent: $showAlert, - title: "Alert title", - message: "Alert message", - duration: 2.0, - dismissOnTap: false, - preset: .custom(UIImage(systemName: "heart")!), - haptic: .success, - layout: .init(), - completion: { - print("Alert is destory") - }) -``` - -## Russian Community - -Я веду [телеграм-канал](https://sparrowcode.io/telegram), там публикую новости и туториалы.
-С проблемой помогут [в чате](https://sparrowcode.io/telegram/chat). - -Видео-туториалы выклыдываю на [YouTube](https://ivanvorobei.io/youtube): +

+ + + + + + + +

-[![Tutorials on YouTube](https://cdn.ivanvorobei.io/github/readme/youtube-preview.jpg)](https://ivanvorobei.io/youtube) +If you use a `SwiftBoost`, add your app via Pull Request. diff --git a/SPAlert.podspec b/SPAlert.podspec index f5a08e5..bd6b44b 100644 --- a/SPAlert.podspec +++ b/SPAlert.podspec @@ -1,16 +1,16 @@ Pod::Spec.new do |s| s.name = 'SPAlert' - s.version = '4.2.0' + s.version = '5.0.0' s.summary = 'Native alert from Apple Music & Feedback. Contains Done, Heart & Message and other presets. Support SwiftUI.' - s.homepage = 'https://github.com/ivanvorobei/SPAlert' - s.source = { :git => 'https://github.com/ivanvorobei/SPAlert.git', :tag => s.version } + s.homepage = 'https://github.com/sparrowcode/AlertKit' + s.source = { :git => 'https://github.com/sparrowcode/AlertKit.git', :tag => s.version } s.license = { :type => "MIT", :file => "LICENSE" } - s.author = { 'Ivan Vorobei' => 'hello@ivanvorobei.io' } + s.author = { 'Sparrow Code' => 'hello@sparrowcode.io' } s.swift_version = '5.1' s.ios.deployment_target = '11.0' - s.source_files = 'Sources/SPAlert/**/*.swift' + s.source_files = 'Sources/AlertKit/**/*.swift' end diff --git a/Sources/AlertKit/AlertHaptic.swift b/Sources/AlertKit/AlertHaptic.swift new file mode 100644 index 0000000..231b01e --- /dev/null +++ b/Sources/AlertKit/AlertHaptic.swift @@ -0,0 +1,23 @@ +import UIKit + +public enum AlertHaptic { + + case success + case warning + case error + case none + + func impact() { + let generator = UINotificationFeedbackGenerator() + switch self { + case .success: + generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.success) + case .warning: + generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.warning) + case .error: + generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.error) + case .none: + break + } + } +} diff --git a/Sources/AlertKit/AlertIcon.swift b/Sources/AlertKit/AlertIcon.swift new file mode 100644 index 0000000..cba14d9 --- /dev/null +++ b/Sources/AlertKit/AlertIcon.swift @@ -0,0 +1,31 @@ +import UIKit + +public enum AlertIcon: Equatable { + + case done + case error + case heart + case spinnerSmall + case spinnerLarge + + case custom(_ image: UIImage) + + func createView(lineThick: CGFloat) -> UIView { + switch self { + case .done: return AlertIconDoneView(lineThick: lineThick) + case .error: return AlertIconErrorView(lineThick: lineThick) + case .heart: return AlertIconHeartView() + case .spinnerSmall: return AlertSpinnerView(style: .medium) + case .spinnerLarge: return AlertSpinnerView(style: .large) + case .custom(let image): + let imageView = UIImageView(image: image) + imageView.contentMode = .scaleAspectFit + return imageView + } + } +} + +public protocol AlertIconAnimatable { + + func animate() +} diff --git a/Sources/AlertKit/AlertKitAPI.swift b/Sources/AlertKit/AlertKitAPI.swift new file mode 100644 index 0000000..6260262 --- /dev/null +++ b/Sources/AlertKit/AlertKitAPI.swift @@ -0,0 +1,19 @@ +import UIKit + +public enum AlertKitAPI { + + public static func present(title: String? = nil, subtitle: String? = nil, icon: AlertIcon? = nil, style: AlertViewStyle, haptic: AlertHaptic? = nil) { + switch style { + case .iOS16AppleMusic: + guard let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first else { return } + let view = AlertAppleMusic16View(title: title, subtitle: subtitle, icon: icon) + view.haptic = haptic + view.present(on: window) + case .iOS17AppleMusic: + guard let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first else { return } + let view = AlertAppleMusic17View(title: title, subtitle: subtitle, icon: icon) + view.haptic = haptic + view.present(on: window) + } + } +} diff --git a/Sources/AlertKit/AlertViewStyle.swift b/Sources/AlertKit/AlertViewStyle.swift new file mode 100644 index 0000000..895a68e --- /dev/null +++ b/Sources/AlertKit/AlertViewStyle.swift @@ -0,0 +1,7 @@ +import Foundation + +public enum AlertViewStyle { + + case iOS16AppleMusic + case iOS17AppleMusic +} diff --git a/Sources/SPAlert/Extensions/UIFontExtension.swift b/Sources/AlertKit/Extensions/UIFontExtension.swift similarity index 100% rename from Sources/SPAlert/Extensions/UIFontExtension.swift rename to Sources/AlertKit/Extensions/UIFontExtension.swift diff --git a/Sources/AlertKit/Extensions/UILabelExtension.swift b/Sources/AlertKit/Extensions/UILabelExtension.swift new file mode 100644 index 0000000..e7930fe --- /dev/null +++ b/Sources/AlertKit/Extensions/UILabelExtension.swift @@ -0,0 +1,12 @@ +import UIKit + +extension UILabel { + + func layoutDynamicHeight(x: CGFloat, y: CGFloat, width: CGFloat) { + frame = CGRect.init(x: x, y: y, width: width, height: frame.height) + sizeToFit() + if frame.width != width { + frame = .init(x: x, y: y, width: width, height: frame.height) + } + } +} diff --git a/Sources/AlertKit/Icons/AlertIconDoneView.swift b/Sources/AlertKit/Icons/AlertIconDoneView.swift new file mode 100644 index 0000000..a8a6386 --- /dev/null +++ b/Sources/AlertKit/Icons/AlertIconDoneView.swift @@ -0,0 +1,42 @@ +import UIKit + +public class AlertIconDoneView: UIView, AlertIconAnimatable { + + private let lineThick: CGFloat + + init(lineThick: CGFloat) { + self.lineThick = lineThick + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func animate() { + let length = frame.width + let animatablePath = UIBezierPath() + animatablePath.move(to: CGPoint(x: length * 0.196, y: length * 0.527)) + animatablePath.addLine(to: CGPoint(x: length * 0.47, y: length * 0.777)) + animatablePath.addLine(to: CGPoint(x: length * 0.99, y: length * 0.25)) + + let animatableLayer = CAShapeLayer() + animatableLayer.path = animatablePath.cgPath + animatableLayer.fillColor = UIColor.clear.cgColor + animatableLayer.strokeColor = tintColor?.cgColor + //animatableLayer.lineWidth = 9 + animatableLayer.lineWidth = lineThick + animatableLayer.lineCap = .round + animatableLayer.lineJoin = .round + animatableLayer.strokeEnd = 0 + 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") + } +} diff --git a/Sources/SPAlert/Preset Views/SPAlertIconErrorView.swift b/Sources/AlertKit/Icons/AlertIconErrorView.swift similarity index 62% rename from Sources/SPAlert/Preset Views/SPAlertIconErrorView.swift rename to Sources/AlertKit/Icons/AlertIconErrorView.swift index 5ef786c..027f413 100644 --- a/Sources/SPAlert/Preset Views/SPAlertIconErrorView.swift +++ b/Sources/AlertKit/Icons/AlertIconErrorView.swift @@ -1,28 +1,18 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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 class AlertIconErrorView: UIView, AlertIconAnimatable { + private let lineThick: CGFloat + + init(lineThick: CGFloat) { + self.lineThick = lineThick + super.init(frame: .zero) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + public func animate() { animateTopToBottomLine() animateBottomToTopLine() @@ -39,7 +29,7 @@ public class SPAlertIconErrorView: UIView, SPAlertIconAnimatable { animatableLayer.path = topToBottomLine.cgPath animatableLayer.fillColor = UIColor.clear.cgColor animatableLayer.strokeColor = tintColor?.cgColor - animatableLayer.lineWidth = 9 + animatableLayer.lineWidth = lineThick animatableLayer.lineCap = .round animatableLayer.lineJoin = .round animatableLayer.strokeEnd = 0 @@ -66,7 +56,7 @@ public class SPAlertIconErrorView: UIView, SPAlertIconAnimatable { animatableLayer.path = bottomToTopLine.cgPath animatableLayer.fillColor = UIColor.clear.cgColor animatableLayer.strokeColor = tintColor?.cgColor - animatableLayer.lineWidth = 9 + animatableLayer.lineWidth = lineThick animatableLayer.lineCap = .round animatableLayer.lineJoin = .round animatableLayer.strokeEnd = 0 diff --git a/Sources/SPAlert/Preset Views/SPAlertIconHeartView.swift b/Sources/AlertKit/Icons/AlertIconHeartView.swift similarity index 76% rename from Sources/SPAlert/Preset Views/SPAlertIconHeartView.swift rename to Sources/AlertKit/Icons/AlertIconHeartView.swift index 38c6a07..12245d2 100644 --- a/Sources/SPAlert/Preset Views/SPAlertIconHeartView.swift +++ b/Sources/AlertKit/Icons/AlertIconHeartView.swift @@ -1,27 +1,6 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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 SPAlertIconHeartView: UIView { +public class AlertIconHeartView: UIView { init() { super.init(frame: .zero) diff --git a/Sources/AlertKit/Icons/AlertSpinnerView.swift b/Sources/AlertKit/Icons/AlertSpinnerView.swift new file mode 100644 index 0000000..649aa8a --- /dev/null +++ b/Sources/AlertKit/Icons/AlertSpinnerView.swift @@ -0,0 +1,25 @@ +import UIKit + +class AlertSpinnerView: UIView { + + let activityIndicatorView: UIActivityIndicatorView + + init(style: UIActivityIndicatorView.Style) { + self.activityIndicatorView = UIActivityIndicatorView(style: style) + super.init(frame: .zero) + self.backgroundColor = .clear + addSubview(activityIndicatorView) + activityIndicatorView.startAnimating() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + activityIndicatorView.sizeToFit() + activityIndicatorView.center = .init(x: frame.width / 2, y: frame.height / 2) + } + +} diff --git a/Sources/AlertKit/Views/AlertAppleMusic16View.swift b/Sources/AlertKit/Views/AlertAppleMusic16View.swift new file mode 100644 index 0000000..7b8450f --- /dev/null +++ b/Sources/AlertKit/Views/AlertAppleMusic16View.swift @@ -0,0 +1,315 @@ +import UIKit + +class AlertAppleMusic16View: UIView { + + open var dismissByTap: Bool = true + open var dismissInTime: Bool = true + open var duration: TimeInterval = 1.5 + open var haptic: AlertHaptic? = nil + + fileprivate let titleLabel: UILabel? + fileprivate let subtitleLabel: UILabel? + fileprivate let iconView: UIView? + + fileprivate weak var viewForPresent: UIView? + fileprivate var presentDismissDuration: TimeInterval = 0.2 + fileprivate var presentDismissScale: CGFloat = 0.8 + + open var completion: (() -> Void)? = nil + + 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 + }() + + init(title: String?, subtitle: String?, icon: AlertIcon?) { + + if let title = title { + 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: title, attributes: [.paragraphStyle: style]) + titleLabel = label + } else { + self.titleLabel = nil + } + + if let subtitle = subtitle { + 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: subtitle, attributes: [.paragraphStyle: style]) + subtitleLabel = label + } else { + self.subtitleLabel = nil + } + + if let icon = icon { + let view = icon.createView(lineThick: 9) + self.iconView = view + } else { + self.iconView = nil + } + + if icon == nil { + layout = AlertLayout.message() + } else { + layout = AlertLayout(for: icon ?? .heart) + } + + super.init(frame: .zero) + + preservesSuperviewLayoutMargins = false + insetsLayoutMarginsFromSafeArea = false + + backgroundColor = .clear + addSubview(backgroundView) + + if let titleLabel = self.titleLabel { + addSubview(titleLabel) + } + if let subtitleLabel = self.subtitleLabel { + addSubview(subtitleLabel) + } + if let iconView = self.iconView { + addSubview(iconView) + } + + layoutMargins = layout.margins + + layer.masksToBounds = true + layer.cornerRadius = 8 + layer.cornerCurve = .continuous + + switch icon { + case .spinnerSmall, .spinnerLarge: + dismissInTime = false + dismissByTap = false + default: + dismissInTime = true + dismissByTap = true + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open func present(on view: UIView, completion: @escaping ()->Void = {}) { + + let contentColor = { + 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, *) { + let interfaceStyle = view.traitCollection.userInterfaceStyle + switch interfaceStyle { + case .light: return lightColor + case .dark: return darkColor + case .unspecified: return lightColor + @unknown default: return lightColor + } + } else { + return lightColor + } + }() + + self.titleLabel?.textColor = contentColor + self.subtitleLabel?.textColor = contentColor + self.iconView?.tintColor = contentColor + + self.completion = completion + self.viewForPresent = view + viewForPresent?.addSubview(self) + guard let viewForPresent = viewForPresent else { return } + + alpha = 0 + sizeToFit() + center = .init(x: viewForPresent.frame.midX, y: viewForPresent.frame.midY) + transform = transform.scaledBy(x: self.presentDismissScale, y: self.presentDismissScale) + + if dismissByTap { + let tapGesterRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismiss)) + addGestureRecognizer(tapGesterRecognizer) + } + + // Present + + haptic?.impact() + + UIView.animate(withDuration: presentDismissDuration, animations: { + self.alpha = 1 + self.transform = CGAffineTransform.identity + }, completion: { [weak self] finished in + guard let self = self else { return } + + if let iconView = self.iconView as? AlertIconAnimatable { + iconView.animate() + } + + if self.dismissInTime { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + self.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?() + }) + } + + private let layout: AlertLayout + + override func layoutSubviews() { + super.layoutSubviews() + backgroundView.frame = self.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) + } + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + let width: CGFloat = 250 + self.frame = .init(x: frame.origin.x, y: frame.origin.y, width: width, height: frame.height) + layoutSubviews() + let height = subtitleLabel?.frame.maxY ?? titleLabel?.frame.maxY ?? iconView?.frame.maxY ?? .zero + return .init(width: width, height: height + layoutMargins.bottom) + } + + private class AlertLayout { + + var iconSize: CGSize + var margins: UIEdgeInsets + var spaceBetweenIconAndTitle: CGFloat + + public init(iconSize: CGSize, margins: UIEdgeInsets, spaceBetweenIconAndTitle: CGFloat) { + self.iconSize = iconSize + self.margins = margins + self.spaceBetweenIconAndTitle = spaceBetweenIconAndTitle + } + + convenience init() { + self.init(iconSize: .init(width: 100, height: 100), margins: .init(top: 43, left: 16, bottom: 25, right: 16), spaceBetweenIconAndTitle: 41) + } + + static func message() -> AlertLayout { + let layout = AlertLayout() + layout.margins = UIEdgeInsets(top: 23, left: 16, bottom: 23, right: 16) + return layout + } + + convenience init(for preset: AlertIcon) { + switch preset { + case .done: + self.init( + iconSize: .init( + width: 112, + height: 112 + ), + margins: .init( + top: 63, + left: Self.defaultHorizontalInset, + bottom: 29, + right: Self.defaultHorizontalInset + ), + spaceBetweenIconAndTitle: 35 + ) + case .heart: + self.init( + iconSize: .init( + width: 112, + height: 77 + ), + margins: .init( + top: 49, + left: Self.defaultHorizontalInset, + bottom: 25, + right: Self.defaultHorizontalInset + ), + spaceBetweenIconAndTitle: 35 + ) + case .error: + self.init( + iconSize: .init( + width: 86, + height: 86 + ), + margins: .init( + top: 63, + left: Self.defaultHorizontalInset, + bottom: 29, + right: Self.defaultHorizontalInset + ), + spaceBetweenIconAndTitle: 39 + ) + case .spinnerLarge, .spinnerSmall: + self.init( + iconSize: .init( + width: 16, + height: 16 + ), + margins: .init( + top: 58, + left: Self.defaultHorizontalInset, + bottom: 27, + right: Self.defaultHorizontalInset + ), + spaceBetweenIconAndTitle: 39 + ) + case .custom(_): + self.init( + iconSize: .init( + width: 100, + height: 100 + ), + margins: .init( + top: 43, + left: Self.defaultHorizontalInset, + bottom: 25, + right: Self.defaultHorizontalInset + ), + spaceBetweenIconAndTitle: 35 + ) + } + } + + private static var defaultHorizontalInset: CGFloat { return 16 } + } +} diff --git a/Sources/AlertKit/Views/AlertAppleMusic17View.swift b/Sources/AlertKit/Views/AlertAppleMusic17View.swift new file mode 100644 index 0000000..83677f2 --- /dev/null +++ b/Sources/AlertKit/Views/AlertAppleMusic17View.swift @@ -0,0 +1,266 @@ +import UIKit + +class AlertAppleMusic17View: UIView { + + open var dismissByTap: Bool = true + open var dismissInTime: Bool = true + open var duration: TimeInterval = 1.5 + open var haptic: AlertHaptic? = nil + + fileprivate let titleLabel: UILabel? + fileprivate let subtitleLabel: UILabel? + fileprivate let iconView: UIView? + + fileprivate weak var viewForPresent: UIView? + fileprivate var presentDismissDuration: TimeInterval = 0.2 + fileprivate var presentDismissScale: CGFloat = 0.8 + + open var completion: (() -> Void)? = nil + + 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 + }() + + init(title: String?, subtitle: String?, icon: AlertIcon?) { + + if let title = title { + let label = UILabel() + label.font = UIFont.preferredFont(forTextStyle: .body, weight: .semibold, addPoints: -2) + label.numberOfLines = 0 + let style = NSMutableParagraphStyle() + style.lineSpacing = 3 + style.alignment = .left + label.attributedText = NSAttributedString(string: title, attributes: [.paragraphStyle: style]) + titleLabel = label + } else { + self.titleLabel = nil + } + + if let subtitle = subtitle { + let label = UILabel() + label.font = UIFont.preferredFont(forTextStyle: .footnote) + label.numberOfLines = 0 + let style = NSMutableParagraphStyle() + style.lineSpacing = 2 + style.alignment = .left + label.attributedText = NSAttributedString(string: subtitle, attributes: [.paragraphStyle: style]) + subtitleLabel = label + } else { + self.subtitleLabel = nil + } + + if let icon = icon { + let view = icon.createView(lineThick: 3) + self.iconView = view + } else { + self.iconView = nil + } + + super.init(frame: .zero) + + preservesSuperviewLayoutMargins = false + insetsLayoutMarginsFromSafeArea = false + + backgroundColor = .clear + addSubview(backgroundView) + + if let titleLabel = self.titleLabel { + addSubview(titleLabel) + } + if let subtitleLabel = self.subtitleLabel { + addSubview(subtitleLabel) + } + if let iconView = self.iconView { + addSubview(iconView) + } + + if subtitleLabel == nil { + layoutMargins = .init(top: 17, left: 15, bottom: 17, right: 15 + ((icon == nil) ? .zero : 3)) + } else { + layoutMargins = .init(top: 15, left: 15, bottom: 15, right: 15 + ((icon == nil) ? .zero : 3)) + } + + layer.masksToBounds = true + layer.cornerRadius = 14 + layer.cornerCurve = .continuous + + switch icon { + case .spinnerSmall, .spinnerLarge: + dismissInTime = false + dismissByTap = false + default: + dismissInTime = true + dismissByTap = true + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open func present(on view: UIView, completion: @escaping ()->Void = {}) { + + let contentColor = { + 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, *) { + let interfaceStyle = view.traitCollection.userInterfaceStyle + switch interfaceStyle { + case .light: return lightColor + case .dark: return darkColor + case .unspecified: return lightColor + @unknown default: return lightColor + } + } else { + return lightColor + } + }() + + self.titleLabel?.textColor = contentColor + self.subtitleLabel?.textColor = contentColor + self.iconView?.tintColor = contentColor + + self.completion = completion + self.viewForPresent = view + viewForPresent?.addSubview(self) + guard let viewForPresent = viewForPresent else { return } + + alpha = 0 + sizeToFit() + center.x = viewForPresent.frame.midX + frame.origin.y = viewForPresent.frame.height - viewForPresent.safeAreaInsets.bottom - frame.height - 64 + transform = transform.scaledBy(x: self.presentDismissScale, y: self.presentDismissScale) + + if dismissByTap { + let tapGesterRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismiss)) + addGestureRecognizer(tapGesterRecognizer) + } + + // Present + + haptic?.impact() + + UIView.animate(withDuration: presentDismissDuration, animations: { + self.alpha = 1 + self.transform = CGAffineTransform.identity + }, completion: { [weak self] finished in + guard let self = self else { return } + + if let iconView = self.iconView as? AlertIconAnimatable { + iconView.animate() + } + + if self.dismissInTime { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + self.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?() + }) + } + + override func layoutSubviews() { + super.layoutSubviews() + backgroundView.frame = self.bounds + layout(maxWidth: frame.width) + } + + override func sizeThatFits(_ size: CGSize) -> CGSize { + layout(maxWidth: nil) + + let maxX = subviews.sorted(by: { $0.frame.maxX > $1.frame.maxX }).first?.frame.maxX ?? .zero + let currentNeedWidth = maxX + layoutMargins.right + + let maxWidth = { + if let viewForPresent = self.viewForPresent { + return min(viewForPresent.frame.width * 0.8, 270) + } else { + return 270 + } + }() + + let usingWidth = min(currentNeedWidth, maxWidth) + layout(maxWidth: usingWidth) + let height = subtitleLabel?.frame.maxY ?? titleLabel?.frame.maxY ?? .zero + return .init(width: usingWidth, height: height + layoutMargins.bottom) + } + + private func layout(maxWidth: CGFloat?) { + + let spaceBetweenLabelAndIcon: CGFloat = 12 + let spaceBetweenTitleAndSubtitle: CGFloat = 4 + + if let iconView = self.iconView { + iconView.frame = .init(x: layoutMargins.left, y: .zero, width: 20, height: 20) + let xPosition = iconView.frame.maxX + spaceBetweenLabelAndIcon + if let maxWidth = maxWidth { + let labelWidth = maxWidth - xPosition - layoutMargins.right + titleLabel?.frame = .init( + x: xPosition, + y: layoutMargins.top, + width: labelWidth, + height: titleLabel?.frame.height ?? .zero + ) + titleLabel?.sizeToFit() + subtitleLabel?.frame = .init( + x: xPosition, + y: (titleLabel?.frame.maxY ?? layoutMargins.top) + spaceBetweenTitleAndSubtitle, + width: labelWidth, + height: subtitleLabel?.frame.height ?? .zero + ) + subtitleLabel?.sizeToFit() + } else { + titleLabel?.sizeToFit() + titleLabel?.frame.origin.x = xPosition + titleLabel?.frame.origin.y = layoutMargins.top + subtitleLabel?.sizeToFit() + subtitleLabel?.frame.origin.x = xPosition + subtitleLabel?.frame.origin.y = (titleLabel?.frame.maxY ?? layoutMargins.top) + spaceBetweenTitleAndSubtitle + } + } else { + if let maxWidth = maxWidth { + let labelWidth = maxWidth - layoutMargins.left - layoutMargins.right + titleLabel?.frame = .init( + x: layoutMargins.left, + y: layoutMargins.top, + width: labelWidth, + height: titleLabel?.frame.height ?? .zero + ) + titleLabel?.sizeToFit() + subtitleLabel?.frame = .init( + x: layoutMargins.left, + y: (titleLabel?.frame.maxY ?? layoutMargins.top) + spaceBetweenTitleAndSubtitle, + width: labelWidth, + height: subtitleLabel?.frame.height ?? .zero + ) + subtitleLabel?.sizeToFit() + } else { + titleLabel?.sizeToFit() + titleLabel?.frame.origin.x = layoutMargins.left + titleLabel?.frame.origin.y = layoutMargins.top + subtitleLabel?.sizeToFit() + subtitleLabel?.frame.origin.x = layoutMargins.left + subtitleLabel?.frame.origin.y = (titleLabel?.frame.maxY ?? layoutMargins.top) + spaceBetweenTitleAndSubtitle + } + } + + iconView?.center.y = frame.height / 2 + } +} diff --git a/Sources/SPAlert/Extensions/SwiftUIExtension.swift b/Sources/SPAlert/Extensions/SwiftUIExtension.swift deleted file mode 100644 index f7c119f..0000000 --- a/Sources/SPAlert/Extensions/SwiftUIExtension.swift +++ /dev/null @@ -1,82 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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. - -// Thanks @HonQii for PR! - -import SwiftUI - -@available(iOS 13.0, *) -@available(iOSApplicationExtension, unavailable) -extension View { - - public func SPAlert( - isPresent: Binding, - alertView: SPAlertView, - duration: TimeInterval = 2.0, - haptic: SPAlertHaptic = .none - ) -> some View { - - if isPresent.wrappedValue { - let alertCompletion = alertView.completion - let alertDismiss = { - isPresent.wrappedValue = false - alertCompletion?() - } - alertView.duration = duration - alertView.present(haptic: haptic, completion: alertDismiss) - } - return self - } - - public func SPAlert( - isPresent: Binding, - title: String = "", - message: String? = nil, - duration: TimeInterval = 2.0, - dismissOnTap: Bool = true, - preset: SPAlertIconPreset = .done, - haptic: SPAlertHaptic = .none, - layout: SPAlertLayout? = nil, - completion: (()-> Void)? = nil - ) -> some View { - - let alertView = SPAlertView(title: title, message: message, preset: preset) - alertView.dismissByTap = dismissOnTap - alertView.layout = layout ?? SPAlertLayout(for: preset) - alertView.completion = completion - return SPAlert(isPresent: isPresent, alertView: alertView, duration: duration, haptic: haptic) - } - - public func SPAlert( - isPresent: Binding, - message: String, - duration: TimeInterval = 2.0, - dismissOnTap: Bool = true, - haptic: SPAlertHaptic = .none, - completion: (()-> Void)? = nil - ) -> some View { - - let alertView = SPAlertView(message: message) - alertView.dismissByTap = dismissOnTap - alertView.completion = completion - return SPAlert(isPresent: isPresent, alertView: alertView, duration: duration, haptic: haptic) - } -} diff --git a/Sources/SPAlert/Extensions/UILabelExtension.swift b/Sources/SPAlert/Extensions/UILabelExtension.swift deleted file mode 100644 index 60dc2c2..0000000 --- a/Sources/SPAlert/Extensions/UILabelExtension.swift +++ /dev/null @@ -1,33 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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 - -extension UILabel { - - func layoutDynamicHeight(x: CGFloat, y: CGFloat, width: CGFloat) { - frame = CGRect.init(x: x, y: y, width: width, height: frame.height) - sizeToFit() - if frame.width != width { - frame = .init(x: x, y: y, width: width, height: frame.height) - } - } -} diff --git a/Sources/SPAlert/Models/SPAlertLayout.swift b/Sources/SPAlert/Models/SPAlertLayout.swift deleted file mode 100644 index 8190612..0000000 --- a/Sources/SPAlert/Models/SPAlertLayout.swift +++ /dev/null @@ -1,49 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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: Layout model for view. - */ -open class SPAlertLayout { - - /** - SPAlert: Icon size. - */ - open var iconSize: CGSize - - /** - SPAlert: Alert margings for each side. - */ - open var margins: UIEdgeInsets - - /** - SPAlert: Space between icon and title if both available. - */ - open var spaceBetweenIconAndTitle: CGFloat - - public init(iconSize: CGSize, margins: UIEdgeInsets, spaceBetweenIconAndTitle: CGFloat) { - self.iconSize = iconSize - self.margins = margins - self.spaceBetweenIconAndTitle = spaceBetweenIconAndTitle - } -} diff --git a/Sources/SPAlert/Preset Views/SPAlertIconDoneView.swift b/Sources/SPAlert/Preset Views/SPAlertIconDoneView.swift deleted file mode 100644 index e211e0a..0000000 --- a/Sources/SPAlert/Preset Views/SPAlertIconDoneView.swift +++ /dev/null @@ -1,51 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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 SPAlertIconDoneView: UIView, SPAlertIconAnimatable { - - public func animate() { - let length = frame.width - let animatablePath = UIBezierPath() - animatablePath.move(to: CGPoint(x: length * 0.196, y: length * 0.527)) - animatablePath.addLine(to: CGPoint(x: length * 0.47, y: length * 0.777)) - animatablePath.addLine(to: CGPoint(x: length * 0.99, y: length * 0.25)) - - let animatableLayer = CAShapeLayer() - animatableLayer.path = animatablePath.cgPath - animatableLayer.fillColor = UIColor.clear.cgColor - animatableLayer.strokeColor = tintColor?.cgColor - animatableLayer.lineWidth = 9 - animatableLayer.lineCap = .round - animatableLayer.lineJoin = .round - animatableLayer.strokeEnd = 0 - 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") - } -} diff --git a/Sources/SPAlert/Preset Views/SPAlertSpinnerView.swift b/Sources/SPAlert/Preset Views/SPAlertSpinnerView.swift deleted file mode 100644 index 31b9d38..0000000 --- a/Sources/SPAlert/Preset Views/SPAlertSpinnerView.swift +++ /dev/null @@ -1,51 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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 - -class SPAlertSpinnerView: UIView { - - let activityIndicatorView: UIActivityIndicatorView = { - if #available(iOS 13.0, *) { - return UIActivityIndicatorView(style: .large) - } else { - return UIActivityIndicatorView() - } - }() - - init() { - super.init(frame: .zero) - self.backgroundColor = .clear - addSubview(activityIndicatorView) - activityIndicatorView.startAnimating() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layoutSubviews() { - super.layoutSubviews() - activityIndicatorView.sizeToFit() - activityIndicatorView.center = .init(x: frame.width / 2, y: frame.height / 2) - } - -} diff --git a/Sources/SPAlert/Protocols/SPAlertIconAnimatable.swift b/Sources/SPAlert/Protocols/SPAlertIconAnimatable.swift deleted file mode 100644 index e938437..0000000 --- a/Sources/SPAlert/Protocols/SPAlertIconAnimatable.swift +++ /dev/null @@ -1,33 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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: Protocol for animatable views. - */ -public protocol SPAlertIconAnimatable { - - /** - SPAlert: Shoud call when need start animation. - */ - func animate() -} diff --git a/Sources/SPAlert/SPAlert.swift b/Sources/SPAlert/SPAlert.swift deleted file mode 100644 index c797722..0000000 --- a/Sources/SPAlert/SPAlert.swift +++ /dev/null @@ -1,105 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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: Acess level. Here you get ready-use methods. - Recomended use it. - */ -@available(iOSApplicationExtension, unavailable) -public enum SPAlert { - - /** - SPAlert: Present alert with preset and custom haptic. - - - parameter title: Title text in alert. - - parameter message: Subtitle text in alert. Optional. - - parameter preset: Icon ready-use style or custom image. - - parameter haptic: Haptic response with present. Default is `.success`. - - parameter completion: Will call with dismiss alert. - */ - public static func present(title: String, message: String? = nil, preset: SPAlertIconPreset, haptic: SPAlertHaptic, completion: (() -> Void)? = nil) { - let alertView = SPAlertView(title: title, message: message, preset: preset) - alertView.present(haptic: haptic, completion: completion) - } - - /** - SPAlert: Present alert with preset and automatically detect type haptic. - - - parameter title: Title text in alert. - - parameter message: Subtitle text in alert. Optional. - - parameter preset: Icon ready-use style or custom image. - - parameter completion: Will call with dismiss alert. - */ - public static func present(title: String, message: String? = nil, preset: SPAlertIconPreset, completion: (() -> Void)? = nil) { - let alertView = SPAlertView(title: title, message: message, preset: preset) - alertView.present(haptic: preset.haptic, completion: completion) - } - - /** - SPAlert: Show only message, without title and icon. - - - parameter message: Title text. - - parameter haptic: Haptic response with present. Default is `.success`. - - parameter completion: Will call with dismiss alert. - */ - public static func present(message: String, haptic: SPAlertHaptic, completion: (() -> Void)? = nil) { - let alertView = SPAlertView(message: message) - alertView.present(haptic: haptic, completion: completion) - } - - /** - SPAlert: Show present only. - - - parameter completion: Will call with dismiss alert. - */ - public static func present(preset: SPAlertIconPreset, completion: (() -> Void)? = nil) { - let alertView = SPAlertView(preset: preset) - alertView.present(haptic: preset.haptic, completion: completion) - } - - /** - SPAlert: Dismiss all `SPAlert` views. - */ - public static func dismiss() { - if #available(iOS 13.0, *) { - for scene in UIApplication.shared.connectedScenes { - if let windowScene = scene as? UIWindowScene { - windowScene.windows.forEach { window in - for view in window.subviews { - if let alertView = view as? SPAlertView { - alertView.dismiss() - } - } - } - } - } - } else { - guard let window = UIApplication.shared.keyWindow else { return } - for view in window.subviews { - if let alertView = view as? SPAlertView { - alertView.dismiss() - } - } - } - } -} diff --git a/Sources/SPAlert/SPAlertIconPreset.swift b/Sources/SPAlert/SPAlertIconPreset.swift deleted file mode 100644 index 1230fc0..0000000 --- a/Sources/SPAlert/SPAlertIconPreset.swift +++ /dev/null @@ -1,156 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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: Represent icon wrapper. - Included default styles and can be custom image. - */ -public enum SPAlertIconPreset: Equatable { - - case done - case error - case heart - case spinner - - case custom(_ image: UIImage) -} - -// Get view and haptic by Preset. - -public extension SPAlertIconPreset { - - func createView() -> UIView { - switch self { - case .done: return SPAlertIconDoneView() - case .error: return SPAlertIconErrorView() - case .heart: return SPAlertIconHeartView() - case .spinner: return SPAlertSpinnerView() - case .custom(let image): - let imageView = UIImageView(image: image) - imageView.contentMode = .scaleAspectFit - return imageView - } - } - - var haptic: SPAlertHaptic { - switch self { - case .done: return .success - case .error: return .error - case .heart: return .success - case .spinner: return .none - case .custom(_): return .none - } - } -} - -// Get layout by preset. - -public extension SPAlertLayout { - - convenience init() { - self.init(iconSize: .init(width: 100, height: 100), margins: .init(top: 43, left: 16, bottom: 25, right: 16), spaceBetweenIconAndTitle: 41) - } - - static func message() -> SPAlertLayout { - let layout = SPAlertLayout() - layout.margins = UIEdgeInsets(top: 23, left: 16, bottom: 23, right: 16) - return layout - } - - convenience init(for preset: SPAlertIconPreset) { - switch preset { - case .done: - self.init( - iconSize: .init( - width: 112, - height: 112 - ), - margins: .init( - top: 63, - left: Self.defaultHorizontalInset, - bottom: 29, - right: Self.defaultHorizontalInset - ), - spaceBetweenIconAndTitle: 35 - ) - case .heart: - self.init( - iconSize: .init( - width: 112, - height: 77 - ), - margins: .init( - top: 49, - left: Self.defaultHorizontalInset, - bottom: 25, - right: Self.defaultHorizontalInset - ), - spaceBetweenIconAndTitle: 35 - ) - case .error: - self.init( - iconSize: .init( - width: 86, - height: 86 - ), - margins: .init( - top: 63, - left: Self.defaultHorizontalInset, - bottom: 29, - right: Self.defaultHorizontalInset - ), - spaceBetweenIconAndTitle: 39 - ) - case .spinner: - self.init( - iconSize: .init( - width: 16, - height: 16 - ), - margins: .init( - top: 58, - left: Self.defaultHorizontalInset, - bottom: 27, - right: Self.defaultHorizontalInset - ), - spaceBetweenIconAndTitle: 39 - ) - case .custom(_): - self.init( - iconSize: .init( - width: 100, - height: 100 - ), - margins: .init( - top: 43, - left: Self.defaultHorizontalInset, - bottom: 25, - right: Self.defaultHorizontalInset - ), - spaceBetweenIconAndTitle: 35 - ) - } - } - - private static var defaultHorizontalInset: CGFloat { return 16 } -} diff --git a/Sources/SPAlert/SPAlertView.swift b/Sources/SPAlert/SPAlertView.swift deleted file mode 100644 index 088fb1b..0000000 --- a/Sources/SPAlert/SPAlertView.swift +++ /dev/null @@ -1,302 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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. - */ -@available(iOSApplicationExtension, unavailable) -open class SPAlertView: UIView { - - open var completion: (() -> Void)? = nil - - // MARK: - Properties - - /** - SPAlert: Wrapper of corner radius of alert. - */ - @objc dynamic open var cornerRadius: CGFloat = 8 { - didSet { - layer.cornerRadius = self.cornerRadius - } - } - - /** - SPAlert: Dismiss alert by tap in any place inside. By default is on. - */ - @objc dynamic open var dismissByTap: Bool = true - - /** - SPAlert: Automatically dismiss in time or not. Duration of dismiss can be changed by property `duration`. - */ - @objc dynamic open var dismissInTime: Bool = true - - /** - SPAlert: Duration for showing alert. If `dismissInTime` disabled, this property ignoring. - */ - @objc dynamic open var duration: TimeInterval = 1.5 - - // 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 convenience init(title: String, message: String? = nil, preset: SPAlertIconPreset) { - self.init(preset: preset) - setTitle(title) - if let message = message { - setMessage(message) - } - setIcon(for: preset) - - switch preset { - case .spinner: - dismissInTime = false - dismissByTap = false - default: - dismissInTime = true - dismissByTap = true - } - } - - public init(preset: SPAlertIconPreset) { - super.init(frame: CGRect.zero) - commonInit() - layout = SPAlertLayout(for: preset) - } - - public init(message: String) { - super.init(frame: CGRect.zero) - commonInit() - layout = SPAlertLayout.message() - setMessage(message) - dismissInTime = true - dismissByTap = true - } - - public required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - commonInit() - dismissInTime = true - dismissByTap = true - } - - private func commonInit() { - preservesSuperviewLayoutMargins = false - if #available(iOS 11.0, *) { - insetsLayoutMarginsFromSafeArea = false - } - - layer.masksToBounds = true - backgroundColor = .clear - addSubview(backgroundView) - - setCornerRadius(self.cornerRadius) - } - - // 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]) - label.textColor = defaultContentColor - 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]) - label.textColor = defaultContentColor - subtitleLabel = label - addSubview(label) - } - - private func setIcon(for preset: SPAlertIconPreset) { - let view = preset.createView() - view.tintColor = defaultContentColor - self.iconView = view - addSubview(view) - } - - private func setCornerRadius(_ value: CGFloat) { - layer.cornerRadius = value - } - - // 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(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 - - alpha = 0 - setFrame() - transform = transform.scaledBy(x: self.presentDismissScale, y: self.presentDismissScale) - - if dismissByTap { - let tapGesterRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismiss)) - addGestureRecognizer(tapGesterRecognizer) - } - - // Present - - haptic.impact() - - UIView.animate(withDuration: presentDismissDuration, animations: { - self.alpha = 1 - self.transform = CGAffineTransform.identity - }, completion: { [weak self] finished in - guard let self = self else { return } - - if let iconView = self.iconView as? SPAlertIconAnimatable { - iconView.animate() - } - - if self.dismissInTime { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + self.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) - } - } -} diff --git a/Sources/SPAlert/Services/SPAlertHaptic.swift b/Sources/SPAlert/Services/SPAlertHaptic.swift deleted file mode 100644 index 87f4850..0000000 --- a/Sources/SPAlert/Services/SPAlertHaptic.swift +++ /dev/null @@ -1,47 +0,0 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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: Wrapper of haptic styles. - */ -public enum SPAlertHaptic { - - case success - case warning - case error - case none - - func impact() { - let generator = UINotificationFeedbackGenerator() - switch self { - case .success: - generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.success) - case .warning: - generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.warning) - case .error: - generator.notificationOccurred(UINotificationFeedbackGenerator.FeedbackType.error) - case .none: - break - } - } -} diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 208df05..0000000 --- a/TODO.md +++ /dev/null @@ -1,5 +0,0 @@ -# TODO - -Here provided ideas or features which will be implemented soon. - -- More animatable views. From b4042c14d9904fe5b0580a60e8e55e7a77560cf2 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Fri, 30 Jun 2023 11:36:14 +0300 Subject: [PATCH 02/37] Update README.md --- README.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4bb6f8c..1376494 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **Popup from Apple Music & Feedback in AppStore**. Contains `Done`, `Heart`, `Error` and other. Supports Dark Mode. I tried to recreate Apple's alerts as much as possible. You can find these alerts in the AppStore after feedback and after you add a song to your library in Apple Music. -![Alert Kit v5](https://cdn.sparrowcode.io/github/alertkit/v5/preview-v1.png) +![Alert Kit v5](https://cdn.sparrowcode.io/github/alertkit/v5/preview-v1_1.png) For run alert just call this: @@ -23,8 +23,9 @@ public enum AlertViewStyle { ### Community +English Speacking: +

- English Speacking: @@ -37,9 +38,26 @@ public enum AlertViewStyle { - Russian Speacking:

+Russian Speacking: + +

+ + + + + + + + + + + + +

+ + ## Installation Ready to use on iOS 13+. From 71323613abc1f9b33e51586769d78b38616ff9f2 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Fri, 30 Jun 2023 11:37:28 +0300 Subject: [PATCH 03/37] Update README.md --- README.md | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/README.md b/README.md index 1376494..dd366b8 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ **Popup from Apple Music & Feedback in AppStore**. Contains `Done`, `Heart`, `Error` and other. Supports Dark Mode. I tried to recreate Apple's alerts as much as possible. You can find these alerts in the AppStore after feedback and after you add a song to your library in Apple Music. -![Alert Kit v5](https://cdn.sparrowcode.io/github/alertkit/v5/preview-v1_1.png) +![Alert Kit v5](https://cdn.sparrowcode.io/github/alertkit/v5/preview-v1_2.png) For run alert just call this: @@ -22,8 +22,6 @@ public enum AlertViewStyle { ``` ### Community - -English Speacking:

@@ -40,24 +38,6 @@ English Speacking:

-Russian Speacking: - -

- - - - - - - - - - - - -

- - ## Installation Ready to use on iOS 13+. From 5809a24430d3d97f5d803f144c2a45840d889fc2 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Fri, 30 Jun 2023 11:38:00 +0300 Subject: [PATCH 04/37] Update README.md --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dd366b8..077542d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,12 @@ I tried to recreate Apple's alerts as much as possible. You can find these alert For run alert just call this: ```swift -AlertKitAPI.present(title: "Added to Library", icon: .done, style: .iOS17AppleMusic, haptic: .success) +AlertKitAPI.present( + title: "Added to Library", + icon: .done, + style: .iOS17AppleMusic, + haptic: .success +) ``` Available 2 styles: From d360697957260bd38f8feeb54fb8d77a2ada5a3a Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Fri, 30 Jun 2023 12:23:38 +0300 Subject: [PATCH 05/37] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 077542d..ba352f6 100644 --- a/README.md +++ b/README.md @@ -92,4 +92,4 @@ If you prefer not to use any of dependency managers, you can integrate manually.

-If you use a `SwiftBoost`, add your app via Pull Request. +If you use a `AlertKit`, add your app via Pull Request. From d067e3c34ecc2d679072cd57cbff248669f12d9c Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Sat, 1 Jul 2023 15:06:15 +0200 Subject: [PATCH 06/37] Tests --- Package.swift | 5 +++++ Tests/AlertAppleMusic16ViewTests.swift | 18 ++++++++++++++++++ Tests/AlertAppleMusic17ViewTests.swift | 18 ++++++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 Tests/AlertAppleMusic16ViewTests.swift create mode 100644 Tests/AlertAppleMusic17ViewTests.swift diff --git a/Package.swift b/Package.swift index 9996a56..873a8aa 100644 --- a/Package.swift +++ b/Package.swift @@ -20,6 +20,11 @@ let package = Package( swiftSettings: [ .define("ALERTKIT_SPM") ] + ), + .testTarget( + name: "AlertKitTests", + dependencies: ["AlertKit"], + path: "Tests" ) ], swiftLanguageVersions: [.v5] diff --git a/Tests/AlertAppleMusic16ViewTests.swift b/Tests/AlertAppleMusic16ViewTests.swift new file mode 100644 index 0000000..aaf08f5 --- /dev/null +++ b/Tests/AlertAppleMusic16ViewTests.swift @@ -0,0 +1,18 @@ +import XCTest +@testable import AlertKit + +class AlertAppleMusic16ViewTests: XCTestCase { + + func testCompletion() { + let expectation = expectation(description: "") + + let view = UIView(frame: .init()) + let alert = AlertAppleMusic16View(title: "title", subtitle: "subtitle", icon: .done) + alert.present(on: view) { + expectation.fulfill() + } + alert.dismiss() + + waitForExpectations(timeout: 10, handler: nil) + } +} diff --git a/Tests/AlertAppleMusic17ViewTests.swift b/Tests/AlertAppleMusic17ViewTests.swift new file mode 100644 index 0000000..fad672d --- /dev/null +++ b/Tests/AlertAppleMusic17ViewTests.swift @@ -0,0 +1,18 @@ +import XCTest +@testable import AlertKit + +class AlertAppleMusic17ViewTests: XCTestCase { + + func testCompletion() { + let expectation = expectation(description: "") + + let view = UIView(frame: .init()) + let alert = AlertAppleMusic17View(title: "title", subtitle: "subtitle", icon: .done) + alert.present(on: view) { + expectation.fulfill() + } + alert.dismiss() + + waitForExpectations(timeout: 10, handler: nil) + } +} From 1da133b6f630f4e11d141ce5c05d09b68200278e Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Sat, 1 Jul 2023 23:54:09 +0300 Subject: [PATCH 07/37] Updated access level for view classes. --- SPAlert.podspec | 4 ++-- Sources/AlertKit/Views/AlertAppleMusic16View.swift | 10 +++++----- Sources/AlertKit/Views/AlertAppleMusic17View.swift | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/SPAlert.podspec b/SPAlert.podspec index bd6b44b..dda12ba 100644 --- a/SPAlert.podspec +++ b/SPAlert.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'SPAlert' - s.version = '5.0.0' + s.version = '5.0.1' s.summary = 'Native alert from Apple Music & Feedback. Contains Done, Heart & Message and other presets. Support SwiftUI.' s.homepage = 'https://github.com/sparrowcode/AlertKit' s.source = { :git => 'https://github.com/sparrowcode/AlertKit.git', :tag => s.version } @@ -9,7 +9,7 @@ Pod::Spec.new do |s| s.author = { 'Sparrow Code' => 'hello@sparrowcode.io' } s.swift_version = '5.1' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '13.0' s.source_files = 'Sources/AlertKit/**/*.swift' diff --git a/Sources/AlertKit/Views/AlertAppleMusic16View.swift b/Sources/AlertKit/Views/AlertAppleMusic16View.swift index 7b8450f..5b29abc 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic16View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic16View.swift @@ -1,6 +1,6 @@ import UIKit -class AlertAppleMusic16View: UIView { +public class AlertAppleMusic16View: UIView { open var dismissByTap: Bool = true open var dismissInTime: Bool = true @@ -15,7 +15,7 @@ class AlertAppleMusic16View: UIView { fileprivate var presentDismissDuration: TimeInterval = 0.2 fileprivate var presentDismissScale: CGFloat = 0.8 - open var completion: (() -> Void)? = nil + var completion: (() -> Void)? = nil private lazy var backgroundView: UIVisualEffectView = { let view: UIVisualEffectView = { @@ -29,7 +29,7 @@ class AlertAppleMusic16View: UIView { return view }() - init(title: String?, subtitle: String?, icon: AlertIcon?) { + public init(title: String?, subtitle: String?, icon: AlertIcon?) { if let title = title { let label = UILabel() @@ -179,7 +179,7 @@ class AlertAppleMusic16View: UIView { private let layout: AlertLayout - override func layoutSubviews() { + public override func layoutSubviews() { super.layoutSubviews() backgroundView.frame = self.bounds @@ -205,7 +205,7 @@ class AlertAppleMusic16View: UIView { } } - override func sizeThatFits(_ size: CGSize) -> CGSize { + public override func sizeThatFits(_ size: CGSize) -> CGSize { let width: CGFloat = 250 self.frame = .init(x: frame.origin.x, y: frame.origin.y, width: width, height: frame.height) layoutSubviews() diff --git a/Sources/AlertKit/Views/AlertAppleMusic17View.swift b/Sources/AlertKit/Views/AlertAppleMusic17View.swift index 83677f2..50681ea 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic17View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic17View.swift @@ -1,6 +1,6 @@ import UIKit -class AlertAppleMusic17View: UIView { +public class AlertAppleMusic17View: UIView { open var dismissByTap: Bool = true open var dismissInTime: Bool = true @@ -29,7 +29,7 @@ class AlertAppleMusic17View: UIView { return view }() - init(title: String?, subtitle: String?, icon: AlertIcon?) { + public init(title: String?, subtitle: String?, icon: AlertIcon?) { if let title = title { let label = UILabel() @@ -176,13 +176,13 @@ class AlertAppleMusic17View: UIView { }) } - override func layoutSubviews() { + public override func layoutSubviews() { super.layoutSubviews() backgroundView.frame = self.bounds layout(maxWidth: frame.width) } - override func sizeThatFits(_ size: CGSize) -> CGSize { + public override func sizeThatFits(_ size: CGSize) -> CGSize { layout(maxWidth: nil) let maxX = subviews.sorted(by: { $0.frame.maxX > $1.frame.maxX }).first?.frame.maxX ?? .zero From 443cfeed8b09685f2745ec953c743a102750d457 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Fri, 7 Jul 2023 06:01:18 +0200 Subject: [PATCH 08/37] tvOS --- Package.swift | 3 ++- Sources/AlertKit/AlertHaptic.swift | 2 ++ Sources/AlertKit/Views/AlertAppleMusic16View.swift | 4 ++++ Sources/AlertKit/Views/AlertAppleMusic17View.swift | 4 ++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 873a8aa..8b612f3 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,8 @@ import PackageDescription let package = Package( name: "AlertKit", platforms: [ - .iOS(.v13) + .iOS(.v13), + .tvOS(.v13) ], products: [ .library( diff --git a/Sources/AlertKit/AlertHaptic.swift b/Sources/AlertKit/AlertHaptic.swift index 231b01e..1b1d7d3 100644 --- a/Sources/AlertKit/AlertHaptic.swift +++ b/Sources/AlertKit/AlertHaptic.swift @@ -8,6 +8,7 @@ public enum AlertHaptic { case none func impact() { + #if os(iOS) let generator = UINotificationFeedbackGenerator() switch self { case .success: @@ -19,5 +20,6 @@ public enum AlertHaptic { case .none: break } + #endif } } diff --git a/Sources/AlertKit/Views/AlertAppleMusic16View.swift b/Sources/AlertKit/Views/AlertAppleMusic16View.swift index 5b29abc..2e2d945 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic16View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic16View.swift @@ -19,11 +19,15 @@ public class AlertAppleMusic16View: UIView { private lazy var backgroundView: UIVisualEffectView = { let view: UIVisualEffectView = { + #if !os(tvOS) if #available(iOS 13.0, *) { return UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial)) } else { return UIVisualEffectView(effect: UIBlurEffect(style: .light)) } + #else + return UIVisualEffectView(effect: UIBlurEffect(style: .light)) + #endif }() view.isUserInteractionEnabled = false return view diff --git a/Sources/AlertKit/Views/AlertAppleMusic17View.swift b/Sources/AlertKit/Views/AlertAppleMusic17View.swift index 50681ea..0cda0f3 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic17View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic17View.swift @@ -19,11 +19,15 @@ public class AlertAppleMusic17View: UIView { private lazy var backgroundView: UIVisualEffectView = { let view: UIVisualEffectView = { + #if !os(tvOS) if #available(iOS 13.0, *) { return UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial)) } else { return UIVisualEffectView(effect: UIBlurEffect(style: .light)) } + #else + return UIVisualEffectView(effect: UIBlurEffect(style: .light)) + #endif }() view.isUserInteractionEnabled = false return view From 7d51d4d02a26c2617fcf0ccb75a6028f9d8b3d06 Mon Sep 17 00:00:00 2001 From: Roman Podymov Date: Fri, 7 Jul 2023 06:02:55 +0200 Subject: [PATCH 09/37] CocoaPods --- SPAlert.podspec | 1 + 1 file changed, 1 insertion(+) diff --git a/SPAlert.podspec b/SPAlert.podspec index dda12ba..4eb75c0 100644 --- a/SPAlert.podspec +++ b/SPAlert.podspec @@ -10,6 +10,7 @@ Pod::Spec.new do |s| s.swift_version = '5.1' s.ios.deployment_target = '13.0' + s.tvos.deployment_target = '13.0' s.source_files = 'Sources/AlertKit/**/*.swift' From ebc2d5c4d48daec53f7aee33528ae97cf1075ecd Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Sat, 14 Oct 2023 22:59:53 +0300 Subject: [PATCH 10/37] Allow customisation. Added support SwiftUI. --- README.md | 44 ++++++++++++++++--- SPAlert.podspec | 2 +- Sources/AlertKit/AlertKitAPI.swift | 5 +++ .../Extensions/SwiftUIExtension.swift | 19 ++++++++ .../AlertKit/Extensions/UIFontExtension.swift | 21 --------- .../AlertKit/Icons/AlertIconDoneView.swift | 1 - .../Views/AlertAppleMusic16View.swift | 33 +++++--------- .../Views/AlertAppleMusic17View.swift | 31 +++++-------- .../AlertKit/Views/AlertViewProtocol.swift | 13 ++++++ 9 files changed, 98 insertions(+), 71 deletions(-) create mode 100644 Sources/AlertKit/Extensions/SwiftUIExtension.swift create mode 100644 Sources/AlertKit/Views/AlertViewProtocol.swift diff --git a/README.md b/README.md index ba352f6..998f8cf 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ I tried to recreate Apple's alerts as much as possible. You can find these alert ![Alert Kit v5](https://cdn.sparrowcode.io/github/alertkit/v5/preview-v1_2.png) -For run alert just call this: +For UIKit & SwiftUI call this: ```swift AlertKitAPI.present( @@ -27,7 +27,7 @@ public enum AlertViewStyle { ``` ### Community - +

@@ -35,17 +35,23 @@ public enum AlertViewStyle { - - -

+## Navigate + +- [Installation](#installation) + - [Swift Package Manager](#swift-package-manager) + - [CocoaPods](#cocoapods) +- [SwiftUI](#swiftui) +- [Customisation](#customisation) +- [Apps Using](#apps-using) + ## Installation -Ready to use on iOS 13+. +Ready to use on iOS 13+. Supports iOS and visionOS. Working with `UIKit` and `SwiftUI`. ### Swift Package Manager @@ -59,7 +65,7 @@ or adding it to the `dependencies` of your `Package.swift`: ```swift dependencies: [ - .package(url: "https://github.com/sparrowcode/AlertKit", .upToNextMajor(from: "5.0.0")) + .package(url: "https://github.com/sparrowcode/AlertKit", .upToNextMajor(from: "5.1.0")) ] ``` @@ -80,11 +86,35 @@ pod 'SPAlert' If you prefer not to use any of dependency managers, you can integrate manually. Put `Sources/AlertKit` folder in your Xcode project. Make sure to enable `Copy items if needed` and `Create groups`. +## SwiftUI + +You can use basic way via AlertKitAPI or call via modifier: + +```swift +let alertView = AlertAppleMusic17View(title: "Hello", subtitle: nil, icon: .done) + +VStack {} + .alert(isPresent: $alertPresented, view: alertView) +``` + +## Customisation + +If you need customisation fonts, icon, colors or any other, make view: + +```swift +let alertView = AlertAppleMusic17View(title: "Added to Library", subtitle: nil, icon: .done) +// Change content color +alertView.contentColor = .systemBlue +// Change font +alertView.titleLabel.font = UIFont.systemFont(ofSize: 21) +``` + ## Apps Using

+ diff --git a/SPAlert.podspec b/SPAlert.podspec index 4eb75c0..75773ab 100644 --- a/SPAlert.podspec +++ b/SPAlert.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'SPAlert' - s.version = '5.0.1' + s.version = '5.1.0' s.summary = 'Native alert from Apple Music & Feedback. Contains Done, Heart & Message and other presets. Support SwiftUI.' s.homepage = 'https://github.com/sparrowcode/AlertKit' s.source = { :git => 'https://github.com/sparrowcode/AlertKit.git', :tag => s.version } diff --git a/Sources/AlertKit/AlertKitAPI.swift b/Sources/AlertKit/AlertKitAPI.swift index 6260262..1cd1653 100644 --- a/Sources/AlertKit/AlertKitAPI.swift +++ b/Sources/AlertKit/AlertKitAPI.swift @@ -2,6 +2,11 @@ import UIKit public enum AlertKitAPI { + public static func present(view: AlertViewProtocol, completion: @escaping ()->Void = {}) { + guard let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first else { return } + view.present(on: window, completion: completion) + } + public static func present(title: String? = nil, subtitle: String? = nil, icon: AlertIcon? = nil, style: AlertViewStyle, haptic: AlertHaptic? = nil) { switch style { case .iOS16AppleMusic: diff --git a/Sources/AlertKit/Extensions/SwiftUIExtension.swift b/Sources/AlertKit/Extensions/SwiftUIExtension.swift new file mode 100644 index 0000000..c1a2bb5 --- /dev/null +++ b/Sources/AlertKit/Extensions/SwiftUIExtension.swift @@ -0,0 +1,19 @@ +import SwiftUI + +@available(iOS 13.0, *) +extension View { + + public func alert(isPresent: Binding, view: AlertViewProtocol) -> some View { + if isPresent.wrappedValue { + let alertCompletion = view.completion + let completion = { + isPresent.wrappedValue = false + alertCompletion?() + } + if let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first { + view.present(on: window, completion: completion) + } + } + return self + } +} diff --git a/Sources/AlertKit/Extensions/UIFontExtension.swift b/Sources/AlertKit/Extensions/UIFontExtension.swift index 3848dba..221b5ff 100644 --- a/Sources/AlertKit/Extensions/UIFontExtension.swift +++ b/Sources/AlertKit/Extensions/UIFontExtension.swift @@ -1,24 +1,3 @@ -// The MIT License (MIT) -// Copyright © 2020 Ivan Vorobei (hello@ivanvorobei.io) -// -// 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 extension UIFont { diff --git a/Sources/AlertKit/Icons/AlertIconDoneView.swift b/Sources/AlertKit/Icons/AlertIconDoneView.swift index a8a6386..85ad855 100644 --- a/Sources/AlertKit/Icons/AlertIconDoneView.swift +++ b/Sources/AlertKit/Icons/AlertIconDoneView.swift @@ -24,7 +24,6 @@ public class AlertIconDoneView: UIView, AlertIconAnimatable { animatableLayer.path = animatablePath.cgPath animatableLayer.fillColor = UIColor.clear.cgColor animatableLayer.strokeColor = tintColor?.cgColor - //animatableLayer.lineWidth = 9 animatableLayer.lineWidth = lineThick animatableLayer.lineCap = .round animatableLayer.lineJoin = .round diff --git a/Sources/AlertKit/Views/AlertAppleMusic16View.swift b/Sources/AlertKit/Views/AlertAppleMusic16View.swift index 2e2d945..c5af32b 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic16View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic16View.swift @@ -1,21 +1,28 @@ import UIKit -public class AlertAppleMusic16View: UIView { +public class AlertAppleMusic16View: UIView, AlertViewProtocol { open var dismissByTap: Bool = true open var dismissInTime: Bool = true open var duration: TimeInterval = 1.5 open var haptic: AlertHaptic? = nil - fileprivate let titleLabel: UILabel? - fileprivate let subtitleLabel: UILabel? - fileprivate let iconView: UIView? + public let titleLabel: UILabel? + public let subtitleLabel: UILabel? + public let iconView: UIView? + + public var contentColor = UIColor { trait in + switch trait.userInterfaceStyle { + case .dark: UIColor(red: 127 / 255, green: 127 / 255, blue: 129 / 255, alpha: 1) + default: UIColor(red: 88 / 255, green: 87 / 255, blue: 88 / 255, alpha: 1) + } + } fileprivate weak var viewForPresent: UIView? fileprivate var presentDismissDuration: TimeInterval = 0.2 fileprivate var presentDismissScale: CGFloat = 0.8 - var completion: (() -> Void)? = nil + open var completion: (() -> Void)? = nil private lazy var backgroundView: UIVisualEffectView = { let view: UIVisualEffectView = { @@ -114,22 +121,6 @@ public class AlertAppleMusic16View: UIView { open func present(on view: UIView, completion: @escaping ()->Void = {}) { - let contentColor = { - 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, *) { - let interfaceStyle = view.traitCollection.userInterfaceStyle - switch interfaceStyle { - case .light: return lightColor - case .dark: return darkColor - case .unspecified: return lightColor - @unknown default: return lightColor - } - } else { - return lightColor - } - }() - self.titleLabel?.textColor = contentColor self.subtitleLabel?.textColor = contentColor self.iconView?.tintColor = contentColor diff --git a/Sources/AlertKit/Views/AlertAppleMusic17View.swift b/Sources/AlertKit/Views/AlertAppleMusic17View.swift index 0cda0f3..ddaeec8 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic17View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic17View.swift @@ -1,15 +1,22 @@ import UIKit -public class AlertAppleMusic17View: UIView { +public class AlertAppleMusic17View: UIView, AlertViewProtocol { open var dismissByTap: Bool = true open var dismissInTime: Bool = true open var duration: TimeInterval = 1.5 open var haptic: AlertHaptic? = nil - fileprivate let titleLabel: UILabel? - fileprivate let subtitleLabel: UILabel? - fileprivate let iconView: UIView? + public let titleLabel: UILabel? + public let subtitleLabel: UILabel? + public let iconView: UIView? + + public var contentColor = UIColor { trait in + switch trait.userInterfaceStyle { + case .dark: UIColor(red: 127 / 255, green: 127 / 255, blue: 129 / 255, alpha: 1) + default: UIColor(red: 88 / 255, green: 87 / 255, blue: 88 / 255, alpha: 1) + } + } fileprivate weak var viewForPresent: UIView? fileprivate var presentDismissDuration: TimeInterval = 0.2 @@ -112,22 +119,6 @@ public class AlertAppleMusic17View: UIView { open func present(on view: UIView, completion: @escaping ()->Void = {}) { - let contentColor = { - 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, *) { - let interfaceStyle = view.traitCollection.userInterfaceStyle - switch interfaceStyle { - case .light: return lightColor - case .dark: return darkColor - case .unspecified: return lightColor - @unknown default: return lightColor - } - } else { - return lightColor - } - }() - self.titleLabel?.textColor = contentColor self.subtitleLabel?.textColor = contentColor self.iconView?.tintColor = contentColor diff --git a/Sources/AlertKit/Views/AlertViewProtocol.swift b/Sources/AlertKit/Views/AlertViewProtocol.swift new file mode 100644 index 0000000..e0e0f1a --- /dev/null +++ b/Sources/AlertKit/Views/AlertViewProtocol.swift @@ -0,0 +1,13 @@ +import UIKit + +public protocol AlertViewProtocol { + + func present(on view: UIView, completion: @escaping ()->Void) + + var completion: (() -> Void)? { get set } +} + +extension AlertViewProtocol where Self: UIView { + + func present(on view: UIView, completion: @escaping ()->Void = {}) {} +} From 0bc23a55142cfb541dc9b941c4d1c1d424d93bd4 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Sat, 14 Oct 2023 23:23:01 +0300 Subject: [PATCH 11/37] Drop tvos. --- Package.swift | 3 +-- SPAlert.podspec | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index 8b612f3..873a8aa 100644 --- a/Package.swift +++ b/Package.swift @@ -5,8 +5,7 @@ import PackageDescription let package = Package( name: "AlertKit", platforms: [ - .iOS(.v13), - .tvOS(.v13) + .iOS(.v13) ], products: [ .library( diff --git a/SPAlert.podspec b/SPAlert.podspec index 75773ab..24376ef 100644 --- a/SPAlert.podspec +++ b/SPAlert.podspec @@ -10,7 +10,7 @@ Pod::Spec.new do |s| s.swift_version = '5.1' s.ios.deployment_target = '13.0' - s.tvos.deployment_target = '13.0' + #s.tvos.deployment_target = '13.0' s.source_files = 'Sources/AlertKit/**/*.swift' From 89a90fd7ffc5aa1838ed221128203be0d47dab15 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Sun, 15 Oct 2023 21:07:43 +0300 Subject: [PATCH 12/37] Added support vision os. --- Package.swift | 5 +- README.md | 6 +- Sources/AlertKit/AlertKitAPI.swift | 4 ++ Sources/AlertKit/AlertViewStyle.swift | 5 ++ .../Views/AlertAppleMusic16View.swift | 12 ++-- .../Views/AlertAppleMusic17View.swift | 60 +++++++++++++------ 6 files changed, 63 insertions(+), 29 deletions(-) diff --git a/Package.swift b/Package.swift index 873a8aa..975aa7f 100644 --- a/Package.swift +++ b/Package.swift @@ -1,11 +1,12 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.9 import PackageDescription let package = Package( name: "AlertKit", platforms: [ - .iOS(.v13) + .iOS(.v13), + .visionOS(.v1) ], products: [ .library( diff --git a/README.md b/README.md index 998f8cf..b43c754 100644 --- a/README.md +++ b/README.md @@ -103,10 +103,10 @@ If you need customisation fonts, icon, colors or any other, make view: ```swift let alertView = AlertAppleMusic17View(title: "Added to Library", subtitle: nil, icon: .done) -// Change content color -alertView.contentColor = .systemBlue -// Change font +// Change Font alertView.titleLabel.font = UIFont.systemFont(ofSize: 21) +// Change Color +alertView.titleLabel.textColor = .white ``` ## Apps Using diff --git a/Sources/AlertKit/AlertKitAPI.swift b/Sources/AlertKit/AlertKitAPI.swift index 1cd1653..9ccd8dc 100644 --- a/Sources/AlertKit/AlertKitAPI.swift +++ b/Sources/AlertKit/AlertKitAPI.swift @@ -9,16 +9,20 @@ public enum AlertKitAPI { public static func present(title: String? = nil, subtitle: String? = nil, icon: AlertIcon? = nil, style: AlertViewStyle, haptic: AlertHaptic? = nil) { switch style { + #if os(iOS) case .iOS16AppleMusic: guard let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first else { return } let view = AlertAppleMusic16View(title: title, subtitle: subtitle, icon: icon) view.haptic = haptic view.present(on: window) + #endif + #if os(iOS) || os(visionOS) case .iOS17AppleMusic: guard let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first else { return } let view = AlertAppleMusic17View(title: title, subtitle: subtitle, icon: icon) view.haptic = haptic view.present(on: window) + #endif } } } diff --git a/Sources/AlertKit/AlertViewStyle.swift b/Sources/AlertKit/AlertViewStyle.swift index 895a68e..38f1c95 100644 --- a/Sources/AlertKit/AlertViewStyle.swift +++ b/Sources/AlertKit/AlertViewStyle.swift @@ -2,6 +2,11 @@ import Foundation public enum AlertViewStyle { + #if os(iOS) case iOS16AppleMusic + #endif + + #if os(iOS) || os(visionOS) case iOS17AppleMusic + #endif } diff --git a/Sources/AlertKit/Views/AlertAppleMusic16View.swift b/Sources/AlertKit/Views/AlertAppleMusic16View.swift index c5af32b..83d89d7 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic16View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic16View.swift @@ -1,5 +1,6 @@ import UIKit +@available(iOS 13, *) public class AlertAppleMusic16View: UIView, AlertViewProtocol { open var dismissByTap: Bool = true @@ -11,7 +12,7 @@ public class AlertAppleMusic16View: UIView, AlertViewProtocol { public let subtitleLabel: UILabel? public let iconView: UIView? - public var contentColor = UIColor { trait in + public static var defaultContentColor = UIColor { trait in switch trait.userInterfaceStyle { case .dark: UIColor(red: 127 / 255, green: 127 / 255, blue: 129 / 255, alpha: 1) default: UIColor(red: 88 / 255, green: 87 / 255, blue: 88 / 255, alpha: 1) @@ -81,6 +82,10 @@ public class AlertAppleMusic16View: UIView, AlertViewProtocol { layout = AlertLayout(for: icon ?? .heart) } + self.titleLabel?.textColor = Self.defaultContentColor + self.subtitleLabel?.textColor = Self.defaultContentColor + self.iconView?.tintColor = Self.defaultContentColor + super.init(frame: .zero) preservesSuperviewLayoutMargins = false @@ -120,11 +125,6 @@ public class AlertAppleMusic16View: UIView, AlertViewProtocol { } open func present(on view: UIView, completion: @escaping ()->Void = {}) { - - self.titleLabel?.textColor = contentColor - self.subtitleLabel?.textColor = contentColor - self.iconView?.tintColor = contentColor - self.completion = completion self.viewForPresent = view viewForPresent?.addSubview(self) diff --git a/Sources/AlertKit/Views/AlertAppleMusic17View.swift b/Sources/AlertKit/Views/AlertAppleMusic17View.swift index ddaeec8..6589fa3 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic17View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic17View.swift @@ -1,5 +1,7 @@ import UIKit +import SwiftUI +@available(iOS 13, visionOS 1, *) public class AlertAppleMusic17View: UIView, AlertViewProtocol { open var dismissByTap: Bool = true @@ -11,11 +13,15 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { public let subtitleLabel: UILabel? public let iconView: UIView? - public var contentColor = UIColor { trait in + public static var defaultContentColor = UIColor { trait in + #if os(visionOS) + return .label + #else switch trait.userInterfaceStyle { case .dark: UIColor(red: 127 / 255, green: 127 / 255, blue: 129 / 255, alpha: 1) default: UIColor(red: 88 / 255, green: 87 / 255, blue: 88 / 255, alpha: 1) } + #endif } fileprivate weak var viewForPresent: UIView? @@ -24,20 +30,18 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { open var completion: (() -> Void)? = nil - private lazy var backgroundView: UIVisualEffectView = { - let view: UIVisualEffectView = { - #if !os(tvOS) - if #available(iOS 13.0, *) { - return UIVisualEffectView(effect: UIBlurEffect(style: .systemThickMaterial)) - } else { - return UIVisualEffectView(effect: UIBlurEffect(style: .light)) - } - #else - return UIVisualEffectView(effect: UIBlurEffect(style: .light)) - #endif - }() + private lazy var backgroundView: UIView = { + #if os(visionOS) + let swiftUIView = VisionGlassBackgroundView(cornerRadius: 12) + let host = UIHostingController(rootView: swiftUIView) + let hostView = host.view ?? UIView() + hostView.isUserInteractionEnabled = false + return hostView + #else + let view = UIVisualEffectView(effect: UIBlurEffect()) view.isUserInteractionEnabled = false return view + #endif }() public init(title: String?, subtitle: String?, icon: AlertIcon?) { @@ -75,6 +79,10 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { self.iconView = nil } + self.titleLabel?.textColor = Self.defaultContentColor + self.subtitleLabel?.textColor = Self.defaultContentColor + self.iconView?.tintColor = Self.defaultContentColor + super.init(frame: .zero) preservesSuperviewLayoutMargins = false @@ -89,6 +97,7 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { if let subtitleLabel = self.subtitleLabel { addSubview(subtitleLabel) } + if let iconView = self.iconView { addSubview(iconView) } @@ -118,11 +127,6 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { } open func present(on view: UIView, completion: @escaping ()->Void = {}) { - - self.titleLabel?.textColor = contentColor - self.subtitleLabel?.textColor = contentColor - self.iconView?.tintColor = contentColor - self.completion = completion self.viewForPresent = view viewForPresent?.addSubview(self) @@ -131,7 +135,12 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { alpha = 0 sizeToFit() center.x = viewForPresent.frame.midX + #if os(visionOS) + frame.origin.y = viewForPresent.safeAreaInsets.top + 24 + #elseif os(iOS) frame.origin.y = viewForPresent.frame.height - viewForPresent.safeAreaInsets.bottom - frame.height - 64 + #endif + transform = transform.scaledBy(x: self.presentDismissScale, y: self.presentDismissScale) if dismissByTap { @@ -258,4 +267,19 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { iconView?.center.y = frame.height / 2 } + + #if os(visionOS) + struct VisionGlassBackgroundView: View { + + let cornerRadius: CGFloat + + var body: some View { + ZStack { + Color.clear + } + .glassBackgroundEffect(in: .rect(cornerRadius: cornerRadius)) + .opacity(0.4) + } + } + #endif } From 61dc3521726bee628a76128318be56a3e80db02b Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Sun, 15 Oct 2023 21:08:17 +0300 Subject: [PATCH 13/37] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b43c754..eee1745 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ or adding it to the `dependencies` of your `Package.swift`: ```swift dependencies: [ - .package(url: "https://github.com/sparrowcode/AlertKit", .upToNextMajor(from: "5.1.0")) + .package(url: "https://github.com/sparrowcode/AlertKit", .upToNextMajor(from: "5.1.1")) ] ``` From b0678c8ea3e5f69850fee214b11b031c04576250 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Mon, 16 Oct 2023 10:14:40 +0300 Subject: [PATCH 14/37] Drop tests target. --- Package.swift | 5 ----- Tests/AlertAppleMusic16ViewTests.swift | 18 ------------------ Tests/AlertAppleMusic17ViewTests.swift | 18 ------------------ 3 files changed, 41 deletions(-) delete mode 100644 Tests/AlertAppleMusic16ViewTests.swift delete mode 100644 Tests/AlertAppleMusic17ViewTests.swift diff --git a/Package.swift b/Package.swift index 975aa7f..d212437 100644 --- a/Package.swift +++ b/Package.swift @@ -21,11 +21,6 @@ let package = Package( swiftSettings: [ .define("ALERTKIT_SPM") ] - ), - .testTarget( - name: "AlertKitTests", - dependencies: ["AlertKit"], - path: "Tests" ) ], swiftLanguageVersions: [.v5] diff --git a/Tests/AlertAppleMusic16ViewTests.swift b/Tests/AlertAppleMusic16ViewTests.swift deleted file mode 100644 index aaf08f5..0000000 --- a/Tests/AlertAppleMusic16ViewTests.swift +++ /dev/null @@ -1,18 +0,0 @@ -import XCTest -@testable import AlertKit - -class AlertAppleMusic16ViewTests: XCTestCase { - - func testCompletion() { - let expectation = expectation(description: "") - - let view = UIView(frame: .init()) - let alert = AlertAppleMusic16View(title: "title", subtitle: "subtitle", icon: .done) - alert.present(on: view) { - expectation.fulfill() - } - alert.dismiss() - - waitForExpectations(timeout: 10, handler: nil) - } -} diff --git a/Tests/AlertAppleMusic17ViewTests.swift b/Tests/AlertAppleMusic17ViewTests.swift deleted file mode 100644 index fad672d..0000000 --- a/Tests/AlertAppleMusic17ViewTests.swift +++ /dev/null @@ -1,18 +0,0 @@ -import XCTest -@testable import AlertKit - -class AlertAppleMusic17ViewTests: XCTestCase { - - func testCompletion() { - let expectation = expectation(description: "") - - let view = UIView(frame: .init()) - let alert = AlertAppleMusic17View(title: "title", subtitle: "subtitle", icon: .done) - alert.present(on: view) { - expectation.fulfill() - } - alert.dismiss() - - waitForExpectations(timeout: 10, handler: nil) - } -} From 578c5ce3881808f7ea51a7701b10d415e9d09b68 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Mon, 16 Oct 2023 10:23:04 +0300 Subject: [PATCH 15/37] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index eee1745..65537da 100644 --- a/README.md +++ b/README.md @@ -30,10 +30,13 @@ public enum AlertViewStyle {

- + - + + + + From b45ed39a7a7312b6ff3cf8411abccb80d8ffa4ea Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Mon, 16 Oct 2023 10:24:21 +0300 Subject: [PATCH 16/37] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 65537da..e54c1ed 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,10 @@ public enum AlertViewStyle { - + - + From 1dfe34ab74a45e04e3a6d17915b085ff2ef980d4 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Mon, 16 Oct 2023 10:26:52 +0300 Subject: [PATCH 17/37] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e54c1ed..980e5ef 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,10 @@ public enum AlertViewStyle {

- + + + + From 6cc3ca08d9f8e52a78ef48cc5e07e816fc8e682a Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Mon, 16 Oct 2023 10:29:35 +0300 Subject: [PATCH 18/37] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 980e5ef..e6f3ed1 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,9 @@ public enum AlertViewStyle { + + + From 08f6e0d80bbf037d05e3d4c52a2fd9678219f859 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Mon, 16 Oct 2023 10:30:19 +0300 Subject: [PATCH 19/37] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e6f3ed1..9393e1d 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,13 @@ public enum AlertViewStyle { - - - - + - + + + + From f4205cccd714e1f5ae95e0f2d4af23c387f1b027 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Mon, 16 Oct 2023 10:30:42 +0300 Subject: [PATCH 20/37] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9393e1d..eaf8886 100644 --- a/README.md +++ b/README.md @@ -31,13 +31,13 @@ public enum AlertViewStyle {

- - - + + + From 1a3805c0d891ba315ca9b65dfbbec6aa7951403f Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Mon, 16 Oct 2023 11:21:15 +0300 Subject: [PATCH 21/37] Updated version. --- SPAlert.podspec | 2 +- Sources/AlertKit/Views/AlertAppleMusic16View.swift | 4 ++-- Sources/AlertKit/Views/AlertAppleMusic17View.swift | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/SPAlert.podspec b/SPAlert.podspec index 24376ef..26098b3 100644 --- a/SPAlert.podspec +++ b/SPAlert.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'SPAlert' - s.version = '5.1.0' + s.version = '5.1.2' s.summary = 'Native alert from Apple Music & Feedback. Contains Done, Heart & Message and other presets. Support SwiftUI.' s.homepage = 'https://github.com/sparrowcode/AlertKit' s.source = { :git => 'https://github.com/sparrowcode/AlertKit.git', :tag => s.version } diff --git a/Sources/AlertKit/Views/AlertAppleMusic16View.swift b/Sources/AlertKit/Views/AlertAppleMusic16View.swift index 83d89d7..df83b68 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic16View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic16View.swift @@ -14,8 +14,8 @@ public class AlertAppleMusic16View: UIView, AlertViewProtocol { public static var defaultContentColor = UIColor { trait in switch trait.userInterfaceStyle { - case .dark: UIColor(red: 127 / 255, green: 127 / 255, blue: 129 / 255, alpha: 1) - default: UIColor(red: 88 / 255, green: 87 / 255, blue: 88 / 255, alpha: 1) + case .dark: return UIColor(red: 127 / 255, green: 127 / 255, blue: 129 / 255, alpha: 1) + default: return UIColor(red: 88 / 255, green: 87 / 255, blue: 88 / 255, alpha: 1) } } diff --git a/Sources/AlertKit/Views/AlertAppleMusic17View.swift b/Sources/AlertKit/Views/AlertAppleMusic17View.swift index 6589fa3..94e3dd8 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic17View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic17View.swift @@ -18,8 +18,8 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { return .label #else switch trait.userInterfaceStyle { - case .dark: UIColor(red: 127 / 255, green: 127 / 255, blue: 129 / 255, alpha: 1) - default: UIColor(red: 88 / 255, green: 87 / 255, blue: 88 / 255, alpha: 1) + case .dark: return UIColor(red: 127 / 255, green: 127 / 255, blue: 129 / 255, alpha: 1) + default: return UIColor(red: 88 / 255, green: 87 / 255, blue: 88 / 255, alpha: 1) } #endif } From 62743c17b1e38cfa52965fdcd202dbc0940d4386 Mon Sep 17 00:00:00 2001 From: nikolajjsj <10490273+nikolajjsj@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:59:46 +0200 Subject: [PATCH 22/37] Add Feedster app to apps-using section --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index eaf8886..25c801e 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ For UIKit & SwiftUI call this: ```swift AlertKitAPI.present( - title: "Added to Library", - icon: .done, - style: .iOS17AppleMusic, + title: "Added to Library", + icon: .done, + style: .iOS17AppleMusic, haptic: .success ) ``` @@ -20,7 +20,7 @@ Available 2 styles: ```swift public enum AlertViewStyle { - + case iOS16AppleMusic case iOS17AppleMusic } @@ -52,8 +52,8 @@ public enum AlertViewStyle { ## Navigate - [Installation](#installation) - - [Swift Package Manager](#swift-package-manager) - - [CocoaPods](#cocoapods) + - [Swift Package Manager](#swift-package-manager) + - [CocoaPods](#cocoapods) - [SwiftUI](#swiftui) - [Customisation](#customisation) - [Apps Using](#apps-using) @@ -64,7 +64,7 @@ Ready to use on iOS 13+. Supports iOS and visionOS. Working with `UIKit` and `Sw ### Swift Package Manager -In Xcode go to Project -> Your Project Name -> `Package Dependencies` -> Tap *Plus*. Insert url: +In Xcode go to Project -> Your Project Name -> `Package Dependencies` -> Tap _Plus_. Insert url: ``` https://github.com/sparrowcode/AlertKit @@ -89,6 +89,7 @@ This is an outdated way of doing things. I advise you to use [SPM](#swift-packag ```ruby pod 'SPAlert' ``` + ### Manually @@ -97,7 +98,7 @@ If you prefer not to use any of dependency managers, you can integrate manually. ## SwiftUI -You can use basic way via AlertKitAPI or call via modifier: +You can use basic way via AlertKitAPI or call via modifier: ```swift let alertView = AlertAppleMusic17View(title: "Hello", subtitle: nil, icon: .done) @@ -108,7 +109,7 @@ VStack {} ## Customisation -If you need customisation fonts, icon, colors or any other, make view: +If you need customisation fonts, icon, colors or any other, make view: ```swift let alertView = AlertAppleMusic17View(title: "Added to Library", subtitle: nil, icon: .done) @@ -121,6 +122,7 @@ alertView.titleLabel.textColor = .white ## Apps Using

+ From 30228c31758799cb5f59da3c45e090e78e7282ca Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Tue, 17 Oct 2023 16:07:51 +0300 Subject: [PATCH 23/37] Fixed blur style for iOS17 preset. --- SPAlert.podspec | 2 +- Sources/AlertKit/Views/AlertAppleMusic17View.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/SPAlert.podspec b/SPAlert.podspec index 26098b3..be5fc5d 100644 --- a/SPAlert.podspec +++ b/SPAlert.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'SPAlert' - s.version = '5.1.2' + s.version = '5.1.4' s.summary = 'Native alert from Apple Music & Feedback. Contains Done, Heart & Message and other presets. Support SwiftUI.' s.homepage = 'https://github.com/sparrowcode/AlertKit' s.source = { :git => 'https://github.com/sparrowcode/AlertKit.git', :tag => s.version } diff --git a/Sources/AlertKit/Views/AlertAppleMusic17View.swift b/Sources/AlertKit/Views/AlertAppleMusic17View.swift index 94e3dd8..e6f10a3 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic17View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic17View.swift @@ -38,7 +38,7 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { hostView.isUserInteractionEnabled = false return hostView #else - let view = UIVisualEffectView(effect: UIBlurEffect()) + let view = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial)) view.isUserInteractionEnabled = false return view #endif From 72ac00d3798c506b6befa3cb7f68841fb18ba6aa Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Tue, 17 Oct 2023 16:21:01 +0300 Subject: [PATCH 24/37] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 25c801e..2124ccc 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,6 @@ alertView.titleLabel.textColor = .white ## Apps Using

- @@ -131,6 +130,7 @@ alertView.titleLabel.textColor = .white +

If you use a `AlertKit`, add your app via Pull Request. From 64dd8516754b0665ae4cce543ddf0a830f6ecbf5 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Tue, 17 Oct 2023 16:21:56 +0300 Subject: [PATCH 25/37] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 2124ccc..446a658 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,6 @@ alertView.titleLabel.textColor = .white -

From 8702253d9a46459e8e232ebf613e40664d3b54a8 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Tue, 31 Oct 2023 13:45:15 +0300 Subject: [PATCH 26/37] Added dismiss all alerts method. --- SPAlert.podspec | 2 +- Sources/AlertKit/AlertKitAPI.swift | 10 ++++++++++ Sources/AlertKit/Views/AlertViewProtocol.swift | 1 + 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/SPAlert.podspec b/SPAlert.podspec index be5fc5d..4dbe57f 100644 --- a/SPAlert.podspec +++ b/SPAlert.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'SPAlert' - s.version = '5.1.4' + s.version = '5.1.5' s.summary = 'Native alert from Apple Music & Feedback. Contains Done, Heart & Message and other presets. Support SwiftUI.' s.homepage = 'https://github.com/sparrowcode/AlertKit' s.source = { :git => 'https://github.com/sparrowcode/AlertKit.git', :tag => s.version } diff --git a/Sources/AlertKit/AlertKitAPI.swift b/Sources/AlertKit/AlertKitAPI.swift index 9ccd8dc..022f54f 100644 --- a/Sources/AlertKit/AlertKitAPI.swift +++ b/Sources/AlertKit/AlertKitAPI.swift @@ -25,4 +25,14 @@ public enum AlertKitAPI { #endif } } + + public static func dismissAllAlerts() { + for window in UIApplication.shared.windows { + for view in window.subviews { + if let view = view as? AlertViewProtocol { + view.dismiss() + } + } + } + } } diff --git a/Sources/AlertKit/Views/AlertViewProtocol.swift b/Sources/AlertKit/Views/AlertViewProtocol.swift index e0e0f1a..e4cbecb 100644 --- a/Sources/AlertKit/Views/AlertViewProtocol.swift +++ b/Sources/AlertKit/Views/AlertViewProtocol.swift @@ -3,6 +3,7 @@ import UIKit public protocol AlertViewProtocol { func present(on view: UIView, completion: @escaping ()->Void) + func dismiss() var completion: (() -> Void)? { get set } } From 84a2c557319e7acc8bcad885ce637ffe5ae108c4 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Tue, 31 Oct 2023 13:48:55 +0300 Subject: [PATCH 27/37] Update README.md --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 446a658..8d892b7 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ public enum AlertViewStyle { - [Swift Package Manager](#swift-package-manager) - [CocoaPods](#cocoapods) - [SwiftUI](#swiftui) +- [Present & Dismiss](#present-dismiss) - [Customisation](#customisation) - [Apps Using](#apps-using) @@ -119,6 +120,24 @@ alertView.titleLabel.font = UIFont.systemFont(ofSize: 21) alertView.titleLabel.textColor = .white ``` +## Present & Dismiss + +You can present and dismiss alerts manually via view. + +```swift +let alertView = AlertAppleMusic17View(title: "Added to Library", subtitle: nil, icon: .done) +alertView.present(on: self) + +// and dismiss +alertView.dismiss() +``` + +For dismiss all alerts that was presented: + +```swift +AlertKitAPI.dismissAllAlerts() +``` + ## Apps Using

From 7684bcde1d34092cedb2710bcc967839797fc163 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Tue, 31 Oct 2023 13:49:13 +0300 Subject: [PATCH 28/37] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d892b7..8b88702 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ or adding it to the `dependencies` of your `Package.swift`: ```swift dependencies: [ - .package(url: "https://github.com/sparrowcode/AlertKit", .upToNextMajor(from: "5.1.1")) + .package(url: "https://github.com/sparrowcode/AlertKit", .upToNextMajor(from: "5.1.5")) ] ``` From fa54349114decee56ded9797a7229137b4f3b0d5 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Tue, 31 Oct 2023 14:12:20 +0300 Subject: [PATCH 29/37] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b88702..8540c9e 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ public enum AlertViewStyle { - [Swift Package Manager](#swift-package-manager) - [CocoaPods](#cocoapods) - [SwiftUI](#swiftui) -- [Present & Dismiss](#present-dismiss) +- [Present & Dismiss](#present--dismiss) - [Customisation](#customisation) - [Apps Using](#apps-using) From 6bd8a8aab893afacf28396a0fa12c598ab400742 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Wed, 1 Nov 2023 14:29:07 +0300 Subject: [PATCH 30/37] Updated completions way. --- SPAlert.podspec | 2 +- Sources/AlertKit/AlertKitAPI.swift | 22 +++++++++++++++++-- .../Extensions/SwiftUIExtension.swift | 9 ++++---- .../Views/AlertAppleMusic16View.swift | 14 ++++++------ .../Views/AlertAppleMusic17View.swift | 14 ++++++------ .../AlertKit/Views/AlertViewProtocol.swift | 11 ++-------- 6 files changed, 41 insertions(+), 31 deletions(-) diff --git a/SPAlert.podspec b/SPAlert.podspec index 4dbe57f..a452884 100644 --- a/SPAlert.podspec +++ b/SPAlert.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'SPAlert' - s.version = '5.1.5' + s.version = '5.1.6' s.summary = 'Native alert from Apple Music & Feedback. Contains Done, Heart & Message and other presets. Support SwiftUI.' s.homepage = 'https://github.com/sparrowcode/AlertKit' s.source = { :git => 'https://github.com/sparrowcode/AlertKit.git', :tag => s.version } diff --git a/Sources/AlertKit/AlertKitAPI.swift b/Sources/AlertKit/AlertKitAPI.swift index 022f54f..e6218aa 100644 --- a/Sources/AlertKit/AlertKitAPI.swift +++ b/Sources/AlertKit/AlertKitAPI.swift @@ -26,13 +26,31 @@ public enum AlertKitAPI { } } - public static func dismissAllAlerts() { + public static func dismissAllAlerts(completion: (() -> Void)? = nil) { + + var alertViews: [AlertViewProtocol] = [] + for window in UIApplication.shared.windows { for view in window.subviews { if let view = view as? AlertViewProtocol { - view.dismiss() + alertViews.append(view) } } } + + if alertViews.isEmpty { + completion?() + } else { + for (index, view) in alertViews.enumerated() { + if index == .zero { + view.dismiss(completion: completion) + } else { + view.dismiss(completion: nil) + } + } + alertViews.first?.dismiss { + completion?() + } + } } } diff --git a/Sources/AlertKit/Extensions/SwiftUIExtension.swift b/Sources/AlertKit/Extensions/SwiftUIExtension.swift index c1a2bb5..4f16b58 100644 --- a/Sources/AlertKit/Extensions/SwiftUIExtension.swift +++ b/Sources/AlertKit/Extensions/SwiftUIExtension.swift @@ -3,15 +3,14 @@ import SwiftUI @available(iOS 13.0, *) extension View { - public func alert(isPresent: Binding, view: AlertViewProtocol) -> some View { + public func alert(isPresent: Binding, view: AlertViewProtocol, completion: (()->Void)? = nil) -> some View { if isPresent.wrappedValue { - let alertCompletion = view.completion - let completion = { + let wrapperCompletion = { isPresent.wrappedValue = false - alertCompletion?() + completion?() } if let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first { - view.present(on: window, completion: completion) + view.present(on: window, completion: wrapperCompletion) } } return self diff --git a/Sources/AlertKit/Views/AlertAppleMusic16View.swift b/Sources/AlertKit/Views/AlertAppleMusic16View.swift index df83b68..c5df8be 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic16View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic16View.swift @@ -23,8 +23,6 @@ public class AlertAppleMusic16View: UIView, AlertViewProtocol { fileprivate var presentDismissDuration: TimeInterval = 0.2 fileprivate var presentDismissScale: CGFloat = 0.8 - open var completion: (() -> Void)? = nil - private lazy var backgroundView: UIVisualEffectView = { let view: UIVisualEffectView = { #if !os(tvOS) @@ -124,8 +122,7 @@ public class AlertAppleMusic16View: UIView, AlertViewProtocol { fatalError("init(coder:) has not been implemented") } - open func present(on view: UIView, completion: @escaping ()->Void = {}) { - self.completion = completion + open func present(on view: UIView, completion: (()->Void)? = nil) { self.viewForPresent = view viewForPresent?.addSubview(self) guard let viewForPresent = viewForPresent else { return } @@ -156,19 +153,22 @@ public class AlertAppleMusic16View: UIView, AlertViewProtocol { if self.dismissInTime { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + self.duration) { - self.dismiss() + // If dismiss manually no need call original completion. + if self.alpha != 0 { + self.dismiss(completion: completion) + } } } }) } - @objc open func dismiss() { + @objc open func dismiss(completion: (()->Void)? = nil) { 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?() + completion?() }) } diff --git a/Sources/AlertKit/Views/AlertAppleMusic17View.swift b/Sources/AlertKit/Views/AlertAppleMusic17View.swift index e6f10a3..968052b 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic17View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic17View.swift @@ -28,8 +28,6 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { fileprivate var presentDismissDuration: TimeInterval = 0.2 fileprivate var presentDismissScale: CGFloat = 0.8 - open var completion: (() -> Void)? = nil - private lazy var backgroundView: UIView = { #if os(visionOS) let swiftUIView = VisionGlassBackgroundView(cornerRadius: 12) @@ -126,8 +124,7 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { fatalError("init(coder:) has not been implemented") } - open func present(on view: UIView, completion: @escaping ()->Void = {}) { - self.completion = completion + open func present(on view: UIView, completion: (()->Void)? = nil) { self.viewForPresent = view viewForPresent?.addSubview(self) guard let viewForPresent = viewForPresent else { return } @@ -164,19 +161,22 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { if self.dismissInTime { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + self.duration) { - self.dismiss() + // If dismiss manually no need call original completion. + if self.alpha != 0 { + self.dismiss(completion: completion) + } } } }) } - @objc open func dismiss() { + @objc open func dismiss(completion: (()->Void)? = nil) { 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?() + completion?() }) } diff --git a/Sources/AlertKit/Views/AlertViewProtocol.swift b/Sources/AlertKit/Views/AlertViewProtocol.swift index e4cbecb..19d1e6c 100644 --- a/Sources/AlertKit/Views/AlertViewProtocol.swift +++ b/Sources/AlertKit/Views/AlertViewProtocol.swift @@ -2,13 +2,6 @@ import UIKit public protocol AlertViewProtocol { - func present(on view: UIView, completion: @escaping ()->Void) - func dismiss() - - var completion: (() -> Void)? { get set } -} - -extension AlertViewProtocol where Self: UIView { - - func present(on view: UIView, completion: @escaping ()->Void = {}) {} + func present(on view: UIView, completion: (()->Void)?) + func dismiss(completion: (()->Void)?) } From 3c7acb28451f1b1d2b9d815280ab1276963544e7 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Wed, 1 Nov 2023 18:02:00 +0300 Subject: [PATCH 31/37] Fix layout bug when trigger in any time. --- Sources/AlertKit/Views/AlertAppleMusic16View.swift | 2 ++ Sources/AlertKit/Views/AlertAppleMusic17View.swift | 1 + 2 files changed, 3 insertions(+) diff --git a/Sources/AlertKit/Views/AlertAppleMusic16View.swift b/Sources/AlertKit/Views/AlertAppleMusic16View.swift index c5df8be..9c04ca3 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic16View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic16View.swift @@ -176,6 +176,8 @@ public class AlertAppleMusic16View: UIView, AlertViewProtocol { public override func layoutSubviews() { super.layoutSubviews() + + guard self.transform == .identity else { return } backgroundView.frame = self.bounds if let iconView = self.iconView { diff --git a/Sources/AlertKit/Views/AlertAppleMusic17View.swift b/Sources/AlertKit/Views/AlertAppleMusic17View.swift index 968052b..86d4611 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic17View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic17View.swift @@ -182,6 +182,7 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { public override func layoutSubviews() { super.layoutSubviews() + guard self.transform == .identity else { return } backgroundView.frame = self.bounds layout(maxWidth: frame.width) } From 3d35de60ad26aab58395ebecdd63b533546862ed Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Wed, 8 Nov 2023 18:16:43 +0300 Subject: [PATCH 32/37] Fixed crash when tap for alert. --- README.md | 12 +++++++----- SPAlert.podspec | 2 +- Sources/AlertKit/AlertKitAPI.swift | 16 +++++++++------- .../AlertKit/Views/AlertAppleMusic16View.swift | 13 ++++++++++--- .../AlertKit/Views/AlertAppleMusic17View.swift | 15 +++++++++++---- .../Views/AlertViewInternalDismissProtocol.swift | 6 ++++++ Sources/AlertKit/Views/AlertViewProtocol.swift | 2 +- 7 files changed, 45 insertions(+), 21 deletions(-) create mode 100644 Sources/AlertKit/Views/AlertViewInternalDismissProtocol.swift diff --git a/README.md b/README.md index 8540c9e..287dedb 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ or adding it to the `dependencies` of your `Package.swift`: ```swift dependencies: [ - .package(url: "https://github.com/sparrowcode/AlertKit", .upToNextMajor(from: "5.1.5")) + .package(url: "https://github.com/sparrowcode/AlertKit", .upToNextMajor(from: "5.1.8")) ] ``` @@ -99,7 +99,7 @@ If you prefer not to use any of dependency managers, you can integrate manually. ## SwiftUI -You can use basic way via AlertKitAPI or call via modifier: +You can use basic way via `AlertKitAPI` or call via modifier: ```swift let alertView = AlertAppleMusic17View(title: "Hello", subtitle: nil, icon: .done) @@ -114,9 +114,10 @@ If you need customisation fonts, icon, colors or any other, make view: ```swift let alertView = AlertAppleMusic17View(title: "Added to Library", subtitle: nil, icon: .done) -// Change Font + +// change font alertView.titleLabel.font = UIFont.systemFont(ofSize: 21) -// Change Color +// change color alertView.titleLabel.textColor = .white ``` @@ -126,8 +127,9 @@ You can present and dismiss alerts manually via view. ```swift let alertView = AlertAppleMusic17View(title: "Added to Library", subtitle: nil, icon: .done) -alertView.present(on: self) +// present +alertView.present(on: self) // and dismiss alertView.dismiss() ``` diff --git a/SPAlert.podspec b/SPAlert.podspec index a452884..9378b4e 100644 --- a/SPAlert.podspec +++ b/SPAlert.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'SPAlert' - s.version = '5.1.6' + s.version = '5.1.8' s.summary = 'Native alert from Apple Music & Feedback. Contains Done, Heart & Message and other presets. Support SwiftUI.' s.homepage = 'https://github.com/sparrowcode/AlertKit' s.source = { :git => 'https://github.com/sparrowcode/AlertKit.git', :tag => s.version } diff --git a/Sources/AlertKit/AlertKitAPI.swift b/Sources/AlertKit/AlertKitAPI.swift index e6218aa..52d9da3 100644 --- a/Sources/AlertKit/AlertKitAPI.swift +++ b/Sources/AlertKit/AlertKitAPI.swift @@ -26,13 +26,16 @@ public enum AlertKitAPI { } } + /** + Call only with this one `completion`. Internal ones is canceled. + */ public static func dismissAllAlerts(completion: (() -> Void)? = nil) { - var alertViews: [AlertViewProtocol] = [] + var alertViews: [AlertViewInternalDismissProtocol] = [] for window in UIApplication.shared.windows { for view in window.subviews { - if let view = view as? AlertViewProtocol { + if let view = view as? AlertViewInternalDismissProtocol { alertViews.append(view) } } @@ -43,14 +46,13 @@ public enum AlertKitAPI { } else { for (index, view) in alertViews.enumerated() { if index == .zero { - view.dismiss(completion: completion) + view.dismiss(customCompletion: { + completion?() + }) } else { - view.dismiss(completion: nil) + view.dismiss(customCompletion: nil) } } - alertViews.first?.dismiss { - completion?() - } } } } diff --git a/Sources/AlertKit/Views/AlertAppleMusic16View.swift b/Sources/AlertKit/Views/AlertAppleMusic16View.swift index 9c04ca3..d447d8b 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic16View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic16View.swift @@ -23,6 +23,8 @@ public class AlertAppleMusic16View: UIView, AlertViewProtocol { fileprivate var presentDismissDuration: TimeInterval = 0.2 fileprivate var presentDismissScale: CGFloat = 0.8 + fileprivate var completion: (()->Void)? = nil + private lazy var backgroundView: UIVisualEffectView = { let view: UIVisualEffectView = { #if !os(tvOS) @@ -124,6 +126,7 @@ public class AlertAppleMusic16View: UIView, AlertViewProtocol { open func present(on view: UIView, completion: (()->Void)? = nil) { self.viewForPresent = view + self.completion = completion viewForPresent?.addSubview(self) guard let viewForPresent = viewForPresent else { return } @@ -155,20 +158,24 @@ public class AlertAppleMusic16View: UIView, AlertViewProtocol { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + self.duration) { // If dismiss manually no need call original completion. if self.alpha != 0 { - self.dismiss(completion: completion) + self.dismiss() } } } }) } - @objc open func dismiss(completion: (()->Void)? = nil) { + @objc open func dismiss() { + self.dismiss(customCompletion: self.completion) + } + + func dismiss(customCompletion: (()->Void)? = nil) { 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() - completion?() + customCompletion?() }) } diff --git a/Sources/AlertKit/Views/AlertAppleMusic17View.swift b/Sources/AlertKit/Views/AlertAppleMusic17View.swift index 86d4611..f965198 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic17View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic17View.swift @@ -2,7 +2,7 @@ import UIKit import SwiftUI @available(iOS 13, visionOS 1, *) -public class AlertAppleMusic17View: UIView, AlertViewProtocol { +public class AlertAppleMusic17View: UIView, AlertViewProtocol, AlertViewInternalDismissProtocol { open var dismissByTap: Bool = true open var dismissInTime: Bool = true @@ -28,6 +28,8 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { fileprivate var presentDismissDuration: TimeInterval = 0.2 fileprivate var presentDismissScale: CGFloat = 0.8 + fileprivate var completion: (()->Void)? = nil + private lazy var backgroundView: UIView = { #if os(visionOS) let swiftUIView = VisionGlassBackgroundView(cornerRadius: 12) @@ -126,6 +128,7 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { open func present(on view: UIView, completion: (()->Void)? = nil) { self.viewForPresent = view + self.completion = completion viewForPresent?.addSubview(self) guard let viewForPresent = viewForPresent else { return } @@ -163,20 +166,24 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol { DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + self.duration) { // If dismiss manually no need call original completion. if self.alpha != 0 { - self.dismiss(completion: completion) + self.dismiss() } } } }) } - @objc open func dismiss(completion: (()->Void)? = nil) { + @objc open func dismiss() { + self.dismiss(customCompletion: self.completion) + } + + func dismiss(customCompletion: (()->Void)? = nil) { 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() - completion?() + customCompletion?() }) } diff --git a/Sources/AlertKit/Views/AlertViewInternalDismissProtocol.swift b/Sources/AlertKit/Views/AlertViewInternalDismissProtocol.swift new file mode 100644 index 0000000..86d5052 --- /dev/null +++ b/Sources/AlertKit/Views/AlertViewInternalDismissProtocol.swift @@ -0,0 +1,6 @@ +import UIKit + +protocol AlertViewInternalDismissProtocol { + + func dismiss(customCompletion: (()->Void)?) +} diff --git a/Sources/AlertKit/Views/AlertViewProtocol.swift b/Sources/AlertKit/Views/AlertViewProtocol.swift index 19d1e6c..83b04cd 100644 --- a/Sources/AlertKit/Views/AlertViewProtocol.swift +++ b/Sources/AlertKit/Views/AlertViewProtocol.swift @@ -3,5 +3,5 @@ import UIKit public protocol AlertViewProtocol { func present(on view: UIView, completion: (()->Void)?) - func dismiss(completion: (()->Void)?) + func dismiss() } From 8150d81958e1478adca2fb00e4930aa2a8e2bfdb Mon Sep 17 00:00:00 2001 From: Vladislav Shakhray Date: Tue, 14 Nov 2023 23:26:14 +0000 Subject: [PATCH 33/37] Add Captionista to apps-using --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 287dedb..e423103 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ AlertKitAPI.dismissAllAlerts() +

If you use a `AlertKit`, add your app via Pull Request. From 23e812f8a893202fc4c436193a740a79fd118bd4 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Wed, 15 Nov 2023 17:18:46 +0300 Subject: [PATCH 34/37] Update README.md --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e423103..ab0ac8c 100644 --- a/README.md +++ b/README.md @@ -143,15 +143,15 @@ AlertKitAPI.dismissAllAlerts() ## Apps Using

- - - - - - - - - + + + + + + + + +

If you use a `AlertKit`, add your app via Pull Request. From fc1ce7a53ee2eb3f375f1b05e8d283ecf8e37fe1 Mon Sep 17 00:00:00 2001 From: Ivan Vorobei Date: Thu, 30 Nov 2023 15:28:29 +0300 Subject: [PATCH 35/37] Update README.md --- README.md | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ab0ac8c..d6b6bda 100644 --- a/README.md +++ b/README.md @@ -26,23 +26,11 @@ public enum AlertViewStyle { } ``` -### Community +### iOS Dev Community

- - - - - - - - - - - - - - + + From 448c7f4fed474cfe769fd63f74da35d5539fe12e Mon Sep 17 00:00:00 2001 From: Alpay Calalli Date: Thu, 11 Apr 2024 12:07:15 +0400 Subject: [PATCH 36/37] Made AlertView's init more readable. --- Sources/AlertKit/Views/AlertAppleMusic16View.swift | 2 +- Sources/AlertKit/Views/AlertAppleMusic17View.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/AlertKit/Views/AlertAppleMusic16View.swift b/Sources/AlertKit/Views/AlertAppleMusic16View.swift index d447d8b..b58062e 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic16View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic16View.swift @@ -41,7 +41,7 @@ public class AlertAppleMusic16View: UIView, AlertViewProtocol { return view }() - public init(title: String?, subtitle: String?, icon: AlertIcon?) { + public init(title: String? = nil, subtitle: String? = nil, icon: AlertIcon? = nil) { if let title = title { let label = UILabel() diff --git a/Sources/AlertKit/Views/AlertAppleMusic17View.swift b/Sources/AlertKit/Views/AlertAppleMusic17View.swift index f965198..6215da7 100644 --- a/Sources/AlertKit/Views/AlertAppleMusic17View.swift +++ b/Sources/AlertKit/Views/AlertAppleMusic17View.swift @@ -44,7 +44,7 @@ public class AlertAppleMusic17View: UIView, AlertViewProtocol, AlertViewInternal #endif }() - public init(title: String?, subtitle: String?, icon: AlertIcon?) { + public init(title: String? = nil, subtitle: String? = nil, icon: AlertIcon? = nil) { if let title = title { let label = UILabel() From 95f5d7f5902a3ba6709faad14400ee3a16701423 Mon Sep 17 00:00:00 2001 From: Saurabh prajapati Date: Fri, 9 Aug 2024 22:56:16 +0530 Subject: [PATCH 37/37] Updated README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d6b6bda..31c5a29 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ dependencies: [ This is an outdated way of doing things. I advise you to use [SPM](#swift-package-manager). However, I will continue to support Cocoapods for some time. -

Cocoapods Instalation +
Cocoapods Installation [CocoaPods](https://cocoapods.org) is a dependency manager. For usage and installation instructions, visit their website. To integrate using CocoaPods, specify it in your `Podfile`:

+ English Speacking: @@ -26,203 +37,56 @@ For get alert from switch silent mode, check library [SPIndicator](https://githu + Russian Speacking: