├── .gitignore ├── .swift-version ├── BlobMenu.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── BlobMenu.xcscheme ├── Example ├── Example.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Example │ ├── App │ ├── AppDelegate.swift │ ├── Environment.swift │ ├── SceneDelegate.swift │ └── Theme.swift │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── Resources │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Ramotion.png │ │ │ ├── icon-120.png │ │ │ ├── icon-152.png │ │ │ ├── icon-167.png │ │ │ ├── icon-180.png │ │ │ ├── icon-20.png │ │ │ ├── icon-29.png │ │ │ ├── icon-40.png │ │ │ ├── icon-58.png │ │ │ ├── icon-60.png │ │ │ ├── icon-76.png │ │ │ ├── icon-80.png │ │ │ └── icon-87.png │ │ ├── Contents.json │ │ ├── Icon_Calendar_selected.imageset │ │ │ ├── Contents.json │ │ │ └── Icon_Calendar_selected.pdf │ │ ├── Icon_Calendar_unselected.imageset │ │ │ ├── Contents.json │ │ │ └── Icon_Calendar_unselected.pdf │ │ ├── Icon_Chat_selected.imageset │ │ │ ├── Contents.json │ │ │ └── Icon_Chat_selected.pdf │ │ ├── Icon_Chat_unselected.imageset │ │ │ ├── Contents.json │ │ │ └── Icon_Chat_unselected.pdf │ │ ├── Icon_Favorite_selected.imageset │ │ │ ├── Contents.json │ │ │ └── Icon_Favorite_selected.pdf │ │ ├── Icon_Favorite_unselected.imageset │ │ │ ├── Contents.json │ │ │ └── Icon_Favorite_unselected.pdf │ │ ├── Icon_Profile_selected.imageset │ │ │ ├── Contents.json │ │ │ └── Icon_Profile_selected.pdf │ │ ├── Icon_Profile_unselected.imageset │ │ │ ├── Contents.json │ │ │ └── Icon_Profile_unselected.pdf │ │ ├── Icon_swipe.imageset │ │ │ ├── Contents.json │ │ │ ├── Icon_Swipe_dark.pdf │ │ │ └── Icon_Swipe_light.pdf │ │ └── Logo.imageset │ │ │ ├── Contents.json │ │ │ ├── Logo_dark.pdf │ │ │ └── Logo_light.pdf │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ └── Info.plist │ ├── Screens │ ├── FirstView.swift │ ├── FourthView.swift │ ├── RootView.swift │ ├── SecondView.swift │ └── ThirdView.swift │ ├── UI │ └── ExtendedScrollView.swift │ └── Utilities │ ├── Screen.swift │ └── Utilities.swift ├── LICENSE ├── Package.swift ├── Promo ├── Blob-Menu-dark.gif ├── Blob-Menu-full.gif ├── Blob-Menu-light.gif └── Header.png ├── README.md ├── Sources ├── BlobMenu.h ├── Configuration │ ├── BlobMenuConfiguration.swift │ └── Theme.swift ├── Effects │ └── Transitions.swift ├── Extensions │ ├── CGPoint+Extensions.swift │ ├── CGRect+Extensions.swift │ ├── CGSize+Extensions.swift │ ├── Collection+Extensions.swift │ ├── Comparable+Extensions.swift │ ├── UIGestureRecognizer+Extensions.swift │ └── UIWindow+Extensions.swift ├── Info.plist ├── Models │ ├── BlobMenuItem.swift │ └── BlobMenuModel.swift ├── Utilities │ ├── AnimationCompletion.swift │ ├── BezierUtilities.swift │ ├── CommonUtilities.swift │ ├── KayframesAnimation.swift │ ├── ScaleKeyframesAnimation.swift │ ├── SizeKeyframesAnimation.swift │ ├── Then.swift │ └── ViewSwapper.swift └── Views │ ├── BackgroundView.swift │ ├── BlobMenuView.swift │ ├── HamburgerView.swift │ ├── MenuItemView.swift │ ├── StickyEffectView.swift │ └── StickyPathGenerator.swift └── blob-menu.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 2 | -------------------------------------------------------------------------------- /BlobMenu.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /BlobMenu.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /BlobMenu.xcodeproj/xcshareddata/xcschemes/BlobMenu.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 57 | 58 | 59 | 60 | 62 | 63 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3908003B2447D01D00E7727C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3908003A2447D01D00E7727C /* AppDelegate.swift */; }; 11 | 3908003D2447D01D00E7727C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3908003C2447D01D00E7727C /* SceneDelegate.swift */; }; 12 | 3908003F2447D01D00E7727C /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3908003E2447D01D00E7727C /* RootView.swift */; }; 13 | 390800412447D01F00E7727C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 390800402447D01F00E7727C /* Assets.xcassets */; }; 14 | 390800442447D01F00E7727C /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 390800432447D01F00E7727C /* Preview Assets.xcassets */; }; 15 | 390800472447D01F00E7727C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 390800452447D01F00E7727C /* LaunchScreen.storyboard */; }; 16 | 3908005A2447D25700E7727C /* BlobMenu.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 390800562447D23300E7727C /* BlobMenu.framework */; }; 17 | 3908005B2447D25700E7727C /* BlobMenu.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 390800562447D23300E7727C /* BlobMenu.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 18 | 390800602447D49200E7727C /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 3908005F2447D49200E7727C /* README.md */; }; 19 | 393BF69A24742B4F004D193D /* ExtendedScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 393BF69924742B4F004D193D /* ExtendedScrollView.swift */; }; 20 | 3948454224774B8A0046236D /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3948454124774B8A0046236D /* Theme.swift */; }; 21 | 3948454424774EF40046236D /* Screen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3948454324774EF40046236D /* Screen.swift */; }; 22 | 398270BF2475756E00BB7A2B /* SecondView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398270BE2475756E00BB7A2B /* SecondView.swift */; }; 23 | 398270C1247575A600BB7A2B /* ThirdView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398270C0247575A600BB7A2B /* ThirdView.swift */; }; 24 | 398270C3247575B500BB7A2B /* FourthView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398270C2247575B500BB7A2B /* FourthView.swift */; }; 25 | 39A6D75D2479E77C00E22881 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A6D75C2479E77C00E22881 /* Environment.swift */; }; 26 | 39A6D8AB246172AE0090F507 /* FirstView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A6D8AA246172AE0090F507 /* FirstView.swift */; }; 27 | 39A6D8AF24619B650090F507 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39A6D8AE24619B650090F507 /* Utilities.swift */; }; 28 | /* End PBXBuildFile section */ 29 | 30 | /* Begin PBXContainerItemProxy section */ 31 | 390800552447D23300E7727C /* PBXContainerItemProxy */ = { 32 | isa = PBXContainerItemProxy; 33 | containerPortal = 390800512447D23300E7727C /* BlobMenu.xcodeproj */; 34 | proxyType = 2; 35 | remoteGlobalIDString = 3908002324474A3800E7727C; 36 | remoteInfo = BlobMenu; 37 | }; 38 | 390800572447D24500E7727C /* PBXContainerItemProxy */ = { 39 | isa = PBXContainerItemProxy; 40 | containerPortal = 390800512447D23300E7727C /* BlobMenu.xcodeproj */; 41 | proxyType = 1; 42 | remoteGlobalIDString = 3908002224474A3800E7727C; 43 | remoteInfo = BlobMenu; 44 | }; 45 | /* End PBXContainerItemProxy section */ 46 | 47 | /* Begin PBXCopyFilesBuildPhase section */ 48 | 3908005C2447D25700E7727C /* Embed Frameworks */ = { 49 | isa = PBXCopyFilesBuildPhase; 50 | buildActionMask = 2147483647; 51 | dstPath = ""; 52 | dstSubfolderSpec = 10; 53 | files = ( 54 | 3908005B2447D25700E7727C /* BlobMenu.framework in Embed Frameworks */, 55 | ); 56 | name = "Embed Frameworks"; 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXCopyFilesBuildPhase section */ 60 | 61 | /* Begin PBXFileReference section */ 62 | 390800372447D01D00E7727C /* Blob Menu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Blob Menu.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | 3908003A2447D01D00E7727C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 64 | 3908003C2447D01D00E7727C /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 65 | 3908003E2447D01D00E7727C /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = ""; }; 66 | 390800402447D01F00E7727C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 67 | 390800432447D01F00E7727C /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 68 | 390800462447D01F00E7727C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 69 | 390800482447D01F00E7727C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 70 | 390800512447D23300E7727C /* BlobMenu.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = BlobMenu.xcodeproj; path = ../BlobMenu.xcodeproj; sourceTree = ""; }; 71 | 3908005F2447D49200E7727C /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../../../README.md; sourceTree = ""; }; 72 | 393BF69924742B4F004D193D /* ExtendedScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtendedScrollView.swift; sourceTree = ""; }; 73 | 3948454124774B8A0046236D /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; 74 | 3948454324774EF40046236D /* Screen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Screen.swift; sourceTree = ""; }; 75 | 398270BE2475756E00BB7A2B /* SecondView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondView.swift; sourceTree = ""; }; 76 | 398270C0247575A600BB7A2B /* ThirdView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdView.swift; sourceTree = ""; }; 77 | 398270C2247575B500BB7A2B /* FourthView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FourthView.swift; sourceTree = ""; }; 78 | 39A6D75C2479E77C00E22881 /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; }; 79 | 39A6D8AA246172AE0090F507 /* FirstView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstView.swift; sourceTree = ""; }; 80 | 39A6D8AE24619B650090F507 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; 81 | /* End PBXFileReference section */ 82 | 83 | /* Begin PBXFrameworksBuildPhase section */ 84 | 390800342447D01D00E7727C /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | 3908005A2447D25700E7727C /* BlobMenu.framework in Frameworks */, 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | /* End PBXFrameworksBuildPhase section */ 93 | 94 | /* Begin PBXGroup section */ 95 | 3908002E2447D01D00E7727C = { 96 | isa = PBXGroup; 97 | children = ( 98 | 390800512447D23300E7727C /* BlobMenu.xcodeproj */, 99 | 390800392447D01D00E7727C /* Example */, 100 | 390800382447D01D00E7727C /* Products */, 101 | 390800592447D25700E7727C /* Frameworks */, 102 | ); 103 | sourceTree = ""; 104 | }; 105 | 390800382447D01D00E7727C /* Products */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 390800372447D01D00E7727C /* Blob Menu.app */, 109 | ); 110 | name = Products; 111 | sourceTree = ""; 112 | }; 113 | 390800392447D01D00E7727C /* Example */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 3908004E2447D04800E7727C /* App */, 117 | 390800502447D0D500E7727C /* Screens */, 118 | 393BF69824741D33004D193D /* UI */, 119 | 39A6D8B02461AC180090F507 /* Utilities */, 120 | 3908004F2447D05A00E7727C /* Resources */, 121 | 390800422447D01F00E7727C /* Preview Content */, 122 | ); 123 | path = Example; 124 | sourceTree = ""; 125 | }; 126 | 390800422447D01F00E7727C /* Preview Content */ = { 127 | isa = PBXGroup; 128 | children = ( 129 | 390800432447D01F00E7727C /* Preview Assets.xcassets */, 130 | ); 131 | path = "Preview Content"; 132 | sourceTree = ""; 133 | }; 134 | 3908004E2447D04800E7727C /* App */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 3908003A2447D01D00E7727C /* AppDelegate.swift */, 138 | 3908003C2447D01D00E7727C /* SceneDelegate.swift */, 139 | 3948454124774B8A0046236D /* Theme.swift */, 140 | 39A6D75C2479E77C00E22881 /* Environment.swift */, 141 | ); 142 | path = App; 143 | sourceTree = ""; 144 | }; 145 | 3908004F2447D05A00E7727C /* Resources */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 3908005F2447D49200E7727C /* README.md */, 149 | 390800402447D01F00E7727C /* Assets.xcassets */, 150 | 390800452447D01F00E7727C /* LaunchScreen.storyboard */, 151 | 390800482447D01F00E7727C /* Info.plist */, 152 | ); 153 | path = Resources; 154 | sourceTree = ""; 155 | }; 156 | 390800502447D0D500E7727C /* Screens */ = { 157 | isa = PBXGroup; 158 | children = ( 159 | 3908003E2447D01D00E7727C /* RootView.swift */, 160 | 39A6D8AA246172AE0090F507 /* FirstView.swift */, 161 | 398270BE2475756E00BB7A2B /* SecondView.swift */, 162 | 398270C0247575A600BB7A2B /* ThirdView.swift */, 163 | 398270C2247575B500BB7A2B /* FourthView.swift */, 164 | ); 165 | path = Screens; 166 | sourceTree = ""; 167 | }; 168 | 390800522447D23300E7727C /* Products */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | 390800562447D23300E7727C /* BlobMenu.framework */, 172 | ); 173 | name = Products; 174 | sourceTree = ""; 175 | }; 176 | 390800592447D25700E7727C /* Frameworks */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | ); 180 | name = Frameworks; 181 | sourceTree = ""; 182 | }; 183 | 393BF69824741D33004D193D /* UI */ = { 184 | isa = PBXGroup; 185 | children = ( 186 | 393BF69924742B4F004D193D /* ExtendedScrollView.swift */, 187 | ); 188 | path = UI; 189 | sourceTree = ""; 190 | }; 191 | 39A6D8B02461AC180090F507 /* Utilities */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | 39A6D8AE24619B650090F507 /* Utilities.swift */, 195 | 3948454324774EF40046236D /* Screen.swift */, 196 | ); 197 | path = Utilities; 198 | sourceTree = ""; 199 | }; 200 | /* End PBXGroup section */ 201 | 202 | /* Begin PBXNativeTarget section */ 203 | 390800362447D01D00E7727C /* Example */ = { 204 | isa = PBXNativeTarget; 205 | buildConfigurationList = 3908004B2447D01F00E7727C /* Build configuration list for PBXNativeTarget "Example" */; 206 | buildPhases = ( 207 | 390800332447D01D00E7727C /* Sources */, 208 | 390800342447D01D00E7727C /* Frameworks */, 209 | 390800352447D01D00E7727C /* Resources */, 210 | 3908005C2447D25700E7727C /* Embed Frameworks */, 211 | ); 212 | buildRules = ( 213 | ); 214 | dependencies = ( 215 | 390800582447D24500E7727C /* PBXTargetDependency */, 216 | ); 217 | name = Example; 218 | productName = Example; 219 | productReference = 390800372447D01D00E7727C /* Blob Menu.app */; 220 | productType = "com.apple.product-type.application"; 221 | }; 222 | /* End PBXNativeTarget section */ 223 | 224 | /* Begin PBXProject section */ 225 | 3908002F2447D01D00E7727C /* Project object */ = { 226 | isa = PBXProject; 227 | attributes = { 228 | LastSwiftUpdateCheck = 1140; 229 | LastUpgradeCheck = 1140; 230 | ORGANIZATIONNAME = Ramotion; 231 | TargetAttributes = { 232 | 390800362447D01D00E7727C = { 233 | CreatedOnToolsVersion = 11.4; 234 | }; 235 | }; 236 | }; 237 | buildConfigurationList = 390800322447D01D00E7727C /* Build configuration list for PBXProject "Example" */; 238 | compatibilityVersion = "Xcode 9.3"; 239 | developmentRegion = en; 240 | hasScannedForEncodings = 0; 241 | knownRegions = ( 242 | en, 243 | Base, 244 | ); 245 | mainGroup = 3908002E2447D01D00E7727C; 246 | productRefGroup = 390800382447D01D00E7727C /* Products */; 247 | projectDirPath = ""; 248 | projectReferences = ( 249 | { 250 | ProductGroup = 390800522447D23300E7727C /* Products */; 251 | ProjectRef = 390800512447D23300E7727C /* BlobMenu.xcodeproj */; 252 | }, 253 | ); 254 | projectRoot = ""; 255 | targets = ( 256 | 390800362447D01D00E7727C /* Example */, 257 | ); 258 | }; 259 | /* End PBXProject section */ 260 | 261 | /* Begin PBXReferenceProxy section */ 262 | 390800562447D23300E7727C /* BlobMenu.framework */ = { 263 | isa = PBXReferenceProxy; 264 | fileType = wrapper.framework; 265 | path = BlobMenu.framework; 266 | remoteRef = 390800552447D23300E7727C /* PBXContainerItemProxy */; 267 | sourceTree = BUILT_PRODUCTS_DIR; 268 | }; 269 | /* End PBXReferenceProxy section */ 270 | 271 | /* Begin PBXResourcesBuildPhase section */ 272 | 390800352447D01D00E7727C /* Resources */ = { 273 | isa = PBXResourcesBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | 390800472447D01F00E7727C /* LaunchScreen.storyboard in Resources */, 277 | 390800442447D01F00E7727C /* Preview Assets.xcassets in Resources */, 278 | 390800602447D49200E7727C /* README.md in Resources */, 279 | 390800412447D01F00E7727C /* Assets.xcassets in Resources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | /* End PBXResourcesBuildPhase section */ 284 | 285 | /* Begin PBXSourcesBuildPhase section */ 286 | 390800332447D01D00E7727C /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 39A6D8AF24619B650090F507 /* Utilities.swift in Sources */, 291 | 398270C1247575A600BB7A2B /* ThirdView.swift in Sources */, 292 | 3908003B2447D01D00E7727C /* AppDelegate.swift in Sources */, 293 | 39A6D75D2479E77C00E22881 /* Environment.swift in Sources */, 294 | 398270BF2475756E00BB7A2B /* SecondView.swift in Sources */, 295 | 393BF69A24742B4F004D193D /* ExtendedScrollView.swift in Sources */, 296 | 3948454224774B8A0046236D /* Theme.swift in Sources */, 297 | 3908003D2447D01D00E7727C /* SceneDelegate.swift in Sources */, 298 | 398270C3247575B500BB7A2B /* FourthView.swift in Sources */, 299 | 3908003F2447D01D00E7727C /* RootView.swift in Sources */, 300 | 3948454424774EF40046236D /* Screen.swift in Sources */, 301 | 39A6D8AB246172AE0090F507 /* FirstView.swift in Sources */, 302 | ); 303 | runOnlyForDeploymentPostprocessing = 0; 304 | }; 305 | /* End PBXSourcesBuildPhase section */ 306 | 307 | /* Begin PBXTargetDependency section */ 308 | 390800582447D24500E7727C /* PBXTargetDependency */ = { 309 | isa = PBXTargetDependency; 310 | name = BlobMenu; 311 | targetProxy = 390800572447D24500E7727C /* PBXContainerItemProxy */; 312 | }; 313 | /* End PBXTargetDependency section */ 314 | 315 | /* Begin PBXVariantGroup section */ 316 | 390800452447D01F00E7727C /* LaunchScreen.storyboard */ = { 317 | isa = PBXVariantGroup; 318 | children = ( 319 | 390800462447D01F00E7727C /* Base */, 320 | ); 321 | name = LaunchScreen.storyboard; 322 | sourceTree = ""; 323 | }; 324 | /* End PBXVariantGroup section */ 325 | 326 | /* Begin XCBuildConfiguration section */ 327 | 390800492447D01F00E7727C /* Debug */ = { 328 | isa = XCBuildConfiguration; 329 | buildSettings = { 330 | ALWAYS_SEARCH_USER_PATHS = NO; 331 | CLANG_ANALYZER_NONNULL = YES; 332 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 333 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 334 | CLANG_CXX_LIBRARY = "libc++"; 335 | CLANG_ENABLE_MODULES = YES; 336 | CLANG_ENABLE_OBJC_ARC = YES; 337 | CLANG_ENABLE_OBJC_WEAK = YES; 338 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 339 | CLANG_WARN_BOOL_CONVERSION = YES; 340 | CLANG_WARN_COMMA = YES; 341 | CLANG_WARN_CONSTANT_CONVERSION = YES; 342 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 344 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 345 | CLANG_WARN_EMPTY_BODY = YES; 346 | CLANG_WARN_ENUM_CONVERSION = YES; 347 | CLANG_WARN_INFINITE_RECURSION = YES; 348 | CLANG_WARN_INT_CONVERSION = YES; 349 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 351 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 352 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 353 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 354 | CLANG_WARN_STRICT_PROTOTYPES = YES; 355 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 356 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 357 | CLANG_WARN_UNREACHABLE_CODE = YES; 358 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 359 | COPY_PHASE_STRIP = NO; 360 | DEBUG_INFORMATION_FORMAT = dwarf; 361 | ENABLE_STRICT_OBJC_MSGSEND = YES; 362 | ENABLE_TESTABILITY = YES; 363 | GCC_C_LANGUAGE_STANDARD = gnu11; 364 | GCC_DYNAMIC_NO_PIC = NO; 365 | GCC_NO_COMMON_BLOCKS = YES; 366 | GCC_OPTIMIZATION_LEVEL = 0; 367 | GCC_PREPROCESSOR_DEFINITIONS = ( 368 | "DEBUG=1", 369 | "$(inherited)", 370 | ); 371 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 372 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 373 | GCC_WARN_UNDECLARED_SELECTOR = YES; 374 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 375 | GCC_WARN_UNUSED_FUNCTION = YES; 376 | GCC_WARN_UNUSED_VARIABLE = YES; 377 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 378 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 379 | MTL_FAST_MATH = YES; 380 | ONLY_ACTIVE_ARCH = YES; 381 | SDKROOT = iphoneos; 382 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 383 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 384 | }; 385 | name = Debug; 386 | }; 387 | 3908004A2447D01F00E7727C /* Release */ = { 388 | isa = XCBuildConfiguration; 389 | buildSettings = { 390 | ALWAYS_SEARCH_USER_PATHS = NO; 391 | CLANG_ANALYZER_NONNULL = YES; 392 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 393 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 394 | CLANG_CXX_LIBRARY = "libc++"; 395 | CLANG_ENABLE_MODULES = YES; 396 | CLANG_ENABLE_OBJC_ARC = YES; 397 | CLANG_ENABLE_OBJC_WEAK = YES; 398 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 399 | CLANG_WARN_BOOL_CONVERSION = YES; 400 | CLANG_WARN_COMMA = YES; 401 | CLANG_WARN_CONSTANT_CONVERSION = YES; 402 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 403 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 404 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 405 | CLANG_WARN_EMPTY_BODY = YES; 406 | CLANG_WARN_ENUM_CONVERSION = YES; 407 | CLANG_WARN_INFINITE_RECURSION = YES; 408 | CLANG_WARN_INT_CONVERSION = YES; 409 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 410 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 411 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 412 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 413 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 414 | CLANG_WARN_STRICT_PROTOTYPES = YES; 415 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 416 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 417 | CLANG_WARN_UNREACHABLE_CODE = YES; 418 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 419 | COPY_PHASE_STRIP = NO; 420 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 421 | ENABLE_NS_ASSERTIONS = NO; 422 | ENABLE_STRICT_OBJC_MSGSEND = YES; 423 | GCC_C_LANGUAGE_STANDARD = gnu11; 424 | GCC_NO_COMMON_BLOCKS = YES; 425 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 426 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 427 | GCC_WARN_UNDECLARED_SELECTOR = YES; 428 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 429 | GCC_WARN_UNUSED_FUNCTION = YES; 430 | GCC_WARN_UNUSED_VARIABLE = YES; 431 | IPHONEOS_DEPLOYMENT_TARGET = 13.4; 432 | MTL_ENABLE_DEBUG_INFO = NO; 433 | MTL_FAST_MATH = YES; 434 | SDKROOT = iphoneos; 435 | SWIFT_COMPILATION_MODE = wholemodule; 436 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 437 | VALIDATE_PRODUCT = YES; 438 | }; 439 | name = Release; 440 | }; 441 | 3908004C2447D01F00E7727C /* Debug */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 445 | CODE_SIGN_STYLE = Automatic; 446 | DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; 447 | DEVELOPMENT_TEAM = 34MUF9YXTA; 448 | ENABLE_PREVIEWS = YES; 449 | INFOPLIST_FILE = Example/Resources/Info.plist; 450 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 451 | LD_RUNPATH_SEARCH_PATHS = ( 452 | "$(inherited)", 453 | "@executable_path/Frameworks", 454 | ); 455 | PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.Example; 456 | PRODUCT_NAME = "Blob Menu"; 457 | SWIFT_VERSION = 5.0; 458 | TARGETED_DEVICE_FAMILY = 1; 459 | }; 460 | name = Debug; 461 | }; 462 | 3908004D2447D01F00E7727C /* Release */ = { 463 | isa = XCBuildConfiguration; 464 | buildSettings = { 465 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 466 | CODE_SIGN_STYLE = Automatic; 467 | DEVELOPMENT_ASSET_PATHS = "\"Example/Preview Content\""; 468 | DEVELOPMENT_TEAM = 34MUF9YXTA; 469 | ENABLE_PREVIEWS = YES; 470 | INFOPLIST_FILE = Example/Resources/Info.plist; 471 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 472 | LD_RUNPATH_SEARCH_PATHS = ( 473 | "$(inherited)", 474 | "@executable_path/Frameworks", 475 | ); 476 | PRODUCT_BUNDLE_IDENTIFIER = com.ramotion.Example; 477 | PRODUCT_NAME = "Blob Menu"; 478 | SWIFT_VERSION = 5.0; 479 | TARGETED_DEVICE_FAMILY = 1; 480 | }; 481 | name = Release; 482 | }; 483 | /* End XCBuildConfiguration section */ 484 | 485 | /* Begin XCConfigurationList section */ 486 | 390800322447D01D00E7727C /* Build configuration list for PBXProject "Example" */ = { 487 | isa = XCConfigurationList; 488 | buildConfigurations = ( 489 | 390800492447D01F00E7727C /* Debug */, 490 | 3908004A2447D01F00E7727C /* Release */, 491 | ); 492 | defaultConfigurationIsVisible = 0; 493 | defaultConfigurationName = Release; 494 | }; 495 | 3908004B2447D01F00E7727C /* Build configuration list for PBXNativeTarget "Example" */ = { 496 | isa = XCConfigurationList; 497 | buildConfigurations = ( 498 | 3908004C2447D01F00E7727C /* Debug */, 499 | 3908004D2447D01F00E7727C /* Release */, 500 | ); 501 | defaultConfigurationIsVisible = 0; 502 | defaultConfigurationName = Release; 503 | }; 504 | /* End XCConfigurationList section */ 505 | }; 506 | rootObject = 3908002F2447D01D00E7727C /* Project object */; 507 | } 508 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/Example/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Example 4 | // 5 | // Created by Igor K. on 16.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import BlobMenu 11 | 12 | @UIApplicationMain 13 | final class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | 17 | return true 18 | } 19 | 20 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 21 | 22 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Example/Example/App/Environment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Environment.swift 3 | // Example 4 | // 5 | // Created by Igor K. on 24.05.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | import BlobMenu 12 | 13 | 14 | extension EnvironmentValues { 15 | var menuEnvironment: BlobMenuModel { 16 | get { return self[MenuEnvironmentKey.self] } 17 | set { self[MenuEnvironmentKey.self] = newValue } 18 | } 19 | } 20 | 21 | struct MenuEnvironmentKey: EnvironmentKey { 22 | static let defaultValue = BlobMenuModel(items: BlobMenuItem.standard) 23 | } 24 | 25 | extension BlobMenuItem { 26 | static let standard: [BlobMenuItem] = [ 27 | BlobMenuItem(selectedIcon: Image.calendarSelected, unselectedIcon: Image.calendarUnselected), 28 | BlobMenuItem(selectedIcon: Image.chatSelected, unselectedIcon: Image.chatUnselected), 29 | BlobMenuItem(selectedIcon: Image.favoriteSelected, unselectedIcon: Image.favoriteUnselected), 30 | ] 31 | 32 | static let extended: [BlobMenuItem] = 33 | standard + [BlobMenuItem(selectedIcon: Image.profileSelected, unselectedIcon: Image.profileUnselected)] 34 | } 35 | 36 | 37 | extension Image { 38 | static let calendarSelected = Image("Icon_Calendar_selected") 39 | static let calendarUnselected = Image("Icon_Calendar_unselected") 40 | static let chatSelected = Image("Icon_Chat_selected") 41 | static let chatUnselected = Image("Icon_Chat_unselected") 42 | static let favoriteSelected = Image("Icon_Favorite_selected") 43 | static let favoriteUnselected = Image("Icon_Favorite_unselected") 44 | static let profileSelected = Image("Icon_Profile_selected") 45 | static let profileUnselected = Image("Icon_Profile_unselected") 46 | } 47 | -------------------------------------------------------------------------------- /Example/Example/App/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Example 4 | // 5 | // Created by Igor K. on 16.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | final class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 17 | 18 | if let windowScene = scene as? UIWindowScene { 19 | let window = UIWindow(windowScene: windowScene) 20 | let view = RootView().environmentObject(MenuEnvironmentKey.defaultValue) 21 | window.rootViewController = UIHostingController(rootView: view) 22 | self.window = window 23 | window.makeKeyAndVisible() 24 | } 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /Example/Example/App/Theme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Theme.swift 3 | // Example 4 | // 5 | // Created by Igor K. on 22.05.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | extension Color { 13 | 14 | static var background: Color { 15 | return Color(UIColor { $0.userInterfaceStyle == .dark ? #colorLiteral(red: 0.1960526407, green: 0.1960932612, blue: 0.1960500479, alpha: 1) : #colorLiteral(red: 0.9998916984, green: 1, blue: 0.9998809695, alpha: 1) }) 16 | } 17 | 18 | static var cardBackgound: Color { 19 | return Color(UIColor { $0.userInterfaceStyle == .dark ? #colorLiteral(red: 0.2470277846, green: 0.2470766604, blue: 0.2470246851, alpha: 1) : #colorLiteral(red: 0.9998916984, green: 1, blue: 0.9998809695, alpha: 1) }) 20 | } 21 | 22 | static var shadow: Color = Color.black.opacity(0.25) 23 | 24 | static var contrastText: Color { 25 | return Color(UIColor { $0.userInterfaceStyle == .dark ? #colorLiteral(red: 0.9528377652, green: 0.9530007243, blue: 0.9528275132, alpha: 1) : #colorLiteral(red: 0.1960526407, green: 0.1960932612, blue: 0.1960500479, alpha: 1) }) 26 | } 27 | 28 | static var contrastInformation: Color { 29 | return Color(UIColor { $0.userInterfaceStyle == .dark ? #colorLiteral(red: 0.7371736169, green: 0.7373017669, blue: 0.7371655703, alpha: 1) : #colorLiteral(red: 0.5175882578, green: 0.517680943, blue: 0.5175824165, alpha: 1) }) 30 | } 31 | 32 | static var buttonText: Color { 33 | return Color(UIColor { $0.userInterfaceStyle == .dark ? #colorLiteral(red: 0.9998916984, green: 1, blue: 0.9998809695, alpha: 1) : #colorLiteral(red: 0.09132350236, green: 0.3897008598, blue: 0.8640318513, alpha: 1) }) 34 | } 35 | 36 | static var buttonBackground: Color { 37 | return Color(UIColor { $0.userInterfaceStyle == .dark ? #colorLiteral(red: 0.2744759619, green: 0.2745292187, blue: 0.2744725645, alpha: 1) : #colorLiteral(red: 0.9083589911, green: 0.9522650838, blue: 1, alpha: 1) }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Example/Example/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "icon-40.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "icon-60.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "icon-58.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "icon-87.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "icon-80.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "icon-120.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "60x60", 41 | "idiom" : "iphone", 42 | "filename" : "icon-120.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "icon-180.png", 49 | "scale" : "3x" 50 | }, 51 | { 52 | "size" : "20x20", 53 | "idiom" : "ipad", 54 | "filename" : "icon-20.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "icon-40.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "idiom" : "ipad", 66 | "filename" : "icon-29.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "icon-58.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "40x40", 77 | "idiom" : "ipad", 78 | "filename" : "icon-40.png", 79 | "scale" : "1x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "icon-80.png", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "size" : "76x76", 89 | "idiom" : "ipad", 90 | "filename" : "icon-76.png", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "icon-152.png", 97 | "scale" : "2x" 98 | }, 99 | { 100 | "size" : "83.5x83.5", 101 | "idiom" : "ipad", 102 | "filename" : "icon-167.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "1024x1024", 107 | "idiom" : "ios-marketing", 108 | "filename" : "Ramotion.png", 109 | "scale" : "1x" 110 | } 111 | ], 112 | "info" : { 113 | "version" : 1, 114 | "author" : "xcode" 115 | }, 116 | "properties" : { 117 | "pre-rendered" : true 118 | } 119 | } -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/Ramotion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/Ramotion.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-120.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-152.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-167.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-180.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-20.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-29.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-58.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-60.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-80.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Example/Example/Resources/Assets.xcassets/AppIcon.appiconset/icon-87.png -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Calendar_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon_Calendar_selected.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Calendar_selected.imageset/Icon_Calendar_selected.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | 1.000000 0.000000 -0.000000 1.000000 3.000000 20.500000 cm 14 | 0.196078 0.196078 0.196078 scn 15 | 24.000000 2.700000 m 16 | 24.000000 4.798682 22.298683 6.500000 20.200001 6.500000 c 17 | 3.800000 6.500000 l 18 | 1.701318 6.500000 0.000000 4.798682 0.000000 2.700000 c 19 | 0.000000 0.000000 l 20 | 24.000000 0.000000 l 21 | 24.000000 2.700000 l 22 | h 23 | f 24 | n 25 | Q 26 | q 27 | 1.000000 0.000000 -0.000000 1.000000 3.000000 1.000000 cm 28 | 0.196078 0.196078 0.196078 scn 29 | 3.800000 25.000000 m 30 | 20.200001 25.000000 l 31 | 20.200001 27.000000 l 32 | 3.800000 27.000000 l 33 | 3.800000 25.000000 l 34 | h 35 | 20.200001 3.000000 m 36 | 3.799999 3.000000 l 37 | 3.799999 1.000000 l 38 | 20.200001 1.000000 l 39 | 20.200001 3.000000 l 40 | h 41 | 1.000000 5.799999 m 42 | 1.000000 19.500000 l 43 | -1.000000 19.500000 l 44 | -1.000000 5.799999 l 45 | 1.000000 5.799999 l 46 | h 47 | 1.000000 19.500000 m 48 | 1.000000 22.200001 l 49 | -1.000000 22.200001 l 50 | -1.000000 19.500000 l 51 | 1.000000 19.500000 l 52 | h 53 | 23.000000 22.200001 m 54 | 23.000000 19.500000 l 55 | 25.000000 19.500000 l 56 | 25.000000 22.200001 l 57 | 23.000000 22.200001 l 58 | h 59 | 23.000000 19.500000 m 60 | 23.000000 5.800001 l 61 | 25.000000 5.800001 l 62 | 25.000000 19.500000 l 63 | 23.000000 19.500000 l 64 | h 65 | 0.000000 18.500000 m 66 | 24.000000 18.500000 l 67 | 24.000000 20.500000 l 68 | 0.000000 20.500000 l 69 | 0.000000 18.500000 l 70 | h 71 | 20.200001 1.000000 m 72 | 22.850967 1.000000 25.000000 3.149035 25.000000 5.800001 c 73 | 23.000000 5.800001 l 74 | 23.000000 4.253603 21.746397 3.000000 20.200001 3.000000 c 75 | 20.200001 1.000000 l 76 | h 77 | 20.200001 25.000000 m 78 | 21.746397 25.000000 23.000000 23.746397 23.000000 22.200001 c 79 | 25.000000 22.200001 l 80 | 25.000000 24.850967 22.850967 27.000000 20.200001 27.000000 c 81 | 20.200001 25.000000 l 82 | h 83 | 3.800000 27.000000 m 84 | 1.149033 27.000000 -1.000000 24.850967 -1.000000 22.200001 c 85 | 1.000000 22.200001 l 86 | 1.000000 23.746397 2.253603 25.000000 3.800000 25.000000 c 87 | 3.800000 27.000000 l 88 | h 89 | 3.799999 3.000000 m 90 | 2.253602 3.000000 1.000000 4.253603 1.000000 5.799999 c 91 | -1.000000 5.799999 l 92 | -1.000000 3.149033 1.149032 1.000000 3.799999 1.000000 c 93 | 3.799999 3.000000 l 94 | h 95 | f 96 | n 97 | Q 98 | 99 | endstream 100 | endobj 101 | 102 | 3 0 obj 103 | 1939 104 | endobj 105 | 106 | 4 0 obj 107 | << /Annots [] 108 | /Type /Page 109 | /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] 110 | /Resources 1 0 R 111 | /Contents 2 0 R 112 | /Parent 5 0 R 113 | >> 114 | endobj 115 | 116 | 5 0 obj 117 | << /Kids [ 4 0 R ] 118 | /Count 1 119 | /Type /Pages 120 | >> 121 | endobj 122 | 123 | 6 0 obj 124 | << /Type /Catalog 125 | /Pages 5 0 R 126 | >> 127 | endobj 128 | 129 | xref 130 | 0 7 131 | 0000000000 65535 f 132 | 0000000010 00000 n 133 | 0000000034 00000 n 134 | 0000002029 00000 n 135 | 0000002052 00000 n 136 | 0000002225 00000 n 137 | 0000002299 00000 n 138 | trailer 139 | << /ID [ (some) (id) ] 140 | /Root 6 0 R 141 | /Size 7 142 | >> 143 | startxref 144 | 2358 145 | %%EOF -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Calendar_unselected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon_Calendar_unselected.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Calendar_unselected.imageset/Icon_Calendar_unselected.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | 1.000000 0.000000 -0.000000 1.000000 3.000000 1.000000 cm 14 | 0.533333 0.533333 0.533333 scn 15 | 3.800000 25.000000 m 16 | 20.200001 25.000000 l 17 | 20.200001 27.000000 l 18 | 3.800000 27.000000 l 19 | 3.800000 25.000000 l 20 | h 21 | 20.200001 3.000000 m 22 | 3.799999 3.000000 l 23 | 3.799999 1.000000 l 24 | 20.200001 1.000000 l 25 | 20.200001 3.000000 l 26 | h 27 | 1.000000 5.799999 m 28 | 1.000000 19.500000 l 29 | -1.000000 19.500000 l 30 | -1.000000 5.799999 l 31 | 1.000000 5.799999 l 32 | h 33 | 1.000000 19.500000 m 34 | 1.000000 22.200001 l 35 | -1.000000 22.200001 l 36 | -1.000000 19.500000 l 37 | 1.000000 19.500000 l 38 | h 39 | 23.000000 22.200001 m 40 | 23.000000 19.500000 l 41 | 25.000000 19.500000 l 42 | 25.000000 22.200001 l 43 | 23.000000 22.200001 l 44 | h 45 | 23.000000 19.500000 m 46 | 23.000000 5.800001 l 47 | 25.000000 5.800001 l 48 | 25.000000 19.500000 l 49 | 23.000000 19.500000 l 50 | h 51 | 0.000000 18.500000 m 52 | 24.000000 18.500000 l 53 | 24.000000 20.500000 l 54 | 0.000000 20.500000 l 55 | 0.000000 18.500000 l 56 | h 57 | 20.200001 1.000000 m 58 | 22.850967 1.000000 25.000000 3.149035 25.000000 5.800001 c 59 | 23.000000 5.800001 l 60 | 23.000000 4.253603 21.746397 3.000000 20.200001 3.000000 c 61 | 20.200001 1.000000 l 62 | h 63 | 20.200001 25.000000 m 64 | 21.746397 25.000000 23.000000 23.746397 23.000000 22.200001 c 65 | 25.000000 22.200001 l 66 | 25.000000 24.850967 22.850967 27.000000 20.200001 27.000000 c 67 | 20.200001 25.000000 l 68 | h 69 | 3.800000 27.000000 m 70 | 1.149033 27.000000 -1.000000 24.850967 -1.000000 22.200001 c 71 | 1.000000 22.200001 l 72 | 1.000000 23.746397 2.253603 25.000000 3.800000 25.000000 c 73 | 3.800000 27.000000 l 74 | h 75 | 3.799999 3.000000 m 76 | 2.253602 3.000000 1.000000 4.253603 1.000000 5.799999 c 77 | -1.000000 5.799999 l 78 | -1.000000 3.149033 1.149032 1.000000 3.799999 1.000000 c 79 | 3.799999 3.000000 l 80 | h 81 | f 82 | n 83 | Q 84 | 85 | endstream 86 | endobj 87 | 88 | 3 0 obj 89 | 1621 90 | endobj 91 | 92 | 4 0 obj 93 | << /Annots [] 94 | /Type /Page 95 | /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] 96 | /Resources 1 0 R 97 | /Contents 2 0 R 98 | /Parent 5 0 R 99 | >> 100 | endobj 101 | 102 | 5 0 obj 103 | << /Kids [ 4 0 R ] 104 | /Count 1 105 | /Type /Pages 106 | >> 107 | endobj 108 | 109 | 6 0 obj 110 | << /Type /Catalog 111 | /Pages 5 0 R 112 | >> 113 | endobj 114 | 115 | xref 116 | 0 7 117 | 0000000000 65535 f 118 | 0000000010 00000 n 119 | 0000000034 00000 n 120 | 0000001711 00000 n 121 | 0000001734 00000 n 122 | 0000001907 00000 n 123 | 0000001981 00000 n 124 | trailer 125 | << /ID [ (some) (id) ] 126 | /Root 6 0 R 127 | /Size 7 128 | >> 129 | startxref 130 | 2040 131 | %%EOF -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Chat_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon_Chat_selected.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Chat_selected.imageset/Icon_Chat_selected.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | 1.000000 0.000000 -0.000000 1.000000 1.000000 1.093994 cm 14 | 0.196078 0.196078 0.196078 scn 15 | 0.000000 22.906006 m 16 | 0.000000 25.115145 1.790861 26.906006 4.000000 26.906006 c 17 | 24.000000 26.906006 l 18 | 26.209139 26.906006 28.000000 25.115145 28.000000 22.906006 c 19 | 28.000000 8.906006 l 20 | 28.000000 6.696867 26.209139 4.906006 24.000000 4.906006 c 21 | 15.250000 4.906006 l 22 | 8.555461 0.442980 l 23 | 7.890906 -0.000057 7.000000 0.475828 7.000000 1.274523 c 24 | 7.000000 4.906006 l 25 | 4.000000 4.906006 l 26 | 1.790861 4.906006 0.000000 6.696867 0.000000 8.906006 c 27 | 0.000000 22.906006 l 28 | h 29 | 8.000000 18.906006 m 30 | 8.000000 19.458290 8.447716 19.906006 9.000000 19.906006 c 31 | 19.000000 19.906006 l 32 | 19.552284 19.906006 20.000000 19.458290 20.000000 18.906006 c 33 | 20.000000 18.353722 19.552284 17.906006 19.000000 17.906006 c 34 | 9.000000 17.906006 l 35 | 8.447715 17.906006 8.000000 18.353722 8.000000 18.906006 c 36 | h 37 | 8.000000 12.906006 m 38 | 8.000000 13.458290 8.447716 13.906006 9.000000 13.906006 c 39 | 19.000000 13.906006 l 40 | 19.552284 13.906006 20.000000 13.458290 20.000000 12.906006 c 41 | 20.000000 12.353722 19.552284 11.906006 19.000000 11.906006 c 42 | 9.000000 11.906006 l 43 | 8.447715 11.906006 8.000000 12.353722 8.000000 12.906006 c 44 | h 45 | f* 46 | n 47 | Q 48 | 49 | endstream 50 | endobj 51 | 52 | 3 0 obj 53 | 1203 54 | endobj 55 | 56 | 4 0 obj 57 | << /Annots [] 58 | /Type /Page 59 | /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] 60 | /Resources 1 0 R 61 | /Contents 2 0 R 62 | /Parent 5 0 R 63 | >> 64 | endobj 65 | 66 | 5 0 obj 67 | << /Kids [ 4 0 R ] 68 | /Count 1 69 | /Type /Pages 70 | >> 71 | endobj 72 | 73 | 6 0 obj 74 | << /Type /Catalog 75 | /Pages 5 0 R 76 | >> 77 | endobj 78 | 79 | xref 80 | 0 7 81 | 0000000000 65535 f 82 | 0000000010 00000 n 83 | 0000000034 00000 n 84 | 0000001293 00000 n 85 | 0000001316 00000 n 86 | 0000001489 00000 n 87 | 0000001563 00000 n 88 | trailer 89 | << /ID [ (some) (id) ] 90 | /Root 6 0 R 91 | /Size 7 92 | >> 93 | startxref 94 | 1622 95 | %%EOF -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Chat_unselected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon_Chat_unselected.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Chat_unselected.imageset/Icon_Chat_unselected.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /BBox [ 0.000000 0.000000 30.000000 30.000000 ] 5 | /Resources << >> 6 | /Subtype /Form 7 | /Length 2 0 R 8 | /Group << /Type /Group 9 | /S /Transparency 10 | >> 11 | /Type /XObject 12 | >> 13 | stream 14 | /DeviceRGB CS 15 | /DeviceRGB cs 16 | q 17 | 1.000000 0.000000 -0.000000 1.000000 0.000000 27.000000 cm 18 | 0.533333 0.533333 0.533333 scn 19 | 9.000000 -7.000000 m 20 | 9.000000 -6.447715 9.447716 -6.000000 10.000000 -6.000000 c 21 | 20.000000 -6.000000 l 22 | 20.552284 -6.000000 21.000000 -6.447715 21.000000 -7.000000 c 23 | 21.000000 -7.552284 20.552284 -8.000000 20.000000 -8.000000 c 24 | 10.000000 -8.000000 l 25 | 9.447715 -8.000000 9.000000 -7.552284 9.000000 -7.000000 c 26 | h 27 | f 28 | n 29 | Q 30 | q 31 | 1.000000 0.000000 -0.000000 1.000000 0.000000 27.000000 cm 32 | 0.533333 0.533333 0.533333 scn 33 | 10.000000 -12.000000 m 34 | 9.447716 -12.000000 9.000000 -12.447715 9.000000 -13.000000 c 35 | 9.000000 -13.552284 9.447715 -14.000000 10.000000 -14.000000 c 36 | 20.000000 -14.000000 l 37 | 20.552284 -14.000000 21.000000 -13.552284 21.000000 -13.000000 c 38 | 21.000000 -12.447715 20.552284 -12.000000 20.000000 -12.000000 c 39 | 10.000000 -12.000000 l 40 | h 41 | f 42 | n 43 | Q 44 | q 45 | 1.000000 0.000000 -0.000000 1.000000 0.000000 -0.198975 cm 46 | 0.533333 0.533333 0.533333 scn 47 | 30.000000 24.198975 m 48 | 30.000000 26.960398 27.761423 29.198975 25.000000 29.198975 c 49 | 5.000000 29.198975 l 50 | 2.238577 29.198975 0.000000 26.960398 0.000000 24.198975 c 51 | 0.000000 10.198975 l 52 | 0.000000 7.437551 2.238575 5.198973 5.000000 5.198973 c 53 | 7.000000 5.198973 l 54 | 7.000000 2.541660 l 55 | 7.000000 0.954786 8.759470 -0.000057 10.089977 0.864773 c 56 | 16.757977 5.198973 l 57 | 25.000000 5.198973 l 58 | 27.761423 5.198973 30.000000 7.437550 30.000000 10.198973 c 59 | 30.000000 24.198975 l 60 | h 61 | 25.000000 27.198975 m 62 | 26.656855 27.198975 28.000000 25.855829 28.000000 24.198975 c 63 | 28.000000 10.198973 l 64 | 28.000000 8.542118 26.656855 7.198973 25.000000 7.198973 c 65 | 16.461536 7.198973 l 66 | 16.268070 7.198973 16.078758 7.142853 15.916547 7.037416 c 67 | 9.000000 2.541660 l 68 | 9.000000 6.198973 l 69 | 9.000000 6.751257 8.552284 7.198973 8.000000 7.198973 c 70 | 5.000000 7.198973 l 71 | 3.343147 7.198973 2.000000 8.542120 2.000000 10.198975 c 72 | 2.000000 24.198975 l 73 | 2.000000 25.855829 3.343145 27.198975 5.000000 27.198975 c 74 | 25.000000 27.198975 l 75 | h 76 | f* 77 | n 78 | Q 79 | 80 | endstream 81 | endobj 82 | 83 | 2 0 obj 84 | 1946 85 | endobj 86 | 87 | 3 0 obj 88 | << /BBox [ 0.000000 0.000000 30.000000 30.000000 ] 89 | /Resources << >> 90 | /Subtype /Form 91 | /Length 4 0 R 92 | /Group << /Type /Group 93 | /S /Transparency 94 | >> 95 | /Type /XObject 96 | >> 97 | stream 98 | /DeviceRGB CS 99 | /DeviceRGB cs 100 | q 101 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 102 | 0.000000 0.000000 0.000000 scn 103 | 0.000000 30.000000 m 104 | 30.000000 30.000000 l 105 | 30.000000 0.000000 l 106 | 0.000000 0.000000 l 107 | 0.000000 30.000000 l 108 | h 109 | f 110 | n 111 | Q 112 | 113 | endstream 114 | endobj 115 | 116 | 4 0 obj 117 | 232 118 | endobj 119 | 120 | 5 0 obj 121 | << /XObject << /X1 1 0 R >> 122 | /ExtGState << /E1 << /SMask << /Type /Mask 123 | /G 3 0 R 124 | /S /Alpha 125 | >> 126 | /Type /ExtGState 127 | >> >> 128 | >> 129 | endobj 130 | 131 | 6 0 obj 132 | << /Length 7 0 R >> 133 | stream 134 | /DeviceRGB CS 135 | /DeviceRGB cs 136 | q 137 | /E1 gs 138 | /X1 Do 139 | Q 140 | 141 | endstream 142 | endobj 143 | 144 | 7 0 obj 145 | 46 146 | endobj 147 | 148 | 8 0 obj 149 | << /Annots [] 150 | /Type /Page 151 | /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] 152 | /Resources 5 0 R 153 | /Contents 6 0 R 154 | /Parent 9 0 R 155 | >> 156 | endobj 157 | 158 | 9 0 obj 159 | << /Kids [ 8 0 R ] 160 | /Count 1 161 | /Type /Pages 162 | >> 163 | endobj 164 | 165 | 10 0 obj 166 | << /Type /Catalog 167 | /Pages 9 0 R 168 | >> 169 | endobj 170 | 171 | xref 172 | 0 11 173 | 0000000000 65535 f 174 | 0000000010 00000 n 175 | 0000002204 00000 n 176 | 0000002227 00000 n 177 | 0000002707 00000 n 178 | 0000002729 00000 n 179 | 0000003027 00000 n 180 | 0000003129 00000 n 181 | 0000003150 00000 n 182 | 0000003323 00000 n 183 | 0000003397 00000 n 184 | trailer 185 | << /ID [ (some) (id) ] 186 | /Root 10 0 R 187 | /Size 11 188 | >> 189 | startxref 190 | 3457 191 | %%EOF -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Favorite_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon_Favorite_selected.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Favorite_selected.imageset/Icon_Favorite_selected.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | 1.000000 0.000000 -0.000000 1.000000 1.229797 1.398682 cm 14 | 0.196078 0.196078 0.196078 scn 15 | 18.626476 19.285370 m 16 | 15.106291 26.183186 l 17 | 14.549581 27.274061 12.990855 27.274063 12.434144 26.183186 c 18 | 8.913960 19.285370 l 19 | 1.265949 18.069017 l 20 | 0.056432 17.876654 -0.425244 16.394220 0.440210 15.527656 c 21 | 5.912626 10.048218 l 22 | 4.706082 2.398655 l 23 | 4.515268 1.188887 5.776306 0.272697 6.867893 0.828007 c 24 | 13.770217 4.339342 l 25 | 20.672541 0.828007 l 26 | 21.764126 0.272697 23.025166 1.188887 22.834352 2.398655 c 27 | 21.627808 10.048220 l 28 | 27.100224 15.527655 l 29 | 27.965673 16.394215 27.484007 17.876652 26.274487 18.069017 c 30 | 18.626476 19.285370 l 31 | h 32 | f* 33 | n 34 | Q 35 | 36 | endstream 37 | endobj 38 | 39 | 3 0 obj 40 | 662 41 | endobj 42 | 43 | 4 0 obj 44 | << /Annots [] 45 | /Type /Page 46 | /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] 47 | /Resources 1 0 R 48 | /Contents 2 0 R 49 | /Parent 5 0 R 50 | >> 51 | endobj 52 | 53 | 5 0 obj 54 | << /Kids [ 4 0 R ] 55 | /Count 1 56 | /Type /Pages 57 | >> 58 | endobj 59 | 60 | 6 0 obj 61 | << /Type /Catalog 62 | /Pages 5 0 R 63 | >> 64 | endobj 65 | 66 | xref 67 | 0 7 68 | 0000000000 65535 f 69 | 0000000010 00000 n 70 | 0000000034 00000 n 71 | 0000000752 00000 n 72 | 0000000774 00000 n 73 | 0000000947 00000 n 74 | 0000001021 00000 n 75 | trailer 76 | << /ID [ (some) (id) ] 77 | /Root 6 0 R 78 | /Size 7 79 | >> 80 | startxref 81 | 1080 82 | %%EOF -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Favorite_unselected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon_Favorite_unselected.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Favorite_unselected.imageset/Icon_Favorite_unselected.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /BBox [ 0.000000 0.000000 30.000000 30.000000 ] 5 | /Resources << >> 6 | /Subtype /Form 7 | /Length 2 0 R 8 | /Group << /Type /Group 9 | /S /Transparency 10 | >> 11 | /Type /XObject 12 | >> 13 | stream 14 | /DeviceRGB CS 15 | /DeviceRGB cs 16 | q 17 | 1.000000 0.000000 -0.000000 1.000000 0.774353 0.123535 cm 18 | 0.533333 0.533333 0.533333 scn 19 | 19.741375 21.468185 m 20 | 16.897787 27.040199 l 21 | 15.784368 29.221949 12.666919 29.221954 11.553496 27.040199 c 22 | 8.709911 21.468185 l 23 | 2.531898 20.485624 l 24 | 0.112862 20.100897 -0.850486 17.136028 0.880420 15.402901 c 25 | 5.301004 10.976645 l 26 | 4.326365 4.797375 l 27 | 3.944739 2.377848 6.466807 0.545458 8.649986 1.656080 c 28 | 14.225642 4.492516 l 29 | 19.801298 1.656080 l 30 | 21.984478 0.545456 24.506544 2.377850 24.124918 4.797375 c 31 | 23.150280 10.976643 l 32 | 27.570864 15.402901 l 33 | 29.301765 17.136023 28.338430 20.100895 25.919388 20.485624 c 34 | 19.741375 21.468185 l 35 | h 36 | 15.116357 26.131075 m 37 | 14.745216 26.858326 13.706067 26.858326 13.334927 26.131075 c 38 | 10.260098 20.105938 l 39 | 10.114797 19.821218 9.842136 19.623119 9.526451 19.572912 c 40 | 2.846033 18.510448 l 41 | 2.039687 18.382206 1.718572 17.393915 2.295540 16.816208 c 42 | 7.075611 12.030003 l 43 | 7.301495 11.803829 7.405642 11.483295 7.355840 11.167547 c 44 | 6.301941 4.485773 l 45 | 6.174733 3.679264 7.015422 3.068468 7.743148 3.438675 c 46 | 13.772223 6.505774 l 47 | 14.057128 6.650711 14.394156 6.650711 14.679061 6.505774 c 48 | 20.708136 3.438675 l 49 | 21.435862 3.068468 22.276550 3.679264 22.149342 4.485773 c 50 | 21.095444 11.167547 l 51 | 21.045641 11.483295 21.149790 11.803829 21.375673 12.030003 c 52 | 26.155745 16.816208 l 53 | 26.732714 17.393915 26.411598 18.382206 25.605253 18.510448 c 54 | 18.924833 19.572912 l 55 | 18.609148 19.623119 18.336489 19.821218 18.191187 20.105938 c 56 | 15.116357 26.131075 l 57 | h 58 | f* 59 | n 60 | Q 61 | 62 | endstream 63 | endobj 64 | 65 | 2 0 obj 66 | 1500 67 | endobj 68 | 69 | 3 0 obj 70 | << /BBox [ 0.000000 0.000000 30.000000 30.000000 ] 71 | /Resources << >> 72 | /Subtype /Form 73 | /Length 4 0 R 74 | /Group << /Type /Group 75 | /S /Transparency 76 | >> 77 | /Type /XObject 78 | >> 79 | stream 80 | /DeviceRGB CS 81 | /DeviceRGB cs 82 | q 83 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 84 | 0.000000 0.000000 0.000000 scn 85 | 0.000000 30.000000 m 86 | 30.000000 30.000000 l 87 | 30.000000 0.000000 l 88 | 0.000000 0.000000 l 89 | 0.000000 30.000000 l 90 | h 91 | f 92 | n 93 | Q 94 | 95 | endstream 96 | endobj 97 | 98 | 4 0 obj 99 | 232 100 | endobj 101 | 102 | 5 0 obj 103 | << /XObject << /X1 1 0 R >> 104 | /ExtGState << /E1 << /SMask << /Type /Mask 105 | /G 3 0 R 106 | /S /Alpha 107 | >> 108 | /Type /ExtGState 109 | >> >> 110 | >> 111 | endobj 112 | 113 | 6 0 obj 114 | << /Length 7 0 R >> 115 | stream 116 | /DeviceRGB CS 117 | /DeviceRGB cs 118 | q 119 | /E1 gs 120 | /X1 Do 121 | Q 122 | 123 | endstream 124 | endobj 125 | 126 | 7 0 obj 127 | 46 128 | endobj 129 | 130 | 8 0 obj 131 | << /Annots [] 132 | /Type /Page 133 | /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] 134 | /Resources 5 0 R 135 | /Contents 6 0 R 136 | /Parent 9 0 R 137 | >> 138 | endobj 139 | 140 | 9 0 obj 141 | << /Kids [ 8 0 R ] 142 | /Count 1 143 | /Type /Pages 144 | >> 145 | endobj 146 | 147 | 10 0 obj 148 | << /Type /Catalog 149 | /Pages 9 0 R 150 | >> 151 | endobj 152 | 153 | xref 154 | 0 11 155 | 0000000000 65535 f 156 | 0000000010 00000 n 157 | 0000001758 00000 n 158 | 0000001781 00000 n 159 | 0000002261 00000 n 160 | 0000002283 00000 n 161 | 0000002581 00000 n 162 | 0000002683 00000 n 163 | 0000002704 00000 n 164 | 0000002877 00000 n 165 | 0000002951 00000 n 166 | trailer 167 | << /ID [ (some) (id) ] 168 | /Root 10 0 R 169 | /Size 11 170 | >> 171 | startxref 172 | 3011 173 | %%EOF -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Profile_selected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon_Profile_selected.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Profile_selected.imageset/Icon_Profile_selected.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | 1.000000 0.000000 -0.000000 1.000000 3.000000 17.000000 cm 14 | 0.196078 0.196078 0.196078 scn 15 | 18.000000 6.000000 m 16 | 18.000000 2.686292 15.313708 0.000000 12.000000 0.000000 c 17 | 8.686292 0.000000 6.000000 2.686292 6.000000 6.000000 c 18 | 6.000000 9.313708 8.686292 12.000000 12.000000 12.000000 c 19 | 15.313708 12.000000 18.000000 9.313708 18.000000 6.000000 c 20 | h 21 | f 22 | n 23 | Q 24 | q 25 | 1.000000 0.000000 -0.000000 1.000000 3.000000 17.000000 cm 26 | 0.196078 0.196078 0.196078 scn 27 | 0.000000 -10.000000 m 28 | 0.000000 -6.134007 3.134007 -3.000000 7.000000 -3.000000 c 29 | 17.000000 -3.000000 l 30 | 20.865993 -3.000000 24.000000 -6.134007 24.000000 -10.000000 c 31 | 24.000000 -12.000000 l 32 | 24.000000 -13.656855 22.656855 -15.000000 21.000000 -15.000000 c 33 | 3.000000 -15.000000 l 34 | 1.343146 -15.000000 0.000000 -13.656855 0.000000 -12.000000 c 35 | 0.000000 -10.000000 l 36 | h 37 | f 38 | n 39 | Q 40 | 41 | endstream 42 | endobj 43 | 44 | 3 0 obj 45 | 843 46 | endobj 47 | 48 | 4 0 obj 49 | << /Annots [] 50 | /Type /Page 51 | /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] 52 | /Resources 1 0 R 53 | /Contents 2 0 R 54 | /Parent 5 0 R 55 | >> 56 | endobj 57 | 58 | 5 0 obj 59 | << /Kids [ 4 0 R ] 60 | /Count 1 61 | /Type /Pages 62 | >> 63 | endobj 64 | 65 | 6 0 obj 66 | << /Type /Catalog 67 | /Pages 5 0 R 68 | >> 69 | endobj 70 | 71 | xref 72 | 0 7 73 | 0000000000 65535 f 74 | 0000000010 00000 n 75 | 0000000034 00000 n 76 | 0000000933 00000 n 77 | 0000000955 00000 n 78 | 0000001128 00000 n 79 | 0000001202 00000 n 80 | trailer 81 | << /ID [ (some) (id) ] 82 | /Root 6 0 R 83 | /Size 7 84 | >> 85 | startxref 86 | 1261 87 | %%EOF -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Profile_unselected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon_Profile_unselected.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | }, 12 | "properties" : { 13 | "preserves-vector-representation" : true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_Profile_unselected.imageset/Icon_Profile_unselected.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | 1.000000 0.000000 -0.000000 1.000000 2.000000 16.000000 cm 14 | 0.533333 0.533333 0.533333 scn 15 | 20.000000 7.000000 m 16 | 20.000000 3.134007 16.865993 0.000000 13.000000 0.000000 c 17 | 9.134007 0.000000 6.000000 3.134007 6.000000 7.000000 c 18 | 6.000000 10.865993 9.134007 14.000000 13.000000 14.000000 c 19 | 16.865993 14.000000 20.000000 10.865993 20.000000 7.000000 c 20 | h 21 | 18.000000 7.000000 m 22 | 18.000000 4.238576 15.761424 2.000000 13.000000 2.000000 c 23 | 10.238577 2.000000 8.000000 4.238576 8.000000 7.000000 c 24 | 8.000000 9.761423 10.238577 12.000000 13.000000 12.000000 c 25 | 15.761424 12.000000 18.000000 9.761423 18.000000 7.000000 c 26 | h 27 | f* 28 | n 29 | Q 30 | q 31 | 1.000000 0.000000 -0.000000 1.000000 2.000000 16.000000 cm 32 | 0.533333 0.533333 0.533333 scn 33 | 0.000000 -10.000000 m 34 | 0.000000 -5.581722 3.581722 -2.000000 8.000000 -2.000000 c 35 | 18.000000 -2.000000 l 36 | 22.418278 -2.000000 26.000000 -5.581722 26.000000 -10.000000 c 37 | 26.000000 -12.000000 l 38 | 26.000000 -14.209139 24.209139 -16.000000 22.000000 -16.000000 c 39 | 4.000000 -16.000000 l 40 | 1.790861 -16.000000 0.000000 -14.209139 0.000000 -12.000000 c 41 | 0.000000 -10.000000 l 42 | h 43 | 2.000000 -10.000000 m 44 | 2.000000 -6.686291 4.686292 -4.000000 8.000000 -4.000000 c 45 | 18.000000 -4.000000 l 46 | 21.313709 -4.000000 24.000000 -6.686291 24.000000 -10.000000 c 47 | 24.000000 -12.000000 l 48 | 24.000000 -13.104568 23.104570 -14.000000 22.000000 -14.000000 c 49 | 4.000000 -14.000000 l 50 | 2.895431 -14.000000 2.000000 -13.104568 2.000000 -12.000000 c 51 | 2.000000 -10.000000 l 52 | h 53 | f* 54 | n 55 | Q 56 | 57 | endstream 58 | endobj 59 | 60 | 3 0 obj 61 | 1468 62 | endobj 63 | 64 | 4 0 obj 65 | << /Annots [] 66 | /Type /Page 67 | /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] 68 | /Resources 1 0 R 69 | /Contents 2 0 R 70 | /Parent 5 0 R 71 | >> 72 | endobj 73 | 74 | 5 0 obj 75 | << /Kids [ 4 0 R ] 76 | /Count 1 77 | /Type /Pages 78 | >> 79 | endobj 80 | 81 | 6 0 obj 82 | << /Type /Catalog 83 | /Pages 5 0 R 84 | >> 85 | endobj 86 | 87 | xref 88 | 0 7 89 | 0000000000 65535 f 90 | 0000000010 00000 n 91 | 0000000034 00000 n 92 | 0000001558 00000 n 93 | 0000001581 00000 n 94 | 0000001754 00000 n 95 | 0000001828 00000 n 96 | trailer 97 | << /ID [ (some) (id) ] 98 | /Root 6 0 R 99 | /Size 7 100 | >> 101 | startxref 102 | 1887 103 | %%EOF -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_swipe.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Icon_Swipe_light.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "Icon_Swipe_dark.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | }, 22 | "properties" : { 23 | "preserves-vector-representation" : true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_swipe.imageset/Icon_Swipe_dark.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | 1.000000 0.000000 -0.000000 1.000000 107.597168 19.904053 cm 14 | 0.629167 0.629167 0.629167 scn 15 | 30.101513 3.293037 m 16 | 30.101513 2.464615 30.773085 1.793037 31.601513 1.793037 c 17 | 32.429939 1.793037 33.101513 2.464615 33.101513 3.293037 c 18 | 30.101513 3.293037 l 19 | h 20 | 25.067957 42.223915 m 21 | 23.674679 41.668217 l 22 | 23.676331 41.664108 l 23 | 25.067957 42.223915 l 24 | h 25 | 14.178696 65.718819 m 26 | 15.513352 66.403534 l 27 | 15.507772 66.414200 l 28 | 14.178696 65.718819 l 29 | h 30 | 4.098353 68.942398 m 31 | 3.422246 70.281395 l 32 | 3.416876 70.278656 l 33 | 4.098353 68.942398 l 34 | h 35 | 0.800461 58.899719 m 36 | 2.141096 59.572552 l 37 | 2.139977 59.574772 l 38 | 0.800461 58.899719 l 39 | h 40 | 2.633274 51.903721 m 41 | 3.004868 51.163307 3.906327 50.864326 4.646739 51.235920 c 42 | 5.387150 51.607513 5.686135 52.508972 5.314540 53.249382 c 43 | 2.633274 51.903721 l 44 | h 45 | 33.101513 3.293037 m 46 | 33.101513 9.816757 33.109821 26.251789 26.459581 42.783718 c 47 | 23.676331 41.664108 l 48 | 30.093204 25.712311 30.101513 9.787609 30.101513 3.293037 c 49 | 33.101513 3.293037 l 50 | h 51 | 26.461227 42.779610 m 52 | 22.700573 52.208557 18.380539 60.814678 15.513309 66.403519 c 53 | 12.844082 65.034126 l 54 | 15.701491 59.464432 19.968418 50.960808 23.674685 41.668221 c 55 | 26.461227 42.779610 l 56 | h 57 | 15.507772 66.414200 m 58 | 14.408175 68.515862 12.522062 70.099152 10.260141 70.822487 c 59 | 9.346363 67.965034 l 60 | 10.858726 67.481400 12.116976 66.423737 12.849618 65.023438 c 61 | 15.507772 66.414200 l 62 | h 63 | 10.260141 70.822487 m 64 | 7.998360 71.545776 5.541566 71.351501 3.422251 70.281387 c 65 | 4.774456 67.603409 l 66 | 6.190899 68.318626 7.833860 68.448715 9.346363 67.965034 c 67 | 10.260141 70.822487 l 68 | h 69 | 3.416876 70.278656 m 70 | 1.296715 69.197395 -0.310238 67.324211 -1.051887 65.065750 c 71 | 1.798364 64.129768 l 72 | 2.292186 65.633545 3.363156 66.883652 4.779831 67.606140 c 73 | 3.416876 70.278656 l 74 | h 75 | -1.051887 65.065750 m 76 | -1.793585 62.807144 -1.609107 60.347977 -0.539054 58.224663 c 77 | 2.139977 59.574772 l 78 | 1.427231 60.989079 1.304591 62.626137 1.798364 64.129768 c 79 | -1.051887 65.065750 l 80 | h 81 | -0.540172 58.226887 m 82 | 2.633274 51.903721 l 83 | 5.314540 53.249382 l 84 | 2.141094 59.572548 l 85 | -0.540172 58.226887 l 86 | h 87 | f 88 | n 89 | Q 90 | q 91 | 1.000000 0.000000 -0.000000 1.000000 88.944702 66.358154 cm 92 | 0.629167 0.629167 0.629167 scn 93 | 4.181628 3.823135 m 94 | 4.561399 3.086884 5.466113 2.797899 6.202364 3.177670 c 95 | 6.938615 3.557440 7.227600 4.462154 6.847828 5.198406 c 96 | 4.181628 3.823135 l 97 | h 98 | 0.910119 13.437599 m 99 | 2.243223 14.125237 l 100 | 2.241588 14.128388 l 101 | 0.910119 13.437599 l 102 | h 103 | 14.848384 21.248571 m 104 | 13.539284 20.516172 l 105 | 13.545394 20.505459 l 106 | 14.848384 21.248571 l 107 | h 108 | 16.403994 18.520935 m 109 | 17.743010 19.196978 l 110 | 17.725847 19.230968 l 111 | 17.706984 19.264048 l 112 | 16.403994 18.520935 l 113 | h 114 | 20.291819 7.492249 m 115 | 20.665186 6.752731 21.567360 6.455908 22.306877 6.829275 c 116 | 23.046396 7.202642 23.343220 8.104816 22.969852 8.844336 c 117 | 20.291819 7.492249 l 118 | h 119 | 6.847828 5.198406 m 120 | 2.243219 14.125236 l 121 | -0.422982 12.749964 l 122 | 4.181628 3.823135 l 123 | 6.847828 5.198406 l 124 | h 125 | 2.241588 14.128388 m 126 | 1.484908 15.586862 1.299585 17.272894 1.721112 18.859379 c 127 | -1.178290 19.629747 l 128 | -1.791699 17.321083 -1.521738 14.867766 -0.421351 12.746811 c 129 | 2.241588 14.128388 l 130 | h 131 | 1.721112 18.859379 m 132 | 2.142666 20.445965 3.141511 21.821243 4.525321 22.717007 c 133 | 2.895110 25.235422 l 134 | 0.887509 23.935867 -0.564908 21.938311 -1.178290 19.629747 c 135 | 1.721112 18.859379 l 136 | h 137 | 4.499123 22.700430 m 138 | 7.435656 24.516294 11.871108 23.497993 13.539328 20.516197 c 139 | 16.157440 21.980946 l 140 | 13.594401 26.562155 7.202802 27.899548 2.921308 25.251999 c 141 | 4.499123 22.700430 l 142 | h 143 | 13.545394 20.505459 m 144 | 15.101003 17.777821 l 145 | 17.706984 19.264048 l 146 | 16.151373 21.991684 l 147 | 13.545394 20.505459 l 148 | h 149 | 15.064976 17.844891 m 150 | 20.291819 7.492249 l 151 | 22.969852 8.844336 l 152 | 17.743010 19.196978 l 153 | 15.064976 17.844891 l 154 | h 155 | f 156 | n 157 | Q 158 | q 159 | 1.000000 0.000000 -0.000000 1.000000 70.995850 57.220703 cm 160 | 0.629167 0.629167 0.629167 scn 161 | 8.562653 2.799356 m 162 | 8.939391 2.061548 9.842909 1.768845 10.580716 2.145582 c 163 | 11.318523 2.522322 11.611228 3.425838 11.234490 4.163647 c 164 | 8.562653 2.799356 l 165 | h 166 | 0.813819 21.273163 m 167 | 2.149764 21.955322 l 168 | 2.145404 21.963730 l 169 | 0.813819 21.273163 l 170 | h 171 | 4.422827 31.439829 m 172 | 3.759426 32.785152 l 173 | 3.752470 32.781723 l 174 | 3.745549 32.778221 l 175 | 4.422827 31.439829 l 176 | h 177 | 15.249868 28.960155 m 178 | 16.551033 29.706461 l 179 | 16.498585 29.797901 l 180 | 16.433865 29.881107 l 181 | 15.249868 28.960155 l 182 | h 183 | 16.245453 27.224380 m 184 | 17.561163 27.944733 l 185 | 17.554018 27.957781 l 186 | 17.546616 27.970686 l 187 | 16.245453 27.224380 l 188 | h 189 | 17.543161 21.730659 m 190 | 17.941002 21.004011 18.852577 20.737461 19.579224 21.135300 c 191 | 20.305870 21.533138 20.572422 22.444714 20.174583 23.171360 c 192 | 17.543161 21.730659 l 193 | h 194 | 11.234490 4.163647 m 195 | 2.149737 21.955307 l 196 | -0.522100 20.591019 l 197 | 8.562653 2.799356 l 198 | 11.234490 4.163647 l 199 | h 200 | 2.145404 21.963730 m 201 | 0.640692 24.865192 1.844065 28.453754 5.100106 30.101437 c 202 | 3.745549 32.778221 l 203 | -0.838677 30.458426 -2.870964 25.120155 -0.517767 20.582596 c 204 | 2.145404 21.963730 l 205 | h 206 | 5.086229 30.094505 m 207 | 6.591342 30.836700 8.308313 31.037558 9.945602 30.662575 c 208 | 10.615340 33.586861 l 209 | 8.305876 34.115788 5.883752 33.832695 3.759426 32.785152 c 210 | 5.086229 30.094505 l 211 | h 212 | 9.945602 30.662575 m 213 | 11.582829 30.287607 13.038308 29.360258 14.065873 28.039202 c 214 | 16.433865 29.881107 l 215 | 14.980840 31.749144 12.924868 33.057919 10.615340 33.586861 c 216 | 9.945602 30.662575 l 217 | h 218 | 13.948705 28.213848 m 219 | 14.944288 26.478075 l 220 | 17.546616 27.970686 l 221 | 16.551033 29.706461 l 222 | 13.948705 28.213848 l 223 | h 224 | 14.929742 26.504028 m 225 | 17.543161 21.730659 l 226 | 20.174583 23.171360 l 227 | 17.561163 27.944733 l 228 | 14.929742 26.504028 l 229 | h 230 | f 231 | n 232 | Q 233 | q 234 | 1.000000 0.000000 -0.000000 1.000000 41.874756 -1.757080 cm 235 | 0.629167 0.629167 0.629167 scn 236 | 52.255276 2.280106 m 237 | 52.794823 1.651466 53.741817 1.579247 54.370453 2.118790 c 238 | 54.999092 2.658340 55.071312 3.605331 54.531769 4.233971 c 239 | 52.255276 2.280106 l 240 | h 241 | 19.605707 23.094429 m 242 | 18.641115 21.945709 l 243 | 18.641294 21.945557 l 244 | 19.605707 23.094429 l 245 | h 246 | 1.685100 41.444016 m 247 | 0.574166 40.436134 l 248 | 0.574391 40.435883 l 249 | 1.685100 41.444016 l 250 | h 251 | 14.938883 49.750931 m 252 | 14.147062 48.476936 l 253 | 14.151858 48.473984 l 254 | 14.938883 49.750931 l 255 | h 256 | 29.623823 40.700111 m 257 | 28.836800 39.423164 l 258 | 29.388638 39.083054 30.096523 39.135902 30.591740 39.554192 c 259 | 29.623823 40.700111 l 260 | h 261 | 32.486141 43.117798 m 262 | 33.454056 41.971878 l 263 | 33.992607 42.426765 34.141724 43.195297 33.812378 43.818581 c 264 | 32.486141 43.117798 l 265 | h 266 | 0.876184 102.939926 m 267 | -0.451024 102.240982 l 268 | -0.450053 102.239143 l 269 | 0.876184 102.939926 l 270 | h 271 | 14.378863 110.068993 m 272 | 15.710394 110.759659 l 273 | 15.707027 110.766151 15.703614 110.772614 15.700153 110.779060 c 274 | 14.378863 110.068993 l 275 | h 276 | 29.350103 77.948456 m 277 | 29.731550 77.213074 30.636921 76.926147 31.372305 77.307594 c 278 | 32.107689 77.689041 32.394611 78.594414 32.013168 79.329796 c 279 | 29.350103 77.948456 l 280 | h 281 | 54.531769 4.233971 m 282 | 53.941753 4.921410 53.042351 5.633354 52.014084 6.341515 c 283 | 50.962776 7.065536 49.682655 7.848564 48.258492 8.668015 c 284 | 45.411137 10.306351 41.902874 12.138573 38.335388 13.994934 c 285 | 34.754112 15.858467 31.108093 17.748909 27.936901 19.525139 c 286 | 24.741688 21.314827 22.131063 22.932976 20.570122 24.243301 c 287 | 18.641294 21.945557 l 288 | 20.440464 20.435249 23.274471 18.698097 26.470863 16.907753 c 289 | 29.691277 15.103951 33.383839 13.189644 36.950584 11.333672 c 290 | 40.531120 9.470520 43.980316 7.668465 46.762318 6.067734 c 291 | 48.152836 5.267639 49.355370 4.529930 50.312496 3.870766 c 292 | 51.292667 3.195732 51.927483 2.662025 52.255276 2.280106 c 293 | 54.531769 4.233971 l 294 | h 295 | 20.570299 24.243149 m 296 | 16.308598 27.821739 8.105620 36.602058 2.795808 42.452148 c 297 | 0.574391 40.435883 l 298 | 5.842715 34.631508 14.191412 25.682159 18.641115 21.945709 c 299 | 20.570299 24.243149 l 300 | h 301 | 2.796033 42.451897 m 302 | 2.368433 42.923218 2.055722 43.485779 1.881652 44.095924 c 303 | -1.003242 43.272888 l 304 | -0.702537 42.218864 -0.162680 41.248314 0.574166 40.436134 c 305 | 2.796033 42.451897 l 306 | h 307 | 1.881652 44.095924 m 308 | 1.707587 44.706055 1.676713 45.347778 1.791360 45.971588 c 309 | -1.159222 46.513863 l 310 | -1.357340 45.435875 -1.303951 44.326935 -1.003242 43.272888 c 311 | 1.881652 44.095924 l 312 | h 313 | 1.791360 45.971588 m 314 | 1.906011 46.595413 2.163192 47.185020 2.543410 47.694733 c 315 | 0.138738 49.488487 l 316 | -0.516876 48.609581 -0.961107 47.591835 -1.159222 46.513863 c 317 | 1.791360 45.971588 l 318 | h 319 | 2.543410 47.694733 m 320 | 2.923647 48.204468 3.416828 48.620750 3.985142 48.910973 c 321 | 2.620721 51.582745 l 322 | 1.643657 51.083778 0.794334 50.367363 0.138738 49.488487 c 323 | 2.543410 47.694733 l 324 | h 325 | 3.983269 48.910019 m 326 | 5.570595 49.717834 7.341166 50.102669 9.122582 50.026741 c 327 | 9.250330 53.024025 l 328 | 6.953609 53.121914 4.670328 52.625820 2.622593 51.583698 c 329 | 3.983269 48.910019 l 330 | h 331 | 9.122582 50.026741 m 332 | 10.903997 49.950821 12.634983 49.416748 14.147068 48.476944 c 333 | 15.730698 51.024910 l 334 | 13.779592 52.237576 11.547053 52.926136 9.250330 53.024025 c 335 | 9.122582 50.026741 l 336 | h 337 | 14.151858 48.473984 m 338 | 28.836800 39.423164 l 339 | 30.410849 41.977058 l 340 | 15.725907 51.027874 l 341 | 14.151858 48.473984 l 342 | h 343 | 30.591740 39.554192 m 344 | 33.454056 41.971878 l 345 | 31.518227 44.263725 l 346 | 28.655907 41.846039 l 347 | 30.591740 39.554192 l 348 | h 349 | 33.812378 43.818581 m 350 | 2.202421 103.640709 l 351 | -0.450053 102.239143 l 352 | 31.159904 42.417023 l 353 | 33.812378 43.818581 l 354 | h 355 | 2.203390 103.638870 m 356 | 1.463501 105.043831 1.300187 106.679459 1.747344 108.201538 c 357 | -1.131015 109.047150 l 358 | -1.798195 106.776131 -1.554216 104.335808 -0.451022 102.240982 c 359 | 2.203390 103.638870 l 360 | h 361 | 1.747344 108.201538 m 362 | 2.194540 109.723755 3.217949 111.015175 4.603770 111.802299 c 363 | 3.122133 114.410889 l 364 | 1.061644 113.240562 -0.463873 111.318031 -1.131015 109.047150 c 365 | 1.747344 108.201538 l 366 | h 367 | 4.599438 111.799843 m 368 | 5.313483 112.202286 6.101340 112.458595 6.916702 112.553513 c 369 | 6.569831 115.533386 l 370 | 5.358835 115.392426 4.188111 115.011688 3.126465 114.413338 c 371 | 4.599438 111.799843 l 372 | h 373 | 6.916702 112.553513 m 374 | 7.732068 112.648422 8.558220 112.579979 9.346520 112.352272 c 375 | 10.179056 115.234444 l 376 | 9.007913 115.572739 7.780822 115.674355 6.569831 115.533386 c 377 | 6.916702 112.553513 l 378 | h 379 | 9.346520 112.352272 m 380 | 10.134811 112.124573 10.869125 111.742264 11.506388 111.228104 c 381 | 13.390175 113.562920 l 382 | 12.442001 114.327927 11.350207 114.896149 10.179056 115.234444 c 383 | 9.346520 112.352272 l 384 | h 385 | 11.506388 111.228104 m 386 | 12.143638 110.713959 12.670938 110.078377 13.057572 109.358925 c 387 | 15.700153 110.779060 l 388 | 15.123836 111.851471 14.338361 112.797897 13.390175 113.562920 c 389 | 11.506388 111.228104 l 390 | h 391 | 13.047332 109.378326 m 392 | 29.350103 77.948456 l 393 | 32.013168 79.329796 l 394 | 15.710394 110.759659 l 395 | 13.047332 109.378326 l 396 | h 397 | f 398 | n 399 | Q 400 | q 401 | 0.980061 -0.198696 -0.198696 -0.980061 18.139366 124.988129 cm 402 | 0.629167 0.629167 0.629167 scn 403 | 14.304977 14.235680 m 404 | 14.304977 10.721280 11.443786 7.862720 7.902489 7.862720 c 405 | 7.902489 4.862720 l 406 | 13.090040 4.862720 17.304977 9.053846 17.304977 14.235680 c 407 | 14.304977 14.235680 l 408 | h 409 | 7.902489 7.862720 m 410 | 4.361192 7.862720 1.500000 10.721280 1.500000 14.235680 c 411 | -1.500000 14.235680 l 412 | -1.500000 9.053846 2.714937 4.862720 7.902489 4.862720 c 413 | 7.902489 7.862720 l 414 | h 415 | 1.500000 14.235680 m 416 | 1.500000 17.750080 4.361194 20.608643 7.902489 20.608643 c 417 | 7.902489 23.608643 l 418 | 2.714936 23.608643 -1.500000 19.417511 -1.500000 14.235680 c 419 | 1.500000 14.235680 l 420 | h 421 | 7.902489 20.608643 m 422 | 11.443784 20.608643 14.304977 17.750080 14.304977 14.235680 c 423 | 17.304977 14.235680 l 424 | 17.304977 19.417511 13.090041 23.608643 7.902489 23.608643 c 425 | 7.902489 20.608643 l 426 | h 427 | f 428 | n 429 | Q 430 | q 431 | 0.980061 -0.198696 -0.198696 -0.980061 18.744465 108.637100 cm 432 | 0.629167 0.629167 0.629167 scn 433 | 17.366123 73.170517 m 434 | 17.836908 73.852165 17.665968 74.786407 16.984314 75.257195 c 435 | 16.302660 75.727982 15.368423 75.557037 14.897636 74.875381 c 436 | 17.366123 73.170517 l 437 | h 438 | -0.216979 6.223732 m 439 | -0.062933 5.409760 0.721807 4.874779 1.535785 5.028824 c 440 | 2.349764 5.182869 2.884745 5.967606 2.730698 6.781586 c 441 | -0.216979 6.223732 l 442 | h 443 | 14.897636 74.875381 m 444 | 5.401996 61.126610 -5.226364 32.693237 -0.216979 6.223732 c 445 | 2.730698 6.781586 l 446 | -2.091360 32.261257 8.194466 59.890839 17.366123 73.170517 c 447 | 14.897636 74.875381 l 448 | h 449 | f 450 | n 451 | Q 452 | q 453 | 0.980061 -0.198696 -0.198696 -0.980061 12.316921 50.234398 cm 454 | 0.629167 0.629167 0.629167 scn 455 | -0.590866 13.476933 m 456 | -1.352314 13.150606 -1.705049 12.268791 -1.378723 11.507343 c 457 | -1.052397 10.745895 -0.170581 10.393160 0.590866 10.719486 c 458 | -0.590866 13.476933 l 459 | h 460 | 11.138151 16.871582 m 461 | 12.604726 17.186476 l 462 | 12.510608 17.624821 12.225348 17.998085 11.827100 18.204004 c 463 | 11.428851 18.409925 10.959372 18.426908 10.547285 18.250305 c 464 | 11.138151 16.871582 l 465 | h 466 | 12.714355 5.946751 m 467 | 12.996131 5.167717 13.856087 4.764609 14.635121 5.046386 c 468 | 15.414155 5.328161 15.817262 6.188118 15.535486 6.967152 c 469 | 12.714355 5.946751 l 470 | h 471 | 0.590866 10.719486 m 472 | 11.729017 15.492859 l 473 | 10.547285 18.250305 l 474 | -0.590866 13.476933 l 475 | 0.590866 10.719486 l 476 | h 477 | 9.671576 16.556688 m 478 | 10.444746 12.955757 11.461326 9.411034 12.714355 5.946751 c 479 | 15.535486 6.967152 l 480 | 14.328569 10.303946 13.349420 13.718168 12.604726 17.186476 c 481 | 9.671576 16.556688 l 482 | h 483 | f 484 | n 485 | Q 486 | 487 | endstream 488 | endobj 489 | 490 | 3 0 obj 491 | 12527 492 | endobj 493 | 494 | 4 0 obj 495 | << /Annots [] 496 | /Type /Page 497 | /MediaBox [ 0.000000 0.000000 140.698730 118.842041 ] 498 | /Resources 1 0 R 499 | /Contents 2 0 R 500 | /Parent 5 0 R 501 | >> 502 | endobj 503 | 504 | 5 0 obj 505 | << /Kids [ 4 0 R ] 506 | /Count 1 507 | /Type /Pages 508 | >> 509 | endobj 510 | 511 | 6 0 obj 512 | << /Type /Catalog 513 | /Pages 5 0 R 514 | >> 515 | endobj 516 | 517 | xref 518 | 0 7 519 | 0000000000 65535 f 520 | 0000000010 00000 n 521 | 0000000034 00000 n 522 | 0000012617 00000 n 523 | 0000012641 00000 n 524 | 0000012816 00000 n 525 | 0000012890 00000 n 526 | trailer 527 | << /ID [ (some) (id) ] 528 | /Root 6 0 R 529 | /Size 7 530 | >> 531 | startxref 532 | 12949 533 | %%EOF -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Icon_swipe.imageset/Icon_Swipe_light.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | 1.000000 0.000000 -0.000000 1.000000 107.597168 19.904175 cm 14 | 0.082353 0.494118 0.984314 scn 15 | 30.101513 3.293037 m 16 | 30.101513 2.464615 30.773085 1.793037 31.601513 1.793037 c 17 | 32.429939 1.793037 33.101513 2.464615 33.101513 3.293037 c 18 | 30.101513 3.293037 l 19 | h 20 | 25.067957 42.223915 m 21 | 23.674679 41.668217 l 22 | 23.676331 41.664108 l 23 | 25.067957 42.223915 l 24 | h 25 | 14.178696 65.718819 m 26 | 15.513352 66.403534 l 27 | 15.507772 66.414200 l 28 | 14.178696 65.718819 l 29 | h 30 | 4.098353 68.942398 m 31 | 3.422246 70.281395 l 32 | 3.416876 70.278656 l 33 | 4.098353 68.942398 l 34 | h 35 | 0.800461 58.899719 m 36 | 2.141096 59.572552 l 37 | 2.139977 59.574772 l 38 | 0.800461 58.899719 l 39 | h 40 | 2.633274 51.903721 m 41 | 3.004868 51.163307 3.906327 50.864326 4.646739 51.235920 c 42 | 5.387150 51.607513 5.686135 52.508972 5.314540 53.249382 c 43 | 2.633274 51.903721 l 44 | h 45 | 33.101513 3.293037 m 46 | 33.101513 9.816757 33.109821 26.251789 26.459581 42.783718 c 47 | 23.676331 41.664108 l 48 | 30.093204 25.712311 30.101513 9.787609 30.101513 3.293037 c 49 | 33.101513 3.293037 l 50 | h 51 | 26.461227 42.779610 m 52 | 22.700573 52.208557 18.380539 60.814678 15.513309 66.403519 c 53 | 12.844082 65.034126 l 54 | 15.701491 59.464432 19.968418 50.960808 23.674685 41.668221 c 55 | 26.461227 42.779610 l 56 | h 57 | 15.507772 66.414200 m 58 | 14.408175 68.515862 12.522062 70.099152 10.260141 70.822487 c 59 | 9.346363 67.965034 l 60 | 10.858726 67.481400 12.116976 66.423737 12.849618 65.023438 c 61 | 15.507772 66.414200 l 62 | h 63 | 10.260141 70.822487 m 64 | 7.998360 71.545776 5.541566 71.351501 3.422251 70.281387 c 65 | 4.774456 67.603409 l 66 | 6.190899 68.318626 7.833860 68.448715 9.346363 67.965034 c 67 | 10.260141 70.822487 l 68 | h 69 | 3.416876 70.278656 m 70 | 1.296715 69.197395 -0.310238 67.324211 -1.051887 65.065750 c 71 | 1.798364 64.129768 l 72 | 2.292186 65.633545 3.363156 66.883652 4.779831 67.606140 c 73 | 3.416876 70.278656 l 74 | h 75 | -1.051887 65.065750 m 76 | -1.793585 62.807144 -1.609107 60.347977 -0.539054 58.224663 c 77 | 2.139977 59.574772 l 78 | 1.427231 60.989079 1.304591 62.626137 1.798364 64.129768 c 79 | -1.051887 65.065750 l 80 | h 81 | -0.540172 58.226887 m 82 | 2.633274 51.903721 l 83 | 5.314540 53.249382 l 84 | 2.141094 59.572548 l 85 | -0.540172 58.226887 l 86 | h 87 | f 88 | n 89 | Q 90 | q 91 | 1.000000 0.000000 -0.000000 1.000000 88.944702 66.358276 cm 92 | 0.082353 0.494118 0.984314 scn 93 | 4.181628 3.823013 m 94 | 4.561399 3.086761 5.466113 2.797777 6.202364 3.177547 c 95 | 6.938615 3.557318 7.227600 4.462032 6.847828 5.198284 c 96 | 4.181628 3.823013 l 97 | h 98 | 0.910119 13.437477 m 99 | 2.243223 14.125115 l 100 | 2.241588 14.128266 l 101 | 0.910119 13.437477 l 102 | h 103 | 14.848384 21.248449 m 104 | 13.539284 20.516050 l 105 | 13.545394 20.505337 l 106 | 14.848384 21.248449 l 107 | h 108 | 16.403994 18.520813 m 109 | 17.743010 19.196856 l 110 | 17.725847 19.230846 l 111 | 17.706984 19.263926 l 112 | 16.403994 18.520813 l 113 | h 114 | 20.291819 7.492126 m 115 | 20.665186 6.752609 21.567360 6.455786 22.306877 6.829153 c 116 | 23.046396 7.202520 23.343220 8.104694 22.969852 8.844213 c 117 | 20.291819 7.492126 l 118 | h 119 | 6.847828 5.198284 m 120 | 2.243219 14.125113 l 121 | -0.422982 12.749842 l 122 | 4.181628 3.823013 l 123 | 6.847828 5.198284 l 124 | h 125 | 2.241588 14.128266 m 126 | 1.484908 15.586740 1.299585 17.272772 1.721112 18.859257 c 127 | -1.178290 19.629625 l 128 | -1.791699 17.320961 -1.521738 14.867644 -0.421351 12.746689 c 129 | 2.241588 14.128266 l 130 | h 131 | 1.721112 18.859257 m 132 | 2.142666 20.445843 3.141511 21.821121 4.525321 22.716885 c 133 | 2.895110 25.235300 l 134 | 0.887509 23.935745 -0.564908 21.938189 -1.178290 19.629625 c 135 | 1.721112 18.859257 l 136 | h 137 | 4.499123 22.700308 m 138 | 7.435656 24.516172 11.871108 23.497871 13.539328 20.516075 c 139 | 16.157440 21.980824 l 140 | 13.594401 26.562033 7.202802 27.899426 2.921308 25.251877 c 141 | 4.499123 22.700308 l 142 | h 143 | 13.545394 20.505337 m 144 | 15.101003 17.777699 l 145 | 17.706984 19.263926 l 146 | 16.151373 21.991562 l 147 | 13.545394 20.505337 l 148 | h 149 | 15.064976 17.844769 m 150 | 20.291819 7.492126 l 151 | 22.969852 8.844213 l 152 | 17.743010 19.196856 l 153 | 15.064976 17.844769 l 154 | h 155 | f 156 | n 157 | Q 158 | q 159 | 1.000000 0.000000 -0.000000 1.000000 70.995850 57.220581 cm 160 | 0.082353 0.494118 0.984314 scn 161 | 8.562653 2.799479 m 162 | 8.939391 2.061670 9.842909 1.768967 10.580716 2.145704 c 163 | 11.318523 2.522444 11.611228 3.425961 11.234490 4.163769 c 164 | 8.562653 2.799479 l 165 | h 166 | 0.813819 21.273285 m 167 | 2.149764 21.955444 l 168 | 2.145404 21.963852 l 169 | 0.813819 21.273285 l 170 | h 171 | 4.422827 31.439951 m 172 | 3.759426 32.785275 l 173 | 3.752470 32.781845 l 174 | 3.745549 32.778343 l 175 | 4.422827 31.439951 l 176 | h 177 | 15.249868 28.960278 m 178 | 16.551033 29.706583 l 179 | 16.498585 29.798023 l 180 | 16.433865 29.881229 l 181 | 15.249868 28.960278 l 182 | h 183 | 16.245453 27.224503 m 184 | 17.561163 27.944855 l 185 | 17.554018 27.957903 l 186 | 17.546616 27.970808 l 187 | 16.245453 27.224503 l 188 | h 189 | 17.543161 21.730782 m 190 | 17.941002 21.004133 18.852577 20.737583 19.579224 21.135422 c 191 | 20.305870 21.533260 20.572422 22.444836 20.174583 23.171482 c 192 | 17.543161 21.730782 l 193 | h 194 | 11.234490 4.163769 m 195 | 2.149737 21.955429 l 196 | -0.522100 20.591141 l 197 | 8.562653 2.799479 l 198 | 11.234490 4.163769 l 199 | h 200 | 2.145404 21.963852 m 201 | 0.640692 24.865314 1.844065 28.453876 5.100106 30.101559 c 202 | 3.745549 32.778343 l 203 | -0.838677 30.458548 -2.870964 25.120277 -0.517767 20.582718 c 204 | 2.145404 21.963852 l 205 | h 206 | 5.086229 30.094627 m 207 | 6.591342 30.836823 8.308313 31.037680 9.945602 30.662697 c 208 | 10.615340 33.586983 l 209 | 8.305876 34.115910 5.883752 33.832817 3.759426 32.785275 c 210 | 5.086229 30.094627 l 211 | h 212 | 9.945602 30.662697 m 213 | 11.582829 30.287729 13.038308 29.360380 14.065873 28.039324 c 214 | 16.433865 29.881229 l 215 | 14.980840 31.749266 12.924868 33.058041 10.615340 33.586983 c 216 | 9.945602 30.662697 l 217 | h 218 | 13.948705 28.213970 m 219 | 14.944288 26.478197 l 220 | 17.546616 27.970808 l 221 | 16.551033 29.706583 l 222 | 13.948705 28.213970 l 223 | h 224 | 14.929742 26.504150 m 225 | 17.543161 21.730782 l 226 | 20.174583 23.171482 l 227 | 17.561163 27.944855 l 228 | 14.929742 26.504150 l 229 | h 230 | f 231 | n 232 | Q 233 | q 234 | 1.000000 0.000000 -0.000000 1.000000 41.874756 -1.757080 cm 235 | 0.082353 0.494118 0.984314 scn 236 | 52.255276 2.280228 m 237 | 52.794823 1.651588 53.741817 1.579369 54.370453 2.118912 c 238 | 54.999092 2.658463 55.071312 3.605453 54.531769 4.234093 c 239 | 52.255276 2.280228 l 240 | h 241 | 19.605707 23.094551 m 242 | 18.641115 21.945831 l 243 | 18.641294 21.945679 l 244 | 19.605707 23.094551 l 245 | h 246 | 1.685100 41.444138 m 247 | 0.574166 40.436256 l 248 | 0.574391 40.436005 l 249 | 1.685100 41.444138 l 250 | h 251 | 14.938883 49.751053 m 252 | 14.147062 48.477058 l 253 | 14.151858 48.474106 l 254 | 14.938883 49.751053 l 255 | h 256 | 29.623823 40.700233 m 257 | 28.836800 39.423286 l 258 | 29.388638 39.083176 30.096523 39.136024 30.591740 39.554314 c 259 | 29.623823 40.700233 l 260 | h 261 | 32.486141 43.117920 m 262 | 33.454056 41.972000 l 263 | 33.992607 42.426888 34.141724 43.195419 33.812378 43.818703 c 264 | 32.486141 43.117920 l 265 | h 266 | 0.876184 102.940048 m 267 | -0.451024 102.241104 l 268 | -0.450053 102.239265 l 269 | 0.876184 102.940048 l 270 | h 271 | 14.378863 110.069115 m 272 | 15.710394 110.759781 l 273 | 15.707027 110.766273 15.703614 110.772736 15.700153 110.779182 c 274 | 14.378863 110.069115 l 275 | h 276 | 29.350103 77.948578 m 277 | 29.731550 77.213196 30.636921 76.926270 31.372305 77.307716 c 278 | 32.107689 77.689163 32.394611 78.594536 32.013168 79.329918 c 279 | 29.350103 77.948578 l 280 | h 281 | 54.531769 4.234093 m 282 | 53.941753 4.921532 53.042351 5.633476 52.014084 6.341637 c 283 | 50.962776 7.065659 49.682655 7.848686 48.258492 8.668137 c 284 | 45.411137 10.306473 41.902874 12.138695 38.335388 13.995056 c 285 | 34.754112 15.858589 31.108093 17.749031 27.936901 19.525261 c 286 | 24.741688 21.314949 22.131063 22.933098 20.570122 24.243423 c 287 | 18.641294 21.945679 l 288 | 20.440464 20.435371 23.274471 18.698219 26.470863 16.907875 c 289 | 29.691277 15.104073 33.383839 13.189766 36.950584 11.333794 c 290 | 40.531120 9.470642 43.980316 7.668587 46.762318 6.067856 c 291 | 48.152836 5.267761 49.355370 4.530052 50.312496 3.870888 c 292 | 51.292667 3.195854 51.927483 2.662148 52.255276 2.280228 c 293 | 54.531769 4.234093 l 294 | h 295 | 20.570299 24.243271 m 296 | 16.308598 27.821861 8.105620 36.602180 2.795808 42.452271 c 297 | 0.574391 40.436005 l 298 | 5.842715 34.631630 14.191412 25.682281 18.641115 21.945831 c 299 | 20.570299 24.243271 l 300 | h 301 | 2.796033 42.452019 m 302 | 2.368433 42.923340 2.055722 43.485901 1.881652 44.096046 c 303 | -1.003242 43.273010 l 304 | -0.702537 42.218987 -0.162680 41.248436 0.574166 40.436256 c 305 | 2.796033 42.452019 l 306 | h 307 | 1.881652 44.096046 m 308 | 1.707587 44.706177 1.676713 45.347900 1.791360 45.971710 c 309 | -1.159222 46.513985 l 310 | -1.357340 45.435997 -1.303951 44.327057 -1.003242 43.273010 c 311 | 1.881652 44.096046 l 312 | h 313 | 1.791360 45.971710 m 314 | 1.906011 46.595535 2.163192 47.185143 2.543410 47.694855 c 315 | 0.138738 49.488609 l 316 | -0.516876 48.609703 -0.961107 47.591957 -1.159222 46.513985 c 317 | 1.791360 45.971710 l 318 | h 319 | 2.543410 47.694855 m 320 | 2.923647 48.204590 3.416828 48.620872 3.985142 48.911095 c 321 | 2.620721 51.582867 l 322 | 1.643657 51.083900 0.794334 50.367485 0.138738 49.488609 c 323 | 2.543410 47.694855 l 324 | h 325 | 3.983269 48.910141 m 326 | 5.570595 49.717957 7.341166 50.102791 9.122582 50.026863 c 327 | 9.250330 53.024147 l 328 | 6.953609 53.122036 4.670328 52.625942 2.622593 51.583820 c 329 | 3.983269 48.910141 l 330 | h 331 | 9.122582 50.026863 m 332 | 10.903997 49.950943 12.634983 49.416870 14.147068 48.477066 c 333 | 15.730698 51.025032 l 334 | 13.779592 52.237698 11.547053 52.926258 9.250330 53.024147 c 335 | 9.122582 50.026863 l 336 | h 337 | 14.151858 48.474106 m 338 | 28.836800 39.423286 l 339 | 30.410849 41.977180 l 340 | 15.725907 51.027996 l 341 | 14.151858 48.474106 l 342 | h 343 | 30.591740 39.554314 m 344 | 33.454056 41.972000 l 345 | 31.518227 44.263847 l 346 | 28.655907 41.846161 l 347 | 30.591740 39.554314 l 348 | h 349 | 33.812378 43.818703 m 350 | 2.202421 103.640831 l 351 | -0.450053 102.239265 l 352 | 31.159904 42.417145 l 353 | 33.812378 43.818703 l 354 | h 355 | 2.203390 103.638992 m 356 | 1.463501 105.043953 1.300187 106.679581 1.747344 108.201660 c 357 | -1.131015 109.047272 l 358 | -1.798195 106.776253 -1.554216 104.335930 -0.451022 102.241104 c 359 | 2.203390 103.638992 l 360 | h 361 | 1.747344 108.201660 m 362 | 2.194540 109.723877 3.217949 111.015297 4.603770 111.802422 c 363 | 3.122133 114.411011 l 364 | 1.061644 113.240685 -0.463873 111.318153 -1.131015 109.047272 c 365 | 1.747344 108.201660 l 366 | h 367 | 4.599438 111.799965 m 368 | 5.313483 112.202408 6.101340 112.458717 6.916702 112.553635 c 369 | 6.569831 115.533508 l 370 | 5.358835 115.392548 4.188111 115.011810 3.126465 114.413460 c 371 | 4.599438 111.799965 l 372 | h 373 | 6.916702 112.553635 m 374 | 7.732068 112.648544 8.558220 112.580101 9.346520 112.352394 c 375 | 10.179056 115.234566 l 376 | 9.007913 115.572861 7.780822 115.674477 6.569831 115.533508 c 377 | 6.916702 112.553635 l 378 | h 379 | 9.346520 112.352394 m 380 | 10.134811 112.124695 10.869125 111.742386 11.506388 111.228226 c 381 | 13.390175 113.563042 l 382 | 12.442001 114.328049 11.350207 114.896271 10.179056 115.234566 c 383 | 9.346520 112.352394 l 384 | h 385 | 11.506388 111.228226 m 386 | 12.143638 110.714081 12.670938 110.078499 13.057572 109.359047 c 387 | 15.700153 110.779182 l 388 | 15.123836 111.851593 14.338361 112.798019 13.390175 113.563042 c 389 | 11.506388 111.228226 l 390 | h 391 | 13.047332 109.378448 m 392 | 29.350103 77.948578 l 393 | 32.013168 79.329918 l 394 | 15.710394 110.759781 l 395 | 13.047332 109.378448 l 396 | h 397 | f 398 | n 399 | Q 400 | q 401 | 0.980061 -0.198696 -0.198696 -0.980061 18.139366 124.988373 cm 402 | 0.082353 0.494118 0.984314 scn 403 | 14.304977 14.235680 m 404 | 14.304977 10.721280 11.443786 7.862720 7.902489 7.862720 c 405 | 7.902489 4.862720 l 406 | 13.090040 4.862720 17.304977 9.053846 17.304977 14.235680 c 407 | 14.304977 14.235680 l 408 | h 409 | 7.902489 7.862720 m 410 | 4.361192 7.862720 1.500000 10.721280 1.500000 14.235680 c 411 | -1.500000 14.235680 l 412 | -1.500000 9.053846 2.714937 4.862720 7.902489 4.862720 c 413 | 7.902489 7.862720 l 414 | h 415 | 1.500000 14.235680 m 416 | 1.500000 17.750080 4.361194 20.608643 7.902489 20.608643 c 417 | 7.902489 23.608643 l 418 | 2.714936 23.608643 -1.500000 19.417511 -1.500000 14.235680 c 419 | 1.500000 14.235680 l 420 | h 421 | 7.902489 20.608643 m 422 | 11.443784 20.608643 14.304977 17.750080 14.304977 14.235680 c 423 | 17.304977 14.235680 l 424 | 17.304977 19.417511 13.090041 23.608643 7.902489 23.608643 c 425 | 7.902489 20.608643 l 426 | h 427 | f 428 | n 429 | Q 430 | q 431 | 0.980061 -0.198696 -0.198696 -0.980061 18.744490 108.637466 cm 432 | 0.082353 0.494118 0.984314 scn 433 | 17.366123 73.170639 m 434 | 17.836908 73.852287 17.665968 74.786530 16.984314 75.257317 c 435 | 16.302660 75.728104 15.368423 75.557159 14.897636 74.875504 c 436 | 17.366123 73.170639 l 437 | h 438 | -0.216979 6.223854 m 439 | -0.062933 5.409882 0.721807 4.874901 1.535785 5.028946 c 440 | 2.349764 5.182991 2.884745 5.967728 2.730698 6.781708 c 441 | -0.216979 6.223854 l 442 | h 443 | 14.897636 74.875504 m 444 | 5.401996 61.126732 -5.226364 32.693359 -0.216979 6.223854 c 445 | 2.730698 6.781708 l 446 | -2.091360 32.261379 8.194466 59.890961 17.366123 73.170639 c 447 | 14.897636 74.875504 l 448 | h 449 | f 450 | n 451 | Q 452 | q 453 | 0.980061 -0.198696 -0.198696 -0.980061 12.316896 50.234650 cm 454 | 0.082353 0.494118 0.984314 scn 455 | -0.590866 13.476810 m 456 | -1.352314 13.150484 -1.705049 12.268669 -1.378723 11.507221 c 457 | -1.052397 10.745773 -0.170581 10.393038 0.590866 10.719364 c 458 | -0.590866 13.476810 l 459 | h 460 | 11.138151 16.871460 m 461 | 12.604726 17.186354 l 462 | 12.510608 17.624699 12.225348 17.997963 11.827100 18.203882 c 463 | 11.428851 18.409803 10.959372 18.426786 10.547285 18.250183 c 464 | 11.138151 16.871460 l 465 | h 466 | 12.714355 5.946629 m 467 | 12.996131 5.167595 13.856087 4.764487 14.635121 5.046264 c 468 | 15.414155 5.328039 15.817262 6.187996 15.535486 6.967030 c 469 | 12.714355 5.946629 l 470 | h 471 | 0.590866 10.719364 m 472 | 11.729017 15.492737 l 473 | 10.547285 18.250183 l 474 | -0.590866 13.476810 l 475 | 0.590866 10.719364 l 476 | h 477 | 9.671576 16.556566 m 478 | 10.444746 12.955635 11.461326 9.410912 12.714355 5.946629 c 479 | 15.535486 6.967030 l 480 | 14.328569 10.303824 13.349420 13.718046 12.604726 17.186354 c 481 | 9.671576 16.556566 l 482 | h 483 | f 484 | n 485 | Q 486 | 487 | endstream 488 | endobj 489 | 490 | 3 0 obj 491 | 12527 492 | endobj 493 | 494 | 4 0 obj 495 | << /Annots [] 496 | /Type /Page 497 | /MediaBox [ 0.000000 0.000000 140.698730 120.520630 ] 498 | /Resources 1 0 R 499 | /Contents 2 0 R 500 | /Parent 5 0 R 501 | >> 502 | endobj 503 | 504 | 5 0 obj 505 | << /Kids [ 4 0 R ] 506 | /Count 1 507 | /Type /Pages 508 | >> 509 | endobj 510 | 511 | 6 0 obj 512 | << /Type /Catalog 513 | /Pages 5 0 R 514 | >> 515 | endobj 516 | 517 | xref 518 | 0 7 519 | 0000000000 65535 f 520 | 0000000010 00000 n 521 | 0000000034 00000 n 522 | 0000012617 00000 n 523 | 0000012641 00000 n 524 | 0000012816 00000 n 525 | 0000012890 00000 n 526 | trailer 527 | << /ID [ (some) (id) ] 528 | /Root 6 0 R 529 | /Size 7 530 | >> 531 | startxref 532 | 12949 533 | %%EOF -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Logo_light.pdf", 5 | "idiom" : "universal" 6 | }, 7 | { 8 | "appearances" : [ 9 | { 10 | "appearance" : "luminosity", 11 | "value" : "dark" 12 | } 13 | ], 14 | "filename" : "Logo_dark.pdf", 15 | "idiom" : "universal" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | }, 22 | "properties" : { 23 | "preserves-vector-representation" : true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Logo.imageset/Logo_dark.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /ExtGState << /E1 << /ca 0.620000 >> >> >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | /E1 gs 14 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 15 | 1.000000 1.000000 1.000000 scn 16 | 20.122700 64.000000 m 17 | 0.000000 64.000000 l 18 | 0.000000 0.000000 l 19 | 40.000000 0.000000 l 20 | 24.539877 24.861538 l 21 | 33.374233 27.076923 40.000000 34.707691 40.000000 44.307693 c 22 | 40.000000 55.138462 31.165646 64.000000 20.122700 64.000000 c 23 | h 24 | f 25 | n 26 | Q 27 | q 28 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 29 | 1.000000 1.000000 1.000000 scn 30 | 0.000000 0.000000 m 31 | 40.000000 0.000000 l 32 | 0.000000 64.000000 l 33 | 0.000000 0.000000 l 34 | h 35 | f 36 | n 37 | Q 38 | 39 | endstream 40 | endobj 41 | 42 | 3 0 obj 43 | 545 44 | endobj 45 | 46 | 4 0 obj 47 | << /Annots [] 48 | /Type /Page 49 | /MediaBox [ 0.000000 0.000000 40.000000 64.000000 ] 50 | /Resources 1 0 R 51 | /Contents 2 0 R 52 | /Parent 5 0 R 53 | >> 54 | endobj 55 | 56 | 5 0 obj 57 | << /Kids [ 4 0 R ] 58 | /Count 1 59 | /Type /Pages 60 | >> 61 | endobj 62 | 63 | 6 0 obj 64 | << /Type /Catalog 65 | /Pages 5 0 R 66 | >> 67 | endobj 68 | 69 | xref 70 | 0 7 71 | 0000000000 65535 f 72 | 0000000010 00000 n 73 | 0000000074 00000 n 74 | 0000000675 00000 n 75 | 0000000697 00000 n 76 | 0000000870 00000 n 77 | 0000000944 00000 n 78 | trailer 79 | << /ID [ (some) (id) ] 80 | /Root 6 0 R 81 | /Size 7 82 | >> 83 | startxref 84 | 1003 85 | %%EOF -------------------------------------------------------------------------------- /Example/Example/Resources/Assets.xcassets/Logo.imageset/Logo_light.pdf: -------------------------------------------------------------------------------- 1 | %PDF-1.7 2 | 3 | 1 0 obj 4 | << /ExtGState << /E1 << /ca 0.620000 >> >> >> 5 | endobj 6 | 7 | 2 0 obj 8 | << /Length 3 0 R >> 9 | stream 10 | /DeviceRGB CS 11 | /DeviceRGB cs 12 | q 13 | /E1 gs 14 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 15 | 0.121569 0.403922 0.850980 scn 16 | 20.122700 64.000000 m 17 | 0.000000 64.000000 l 18 | 0.000000 0.000000 l 19 | 40.000000 0.000000 l 20 | 24.539877 24.861538 l 21 | 33.374233 27.076923 40.000000 34.707691 40.000000 44.307693 c 22 | 40.000000 55.138462 31.165646 64.000000 20.122700 64.000000 c 23 | h 24 | f 25 | n 26 | Q 27 | q 28 | 1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm 29 | 0.121569 0.403922 0.850980 scn 30 | 0.000000 0.000000 m 31 | 40.000000 0.000000 l 32 | 0.000000 64.000000 l 33 | 0.000000 0.000000 l 34 | h 35 | f 36 | n 37 | Q 38 | 39 | endstream 40 | endobj 41 | 42 | 3 0 obj 43 | 545 44 | endobj 45 | 46 | 4 0 obj 47 | << /Annots [] 48 | /Type /Page 49 | /MediaBox [ 0.000000 0.000000 40.000000 64.000000 ] 50 | /Resources 1 0 R 51 | /Contents 2 0 R 52 | /Parent 5 0 R 53 | >> 54 | endobj 55 | 56 | 5 0 obj 57 | << /Kids [ 4 0 R ] 58 | /Count 1 59 | /Type /Pages 60 | >> 61 | endobj 62 | 63 | 6 0 obj 64 | << /Type /Catalog 65 | /Pages 5 0 R 66 | >> 67 | endobj 68 | 69 | xref 70 | 0 7 71 | 0000000000 65535 f 72 | 0000000010 00000 n 73 | 0000000074 00000 n 74 | 0000000675 00000 n 75 | 0000000697 00000 n 76 | 0000000870 00000 n 77 | 0000000944 00000 n 78 | trailer 79 | << /ID [ (some) (id) ] 80 | /Root 6 0 R 81 | /Size 7 82 | >> 83 | startxref 84 | 1003 85 | %%EOF -------------------------------------------------------------------------------- /Example/Example/Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Example/Example/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationPortraitUpsideDown 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Example/Example/Screens/FirstView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirstView.swift 3 | // Example 4 | // 5 | // Created by Igor K. on 05.05.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct FirstView: View { 13 | 14 | var body: some View { 15 | Screen(color: .background) { 16 | VStack(alignment: .leading) { 17 | Image("Logo") 18 | .padding(.leading, 40) 19 | 20 | Text("SwiftUI Blob Menu") 21 | .font(Font.system(size: 24, weight: Font.Weight.semibold)) 22 | .foregroundColor(.contrastText) 23 | .lineLimit(1) 24 | .padding(.top, 110) 25 | .padding(.horizontal, 40) 26 | 27 | Text("Open the menu by tapping on the hamburger. To close the menu, just tap outside. In this example, by tapping on the menu you will switch the screen view, but it can be used for other purposes. In code you can update the configuration to the close menu after item has been selected.") 28 | .font(Font.system(size: 16)) 29 | .lineSpacing(6) 30 | .foregroundColor(.contrastInformation) 31 | .fixedSize(horizontal: false, vertical: true) 32 | .padding(.top, 24) 33 | .padding(.horizontal, 40) 34 | 35 | Spacer() 36 | HStack { Spacer() } 37 | } 38 | .frame(maxHeight: 500) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Example/Example/Screens/FourthView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FourthView.swift 3 | // Example 4 | // 5 | // Created by Igor K. on 20.05.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | private let cardHeight: CGFloat = 156 12 | 13 | struct FourthView: View { 14 | 15 | @Binding var isDragging: Bool 16 | 17 | var body: some View { 18 | let v = (UIScreen.main.bounds.height - cardHeight) / 2 19 | let vd: CGFloat = 50 20 | let insets = UIEdgeInsets(top: v - vd, left: 0, bottom: v + vd, right: 0) 21 | 22 | return Screen(color: .background) { 23 | ZStack { 24 | VStack(alignment: .leading) { 25 | Image("Logo") 26 | .padding(.leading, 40) 27 | 28 | Spacer() 29 | 30 | HStack { 31 | Spacer() 32 | Image("Icon_Swipe") 33 | Spacer() 34 | } 35 | .padding(.bottom, UIScreen.main.bounds.height < 800 ? 50 : 0) 36 | } 37 | .frame(maxHeight: 500) 38 | 39 | ExtendedScrollView(isDragging: self.$isDragging, contentInset: insets) { 40 | ItemCell().background(Color.clear) 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | private struct ItemCell: View { 48 | 49 | var body: some View { 50 | VStack(alignment: .leading) { 51 | 52 | Text("Drag scroll view") 53 | .font(Font.system(size: 24, weight: Font.Weight.semibold)) 54 | .foregroundColor(Color.contrastText) 55 | .lineLimit(1) 56 | .padding(Edge.Set.all.subtracting(.bottom)) 57 | 58 | Text("This is example shows how you can hide the menu while the user is interacting with other UI.") 59 | .font(Font.system(size: 16)) 60 | .lineSpacing(6) 61 | .foregroundColor(Color.contrastInformation) 62 | .fixedSize(horizontal: false, vertical: true) 63 | .padding(.trailing, 60) 64 | .padding(Edge.Set.all.subtracting(.trailing)) 65 | } 66 | .frame(height: cardHeight) 67 | .background(Color.cardBackgound) 68 | .clipShape(RoundedRectangle(cornerRadius: 8)) 69 | .shadow(color: Color.shadow, radius: 7, y: 3) 70 | .padding(EdgeInsets(top: 7, leading: 15, bottom: 7, trailing: 15)) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Example/Example/Screens/RootView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Example 4 | // 5 | // Created by Igor K. on 16.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import BlobMenu 11 | 12 | struct RootView: View { 13 | 14 | enum Screen: Int, CaseIterable { 15 | case wallet 16 | case exchange 17 | case commerce 18 | case stocks 19 | } 20 | 21 | @State private var screen: Screen = .wallet 22 | @State private var isDragging: Bool = false 23 | 24 | //You can use blob menu as simple observable object 25 | //@ObservedObject private var blobMenuModel = BlobMenuModel(items: BlobMenuItem.all) 26 | 27 | //Or by using it as environment object 28 | @EnvironmentObject var blobMenuModel: BlobMenuModel 29 | 30 | 31 | var body: some View { 32 | ZStack { 33 | screenView.edgesIgnoringSafeArea(Edge.Set.all.subtracting(.top)) 34 | menuView.opacity(isDragging ? 0.05 : 1) 35 | } 36 | .background(Color.background) 37 | } 38 | 39 | private var screenView: some View { 40 | let screen = Screen(rawValue: blobMenuModel.selectedIndex) ?? .wallet 41 | switch screen { 42 | case .wallet: return FirstView().asAnyView 43 | case .exchange: return SecondView().asAnyView 44 | case .commerce: return ThirdView().asAnyView 45 | case .stocks: return FourthView(isDragging: $isDragging.animatable).asAnyView 46 | } 47 | } 48 | 49 | private var menuView: some View { 50 | VStack { 51 | Spacer() 52 | BlobMenuView(model: blobMenuModel).padding(.bottom, 50) 53 | } 54 | } 55 | } 56 | 57 | struct RootView_Previews: PreviewProvider { 58 | static var previews: some View { 59 | RootView() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Example/Example/Screens/SecondView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecondView.swift 3 | // Example 4 | // 5 | // Created by Igor K. on 20.05.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import BlobMenu 11 | 12 | struct SecondView: View { 13 | 14 | @Environment(\.menuEnvironment) private var menuModel: BlobMenuModel 15 | 16 | var body: some View { 17 | Screen(color: .background) { 18 | VStack(alignment: .leading) { 19 | 20 | Image("Logo") 21 | .padding(.leading, 40) 22 | 23 | Text("Menu model") 24 | .font(Font.system(size: 24, weight: Font.Weight.semibold)) 25 | .foregroundColor(.contrastText) 26 | .lineLimit(1) 27 | .padding(.top, 110) 28 | .padding(.horizontal, 40) 29 | 30 | Text("Also, the menu can be opened and closed programmatically. Or switch the selected index") 31 | .font(Font.system(size: 16)) 32 | .lineSpacing(6) 33 | .foregroundColor(.contrastInformation) 34 | .fixedSize(horizontal: false, vertical: true) 35 | .padding(.top, 24) 36 | .padding(.horizontal, 40) 37 | 38 | HStack(spacing: 16) { 39 | Buttons.roundRect(title: "Close") { self.menuModel.closeMenu() } 40 | Buttons.roundRect(title: "Open") { self.menuModel.openMenu() } 41 | Buttons.roundRect(title: "Next") { self.menuModel.selectIndex(2) } 42 | } 43 | .padding(.horizontal, 40) 44 | .padding(.top, 40) 45 | 46 | Spacer() 47 | HStack { Spacer() } 48 | } 49 | .frame(maxHeight: 500) 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Example/Example/Screens/ThirdView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ThirdView.swift 3 | // Example 4 | // 5 | // Created by Igor K. on 20.05.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import BlobMenu 11 | 12 | struct ThirdView: View { 13 | 14 | @Environment(\.menuEnvironment) private var menuModel: BlobMenuModel 15 | 16 | var body: some View { 17 | Screen(color: .background) { 18 | VStack(alignment: .leading) { 19 | 20 | Image("Logo") 21 | .padding(.leading, 40) 22 | 23 | Text("Menu items") 24 | .font(Font.system(size: 24, weight: Font.Weight.semibold)) 25 | .foregroundColor(.contrastText) 26 | .lineLimit(1) 27 | .padding(.top, 110) 28 | .padding(.horizontal, 40) 29 | 30 | Text("The menu supports dynamically updating item count. If the UI layout can’t to show all items simultaneously, they will be wrapped in a scroll view.") 31 | .font(Font.system(size: 16)) 32 | .lineSpacing(6) 33 | .foregroundColor(.contrastInformation) 34 | .fixedSize(horizontal: false, vertical: true) 35 | .padding(.top, 24) 36 | .padding(.horizontal, 40) 37 | 38 | HStack(spacing: 16) { 39 | Buttons.roundRect(title: "Remove") { self.menuModel.items = BlobMenuItem.standard } 40 | Buttons.roundRect(title: "Add") { self.menuModel.items = BlobMenuItem.extended } 41 | } 42 | .padding(.horizontal, 40) 43 | .padding(.top, 40) 44 | 45 | Spacer() 46 | HStack { Spacer() } 47 | } 48 | .frame(maxHeight: 500) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Example/Example/UI/ExtendedScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExtendedScrollView.swift 3 | // Example 4 | // 5 | // Created by Igor K. on 19.05.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct ExtendedScrollView: UIViewRepresentable { 13 | 14 | private let isDragging: Binding 15 | private let scrollView = UIScrollView() 16 | 17 | func makeCoordinator() -> Coordinator { 18 | return Coordinator(control: self, isDragging: isDragging) 19 | } 20 | 21 | func makeUIView(context: Context) -> UIScrollView { 22 | scrollView.delegate = context.coordinator 23 | return scrollView 24 | } 25 | 26 | func updateUIView(_ uiView: UIScrollView, context: Context) { 27 | 28 | } 29 | 30 | init(axis: Axis = .vertical, 31 | isDragging: Binding = .constant(false), 32 | showsIndicators: Bool = false, 33 | alwaysBounce: Bool = true, 34 | contentInset: UIEdgeInsets = .zero, 35 | @ViewBuilder content: () -> Content) { 36 | 37 | self.isDragging = isDragging 38 | 39 | let hosting = UIHostingController(rootView: content()) 40 | hosting.view.translatesAutoresizingMaskIntoConstraints = false 41 | hosting.view.backgroundColor = .clear 42 | hosting.edgesForExtendedLayout = .all 43 | hosting.extendedLayoutIncludesOpaqueBars = true 44 | 45 | scrollView.addSubview(hosting.view) 46 | scrollView.showsVerticalScrollIndicator = showsIndicators 47 | scrollView.showsHorizontalScrollIndicator = showsIndicators 48 | scrollView.contentInsetAdjustmentBehavior = .never 49 | scrollView.contentInset = contentInset 50 | scrollView.contentOffset = .zero 51 | scrollView.backgroundColor = .clear 52 | 53 | let constraints: [NSLayoutConstraint] 54 | switch axis { 55 | case .horizontal: 56 | scrollView.alwaysBounceHorizontal = alwaysBounce 57 | 58 | constraints = [ 59 | hosting.view.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor), 60 | hosting.view.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor), 61 | hosting.view.topAnchor.constraint(equalTo: scrollView.topAnchor), 62 | hosting.view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), 63 | hosting.view.heightAnchor.constraint(equalTo: scrollView.heightAnchor) 64 | ] 65 | case .vertical: 66 | scrollView.alwaysBounceVertical = alwaysBounce 67 | 68 | constraints = [ 69 | hosting.view.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), 70 | hosting.view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), 71 | hosting.view.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), 72 | hosting.view.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor), 73 | hosting.view.widthAnchor.constraint(equalTo: scrollView.widthAnchor) 74 | ] 75 | } 76 | scrollView.addConstraints(constraints) 77 | } 78 | 79 | final class Coordinator: NSObject, UIScrollViewDelegate { 80 | private let isDragging: Binding 81 | private let control: ExtendedScrollView 82 | 83 | init(control: ExtendedScrollView, isDragging: Binding) { 84 | self.control = control 85 | self.isDragging = isDragging 86 | } 87 | 88 | //MARK: - UIScrollView delegate methods 89 | func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { 90 | isDragging.wrappedValue = true 91 | } 92 | 93 | func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { 94 | isDragging.wrappedValue = false 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Example/Example/Utilities/Screen.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Screen.swift 3 | // Example 4 | // 5 | // Created by Igor K. on 22.05.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct Screen: View where Content: View { 13 | let content: () -> Content 14 | let backgoundColor: Color 15 | 16 | init(color: Color = .white, @ViewBuilder content: @escaping () -> Content) { 17 | self.content = content 18 | self.backgoundColor = color 19 | } 20 | 21 | var body: some View { 22 | ZStack { 23 | backgoundColor.edgesIgnoringSafeArea(.all) 24 | content() 25 | } 26 | .frame(size: UIScreen.main.bounds.size) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Example/Example/Utilities/Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utilities.swift 3 | // Example 4 | // 5 | // Created by Igor K. on 05.05.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | extension Binding where Value == Bool { 13 | var animatable: Binding { 14 | return Binding(get: { return self.wrappedValue }, 15 | set: { b in withAnimation { self.wrappedValue = b } }) 16 | } 17 | } 18 | 19 | struct Buttons { 20 | static func roundRect(title: String, action: @escaping () -> Void) -> some View { 21 | Button(action: action, label: { 22 | Text(title).font(Font.system(size: 15)) 23 | }) 24 | .buttonStyle(RoundRectStyle(fontColor: Color.buttonText, backgroundColor: Color.buttonBackground)) 25 | } 26 | } 27 | 28 | struct RoundRectStyle: ButtonStyle { 29 | let fontColor: Color 30 | let backgroundColor: Color 31 | 32 | public func makeBody(configuration: RoundRectStyle.Configuration) -> some View { 33 | return RoundRectButton(fontColor: fontColor, backgroundColor: backgroundColor, configuration: configuration) 34 | } 35 | 36 | private struct RoundRectButton: View { 37 | let fontColor: Color 38 | let backgroundColor: Color 39 | let configuration: ButtonStyle.Configuration 40 | 41 | @Environment(\.isEnabled) private var isEnabled: Bool 42 | 43 | var body: some View { 44 | configuration.label 45 | .foregroundColor(isEnabled ? fontColor : backgroundColor.opacity(0.6)) 46 | .padding(.horizontal, 24) 47 | .frame(height: 50) 48 | .background( 49 | RoundedRectangle(cornerRadius: 25) 50 | .fill(isEnabled ? backgroundColor : backgroundColor.opacity(0.1)) 51 | ) 52 | .compositingGroup() 53 | .opacity(configuration.isPressed ? 0.7 : 1.0) 54 | .scaleEffect(configuration.isPressed ? 0.97 : 1.0) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Ramotion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // 3 | // Package.swift 4 | // 5 | // Copyright (c) Ramotion Inc. (https://www.ramotion.com/) 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | // 25 | 26 | import PackageDescription 27 | 28 | 29 | let package = Package( 30 | name: "BlobMenu", 31 | platforms: [ 32 | .iOS(.v13) 33 | ], 34 | products: [ 35 | .library(name: "BlobMenu", 36 | targets: ["BlobMenu"]), 37 | ], 38 | targets: [ 39 | .target(name: "BlobMenu", 40 | path: "Sources") 41 | ], 42 | swiftLanguageVersions: [.v5] 43 | ) 44 | -------------------------------------------------------------------------------- /Promo/Blob-Menu-dark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Promo/Blob-Menu-dark.gif -------------------------------------------------------------------------------- /Promo/Blob-Menu-full.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Promo/Blob-Menu-full.gif -------------------------------------------------------------------------------- /Promo/Blob-Menu-light.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Promo/Blob-Menu-light.gif -------------------------------------------------------------------------------- /Promo/Header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ramotion/blob-menu/4c67580e39dda4ddff0f75b9285e4a1919334fb6/Promo/Header.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

BLOB MENU

7 | 8 |

Swift UI menu library.

9 | ___ 10 | 11 |

We specialize in the designing and coding of custom UI for Mobile Apps and Websites.
12 | 13 | 14 |

15 |

Stay tuned for the latest updates:
16 | 17 |

18 | 19 |
20 | 21 | [![Twitter](https://img.shields.io/badge/Twitter-@Ramotion-blue.svg?style=flat)](http://twitter.com/Ramotion) 22 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Ramotion/blob-menu) 23 | [![codebeat badge](https://codebeat.co/badges/6f67da5d-c416-4bac-9fb7-c2dc938feedc)](https://codebeat.co/projects/github-com-ramotion-blob-menu) 24 | [![Swift 5.0](https://img.shields.io/badge/Swift-5.0-green.svg?style=flat)](https://developer.apple.com/swift/) 25 | [![Donate](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/Ramotion) 26 | 27 | 28 | ## Requirements 29 | 30 | - iOS 13.0 31 | - Xcode 11.4 32 | - Swift 5.0 33 | 34 | 35 | ## Installation 36 | You can install `blob-menu` in several ways: 37 | 38 | - By adding the source files to your project. 39 | 40 |
41 | 42 | - Via [Swift Package Manager](https://swift.org/package-manager/). 43 | 44 |
45 | 46 | - Via [CocoaPods](https://cocoapods.org): 47 | ``` ruby 48 | pod 'blob-menu' 49 | ``` 50 | 51 |
52 | 53 | - Via [Carthage](https://github.com/Carthage/Carthage): 54 | ``` 55 | github "Ramotion/blob-menu" 56 | ``` 57 | 58 | ## Usage 59 | 60 | ### Blob Menu 61 | 62 | 1. Create several menu items. You need to provide at least one image icon to initialize `BlobMenuItem`. 63 | 2. Use these menu items to create a menu model `BlobMenuModel`. 64 | 65 | `public init(items: [BlobMenuItem], selectedIndex: Int = 0, isOpened: Bool = false)` 66 | 67 | 3. Finally, use the menu model to initialize `BlobMenuView`. You can use this view in your layout. 68 | 69 | ### Example 70 | ``` 71 | extension BlobMenuItem { 72 | static let all = [ 73 | BlobMenuItem(icon: <#Image#>), 74 | BlobMenuItem(icon: <#Image#>), 75 | BlobMenuItem(icon: <#Image#>), 76 | BlobMenuItem(icon: <#Image#>) 77 | ] 78 | } 79 | 80 | struct ContentView: View { 81 | 82 | @ObservedObject private var model = BlobMenuModel(items: BlobMenuItem.all) 83 | 84 | var body: some View { 85 | VStack { 86 | Spacer() 87 | BlobMenuView(model: model).padding(.bottom, 30) 88 | } 89 | } 90 | } 91 | ``` 92 | 93 | ### Configuration 94 | 95 | To configure additional menu parameters, use a custom `BlobMenuConfiguration` in the `BlobMenuView` init method. 96 | 97 | `public init(model: BlobMenuModel, configuration: BlobMenuConfiguration = .default)` 98 | 99 | Right now we provide only color parameters. If you have ideas on what else should be configurable, please create an issue with `suggestion` label. 100 | For more integration details, please take a look at the `Example` project. 101 | 102 | ## �� License 103 | 104 | Blob Menu is released under the MIT license. 105 | See [LICENSE](./LICENSE) for details. 106 | 107 | This library is a part of a selection of our best UI open-source projects. 108 | 109 | If you use the open-source library in your project, please make sure to credit and backlink to www.ramotion.com 110 | 111 | 112 | ## �� Get the Showroom App for iOS to give it a try 113 | Try this UI component and more like this in our iOS app. Contact us if interested. 114 | 115 | 116 | 117 | 118 | 119 | 120 |
121 |
122 | -------------------------------------------------------------------------------- /Sources/BlobMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // BlobMenu.h 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 15.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for BlobMenu. 12 | FOUNDATION_EXPORT double BlobMenuVersionNumber; 13 | 14 | //! Project version string for BlobMenu. 15 | FOUNDATION_EXPORT const unsigned char BlobMenuVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /Sources/Configuration/BlobMenuConfiguration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlobMenuConfiguration.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 23.05.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | public struct BlobMenuConfiguration { 13 | 14 | public let hamburgerColor: Color 15 | public let backgroundColor: Color 16 | public let selectionColor: Color 17 | 18 | public init(hamburgerColor: Color, 19 | backgroundColor: Color, 20 | selectionColor: Color) { 21 | 22 | self.hamburgerColor = hamburgerColor 23 | self.backgroundColor = backgroundColor 24 | self.selectionColor = selectionColor 25 | } 26 | 27 | public static var `default`: BlobMenuConfiguration { 28 | BlobMenuConfiguration(hamburgerColor: Color.hamburgerColor, 29 | backgroundColor: Color.backgroundColor, 30 | selectionColor: Color.selectionColor) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/Configuration/Theme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Theme.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 16.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | extension Color { 13 | 14 | static var hamburgerColor: Color { 15 | return Color(UIColor { $0.userInterfaceStyle == .dark ? #colorLiteral(red: 0.1960526407, green: 0.1960932612, blue: 0.1960500479, alpha: 1) : #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) }) 16 | } 17 | 18 | static var backgroundColor: Color { 19 | return Color(UIColor { $0.userInterfaceStyle == .dark ? #colorLiteral(red: 0.9705940673, green: 0.9705940673, blue: 0.9705940673, alpha: 1) : #colorLiteral(red: 0.1960526407, green: 0.1960932612, blue: 0.1960500479, alpha: 1) }) 20 | } 21 | 22 | static let selectionColor = Color(#colorLiteral(red: 0.9983773828, green: 0.7375702262, blue: 0.1739521325, alpha: 1)) 23 | } 24 | -------------------------------------------------------------------------------- /Sources/Effects/Transitions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Transitions.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 23.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | extension AnyTransition { 13 | static var blobBlobMenuItem: AnyTransition { 14 | return AnyTransition.scale.combined(with: AnyTransition.rotation) 15 | .animation(.easeOut(duration: 0.35)) 16 | } 17 | } 18 | 19 | 20 | extension AnyTransition { 21 | 22 | static var rotation: AnyTransition { 23 | return AnyTransition.asymmetric(insertion: .rotationIn, removal: .rotationOut) 24 | } 25 | 26 | static var rotationIn: AnyTransition { 27 | return AnyTransition.modifier(active: RotationTransition(percentage: -0.15), identity: RotationTransition(percentage: 0)) 28 | } 29 | 30 | static var rotationOut: AnyTransition { 31 | return AnyTransition.modifier(active: RotationTransition(percentage: 0.85), identity: RotationTransition(percentage: 1)) 32 | } 33 | } 34 | 35 | struct RotationTransition: GeometryEffect { 36 | var percentage: CGFloat 37 | 38 | var animatableData: CGFloat { 39 | get { percentage } 40 | set { percentage = newValue } 41 | } 42 | 43 | func effectValue(size: CGSize) -> ProjectionTransform { 44 | let angle = CGFloat.animationAngle(percentage: percentage) 45 | let rotation = CGAffineTransform(rotationAngle: angle) 46 | let from = CGAffineTransform(translationX: -size.width / 2.0, y: -size.height / 2.0) 47 | let to = CGAffineTransform(translationX: size.width / 2.0, y: size.height / 2.0) 48 | return ProjectionTransform(from.concatenating(rotation).concatenating(to)) 49 | } 50 | } 51 | 52 | 53 | //MARK: - Utilities 54 | extension FloatingPoint { 55 | 56 | static func animationAngle(percentage: T) -> T { 57 | let pi2 = T.pi * 2 58 | let sign: T = percentage < 0 ? -1 : 1 59 | return sign * (pi2 * (1 - percentage)).truncatingRemainder(dividingBy: pi2) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Sources/Extensions/CGPoint+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGPoint+Extensions.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 29.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 14 | return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 15 | } 16 | 17 | func += (lhs: inout CGPoint, rhs: CGPoint) { 18 | lhs = lhs + rhs 19 | } 20 | 21 | func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 22 | return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) 23 | } 24 | 25 | func -= (lhs: inout CGPoint, rhs: CGPoint) { 26 | lhs = lhs - rhs 27 | } 28 | 29 | func * (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 30 | return CGPoint(x: lhs.x * rhs.x, y: lhs.y * rhs.y) 31 | } 32 | 33 | func *= (lhs: inout CGPoint, rhs: CGPoint) { 34 | lhs = lhs * rhs 35 | } 36 | 37 | func / (lhs: CGPoint, rhs: CGPoint) -> CGPoint { 38 | return CGPoint(x: lhs.x / rhs.x, y: lhs.y / rhs.y) 39 | } 40 | 41 | func /= (lhs: inout CGPoint, rhs: CGPoint) { 42 | lhs = lhs / rhs 43 | } 44 | 45 | func * (lhs: CGSize, rhs: CGFloat) -> CGSize { 46 | return CGSize(width: lhs.width * rhs, height: lhs.height * rhs) 47 | } 48 | 49 | 50 | extension CGPoint { 51 | var length: CGFloat { 52 | return sqrt(self.x * self.x + self.y * self.y) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/Extensions/CGRect+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGRect+Extensions.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 20.05.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | extension CGRect { 13 | 14 | public init(size: CGSize) { 15 | self.init(origin: .zero, size: size) 16 | } 17 | 18 | public var center: CGPoint { 19 | return CGPoint(x: midX, y: midY) 20 | } 21 | 22 | public init(center: CGPoint, size: CGSize) { 23 | self.init(x: center.x - size.width / 2, y: center.y - size.height / 2, width: size.width, height: size.height) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/Extensions/CGSize+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CGSize+Extensions.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 26.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | extension CGSize { 13 | public init(uniform side: CGFloat) { 14 | self.init(width: side, height: side) 15 | } 16 | } 17 | 18 | 19 | extension CGSize: VectorArithmetic { 20 | 21 | public mutating func scale(by rhs: Double) { 22 | self.width = self.width * CGFloat(rhs) 23 | self.height = self.height * CGFloat(rhs) 24 | } 25 | 26 | public var magnitudeSquared: Double { 27 | return Double(sqrt( self.width * self.width + self.height * self.height )) 28 | } 29 | 30 | public static func + (lhs: CGSize, rhs: CGSize) -> CGSize { 31 | return CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height) 32 | } 33 | 34 | public static func - (lhs: CGSize, rhs: CGSize) -> CGSize { 35 | return CGSize(width: lhs.width - rhs.width, height: lhs.height - rhs.height) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Extensions/Collection+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Collection+Extensions.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 23.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Collection { 12 | 13 | func enumeratedArray() -> Array<(offset: Int, element: Self.Element)> { 14 | return Array(self.enumerated()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Sources/Extensions/Comparable+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Compare+Extensions.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 23.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Comparable { 12 | 13 | /** 14 | Bound the current value between a minimum and maximum value 15 | 16 | - parameter min: The minimum possible value 17 | - parameter max: The maximum possible value 18 | 19 | - returns: The current value bounded between a minimum and maximum value 20 | */ 21 | func limited(min: Self, max: Self) -> Self { 22 | var value = self 23 | value.limit(min: min, max: max) 24 | return value 25 | } 26 | 27 | /** 28 | Bound the current value between a minimum and maximum value 29 | 30 | - parameter min: The minimum possible value 31 | - parameter max: The maximum possible value 32 | 33 | - returns: The current value bounded between a minimum and maximum value 34 | */ 35 | func limited(_ min: Self, _ max: Self) -> Self { 36 | return limited(min: min, max: max) 37 | } 38 | 39 | /** 40 | Bound self between a minimum and maximum value, in place 41 | 42 | - parameter min: The minimum possible value 43 | - parameter max: The maximum possible value 44 | */ 45 | mutating func limit(min: Self, max: Self) { 46 | self = Swift.max(Swift.min(self, max), min) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Extensions/UIGestureRecognizer+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIGestureRecognizer+Extensions.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 23.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIGestureRecognizer { 13 | private class GestureAction { 14 | var action: (UIGestureRecognizer) -> Void 15 | 16 | init(action: @escaping (UIGestureRecognizer) -> Void) { 17 | self.action = action 18 | } 19 | } 20 | 21 | private struct AssociatedKeys { 22 | static var ActionName = "action" 23 | } 24 | 25 | private var gestureAction: GestureAction? { 26 | set { objc_setAssociatedObject(self, &AssociatedKeys.ActionName, newValue, .OBJC_ASSOCIATION_RETAIN) } 27 | get { return objc_getAssociatedObject(self, &AssociatedKeys.ActionName) as? GestureAction } 28 | } 29 | 30 | /** 31 | Convenience initializer, associating an action closure with the gesture recognizer (instead of the more traditional target/action). 32 | 33 | - parameter action: The closure for the recognizer to execute. There is no pre-logic to conditionally invoke the closure or not (e.g. only invoke the closure if the gesture recognizer is in a particular state). The closure is merely invoked directly; all handler logic is up to the closure. 34 | 35 | - returns: The UIGestureRecognizer. 36 | */ 37 | public convenience init(action: @escaping (UIGestureRecognizer) -> Void) { 38 | self.init() 39 | gestureAction = GestureAction(action: action) 40 | addTarget(self, action: #selector(handleAction(_:))) 41 | } 42 | 43 | @objc private dynamic func handleAction(_ recognizer: UIGestureRecognizer) { 44 | gestureAction?.action(recognizer) 45 | } 46 | } 47 | 48 | 49 | extension UIGestureRecognizer { 50 | 51 | public var isActive: Bool { 52 | return isEnabled && (state == .changed || state == .began) 53 | } 54 | } 55 | 56 | 57 | extension UIView { 58 | 59 | public enum GestureType { 60 | case tap 61 | case longPress 62 | case pan 63 | case swipe(UISwipeGestureRecognizer.Direction) 64 | case tapCount(Int) 65 | } 66 | 67 | public func addGesture(type: GestureType, callback: @escaping (UIGestureRecognizer) -> Void) { 68 | switch type { 69 | case .tap: 70 | addGestureRecognizer(UITapGestureRecognizer(action: callback)) 71 | case let .tapCount(count): 72 | addGestureRecognizer(UITapGestureRecognizer(action: callback).then { $0.numberOfTapsRequired = count }) 73 | case .longPress: 74 | addGestureRecognizer(UILongPressGestureRecognizer(action: callback)) 75 | case .pan: 76 | addGestureRecognizer(UIPanGestureRecognizer(action: callback)) 77 | case let .swipe(direction): 78 | addGestureRecognizer(UISwipeGestureRecognizer(action: callback).then { $0.direction = direction }) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Sources/Extensions/UIWindow+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIWindow+Extensions.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 23.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension UIWindow { 13 | 14 | static var current: UIWindow? { 15 | //if scene is not connected will use first normal level key window 16 | return sceneActiveWindow ?? keyWindow 17 | } 18 | 19 | static var keyWindow: UIWindow? { 20 | var window = UIApplication.shared.windows.first { $0.isKeyWindow } 21 | if window == nil || window?.windowLevel != .normal { 22 | for w in UIApplication.shared.windows { 23 | if w.windowLevel == .normal { 24 | window = w 25 | } 26 | } 27 | } 28 | return window 29 | } 30 | 31 | static var sceneActiveWindow: UIWindow? { 32 | let window = UIApplication.shared.connectedScenes 33 | .filter({ $0.activationState == .foregroundActive }) 34 | .map({ $0 as? UIWindowScene }) 35 | .compactMap({ $0 }) 36 | .first?.windows 37 | .filter({ $0.isKeyWindow }).first 38 | return window 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /Sources/Models/BlobMenuItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlobMenuItem.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 29.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | public struct BlobMenuItem: Identifiable, Hashable { 13 | 14 | public let id = UUID() 15 | public let selectedIcon: Image 16 | public let unselectedIcon: Image 17 | public let offset: CGPoint 18 | 19 | public init(selectedIcon: Image, 20 | unselectedIcon: Image, 21 | offset: CGPoint = .zero) { 22 | 23 | self.selectedIcon = selectedIcon 24 | self.unselectedIcon = unselectedIcon 25 | self.offset = offset 26 | } 27 | 28 | public init(icon: Image, offset: CGPoint = .zero) { 29 | self.selectedIcon = icon 30 | self.unselectedIcon = icon 31 | self.offset = offset 32 | } 33 | 34 | public func hash(into hasher: inout Hasher) { 35 | hasher.combine(id) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Models/BlobMenuModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlobMenuMode.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 22.05.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | public final class BlobMenuModel: ObservableObject { 13 | @Published public var items: [BlobMenuItem] 14 | @Published public internal(set) var isOpened: Bool 15 | @Published public internal(set) var isBlobMenuItemsVisible: Bool = false 16 | @Published public private(set) var selectedIndex: Int 17 | 18 | private let closeOnSelect: Bool 19 | 20 | public init(items: [BlobMenuItem], 21 | selectedIndex: Int = 0, 22 | isOpened: Bool = false, 23 | closeMenuOnSelect: Bool = false) { 24 | 25 | self.items = items 26 | self.isOpened = isOpened 27 | self.selectedIndex = selectedIndex.limited(0, items.count - 1) 28 | self.closeOnSelect = closeMenuOnSelect 29 | } 30 | 31 | public func selectIndex(_ index: Int) { 32 | let limitedIndex = index.limited(0, items.count - 1) 33 | selectedIndex = limitedIndex 34 | if closeOnSelect { 35 | delay(0.25) { self.closeMenu() } 36 | } 37 | } 38 | 39 | public func closeMenu() { 40 | isOpened = false 41 | isBlobMenuItemsVisible = false 42 | } 43 | 44 | public func openMenu() { 45 | withAnimation { self.isOpened = true } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Utilities/AnimationCompletion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlobMenuBackgoundEffect.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 21.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | private struct CompletionPreferenceKey: PreferenceKey { 13 | typealias Value = CGFloat 14 | 15 | static let defaultValue: CGFloat = 0 16 | 17 | static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { 18 | value = nextValue() 19 | } 20 | } 21 | 22 | 23 | private struct CompletionModifier: AnimatableModifier { 24 | var progress: CGFloat = 0 25 | 26 | var animatableData: CGFloat { 27 | get { progress } 28 | set { progress = newValue } 29 | } 30 | 31 | func body(content: Content) -> some View { 32 | return content.preference(key: CompletionPreferenceKey.self, value: progress) 33 | } 34 | } 35 | 36 | 37 | private final class AnimationHolder { 38 | var animation: Animation? 39 | var disablesAnimations: Bool = false 40 | } 41 | 42 | 43 | extension View { 44 | 45 | func onAnimationCompleted(condition: Bool, completion: @escaping () -> Void) -> some View { 46 | let holder = AnimationHolder() 47 | var completed = false 48 | return self 49 | .transaction { transaction in 50 | // Restore the transaction values 51 | transaction.animation = holder.animation 52 | transaction.disablesAnimations = holder.disablesAnimations 53 | } 54 | .modifier(CompletionModifier(progress: condition ? 1 : 0)) 55 | .transaction { transaction in 56 | // Store transaction value before modification 57 | holder.animation = transaction.animation 58 | holder.disablesAnimations = transaction.disablesAnimations 59 | } 60 | .onPreferenceChange(CompletionPreferenceKey.self) { progress in 61 | if completed == false && progress >= 1 { 62 | completed = true 63 | completion() 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/Utilities/BezierUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BezierUtilities.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 29.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct BezierUtilities { 13 | // 14 | // get Beizer curve points 15 | // p0 - start point 16 | // p1 - first control point 17 | // p2 - second control point 18 | // p3 - finish point 19 | // 20 | static func getCubeCurvePoints(p0: CGPoint, p1: CGPoint, p2: CGPoint, p3: CGPoint) -> [CGPoint] { 21 | //0 <= t >= 1 22 | // used t = 0.3...0.7 for fast calculation 23 | var points: [CGPoint] = [] 24 | for t in stride(from: 0.3, to: 0.7, by: 0.1) { 25 | let x0 = pow((1-t), 3) * Double(p0.x) 26 | let x1 = 3 * t * pow((1-t), 2) * Double(p1.x) 27 | let x2 = 3 * pow(t, 2) * (1-t) * Double(p2.x) 28 | let x3 = pow(t, 3) * Double(p3.x) 29 | let X = x0 + x1 + x2 + x3 30 | 31 | let y0 = pow((1-t), 3) * Double(p0.y) 32 | let y1 = 3 * t * pow((1-t), 2) * Double(p1.y) 33 | let y2 = 3 * pow(t, 2) * (1-t) * Double(p2.y) 34 | let y3 = pow(t, 3) * Double(p3.y) 35 | let Y = y0 + y1 + y2 + y3 36 | points.append(CGPoint(x: X, y: Y)) 37 | } 38 | 39 | return points 40 | } 41 | 42 | // 43 | // get Beizer curve Apex point (Middle of the line) 44 | // p0 - start point 45 | // p1 - first control point 46 | // p2 - second control point 47 | // p3 - finish point 48 | // 49 | static func getCubeCurveApexPoint(p0: CGPoint, p1: CGPoint, p2: CGPoint, p3: CGPoint) -> CGPoint { 50 | let t = 0.5 //Apex need only, 0 <= t >= 1 51 | 52 | let x0 = pow((1-t), 3) * Double(p0.x) 53 | let x1 = 3 * t * pow((1-t), 2) * Double(p1.x) 54 | let x2 = 3 * pow(t, 2) * (1-t) * Double(p2.x) 55 | let x3 = pow(t, 3) * Double(p3.x) 56 | let X = x0 + x1 + x2 + x3 57 | 58 | let y0 = pow((1-t), 3) * Double(p0.y) 59 | let y1 = 3 * t * pow((1-t), 2) * Double(p1.y) 60 | let y2 = 3 * pow(t, 2) * (1-t) * Double(p2.y) 61 | let y3 = pow(t, 3) * Double(p3.y) 62 | let Y = y0 + y1 + y2 + y3 63 | 64 | return CGPoint(x: X, y: Y) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/Utilities/CommonUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommonUtilities.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 21.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | extension View { 13 | public func frame(size: CGSize, alignment: Alignment = .center) -> some View { 14 | 15 | return self.frame(width: size.width, height: size.height, alignment: alignment) 16 | } 17 | 18 | public func offset(_ offset: CGPoint) -> some View { 19 | return self.offset(x: offset.x, y: offset.y) 20 | } 21 | 22 | public var asAnyView: AnyView { 23 | return AnyView(self) 24 | } 25 | } 26 | 27 | 28 | extension Spacer { 29 | 30 | public func size(width: CGFloat? = nil, height: CGFloat? = nil) -> some View { 31 | 32 | guard width != nil || height != nil else { 33 | return AnyView(self) 34 | } 35 | 36 | return AnyView(self.frame(width: width, height: height)) 37 | } 38 | } 39 | 40 | 41 | extension View { 42 | // If condition is met, apply modifier, otherwise, leave the view untouched 43 | public func conditionalModifier(_ condition: Bool, _ modifier: T) -> some View where T: ViewModifier { 44 | Group { 45 | if condition { 46 | self.modifier(modifier) 47 | } else { 48 | self 49 | } 50 | } 51 | } 52 | 53 | // Apply trueModifier if condition is met, or falseModifier if not. 54 | public func conditionalModifier(_ condition: Bool, _ trueModifier: M1, _ falseModifier: M2) -> some View where M1: ViewModifier, M2: ViewModifier { 55 | Group { 56 | if condition { 57 | self.modifier(trueModifier) 58 | } else { 59 | self.modifier(falseModifier) 60 | } 61 | } 62 | } 63 | } 64 | 65 | extension CGFloat { 66 | static let minAlowedScale: CGFloat = 0.001 67 | } 68 | 69 | 70 | func delay(_ delay: TimeInterval, _ closure: @escaping () -> Void) { 71 | DispatchQueue.main.asyncAfter( 72 | deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure) 73 | } 74 | -------------------------------------------------------------------------------- /Sources/Utilities/KayframesAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KayframesAnimation.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 26.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | /// One keyframe in the animation. 13 | public struct Keyframe: Equatable { 14 | 15 | /// The value of the keyfame. 16 | public let value: V 17 | 18 | /// The point in time where the `value` is reached, must be in range [0, 1]. 19 | public let progress: Double 20 | 21 | /// Initialize with a value and progress. 22 | public init(value: V, progress: Double) { 23 | self.value = value 24 | self.progress = progress 25 | } 26 | } 27 | 28 | public struct KeyframeModifier: AnimatableModifier { 29 | 30 | /// The array of keyframes. 31 | public let keyframes: [Keyframe] 32 | 33 | 34 | /// The `AnimatableModifier`. 35 | public let modifier: M 36 | 37 | 38 | /// The current progress. Must be in range [0, 1]. 39 | private var progress: Double 40 | 41 | 42 | public var animatableData: Double { 43 | get { progress } 44 | set { progress = newValue } 45 | } 46 | 47 | /// Initialize a new `KeyframeModifier`. 48 | /// - Parameter keyframes: An array of the keyframes. Does not have to be sorted. 49 | /// - Parameter modifier: The modifier used to apply the changes. 50 | /// - Parameter progress: The progress of the animation. Use a `@State` variable of value `0` and set to `1` to start animation. 51 | public init(keyframes: [Keyframe], modifier: M, progress: Double) { 52 | self.keyframes = keyframes.sorted(by: { $0.progress <= $1.progress }) 53 | self.modifier = modifier 54 | self.progress = progress 55 | } 56 | 57 | /// Initialize a new `KeyframeModifier`. 58 | /// - Parameter keyframes: An array of values which will be used as equally distributed keyframes. 59 | /// - Parameter modifier: The modifier used to apply the changes. 60 | /// - Parameter progress: The progress of the animation. Use a `@State` variable of value `0` and set to `1` to start animation. 61 | public init(values: [M.AnimatableData], modifier: M, progress: Double) { 62 | let frames = values.enumerated() 63 | .map { Keyframe(value: $1, progress: 1 / Double(values.count - 1) * Double($0)) } 64 | self.init(keyframes: frames, modifier: modifier, progress: progress) 65 | } 66 | 67 | public func body(content: Content) -> some View { 68 | let progress = min(max(self.progress, 0), 1) 69 | let prev = keyframes.last(where: { $0.progress <= progress })! 70 | let next = keyframes.first(where: { $0.progress >= progress })! 71 | var m = modifier 72 | 73 | if prev != next { 74 | let factor = 1 / (next.progress - prev.progress) 75 | var val1 = next.value 76 | val1.scale(by: (progress - prev.progress) * factor) 77 | 78 | var val2 = prev.value 79 | val2.scale(by: (next.progress - progress) * factor) 80 | 81 | m.animatableData = val1 + val2 82 | } else { 83 | m.animatableData = prev.value 84 | } 85 | return content.modifier(m) 86 | } 87 | } 88 | 89 | extension View { 90 | 91 | /// Creates a keyframe animation. Use an `.animation` modifier to specify the animation. 92 | /// - Parameter keyframes: The array of keyframes. Does not have to be sorted. 93 | /// - Parameter progress: The progress of the animation. Use a `@State` variable of value `0` and set to `1` to start animation. 94 | /// - Parameter modifier: The modifier used to apply the changes. 95 | public func keyframes(_ keyframes: [Keyframe], 96 | progress: Double, 97 | modifier: M) -> some View { 98 | return self.modifier(KeyframeModifier(keyframes: keyframes, modifier: modifier, progress: progress)) 99 | } 100 | 101 | /// Creates a keyframe animation by equally distributing the `values`. Use an `.animation` modifier to specify the animation. 102 | /// - Parameter values: The values of the animation. 103 | /// - Parameter progress: The progress of the animation. Use a `@State` variable of value `0` and set to `1` to start animation. 104 | /// - Parameter modifier: The modifier used to apply the changes. 105 | public func keyframes(_ values: [M.AnimatableData], 106 | progress: Double, 107 | modifier: M) -> some View { 108 | return self.modifier(KeyframeModifier(values: values, modifier: modifier, progress: progress)) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/Utilities/ScaleKeyframesAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScaleKeyframesAnimation.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 26.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | /// An example of a `AnimatableModifier` to animate the `.scale` of a `View ` in an keyframe animation. 13 | private struct ScaleModifier: AnimatableModifier { 14 | var animatableData: CGFloat = .zero 15 | 16 | func body(content: Content) -> some View { 17 | return content.scaleEffect(animatableData, anchor: .center) 18 | } 19 | } 20 | 21 | extension View { 22 | 23 | /// Create a keyframe animation of the scale of the view. Use an `.animation` modifier to specify the animation. 24 | /// - Parameter keyframes: The array of keyframes. Does not have to be sorted. 25 | /// - Parameter progress: The progress of the animation. Use a `@State` variable of value `0` and set to `1` to start animation. 26 | private func keyframes(scale keyframes: [Keyframe], progress: Double) -> some View { 27 | return self.keyframes(keyframes, progress: progress, modifier: ScaleModifier()) 28 | } 29 | 30 | /// Create a keyframe animation of the scale of the view by equally distributing the `values`. Use an `.animation` modifier to specify the animation. 31 | /// - Parameter keyframes: The scale values of the animation. 32 | /// - Parameter progress: The progress of the animation. Use a `@State` variable of value `0` and set to `1` to start animation. 33 | public func keyframes(scale values: [CGFloat], progress: Double) -> some View { 34 | return self.keyframes(values, progress: progress, modifier: ScaleModifier()) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Utilities/SizeKeyframesAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FrameKeyframesAnimation.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 26.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | /// An example of a `AnimatableModifier` to animate the `.scale` of a `View ` in an keyframe animation. 13 | private struct SizeModifier: AnimatableModifier { 14 | var animatableData: CGSize = .zero 15 | 16 | func body(content: Content) -> some View { 17 | return content.frame(size: animatableData) 18 | } 19 | } 20 | 21 | 22 | extension View { 23 | 24 | /// Create a keyframe animation of the size of the view. Use an `.animation` modifier to specify the animation. 25 | /// - Parameter keyframes: The array of keyframes. Does not have to be sorted. 26 | /// - Parameter progress: The progress of the animation. Use a `@State` variable of value `0` and set to `1` to start animation. 27 | public func keyframes(size keyframes: [Keyframe], progress: Double) -> some View { 28 | return self.keyframes(keyframes, progress: progress, modifier: SizeModifier()) 29 | } 30 | 31 | /// Create a keyframe animation of the size of the view by equally distributing the `values`. Use an `.animation` modifier to specify the animation. 32 | /// - Parameter keyframes: The size values of the animation. 33 | /// - Parameter progress: The progress of the animation. Use a `@State` variable of value `0` and set to `1` to start animation. 34 | public func keyframes(size values: [CGSize], progress: Double) -> some View { 35 | return self.keyframes(values, progress: progress, modifier: SizeModifier()) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/Utilities/Then.swift: -------------------------------------------------------------------------------- 1 | import CoreGraphics 2 | import Foundation 3 | #if os(iOS) || os(tvOS) 4 | import UIKit.UIGeometry 5 | #endif 6 | 7 | public protocol Then {} 8 | 9 | extension Then where Self: Any { 10 | 11 | /// Makes it available to set properties with closures just after initializing and copying the value types. 12 | /// 13 | /// let frame = CGRect().with { 14 | /// $0.origin.x = 10 15 | /// $0.size.width = 100 16 | /// } 17 | public func with(_ block: (inout Self) throws -> Void) rethrows -> Self { 18 | var copy = self 19 | try block(©) 20 | return copy 21 | } 22 | 23 | /// Makes it available to execute something with closures. 24 | /// 25 | /// UserDefaults.standard.do { 26 | /// $0.set("devxoul", forKey: "username") 27 | /// $0.set("devxoul@gmail.com", forKey: "email") 28 | /// $0.synchronize() 29 | /// } 30 | public func `do`(_ block: (Self) throws -> Void) rethrows { 31 | try block(self) 32 | } 33 | } 34 | 35 | extension Then where Self: AnyObject { 36 | 37 | /// Makes it available to set properties with closures just after initializing. 38 | /// 39 | /// let label = UILabel().then { 40 | /// $0.textAlignment = .Center 41 | /// $0.textColor = UIColor.blackColor() 42 | /// $0.text = "Hello, World!" 43 | /// } 44 | public func then(_ block: (Self) throws -> Void) rethrows -> Self { 45 | try block(self) 46 | return self 47 | } 48 | } 49 | 50 | extension NSObject: Then {} 51 | 52 | extension CGPoint: Then {} 53 | extension CGRect: Then {} 54 | extension CGSize: Then {} 55 | extension CGVector: Then {} 56 | 57 | #if os(iOS) || os(tvOS) 58 | extension UIEdgeInsets: Then {} 59 | extension UIOffset: Then {} 60 | extension UIRectEdge: Then {} 61 | #endif 62 | -------------------------------------------------------------------------------- /Sources/Utilities/ViewSwapper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewSwapper.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 21.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct ViewSwapper: View { 13 | let first: T 14 | let second: M 15 | let isFirst: Bool 16 | 17 | var body: some View { 18 | Group { 19 | if isFirst { 20 | first 21 | } else { 22 | second 23 | } 24 | } 25 | } 26 | } 27 | 28 | 29 | extension View { 30 | @ViewBuilder func add(if condition: Bool, transform: (Self) -> T) -> some View where T : View { 31 | 32 | if condition { 33 | transform(self) 34 | } else { 35 | self 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Sources/Views/BackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackgroundView.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 27.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | 13 | struct BackgroundPreferenceData { 14 | let bounds: Anchor 15 | } 16 | 17 | struct BackgroundPreferenceKey: PreferenceKey { 18 | typealias Value = [BackgroundPreferenceData] 19 | 20 | static var defaultValue: [BackgroundPreferenceData] = [] 21 | 22 | static func reduce(value: inout [BackgroundPreferenceData], nextValue: () -> [BackgroundPreferenceData]) { 23 | value.append(contentsOf: nextValue()) 24 | } 25 | } 26 | 27 | struct BackgroundView: View { 28 | 29 | let color: Color 30 | 31 | var body: some View { 32 | return Rectangle() 33 | .fill(color) 34 | .anchorPreference(key: BackgroundPreferenceKey.self, value: .bounds, transform: { [BackgroundPreferenceData(bounds: $0)] }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Sources/Views/BlobMenuView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlobMenuView.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 27.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | public struct BlobMenuView: View { 12 | 13 | private let configuration: BlobMenuConfiguration 14 | @ObservedObject private var viewModel: BlobMenuModel 15 | private let hapticFeedback: UIImpactFeedbackGenerator.FeedbackStyle? 16 | 17 | public init(model: BlobMenuModel, 18 | configuration: BlobMenuConfiguration = .default, 19 | hapticFeedback: UIImpactFeedbackGenerator.FeedbackStyle? = .light) { 20 | 21 | self.viewModel = model 22 | self.configuration = configuration 23 | self.hapticFeedback = hapticFeedback 24 | 25 | UIWindow.current?.addGesture(type: .tap) {[weak model] _ in 26 | model?.closeMenu() 27 | } 28 | } 29 | 30 | public var body: some View { 31 | ZStack(alignment: .center) { 32 | HStack { 33 | Spacer() 34 | self.background.overlay(self.itemsView) 35 | Spacer().size(width: self.viewModel.isOpened ? nil : Theme.padding) 36 | } 37 | .backgroundPreferenceValue(BackgroundPreferenceKey.self) { p in 38 | GeometryReader { geometry in 39 | self.createStickyView(geometry: geometry, preferences: p) 40 | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) 41 | } 42 | } 43 | HStack { 44 | Spacer() 45 | HamburgerView(isOpened: viewModel.isOpened, color: configuration.hamburgerColor) 46 | Spacer().size(width: Theme.padding) 47 | } 48 | } 49 | .frame(height: Theme.height) 50 | } 51 | 52 | private func createStickyView(geometry: GeometryProxy, preferences: [BackgroundPreferenceData]) -> some View { 53 | 54 | guard !self.viewModel.isBlobMenuItemsVisible else { 55 | return AnyView(Color.clear) 56 | } 57 | 58 | let p = preferences.first 59 | let f = geometry.frame(in: .local) 60 | let b = p != nil ? geometry[p!.bounds] : .zero 61 | 62 | let r = Theme.closedSize.height / 2 63 | let w = f.width - b.maxX + r 64 | let base = CGRect(x: f.width, y: f.minY, width: 50, height: f.height) 65 | 66 | let effectView = StickyEffectShape(baseRect: base, figureRect: b, figureCornerRadius: r, avulsionDistance: Theme.stickyEffectAvulsionDistance) 67 | 68 | return effectView 69 | .fill(configuration.backgroundColor) 70 | .frame(size: CGSize(width: w, height: f.height)) 71 | .asAnyView 72 | } 73 | 74 | private var background: some View { 75 | BackgroundView(color: configuration.backgroundColor) 76 | .cornerRadius(Theme.closedSize.height / 2) 77 | .keyframes(size: Theme.backgroundSizeKeyframes(isOpened: viewModel.isOpened, items: viewModel.items), progress: viewModel.isOpened ? 1 : 0) 78 | .animation(Animation.interpolatingSpring(mass: 1, stiffness: 170, damping: 15, initialVelocity: 1).delay(viewModel.isOpened ? 0.12 : 0)) 79 | .onAnimationCompleted(condition: viewModel.isOpened) { 80 | self.viewModel.isBlobMenuItemsVisible = true 81 | } 82 | .onTapGesture { 83 | withAnimation { 84 | self.viewModel.isOpened = true 85 | } 86 | } 87 | .allowsHitTesting(!self.viewModel.isOpened) 88 | } 89 | 90 | private var itemsView: some View { 91 | Group { 92 | if Theme.isScrollable(items: viewModel.items) { 93 | ScrollView(.horizontal, showsIndicators: false) { itemsContent } 94 | } else { 95 | itemsContent 96 | } 97 | } 98 | .clipShape(Capsule(style: .circular)) 99 | .animation(Animation.easeInOut(duration: 0.35).delay(0.15)) 100 | .allowsHitTesting(self.viewModel.isBlobMenuItemsVisible) 101 | } 102 | 103 | private var itemsContent: some View { 104 | HStack(spacing: 20) { 105 | ForEach(viewModel.items.enumeratedArray(), id: \.element) { index, item in 106 | BlobMenuItemView(item: item, 107 | isSelected: self.viewModel.selectedIndex == index, 108 | isOpened: self.viewModel.isBlobMenuItemsVisible, 109 | selectionColor: self.configuration.selectionColor) 110 | .onTapGesture { 111 | guard self.viewModel.selectedIndex != index else { return } 112 | 113 | if let style = self.hapticFeedback { 114 | let generator = UIImpactFeedbackGenerator(style: style) 115 | generator.prepare() 116 | generator.impactOccurred() 117 | } 118 | 119 | self.viewModel.selectIndex(index) 120 | } 121 | } 122 | } 123 | } 124 | } 125 | 126 | 127 | //MARK: - Theme 128 | extension BlobMenuView { 129 | enum Theme { 130 | static let height: CGFloat = 60 131 | static let padding: CGFloat = 10 132 | static let BlobMenuItemsSpace: CGFloat = 20 133 | static let stickyEffectAvulsionDistance: CGFloat = 120 134 | static let closedSize = CGSize(width: 60, height: height) 135 | static let collapsedSize = CGSize(width: 90, height: 70) 136 | static let maxOpenSize = CGSize(width: UIScreen.main.bounds.width - 60, height: height) 137 | 138 | private static func itemsSize(items: [BlobMenuItem]) -> CGSize { 139 | let w = CGFloat(items.count) * BlobMenuItemView.Theme.size.width + CGFloat(items.count - 1) * BlobMenuItemsSpace 140 | return CGSize(width: w, height: height) 141 | } 142 | 143 | static func openedSize(items: [BlobMenuItem]) -> CGSize { 144 | if isScrollable(items: items) { 145 | return maxOpenSize 146 | } else { 147 | return itemsSize(items: items) 148 | } 149 | } 150 | 151 | static func isScrollable(items: [BlobMenuItem]) -> Bool { 152 | return itemsSize(items: items).width > maxOpenSize.width 153 | } 154 | 155 | static func backgroundSizeKeyframes(isOpened: Bool, items: [BlobMenuItem]) -> [Keyframe] { 156 | if isOpened { 157 | //will use during opening animation 158 | return [ 159 | Keyframe(value: closedSize, progress: 0), 160 | Keyframe(value: openedSize(items: items), progress: 1) 161 | ] 162 | } else { 163 | //will use during closing animation 164 | return [ 165 | Keyframe(value: closedSize, progress: 0), 166 | Keyframe(value: collapsedSize, progress: 0.3), 167 | Keyframe(value: openedSize(items: items), progress: 1) 168 | ] 169 | } 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /Sources/Views/HamburgerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HamburgerView.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 29.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct HamburgerView: View { 12 | 13 | let isOpened: Bool 14 | let color: Color 15 | 16 | private var rotationAngle: Angle { 17 | return Angle(degrees: isOpened ? -90 : 0) 18 | } 19 | 20 | var body: some View { 21 | VStack(alignment: .center, spacing: Theme.lineSpacing) { 22 | line 23 | line 24 | line 25 | } 26 | .frame(size: BlobMenuView.Theme.closedSize) 27 | .opacity(isOpened ? 0 : 1) 28 | .rotationEffect(rotationAngle, anchor: UnitPoint.center) 29 | .animation(Animation.easeInOut(duration: 0.25).delay( isOpened ? 0 : 0.2)) 30 | } 31 | 32 | private var line: some View { 33 | Rectangle() 34 | .frame(width: Theme.lineWidth, height: Theme.lineThickness) 35 | .foregroundColor(color) 36 | .cornerRadius(Theme.lineCornerRadius) 37 | } 38 | } 39 | 40 | 41 | //MARK: - Preview 42 | struct HamburgerView_Previews: PreviewProvider { 43 | static var previews: some View { 44 | return HamburgerView(isOpened: false, color: .hamburgerColor) 45 | } 46 | } 47 | 48 | 49 | //MARK: - Theme 50 | extension HamburgerView { 51 | enum Theme { 52 | static let lineSpacing: CGFloat = 6 53 | static let lineThickness: CGFloat = 2 54 | static let lineCornerRadius: CGFloat = 1 55 | static let lineWidth: CGFloat = 24 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sources/Views/MenuItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BlobMenuItemView.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 29.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct BlobMenuItemView: View { 13 | 14 | let item: BlobMenuItem 15 | let isSelected: Bool 16 | let isOpened: Bool 17 | let selectionColor: Color 18 | 19 | public var body: some View { 20 | ZStack { 21 | selectionView 22 | ringView 23 | if isOpened { iconView } 24 | } 25 | .padding(Theme.contentInsets) 26 | .contentShape(Rectangle()) 27 | .frame(size: Theme.size) 28 | } 29 | 30 | private var iconView: some View { 31 | let image = isSelected ? item.selectedIcon : item.unselectedIcon 32 | return image 33 | .offset(item.offset) 34 | .transition(AnyTransition.blobBlobMenuItem) 35 | } 36 | 37 | private var selectionView: some View { 38 | Circle() 39 | .foregroundColor(selectionColor) 40 | .frame(size: Theme.contentSize) 41 | .opacity(isSelected ? 1 : 0) 42 | .animation(nil) 43 | .scaleEffect(isOpened ? (isSelected ? 1 : 0.8) : CGFloat.minAlowedScale) 44 | .animation(.interpolatingSpring(mass: 1, stiffness: 170, damping: isOpened ? 8 : 50, initialVelocity: 1)) 45 | } 46 | 47 | private var ringView: some View { 48 | let show = isOpened && isSelected 49 | return Circle() 50 | .stroke(selectionColor) 51 | .frame(size: Theme.contentSize) 52 | .opacity(show ? 0 : 1) 53 | .animation(show ? Animation.easeInOut.delay(0.2) : nil) 54 | .scaleEffect(show ? 1.3 : CGFloat.minAlowedScale) 55 | .animation(show ? .easeInOut : nil) 56 | } 57 | } 58 | 59 | 60 | //MARK: - Theme 61 | extension BlobMenuItemView { 62 | enum Theme { 63 | static let size = CGSize(uniform: 60) 64 | static let contentInsets: CGFloat = 15 65 | static let contentSize = CGSize(uniform: size.height - contentInsets) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/Views/StickyEffectView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StickyEffectView.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 27.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | struct StickyEffectShape: Shape { 13 | 14 | private let pathGenerator = StickyPathGenerator.default 15 | private let baseRect: CGRect 16 | private let figureRect: CGRect 17 | private let figureCornerRadius: CGFloat 18 | private let avulsionDistance: CGFloat 19 | 20 | init(baseRect: CGRect, 21 | figureRect: CGRect, 22 | figureCornerRadius: CGFloat, 23 | avulsionDistance: CGFloat) { 24 | 25 | self.baseRect = baseRect 26 | self.figureRect = figureRect 27 | self.figureCornerRadius = figureCornerRadius 28 | self.avulsionDistance = avulsionDistance 29 | } 30 | 31 | func path(in rect: CGRect) -> Path { 32 | 33 | let path = pathGenerator.generatePath(baseRect: baseRect, 34 | figureRect: figureRect, 35 | figureCornerRadius: figureCornerRadius, 36 | avulsionDistance: avulsionDistance) 37 | 38 | return Path(path) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/Views/StickyPathGenerator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StickyPathGenerator.swift 3 | // BlobMenu 4 | // 5 | // Created by Igor K. on 29.04.2020. 6 | // Copyright © 2020 Ramotion. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SwiftUI 11 | 12 | final class StickyPathGenerator { 13 | 14 | static let `default` = StickyPathGenerator() 15 | 16 | private enum Direction { 17 | case up 18 | case left 19 | case right 20 | case down 21 | } 22 | 23 | private struct Input { 24 | let baseRect: CGRect 25 | let figureRect: CGRect 26 | let figureCornerRadius: CGFloat 27 | let avulsionDistance: CGFloat 28 | 29 | /// Calculated constants 30 | let figurePointA: CGPoint 31 | let figurePointB: CGPoint 32 | let basePointA: CGPoint 33 | let basePointB: CGPoint 34 | let initialBasePointA: CGPoint 35 | let initialBasePointB: CGPoint 36 | 37 | /// Distance between figure and base 38 | let distance: CGFloat 39 | 40 | init(baseRect: CGRect, 41 | figureRect: CGRect, 42 | figureCornerRadius: CGFloat, 43 | avulsionDistance: CGFloat) { 44 | 45 | 46 | self.baseRect = baseRect 47 | self.figureRect = figureRect 48 | self.figureCornerRadius = figureCornerRadius 49 | self.avulsionDistance = avulsionDistance 50 | 51 | figurePointA = CGPoint(x: figureRect.maxX, y: figureRect.minY) 52 | figurePointB = CGPoint(x: figureRect.maxX, y: figureRect.maxY) 53 | basePointA = CGPoint(x: baseRect.minX, y: max(baseRect.minY, figureRect.minY)) 54 | basePointB = CGPoint(x: baseRect.minX, y: min(baseRect.maxY, figureRect.maxY)) 55 | initialBasePointA = basePointA 56 | initialBasePointB = basePointB 57 | distance = baseRect.minX - figureRect.maxX 58 | } 59 | } 60 | 61 | ///Bool flag to control shape constriction 62 | private var stopConstriction: Bool = false 63 | 64 | private var apexControlUp: CGPoint = CGPoint.zero 65 | private var apexControlDown: CGPoint = CGPoint.zero 66 | 67 | private var storedBasePointA: CGPoint? 68 | private var storedBasePointB: CGPoint? 69 | 70 | func generatePath(baseRect: CGRect, 71 | figureRect: CGRect, 72 | figureCornerRadius: CGFloat, 73 | avulsionDistance: CGFloat) -> CGPath { 74 | 75 | let input = Input(baseRect: baseRect, figureRect: figureRect, figureCornerRadius: figureCornerRadius, avulsionDistance: avulsionDistance) 76 | 77 | return generateShapePath(input: input).cgPath 78 | } 79 | 80 | 81 | /// Calculate Base Line Points for shape 82 | /// - used movement on Xaxis 83 | /// - return (point0, point3) 84 | private func calculateBaseLinePoints(input: Input) -> (CGPoint, CGPoint) { 85 | let distance = max(0, input.distance) 86 | let pY = { (from: CGFloat, to: CGFloat, lineSegment: CGFloat) -> CGFloat in 87 | //calculate movement coeficient from start to finish while distance <= avulsion 88 | let coef = abs(to - from) / lineSegment 89 | let p = from < to ? from + coef * distance : from - coef * distance 90 | return p 91 | } 92 | 93 | let movement = (input.figurePointA.y - input.initialBasePointA.y) 94 | let cornerRadius = input.figureCornerRadius 95 | 96 | //point0 97 | let startY0 = max(input.baseRect.minY, input.initialBasePointA.y - cornerRadius + movement) 98 | let finishY0 = max(input.baseRect.minY, input.initialBasePointA.y + cornerRadius + movement) 99 | let point0 = CGPoint(x: input.initialBasePointA.x, 100 | y: pY(startY0, finishY0, input.avulsionDistance)) 101 | 102 | //point3 103 | let startY3 = min(input.baseRect.maxY, input.initialBasePointB.y + cornerRadius + movement) 104 | let finishY3 = min(input.baseRect.maxY, input.initialBasePointB.y - cornerRadius + movement) 105 | let point3 = CGPoint(x: input.initialBasePointB.x, 106 | y: pY(startY3, finishY3, input.avulsionDistance)) 107 | 108 | //set limit for movement 109 | let limitDistanceBetwenPoints = input.figureRect.height - cornerRadius * 2 110 | if stopConstriction || point3.y - point0.y < limitDistanceBetwenPoints, 111 | let pA = storedBasePointA, let pB = storedBasePointB { 112 | //in that case use stored values 113 | return (pA, pB) 114 | } 115 | 116 | return (point0, point3) 117 | } 118 | 119 | /// Calculate points of intercsection betwen two circles 120 | /// The solution is to find tangents by calculation of intersection of two circles 121 | /// https://www.mathsisfun.com/geometry/construct-circletangent.html 122 | /// Intersection of two circles 123 | /// Discussion http://stackoverflow.com/questions/3349125/circle-circle-intersection-points 124 | /// Description http://paulbourke.net/geometry/circlesphere/ 125 | 126 | private func findIntersection(centerCircle1 c1: CGPoint, radiusCircle1 c1r: CGFloat, centerCircle2 c2: CGPoint) -> (CGPoint, CGPoint) { 127 | 128 | //Calculate distance between centres of circle 129 | let d = (c1 - c2).length 130 | let c2r = d //in our case 131 | let m = c1r + c2r 132 | var n = c1r - c2r 133 | 134 | if (n < 0) { 135 | n = n * -1 136 | } 137 | 138 | //No solns 139 | if (d > m) { 140 | return (CGPoint.zero, CGPoint.zero) 141 | } 142 | //Circle are contained within each other 143 | if (d < n) { 144 | return (CGPoint.zero, CGPoint.zero) 145 | } 146 | //Circles are the same 147 | if (d == 0 && c1r == c2r) { 148 | return (CGPoint.zero, CGPoint.zero) 149 | } 150 | 151 | let a = (c1r * c1r - c2r * c2r + d * d) / (2 * d) 152 | 153 | let h = sqrt(c1r * c1r - a * a) 154 | 155 | //Calculate point p, where the line through the circle intersection points crosses the line between the circle centers. 156 | 157 | var x = c1.x + (a / d) * (c2.x - c1.x) 158 | var y = c1.y + (a / d) * (c2.y - c1.y) 159 | let p = CGPoint(x: x, y: y) 160 | 161 | //1 Intersection , circles are touching 162 | if (d == c1r + c2r) { 163 | return (p, CGPoint.zero) 164 | } 165 | 166 | //2 Intersections 167 | //Intersection 1 168 | x = p.x + (h / d) * (c2.y - c1.y) 169 | y = p.y - (h / d) * (c2.x - c1.x) 170 | let p1 = CGPoint(x: x, y: y) 171 | 172 | //Intersection 2 173 | x = p.x - (h / d) * (c2.y - c1.y) 174 | y = p.y + (h / d) * (c2.x - c1.x) 175 | let p2 = CGPoint(x: x, y: y) 176 | 177 | return (p1, p2) 178 | } 179 | 180 | 181 | /// Calculate left top point of shape 182 | /// - used method of intersection betwen two circles 183 | /// - first circle - centerfigCircle 184 | /// - second circle - circle from controlPoint 185 | private func calculateShapePoint1(input: Input, cp:CGPoint, point0:CGPoint) -> CGPoint { 186 | let cornerRadius = input.figureCornerRadius 187 | var centerfigCircle = CGPoint(x: input.figurePointA.x - cornerRadius, 188 | y: input.figurePointA.y + cornerRadius) 189 | centerfigCircle.x = point0.x - centerfigCircle.x < cornerRadius ? point0.x - cornerRadius : centerfigCircle.x 190 | centerfigCircle.x = max(centerfigCircle.x, input.figurePointA.x + cornerRadius - input.figureRect.width) 191 | let x3 = (centerfigCircle.x + cp.x) / 2 192 | let y3 = (centerfigCircle.y + cp.y) / 2 193 | let c1 = centerfigCircle 194 | let c2 = CGPoint(x: x3, y: y3) 195 | let c1r = cornerRadius 196 | var (p1, _) = findIntersection(centerCircle1: c1, radiusCircle1: c1r, centerCircle2: c2) 197 | 198 | //when something wrong in findIntersection pass top center point 199 | if p1 == CGPoint.zero { 200 | p1 = CGPoint(x: centerfigCircle.x, y: centerfigCircle.y - cornerRadius) 201 | } 202 | 203 | return p1 204 | } 205 | 206 | /// Calculate left bottom point of shape 207 | /// - used method of intersection betwen two circles 208 | /// - first circle - centerfigCircle 209 | /// - second circle - circle from controlPoint 210 | private func calculateShapePoint2(input: Input, cp:CGPoint, point3:CGPoint) -> CGPoint { 211 | let cornerRadius = input.figureCornerRadius 212 | var circleCenter = CGPoint(x: input.figurePointB.x - cornerRadius, 213 | y: input.figurePointB.y - cornerRadius) 214 | circleCenter.x = point3.x - circleCenter.x < cornerRadius ? point3.x - cornerRadius : circleCenter.x 215 | circleCenter.x = max(circleCenter.x, input.figurePointA.x + cornerRadius - input.figureRect.width) 216 | let x3 = (circleCenter.x + cp.x) / 2 217 | let y3 = (circleCenter.y + cp.y) / 2 218 | let c1 = circleCenter 219 | let c2 = CGPoint(x: x3, y: y3) 220 | let c1r = cornerRadius 221 | var (_, p2) = findIntersection(centerCircle1: c1, radiusCircle1: c1r, centerCircle2: c2) 222 | 223 | if p2 == CGPoint.zero { 224 | p2 = CGPoint(x: circleCenter.x, y: circleCenter.y - cornerRadius) 225 | } 226 | 227 | return p2 228 | } 229 | 230 | 231 | /// Calculate 2 control points for Bezier Path 232 | /// - concavity depends of distance 233 | /// - used movement for concavity effect 234 | private func calculateControlPoints(input: Input, basePoint: CGPoint, direction: Direction) -> (CGPoint, CGPoint) { 235 | 236 | let k: CGFloat = (input.figureRect.width > input.figureRect.height) ? 1.05 : 1.3 237 | var y: CGFloat = 0 238 | 239 | if direction == .down { 240 | let controlPointСoncavity = input.distance + input.distance * 0.05 241 | let deltaY = abs(basePoint.y - input.figurePointA.y) / k 242 | y = basePoint.y + controlPointСoncavity 243 | y = max(basePoint.y + deltaY, y) 244 | 245 | } else if direction == .up { 246 | let controlPointСoncavity = max(0, input.distance + input.distance * 0.05) 247 | let deltaY = abs(basePoint.y - input.figurePointB.y) / k 248 | y = basePoint.y - controlPointСoncavity 249 | y = min(basePoint.y - deltaY, y) 250 | } 251 | 252 | let cp1 = CGPoint(x: basePoint.x, y: y) 253 | let cp2 = CGPoint(x: min(basePoint.x, input.figurePointA.x + input.distance / 4), y: cp1.y) 254 | 255 | return (cp1, cp2) 256 | } 257 | 258 | //MARK: - Generate main path 259 | /* 260 | vertical scheme: 261 | point1 |------------------------------| point2 262 | | | 263 | | -cpUp2 cpDown2- | 264 | | | 265 | | -cpUp1 cpDown1- | 266 | point0 |______________________________| point3 267 | 268 | 269 | horizontal scheme: 270 | point1 _____________________ point0 271 | | cpTop2 cpTop1 | 272 | | | 273 | | | 274 | | | 275 | | cpBottom2 cpBottom1 | 276 | point2 |_____________________| point3 277 | 278 | */ 279 | 280 | /// Generate shape path 281 | /// - calculate baseLinePoints (point0, point3) 282 | /// - calculate control points 283 | /// - calculate topLinePoints (point1, point2) 284 | /// - constrinction control 285 | /// - draw the shape by points 286 | private func generateShapePath(input: Input) -> UIBezierPath { 287 | let distance = max(0, input.distance) 288 | 289 | //calculate base points 290 | let (point0, point3) = calculateBaseLinePoints(input: input) 291 | 292 | //calculete control points 293 | var (cpUp1, cpUp2) = calculateControlPoints(input: input, basePoint: point0, direction: .down) 294 | var (cpDown1, cpDown2) = calculateControlPoints(input: input, basePoint: point3, direction: .up) 295 | var point1 = calculateShapePoint1(input: input, cp: cpUp2, point0: point0) 296 | var point2 = calculateShapePoint2(input: input, cp: cpDown2, point3: point3) 297 | 298 | //constriction control 299 | let upPoints = BezierUtilities.getCubeCurvePoints(p0: point0, p1: cpUp1, p2: cpUp2, p3: point1) 300 | let downPoints = BezierUtilities.getCubeCurvePoints(p0: point2, p1: cpDown2, p2: cpDown1, p3: point3) 301 | 302 | //search for curve cross 303 | var crossed = false 304 | for up in upPoints { 305 | for dp in downPoints { 306 | let delta = dp.y - up.y 307 | if delta <= 1 { 308 | crossed = true 309 | break 310 | } 311 | } 312 | } 313 | 314 | self.stopConstriction = crossed 315 | 316 | //if true - we'll use old values 317 | if crossed { 318 | cpUp2.y = apexControlUp.y 319 | cpUp1.y = apexControlUp.y 320 | 321 | cpDown2.y = apexControlDown.y 322 | cpDown1.y = apexControlDown.y 323 | 324 | point1 = calculateShapePoint1(input: input, cp: cpUp2, point0: point0) 325 | point2 = calculateShapePoint2(input: input, cp: cpDown2, point3: point3) 326 | } else { 327 | storedBasePointA = point0 328 | storedBasePointB = point3 329 | apexControlUp = cpUp2 330 | apexControlDown = cpDown2 331 | } 332 | 333 | 334 | //Do not need to draw shape under baseLine or after avulsion 335 | if distance <= -input.figureRect.width || distance > input.avulsionDistance { 336 | stopConstriction = false 337 | 338 | point1 = CGPoint(x: point0.x, y: point0.y) 339 | point2 = CGPoint(x: point0.x, y: point0.y + input.figureRect.height) 340 | cpUp1 = point0 341 | cpUp2 = point0 342 | cpDown1 = point3 343 | cpDown2 = point3 344 | } 345 | 346 | let curvePath = UIBezierPath() 347 | curvePath.move(to: point0) 348 | curvePath.addCurve(to: point1, controlPoint1: cpUp1, controlPoint2: cpUp2) 349 | if crossed { 350 | curvePath.addLine(to: point2) 351 | } else { 352 | let center = input.figureRect.center 353 | let dc = CGPoint(x: center.x - 0.5, y: center.y) // to fix small gap between menu and arc 354 | let start = atan((point1.y - center.y) / (point1.x - center.x)) 355 | let end = atan((point2.y - center.y) / (point2.x - center.x)) 356 | curvePath.addArc(withCenter: dc, radius: input.figureCornerRadius, startAngle: start, endAngle: end, clockwise: true) 357 | curvePath.addLine(to: point2) 358 | } 359 | curvePath.addCurve(to: point3, controlPoint1: cpDown2, controlPoint2: cpDown1) 360 | 361 | return curvePath 362 | } 363 | } 364 | 365 | -------------------------------------------------------------------------------- /blob-menu.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'blob-menu' 3 | s.version = '1.0.0' 4 | s.summary = 'SwiftUI menu control.' 5 | s.homepage = 'https://github.com/Ramotion/garland-view' 6 | s.license = 'MIT' 7 | s.authors = { 'Igor Kolpachkov' => 'igor.k@ramotion.agency' } 8 | s.ios.deployment_target = '13.4' 9 | s.source = { :git => 'https://github.com/Ramotion/blob-menu.git', :tag => s.version.to_s } 10 | s.source_files = 'Sources/*.swift' 11 | end 12 | --------------------------------------------------------------------------------