diff --git a/Sample/Podfile b/Sample/Podfile index e7ec42f..e9d1fa3 100644 --- a/Sample/Podfile +++ b/Sample/Podfile @@ -6,5 +6,6 @@ target 'ShowCaseSample' do use_frameworks! # Pods for ShowCaseSample + pod 'BubbleShowCase' end diff --git a/Sample/Podfile.lock b/Sample/Podfile.lock index 24f6519..edd8dcb 100644 --- a/Sample/Podfile.lock +++ b/Sample/Podfile.lock @@ -1,3 +1,16 @@ -PODFILE CHECKSUM: 19f2f21b76b5f27733662b896c0465d1d2a543e3 +PODS: + - BubbleShowCase (0.0.1) + +DEPENDENCIES: + - BubbleShowCase + +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - BubbleShowCase + +SPEC CHECKSUMS: + BubbleShowCase: 29c92e692b7b6ea1b54063efcbe1fa0157098436 + +PODFILE CHECKSUM: c9f2160adbf20d26d642865800eac30cde2a746c COCOAPODS: 1.5.3 diff --git a/Sample/Pods/BubbleShowCase/LICENSE b/Sample/Pods/BubbleShowCase/LICENSE new file mode 100644 index 0000000..6831d20 --- /dev/null +++ b/Sample/Pods/BubbleShowCase/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Datos + +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. diff --git a/Sample/Pods/BubbleShowCase/README.md b/Sample/Pods/BubbleShowCase/README.md new file mode 100644 index 0000000..3c197f1 --- /dev/null +++ b/Sample/Pods/BubbleShowCase/README.md @@ -0,0 +1,2 @@ +# ShowCase-iOS +ShowCase is a framework that provides with a view that you can display to help your users understand your App features diff --git a/ShowCase/ShowCase/ShowCase.swift b/Sample/Pods/BubbleShowCase/ShowCase/ShowCase/ShowCase.swift similarity index 100% rename from ShowCase/ShowCase/ShowCase.swift rename to Sample/Pods/BubbleShowCase/ShowCase/ShowCase/ShowCase.swift diff --git a/Sample/Pods/Manifest.lock b/Sample/Pods/Manifest.lock index 24f6519..edd8dcb 100644 --- a/Sample/Pods/Manifest.lock +++ b/Sample/Pods/Manifest.lock @@ -1,3 +1,16 @@ -PODFILE CHECKSUM: 19f2f21b76b5f27733662b896c0465d1d2a543e3 +PODS: + - BubbleShowCase (0.0.1) + +DEPENDENCIES: + - BubbleShowCase + +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - BubbleShowCase + +SPEC CHECKSUMS: + BubbleShowCase: 29c92e692b7b6ea1b54063efcbe1fa0157098436 + +PODFILE CHECKSUM: c9f2160adbf20d26d642865800eac30cde2a746c COCOAPODS: 1.5.3 diff --git a/Sample/Pods/Pods.xcodeproj/project.pbxproj b/Sample/Pods/Pods.xcodeproj/project.pbxproj index 8d5e1b8..97ba2fc 100644 --- a/Sample/Pods/Pods.xcodeproj/project.pbxproj +++ b/Sample/Pods/Pods.xcodeproj/project.pbxproj @@ -7,47 +7,69 @@ objects = { /* Begin PBXBuildFile section */ - 2A8275948DCC0CC0879FF9AED3DC7680 /* Pods-ShowCaseSample-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 126898D2985B10CD2726D97AF4877915 /* Pods-ShowCaseSample-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 8507E6B1F43945DA23E18760419E59BA /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A16F4CFC63FAC439D7A04994F579A03 /* Foundation.framework */; }; - 8A0F2753D526793BE4B4ADCDA6610594 /* Pods-ShowCaseSample-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = C72446B27E280D9AC95E741F64F18424 /* Pods-ShowCaseSample-dummy.m */; }; + 338F0B4D49590C1476D5F92962475AA8 /* Pods-ShowCaseSample-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = C72446B27E280D9AC95E741F64F18424 /* Pods-ShowCaseSample-dummy.m */; }; + 4FC20C1EBB848818306E7C705A778830 /* BubbleShowCase-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = DC5E6F0A80081EF43F05673F8DCE61F8 /* BubbleShowCase-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 615B114320A6888A665D1B7A2FAA40CD /* BubbleShowCase-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = FC8E95BCCBABFEE769D6068B518E2D99 /* BubbleShowCase-dummy.m */; }; + 9A27E279EB33AB1308D2BAA3C5FAF7B7 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A16F4CFC63FAC439D7A04994F579A03 /* Foundation.framework */; }; + 9CC069756574A559954F1C8A11EF858B /* Pods-ShowCaseSample-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 126898D2985B10CD2726D97AF4877915 /* Pods-ShowCaseSample-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B1ADE415EB50F361C545901CEF77A17F /* ShowCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C719E0F5C1958D4F76B39FDFE874B92 /* ShowCase.swift */; }; + CACDE44CB372F74D094FE9B028C1C91F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5A16F4CFC63FAC439D7A04994F579A03 /* Foundation.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + BCBCA9054A9C0C3796DC7F95D953A0E5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D41D8CD98F00B204E9800998ECF8427E /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7AD4E529A5EB52D89623D6AC30559784; + remoteInfo = BubbleShowCase; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ 06CB474FD09D0642F587C10F8F1EDEC2 /* Pods-ShowCaseSample-resources.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-ShowCaseSample-resources.sh"; sourceTree = ""; }; 126898D2985B10CD2726D97AF4877915 /* Pods-ShowCaseSample-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-ShowCaseSample-umbrella.h"; sourceTree = ""; }; 1372A7A0726B83DC5ACDD6B44BA75919 /* Pods-ShowCaseSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ShowCaseSample.debug.xcconfig"; sourceTree = ""; }; + 41DDEE0C6750415C1CFFDC66C4E618E0 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49E03F3A261F217373CA84A6B1C39367 /* Pods-ShowCaseSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-ShowCaseSample.release.xcconfig"; sourceTree = ""; }; + 4CE65D5CF3454C45CA799A425E40C417 /* BubbleShowCase.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = BubbleShowCase.xcconfig; sourceTree = ""; }; + 52A96A16BF1568D37D5F7CF0B53C90FA /* BubbleShowCase-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "BubbleShowCase-prefix.pch"; sourceTree = ""; }; 5A16F4CFC63FAC439D7A04994F579A03 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.3.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; + 6A4E2E67559DE2AF5BF37CBB20BA2E7C /* BubbleShowCase.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = BubbleShowCase.modulemap; sourceTree = ""; }; + 787239B4E878DE0CFC11D5F59E37596B /* BubbleShowCase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = BubbleShowCase.framework; path = BubbleShowCase.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8AC4471BAC5432FE6893FFA5ACCA2B38 /* Pods-ShowCaseSample-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-ShowCaseSample-acknowledgements.markdown"; sourceTree = ""; }; 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; + 9C719E0F5C1958D4F76B39FDFE874B92 /* ShowCase.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ShowCase.swift; path = ShowCase/ShowCase/ShowCase.swift; sourceTree = ""; }; + A3F70133F24D6C52660CD7751DC89378 /* Pods_ShowCaseSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_ShowCaseSample.framework; path = "Pods-ShowCaseSample.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; B126BB976CAB85C61799E299D4A54AE5 /* Pods-ShowCaseSample-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-ShowCaseSample-frameworks.sh"; sourceTree = ""; }; C0C8E5B1A4047A16109554A7A511765B /* Pods-ShowCaseSample-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-ShowCaseSample-acknowledgements.plist"; sourceTree = ""; }; C72446B27E280D9AC95E741F64F18424 /* Pods-ShowCaseSample-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-ShowCaseSample-dummy.m"; sourceTree = ""; }; - C8A22EF7A9F57E38722ABDD735BA32EF /* Pods_ShowCaseSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Pods_ShowCaseSample.framework; path = "Pods-ShowCaseSample.framework"; sourceTree = BUILT_PRODUCTS_DIR; }; C9CBEA23C25DDFCC02284DF431343245 /* Pods-ShowCaseSample.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-ShowCaseSample.modulemap"; sourceTree = ""; }; + DC5E6F0A80081EF43F05673F8DCE61F8 /* BubbleShowCase-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "BubbleShowCase-umbrella.h"; sourceTree = ""; }; DCFD2A13ABA3844E66BE2226DFFB9177 /* Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + FC8E95BCCBABFEE769D6068B518E2D99 /* BubbleShowCase-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "BubbleShowCase-dummy.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - F2F3662E3D5F11C1E7B43B8E5A1A7605 /* Frameworks */ = { + 057753F96DFE102039E82A3F98DF729F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + CACDE44CB372F74D094FE9B028C1C91F /* Foundation.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 94A306E7A6DFD5BB1B6F2AA20D5DBF88 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8507E6B1F43945DA23E18760419E59BA /* Foundation.framework in Frameworks */, + 9A27E279EB33AB1308D2BAA3C5FAF7B7 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 38C3406BA47BD3F9624F268D12A1F91C /* Products */ = { - isa = PBXGroup; - children = ( - C8A22EF7A9F57E38722ABDD735BA32EF /* Pods_ShowCaseSample.framework */, - ); - name = Products; - sourceTree = ""; - }; 3ABF5818CD98C5BB8A26B2FF9FBAE2B6 /* Targets Support Files */ = { isa = PBXGroup; children = ( @@ -69,7 +91,8 @@ children = ( 93A4A3777CF96A4AAC1D13BA6DCCEA73 /* Podfile */, BC3CA7F9E30CC8F7E2DD044DD34432FC /* Frameworks */, - 38C3406BA47BD3F9624F268D12A1F91C /* Products */, + F52F1084FD78FE1B8C8D5A78C1E458F5 /* Pods */, + F511823538FD00C048696D2A7A6A7732 /* Products */, 3ABF5818CD98C5BB8A26B2FF9FBAE2B6 /* Targets Support Files */, ); sourceTree = ""; @@ -100,35 +123,102 @@ name = Frameworks; sourceTree = ""; }; + C53650D6074D6EEFFB8DB67136AEBAA8 /* Support Files */ = { + isa = PBXGroup; + children = ( + 6A4E2E67559DE2AF5BF37CBB20BA2E7C /* BubbleShowCase.modulemap */, + 4CE65D5CF3454C45CA799A425E40C417 /* BubbleShowCase.xcconfig */, + FC8E95BCCBABFEE769D6068B518E2D99 /* BubbleShowCase-dummy.m */, + 52A96A16BF1568D37D5F7CF0B53C90FA /* BubbleShowCase-prefix.pch */, + DC5E6F0A80081EF43F05673F8DCE61F8 /* BubbleShowCase-umbrella.h */, + 41DDEE0C6750415C1CFFDC66C4E618E0 /* Info.plist */, + ); + name = "Support Files"; + path = "../Target Support Files/BubbleShowCase"; + sourceTree = ""; + }; + F511823538FD00C048696D2A7A6A7732 /* Products */ = { + isa = PBXGroup; + children = ( + 787239B4E878DE0CFC11D5F59E37596B /* BubbleShowCase.framework */, + A3F70133F24D6C52660CD7751DC89378 /* Pods_ShowCaseSample.framework */, + ); + name = Products; + sourceTree = ""; + }; + F52F1084FD78FE1B8C8D5A78C1E458F5 /* Pods */ = { + isa = PBXGroup; + children = ( + FF90FDD03366CE4496E08A2C6B01C27E /* BubbleShowCase */, + ); + name = Pods; + sourceTree = ""; + }; + FF90FDD03366CE4496E08A2C6B01C27E /* BubbleShowCase */ = { + isa = PBXGroup; + children = ( + 9C719E0F5C1958D4F76B39FDFE874B92 /* ShowCase.swift */, + C53650D6074D6EEFFB8DB67136AEBAA8 /* Support Files */, + ); + name = BubbleShowCase; + path = BubbleShowCase; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ - 9B230D7C565314FAE56AB0BF34AFAF7B /* Headers */ = { + 0B08A03DBA1BCDC078A2FD118BDA16EF /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 9CC069756574A559954F1C8A11EF858B /* Pods-ShowCaseSample-umbrella.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 8B33B4BB7A8743CE4631E54A26440B94 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 2A8275948DCC0CC0879FF9AED3DC7680 /* Pods-ShowCaseSample-umbrella.h in Headers */, + 4FC20C1EBB848818306E7C705A778830 /* BubbleShowCase-umbrella.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ - A6FB77729702F1A849A08A3BEDFB8D05 /* Pods-ShowCaseSample */ = { + 63D7931B9806FE5F105A63790F4F1CED /* Pods-ShowCaseSample */ = { isa = PBXNativeTarget; - buildConfigurationList = 4DFE4007EBDD28D5A8AFDDD30F5174A5 /* Build configuration list for PBXNativeTarget "Pods-ShowCaseSample" */; + buildConfigurationList = DFD18AA4B416011B26D7D6FF222D2248 /* Build configuration list for PBXNativeTarget "Pods-ShowCaseSample" */; buildPhases = ( - FB7FD74F30D01F36D2094834AF50BCCF /* Sources */, - F2F3662E3D5F11C1E7B43B8E5A1A7605 /* Frameworks */, - 9B230D7C565314FAE56AB0BF34AFAF7B /* Headers */, + FA160C44BBB84D6B3DBE242D52A30699 /* Sources */, + 94A306E7A6DFD5BB1B6F2AA20D5DBF88 /* Frameworks */, + 0B08A03DBA1BCDC078A2FD118BDA16EF /* Headers */, ); buildRules = ( ); dependencies = ( + 83DBFC257E82E058E995994671015E6A /* PBXTargetDependency */, ); name = "Pods-ShowCaseSample"; productName = "Pods-ShowCaseSample"; - productReference = C8A22EF7A9F57E38722ABDD735BA32EF /* Pods_ShowCaseSample.framework */; + productReference = A3F70133F24D6C52660CD7751DC89378 /* Pods_ShowCaseSample.framework */; + productType = "com.apple.product-type.framework"; + }; + 7AD4E529A5EB52D89623D6AC30559784 /* BubbleShowCase */ = { + isa = PBXNativeTarget; + buildConfigurationList = 796A398B6F7CFE13542AFCC16D921542 /* Build configuration list for PBXNativeTarget "BubbleShowCase" */; + buildPhases = ( + CF6162F544D00158F5A3539E456A057D /* Sources */, + 057753F96DFE102039E82A3F98DF729F /* Frameworks */, + 8B33B4BB7A8743CE4631E54A26440B94 /* Headers */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BubbleShowCase; + productName = BubbleShowCase; + productReference = 787239B4E878DE0CFC11D5F59E37596B /* BubbleShowCase.framework */; productType = "com.apple.product-type.framework"; }; /* End PBXNativeTarget section */ @@ -148,26 +238,45 @@ en, ); mainGroup = 7DB346D0F39D3F0E887471402A8071AB; - productRefGroup = 38C3406BA47BD3F9624F268D12A1F91C /* Products */; + productRefGroup = F511823538FD00C048696D2A7A6A7732 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( - A6FB77729702F1A849A08A3BEDFB8D05 /* Pods-ShowCaseSample */, + 7AD4E529A5EB52D89623D6AC30559784 /* BubbleShowCase */, + 63D7931B9806FE5F105A63790F4F1CED /* Pods-ShowCaseSample */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ - FB7FD74F30D01F36D2094834AF50BCCF /* Sources */ = { + CF6162F544D00158F5A3539E456A057D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 8A0F2753D526793BE4B4ADCDA6610594 /* Pods-ShowCaseSample-dummy.m in Sources */, + 615B114320A6888A665D1B7A2FAA40CD /* BubbleShowCase-dummy.m in Sources */, + B1ADE415EB50F361C545901CEF77A17F /* ShowCase.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FA160C44BBB84D6B3DBE242D52A30699 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 338F0B4D49590C1476D5F92962475AA8 /* Pods-ShowCaseSample-dummy.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 83DBFC257E82E058E995994671015E6A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = BubbleShowCase; + target = 7AD4E529A5EB52D89623D6AC30559784 /* BubbleShowCase */; + targetProxy = BCBCA9054A9C0C3796DC7F95D953A0E5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 199D972A13F2B4C56847F7A89CCA83BC /* Debug */ = { isa = XCBuildConfiguration; @@ -232,9 +341,45 @@ }; name = Debug; }; - 85498F86ED99765598E23A0C04F89FD3 /* Debug */ = { + 233FA04E435CF9FFE948E86467C41E27 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1372A7A0726B83DC5ACDD6B44BA75919 /* Pods-ShowCaseSample.debug.xcconfig */; + baseConfigurationReference = 4CE65D5CF3454C45CA799A425E40C417 /* BubbleShowCase.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/BubbleShowCase/BubbleShowCase-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/BubbleShowCase/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/BubbleShowCase/BubbleShowCase.modulemap"; + PRODUCT_MODULE_NAME = BubbleShowCase; + PRODUCT_NAME = BubbleShowCase; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 776C10EF702D491E1DF045272CFB3986 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 49E03F3A261F217373CA84A6B1C39367 /* Pods-ShowCaseSample.release.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CODE_SIGN_IDENTITY = ""; @@ -263,15 +408,17 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = Debug; + name = Release; }; - D8B732CD64D7CD470EE6A02658C585F2 /* Release */ = { + D96B6AD5E8791A416BE8D5EF5043DDC2 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 49E03F3A261F217373CA84A6B1C39367 /* Pods-ShowCaseSample.release.xcconfig */; + baseConfigurationReference = 1372A7A0726B83DC5ACDD6B44BA75919 /* Pods-ShowCaseSample.debug.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CODE_SIGN_IDENTITY = ""; @@ -300,6 +447,44 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + F0A9EC7792ED6CA0572AFAC8EB6F3F7D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4CE65D5CF3454C45CA799A425E40C417 /* BubbleShowCase.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_PREFIX_HEADER = "Target Support Files/BubbleShowCase/BubbleShowCase-prefix.pch"; + INFOPLIST_FILE = "Target Support Files/BubbleShowCase/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MODULEMAP_FILE = "Target Support Files/BubbleShowCase/BubbleShowCase.modulemap"; + PRODUCT_MODULE_NAME = BubbleShowCase; + PRODUCT_NAME = BubbleShowCase; + SDKROOT = iphoneos; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -377,11 +562,20 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 4DFE4007EBDD28D5A8AFDDD30F5174A5 /* Build configuration list for PBXNativeTarget "Pods-ShowCaseSample" */ = { + 796A398B6F7CFE13542AFCC16D921542 /* Build configuration list for PBXNativeTarget "BubbleShowCase" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 233FA04E435CF9FFE948E86467C41E27 /* Debug */, + F0A9EC7792ED6CA0572AFAC8EB6F3F7D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DFD18AA4B416011B26D7D6FF222D2248 /* Build configuration list for PBXNativeTarget "Pods-ShowCaseSample" */ = { isa = XCConfigurationList; buildConfigurations = ( - 85498F86ED99765598E23A0C04F89FD3 /* Debug */, - D8B732CD64D7CD470EE6A02658C585F2 /* Release */, + D96B6AD5E8791A416BE8D5EF5043DDC2 /* Debug */, + 776C10EF702D491E1DF045272CFB3986 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase-dummy.m b/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase-dummy.m new file mode 100644 index 0000000..2b3ab37 --- /dev/null +++ b/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase-dummy.m @@ -0,0 +1,5 @@ +#import +@interface PodsDummy_BubbleShowCase : NSObject +@end +@implementation PodsDummy_BubbleShowCase +@end diff --git a/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase-prefix.pch b/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase-prefix.pch new file mode 100644 index 0000000..beb2a24 --- /dev/null +++ b/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase-prefix.pch @@ -0,0 +1,12 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + diff --git a/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase-umbrella.h b/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase-umbrella.h new file mode 100644 index 0000000..ca39a9a --- /dev/null +++ b/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase-umbrella.h @@ -0,0 +1,16 @@ +#ifdef __OBJC__ +#import +#else +#ifndef FOUNDATION_EXPORT +#if defined(__cplusplus) +#define FOUNDATION_EXPORT extern "C" +#else +#define FOUNDATION_EXPORT extern +#endif +#endif +#endif + + +FOUNDATION_EXPORT double BubbleShowCaseVersionNumber; +FOUNDATION_EXPORT const unsigned char BubbleShowCaseVersionString[]; + diff --git a/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase.modulemap b/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase.modulemap new file mode 100644 index 0000000..404a03c --- /dev/null +++ b/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase.modulemap @@ -0,0 +1,6 @@ +framework module BubbleShowCase { + umbrella header "BubbleShowCase-umbrella.h" + + export * + module * { export * } +} diff --git a/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase.xcconfig b/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase.xcconfig new file mode 100644 index 0000000..9fb7449 --- /dev/null +++ b/Sample/Pods/Target Support Files/BubbleShowCase/BubbleShowCase.xcconfig @@ -0,0 +1,10 @@ +CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/BubbleShowCase +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 +OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" +PODS_BUILD_DIR = ${BUILD_DIR} +PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) +PODS_ROOT = ${SRCROOT} +PODS_TARGET_SRCROOT = ${PODS_ROOT}/BubbleShowCase +PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} +SKIP_INSTALL = YES +SWIFT_VERSION = 4.0 diff --git a/Sample/Pods/Target Support Files/BubbleShowCase/Info.plist b/Sample/Pods/Target Support Files/BubbleShowCase/Info.plist new file mode 100644 index 0000000..cba2585 --- /dev/null +++ b/Sample/Pods/Target Support Files/BubbleShowCase/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + ${PRODUCT_BUNDLE_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ${PRODUCT_NAME} + CFBundlePackageType + FMWK + CFBundleShortVersionString + 0.0.1 + CFBundleSignature + ???? + CFBundleVersion + ${CURRENT_PROJECT_VERSION} + NSPrincipalClass + + + diff --git a/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-acknowledgements.markdown b/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-acknowledgements.markdown index 102af75..160648a 100644 --- a/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-acknowledgements.markdown +++ b/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-acknowledgements.markdown @@ -1,3 +1,28 @@ # Acknowledgements This application makes use of the following third party libraries: + +## BubbleShowCase + +MIT License + +Copyright (c) 2018 Datos + +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. + Generated by CocoaPods - https://cocoapods.org diff --git a/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-acknowledgements.plist b/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-acknowledgements.plist index 7acbad1..8e541a6 100644 --- a/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-acknowledgements.plist +++ b/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-acknowledgements.plist @@ -12,6 +12,37 @@ Type PSGroupSpecifier + + FooterText + MIT License + +Copyright (c) 2018 Datos + +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. + + License + MIT + Title + BubbleShowCase + Type + PSGroupSpecifier + FooterText Generated by CocoaPods - https://cocoapods.org diff --git a/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-frameworks.sh b/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-frameworks.sh index 08e3eaa..4cdf89f 100755 --- a/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-frameworks.sh +++ b/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-frameworks.sh @@ -141,6 +141,13 @@ strip_invalid_archs() { STRIP_BINARY_RETVAL=1 } + +if [[ "$CONFIGURATION" == "Debug" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/BubbleShowCase/BubbleShowCase.framework" +fi +if [[ "$CONFIGURATION" == "Release" ]]; then + install_framework "${BUILT_PRODUCTS_DIR}/BubbleShowCase/BubbleShowCase.framework" +fi if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then wait fi diff --git a/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample.debug.xcconfig b/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample.debug.xcconfig index 566aa02..f7afb0b 100644 --- a/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample.debug.xcconfig +++ b/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample.debug.xcconfig @@ -1,6 +1,12 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/BubbleShowCase" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/BubbleShowCase/BubbleShowCase.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "BubbleShowCase" +OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_ROOT = ${SRCROOT}/Pods +SWIFT_VERSION = 4.0 diff --git a/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample.release.xcconfig b/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample.release.xcconfig index 566aa02..f7afb0b 100644 --- a/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample.release.xcconfig +++ b/Sample/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample.release.xcconfig @@ -1,6 +1,12 @@ +ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES +FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/BubbleShowCase" GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' +OTHER_CFLAGS = $(inherited) -iquote "${PODS_CONFIGURATION_BUILD_DIR}/BubbleShowCase/BubbleShowCase.framework/Headers" +OTHER_LDFLAGS = $(inherited) -framework "BubbleShowCase" +OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. PODS_ROOT = ${SRCROOT}/Pods +SWIFT_VERSION = 4.0 diff --git a/Sample/ShowCaseSample.xcodeproj/project.pbxproj b/Sample/ShowCaseSample.xcodeproj/project.pbxproj index 59361c9..a01fb36 100644 --- a/Sample/ShowCaseSample.xcodeproj/project.pbxproj +++ b/Sample/ShowCaseSample.xcodeproj/project.pbxproj @@ -11,7 +11,6 @@ B09C540F213942950073610D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09C540E213942950073610D /* AppDelegate.swift */; }; B09C5416213942990073610D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B09C5415213942990073610D /* Assets.xcassets */; }; B09C5419213942990073610D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B09C5417213942990073610D /* LaunchScreen.storyboard */; }; - B09C5421213942FD0073610D /* ShowCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09C5420213942FD0073610D /* ShowCase.swift */; }; B09C5426213946D80073610D /* DummyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09C5424213946D80073610D /* DummyViewController.swift */; }; B09C5427213946D80073610D /* DummyViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = B09C5425213946D80073610D /* DummyViewController.xib */; }; B09C542A213947990073610D /* ShowCaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B09C5428213947990073610D /* ShowCaseViewController.swift */; }; @@ -27,7 +26,6 @@ B09C5415213942990073610D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B09C5418213942990073610D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; B09C541A213942990073610D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B09C5420213942FD0073610D /* ShowCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowCase.swift; sourceTree = ""; }; B09C5424213946D80073610D /* DummyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyViewController.swift; sourceTree = ""; }; B09C5425213946D80073610D /* DummyViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DummyViewController.xib; sourceTree = ""; }; B09C5428213947990073610D /* ShowCaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShowCaseViewController.swift; sourceTree = ""; }; @@ -79,7 +77,6 @@ B09C5415213942990073610D /* Assets.xcassets */, B09C540E213942950073610D /* AppDelegate.swift */, B09C5417213942990073610D /* LaunchScreen.storyboard */, - B09C5420213942FD0073610D /* ShowCase.swift */, B09C5424213946D80073610D /* DummyViewController.swift */, B09C5425213946D80073610D /* DummyViewController.xib */, B09C5428213947990073610D /* ShowCaseViewController.swift */, @@ -108,6 +105,7 @@ B09C5407213942950073610D /* Sources */, B09C5408213942950073610D /* Frameworks */, B09C5409213942950073610D /* Resources */, + E3D1A31154AA15F38A9A3B52 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -184,6 +182,24 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + E3D1A31154AA15F38A9A3B52 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/BubbleShowCase/BubbleShowCase.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BubbleShowCase.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ShowCaseSample/Pods-ShowCaseSample-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -191,7 +207,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B09C5421213942FD0073610D /* ShowCase.swift in Sources */, B09C540F213942950073610D /* AppDelegate.swift in Sources */, B09C5426213946D80073610D /* DummyViewController.swift in Sources */, B09C542A213947990073610D /* ShowCaseViewController.swift in Sources */, @@ -343,6 +358,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "${SRCROOT}/**"; }; name = Debug; }; @@ -363,6 +379,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = "1,2"; + USER_HEADER_SEARCH_PATHS = "${SRCROOT}/**"; }; name = Release; }; diff --git a/Sample/ShowCaseSample/ShowCaseViewController.swift b/Sample/ShowCaseSample/ShowCaseViewController.swift index 9385170..b816a54 100644 --- a/Sample/ShowCaseSample/ShowCaseViewController.swift +++ b/Sample/ShowCaseSample/ShowCaseViewController.swift @@ -7,6 +7,7 @@ // import UIKit +import BubbleShowCase class ShowCaseViewController: UIViewController { diff --git a/ShowCase/ShowCase.xcodeproj/project.pbxproj b/ShowCase/ShowCase.xcodeproj/project.pbxproj index 2513abb..c3103ab 100644 --- a/ShowCase/ShowCase.xcodeproj/project.pbxproj +++ b/ShowCase/ShowCase.xcodeproj/project.pbxproj @@ -8,14 +8,14 @@ /* Begin PBXBuildFile section */ B0EE5295213E89F7003E84EC /* ShowCase.h in Headers */ = {isa = PBXBuildFile; fileRef = B0EE5293213E89F7003E84EC /* ShowCase.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B0EE529C213E8A97003E84EC /* ShowCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0EE529B213E8A97003E84EC /* ShowCase.swift */; }; + B0EE529C213E8A97003E84EC /* BubbleShowCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B0EE529B213E8A97003E84EC /* BubbleShowCase.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ B0EE5290213E89F7003E84EC /* ShowCase.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ShowCase.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B0EE5293213E89F7003E84EC /* ShowCase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ShowCase.h; sourceTree = ""; }; B0EE5294213E89F7003E84EC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B0EE529B213E8A97003E84EC /* ShowCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShowCase.swift; sourceTree = ""; }; + B0EE529B213E8A97003E84EC /* BubbleShowCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleShowCase.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -49,7 +49,7 @@ isa = PBXGroup; children = ( B0EE5293213E89F7003E84EC /* ShowCase.h */, - B0EE529B213E8A97003E84EC /* ShowCase.swift */, + B0EE529B213E8A97003E84EC /* BubbleShowCase.swift */, B0EE5294213E89F7003E84EC /* Info.plist */, ); path = ShowCase; @@ -134,7 +134,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B0EE529C213E8A97003E84EC /* ShowCase.swift in Sources */, + B0EE529C213E8A97003E84EC /* BubbleShowCase.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ShowCase/ShowCase/BubbleShowCase.podspec b/ShowCase/ShowCase/BubbleShowCase.podspec new file mode 100644 index 0000000..98e96dc --- /dev/null +++ b/ShowCase/ShowCase/BubbleShowCase.podspec @@ -0,0 +1,26 @@ +Pod::Spec.new do |s| + + s.name = "BubbleShowCase" + s.version = "0.0.1" + s.summary = "A wonderful way to show case your users your App features" + + s.description = <<-DESC + This framework shows up a bubble-like view which targets some element in your scene whose feature you would like to explain to your users. + It is really simple to use, comes with animations and helps your users understand your App design. + DESC + + s.homepage = "https://www.elconfidencial.com" + s.license = { :type => "MIT", :file => "LICENSE" } + s.author = { "ElConfidencial" => "apps@elconfidencial.com", "fermoya" => "fmdr.ct@gmail.com" } + s.social_media_url = "https://twitter.com/elconfidencial" + + s.platform = :ios + s.ios.deployment_target = '9.0' + s.swift_version = '4.0' + + s.source = { :git => "https://github.com/ECLaboratorio/ShowCase-iOS.git", :tag => "#{s.version}" } + s.source_files = "ShowCase/ShowCase/*.swift" + + s.xcconfig = { "SWIFT_VERSION" => "4.0" } + +end diff --git a/ShowCase/ShowCase/BubbleShowCase.swift b/ShowCase/ShowCase/BubbleShowCase.swift new file mode 100644 index 0000000..cae248c --- /dev/null +++ b/ShowCase/ShowCase/BubbleShowCase.swift @@ -0,0 +1,1058 @@ +// +// ShowCase.swift +// Angela +// +// Created by fermoya on 8/13/18. +// Copyright © 2018 ElConfidencial. All rights reserved. +// + +import UIKit + +/** +The delegate of BubbleShowCase must adopt the BubbleShowCaseDelegate protocol. Optional methods let the delegate know when the show case appears and dismisses and when some of the most common gestures are recognized by the target. +*/ +@objc public protocol BubbleShowCaseDelegate: class { + + /// Tells the delegate the show case is going to appear into the screen + @objc optional func bubbleShowCaseWillTransitionIntoScreen(_ bubbleShowCase: BubbleShowCase) + + /// Tells the delegate appeared into the screen + @objc optional func bubbleShowCaseDidTransitionIntoScreen(_ bubbleShowCase: BubbleShowCase) + + /// Tells the delegate the show case is going to be removed from the screen + @objc optional func bubbleShowCaseWillDismiss(_ bubbleShowCase: BubbleShowCase) + + /// Tells the delegate the show case was removed from the screen + @objc optional func bubbleShowCaseDidDismiss(_ bubbleShowCase: BubbleShowCase) + + /// Tells the delegate the target was tapped + @objc optional func bubbleShowCase(_ bubbleShowCase: BubbleShowCase, didTap target: UIView!, gestureRecognizer: UITapGestureRecognizer) + + /// Tells the delegate the target was double tapped + @objc optional func bubbleShowCase(_ bubbleShowCase: BubbleShowCase, didDoubleTap target: UIView!, gestureRecognizer: UITapGestureRecognizer) + + /// Tells the delegate the target was swiped leftwards + @objc optional func bubbleShowCase(_ bubbleShowCase: BubbleShowCase, didSwipeLeft target: UIView!, gestureRecognizer: UISwipeGestureRecognizer) + + /// Tells the delegate the target was swiped rightwards + @objc optional func bubbleShowCase(_ bubbleShowCase: BubbleShowCase, didSwipeRight target: UIView!, gestureRecognizer: UISwipeGestureRecognizer) + + /// Tells the delegate the target was swiped upwards + @objc optional func bubbleShowCase(_ bubbleShowCase: BubbleShowCase, didSwipeUp target: UIView!, gestureRecognizer: UISwipeGestureRecognizer) + + /// Tells the delegate the target was swiped downwards + @objc optional func bubbleShowCase(_ bubbleShowCase: BubbleShowCase, didSwipeDown target: UIView!, gestureRecognizer: UISwipeGestureRecognizer) + + /// Tells the delegate the target was long pressed + @objc optional func bubbleShowCase(_ bubbleShowCase: BubbleShowCase, didLongPress target: UIView!, gestureRecognizer: UILongPressGestureRecognizer) +} + +/** +A view which inteds to explain some feature of the scene it's shown in by displaying a message that points to a target view. +This so called show case obscurs the scene, pops up above any other view in the screen highlights and animates the target view to catch the user's attention. + +# Example # +```` +... +let showCase = BubbleShowCaseShow(target: myBarButton, label: "BarButtonShowCase") +showCase.titleText = "You know what?" +showCase.descriptionText = "You can do amazing things if you tap on this navbar button" +showCase.image = UIImage(named: "show-case-bar-button") +showCase.show() +... + +// Et voilà.... +```` + +*/ +public class BubbleShowCase: UIView { + + /** + It indicates the direction the show case should point to. There are 6 options: + - Left + - Right + - Up + - Down + - Up and Down + - Left and right + */ + public enum ArrowDirection { + /// It points leftwards, making the show case stays on the right side of the target. + case left + + /// It points rightwards, making the show case stays on the left side of the target. + case right + + /// It points upwards, making the show case stays below the target. + case up + + /// It points downwards, making the show case stays above of the target. + case down + + /// It displays to arrows pointing upwards and downwards respectively. The show case pops up centered in the target. + case upAndDown + + /// It displays to arrows pointing leftwards and rightwards respectively. The show case pops up centered in the target. + case leftAndRight + } + + //MARK: Public properties + + /// Background color of the show case. + public var color = UIColor(red: 18.0 / 255.0, green: 156.0 / 255.0, blue: 226.0 / 255.0, alpha: 1) { didSet { setNeedsDisplay() } } + + /// Text color of both the title and description. + public var textColor = UIColor.white { didSet { setNeedsDisplay() } } + + /// Blurred shadow color around the screenshot. If *nil*, it doesn't display any shadow. + public var shadowColor: UIColor? = UIColor.white { didSet { setNeedsDisplay() } } + + /// Color of the cross on the top right of the show case. If not set, it takes the same color of the text. + public var crossColor: UIColor? { didSet { setNeedsDisplay() } } + + /** + Image set on the left side of the show case. + + - Remark: Its size is forced to be 50x50. + */ + public var image: UIImage? { + didSet { + setNeedsLayout() + setNeedsDisplay() + } + } + + /// Text displayed as the show case title. + public var titleText = "" { + didSet { + titleLabel?.text = titleText + setNeedsLayout() + setNeedsDisplay() + } + } + + /// Font for the title label. + public var titleFont: UIFont = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.bold) { + didSet { + titleLabel?.font = titleFont + setNeedsLayout() + setNeedsDisplay() + } + } + + /// Font for the description label. + public var descriptionFont: UIFont = UIFont.systemFont(ofSize: 15, weight: UIFont.Weight.regular) { + didSet { + descriptionLabel?.font = descriptionFont + setNeedsLayout() + setNeedsDisplay() + } + } + + /// Text displayed as the show case description. + public var descriptionText = "" { + didSet { + descriptionLabel?.text = descriptionText + setNeedsLayout() + setNeedsDisplay() + } + } + + /// Indicates wether or not the user can close the show case by tapping a cross that is displayed on the top right. Set this property to *false* this to force the user to perform any action on the target. + public var isCrossDismissable = true { didSet { cross?.isHidden = !isCrossDismissable } } + + /// The object that acts as the delegate of a Show Case. The delegate is not retained and is therefore qualified as *weak*. + public weak var delegate: BubbleShowCaseDelegate? + + /** + Direction of the arrow the show case points to. There are 6 possible values. + + - Remark: You must ensure the show case will fit on that size + */ + public var arrowDirection: ArrowDirection { didSet { setNeedsDisplay() } } + + //MARK: Private properties + + /**************** SUBVIEWS ********************/ + private var icon: UIImageView? { didSet { icon?.layer.cornerRadius = imageSize.height / 2 } } + private var titleLabel: UILabel! + private var descriptionLabel: UILabel! + private var screenshotContainer: UIView! + private var bubble: UIView! + private var cross: UIButton! + + /**************** GEOMETRY ********************/ + private var imageSize = CGSize(width: 50, height: 50) + private var crossSize: CGFloat = 12 + private var crossPadding: CGFloat = 10 + private var crossArea: CGFloat { return 2 * crossPadding + crossSize } + private var arrowSize: CGFloat = 10 + private var bubblePadding: UIEdgeInsets { return UIEdgeInsets(top: 15, left: 20, bottom: 15, right: 0) } + private var padding: CGFloat { return arrowSize } + private var margins: CGFloat = 10 + + /*************** CONSTRAINTS ******************/ + private var descriptionLeading: NSLayoutConstraint! + private var sideShowCaseCenterY: NSLayoutConstraint? + private var sideShowCaseTop: NSLayoutConstraint? + private var sideShowCaseBottom: NSLayoutConstraint? + + /****************** FLAGS *********************/ + private var shouldWhitenScreenshot = false + private var isInitialized = false + + /************* ROOT HIERARCHY *****************/ + private weak var target: UIView! + private weak var scrollView: UIScrollView? + private var screenWindow: UIWindow { return UIApplication.shared.keyWindow! } + + /******************* ID ***********************/ + public private(set) var label: String? + + /***************** LAYERS *********************/ + private var crossLayer: CAShapeLayer? + private var arrowLayers: [CAShapeLayer]? + + /***************** CONCAT *********************/ + fileprivate var nextShowCase: BubbleShowCase? + + //MARK: Initializers + + /** + Initializes a new Show Case View. + + - Parameter target: The view that's targeted and whose feature is intended to be exaplained + - Parameter arrowDirection: The direction towards the show case must point the target + - Parameter label: *Optional*, an identifier for the show case + + */ + public init(target: UIView, arrowDirection: ArrowDirection, label: String? = nil) { + self.target = target + self.arrowDirection = arrowDirection + self.label = label + super.init(frame: CGRect.zero) + } + + /** + Initializes a new Show Case View. + + - Parameter target: The bar button that's targeted and whose feature is intended to be exaplained + - Parameter label: *Optional*, an identifier for the show case + + - Remark: From iOS 11 onwards, the target must be a custom bar button item, that is, it must have been created by using *init(customView: UIView?)* constructor. If not so, it'll return *nil* + - Note: The show case arrow direction will always point upwards + + */ + public init?(target: UIBarButtonItem, label: String? = nil) { + let targetViewUnwrapped = (target.value(forKey: "view") as? UIView) ?? target.customView + guard let targetView = targetViewUnwrapped else { return nil } + + self.target = targetView + self.arrowDirection = .up + self.label = label + super.init(frame: CGRect.zero) + shouldWhitenScreenshot = true + } + + + /** + Initializes a new Show Case View. + + - Parameter tabBar: The tabBar where the show case should be installed over + - Parameter index: Index of the tab bar item which is the target + - Parameter label: *Optional*, an identifier for the show case + + - Remark: In case there's no room in screen, the UITabBarItem might be embeded in the "More" item. If so, the show case will place itself over this item. + - Note: The show case arrow direction will always point downwards + + */ + public init(tabBar: UITabBar, index: Int, label: String? = nil) { + let tabBarItems = tabBar.subviews.filter { $0.isKind(of: NSClassFromString("UITabBarButton")!) } + + let maxIndex = tabBarItems.count - 1 + let index = index < maxIndex ? index : maxIndex + let target = tabBarItems[index] + + self.target = target + self.arrowDirection = .down + self.label = label + super.init(frame: CGRect.zero) + shouldWhitenScreenshot = true + } + + /** + Initializes a new Show Case View. + + - Parameter cell: The collection cell that's targeted + - Parameter arrowDirection: The direction towards the show case must point the target + - Parameter target: Target view whose feature is intended to be explained. If *nil*, the show case will target the whole cell. + - Parameter label: *Optional*, an identifier for the show case + + - Remark: If the cell isn't displayed, it'll scroll to make the cell visible. + + */ + public init(cell: UICollectionViewCell, target: UIView?, arrowDirection: ArrowDirection, label: String? = nil) { + self.target = target ?? cell + self.arrowDirection = arrowDirection + self.label = label + self.scrollView = cell.superview as? UIScrollView + super.init(frame: CGRect.zero) + } + + /** + Initializes a new Show Case View. If the cell isn't displayed, it'll scroll to make the cell visible. + + - Parameter cell: The table cell that's targeted + - Parameter arrowDirection: The direction towards the show case must point the target + - Parameter target: Target view whose feature is intended to be explained. If *nil*, the show case will target the whole cell + - Parameter label: *Optional*, an identifier for the show case + + - Remark: If the cell isn't displayed, it'll scroll to make the cell visible. + + */ + public init(cell: UITableViewCell, target: UIView?, arrowDirection: ArrowDirection, label: String? = nil) { + self.target = target ?? cell + self.arrowDirection = arrowDirection + self.label = label + self.scrollView = cell.superview as? UIScrollView + super.init(frame: CGRect.zero) + } + + /// **Not implemented**. It raises a *Fatal Exepction*. + public required init?(coder aDecoder: NSCoder) { + fatalError("Not Implemented") + } + + deinit { + descriptionLeading = nil + sideShowCaseCenterY = nil + sideShowCaseTop = nil + sideShowCaseBottom = nil + nextShowCase = nil + } + + //MARK: Public Methods + + /// Dismisses the show case out of the screen with an animation. + public func dimiss() { + animateDisappearance() + } + + /** + Displays the show case in the screen with an animation. + + - Remark: You should call this method after any change has happened in the screen. + */ + public func show() { + if !isInitialized { + initialize() + } + } + + /// Call this method to concat a show case. It will be automatically displayed after this is completed. + public func concat(bubbleShowCase: BubbleShowCase) { + self.nextShowCase = bubbleShowCase + } + + /// Displays the show case after the one passed as argument has finished. + public func show(after bubbleShowCase: BubbleShowCase) { + bubbleShowCase.nextShowCase = self + } + + //MARK: Override + + public override func draw(_ rect: CGRect) { + if let _ = image, icon == nil { + embedImage() + } + + UIColor.black.withAlphaComponent(0.5).setFill() + UIRectFill(rect) + + titleLabel.textColor = textColor + descriptionLabel.textColor = textColor + bubble.backgroundColor = color + + if let arrowLayers = arrowLayers { + for arrowLayer in arrowLayers { + fill(color: color, in: arrowLayer) + } + } + + if let crossLayer = crossLayer { + let crossColor = self.crossColor ?? textColor + fill(color: crossColor, in: crossLayer) + } + } + + public override func layoutSubviews() { + super.layoutSubviews() + if arrowDirection == .left || arrowDirection == .right { + updateSideConstraints() + } + } + + //MARK: Modification of constraints + + // Updates bubble top and bottom constraints to make fit the bubble into the screen. It should be called once the subviews have been rendered. + private func updateSideConstraints() { + let topAvailableSpace = screenshotContainer.frame.midY - margins + let bottomAvailableSpace = screenWindow.frame.height - screenshotContainer.frame.midY - margins + + if topAvailableSpace < bubble.frame.height / 2 { + sideShowCaseCenterY?.isActive = false + sideShowCaseBottom?.isActive = false + let topConstant = UIApplication.shared.statusBarFrame.size.height + margins + if self.sideShowCaseTop == nil { + let top = NSLayoutConstraint(item: bubble, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: topConstant) + sideShowCaseTop = top + addConstraint(top) + } else { + sideShowCaseTop?.isActive = true + sideShowCaseTop?.constant = topConstant + } + } else if bottomAvailableSpace < bubble.frame.height / 2 { + sideShowCaseCenterY?.isActive = false + sideShowCaseTop?.isActive = false + let bottomConstant = margins + if self.sideShowCaseBottom == nil { + let bottom = NSLayoutConstraint(item: bubble, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: -bottomConstant) + sideShowCaseBottom = bottom + addConstraint(bottom) + } else { + sideShowCaseBottom?.isActive = true + sideShowCaseBottom?.constant = -bottomConstant + } + } else { + sideShowCaseCenterY?.isActive = true + sideShowCaseTop?.isActive = false + sideShowCaseBottom?.isActive = false + } + + layoutIfNeeded() + } + + //MARK: Initialization + + // Initializes the show case hierarchy and displays the show case into the screen. + private func initialize() { + isInitialized = true + self.isOpaque = false + + if shouldStopScrollView { + scrollTargetToDisplay() + } + + embedInSuperView() + if arrowDirection != .leftAndRight && arrowDirection != .upAndDown { + embedScreenshot() + } + + embedBubble() + embedLabels() + if isCrossDismissable { drawCross() } + drawArrow() + + animateAppearance() + } + + //MARK: Animations + + // Animates the appearance of both the show case and its subviews + private func animateDisappearance() { + delegate?.bubbleShowCaseWillDismiss?(self) + nextShowCase?.show() + UIView.animate(withDuration: 0.4, animations: { [weak self] in + self?.alpha = 0 + }, completion: { [weak self] _ in + guard let `self` = self else { return } + self.delegate?.bubbleShowCaseDidDismiss?(self) + self.removeFromSuperview() + }) + } + + // Animates the removal of the show case out of the screen + private func animateAppearance() { + delegate?.bubbleShowCaseWillTransitionIntoScreen?(self) + alpha = 0 + bubble.alpha = 0 + screenshotContainer?.alpha = 0 + screenWindow.bringSubview(toFront: self) + UIView.animate(withDuration: 0.4, animations: { [weak self] in + self?.alpha = 1 + self?.screenshotContainer?.alpha = 1 + }, completion: { [weak self] _ in + guard let `self` = self else { return } + self.bubble.transform = CGAffineTransform(scaleX: 0.001, y: 0.001) + self.bubble.alpha = 1 + + UIView.animate(withDuration: 0.5, delay: 0.5, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: [], animations: { [weak self] in + self?.bubble.transform = .identity + }, completion: { [weak self] _ in + guard let `self` = self else { return } + self.delegate?.bubbleShowCaseDidTransitionIntoScreen?(self) + UIView.animate(withDuration: 0.4, delay: 0, options: [.autoreverse, .repeat, .allowUserInteraction], animations: { [weak self] in + self?.screenshotContainer?.transform = CGAffineTransform(scaleX: 1.05, y: 1.05) + }, completion: nil) + }) + }) + + } + + //MARK: Drawing + + // Creates a button, draws a cross inside and places it at the top right of the bubble + private func drawCross() { + cross = UIButton() + cross.addTarget(self, action: #selector(crossDidTap), for: .touchUpInside) + cross.translatesAutoresizingMaskIntoConstraints = false + bubble.addSubview(cross) + + let top = NSLayoutConstraint(item: cross, attribute: .top, relatedBy: .equal, toItem: bubble, attribute: .top, multiplier: 1, constant: 0) + let trailing = NSLayoutConstraint(item: cross, attribute: .trailing, relatedBy: .equal, toItem: bubble, attribute: .trailing, multiplier: 1, constant: 0) + let height = NSLayoutConstraint(item: cross, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: crossArea) + let width = NSLayoutConstraint(item: cross, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: crossArea) + addConstraints([top, trailing]) + cross.addConstraints([height, width]) + + let path = UIBezierPath() + path.move(to: CGPoint(x: crossPadding, y: crossPadding)) + path.addLine(to: CGPoint(x: crossPadding + crossSize, y: crossPadding + crossSize)) + + path.move(to: CGPoint(x: crossPadding + crossSize, y: crossPadding)) + path.addLine(to: CGPoint(x: crossPadding, y: crossPadding + crossSize)) + path.close() + + let rect = CGRect(x: 0, y: 0, width: crossArea, height: crossArea) + let shapeLayer = CAShapeLayer() + shapeLayer.frame = rect + shapeLayer.path = path.cgPath + shapeLayer.lineWidth = 2 + let crossColor = self.crossColor ?? textColor + shapeLayer.strokeColor = crossColor.cgColor + crossLayer = shapeLayer + cross.layer.addSublayer(shapeLayer) + } + + // Draws the arrow that the bubble will use to point to the target considering the direction specified. + private func drawArrow() { + let arrow = UIView() + arrow.translatesAutoresizingMaskIntoConstraints = false + bubble.addSubview(arrow) + + var arrow2: UIView! = nil + var path2: UIBezierPath! = nil + + let path = UIBezierPath() + let targetFrame = target.convert(target.bounds, to: screenWindow) + + switch arrowDirection { + case .up: + let top = NSLayoutConstraint(item: arrow, attribute: .top, relatedBy: .equal, toItem: bubble, attribute: .top, multiplier: 1, constant: -arrowSize) + let leading = NSLayoutConstraint(item: arrow, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: targetFrame.midX - arrowSize) + let height = NSLayoutConstraint(item: arrow, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: arrowSize) + let width = NSLayoutConstraint(item: arrow, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 2 * arrowSize) + + bubble.addConstraint(top) + addConstraint(leading) + arrow.addConstraints([width, height]) + + path.move(to: CGPoint(x: arrowSize, y: 0)) + path.addLine(to: CGPoint(x: 0, y: arrowSize)) + path.addLine(to: CGPoint(x: 2 * arrowSize, y: arrowSize)) + path.addLine(to: CGPoint(x: arrowSize, y: 0)) + case .down: + let bottom = NSLayoutConstraint(item: arrow, attribute: .bottom, relatedBy: .equal, toItem: bubble, attribute: .bottom, multiplier: 1, constant: arrowSize) + let leading = NSLayoutConstraint(item: arrow, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: targetFrame.midX - arrowSize) + let height = NSLayoutConstraint(item: arrow, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: arrowSize) + let width = NSLayoutConstraint(item: arrow, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 2 * arrowSize) + + bubble.addConstraint(bottom) + addConstraint(leading) + arrow.addConstraints([width, height]) + + path.move(to: CGPoint(x: arrowSize, y: arrowSize)) + path.addLine(to: CGPoint(x: 0, y: 0)) + path.addLine(to: CGPoint(x: 2 * arrowSize, y: 0)) + path.addLine(to: CGPoint(x: arrowSize, y: arrowSize)) + case .left: + let top = NSLayoutConstraint(item: arrow, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: targetFrame.midY - arrowSize) + let leading = NSLayoutConstraint(item: arrow, attribute: .leading, relatedBy: .equal, toItem: bubble, attribute: .leading, multiplier: 1, constant: -arrowSize) + let height = NSLayoutConstraint(item: arrow, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 2 * arrowSize) + let width = NSLayoutConstraint(item: arrow, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: arrowSize) + + bubble.addConstraint(leading) + addConstraint(top) + arrow.addConstraints([width, height]) + + path.move(to: CGPoint(x: 0, y: arrowSize)) + path.addLine(to: CGPoint(x: arrowSize, y: 0)) + path.addLine(to: CGPoint(x: arrowSize, y: 2 * arrowSize)) + path.addLine(to: CGPoint(x: 0, y: arrowSize)) + case .right: + let top = NSLayoutConstraint(item: arrow, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: targetFrame.midY - arrowSize) + let trailing = NSLayoutConstraint(item: arrow, attribute: .trailing, relatedBy: .equal, toItem: bubble, attribute: .trailing, multiplier: 1, constant: arrowSize) + let height = NSLayoutConstraint(item: arrow, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 2 * arrowSize) + let width = NSLayoutConstraint(item: arrow, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: arrowSize) + + bubble.addConstraint(trailing) + addConstraint(top) + arrow.addConstraints([width, height]) + + path.move(to: CGPoint(x: arrowSize, y: arrowSize)) + path.addLine(to: CGPoint(x: 0, y: 0)) + path.addLine(to: CGPoint(x: 0, y: 2 * arrowSize)) + path.addLine(to: CGPoint(x: arrowSize, y: arrowSize)) + case .upAndDown: + let top = NSLayoutConstraint(item: arrow, attribute: .top, relatedBy: .equal, toItem: bubble, attribute: .top, multiplier: 1, constant: -arrowSize) + let leading = NSLayoutConstraint(item: arrow, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: targetFrame.midX - arrowSize) + let height = NSLayoutConstraint(item: arrow, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: arrowSize) + let width = NSLayoutConstraint(item: arrow, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 2 * arrowSize) + + bubble.addConstraint(top) + addConstraint(leading) + arrow.addConstraints([width, height]) + + path.move(to: CGPoint(x: arrowSize, y: 0)) + path.addLine(to: CGPoint(x: 0, y: arrowSize)) + path.addLine(to: CGPoint(x: 2 * arrowSize, y: arrowSize)) + path.addLine(to: CGPoint(x: arrowSize, y: 0)) + + path2 = UIBezierPath() + arrow2 = UIView() + arrow2.translatesAutoresizingMaskIntoConstraints = false + bubble.addSubview(arrow2) + + let bottom2 = NSLayoutConstraint(item: arrow2, attribute: .bottom, relatedBy: .equal, toItem: bubble, attribute: .bottom, multiplier: 1, constant: arrowSize) + let leading2 = NSLayoutConstraint(item: arrow2, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: targetFrame.midX - arrowSize) + let height2 = NSLayoutConstraint(item: arrow2, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: arrowSize) + let width2 = NSLayoutConstraint(item: arrow2, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 2 * arrowSize) + + bubble.addConstraint(bottom2) + addConstraint(leading2) + arrow2.addConstraints([width2, height2]) + + path2.move(to: CGPoint(x: arrowSize, y: arrowSize)) + path2.addLine(to: CGPoint(x: 0, y: 0)) + path2.addLine(to: CGPoint(x: 2 * arrowSize, y: 0)) + path2.addLine(to: CGPoint(x: arrowSize, y: arrowSize)) + case .leftAndRight: + let top = NSLayoutConstraint(item: arrow, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: targetFrame.midY - arrowSize) + let leading = NSLayoutConstraint(item: arrow, attribute: .leading, relatedBy: .equal, toItem: bubble, attribute: .leading, multiplier: 1, constant: -arrowSize) + let height = NSLayoutConstraint(item: arrow, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 2 * arrowSize) + let width = NSLayoutConstraint(item: arrow, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: arrowSize) + + bubble.addConstraint(leading) + addConstraint(top) + arrow.addConstraints([width, height]) + + path.move(to: CGPoint(x: 0, y: arrowSize)) + path.addLine(to: CGPoint(x: arrowSize, y: 0)) + path.addLine(to: CGPoint(x: arrowSize, y: 2 * arrowSize)) + path.addLine(to: CGPoint(x: 0, y: arrowSize)) + + path2 = UIBezierPath() + arrow2 = UIView() + arrow2.translatesAutoresizingMaskIntoConstraints = false + bubble.addSubview(arrow2) + + let top2 = NSLayoutConstraint(item: arrow2, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: targetFrame.midY - arrowSize) + let trailing2 = NSLayoutConstraint(item: arrow2, attribute: .trailing, relatedBy: .equal, toItem: bubble, attribute: .trailing, multiplier: 1, constant: arrowSize) + let height2 = NSLayoutConstraint(item: arrow2, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: 2 * arrowSize) + let width2 = NSLayoutConstraint(item: arrow2, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: arrowSize) + + bubble.addConstraint(trailing2) + addConstraint(top2) + arrow2.addConstraints([width2, height2]) + + path2.move(to: CGPoint(x: arrowSize, y: arrowSize)) + path2.addLine(to: CGPoint(x: 0, y: 0)) + path2.addLine(to: CGPoint(x: 0, y: 2 * arrowSize)) + path2.addLine(to: CGPoint(x: arrowSize, y: arrowSize)) + } + + let layer = CAShapeLayer() + layer.fillColor = color.cgColor + layer.strokeColor = color.cgColor + layer.lineWidth = 1 + layer.path = path.cgPath + layer.frame = CGRect(origin: CGPoint.zero, size: arrow.frame.size) + + arrowLayers = [layer] + if let path2 = path2 { + let layer2 = CAShapeLayer() + layer2.fillColor = color.cgColor + layer2.strokeColor = color.cgColor + layer2.lineWidth = 1 + layer2.path = path2.cgPath + layer2.frame = CGRect(origin: CGPoint.zero, size: arrow2.frame.size) + arrow2.layer.addSublayer(layer2) + arrowLayers?.append(layer2) + } + + arrow.layer.addSublayer(layer) + } + + // MARK: Completion + + // Called whenever the cross is tapped + @objc private func crossDidTap() { + animateDisappearance() + } + + //MARK: Utils + + // For UITableViewCell and UICollectionViewCell only. Returns if the target is within the screen bounds + private var shouldStopScrollView: Bool { + guard let scrollView = scrollView else { return false } + + let targetFrame = target.convert(target.bounds, to: scrollView) + var height = scrollView.frame.size.height - targetFrame.height + if #available(iOS 11, *) { + height = height - scrollView.safeAreaInsets.bottom - scrollView.safeAreaInsets.top + } + + let container = CGRect(x: scrollView.contentOffset.x, + y: scrollView.contentOffset.y, + width: scrollView.frame.size.width, + height: scrollView.frame.size.height - targetFrame.height); + guard !targetFrame.intersects(container) else { return false } + + return true + } + + // For UITableViewCell and UICollectionViewCell only. Makes the target visible in the screen. + private func scrollTargetToDisplay() { + guard let scrollView = self.scrollView else { return } + + scrollView.isScrollEnabled = false + scrollView.isScrollEnabled = true + var contentOffset = scrollView.contentOffset + contentOffset.y = contentOffset.y + target.frame.height + 30 + scrollView.setContentOffset(contentOffset, animated: false) + } + + // Changes the color of a layer. + private func fill(color: UIColor, in layer: CAShapeLayer) { + layer.fillColor = color.cgColor + layer.strokeColor = color.cgColor + } + + // Adds gestureRecognizers to the target screenshot to react to some gestures. + private func addGestureRecognizersToScreenshot() { + let longPressTapGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(targetDidLongPress)) + longPressTapGestureRecognizer.minimumPressDuration = 0.5 + screenshotContainer.addGestureRecognizer(longPressTapGestureRecognizer) + + let doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(targetDidDoubleTap(gestureRecognizer:))) + doubleTapGestureRecognizer.numberOfTapsRequired = 2 + doubleTapGestureRecognizer.require(toFail: longPressTapGestureRecognizer) + screenshotContainer.addGestureRecognizer(doubleTapGestureRecognizer) + + let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(targetDidTap(gestureRecognizer:))) + tapGestureRecognizer.numberOfTapsRequired = 1 + tapGestureRecognizer.require(toFail: doubleTapGestureRecognizer) + screenshotContainer.addGestureRecognizer(tapGestureRecognizer) + + let swipeLeftGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(targetDidSwipeLeft(gestureRecognizer:))) + swipeLeftGestureRecognizer.direction = .left + screenshotContainer.addGestureRecognizer(swipeLeftGestureRecognizer) + + let swipeRightGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(targetDidSwipeRight(gestureRecognizer:))) + swipeRightGestureRecognizer.direction = .right + swipeRightGestureRecognizer.require(toFail: swipeLeftGestureRecognizer) + screenshotContainer.addGestureRecognizer(swipeRightGestureRecognizer) + + let swipeUpGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(targetDidSwipeUp(gestureRecognizer:))) + swipeUpGestureRecognizer.direction = .up + swipeUpGestureRecognizer.require(toFail: swipeRightGestureRecognizer) + screenshotContainer.addGestureRecognizer(swipeUpGestureRecognizer) + + let swipeDownGestureRecognizer = UISwipeGestureRecognizer(target: self, action: #selector(targetDidSwipeDown(gestureRecognizer:))) + swipeDownGestureRecognizer.direction = .down + swipeDownGestureRecognizer.require(toFail: swipeUpGestureRecognizer) + screenshotContainer.addGestureRecognizer(swipeDownGestureRecognizer) + } + + //MARK: Events + + // Screenshot was tapped + @objc private func targetDidTap(gestureRecognizer: UIGestureRecognizer) { + guard let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer else { return } + delegate?.bubbleShowCase?(self, didTap: target, gestureRecognizer: tapGestureRecognizer) + } + + // Screenshot was double long pressed + @objc private func targetDidLongPress(gestureRecognizer: UIGestureRecognizer) { + guard let longPressGestureRecognizer = gestureRecognizer as? UILongPressGestureRecognizer else { return } + guard longPressGestureRecognizer.state == .ended else { return } + delegate?.bubbleShowCase?(self, didLongPress: target, gestureRecognizer: longPressGestureRecognizer) + } + + // Screenshot was double tapped + @objc private func targetDidDoubleTap(gestureRecognizer: UIGestureRecognizer) { + guard let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer else { return } + delegate?.bubbleShowCase?(self, didDoubleTap: target, gestureRecognizer: tapGestureRecognizer) + } + + // Screenshot was double swiped leftwards + @objc private func targetDidSwipeLeft(gestureRecognizer: UIGestureRecognizer) { + guard let swipeGestureRecognizer = gestureRecognizer as? UISwipeGestureRecognizer else { return } + delegate?.bubbleShowCase?(self, didSwipeLeft: target, gestureRecognizer: swipeGestureRecognizer) + } + + // Screenshot was double swiped rightwards + @objc private func targetDidSwipeRight(gestureRecognizer: UIGestureRecognizer) { + guard let swipeGestureRecognizer = gestureRecognizer as? UISwipeGestureRecognizer else { return } + delegate?.bubbleShowCase?(self, didSwipeRight: target, gestureRecognizer: swipeGestureRecognizer) + } + + // Screenshot was double swiped downwards + @objc private func targetDidSwipeDown(gestureRecognizer: UIGestureRecognizer) { + guard let swipeGestureRecognizer = gestureRecognizer as? UISwipeGestureRecognizer else { return } + delegate?.bubbleShowCase?(self, didSwipeDown: target, gestureRecognizer: swipeGestureRecognizer) + } + + // Screenshot was double swiped upwards + @objc private func targetDidSwipeUp(gestureRecognizer: UIGestureRecognizer) { + guard let swipeGestureRecognizer = gestureRecognizer as? UISwipeGestureRecognizer else { return } + delegate?.bubbleShowCase?(self, didSwipeUp: target, gestureRecognizer: swipeGestureRecognizer) + } + + //MARK: Embed subviews + + // Embeds the show case in the application window + private func embedInSuperView() { + self.translatesAutoresizingMaskIntoConstraints = false + screenWindow.addSubview(self) + + let top = NSLayoutConstraint(item: self, attribute: .top, relatedBy: .equal, toItem: screenWindow, attribute: .top, multiplier: 1, constant: 0) + let bottom = NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: screenWindow, attribute: .bottom, multiplier: 1, constant: 0) + let leading = NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: screenWindow, attribute: .leading, multiplier: 1, constant: 0) + let trailing = NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: screenWindow, attribute: .trailing, multiplier: 1, constant: 0) + + screenWindow.addConstraints([top, bottom, leading, trailing]) + } + + // Takes a screenshot and places it over the target + private func embedScreenshot() { + screenshotContainer = UIView() + screenshotContainer.translatesAutoresizingMaskIntoConstraints = false + addSubview(screenshotContainer) + + let navbarHeight = UIApplication.shared.keyWindow?.rootViewController?.navigationController?.navigationBar.frame.height ?? 44 + let targetFrame = target.convert(target.bounds, to: screenWindow) + var screenShotFrame = targetFrame + if shouldWhitenScreenshot { + screenShotFrame = CGRect(x: targetFrame.origin.x - 4, + y: targetFrame.origin.y - (navbarHeight - targetFrame.height) / 2, + width: targetFrame.width + 2 * 4, + height: navbarHeight) + } + + let top = NSLayoutConstraint(item: screenshotContainer, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: screenShotFrame.origin.y) + let leading = NSLayoutConstraint(item: screenshotContainer, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: screenShotFrame.origin.x) + let height = NSLayoutConstraint(item: screenshotContainer, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: screenShotFrame.height) + let width = NSLayoutConstraint(item: screenshotContainer, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: screenShotFrame.width) + + screenshotContainer.addConstraints([height, width]) + addConstraints([top, leading]) + + if let shadowColor = self.shadowColor { + screenshotContainer.layer.backgroundColor = UIColor.clear.cgColor + screenshotContainer.layer.shadowColor = shadowColor.cgColor + screenshotContainer.layer.shadowOffset = CGSize(width: 0.0, height: 2.0) + screenshotContainer.layer.shadowOpacity = 0.5 + screenshotContainer.layer.shadowRadius = 5.0 + } + addGestureRecognizersToScreenshot() + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) { [weak self] in // Takes the screenshot asynchronously while animating appearance + guard let `self` = self else { return } + + let parent: UIView! = self.target.superview ?? self.target + let screenshot: UIView! = parent.resizableSnapshotView(from: self.target.frame, afterScreenUpdates: false, withCapInsets: UIEdgeInsets.zero) + + if self.shouldWhitenScreenshot { + screenshot.backgroundColor = UIColor.white + } + + screenshot.translatesAutoresizingMaskIntoConstraints = false + screenshot.layer.cornerRadius = 5 + screenshot.layer.masksToBounds = true + screenshot.contentMode = .center + screenshot.isUserInteractionEnabled = false + self.screenshotContainer.addSubview(screenshot) + + let centerXImage = screenshot.centerXAnchor.constraint(equalTo: self.screenshotContainer.centerXAnchor) + centerXImage.identifier = "centerX" + let centerYImage = screenshot.centerYAnchor.constraint(equalTo: self.screenshotContainer.centerYAnchor) + centerYImage.identifier = "centerY" + let widthImage = screenshot.widthAnchor.constraint(equalToConstant: screenShotFrame.width) + widthImage.identifier = "width" + let heightImage = screenshot.heightAnchor.constraint(equalToConstant: screenShotFrame.height) + heightImage.identifier = "height" + self.screenshotContainer.addConstraints([centerXImage, centerYImage, heightImage, widthImage]) + } + + } + + // Embeds the bubble in the show case view and places it next to the target according to the arrow direction + private func embedBubble() { + bubble = UIView() + bubble.layer.cornerRadius = 5 + bubble.clipsToBounds = false + bubble.translatesAutoresizingMaskIntoConstraints = false + addSubview(bubble) + + let height = NSLayoutConstraint(item: bubble, attribute: .height, relatedBy: .greaterThanOrEqual, toItem: nil, attribute: .height, multiplier: 1, constant: 50) + bubble.addConstraint(height) + + switch arrowDirection { + case .up, .down: + constraintBubbleForTopDirections() + case .left, .right: + contraintBubbleForSideDirections() + case .leftAndRight, .upAndDown: + constraintBubbleForDoubleDirections() + } + } + + // Constraints the bubble to the target for both leftAndSide and upAndDown arrow directions + private func constraintBubbleForDoubleDirections() { + let centerY = NSLayoutConstraint(item: bubble, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0) + let centerX = NSLayoutConstraint(item: bubble, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0) + addConstraints([centerY, centerX]) + + if UIDevice.current.userInterfaceIdiom == .pad { + let maxSize: CGFloat = 420 + let width = NSLayoutConstraint(item: bubble, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: maxSize) + addConstraint(width) + } else { + let leading = NSLayoutConstraint(item: bubble, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 2 * margins) + addConstraint(leading) + } + } + + // Constraints the bubble to the target for both left and right arrow directions + private func contraintBubbleForSideDirections() { + let centerY = NSLayoutConstraint(item: bubble, attribute: .centerY, relatedBy: .equal, toItem: screenshotContainer, attribute: .centerY, multiplier: 1, constant: 0) + addConstraint(centerY) + sideShowCaseCenterY = centerY + + if arrowDirection == .left { + let leading = NSLayoutConstraint(item: bubble, attribute: .leading, relatedBy: .equal, toItem: screenshotContainer, attribute: .trailing, multiplier: 1, constant: margins + padding) + addConstraint(leading) + } else { + let trailing = NSLayoutConstraint(item: bubble, attribute: .trailing, relatedBy: .equal, toItem: screenshotContainer, attribute: .leading, multiplier: 1, constant: -(margins + padding)) + addConstraint(trailing) + } + + if UIDevice.current.userInterfaceIdiom == .pad { + let maxSize: CGFloat = 420 + let width = NSLayoutConstraint(item: bubble, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: maxSize) + addConstraint(width) + } else { + if arrowDirection == .left { + let trailing = NSLayoutConstraint(item: bubble, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: -margins) + addConstraint(trailing) + } else { + let leading = NSLayoutConstraint(item: bubble, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: margins) + addConstraint(leading) + } + } + } + + // Constraints the bubble to the target for both up and down arrow directions + private func constraintBubbleForTopDirections() { + if arrowDirection == .up { + let topConstant = margins + padding + let top = NSLayoutConstraint(item: bubble, attribute: .top, relatedBy: .equal, toItem: screenshotContainer, attribute: .bottom, multiplier: 1, constant: topConstant) + addConstraint(top) + } else { + let bottom = NSLayoutConstraint(item: bubble, attribute: .bottom, relatedBy: .equal, toItem: screenshotContainer, attribute: .top, multiplier: 1, constant: -margins - padding) + addConstraint(bottom) + } + + let leading: NSLayoutConstraint? + let centerX: NSLayoutConstraint? + if UIDevice.current.userInterfaceIdiom == .pad { + let screenWidth = UIScreen.main.bounds.width + let targetFrame = target.convert(target.bounds, to: UIApplication.shared.keyWindow!) + let leftAvailableSpace = targetFrame.midX + let rightAvailableSpace = screenWidth - targetFrame.midX + let maxSize: CGFloat = 420 + + if rightAvailableSpace > maxSize / 2, leftAvailableSpace > maxSize / 2 { + leading = nil + centerX = NSLayoutConstraint(item: bubble, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0) + } else { + centerX = nil + if leftAvailableSpace > rightAvailableSpace { + leading = NSLayoutConstraint(item: bubble, attribute: .trailing, relatedBy: .greaterThanOrEqual, toItem: self, attribute: .trailing, multiplier: 1, constant: -margins) + } else { + leading = NSLayoutConstraint(item: bubble, attribute: .leading, relatedBy: .greaterThanOrEqual, toItem: self, attribute: .leading, multiplier: 1, constant: margins) + } + } + + let width = NSLayoutConstraint(item: bubble, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: 420) + width.priority = .required + addConstraint(width) + } else { + leading = NSLayoutConstraint(item: bubble, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: margins) + centerX = NSLayoutConstraint(item: bubble, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0) + } + + if let leading = leading { addConstraint(leading) } + if let centerX = centerX { addConstraint(centerX) } + } + + // Embeds both the title and description into the show case + private func embedLabels() { + titleLabel = UILabel() + titleLabel.numberOfLines = 0 + titleLabel.text = titleText + titleLabel.font = titleFont + titleLabel.translatesAutoresizingMaskIntoConstraints = false + bubble.addSubview(titleLabel) + + descriptionLabel = UILabel() + descriptionLabel.numberOfLines = 0 + descriptionLabel.text = descriptionText + descriptionLabel.font = descriptionFont + descriptionLabel.translatesAutoresizingMaskIntoConstraints = false + bubble.addSubview(descriptionLabel) + + let descriptionLeading = NSLayoutConstraint(item: descriptionLabel, attribute: .leading, relatedBy: .equal, toItem: bubble, attribute: .leading, multiplier: 1, constant: bubblePadding.left) + self.descriptionLeading = descriptionLeading + let descriptionTrailing = NSLayoutConstraint(item: descriptionLabel, attribute: .trailing, relatedBy: .equal, toItem: bubble, attribute: .trailing, multiplier: 1, constant: -bubblePadding.right - crossArea) + let descriptionBottom = NSLayoutConstraint(item: descriptionLabel, attribute: .bottom, relatedBy: .equal, toItem: bubble, attribute: .bottom, multiplier: 1, constant: -bubblePadding.bottom) + bubble.addConstraints([descriptionLeading, descriptionTrailing, descriptionBottom]) + + let titleLeading = NSLayoutConstraint(item: titleLabel, attribute: .leading, relatedBy: .equal, toItem: descriptionLabel, attribute: .leading, multiplier: 1, constant: 0) + let titleTrailing = NSLayoutConstraint(item: titleLabel, attribute: .trailing, relatedBy: .equal, toItem: descriptionLabel, attribute: .trailing, multiplier: 1, constant: 0) + let titleTop = NSLayoutConstraint(item: titleLabel, attribute: .top, relatedBy: .equal, toItem: bubble, attribute: .top, multiplier: 1, constant: bubblePadding.top) + let titleBottom = NSLayoutConstraint(item: titleLabel, attribute: .bottom, relatedBy: .equal, toItem: descriptionLabel, attribute: .top, multiplier: 1, constant: -5) + bubble.addConstraints([titleLeading, titleTrailing, titleTop, titleBottom]) + } + + // Embeds the icon in the show case, shifting the labels rightwards + private func embedImage() { + icon = UIImageView(image: image) + icon?.translatesAutoresizingMaskIntoConstraints = false + bubble.addSubview(icon!) + + let height = NSLayoutConstraint(item: icon!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: imageSize.height) + let width = NSLayoutConstraint(item: icon!, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: imageSize.width) + icon?.addConstraints([height, width]) + + let leading = NSLayoutConstraint(item: icon!, attribute: .leading, relatedBy: .equal, toItem: bubble, attribute: .leading, multiplier: 1, constant: bubblePadding.left) + let centerY = NSLayoutConstraint(item: icon!, attribute: .centerY, relatedBy: .equal, toItem: bubble, attribute: .centerY, multiplier: 1, constant: 0) + bubble.addConstraints([leading, centerY]) + + bubble.removeConstraint(descriptionLeading) + descriptionLeading = NSLayoutConstraint(item: descriptionLabel, attribute: .leading, relatedBy: .equal, toItem: icon!, attribute: .trailing, multiplier: 1, constant: 15) + bubble.addConstraint(descriptionLeading) + } + +} diff --git a/ShowCase/ShowCase/ShowCase.podspec b/ShowCase/ShowCase/ShowCase.podspec index 8034606..98e96dc 100644 --- a/ShowCase/ShowCase/ShowCase.podspec +++ b/ShowCase/ShowCase/ShowCase.podspec @@ -1,14 +1,6 @@ -# -# Be sure to run `pod spec lint ShowCase.podspec' to ensure this is a -# valid spec and to remove all comments including this before submitting the spec. -# -# To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html -# To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ -# - Pod::Spec.new do |s| - s.name = " ShowCase" + s.name = "BubbleShowCase" s.version = "0.0.1" s.summary = "A wonderful way to show case your users your App features" @@ -17,22 +9,18 @@ Pod::Spec.new do |s| It is really simple to use, comes with animations and helps your users understand your App design. DESC - s.homepage = "https://wwww.elconfidencial.com" - # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" + s.homepage = "https://www.elconfidencial.com" s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "ElConfidencial" => "apps@elconfidencial.com", "fermoya" => "fmdr.ct@gmail.com" } s.social_media_url = "https://twitter.com/elconfidencial" s.platform = :ios s.ios.deployment_target = '9.0' + s.swift_version = '4.0' s.source = { :git => "https://github.com/ECLaboratorio/ShowCase-iOS.git", :tag => "#{s.version}" } s.source_files = "ShowCase/ShowCase/*.swift" - # s.public_header_files = "Classes/**/*.h" - - # s.resource = "icon.png" - # s.resources = "Resources/*.png" - # s.preserve_paths = "FilesToSave", "MoreFilesToSave" + s.xcconfig = { "SWIFT_VERSION" => "4.0" } end