├── .gitignore ├── AnchoredPopupExample ├── AnchoredPopupExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── AnchoredPopupExample.xcscheme └── AnchoredPopupExample │ ├── AnchoredPopupExample.entitlements │ ├── AnchoredPopupExampleApp.swift │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── Colors │ │ ├── Contents.json │ │ ├── popupAzureishWhite.colorset │ │ │ └── Contents.json │ │ ├── popupCornflowerBlue.colorset │ │ │ └── Contents.json │ │ ├── popupDarkViolet.colorset │ │ │ └── Contents.json │ │ ├── popupGray1.colorset │ │ │ └── Contents.json │ │ ├── popupGray2.colorset │ │ │ └── Contents.json │ │ ├── popupGray3.colorset │ │ │ └── Contents.json │ │ ├── popupGray5.colorset │ │ │ └── Contents.json │ │ ├── popupLavender.colorset │ │ │ └── Contents.json │ │ ├── popupLightBlue.colorset │ │ │ └── Contents.json │ │ ├── popupLightGreen.colorset │ │ │ └── Contents.json │ │ ├── popupLightPeriwinkle.colorset │ │ │ └── Contents.json │ │ ├── popupMenthol.colorset │ │ │ └── Contents.json │ │ ├── popupSeparator.colorset │ │ │ └── Contents.json │ │ ├── popupViolet.colorset │ │ │ └── Contents.json │ │ ├── popupYellow.colorset │ │ │ └── Contents.json │ │ └── popupYellow2.colorset │ │ │ └── Contents.json │ ├── Contents.json │ ├── arrowRight.imageset │ │ ├── Contents.json │ │ └── Vector.pdf │ ├── avatar.imageset │ │ ├── Contents.json │ │ └── Rectangle 62.pdf │ ├── congratulations.imageset │ │ ├── Contents.json │ │ └── Winner 1.pdf │ ├── cross.imageset │ │ ├── Contents.json │ │ └── Vector.pdf │ ├── gift.imageset │ │ ├── Contents.json │ │ └── image 5.pdf │ ├── giftBG.imageset │ │ ├── Contents.json │ │ └── Ellipse 26.pdf │ ├── girl1.imageset │ │ ├── Contents.json │ │ └── oksana-taran-xB4ExGcUai0-unsplash-removebg-preview 1.pdf │ ├── girl2.imageset │ │ ├── Contents.json │ │ └── clem-onojeghuo-n6gnCa77Urc-unsplash-removebg-preview 1.pdf │ ├── mainMenuCamera.imageset │ │ ├── Contents.json │ │ └── Videocamera.pdf │ ├── mainMenuDumbbell.imageset │ │ ├── Contents.json │ │ └── Dumbbell.pdf │ ├── mainMenuLibrary.imageset │ │ ├── Contents.json │ │ └── Video Library.pdf │ ├── mainPlay.imageset │ │ ├── Contents.json │ │ └── Polygon 1.pdf │ ├── mainPlaySmall.imageset │ │ ├── Contents.json │ │ └── Play.pdf │ ├── mainTrophy.imageset │ │ ├── Contents.json │ │ └── Vector.pdf │ ├── navBack.imageset │ │ ├── Contents.json │ │ └── Left Accessory Button.pdf │ ├── profile.imageset │ │ ├── Contents.json │ │ └── image.pdf │ ├── profileFavorites.imageset │ │ ├── Contents.json │ │ └── Heart.pdf │ ├── profileTicket.imageset │ │ ├── Contents.json │ │ └── Vector.pdf │ ├── profileTicketBG.imageset │ │ ├── Contents.json │ │ └── Group.pdf │ ├── profileWorkout.imageset │ │ ├── Contents.json │ │ └── Dumbbell.pdf │ ├── question.imageset │ │ ├── Contents.json │ │ └── Question Circle.pdf │ └── xmark.imageset │ │ ├── Contents.json │ │ └── Vector.pdf │ ├── Fonts │ ├── ChakraPetch-Light.ttf │ ├── ChakraPetch-Medium.ttf │ ├── ChakraPetch-Regular.ttf │ ├── IBMPlexMono-Bold.ttf │ ├── IBMPlexMono-Medium.ttf │ └── IBMPlexMono-Regular.ttf │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── Screens │ ├── MainView.swift │ ├── Popups.swift │ └── ProfileView.swift │ └── Utils │ ├── FontUtils.swift │ └── Utils.swift ├── LICENSE ├── Package.swift ├── README.md └── Sources └── AnchoredPopup ├── AnchoredAnimationManager.swift ├── AnchoredPopup.swift ├── BlurBackdropView.swift ├── PublicAPI.swift ├── Utils.swift └── WindowManager.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 63; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5B1858D62D51FD3A00625A0F /* AnchoredPopup in Frameworks */ = {isa = PBXBuildFile; productRef = 5B1858D52D51FD3A00625A0F /* AnchoredPopup */; }; 11 | 5B4929972D673CC9002D045E /* ChakraPetch-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5B4929962D673CC9002D045E /* ChakraPetch-Light.ttf */; }; 12 | 5B4D04F82D92F9A8005262A1 /* AnchoredPopup in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4D04F72D92F9A8005262A1 /* AnchoredPopup */; }; 13 | 5B4DBAD32D92F74A0067A006 /* AnchoredPopup in Frameworks */ = {isa = PBXBuildFile; productRef = 5B4DBAD22D92F74A0067A006 /* AnchoredPopup */; }; 14 | 5B55B9D52D9A6E71004A31C0 /* AnchoredPopup in Frameworks */ = {isa = PBXBuildFile; productRef = 5B55B9D42D9A6E71004A31C0 /* AnchoredPopup */; }; 15 | 5B87F2BB2D3FAE7600A1D1FA /* AnchoredPopup in Frameworks */ = {isa = PBXBuildFile; productRef = 5B87F2BA2D3FAE7600A1D1FA /* AnchoredPopup */; }; 16 | 5B87F2BE2D3FAEE400A1D1FA /* AnchoredPopup in Frameworks */ = {isa = PBXBuildFile; productRef = 5B87F2BD2D3FAEE400A1D1FA /* AnchoredPopup */; }; 17 | 5B87F2C12D3FB04A00A1D1FA /* AnchoredPopup in Frameworks */ = {isa = PBXBuildFile; productRef = 5B87F2C02D3FB04A00A1D1FA /* AnchoredPopup */; }; 18 | 5B87F2C42D3FB0D100A1D1FA /* AnchoredPopup in Frameworks */ = {isa = PBXBuildFile; productRef = 5B87F2C32D3FB0D100A1D1FA /* AnchoredPopup */; }; 19 | 5BACA4352D64806C0093E0CF /* AnchoredPopup in Frameworks */ = {isa = PBXBuildFile; productRef = 5BACA4342D64806C0093E0CF /* AnchoredPopup */; }; 20 | 5BACA4492D64808A0093E0CF /* IBMPlexMono-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5BACA43A2D64808A0093E0CF /* IBMPlexMono-Regular.ttf */; }; 21 | 5BACA44A2D64808A0093E0CF /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BACA43C2D64808A0093E0CF /* Preview Assets.xcassets */; }; 22 | 5BACA44C2D64808A0093E0CF /* IBMPlexMono-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5BACA4382D64808A0093E0CF /* IBMPlexMono-Bold.ttf */; }; 23 | 5BACA44D2D64808A0093E0CF /* ChakraPetch-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5BACA4362D64808A0093E0CF /* ChakraPetch-Medium.ttf */; }; 24 | 5BACA44E2D64808A0093E0CF /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BACA4462D64808A0093E0CF /* Assets.xcassets */; }; 25 | 5BACA44F2D64808A0093E0CF /* ChakraPetch-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5BACA4372D64808A0093E0CF /* ChakraPetch-Regular.ttf */; }; 26 | 5BACA4502D64808A0093E0CF /* IBMPlexMono-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5BACA4392D64808A0093E0CF /* IBMPlexMono-Medium.ttf */; }; 27 | 5BACA4512D64808A0093E0CF /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BACA4432D64808A0093E0CF /* Utils.swift */; }; 28 | 5BACA4522D64808A0093E0CF /* AnchoredPopupExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BACA4452D64808A0093E0CF /* AnchoredPopupExampleApp.swift */; }; 29 | 5BACA4532D64808A0093E0CF /* Popups.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BACA43F2D64808A0093E0CF /* Popups.swift */; }; 30 | 5BACA4542D64808A0093E0CF /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BACA43E2D64808A0093E0CF /* MainView.swift */; }; 31 | 5BACA4552D64808A0093E0CF /* FontUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BACA4422D64808A0093E0CF /* FontUtils.swift */; }; 32 | 5BACA4562D64808A0093E0CF /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BACA4402D64808A0093E0CF /* ProfileView.swift */; }; 33 | 5BB5D7002D647EC900D26B4D /* AnchoredPopup in Frameworks */ = {isa = PBXBuildFile; productRef = 5BB5D6FF2D647EC900D26B4D /* AnchoredPopup */; }; 34 | /* End PBXBuildFile section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 5B4929962D673CC9002D045E /* ChakraPetch-Light.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ChakraPetch-Light.ttf"; sourceTree = ""; }; 38 | 5B87F2A22D3FA2B300A1D1FA /* AnchoredPopupExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AnchoredPopupExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 5BACA4362D64808A0093E0CF /* ChakraPetch-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ChakraPetch-Medium.ttf"; sourceTree = ""; }; 40 | 5BACA4372D64808A0093E0CF /* ChakraPetch-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "ChakraPetch-Regular.ttf"; sourceTree = ""; }; 41 | 5BACA4382D64808A0093E0CF /* IBMPlexMono-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "IBMPlexMono-Bold.ttf"; sourceTree = ""; }; 42 | 5BACA4392D64808A0093E0CF /* IBMPlexMono-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "IBMPlexMono-Medium.ttf"; sourceTree = ""; }; 43 | 5BACA43A2D64808A0093E0CF /* IBMPlexMono-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "IBMPlexMono-Regular.ttf"; sourceTree = ""; }; 44 | 5BACA43C2D64808A0093E0CF /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 45 | 5BACA43E2D64808A0093E0CF /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; 46 | 5BACA43F2D64808A0093E0CF /* Popups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Popups.swift; sourceTree = ""; }; 47 | 5BACA4402D64808A0093E0CF /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = ""; }; 48 | 5BACA4422D64808A0093E0CF /* FontUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontUtils.swift; sourceTree = ""; }; 49 | 5BACA4432D64808A0093E0CF /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = ""; }; 50 | 5BACA4452D64808A0093E0CF /* AnchoredPopupExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnchoredPopupExampleApp.swift; sourceTree = ""; }; 51 | 5BACA4462D64808A0093E0CF /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | 5BACA4472D64808A0093E0CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 5B87F29F2D3FA2B300A1D1FA /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | 5B87F2BB2D3FAE7600A1D1FA /* AnchoredPopup in Frameworks */, 61 | 5B87F2C42D3FB0D100A1D1FA /* AnchoredPopup in Frameworks */, 62 | 5B1858D62D51FD3A00625A0F /* AnchoredPopup in Frameworks */, 63 | 5B87F2BE2D3FAEE400A1D1FA /* AnchoredPopup in Frameworks */, 64 | 5BACA4352D64806C0093E0CF /* AnchoredPopup in Frameworks */, 65 | 5B4DBAD32D92F74A0067A006 /* AnchoredPopup in Frameworks */, 66 | 5B87F2C12D3FB04A00A1D1FA /* AnchoredPopup in Frameworks */, 67 | 5B55B9D52D9A6E71004A31C0 /* AnchoredPopup in Frameworks */, 68 | 5BB5D7002D647EC900D26B4D /* AnchoredPopup in Frameworks */, 69 | 5B4D04F82D92F9A8005262A1 /* AnchoredPopup in Frameworks */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | 5B87F2992D3FA2B300A1D1FA = { 77 | isa = PBXGroup; 78 | children = ( 79 | 5BACA4482D64808A0093E0CF /* AnchoredPopupExample */, 80 | 5B87F2A32D3FA2B300A1D1FA /* Products */, 81 | ); 82 | sourceTree = ""; 83 | }; 84 | 5B87F2A32D3FA2B300A1D1FA /* Products */ = { 85 | isa = PBXGroup; 86 | children = ( 87 | 5B87F2A22D3FA2B300A1D1FA /* AnchoredPopupExample.app */, 88 | ); 89 | name = Products; 90 | sourceTree = ""; 91 | }; 92 | 5BACA43B2D64808A0093E0CF /* Fonts */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 5B4929962D673CC9002D045E /* ChakraPetch-Light.ttf */, 96 | 5BACA4362D64808A0093E0CF /* ChakraPetch-Medium.ttf */, 97 | 5BACA4372D64808A0093E0CF /* ChakraPetch-Regular.ttf */, 98 | 5BACA4382D64808A0093E0CF /* IBMPlexMono-Bold.ttf */, 99 | 5BACA4392D64808A0093E0CF /* IBMPlexMono-Medium.ttf */, 100 | 5BACA43A2D64808A0093E0CF /* IBMPlexMono-Regular.ttf */, 101 | ); 102 | path = Fonts; 103 | sourceTree = ""; 104 | }; 105 | 5BACA43D2D64808A0093E0CF /* Preview Content */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 5BACA43C2D64808A0093E0CF /* Preview Assets.xcassets */, 109 | ); 110 | path = "Preview Content"; 111 | sourceTree = ""; 112 | }; 113 | 5BACA4412D64808A0093E0CF /* Screens */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 5BACA43E2D64808A0093E0CF /* MainView.swift */, 117 | 5BACA43F2D64808A0093E0CF /* Popups.swift */, 118 | 5BACA4402D64808A0093E0CF /* ProfileView.swift */, 119 | ); 120 | path = Screens; 121 | sourceTree = ""; 122 | }; 123 | 5BACA4442D64808A0093E0CF /* Utils */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 5BACA4422D64808A0093E0CF /* FontUtils.swift */, 127 | 5BACA4432D64808A0093E0CF /* Utils.swift */, 128 | ); 129 | path = Utils; 130 | sourceTree = ""; 131 | }; 132 | 5BACA4482D64808A0093E0CF /* AnchoredPopupExample */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | 5BACA43B2D64808A0093E0CF /* Fonts */, 136 | 5BACA43D2D64808A0093E0CF /* Preview Content */, 137 | 5BACA4412D64808A0093E0CF /* Screens */, 138 | 5BACA4442D64808A0093E0CF /* Utils */, 139 | 5BACA4452D64808A0093E0CF /* AnchoredPopupExampleApp.swift */, 140 | 5BACA4462D64808A0093E0CF /* Assets.xcassets */, 141 | 5BACA4472D64808A0093E0CF /* Info.plist */, 142 | ); 143 | path = AnchoredPopupExample; 144 | sourceTree = ""; 145 | }; 146 | /* End PBXGroup section */ 147 | 148 | /* Begin PBXNativeTarget section */ 149 | 5B87F2A12D3FA2B300A1D1FA /* AnchoredPopupExample */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = 5B87F2B02D3FA2B400A1D1FA /* Build configuration list for PBXNativeTarget "AnchoredPopupExample" */; 152 | buildPhases = ( 153 | 5B87F29E2D3FA2B300A1D1FA /* Sources */, 154 | 5B87F29F2D3FA2B300A1D1FA /* Frameworks */, 155 | 5B87F2A02D3FA2B300A1D1FA /* Resources */, 156 | ); 157 | buildRules = ( 158 | ); 159 | dependencies = ( 160 | ); 161 | name = AnchoredPopupExample; 162 | packageProductDependencies = ( 163 | 5B87F2BA2D3FAE7600A1D1FA /* AnchoredPopup */, 164 | 5B87F2BD2D3FAEE400A1D1FA /* AnchoredPopup */, 165 | 5B87F2C02D3FB04A00A1D1FA /* AnchoredPopup */, 166 | 5B87F2C32D3FB0D100A1D1FA /* AnchoredPopup */, 167 | 5B1858D52D51FD3A00625A0F /* AnchoredPopup */, 168 | 5BB5D6FF2D647EC900D26B4D /* AnchoredPopup */, 169 | 5BACA4342D64806C0093E0CF /* AnchoredPopup */, 170 | 5B4DBAD22D92F74A0067A006 /* AnchoredPopup */, 171 | 5B4D04F72D92F9A8005262A1 /* AnchoredPopup */, 172 | 5B55B9D42D9A6E71004A31C0 /* AnchoredPopup */, 173 | ); 174 | productName = AnchoredPopupExample; 175 | productReference = 5B87F2A22D3FA2B300A1D1FA /* AnchoredPopupExample.app */; 176 | productType = "com.apple.product-type.application"; 177 | }; 178 | /* End PBXNativeTarget section */ 179 | 180 | /* Begin PBXProject section */ 181 | 5B87F29A2D3FA2B300A1D1FA /* Project object */ = { 182 | isa = PBXProject; 183 | attributes = { 184 | BuildIndependentTargetsInParallel = 1; 185 | LastSwiftUpdateCheck = 1600; 186 | LastUpgradeCheck = 1600; 187 | TargetAttributes = { 188 | 5B87F2A12D3FA2B300A1D1FA = { 189 | CreatedOnToolsVersion = 16.0; 190 | }; 191 | }; 192 | }; 193 | buildConfigurationList = 5B87F29D2D3FA2B300A1D1FA /* Build configuration list for PBXProject "AnchoredPopupExample" */; 194 | compatibilityVersion = "Xcode 15.0"; 195 | developmentRegion = en; 196 | hasScannedForEncodings = 0; 197 | knownRegions = ( 198 | en, 199 | Base, 200 | ); 201 | mainGroup = 5B87F2992D3FA2B300A1D1FA; 202 | minimizedProjectReferenceProxies = 1; 203 | packageReferences = ( 204 | 5B55B9D32D9A6E71004A31C0 /* XCLocalSwiftPackageReference "../../AnchoredPopup" */, 205 | ); 206 | productRefGroup = 5B87F2A32D3FA2B300A1D1FA /* Products */; 207 | projectDirPath = ""; 208 | projectRoot = ""; 209 | targets = ( 210 | 5B87F2A12D3FA2B300A1D1FA /* AnchoredPopupExample */, 211 | ); 212 | }; 213 | /* End PBXProject section */ 214 | 215 | /* Begin PBXResourcesBuildPhase section */ 216 | 5B87F2A02D3FA2B300A1D1FA /* Resources */ = { 217 | isa = PBXResourcesBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | 5BACA4492D64808A0093E0CF /* IBMPlexMono-Regular.ttf in Resources */, 221 | 5BACA44A2D64808A0093E0CF /* Preview Assets.xcassets in Resources */, 222 | 5BACA44C2D64808A0093E0CF /* IBMPlexMono-Bold.ttf in Resources */, 223 | 5BACA44D2D64808A0093E0CF /* ChakraPetch-Medium.ttf in Resources */, 224 | 5B4929972D673CC9002D045E /* ChakraPetch-Light.ttf in Resources */, 225 | 5BACA44E2D64808A0093E0CF /* Assets.xcassets in Resources */, 226 | 5BACA44F2D64808A0093E0CF /* ChakraPetch-Regular.ttf in Resources */, 227 | 5BACA4502D64808A0093E0CF /* IBMPlexMono-Medium.ttf in Resources */, 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | /* End PBXResourcesBuildPhase section */ 232 | 233 | /* Begin PBXSourcesBuildPhase section */ 234 | 5B87F29E2D3FA2B300A1D1FA /* Sources */ = { 235 | isa = PBXSourcesBuildPhase; 236 | buildActionMask = 2147483647; 237 | files = ( 238 | 5BACA4512D64808A0093E0CF /* Utils.swift in Sources */, 239 | 5BACA4522D64808A0093E0CF /* AnchoredPopupExampleApp.swift in Sources */, 240 | 5BACA4532D64808A0093E0CF /* Popups.swift in Sources */, 241 | 5BACA4542D64808A0093E0CF /* MainView.swift in Sources */, 242 | 5BACA4552D64808A0093E0CF /* FontUtils.swift in Sources */, 243 | 5BACA4562D64808A0093E0CF /* ProfileView.swift in Sources */, 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | }; 247 | /* End PBXSourcesBuildPhase section */ 248 | 249 | /* Begin XCBuildConfiguration section */ 250 | 5B87F2AE2D3FA2B400A1D1FA /* Debug */ = { 251 | isa = XCBuildConfiguration; 252 | buildSettings = { 253 | ALWAYS_SEARCH_USER_PATHS = NO; 254 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 255 | CLANG_ANALYZER_NONNULL = YES; 256 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 257 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 258 | CLANG_ENABLE_MODULES = YES; 259 | CLANG_ENABLE_OBJC_ARC = YES; 260 | CLANG_ENABLE_OBJC_WEAK = YES; 261 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 262 | CLANG_WARN_BOOL_CONVERSION = YES; 263 | CLANG_WARN_COMMA = YES; 264 | CLANG_WARN_CONSTANT_CONVERSION = YES; 265 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 266 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 267 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 268 | CLANG_WARN_EMPTY_BODY = YES; 269 | CLANG_WARN_ENUM_CONVERSION = YES; 270 | CLANG_WARN_INFINITE_RECURSION = YES; 271 | CLANG_WARN_INT_CONVERSION = YES; 272 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 273 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 274 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 275 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 276 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 277 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 278 | CLANG_WARN_STRICT_PROTOTYPES = YES; 279 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 280 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 281 | CLANG_WARN_UNREACHABLE_CODE = YES; 282 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 283 | COPY_PHASE_STRIP = NO; 284 | DEBUG_INFORMATION_FORMAT = dwarf; 285 | ENABLE_STRICT_OBJC_MSGSEND = YES; 286 | ENABLE_TESTABILITY = YES; 287 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 288 | GCC_C_LANGUAGE_STANDARD = gnu17; 289 | GCC_DYNAMIC_NO_PIC = NO; 290 | GCC_NO_COMMON_BLOCKS = YES; 291 | GCC_OPTIMIZATION_LEVEL = 0; 292 | GCC_PREPROCESSOR_DEFINITIONS = ( 293 | "DEBUG=1", 294 | "$(inherited)", 295 | ); 296 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 297 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 298 | GCC_WARN_UNDECLARED_SELECTOR = YES; 299 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 300 | GCC_WARN_UNUSED_FUNCTION = YES; 301 | GCC_WARN_UNUSED_VARIABLE = YES; 302 | IPHONEOS_DEPLOYMENT_TARGET = 18.0; 303 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 304 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 305 | MTL_FAST_MATH = YES; 306 | ONLY_ACTIVE_ARCH = YES; 307 | SDKROOT = iphoneos; 308 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 309 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 310 | SWIFT_VERSION = 6.0; 311 | }; 312 | name = Debug; 313 | }; 314 | 5B87F2AF2D3FA2B400A1D1FA /* Release */ = { 315 | isa = XCBuildConfiguration; 316 | buildSettings = { 317 | ALWAYS_SEARCH_USER_PATHS = NO; 318 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 319 | CLANG_ANALYZER_NONNULL = YES; 320 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 321 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 322 | CLANG_ENABLE_MODULES = YES; 323 | CLANG_ENABLE_OBJC_ARC = YES; 324 | CLANG_ENABLE_OBJC_WEAK = YES; 325 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 326 | CLANG_WARN_BOOL_CONVERSION = YES; 327 | CLANG_WARN_COMMA = YES; 328 | CLANG_WARN_CONSTANT_CONVERSION = YES; 329 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 330 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 331 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 332 | CLANG_WARN_EMPTY_BODY = YES; 333 | CLANG_WARN_ENUM_CONVERSION = YES; 334 | CLANG_WARN_INFINITE_RECURSION = YES; 335 | CLANG_WARN_INT_CONVERSION = YES; 336 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 337 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 338 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 339 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 340 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 341 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 342 | CLANG_WARN_STRICT_PROTOTYPES = YES; 343 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 344 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 345 | CLANG_WARN_UNREACHABLE_CODE = YES; 346 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 347 | COPY_PHASE_STRIP = NO; 348 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 349 | ENABLE_NS_ASSERTIONS = NO; 350 | ENABLE_STRICT_OBJC_MSGSEND = YES; 351 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 352 | GCC_C_LANGUAGE_STANDARD = gnu17; 353 | GCC_NO_COMMON_BLOCKS = YES; 354 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 355 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 356 | GCC_WARN_UNDECLARED_SELECTOR = YES; 357 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 358 | GCC_WARN_UNUSED_FUNCTION = YES; 359 | GCC_WARN_UNUSED_VARIABLE = YES; 360 | IPHONEOS_DEPLOYMENT_TARGET = 18.0; 361 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 362 | MTL_ENABLE_DEBUG_INFO = NO; 363 | MTL_FAST_MATH = YES; 364 | SDKROOT = iphoneos; 365 | SWIFT_COMPILATION_MODE = wholemodule; 366 | SWIFT_VERSION = 6.0; 367 | VALIDATE_PRODUCT = YES; 368 | }; 369 | name = Release; 370 | }; 371 | 5B87F2B12D3FA2B400A1D1FA /* Debug */ = { 372 | isa = XCBuildConfiguration; 373 | buildSettings = { 374 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 375 | CODE_SIGN_STYLE = Automatic; 376 | CURRENT_PROJECT_VERSION = 1; 377 | DEVELOPMENT_ASSET_PATHS = "\"AnchoredPopupExample/Preview Content\""; 378 | DEVELOPMENT_TEAM = FZXCM5CJ7P; 379 | ENABLE_PREVIEWS = YES; 380 | GENERATE_INFOPLIST_FILE = YES; 381 | INFOPLIST_FILE = AnchoredPopupExample/Info.plist; 382 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 383 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 384 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 385 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 386 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 387 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 388 | LD_RUNPATH_SEARCH_PATHS = ( 389 | "$(inherited)", 390 | "@executable_path/Frameworks", 391 | ); 392 | MARKETING_VERSION = 1.0; 393 | PRODUCT_BUNDLE_IDENTIFIER = com.exyte.AnchoredPopupExample; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 396 | SUPPORTS_MACCATALYST = NO; 397 | SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; 398 | SWIFT_EMIT_LOC_STRINGS = YES; 399 | SWIFT_VERSION = 6.0; 400 | TARGETED_DEVICE_FAMILY = "1,2"; 401 | }; 402 | name = Debug; 403 | }; 404 | 5B87F2B22D3FA2B400A1D1FA /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 408 | CODE_SIGN_STYLE = Automatic; 409 | CURRENT_PROJECT_VERSION = 1; 410 | DEVELOPMENT_ASSET_PATHS = "\"AnchoredPopupExample/Preview Content\""; 411 | DEVELOPMENT_TEAM = FZXCM5CJ7P; 412 | ENABLE_PREVIEWS = YES; 413 | GENERATE_INFOPLIST_FILE = YES; 414 | INFOPLIST_FILE = AnchoredPopupExample/Info.plist; 415 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 416 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 417 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 418 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 419 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 420 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 421 | LD_RUNPATH_SEARCH_PATHS = ( 422 | "$(inherited)", 423 | "@executable_path/Frameworks", 424 | ); 425 | MARKETING_VERSION = 1.0; 426 | PRODUCT_BUNDLE_IDENTIFIER = com.exyte.AnchoredPopupExample; 427 | PRODUCT_NAME = "$(TARGET_NAME)"; 428 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; 429 | SUPPORTS_MACCATALYST = NO; 430 | SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; 431 | SWIFT_EMIT_LOC_STRINGS = YES; 432 | SWIFT_VERSION = 6.0; 433 | TARGETED_DEVICE_FAMILY = "1,2"; 434 | }; 435 | name = Release; 436 | }; 437 | /* End XCBuildConfiguration section */ 438 | 439 | /* Begin XCConfigurationList section */ 440 | 5B87F29D2D3FA2B300A1D1FA /* Build configuration list for PBXProject "AnchoredPopupExample" */ = { 441 | isa = XCConfigurationList; 442 | buildConfigurations = ( 443 | 5B87F2AE2D3FA2B400A1D1FA /* Debug */, 444 | 5B87F2AF2D3FA2B400A1D1FA /* Release */, 445 | ); 446 | defaultConfigurationIsVisible = 0; 447 | defaultConfigurationName = Release; 448 | }; 449 | 5B87F2B02D3FA2B400A1D1FA /* Build configuration list for PBXNativeTarget "AnchoredPopupExample" */ = { 450 | isa = XCConfigurationList; 451 | buildConfigurations = ( 452 | 5B87F2B12D3FA2B400A1D1FA /* Debug */, 453 | 5B87F2B22D3FA2B400A1D1FA /* Release */, 454 | ); 455 | defaultConfigurationIsVisible = 0; 456 | defaultConfigurationName = Release; 457 | }; 458 | /* End XCConfigurationList section */ 459 | 460 | /* Begin XCLocalSwiftPackageReference section */ 461 | 5B55B9D32D9A6E71004A31C0 /* XCLocalSwiftPackageReference "../../AnchoredPopup" */ = { 462 | isa = XCLocalSwiftPackageReference; 463 | relativePath = ../../AnchoredPopup; 464 | }; 465 | /* End XCLocalSwiftPackageReference section */ 466 | 467 | /* Begin XCSwiftPackageProductDependency section */ 468 | 5B1858D52D51FD3A00625A0F /* AnchoredPopup */ = { 469 | isa = XCSwiftPackageProductDependency; 470 | productName = AnchoredPopup; 471 | }; 472 | 5B4D04F72D92F9A8005262A1 /* AnchoredPopup */ = { 473 | isa = XCSwiftPackageProductDependency; 474 | productName = AnchoredPopup; 475 | }; 476 | 5B4DBAD22D92F74A0067A006 /* AnchoredPopup */ = { 477 | isa = XCSwiftPackageProductDependency; 478 | productName = AnchoredPopup; 479 | }; 480 | 5B55B9D42D9A6E71004A31C0 /* AnchoredPopup */ = { 481 | isa = XCSwiftPackageProductDependency; 482 | productName = AnchoredPopup; 483 | }; 484 | 5B87F2BA2D3FAE7600A1D1FA /* AnchoredPopup */ = { 485 | isa = XCSwiftPackageProductDependency; 486 | productName = AnchoredPopup; 487 | }; 488 | 5B87F2BD2D3FAEE400A1D1FA /* AnchoredPopup */ = { 489 | isa = XCSwiftPackageProductDependency; 490 | productName = AnchoredPopup; 491 | }; 492 | 5B87F2C02D3FB04A00A1D1FA /* AnchoredPopup */ = { 493 | isa = XCSwiftPackageProductDependency; 494 | productName = AnchoredPopup; 495 | }; 496 | 5B87F2C32D3FB0D100A1D1FA /* AnchoredPopup */ = { 497 | isa = XCSwiftPackageProductDependency; 498 | productName = AnchoredPopup; 499 | }; 500 | 5BACA4342D64806C0093E0CF /* AnchoredPopup */ = { 501 | isa = XCSwiftPackageProductDependency; 502 | productName = AnchoredPopup; 503 | }; 504 | 5BB5D6FF2D647EC900D26B4D /* AnchoredPopup */ = { 505 | isa = XCSwiftPackageProductDependency; 506 | productName = AnchoredPopup; 507 | }; 508 | /* End XCSwiftPackageProductDependency section */ 509 | }; 510 | rootObject = 5B87F29A2D3FA2B300A1D1FA /* Project object */; 511 | } 512 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample.xcodeproj/xcshareddata/xcschemes/AnchoredPopupExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 46 | 47 | 57 | 59 | 65 | 66 | 67 | 68 | 74 | 76 | 82 | 83 | 84 | 85 | 87 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/AnchoredPopupExample.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/AnchoredPopupExampleApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnchoredPopupExampleApp.swift 3 | // AnchoredPopupExample 4 | // 5 | // Created by Alisa Mylnikova on 21.01.2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct AnchoredPopupExampleApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | MainView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupAzureishWhite.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xF2", 9 | "green" : "0xE8", 10 | "red" : "0xE0" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupCornflowerBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xFF", 9 | "green" : "0xD2", 10 | "red" : "0xB1" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupDarkViolet.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xF6", 9 | "green" : "0x50", 10 | "red" : "0x82" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupGray1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xF6", 9 | "green" : "0xF6", 10 | "red" : "0xF6" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupGray2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x88", 9 | "green" : "0x88", 10 | "red" : "0x88" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupGray3.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x52", 9 | "green" : "0x52", 10 | "red" : "0x52" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupGray5.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x38", 9 | "green" : "0x38", 10 | "red" : "0x38" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupLavender.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xF9", 9 | "green" : "0xC7", 10 | "red" : "0xD7" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupLightBlue.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xFF", 9 | "green" : "0xF0", 10 | "red" : "0xE5" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupLightGreen.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xE0", 9 | "green" : "0xF7", 10 | "red" : "0xEE" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupLightPeriwinkle.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xDC", 9 | "green" : "0xCB", 10 | "red" : "0xBC" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupMenthol.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xA2", 9 | "green" : "0xE7", 10 | "red" : "0xCC" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupSeparator.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xB2", 9 | "green" : "0xA8", 10 | "red" : "0x9F" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupViolet.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0xF8", 9 | "green" : "0x65", 10 | "red" : "0x92" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupYellow.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x39", 9 | "green" : "0x95", 10 | "red" : "0xF7" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Colors/popupYellow2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "0x00", 9 | "green" : "0xC2", 10 | "red" : "0xFF" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | }, 15 | { 16 | "appearances" : [ 17 | { 18 | "appearance" : "luminosity", 19 | "value" : "dark" 20 | } 21 | ], 22 | "color" : { 23 | "color-space" : "srgb", 24 | "components" : { 25 | "alpha" : "1.000", 26 | "blue" : "0xFF", 27 | "green" : "0xFF", 28 | "red" : "0xFE" 29 | } 30 | }, 31 | "idiom" : "universal" 32 | } 33 | ], 34 | "info" : { 35 | "author" : "xcode", 36 | "version" : 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/arrowRight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Vector.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/arrowRight.imageset/Vector.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/arrowRight.imageset/Vector.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/avatar.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Rectangle 62.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/avatar.imageset/Rectangle 62.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/avatar.imageset/Rectangle 62.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/congratulations.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Winner 1.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/congratulations.imageset/Winner 1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/congratulations.imageset/Winner 1.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/cross.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Vector.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/cross.imageset/Vector.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/cross.imageset/Vector.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/gift.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "image 5.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/gift.imageset/image 5.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/gift.imageset/image 5.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/giftBG.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Ellipse 26.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/giftBG.imageset/Ellipse 26.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/giftBG.imageset/Ellipse 26.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/girl1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "oksana-taran-xB4ExGcUai0-unsplash-removebg-preview 1.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/girl1.imageset/oksana-taran-xB4ExGcUai0-unsplash-removebg-preview 1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/girl1.imageset/oksana-taran-xB4ExGcUai0-unsplash-removebg-preview 1.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/girl2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "clem-onojeghuo-n6gnCa77Urc-unsplash-removebg-preview 1.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/girl2.imageset/clem-onojeghuo-n6gnCa77Urc-unsplash-removebg-preview 1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/girl2.imageset/clem-onojeghuo-n6gnCa77Urc-unsplash-removebg-preview 1.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainMenuCamera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Videocamera.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainMenuCamera.imageset/Videocamera.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainMenuCamera.imageset/Videocamera.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainMenuDumbbell.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Dumbbell.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainMenuDumbbell.imageset/Dumbbell.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainMenuDumbbell.imageset/Dumbbell.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainMenuLibrary.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Video Library.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainMenuLibrary.imageset/Video Library.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainMenuLibrary.imageset/Video Library.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainPlay.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Polygon 1.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainPlay.imageset/Polygon 1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainPlay.imageset/Polygon 1.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainPlaySmall.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Play.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainPlaySmall.imageset/Play.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainPlaySmall.imageset/Play.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainTrophy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Vector.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainTrophy.imageset/Vector.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/mainTrophy.imageset/Vector.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/navBack.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Left Accessory Button.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/navBack.imageset/Left Accessory Button.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/navBack.imageset/Left Accessory Button.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profile.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "image.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profile.imageset/image.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profile.imageset/image.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profileFavorites.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Heart.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profileFavorites.imageset/Heart.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profileFavorites.imageset/Heart.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profileTicket.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Vector.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profileTicket.imageset/Vector.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profileTicket.imageset/Vector.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profileTicketBG.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Group.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profileTicketBG.imageset/Group.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profileTicketBG.imageset/Group.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profileWorkout.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Dumbbell.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profileWorkout.imageset/Dumbbell.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/profileWorkout.imageset/Dumbbell.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/question.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Question Circle.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/question.imageset/Question Circle.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/question.imageset/Question Circle.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/xmark.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Vector.pdf", 5 | "idiom" : "universal" 6 | } 7 | ], 8 | "info" : { 9 | "author" : "xcode", 10 | "version" : 1 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/xmark.imageset/Vector.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Assets.xcassets/xmark.imageset/Vector.pdf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Fonts/ChakraPetch-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Fonts/ChakraPetch-Light.ttf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Fonts/ChakraPetch-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Fonts/ChakraPetch-Medium.ttf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Fonts/ChakraPetch-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Fonts/ChakraPetch-Regular.ttf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Fonts/IBMPlexMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Fonts/IBMPlexMono-Bold.ttf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Fonts/IBMPlexMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Fonts/IBMPlexMono-Medium.ttf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Fonts/IBMPlexMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/exyte/AnchoredPopup/43fc3deed8c9d3edf72ee9cc84d151541ba709f5/AnchoredPopupExample/AnchoredPopupExample/Fonts/IBMPlexMono-Regular.ttf -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIAppFonts 6 | 7 | IBMPlexMono-Medium.ttf 8 | IBMPlexMono-Bold.ttf 9 | IBMPlexMono-Regular.ttf 10 | ChakraPetch-Medium.ttf 11 | ChakraPetch-Light.ttf 12 | ChakraPetch-Regular.ttf 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Screens/MainView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Untitled.swift 3 | // AnchoredPopupExample 4 | // 5 | // Created by Alisa Mylnikova on 22.01.2025. 6 | // 7 | 8 | import SwiftUI 9 | import AnchoredPopup 10 | 11 | struct MainView: View { 12 | 13 | var body: some View { 14 | ZStack(alignment: .bottom) { 15 | ScrollView { 16 | content 17 | } 18 | 19 | HStack { 20 | Circle().foregroundStyle(.popupViolet) 21 | .overlay { 22 | Image(.mainPlay) 23 | } 24 | .size(60) 25 | .useAsPopupAnchor(id: "main_menu") { 26 | MainMenuView() 27 | } customize: { 28 | $0.position(.anchorRelative(.bottomLeading)) 29 | .background(.none) 30 | .isBackgroundPassthrough(true) 31 | .closeOnTap(false) 32 | } 33 | 34 | Spacer() 35 | 36 | Circle().foregroundStyle(RadialGradient(colors: [.popupYellow, .popupYellow2], center: .center, startRadius: 0, endRadius: 30)) 37 | .overlay { 38 | Image(.mainTrophy) 39 | } 40 | .size(60) 41 | .useAsPopupAnchor(id: "congratulations_view") { 42 | CongratulationsView() 43 | } customize: { 44 | $0.position(.screenRelative()) 45 | .closeOnTap(false) 46 | } 47 | } 48 | .padding(16) 49 | } 50 | } 51 | 52 | var content: some View { 53 | VStack(spacing: 16) { 54 | HStack { 55 | VStack(alignment: .leading) { 56 | Text("Today").plexMedium(16, .popupViolet) 57 | Text("Sat, 23 April 🌟").chakraMedium(24) 58 | } 59 | 60 | Spacer() 61 | 62 | Image(.avatar) 63 | .useAsPopupAnchor(id: "profile_view") { 64 | ProfileView() 65 | } customize: { 66 | $0.closeOnTap(false) 67 | } 68 | .overlay(alignment: .topTrailing) { 69 | Circle().styled(.popupViolet, border: .white, 4) 70 | .size(16) 71 | .padding(.top, -2) 72 | .padding(.trailing, -2) 73 | } 74 | } 75 | 76 | HStack { 77 | statView(emoji: "🔥", value: "14 920", title: "calories") 78 | statView(emoji: "💪", value: "8’03”", title: "average pace") 79 | statView(emoji: "⏱️", value: "1:52:58", title: "time") 80 | } 81 | .padding(.vertical, 16) 82 | .background { 83 | RoundedRectangle(cornerRadius: 16) 84 | .foregroundStyle(.popupLavender) 85 | } 86 | .padding(.bottom, 24) 87 | 88 | HStack { 89 | Text("Select Workout").chakraMedium(24) 90 | Spacer() 91 | Text("SEE ALL").plexBold(15, .popupViolet) 92 | } 93 | 94 | ScrollView(.horizontal) { 95 | HStack { 96 | ForEach(Constants.sportEmoji) { emoji in 97 | Circle().foregroundStyle(.popupGray1) 98 | .size(80) 99 | .overlay { 100 | Text(emoji).font(.system(size: 32)) 101 | } 102 | } 103 | } 104 | } 105 | 106 | girl1View() 107 | girl2View() 108 | } 109 | .padding(20) 110 | } 111 | 112 | func statView(emoji: String, value: String, title: String) -> some View { 113 | VStack { 114 | Circle().foregroundStyle(.white) 115 | .size(44).overlay { 116 | Text(emoji).font(.system(size: 26)) 117 | } 118 | Text(value).chakraMedium(20) 119 | Text(title).plexMedium(12, .popupDarkViolet) 120 | } 121 | .greedyWidth() 122 | } 123 | 124 | func girl1View() -> some View { 125 | HStack { 126 | VStack(alignment: .leading) { 127 | Text("Yoga").chakraMedium(24) 128 | .padding(.bottom, 4) 129 | Text("18 exercises").plex(13, .popupGray2) 130 | .padding(.bottom, 16) 131 | HStack { 132 | Image(.mainPlaySmall) 133 | Text("60 min").plexMedium(12) 134 | } 135 | .padding(16, 6) 136 | .background { 137 | RoundedRectangle(cornerRadius: 6) 138 | .foregroundStyle(.popupCornflowerBlue) 139 | } 140 | } 141 | 142 | Spacer() 143 | Image(.girl1) 144 | } 145 | .padding(.horizontal, 32) 146 | .background { 147 | RoundedRectangle(cornerRadius: 20) 148 | .foregroundStyle(.popupLightBlue) 149 | .greedyWidth() 150 | } 151 | } 152 | 153 | func girl2View() -> some View { 154 | HStack { 155 | VStack(alignment: .leading) { 156 | Text("Fitness").chakraMedium(24) 157 | .padding(.bottom, 4) 158 | Text("24 exercises").plex(13, .popupGray2) 159 | .padding(.bottom, 16) 160 | HStack { 161 | Image(.mainPlaySmall) 162 | Text("82 min").plexMedium(12) 163 | } 164 | .padding(16, 6) 165 | .background { 166 | RoundedRectangle(cornerRadius: 6) 167 | .foregroundStyle(.popupMenthol) 168 | } 169 | } 170 | 171 | Spacer() 172 | Image(.girl2) 173 | } 174 | .padding(.horizontal, 32) 175 | .background { 176 | RoundedRectangle(cornerRadius: 20) 177 | .foregroundStyle(.popupLightGreen) 178 | .greedyWidth() 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Screens/Popups.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Popups.swift 3 | // AnchoredPopupExample 4 | // 5 | // Created by Alisa Mylnikova on 22.01.2025. 6 | // 7 | 8 | import SwiftUI 9 | import AnchoredPopup 10 | 11 | struct MainMenuView: View { 12 | var body: some View { 13 | ZStack { 14 | Color.popupDarkViolet 15 | .cornerRadius(30) 16 | 17 | VStack(alignment: .leading, spacing: 0) { 18 | Label { 19 | Text("Start workout").chakra(15, .white) 20 | } icon: { 21 | Image(.mainMenuDumbbell) 22 | } 23 | .padding(.horizontal, 20) 24 | .padding(.top, 30) 25 | 26 | Color.popupViolet.frame(height: 1) 27 | .padding(16, 12) 28 | 29 | Label { 30 | Text("Record your exercises ").chakra(15, .white) 31 | } icon: { 32 | Image(.mainMenuCamera) 33 | } 34 | .padding(.horizontal, 20) 35 | 36 | Color.popupViolet.frame(height: 1) 37 | .padding(16, 12) 38 | 39 | Label { 40 | Text("Watch lesson").chakra(15, .white) 41 | } icon: { 42 | Image(.mainMenuLibrary) 43 | } 44 | .padding(.horizontal, 20) 45 | .padding(.bottom, 16) 46 | 47 | ZStack { 48 | Circle().foregroundStyle(.popupViolet) 49 | .size(60) 50 | Image(.cross) 51 | } 52 | .onTapGesture { 53 | AnchoredPopup.launchShrinkingAnimation(id: "main_menu") 54 | } 55 | } 56 | } 57 | .fixedSize() 58 | } 59 | } 60 | 61 | struct CongratulationsView: View { 62 | @Environment(\.anchoredPopupDismiss) var dismiss 63 | 64 | var body: some View { 65 | VStack(spacing: 12) { 66 | Image(.congratulations) 67 | Text("Congratulations!").chakraMedium(24) 68 | Text("In two weeks, you did 12 workouts and burned 2671 calories. That's 566 calories more than last month. Continue at the same pace and the result will please you.") 69 | .plex(15, .popupGray3) 70 | .multilineTextAlignment(.center) 71 | 72 | Button { 73 | dismiss?() 74 | } label: { 75 | ZStack { 76 | RoundedRectangle(cornerRadius: 12) 77 | .foregroundStyle(.popupViolet) 78 | .greedyWidth() 79 | Text("THANKS").plexMedium(18, .white) 80 | .padding(18) 81 | } 82 | .fixedSize(horizontal: false, vertical: true) 83 | } 84 | .padding(.horizontal, 24) 85 | .padding(.top, 20) 86 | } 87 | .padding(24, 40) 88 | .background { 89 | Color.white.cornerRadius(20) 90 | } 91 | .overlay(alignment: .topTrailing) { 92 | Button { 93 | dismiss?() 94 | } label: { 95 | ZStack { 96 | RoundedRectangle(cornerRadius: 6) 97 | .foregroundStyle(.popupAzureishWhite) 98 | .size(32) 99 | Image(.xmark) 100 | } 101 | } 102 | .padding(16, 20) 103 | } 104 | .padding(40) 105 | } 106 | } 107 | 108 | struct QuestionsView: View { 109 | 110 | var body: some View { 111 | VStack(spacing: 0) { 112 | VStack(alignment: .leading, spacing: 16) { 113 | Text("Help center") 114 | Text("Support forum") 115 | Text("YouTube videos") 116 | Text("Submit feedback") 117 | Text("Ask the community") 118 | 119 | Color.popupSeparator.frame(height: 0.5) 120 | .padding(.horizontal, -4) 121 | 122 | Text("Change language...") 123 | } 124 | .chakraLight(15, .popupGray5) 125 | .padding(.horizontal, 20) 126 | .padding(.top, 28) 127 | .padding(.bottom, 4) 128 | 129 | HStack { 130 | Spacer() 131 | Circle().styled(.popupLightPeriwinkle) 132 | .overlay { 133 | Image(.cross) 134 | } 135 | .size(56) 136 | } 137 | } 138 | .background { 139 | RoundedRectangle(cornerRadius: 28).styled(.popupAzureishWhite) 140 | } 141 | .frame(width: 230) 142 | } 143 | } 144 | 145 | struct InviteView: View { 146 | @Environment(\.anchoredPopupDismiss) var dismiss 147 | 148 | var body: some View { 149 | VStack(spacing: 12) { 150 | ZStack { 151 | Image(.giftBG) 152 | Image(.gift) 153 | } 154 | Text("Invite friends. Get free Plus").chakraMedium(20) 155 | .multilineTextAlignment(.center) 156 | Text("Get month of free Workout Plus for every friend who joins via your invite link.") 157 | .plex(13, .popupGray3) 158 | .multilineTextAlignment(.center) 159 | 160 | Button { 161 | dismiss?() 162 | } label: { 163 | ZStack { 164 | RoundedRectangle(cornerRadius: 12) 165 | .foregroundStyle(.popupViolet) 166 | .greedyWidth() 167 | Text("SEND INVITE").plexMedium(18, .white) 168 | .padding(18) 169 | } 170 | .fixedSize(horizontal: false, vertical: true) 171 | } 172 | .padding(.top, 20) 173 | } 174 | .frame(width: 230) 175 | .padding(16, 24) 176 | .background { 177 | Color.white.cornerRadius(20) 178 | } 179 | .overlay(alignment: .topTrailing) { 180 | Button { 181 | dismiss?() 182 | } label: { 183 | ZStack { 184 | RoundedRectangle(cornerRadius: 6) 185 | .foregroundStyle(.popupAzureishWhite) 186 | .size(32) 187 | Image(.xmark) 188 | } 189 | } 190 | .padding(12) 191 | } 192 | .padding(16, 38) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Screens/ProfileView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ProfileView.swift 3 | // AnchoredPopupExample 4 | // 5 | // Created by Alisa Mylnikova on 18.02.2025. 6 | // 7 | 8 | import SwiftUI 9 | import AnchoredPopup 10 | 11 | struct ProfileView: View { 12 | @Environment(\.anchoredPopupDismiss) var dismiss 13 | 14 | var body: some View { 15 | content 16 | .ignoresSafeArea() 17 | .background(Color.white) 18 | .overlay(alignment: .topLeading) { 19 | Button { 20 | dismiss?() 21 | } label: { 22 | Image(.navBack) 23 | .padding(8) 24 | } 25 | .padding(.top, 45) 26 | } 27 | .overlay(alignment: .bottom) { 28 | HStack { 29 | Image(.profileTicketBG) 30 | .overlay { 31 | Image(.profileTicket) 32 | } 33 | .size(60) 34 | .useAsPopupAnchor(id: "send_invite") { 35 | InviteView() 36 | } customize: { 37 | $0.position(.screenRelative(.bottomTrailing)) 38 | .closeOnTap(false) 39 | .background(.color(.black.opacity(0.4))) 40 | } 41 | 42 | Spacer() 43 | 44 | Circle().styled(.popupLightPeriwinkle) 45 | .overlay { 46 | Image(.question) 47 | } 48 | .size(56) 49 | .useAsPopupAnchor(id: "questions_view") { 50 | QuestionsView() 51 | } customize: { 52 | $0.position(.anchorRelative(.bottomTrailing)) 53 | .background(.none) 54 | .isBackgroundPassthrough(true) 55 | } 56 | } 57 | .padding(16) 58 | .padding(.bottom, 30) 59 | } 60 | } 61 | 62 | var content: some View { 63 | VStack { 64 | Image(.profile) 65 | .resizable() 66 | .scaledToFill() 67 | .frame(width: UIScreen.main.bounds.width, height: 330) 68 | 69 | Spacer() 70 | 71 | VStack { 72 | Text("Online").plexMedium(16, .popupViolet) 73 | Text("Mary Goodwin").chakraMedium(24) 74 | 75 | Spacer() 76 | 77 | HStack(spacing: 12) { 78 | profileButton(.profileWorkout, "Workout plan") 79 | profileButton(.profileFavorites, "Favourites") 80 | } 81 | 82 | Spacer() 83 | 84 | profileCell("Notifications") 85 | profileCell("Settings") 86 | profileCell("FAQ") 87 | profileCell("Support") 88 | profileCell("Log out") 89 | } 90 | .padding(.horizontal, 20) 91 | .padding(.bottom, 100) 92 | } 93 | } 94 | 95 | func profileButton(_ image: ImageResource, _ title: String) -> some View { 96 | HStack { 97 | Circle().foregroundStyle(.popupLavender) 98 | .size(42) 99 | .overlay { 100 | Image(image) 101 | } 102 | Spacer() 103 | Text(title).chakraMedium(15) 104 | Spacer() 105 | } 106 | .padding(6) 107 | .overlay { 108 | RoundedRectangle(cornerRadius: .infinity) 109 | .styled(.clear, border: .popupAzureishWhite, 1) 110 | } 111 | } 112 | 113 | @ViewBuilder 114 | func profileCell(_ title: String) -> some View { 115 | HStack { 116 | Text(title).chakra(20) 117 | Spacer() 118 | Image(.arrowRight) 119 | } 120 | Spacer() 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Utils/FontUtils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FontUtils.swift 3 | // ExyteApp 4 | // 5 | // Created by Alisa Mylnikova on 26.10.2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension View { 11 | func chakra(_ size: CGFloat, _ color: Color = .black) -> some View { 12 | self.font(.custom("ChakraPetch-Regular", size: size)) 13 | .foregroundStyle(color) 14 | } 15 | 16 | func chakraMedium(_ size: CGFloat, _ color: Color = .black) -> some View { 17 | self.font(.custom("ChakraPetch-Medium", size: size)) 18 | .foregroundStyle(color) 19 | } 20 | func chakraLight(_ size: CGFloat, _ color: Color = .black) -> some View { 21 | self.font(.custom("ChakraPetch-Light", size: size)) 22 | .foregroundStyle(color) 23 | } 24 | 25 | func plex(_ size: CGFloat, _ color: Color = .black) -> some View { 26 | self.font(.custom("IBMPlexMono-Regular", size: size)) 27 | .foregroundStyle(color) 28 | } 29 | 30 | func plexMedium(_ size: CGFloat, _ color: Color = .black) -> some View { 31 | self.font(.custom("IBMPlexMono-Medium", size: size)) 32 | .foregroundStyle(color) 33 | } 34 | 35 | func plexBold(_ size: CGFloat, _ color: Color = .black) -> some View { 36 | self.font(.custom("IBMPlexMono-Bold", size: size)) 37 | .foregroundStyle(color) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AnchoredPopupExample/AnchoredPopupExample/Utils/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // AnchoredPopupExample 4 | // 5 | // Created by Alisa Mylnikova on 22.01.2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | #if compiler(>=6.0) 11 | extension String: @retroactive Identifiable { 12 | public var id: String { self } 13 | } 14 | #else 15 | extension String: Identifiable { 16 | public var id: String { self } 17 | } 18 | #endif 19 | 20 | extension View { 21 | func greedyWidth() -> some View { 22 | self.frame(maxWidth: .infinity) 23 | } 24 | 25 | func padding(_ horizontal: CGFloat, _ vertical: CGFloat) -> some View { 26 | self.padding(.horizontal, horizontal) 27 | .padding(.vertical, vertical) 28 | } 29 | 30 | func size(_ size: CGFloat) -> some View { 31 | self.frame(width: size, height: size) 32 | } 33 | 34 | func fullTap(action: @escaping () -> Void) -> some View { 35 | self.contentShape(Rectangle()) 36 | .onTapGesture { 37 | action() 38 | } 39 | } 40 | 41 | @ViewBuilder 42 | func isHidden(_ hidden: Bool) -> some View { 43 | if hidden { 44 | self.hidden() 45 | } else { 46 | self 47 | } 48 | } 49 | 50 | @ViewBuilder 51 | func applyIf(_ condition: Bool, apply: (Self) -> T) -> some View { 52 | if condition { 53 | apply(self) 54 | } else { 55 | self 56 | } 57 | } 58 | } 59 | 60 | // Extension to easily apply the modifier to any shape 61 | extension Shape { 62 | func styled(_ foregroundColor: Color, border borderColor: Color = .clear, _ borderWidth: CGFloat = 0) -> some View { 63 | self.foregroundStyle(foregroundColor) // Apply foreground color 64 | .overlay( 65 | self 66 | .stroke(borderColor, lineWidth: borderWidth) // Apply border color and width 67 | ) 68 | } 69 | } 70 | 71 | class Constants { 72 | static let sportEmoji = ["🤼‍♂️", "🧘", "🚴", "🏊", "🏄", "🤸", "⛹️", "🏋️", "⚽️"] 73 | } 74 | 75 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Exyte 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.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "AnchoredPopup", 7 | platforms: [ 8 | .iOS(.v17) 9 | ], 10 | products: [ 11 | // Products define the executables and libraries a package produces, making them visible to other packages. 12 | .library( 13 | name: "AnchoredPopup", 14 | targets: ["AnchoredPopup"]), 15 | ], 16 | targets: [ 17 | // Targets are the basic building blocks of a package, defining a module or a test suite. 18 | // Targets can depend on other targets in this package and products from dependencies. 19 | .target( 20 | name: "AnchoredPopup", 21 | swiftSettings: [ 22 | .enableExperimentalFeature("StrictConcurrency") 23 | ] 24 | ) 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |       4 | 5 | 6 |

7 | 8 | 9 | 10 |

11 | 12 |

Anchored Popup

13 | 14 |

Anchored Popup grows "out" of a trigger view, anchoring to a UnitPoint of the trigger, written with SwiftUI

15 | 16 | ![](https://img.shields.io/github/v/tag/exyte/anchoredPopup?label=Version) 17 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fexyte%2FAnchoredPopup%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/exyte/AnchoredPopup) 18 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fexyte%2FAnchoredPopup%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/exyte/AnchoredPopup) 19 | [![SPM](https://img.shields.io/badge/SPM-Compatible-brightgreen.svg)](https://swiftpackageindex.com/exyte/AnchoredPopup) 20 | [![License: MIT](https://img.shields.io/badge/License-MIT-black.svg)](https://opensource.org/licenses/MIT) 21 | 22 | # Usage 23 | 24 | ### Minimal example 25 | 26 | ```swift 27 | import AnchoredPopup 28 | 29 | Circle() 30 | .useAsPopupAnchor(id: "main_menu") { 31 | MainMenuView() 32 | } 33 | ``` 34 | 35 | Customized example: 36 | ```swift 37 | .useAsPopupAnchor(id: "main_menu") { 38 | MainMenuView() 39 | } customize: { 40 | $0.position(.anchorRelative(.bottomLeading)) 41 | .background(.none) 42 | .isBackgroundPassthrough(true) 43 | .closeOnTap(false) 44 | } 45 | ``` 46 | 47 | ### Required parameters - useAsPopupAnchor 48 | - `id` - A unique `String` to store everything related to this animation, you can use it to manually launch animations using this func `AnchoredPopup.launchAnchoredAnimation` 49 | - `contentBuilder` - popup body builder 50 | 51 | ### Optional parameters 52 | - `position` - a `UnitPoint` to align with `UnitPoint`-th part of anchor view. Could be an `anchorRelative` or `screenRelative` 'UnitPoint' 53 | - `animation` - appear/disappear animation 54 | - `closeOnTap` - enable/disable closing on tap on popup 55 | - `closeOnTapOutside` - enable/disable closing on tap on popup's background 56 | - `isBackgroundPassthrough` - enable/disable taps passing through the popup's background 57 | - `background` - Available options are: 58 | * `.none` 59 | * `.color(Color)` 60 | * `.blur(radius: CGFloat)` - blurred fullscreen overlay 61 | * `.view(AnyView)` - custom view builder 62 | 63 | ## State management pitfall 64 | AnchoredPopup uses `UIWindow` to display itself above anything you might have on screen, so remember - to get adequate UI updates, use `ObservableObjects` or `@Bindings` instead of `@State`. This won't work: 65 | ```swift 66 | struct MainView: View { 67 | @State var name = "Mike" 68 | var body: some View { 69 | Text("Show popup") 70 | .useAsPopupAnchor(id: "a") { 71 | ZStack { 72 | Color.red.size(100) 73 | VStack { 74 | Text(name) 75 | Button("Change text") { 76 | name = "John" 77 | } 78 | } 79 | } 80 | } customize: { 81 | $0.position(.anchorRelative(.bottomLeading)) 82 | .closeOnTap(false) 83 | } 84 | } 85 | } 86 | ``` 87 | This will work: 88 | ```swift 89 | struct MainView: View { 90 | @State var name = "Mike" 91 | var body: some View { 92 | Text("Show popup") 93 | .useAsPopupAnchor(id: "a") { 94 | Popup(name: $name) 95 | } customize: { 96 | $0.position(.anchorRelative(.bottomLeading)) 97 | .closeOnTap(false) 98 | } 99 | } 100 | } 101 | 102 | struct Popup: View { 103 | @Binding var name: String 104 | var body: some View { 105 | ZStack { 106 | Color.red.size(100) 107 | VStack { 108 | Text(name) 109 | Button("Change text") { 110 | name = "John" 111 | } 112 | } 113 | } 114 | } 115 | } 116 | ``` 117 | This will work too: 118 | ```swift 119 | struct MainView: View { 120 | var body: some View { 121 | Text("Show popup") 122 | .useAsPopupAnchor(id: "a") { 123 | Popup() 124 | } customize: { 125 | $0.position(.anchorRelative(.bottomLeading)) 126 | .closeOnTap(false) 127 | } 128 | } 129 | } 130 | 131 | struct Popup: View { 132 | @State var name = "Mike" 133 | var body: some View { 134 | ZStack { 135 | Color.red.size(100) 136 | VStack { 137 | Text(name) 138 | Button("Change text") { 139 | name = "John" 140 | } 141 | } 142 | } 143 | } 144 | } 145 | ``` 146 | 147 | ## Examples 148 | 149 | To try AnchoredPopup examples: 150 | - Clone the repo `https://github.com/exyte/AnchoredPopup.git` 151 | - Open `AnchoredPopupExample.xcodeproj` 152 | - Try it! 153 | 154 | ## Installation 155 | 156 | ### [Swift Package Manager](https://swift.org/package-manager/) 157 | 158 | ```swift 159 | dependencies: [ 160 | .package(url: "https://github.com/exyte/AnchoredPopup.git") 161 | ] 162 | ``` 163 | 164 | ## Requirements 165 | 166 | * iOS 17.0+ 167 | 168 | ## Our other open source SwiftUI libraries 169 | [PopupView](https://github.com/exyte/PopupView) - Toasts and popups library 170 | [Grid](https://github.com/exyte/Grid) - The most powerful Grid container 171 | [AnimatedTabBar](https://github.com/exyte/AnimatedTabBar) - A tabbar with a number of preset animations 172 | [ScalingHeaderScrollView](https://github.com/exyte/ScalingHeaderScrollView) - A scroll view with a sticky header which shrinks as you scroll 173 | [MediaPicker](https://github.com/exyte/mediapicker) - Customizable media picker 174 | [Chat](https://github.com/exyte/chat) - Chat UI framework with fully customizable message cells, input view, and a built-in media picker 175 | [OpenAI](https://github.com/exyte/OpenAI) Wrapper lib for [OpenAI REST API](https://platform.openai.com/docs/api-reference/introduction) 176 | [AnimatedGradient](https://github.com/exyte/AnimatedGradient) - Animated linear gradient 177 | [ConcentricOnboarding](https://github.com/exyte/ConcentricOnboarding) - Animated onboarding flow 178 | [FloatingButton](https://github.com/exyte/FloatingButton) - Floating button menu 179 | [ActivityIndicatorView](https://github.com/exyte/ActivityIndicatorView) - A number of animated loading indicators 180 | [ProgressIndicatorView](https://github.com/exyte/ProgressIndicatorView) - A number of animated progress indicators 181 | [FlagAndCountryCode](https://github.com/exyte/FlagAndCountryCode) - Phone codes and flags for every country 182 | [SVGView](https://github.com/exyte/SVGView) - SVG parser 183 | [LiquidSwipe](https://github.com/exyte/LiquidSwipe) - Liquid navigation animation 184 | -------------------------------------------------------------------------------- /Sources/AnchoredPopup/AnchoredAnimationManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnchoredAnimationManager.swift 3 | // 4 | // Created by Alisa Mylnikova on 23.10.2024. 5 | // 6 | 7 | import SwiftUI 8 | import Combine 9 | 10 | /// this manager stores states for all the paired growing/shrinking animations 11 | @MainActor 12 | class AnchoredAnimationManager: ObservableObject { 13 | static let shared = AnchoredAnimationManager() 14 | 15 | enum GrowingViewState { 16 | case hidden, growing, displayed, shrinking 17 | } 18 | 19 | struct AnimationItem: Equatable { 20 | var id: String 21 | var buttonFrame: IntRect 22 | var state: GrowingViewState 23 | 24 | static func == (lhs: AnimationItem, rhs: AnimationItem) -> Bool { 25 | lhs.id == rhs.id 26 | && lhs.buttonFrame == rhs.buttonFrame 27 | && lhs.state == rhs.state 28 | } 29 | } 30 | 31 | @Published var animations: [AnimationItem] = [] 32 | 33 | private var statePublishers: [String: CurrentValueSubject] = [:] 34 | private var framePublishers: [String: CurrentValueSubject] = [:] 35 | private var cancellables = Set() 36 | 37 | static subscript(id: String) -> AnimationItem? { 38 | shared.animations.first { $0.id == id } 39 | } 40 | 41 | func changeStateForAnimation(for id: String, state: GrowingViewState) { 42 | if let index = animations.firstIndex(where: { $0.id == id }) { 43 | animations[index].state = state 44 | } 45 | } 46 | 47 | func updateFrame(for id: String, frame: CGRect) { 48 | if let index = animations.firstIndex(where: { $0.id == id }) { 49 | animations[index].buttonFrame = frame.toIntRect() 50 | } else { 51 | animations.append(AnimationItem(id: id, buttonFrame: frame.toIntRect(), state: .hidden)) 52 | } 53 | } 54 | 55 | func statePublisher(for id: String) -> CurrentValueSubject { 56 | if let publisher = statePublishers[id] { 57 | return publisher 58 | } 59 | 60 | // Track the last emitted value for comparison 61 | var lastValue: AnimationItem? = nil 62 | 63 | // Create a CurrentValueSubject to hold the current value 64 | let subject = CurrentValueSubject(nil) 65 | 66 | // Generate the publisher and handle state changes 67 | $animations 68 | .map { animations in 69 | animations.first { $0.id == id } 70 | } 71 | .compactMap { $0 } 72 | .filter { newItem in 73 | if let last = lastValue { 74 | // Only emit if the item has changed from the last value 75 | if last.state != newItem.state { 76 | lastValue = newItem // Update the last value 77 | return true // Emit if there's a change 78 | } else { 79 | return false // Don't emit if no change 80 | } 81 | } else { 82 | lastValue = newItem // Set initial value 83 | return true // Emit the first time 84 | } 85 | } 86 | .sink { newItem in 87 | // Emit the value to the CurrentValueSubject 88 | subject.send(newItem) 89 | } 90 | .store(in: &cancellables) 91 | 92 | statePublishers[id] = subject 93 | return subject 94 | } 95 | 96 | func framePublisher(for id: String) -> CurrentValueSubject { 97 | if let publisher = framePublishers[id] { 98 | return publisher 99 | } 100 | 101 | // Track the last emitted value for comparison 102 | var lastValue: AnimationItem? = nil 103 | 104 | // Create a CurrentValueSubject to hold the current value 105 | let subject = CurrentValueSubject(nil) 106 | 107 | // Generate the publisher and handle state changes 108 | $animations 109 | .map { animations in 110 | animations.first { $0.id == id } 111 | } 112 | .compactMap { $0 } 113 | .filter { newItem in 114 | if let last = lastValue { 115 | // Only emit if the item has changed from the last value 116 | if last.buttonFrame != newItem.buttonFrame { 117 | lastValue = newItem // Update the last value 118 | return true // Emit if there's a change 119 | } else { 120 | return false // Don't emit if no change 121 | } 122 | } else { 123 | lastValue = newItem // Set initial value 124 | return true // Emit the first time 125 | } 126 | } 127 | .sink { newItem in 128 | // Emit the value to the CurrentValueSubject 129 | subject.send(newItem) 130 | } 131 | .store(in: &cancellables) 132 | 133 | framePublishers[id] = subject 134 | return subject 135 | } 136 | } 137 | 138 | struct TriggerButton: ViewModifier where V: View { 139 | @State var id: String 140 | var params: PopupParameters 141 | @ViewBuilder var contentBuilder: () -> V 142 | 143 | @State private var cancellable: AnyCancellable? 144 | 145 | func body(content: Content) -> some View { 146 | content 147 | .overlay { 148 | GeometryReader { geo in 149 | Color.clear 150 | .preference(key: ButtonFramePreferenceKey.self, value: ButtonFrameInfo(id: id, frame: geo.frame(in: .global))) 151 | } 152 | } 153 | .onPreferenceChange(ButtonFramePreferenceKey.self) { value in 154 | DispatchQueue.main.async { 155 | if id == value.id { 156 | AnchoredAnimationManager.shared.updateFrame(for: value.id, frame: value.frame) 157 | } 158 | } 159 | } 160 | .simultaneousGesture( 161 | TapGesture().onEnded { gesture in 162 | // trigger displaying animation 163 | hideKeyboard() 164 | AnchoredAnimationManager.shared.changeStateForAnimation(for: id, state: .growing) 165 | } 166 | ) 167 | .onReceive(AnchoredAnimationManager.shared.statePublisher(for: id)) { animation in 168 | if animation?.state == .growing { 169 | WindowManager.openNewWindow(id: id, isPassthrough: params.isPassthrough) { 170 | ZStack { 171 | AnimatedBackgroundView(id: $id, background: params.background) 172 | .simultaneousGesture( 173 | TapGesture().onEnded { 174 | if params.closeOnTapOutside { 175 | // trigger hiding animation 176 | AnchoredAnimationManager.shared.changeStateForAnimation(for: id, state: .shrinking) 177 | } 178 | } 179 | ) 180 | AnchoredAnimationView(id: id, params: params, contentBuilder: contentBuilder) 181 | } 182 | } 183 | } else if animation?.state == .hidden { 184 | WindowManager.closeWindow(id: id) 185 | } 186 | } 187 | } 188 | } 189 | 190 | @MainActor 191 | fileprivate struct AnchoredAnimationView: View where V: View { 192 | var id: String 193 | var params: PopupParameters 194 | var contentBuilder: () -> V 195 | 196 | @State private var animatableOpacity: CGFloat = 0 197 | @State private var animatableScale: CGSize = .zero 198 | @State private var animatableOffset: CGSize = .zero 199 | 200 | @State private var triggerButtonFrame: IntRect = .zero 201 | @State private var contentSize: IntSize = .zero 202 | 203 | @State private var semaphore = DispatchSemaphore(value: 1) 204 | 205 | var body: some View { 206 | VStack { 207 | contentBuilder() 208 | .overlay(GeometryReader { geo in 209 | Color.clear.onAppear { 210 | DispatchQueue.main.async { 211 | contentSize = geo.size.toIntSize() 212 | if let animation = AnchoredAnimationManager.shared.animations.first(where: { $0.id == id }) { 213 | setupAndLaunchAnimation(animation) 214 | } 215 | } 216 | } 217 | }) 218 | .scaleEffect(animatableScale) 219 | .offset(animatableOffset) 220 | .position(x: triggerButtonFrame.floatMidX, y: triggerButtonFrame.floatMidY) 221 | .opacity(animatableOpacity) 222 | .ignoresSafeArea() 223 | .simultaneousGesture( 224 | TapGesture().onEnded { gesture in 225 | if params.closeOnTap { 226 | // trigger hiding animation 227 | AnchoredAnimationManager.shared.changeStateForAnimation(for: id, state: .shrinking) 228 | } 229 | } 230 | ) 231 | } 232 | .onReceive(AnchoredAnimationManager.shared.framePublisher(for: id)) { animation in 233 | if let animation, triggerButtonFrame == .zero { 234 | triggerButtonFrame = animation.buttonFrame 235 | } 236 | } 237 | .onReceive(AnchoredAnimationManager.shared.statePublisher(for: id)) { animation in 238 | if let animation { 239 | setupAndLaunchAnimation(animation) 240 | } 241 | } 242 | } 243 | 244 | private func setupAndLaunchAnimation(_ animation: AnchoredAnimationManager.AnimationItem) { 245 | if contentSize == .zero || triggerButtonFrame == .zero { return } 246 | 247 | semaphore.wait() 248 | if let animation = AnchoredAnimationManager[id] { 249 | if animation.state == .growing { 250 | setHiddenState() 251 | 252 | if #available(iOS 17.0, *) { 253 | withAnimation(params.animation) { 254 | setDisplayedState() 255 | } completion: { 256 | AnchoredAnimationManager.shared.changeStateForAnimation(for: id, state: .displayed) 257 | semaphore.signal() 258 | } 259 | } else { 260 | withAnimation(params.animation) { 261 | setDisplayedState() 262 | } 263 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 264 | AnchoredAnimationManager.shared.changeStateForAnimation(for: id, state: .displayed) 265 | semaphore.signal() 266 | } 267 | } 268 | } else if animation.state == .shrinking { 269 | if #available(iOS 17.0, *) { 270 | withAnimation(params.animation) { 271 | setHiddenState() 272 | } completion: { 273 | AnchoredAnimationManager.shared.changeStateForAnimation(for: id, state: .hidden) 274 | semaphore.signal() 275 | } 276 | } else { 277 | withAnimation(params.animation) { 278 | setHiddenState() 279 | } 280 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 281 | AnchoredAnimationManager.shared.changeStateForAnimation(for: id, state: .hidden) 282 | semaphore.signal() 283 | } 284 | } 285 | } else { 286 | semaphore.signal() 287 | } 288 | } 289 | } 290 | 291 | private func setHiddenState() { 292 | animatableOffset = .zero 293 | animatableScale = calculateHiddenScale() 294 | animatableOpacity = 0 295 | } 296 | 297 | private func setDisplayedState() { 298 | animatableOffset = calculateDisplayedOffset() 299 | animatableScale = CGSize(width: 1, height: 1) 300 | animatableOpacity = 1 301 | } 302 | 303 | /// start with popup matching trigger's position and size 304 | private func calculateHiddenScale() -> CGSize { 305 | let tw = triggerButtonFrame.floatWidth 306 | let th = triggerButtonFrame.floatHeight 307 | let pw = contentSize.floatWidth 308 | let ph = contentSize.floatHeight 309 | return CGSize(width: tw/pw, height: th/ph) 310 | } 311 | 312 | /// starting position is center of the trigger 313 | private func calculateDisplayedOffset() -> CGSize { 314 | let cw = contentSize.floatWidth 315 | let ch = contentSize.floatHeight 316 | 317 | switch params.position { 318 | case .anchorRelative(let p): 319 | let tw = triggerButtonFrame.floatWidth 320 | let th = triggerButtonFrame.floatHeight 321 | 322 | // difference between centers 323 | let w = cw/2 - tw/2 324 | let h = ch/2 - th/2 325 | 326 | // normalization: (0, 1) -> (1, -1) 327 | let px = -2 * p.x + 1 328 | let py = -2 * p.y + 1 329 | 330 | // the content view center is currently same as anchor view 331 | // +/- the difference between centers 332 | return CGSize(width: w * px, height: h * py) 333 | 334 | case .screenRelative(let p): 335 | let tx = triggerButtonFrame.floatMidX 336 | let ty = triggerButtonFrame.floatMidY 337 | let sw = UIScreen.main.bounds.width 338 | let sh = UIScreen.main.bounds.height 339 | 340 | // normalization: (0, 1) -> (1, -1) 341 | let px = -2 * p.x + 1 342 | let py = -2 * p.y + 1 343 | 344 | // the content view center is currently same as anchor view 345 | // -tx: put middle of popup into (0,0) 346 | // sw * p.x: put middle of popup into required unit point of screen 347 | // cw/2 * px: align required unit point of popup with the screen 348 | return CGSize(width: -tx + sw * p.x + cw/2 * px, height: -ty + sh * p.y + ch/2 * py) 349 | } 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /Sources/AnchoredPopup/AnchoredPopup.swift: -------------------------------------------------------------------------------- 1 | // The Swift Programming Language 2 | // https://docs.swift.org/swift-book 3 | -------------------------------------------------------------------------------- /Sources/AnchoredPopup/BlurBackdropView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIBackdropView.swift 3 | // Beyond 4 | // 5 | // Created by Alisa Mylnikova on 30.10.2024. 6 | // 7 | 8 | import SwiftUI 9 | 10 | open class UIBackdropView: UIView { 11 | open override class var layerClass: AnyClass { 12 | NSClassFromString("CABackdropLayer") ?? CALayer.self 13 | } 14 | } 15 | 16 | public struct Backdrop: UIViewRepresentable { 17 | public init() {} 18 | 19 | public func makeUIView(context: Context) -> UIBackdropView { 20 | UIBackdropView() 21 | } 22 | 23 | public func updateUIView(_ uiView: UIBackdropView, context: Context) {} 24 | } 25 | 26 | public struct Blur: View { 27 | public var radius: CGFloat 28 | public var opaque: Bool 29 | 30 | public init(radius: CGFloat = 3.0, opaque: Bool = false) { 31 | self.radius = radius 32 | self.opaque = opaque 33 | } 34 | 35 | public var body: some View { 36 | Backdrop() 37 | .blur(radius: radius, opaque: opaque) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/AnchoredPopup/PublicAPI.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PublicAPI.swift 3 | // AnchoredPopup 4 | // 5 | // Created by Alisa Mylnikova on 04.02.2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | // - MARK: Popup creation 11 | 12 | public extension View { 13 | func useAsPopupAnchor(id: String, @ViewBuilder contentBuilder: @escaping () -> V, customize: @escaping (PopupParameters) -> PopupParameters) -> some View { 14 | self.modifier(TriggerButton(id: id, params: customize(PopupParameters()), contentBuilder: contentBuilder)) 15 | } 16 | 17 | func useAsPopupAnchor(id: String, @ViewBuilder contentBuilder: @escaping () -> V) -> some View { 18 | self.modifier(TriggerButton(id: id, params: PopupParameters(), contentBuilder: contentBuilder)) 19 | } 20 | } 21 | 22 | /// convenience methods to open/close the popup manually from code 23 | public class AnchoredPopup { 24 | @MainActor public static func launchGrowingAnimation(id: String) { 25 | AnchoredAnimationManager.shared.changeStateForAnimation(for: id, state: .growing) 26 | } 27 | 28 | @MainActor public static func launchShrinkingAnimation(id: String) { 29 | AnchoredAnimationManager.shared.changeStateForAnimation(for: id, state: .shrinking) 30 | } 31 | } 32 | 33 | // - MARK: Customization parameters 34 | 35 | public enum AnchoredPopupPosition { 36 | case anchorRelative(_ point: UnitPoint) // popup view will be aligned to anchor view at corresponding proportion 37 | case screenRelative(_ point: UnitPoint = .center) // popup view will be aligned to whole screen 38 | } 39 | 40 | public enum AnchoredPopupBackground { 41 | case none 42 | case color(Color) 43 | case blur(radius: CGFloat = 6) 44 | case view(AnyView) 45 | 46 | // Convenience initializer for `view` that automatically wraps the content in `AnyView` 47 | public init(viewBuilder: @escaping () -> Content) { 48 | self = .view(AnyView(viewBuilder())) 49 | } 50 | } 51 | 52 | public struct PopupParameters { 53 | var position: AnchoredPopupPosition = .screenRelative() 54 | var animation: Animation = .easeIn(duration: 0.3) 55 | 56 | /// Should close on tap anywhere inside the popup 57 | var closeOnTap: Bool = true 58 | 59 | /// Should close on tap anywhere outside of the popup 60 | var closeOnTapOutside: Bool = false 61 | 62 | /// Should taps pass through the popup's background 63 | var isPassthrough: Bool = false 64 | 65 | var background: AnchoredPopupBackground = .blur() 66 | 67 | public func position(_ position: AnchoredPopupPosition) -> PopupParameters { 68 | var params = self 69 | params.position = position 70 | return params 71 | } 72 | 73 | /// Appear/disappear animation - default is `easeOut` 74 | public func animation(_ animation: Animation) -> PopupParameters { 75 | var params = self 76 | params.animation = animation 77 | return params 78 | } 79 | 80 | /// Should close on tap - default is `true` 81 | public func closeOnTap(_ closeOnTap: Bool) -> PopupParameters { 82 | var params = self 83 | params.closeOnTap = closeOnTap 84 | return params 85 | } 86 | 87 | /// Should close on tap outside - default is `false` 88 | public func closeOnTapOutside(_ closeOnTapOutside: Bool) -> PopupParameters { 89 | var params = self 90 | params.closeOnTapOutside = closeOnTapOutside 91 | return params 92 | } 93 | 94 | /// Should taps pass through the popup's background - default is `false` 95 | public func isBackgroundPassthrough(_ isPassthrough: Bool) -> PopupParameters { 96 | var params = self 97 | params.isPassthrough = isPassthrough 98 | return params 99 | } 100 | 101 | /// Background for popup - default is `.blur` 102 | public func background(_ background: AnchoredPopupBackground) -> PopupParameters { 103 | var params = self 104 | params.background = background 105 | return params 106 | } 107 | } 108 | 109 | // - MARK: Environmental dismiss 110 | 111 | public typealias SendableClosure = @Sendable @MainActor () -> Void 112 | 113 | struct AnchoredPopupDismissKey: EnvironmentKey { 114 | static let defaultValue: SendableClosure? = nil 115 | } 116 | 117 | public extension EnvironmentValues { 118 | var anchoredPopupDismiss: SendableClosure? { 119 | get { self[AnchoredPopupDismissKey.self] } 120 | set { self[AnchoredPopupDismissKey.self] = newValue } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Sources/AnchoredPopup/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // AnchoredPopup 4 | // 5 | // Created by Alisa Mylnikova on 21.01.2025. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @MainActor func hideKeyboard() { 11 | UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) 12 | } 13 | 14 | // MARK: - Frame getting 15 | 16 | struct ButtonFrameInfo: Equatable { 17 | let id: String 18 | let frame: CGRect 19 | } 20 | 21 | struct ButtonFramePreferenceKey: PreferenceKey { 22 | typealias Value = ButtonFrameInfo 23 | static let defaultValue: ButtonFrameInfo = ButtonFrameInfo(id: "", frame: .zero) 24 | 25 | static func reduce(value: inout ButtonFrameInfo, nextValue: () -> ButtonFrameInfo) { 26 | if value != nextValue() { 27 | value = nextValue() 28 | } 29 | } 30 | } 31 | 32 | // MARK: - AnimatedBackgroundView 33 | 34 | struct AnimatedBackgroundView: View { 35 | @Binding var id: String 36 | var background: AnchoredPopupBackground 37 | 38 | @State private var animatableOpacity: CGFloat = 0 39 | 40 | var body: some View { 41 | Group { 42 | switch background { 43 | case .none: 44 | EmptyView() 45 | case .color(let color): 46 | color 47 | case .blur(let radius): 48 | Blur(radius: radius) 49 | case .view(let anyView): 50 | anyView 51 | } 52 | } 53 | .ignoresSafeArea() 54 | .opacity(animatableOpacity) 55 | .onReceive(AnchoredAnimationManager.shared.statePublisher(for: id)) { animation in 56 | if let animation { 57 | setupAndLaunchAnimation(animation) 58 | } 59 | } 60 | } 61 | 62 | private func setupAndLaunchAnimation(_ animation: AnchoredAnimationManager.AnimationItem) { 63 | DispatchQueue.main.async { 64 | withAnimation(.easeInOut(duration: 0.2)) { 65 | if animation.state == .growing { 66 | setDisplayedState() 67 | } else if animation.state == .shrinking { 68 | setHiddenState() 69 | } 70 | } 71 | } 72 | } 73 | 74 | private func setHiddenState() { 75 | animatableOpacity = 0 76 | } 77 | 78 | private func setDisplayedState() { 79 | animatableOpacity = 1 80 | } 81 | } 82 | 83 | // MARK: - IntRect 84 | 85 | struct IntRect: Equatable { 86 | var midX, midY, width, height: Int 87 | var floatMidX: CGFloat { CGFloat(midX) } 88 | var floatMidY: CGFloat { CGFloat(midY) } 89 | var floatWidth: CGFloat { CGFloat(width) } 90 | var floatHeight: CGFloat { CGFloat(height) } 91 | 92 | static let zero = IntRect(midX: 0, midY: 0, width: 0, height: 0) 93 | 94 | static func == (lhs: Self, rhs: Self) -> Bool { 95 | lhs.midX == rhs.midX 96 | && lhs.midY == rhs.midY 97 | && lhs.width == rhs.width 98 | && lhs.height == rhs.height 99 | } 100 | } 101 | 102 | extension CGRect { 103 | func toIntRect() -> IntRect { 104 | IntRect(midX: Int(midX), midY: Int(midY), width: Int(width), height: Int(height)) 105 | } 106 | } 107 | 108 | struct IntSize: Equatable { 109 | var width, height: Int 110 | var floatWidth: CGFloat { CGFloat(width) } 111 | var floatHeight: CGFloat { CGFloat(height) } 112 | 113 | static let zero = IntSize(width: 0, height: 0) 114 | 115 | static func == (lhs: Self, rhs: Self) -> Bool { 116 | lhs.width == rhs.width 117 | && lhs.height == rhs.height 118 | } 119 | } 120 | 121 | extension CGSize { 122 | func toIntSize() -> IntSize { 123 | IntSize(width: Int(width), height: Int(height)) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Sources/AnchoredPopup/WindowManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WindowManager.swift 3 | // Beyond 4 | // 5 | // Created by Alisa Mylnikova on 19.12.2024. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @MainActor 11 | final class WindowManager { 12 | static let shared = WindowManager() 13 | private var windows: [String: UIWindow] = [:] 14 | 15 | static func openNewWindow(id: String, isPassthrough: Bool, content: ()->Content) { 16 | guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { 17 | print("No valid scene available") 18 | return 19 | } 20 | 21 | let window = isPassthrough ? UIPassthroughWindow(windowScene: scene) : UIWindow(windowScene: scene) 22 | window.backgroundColor = .clear 23 | let root = content() 24 | .environment(\.anchoredPopupDismiss) { 25 | AnchoredAnimationManager.shared.changeStateForAnimation(for: id, state: .shrinking) 26 | } 27 | let controller = isPassthrough ? UIPassthroughVC(rootView: root) : UIHostingController(rootView: root) 28 | controller.view.backgroundColor = .clear 29 | window.rootViewController = controller 30 | window.windowLevel = .alert + 1 31 | window.makeKeyAndVisible() 32 | shared.windows[id] = window 33 | } 34 | 35 | static func closeWindow(id: String) { 36 | shared.windows[id]?.isHidden = true 37 | shared.windows.removeValue(forKey: id) 38 | } 39 | } 40 | 41 | class UIPassthroughWindow: UIWindow { 42 | 43 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 44 | if let vc = self.rootViewController { 45 | vc.view.layoutSubviews() // otherwise the frame is as if the popup is still outside the screen 46 | if let _ = isTouchInsideSubview(point: point, vc: vc.view) { 47 | // pass tap to this UIPassthroughVC 48 | return vc.view 49 | } 50 | } 51 | return nil // pass to next window 52 | } 53 | 54 | private func isTouchInsideSubview(point: CGPoint, vc: UIView) -> UIView? { 55 | for subview in vc.subviews { 56 | if subview.frame.contains(point) { 57 | return subview 58 | } 59 | } 60 | return nil 61 | } 62 | } 63 | 64 | class UIPassthroughVC: UIHostingController { 65 | 66 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 67 | // Check if any touch is inside one of the subviews, if so, ignore it 68 | if !isTouchInsideSubview(touches) { 69 | // If touch is not inside any subview, pass the touch to the next responder 70 | super.touchesBegan(touches, with: event) 71 | } 72 | } 73 | 74 | override func touchesMoved(_ touches: Set, with event: UIEvent?) { 75 | if !isTouchInsideSubview(touches) { 76 | super.touchesMoved(touches, with: event) 77 | } 78 | } 79 | 80 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 81 | if !isTouchInsideSubview(touches) { 82 | super.touchesEnded(touches, with: event) 83 | } 84 | } 85 | 86 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 87 | if !isTouchInsideSubview(touches) { 88 | super.touchesCancelled(touches, with: event) 89 | } 90 | } 91 | 92 | // Helper function to determine if any touch is inside a subview 93 | private func isTouchInsideSubview(_ touches: Set) -> Bool { 94 | guard let touch = touches.first else { 95 | return false 96 | } 97 | 98 | let touchLocation = touch.location(in: self.view) 99 | 100 | // Iterate over all subviews to check if the touch is inside any of them 101 | for subview in self.view.subviews { 102 | if subview.frame.contains(touchLocation) { 103 | return true 104 | } 105 | } 106 | return false 107 | } 108 | } 109 | --------------------------------------------------------------------------------