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 85fc719..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/Icon.png and /dev/null differ 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 dcdc098..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt.png and /dev/null differ diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png deleted file mode 100644 index 2528860..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x-1.png and /dev/null differ diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png deleted file mode 100644 index 2528860..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt@2x.png and /dev/null differ diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png deleted file mode 100644 index 9bfe511..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_20pt@3x.png and /dev/null differ 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 6eeab63..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt.png and /dev/null differ diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png deleted file mode 100644 index 5b5611a..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x-1.png and /dev/null differ 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 5b5611a..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt@2x.png and /dev/null differ 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 f3cf00c..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_29pt@3x.png and /dev/null differ diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt.png deleted file mode 100644 index 2528860..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt.png and /dev/null differ diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png deleted file mode 100644 index 2f91efe..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x-1.png and /dev/null differ diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png deleted file mode 100644 index 2f91efe..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt@2x.png and /dev/null differ diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png deleted file mode 100644 index 8dffade..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_40pt@3x.png and /dev/null differ diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png deleted file mode 100644 index 8dffade..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_60pt@2x.png and /dev/null differ diff --git a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png b/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png deleted file mode 100644 index db04124..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_60pt@3x.png and /dev/null differ 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 93ce2c6..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_76pt.png and /dev/null differ 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 fcb1a1f..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_76pt@2x.png and /dev/null differ 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 088c52c..0000000 Binary files a/Example App/iOS Example/App/Assets.xcassets/AppIcon.appiconset/icon_83.5@2x.png and /dev/null differ 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..d212437 100644 --- a/Package.swift +++ b/Package.swift @@ -1,24 +1,25 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.9 import PackageDescription let package = Package( - name: "SPAlert", + name: "AlertKit", platforms: [ - .iOS(.v11) + .iOS(.v13), + .visionOS(.v1) ], 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..31c5a29 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,36 @@ -# 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_2.png) + +For UIKit & SwiftUI call this: + +```swift +AlertKitAPI.present( + title: "Added to Library", + icon: .done, + style: .iOS17AppleMusic, + haptic: .success +) +``` -### Community +Available 2 styles: + +```swift +public enum AlertViewStyle { + + case iOS16AppleMusic + case iOS17AppleMusic +} +``` + +### iOS Dev Community

- - - - - - - - + + @@ -31,198 +40,106 @@ For get alert from switch silent mode, check library [SPIndicator](https://githu ## 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) + - [Swift Package Manager](#swift-package-manager) + - [CocoaPods](#cocoapods) - [SwiftUI](#swiftui) -- [Russian Community](#russian-community) +- [Present & Dismiss](#present--dismiss) +- [Customisation](#customisation) +- [Apps Using](#apps-using) ## Installation -Ready for use on iOS 11+. +Ready to use on iOS 13+. Supports iOS and visionOS. Working with `UIKit` and `SwiftUI`. ### 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.1.8")) ] ``` ### 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 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`: ```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 - -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 -``` +### Manually -In this case, you should dismiss the alert manually. +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`. -### Dismiss +## SwiftUI -If you tap the alert, it will disappear. This can be disabled: +You can use basic way via `AlertKitAPI` or call via modifier: ```swift -alertView.dismissByTap = false -``` - -Also, you can manually dismiss all alerts, simply call this: +let alertView = AlertAppleMusic17View(title: "Hello", subtitle: nil, icon: .done) -```swift -SPAlert.dismiss() +VStack {} + .alert(isPresent: $alertPresented, view: alertView) ``` -### Layout +## Customisation -For customise layout and margins, use `layout` property. You can manage margins for each side, icon size and space between image and titles: +If you need customisation fonts, icon, colors or any other, make view: ```swift -alertView.layout.iconSize = .init(width: 24, height: 24) -alertView.layout.margins.top = 12 -alertView.layout.spaceBetweenIconAndTitle = 8 -``` - -### Haptic +let alertView = AlertAppleMusic17View(title: "Added to Library", subtitle: nil, icon: .done) -To manage haptic, you should pass it in present method: - -```swift -alertView.present(duration: 1.5, haptic: .success, completion: nil) +// change font +alertView.titleLabel.font = UIFont.systemFont(ofSize: 21) +// change color +alertView.titleLabel.textColor = .white ``` -You can remove duration and completion, because they have default values. - -### Spinner +## Present & Dismiss -I added the preset `.spinner`, to use it simply call this: +You can present and dismiss alerts manually via view. ```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: +let alertView = AlertAppleMusic17View(title: "Added to Library", subtitle: nil, icon: .done) -```swift -// For one alert +// present +alertView.present(on: self) +// and dismiss 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: +For dismiss all alerts that was presented: ```swift -SPAlertView.appearance().duration = 2 -SPAlertView.appearance().cornerRadius = 12 +AlertKitAPI.dismissAllAlerts() ``` -It will apply to all alerts. I recommend setting it in the app delegate, but you can change it in runtime. +## Apps Using -## 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 `AlertKit`, add your app via Pull Request. diff --git a/SPAlert.podspec b/SPAlert.podspec index f5a08e5..9378b4e 100644 --- a/SPAlert.podspec +++ b/SPAlert.podspec @@ -1,16 +1,17 @@ Pod::Spec.new do |s| s.name = 'SPAlert' - s.version = '4.2.0' + 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/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.ios.deployment_target = '13.0' + #s.tvos.deployment_target = '13.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..1b1d7d3 --- /dev/null +++ b/Sources/AlertKit/AlertHaptic.swift @@ -0,0 +1,25 @@ +import UIKit + +public enum AlertHaptic { + + case success + case warning + case error + case none + + func impact() { + #if os(iOS) + 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 + } + #endif + } +} 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..52d9da3 --- /dev/null +++ b/Sources/AlertKit/AlertKitAPI.swift @@ -0,0 +1,58 @@ +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 { + #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 + } + } + + /** + Call only with this one `completion`. Internal ones is canceled. + */ + public static func dismissAllAlerts(completion: (() -> Void)? = nil) { + + var alertViews: [AlertViewInternalDismissProtocol] = [] + + for window in UIApplication.shared.windows { + for view in window.subviews { + if let view = view as? AlertViewInternalDismissProtocol { + alertViews.append(view) + } + } + } + + if alertViews.isEmpty { + completion?() + } else { + for (index, view) in alertViews.enumerated() { + if index == .zero { + view.dismiss(customCompletion: { + completion?() + }) + } else { + view.dismiss(customCompletion: nil) + } + } + } + } +} diff --git a/Sources/AlertKit/AlertViewStyle.swift b/Sources/AlertKit/AlertViewStyle.swift new file mode 100644 index 0000000..38f1c95 --- /dev/null +++ b/Sources/AlertKit/AlertViewStyle.swift @@ -0,0 +1,12 @@ +import Foundation + +public enum AlertViewStyle { + + #if os(iOS) + case iOS16AppleMusic + #endif + + #if os(iOS) || os(visionOS) + case iOS17AppleMusic + #endif +} diff --git a/Sources/AlertKit/Extensions/SwiftUIExtension.swift b/Sources/AlertKit/Extensions/SwiftUIExtension.swift new file mode 100644 index 0000000..4f16b58 --- /dev/null +++ b/Sources/AlertKit/Extensions/SwiftUIExtension.swift @@ -0,0 +1,18 @@ +import SwiftUI + +@available(iOS 13.0, *) +extension View { + + public func alert(isPresent: Binding, view: AlertViewProtocol, completion: (()->Void)? = nil) -> some View { + if isPresent.wrappedValue { + let wrapperCompletion = { + isPresent.wrappedValue = false + completion?() + } + if let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first { + view.present(on: window, completion: wrapperCompletion) + } + } + return self + } +} diff --git a/Sources/AlertKit/Extensions/UIFontExtension.swift b/Sources/AlertKit/Extensions/UIFontExtension.swift new file mode 100644 index 0000000..221b5ff --- /dev/null +++ b/Sources/AlertKit/Extensions/UIFontExtension.swift @@ -0,0 +1,11 @@ +import UIKit + +extension UIFont { + + static func preferredFont(forTextStyle style: TextStyle, weight: Weight, addPoints: CGFloat = 0) -> UIFont { + let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style) + let font = UIFont.systemFont(ofSize: descriptor.pointSize + addPoints, weight: weight) + let metrics = UIFontMetrics(forTextStyle: style) + return metrics.scaledFont(for: font) + } +} 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..85ad855 --- /dev/null +++ b/Sources/AlertKit/Icons/AlertIconDoneView.swift @@ -0,0 +1,41 @@ +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 = 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..b58062e --- /dev/null +++ b/Sources/AlertKit/Views/AlertAppleMusic16View.swift @@ -0,0 +1,319 @@ +import UIKit + +@available(iOS 13, *) +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 + + public let titleLabel: UILabel? + public let subtitleLabel: UILabel? + public let iconView: UIView? + + public static var defaultContentColor = UIColor { trait in + switch trait.userInterfaceStyle { + 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) + } + } + + fileprivate weak var viewForPresent: UIView? + 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) + 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 + }() + + public init(title: String? = nil, subtitle: String? = nil, icon: AlertIcon? = nil) { + + 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) + } + + self.titleLabel?.textColor = Self.defaultContentColor + self.subtitleLabel?.textColor = Self.defaultContentColor + self.iconView?.tintColor = Self.defaultContentColor + + 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: (()->Void)? = nil) { + self.viewForPresent = view + self.completion = completion + 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) { + // If dismiss manually no need call original completion. + if self.alpha != 0 { + self.dismiss() + } + } + } + }) + } + + @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() + customCompletion?() + }) + } + + private let layout: AlertLayout + + public override func layoutSubviews() { + super.layoutSubviews() + + guard self.transform == .identity else { return } + 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) + } + } + + 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() + 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..6215da7 --- /dev/null +++ b/Sources/AlertKit/Views/AlertAppleMusic17View.swift @@ -0,0 +1,293 @@ +import UIKit +import SwiftUI + +@available(iOS 13, visionOS 1, *) +public class AlertAppleMusic17View: UIView, AlertViewProtocol, AlertViewInternalDismissProtocol { + + open var dismissByTap: Bool = true + open var dismissInTime: Bool = true + open var duration: TimeInterval = 1.5 + open var haptic: AlertHaptic? = nil + + public let titleLabel: UILabel? + public let subtitleLabel: UILabel? + public let iconView: UIView? + + public static var defaultContentColor = UIColor { trait in + #if os(visionOS) + return .label + #else + switch trait.userInterfaceStyle { + 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 + } + + fileprivate weak var viewForPresent: UIView? + 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) + let host = UIHostingController(rootView: swiftUIView) + let hostView = host.view ?? UIView() + hostView.isUserInteractionEnabled = false + return hostView + #else + let view = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial)) + view.isUserInteractionEnabled = false + return view + #endif + }() + + public init(title: String? = nil, subtitle: String? = nil, icon: AlertIcon? = nil) { + + 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 + } + + self.titleLabel?.textColor = Self.defaultContentColor + self.subtitleLabel?.textColor = Self.defaultContentColor + self.iconView?.tintColor = Self.defaultContentColor + + 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: (()->Void)? = nil) { + self.viewForPresent = view + self.completion = completion + viewForPresent?.addSubview(self) + guard let viewForPresent = viewForPresent else { return } + + 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 { + 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) { + // If dismiss manually no need call original completion. + if self.alpha != 0 { + self.dismiss() + } + } + } + }) + } + + @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() + customCompletion?() + }) + } + + public override func layoutSubviews() { + super.layoutSubviews() + guard self.transform == .identity else { return } + backgroundView.frame = self.bounds + layout(maxWidth: frame.width) + } + + 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 + 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 + } + + #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 +} 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 new file mode 100644 index 0000000..83b04cd --- /dev/null +++ b/Sources/AlertKit/Views/AlertViewProtocol.swift @@ -0,0 +1,7 @@ +import UIKit + +public protocol AlertViewProtocol { + + func present(on view: UIView, completion: (()->Void)?) + func dismiss() +} 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/UIFontExtension.swift b/Sources/SPAlert/Extensions/UIFontExtension.swift deleted file mode 100644 index 3848dba..0000000 --- a/Sources/SPAlert/Extensions/UIFontExtension.swift +++ /dev/null @@ -1,32 +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 UIFont { - - static func preferredFont(forTextStyle style: TextStyle, weight: Weight, addPoints: CGFloat = 0) -> UIFont { - let descriptor = UIFontDescriptor.preferredFontDescriptor(withTextStyle: style) - let font = UIFont.systemFont(ofSize: descriptor.pointSize + addPoints, weight: weight) - let metrics = UIFontMetrics(forTextStyle: style) - return metrics.scaledFont(for: font) - } -} 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.