├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── critical-bug-report.md ├── dependabot.yml └── workflows │ ├── pull_request.yml │ └── push.yml ├── .gitignore ├── .swiftlint.yml ├── Example ├── DropViewExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── DropViewExample │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── ContentView.swift │ ├── DropViewExampleApp.swift │ ├── HashableVerticalAlignment.swift │ └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── Package.swift ├── Resources └── notification.png ├── Sources └── DropView │ ├── Environment Values │ ├── BackgroundColor.swift │ ├── Balancing.swift │ ├── SeparatorColor.swift │ └── Shadow.swift │ ├── View Modifiers │ ├── ItemDropViewPresenter.swift │ └── ToggleDropViewPresenter.swift │ └── Views │ ├── DropView+Init.swift │ └── DropView.swift └── docs ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md └── README.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: sbertix 4 | custom: ['https://www.paypal.me/sbertix'] 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report things behaving unexpectedly requiring a fix. Use the new Discussions 4 | section for requests and support. 5 | title: '' 6 | labels: bug 7 | assignees: sbertix 8 | 9 | --- 10 | 11 | - [ ] I've searched past issues and I couldn't find reference to this. 12 | 13 | **DropView version** 14 | e.g. `0.1.0`, etc. 15 | 16 | **Describe the bug** 17 | A clear and concise description of what the bug is. 18 | 19 | **To reproduce** 20 | Steps to reproduce the behavior: 21 | 1. Go to '...' 22 | 2. Click on '....' 23 | 3. Scroll down to '....' 24 | 4. See error 25 | 26 | **Expected behavior** 27 | A clear and concise description of what you expected to happen. 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | 32 | **Device and system** 33 | e.g. iPhone Simulator 13.5. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/critical-bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Critical bug report 3 | about: Report crashes and other exceptions rendering Swiftagram unusable. 4 | title: '' 5 | labels: critical 6 | assignees: sbertix 7 | 8 | --- 9 | 10 | - [ ] I've searched past issues and I couldn't find reference to this. 11 | 12 | **DropView version** 13 | e.g. `0.1.0`, etc. 14 | 15 | **Describe the bug** 16 | A clear and concise description of what the bug is. 17 | 18 | **To reproduce** 19 | Steps to reproduce the behavior: 20 | 1. Go to '...' 21 | 2. Click on '....' 22 | 3. Scroll down to '....' 23 | 4. See error 24 | 25 | **Expected behavior** 26 | A clear and concise description of what you expected to happen. 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | 31 | **Device and system** 32 | e.g. iPhone Simulator 13.5. 33 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'daily' 7 | -------------------------------------------------------------------------------- /.github/workflows/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request 2 | 3 | on: 4 | pull_request: 5 | branches: [main] 6 | types: [opened, synchronize] 7 | 8 | jobs: 9 | # pull request-sepcific steps. 10 | validate_commits: 11 | name: Conventional Commits 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | # validate commits. 18 | - name: Validate commits 19 | uses: tomtom-international/commisery-action@master 20 | with: 21 | token: ${{ secrets.GITHUB_TOKEN }} 22 | 23 | # lint code. 24 | lint: 25 | name: Lint 26 | runs-on: macos-latest 27 | 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | # only lint on actual code changes. 32 | - uses: dorny/paths-filter@v2 33 | id: changes 34 | with: 35 | base: ${{ github.base_ref }} 36 | filters: | 37 | src: 38 | - '**/*.swift' 39 | - name: Lint 40 | if: steps.changes.outputs.src == 'true' 41 | run: | 42 | set -o pipefail 43 | swiftlint lint --strict --quiet | sed -E 's/^(.*):([0-9]+):([0-9]+): (warning|error|[^:]+): (.*)/::\4 title=Lint error,file=\1,line=\2,col=\3::\5\n\1:\2:\3/' 44 | 45 | # build the library. 46 | build: 47 | name: Build 48 | needs: lint 49 | runs-on: macos-latest 50 | 51 | steps: 52 | - name: Checkout 53 | uses: actions/checkout@v3 54 | # only build on actual code changes. 55 | - uses: dorny/paths-filter@v2 56 | id: changes 57 | with: 58 | base: ${{ github.base_ref }} 59 | filters: | 60 | src: 61 | - '**/*.swift' 62 | - name: Build 63 | if: steps.changes.outputs.src == 'true' 64 | run: swift build 65 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | # lint code. 9 | lint: 10 | name: Lint 11 | runs-on: macos-latest 12 | 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | # only lint on actual code changes. 17 | - uses: dorny/paths-filter@v2 18 | id: changes 19 | with: 20 | base: ${{ github.base_ref }} 21 | filters: | 22 | src: 23 | - '**/*.swift' 24 | - name: Lint 25 | if: steps.changes.outputs.src == 'true' 26 | run: | 27 | set -o pipefail 28 | swiftlint lint --strict --quiet | sed -E 's/^(.*):([0-9]+):([0-9]+): (warning|error|[^:]+): (.*)/::\4 title=Lint error,file=\1,line=\2,col=\3::\5\n\1:\2:\3/' 29 | 30 | # build the library. 31 | build: 32 | name: Build 33 | needs: lint 34 | runs-on: macos-latest 35 | 36 | steps: 37 | - name: Checkout 38 | uses: actions/checkout@v3 39 | # only build on actual code changes. 40 | - uses: dorny/paths-filter@v2 41 | id: changes 42 | with: 43 | base: ${{ github.event.push.before }} 44 | filters: | 45 | src: 46 | - '**/*.swift' 47 | - name: Build 48 | if: steps.changes.outputs.src == 'true' 49 | run: swift build 50 | 51 | # release a new version. 52 | release: 53 | name: Release 54 | needs: build 55 | runs-on: ubuntu-latest 56 | 57 | steps: 58 | # checkout `main`. 59 | - name: Checkout 60 | id: checkout 61 | uses: actions/checkout@v3 62 | - name: Release version 63 | id: release-version 64 | uses: tomtom-international/commisery-action/bump@v1 65 | with: 66 | fetch-depth: 0 67 | token: ${{ secrets.GITHUB_TOKEN }} 68 | ref: main 69 | # create the changelog. 70 | - name: Changelog 71 | id: changelog 72 | uses: TriPSs/conventional-changelog-action@v3 73 | with: 74 | git-message: "chore(release): relase \'v{version}\'" 75 | git-user-name: "github-actions" 76 | git-user-email: "41898282+github-actions[bot]@users.noreply.github.com" 77 | github-token: ${{ secrets.GITHUB_TOKEN }} 78 | tag-prefix: '' 79 | output-file: 'false' 80 | skip-commit: 'true' 81 | skip-version-file: 'true' 82 | # release the new version. 83 | - name: Release 84 | id: release 85 | uses: actions/create-release@v1 86 | if: ${{ steps.changelog.outputs.skipped == 'false' }} 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | with: 90 | tag_name: ${{ steps.changelog.outputs.tag }} 91 | release_name: v${{ steps.changelog.outputs.tag }} 92 | body: ${{ steps.changelog.outputs.clean_changelog }} 93 | 94 | # create docs. 95 | docs: 96 | name: Docs 97 | needs: release 98 | runs-on: ubuntu-latest 99 | 100 | steps: 101 | # checkout the `main` branch. 102 | - name: Checkout 103 | id: checkout 104 | uses: actions/checkout@v3 105 | with: 106 | token: ${{ secrets.GITHUB_TOKEN }} 107 | ref: main 108 | # only push docs on actual code changes. 109 | - uses: dorny/paths-filter@v2 110 | id: changes 111 | with: 112 | base: ${{ github.event.push.before }} 113 | filters: | 114 | src: 115 | - '**/*.swift' 116 | - '**/push.yml' 117 | # create documentation for `DropView`. 118 | - name: Docs (DropView) 119 | if: steps.changes.outputs.src == 'true' 120 | uses: SwiftDocOrg/swift-doc@master 121 | with: 122 | base-url: "https://sbertix.github.io/DropView/DropView/" 123 | format: "html" 124 | inputs: "Sources" 125 | module-name: DropView 126 | output: docs/DropView 127 | # update permissions. 128 | - name: Update Permissions 129 | if: steps.changes.outputs.src == 'true' 130 | run: 'sudo chown --recursive $USER docs' 131 | # publish to GitHub pages. 132 | - name: Publish 133 | if: steps.changes.outputs.src == 'true' 134 | uses: JamesIves/github-pages-deploy-action@releases/v3 135 | with: 136 | ACCESS_TOKEN: ${{ secrets.CHATOPS_PAT }} 137 | BRANCH: gh-pages 138 | FOLDER: docs 139 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/ 8 | /Example/build 9 | Package.resolved 10 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | identifier_name: 2 | excluded: 3 | - x 4 | - y 5 | - id 6 | -------------------------------------------------------------------------------- /Example/DropViewExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | E0240B1D293CD0B300208ECD /* DropView in Frameworks */ = {isa = PBXBuildFile; productRef = E0240B1C293CD0B300208ECD /* DropView */; }; 11 | E05FFDE8293C0C8D001BF844 /* DropViewExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = E05FFDE7293C0C8D001BF844 /* DropViewExampleApp.swift */; }; 12 | E05FFDEA293C0C8D001BF844 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E05FFDE9293C0C8D001BF844 /* ContentView.swift */; }; 13 | E05FFDEF293C0C8E001BF844 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E05FFDEE293C0C8E001BF844 /* Preview Assets.xcassets */; }; 14 | E05FFDF9293C0FB6001BF844 /* HashableVerticalAlignment.swift in Sources */ = {isa = PBXBuildFile; fileRef = E05FFDF8293C0FB6001BF844 /* HashableVerticalAlignment.swift */; }; 15 | E088CB8E293CD15D005B4F74 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E088CB8D293CD15D005B4F74 /* Assets.xcassets */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | E05FFDE4293C0C8D001BF844 /* DropViewExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DropViewExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | E05FFDE7293C0C8D001BF844 /* DropViewExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropViewExampleApp.swift; sourceTree = ""; }; 21 | E05FFDE9293C0C8D001BF844 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 22 | E05FFDEE293C0C8E001BF844 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 23 | E05FFDF8293C0FB6001BF844 /* HashableVerticalAlignment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashableVerticalAlignment.swift; sourceTree = ""; }; 24 | E088CB8D293CD15D005B4F74 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 25 | /* End PBXFileReference section */ 26 | 27 | /* Begin PBXFrameworksBuildPhase section */ 28 | E05FFDE1293C0C8D001BF844 /* Frameworks */ = { 29 | isa = PBXFrameworksBuildPhase; 30 | buildActionMask = 2147483647; 31 | files = ( 32 | E0240B1D293CD0B300208ECD /* DropView in Frameworks */, 33 | ); 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXFrameworksBuildPhase section */ 37 | 38 | /* Begin PBXGroup section */ 39 | E05FFDDB293C0C8D001BF844 = { 40 | isa = PBXGroup; 41 | children = ( 42 | E05FFDE6293C0C8D001BF844 /* DropViewExample */, 43 | E05FFDE5293C0C8D001BF844 /* Products */, 44 | ); 45 | sourceTree = ""; 46 | }; 47 | E05FFDE5293C0C8D001BF844 /* Products */ = { 48 | isa = PBXGroup; 49 | children = ( 50 | E05FFDE4293C0C8D001BF844 /* DropViewExample.app */, 51 | ); 52 | name = Products; 53 | sourceTree = ""; 54 | }; 55 | E05FFDE6293C0C8D001BF844 /* DropViewExample */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | E05FFDE7293C0C8D001BF844 /* DropViewExampleApp.swift */, 59 | E05FFDF8293C0FB6001BF844 /* HashableVerticalAlignment.swift */, 60 | E05FFDE9293C0C8D001BF844 /* ContentView.swift */, 61 | E088CB8D293CD15D005B4F74 /* Assets.xcassets */, 62 | E05FFDED293C0C8E001BF844 /* Preview Content */, 63 | ); 64 | path = DropViewExample; 65 | sourceTree = ""; 66 | }; 67 | E05FFDED293C0C8E001BF844 /* Preview Content */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | E05FFDEE293C0C8E001BF844 /* Preview Assets.xcassets */, 71 | ); 72 | path = "Preview Content"; 73 | sourceTree = ""; 74 | }; 75 | /* End PBXGroup section */ 76 | 77 | /* Begin PBXNativeTarget section */ 78 | E05FFDE3293C0C8D001BF844 /* DropViewExample */ = { 79 | isa = PBXNativeTarget; 80 | buildConfigurationList = E05FFDF2293C0C8E001BF844 /* Build configuration list for PBXNativeTarget "DropViewExample" */; 81 | buildPhases = ( 82 | E05FFDE0293C0C8D001BF844 /* Sources */, 83 | E05FFDE1293C0C8D001BF844 /* Frameworks */, 84 | E05FFDE2293C0C8D001BF844 /* Resources */, 85 | ); 86 | buildRules = ( 87 | ); 88 | dependencies = ( 89 | ); 90 | name = DropViewExample; 91 | packageProductDependencies = ( 92 | E0240B1C293CD0B300208ECD /* DropView */, 93 | ); 94 | productName = Ciaone; 95 | productReference = E05FFDE4293C0C8D001BF844 /* DropViewExample.app */; 96 | productType = "com.apple.product-type.application"; 97 | }; 98 | /* End PBXNativeTarget section */ 99 | 100 | /* Begin PBXProject section */ 101 | E05FFDDC293C0C8D001BF844 /* Project object */ = { 102 | isa = PBXProject; 103 | attributes = { 104 | BuildIndependentTargetsInParallel = 1; 105 | LastSwiftUpdateCheck = 1410; 106 | LastUpgradeCheck = 1410; 107 | TargetAttributes = { 108 | E05FFDE3293C0C8D001BF844 = { 109 | CreatedOnToolsVersion = 14.1; 110 | }; 111 | }; 112 | }; 113 | buildConfigurationList = E05FFDDF293C0C8D001BF844 /* Build configuration list for PBXProject "DropViewExample" */; 114 | compatibilityVersion = "Xcode 14.0"; 115 | developmentRegion = en; 116 | hasScannedForEncodings = 0; 117 | knownRegions = ( 118 | en, 119 | Base, 120 | ); 121 | mainGroup = E05FFDDB293C0C8D001BF844; 122 | packageReferences = ( 123 | E0240B1B293CD0B300208ECD /* XCRemoteSwiftPackageReference "." */, 124 | ); 125 | productRefGroup = E05FFDE5293C0C8D001BF844 /* Products */; 126 | projectDirPath = ""; 127 | projectRoot = ""; 128 | targets = ( 129 | E05FFDE3293C0C8D001BF844 /* DropViewExample */, 130 | ); 131 | }; 132 | /* End PBXProject section */ 133 | 134 | /* Begin PBXResourcesBuildPhase section */ 135 | E05FFDE2293C0C8D001BF844 /* Resources */ = { 136 | isa = PBXResourcesBuildPhase; 137 | buildActionMask = 2147483647; 138 | files = ( 139 | E088CB8E293CD15D005B4F74 /* Assets.xcassets in Resources */, 140 | E05FFDEF293C0C8E001BF844 /* Preview Assets.xcassets in Resources */, 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | /* End PBXResourcesBuildPhase section */ 145 | 146 | /* Begin PBXSourcesBuildPhase section */ 147 | E05FFDE0293C0C8D001BF844 /* Sources */ = { 148 | isa = PBXSourcesBuildPhase; 149 | buildActionMask = 2147483647; 150 | files = ( 151 | E05FFDEA293C0C8D001BF844 /* ContentView.swift in Sources */, 152 | E05FFDE8293C0C8D001BF844 /* DropViewExampleApp.swift in Sources */, 153 | E05FFDF9293C0FB6001BF844 /* HashableVerticalAlignment.swift in Sources */, 154 | ); 155 | runOnlyForDeploymentPostprocessing = 0; 156 | }; 157 | /* End PBXSourcesBuildPhase section */ 158 | 159 | /* Begin XCBuildConfiguration section */ 160 | E05FFDF0293C0C8E001BF844 /* Debug */ = { 161 | isa = XCBuildConfiguration; 162 | buildSettings = { 163 | ALWAYS_SEARCH_USER_PATHS = NO; 164 | CLANG_ANALYZER_NONNULL = YES; 165 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 166 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 167 | CLANG_ENABLE_MODULES = YES; 168 | CLANG_ENABLE_OBJC_ARC = YES; 169 | CLANG_ENABLE_OBJC_WEAK = YES; 170 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 171 | CLANG_WARN_BOOL_CONVERSION = YES; 172 | CLANG_WARN_COMMA = YES; 173 | CLANG_WARN_CONSTANT_CONVERSION = YES; 174 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 175 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 176 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 177 | CLANG_WARN_EMPTY_BODY = YES; 178 | CLANG_WARN_ENUM_CONVERSION = YES; 179 | CLANG_WARN_INFINITE_RECURSION = YES; 180 | CLANG_WARN_INT_CONVERSION = YES; 181 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 182 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 183 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 184 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 185 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 186 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 187 | CLANG_WARN_STRICT_PROTOTYPES = YES; 188 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 189 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 190 | CLANG_WARN_UNREACHABLE_CODE = YES; 191 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 192 | COPY_PHASE_STRIP = NO; 193 | DEBUG_INFORMATION_FORMAT = dwarf; 194 | ENABLE_STRICT_OBJC_MSGSEND = YES; 195 | ENABLE_TESTABILITY = YES; 196 | GCC_C_LANGUAGE_STANDARD = gnu11; 197 | GCC_DYNAMIC_NO_PIC = NO; 198 | GCC_NO_COMMON_BLOCKS = YES; 199 | GCC_OPTIMIZATION_LEVEL = 0; 200 | GCC_PREPROCESSOR_DEFINITIONS = ( 201 | "DEBUG=1", 202 | "$(inherited)", 203 | ); 204 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 205 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 206 | GCC_WARN_UNDECLARED_SELECTOR = YES; 207 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 208 | GCC_WARN_UNUSED_FUNCTION = YES; 209 | GCC_WARN_UNUSED_VARIABLE = YES; 210 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 211 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 212 | MTL_FAST_MATH = YES; 213 | ONLY_ACTIVE_ARCH = YES; 214 | SDKROOT = iphoneos; 215 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 216 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 217 | }; 218 | name = Debug; 219 | }; 220 | E05FFDF1293C0C8E001BF844 /* Release */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | ALWAYS_SEARCH_USER_PATHS = NO; 224 | CLANG_ANALYZER_NONNULL = YES; 225 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 226 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 227 | CLANG_ENABLE_MODULES = YES; 228 | CLANG_ENABLE_OBJC_ARC = YES; 229 | CLANG_ENABLE_OBJC_WEAK = YES; 230 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 231 | CLANG_WARN_BOOL_CONVERSION = YES; 232 | CLANG_WARN_COMMA = YES; 233 | CLANG_WARN_CONSTANT_CONVERSION = YES; 234 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 235 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 236 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 237 | CLANG_WARN_EMPTY_BODY = YES; 238 | CLANG_WARN_ENUM_CONVERSION = YES; 239 | CLANG_WARN_INFINITE_RECURSION = YES; 240 | CLANG_WARN_INT_CONVERSION = YES; 241 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 242 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 243 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 244 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 245 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 246 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 247 | CLANG_WARN_STRICT_PROTOTYPES = YES; 248 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 249 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 250 | CLANG_WARN_UNREACHABLE_CODE = YES; 251 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 252 | COPY_PHASE_STRIP = NO; 253 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 254 | ENABLE_NS_ASSERTIONS = NO; 255 | ENABLE_STRICT_OBJC_MSGSEND = YES; 256 | GCC_C_LANGUAGE_STANDARD = gnu11; 257 | GCC_NO_COMMON_BLOCKS = YES; 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 265 | MTL_ENABLE_DEBUG_INFO = NO; 266 | MTL_FAST_MATH = YES; 267 | SDKROOT = iphoneos; 268 | SWIFT_COMPILATION_MODE = wholemodule; 269 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 270 | VALIDATE_PRODUCT = YES; 271 | }; 272 | name = Release; 273 | }; 274 | E05FFDF3293C0C8E001BF844 /* Debug */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 278 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 279 | CODE_SIGN_IDENTITY = "Apple Development"; 280 | CODE_SIGN_STYLE = Automatic; 281 | CURRENT_PROJECT_VERSION = 1; 282 | DEVELOPMENT_ASSET_PATHS = "\"DropViewExample/Preview Content\""; 283 | DEVELOPMENT_TEAM = ""; 284 | ENABLE_PREVIEWS = YES; 285 | GENERATE_INFOPLIST_FILE = YES; 286 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 287 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 288 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 289 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 290 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 291 | LD_RUNPATH_SEARCH_PATHS = ( 292 | "$(inherited)", 293 | "@executable_path/Frameworks", 294 | ); 295 | MARKETING_VERSION = 1.0; 296 | PRODUCT_BUNDLE_IDENTIFIER = com.bertagno.DropViewExample; 297 | PRODUCT_NAME = "$(TARGET_NAME)"; 298 | PROVISIONING_PROFILE_SPECIFIER = ""; 299 | SWIFT_EMIT_LOC_STRINGS = YES; 300 | SWIFT_VERSION = 5.0; 301 | TARGETED_DEVICE_FAMILY = "1,2"; 302 | }; 303 | name = Debug; 304 | }; 305 | E05FFDF4293C0C8E001BF844 /* Release */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 309 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 310 | CODE_SIGN_IDENTITY = "Apple Development"; 311 | CODE_SIGN_STYLE = Automatic; 312 | CURRENT_PROJECT_VERSION = 1; 313 | DEVELOPMENT_ASSET_PATHS = "\"DropViewExample/Preview Content\""; 314 | DEVELOPMENT_TEAM = ""; 315 | ENABLE_PREVIEWS = YES; 316 | GENERATE_INFOPLIST_FILE = YES; 317 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 318 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 319 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 320 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 321 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 322 | LD_RUNPATH_SEARCH_PATHS = ( 323 | "$(inherited)", 324 | "@executable_path/Frameworks", 325 | ); 326 | MARKETING_VERSION = 1.0; 327 | PRODUCT_BUNDLE_IDENTIFIER = com.bertagno.DropViewExample; 328 | PRODUCT_NAME = "$(TARGET_NAME)"; 329 | PROVISIONING_PROFILE_SPECIFIER = ""; 330 | SWIFT_EMIT_LOC_STRINGS = YES; 331 | SWIFT_VERSION = 5.0; 332 | TARGETED_DEVICE_FAMILY = "1,2"; 333 | }; 334 | name = Release; 335 | }; 336 | /* End XCBuildConfiguration section */ 337 | 338 | /* Begin XCConfigurationList section */ 339 | E05FFDDF293C0C8D001BF844 /* Build configuration list for PBXProject "DropViewExample" */ = { 340 | isa = XCConfigurationList; 341 | buildConfigurations = ( 342 | E05FFDF0293C0C8E001BF844 /* Debug */, 343 | E05FFDF1293C0C8E001BF844 /* Release */, 344 | ); 345 | defaultConfigurationIsVisible = 0; 346 | defaultConfigurationName = Release; 347 | }; 348 | E05FFDF2293C0C8E001BF844 /* Build configuration list for PBXNativeTarget "DropViewExample" */ = { 349 | isa = XCConfigurationList; 350 | buildConfigurations = ( 351 | E05FFDF3293C0C8E001BF844 /* Debug */, 352 | E05FFDF4293C0C8E001BF844 /* Release */, 353 | ); 354 | defaultConfigurationIsVisible = 0; 355 | defaultConfigurationName = Release; 356 | }; 357 | /* End XCConfigurationList section */ 358 | 359 | /* Begin XCRemoteSwiftPackageReference section */ 360 | E0240B1B293CD0B300208ECD /* XCRemoteSwiftPackageReference "." */ = { 361 | isa = XCRemoteSwiftPackageReference; 362 | repositoryURL = ../; 363 | requirement = { 364 | branch = main; 365 | kind = branch; 366 | }; 367 | }; 368 | /* End XCRemoteSwiftPackageReference section */ 369 | 370 | /* Begin XCSwiftPackageProductDependency section */ 371 | E0240B1C293CD0B300208ECD /* DropView */ = { 372 | isa = XCSwiftPackageProductDependency; 373 | package = E0240B1B293CD0B300208ECD /* XCRemoteSwiftPackageReference "." */; 374 | productName = DropView; 375 | }; 376 | /* End XCSwiftPackageProductDependency section */ 377 | }; 378 | rootObject = E05FFDDC293C0C8D001BF844 /* Project object */; 379 | } 380 | -------------------------------------------------------------------------------- /Example/DropViewExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/DropViewExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/DropViewExample/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 | -------------------------------------------------------------------------------- /Example/DropViewExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Example/DropViewExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/DropViewExample/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Ciaone 4 | // 5 | // Created by Stefano Bertagno on 04/12/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | import DropView 11 | 12 | enum Kind: String, Identifiable { 13 | case one 14 | case two 15 | 16 | var id: String { rawValue } 17 | } 18 | 19 | struct ContentView: View { 20 | /// Whether it's presenting the drop view or not. 21 | @State var item: Kind? 22 | /// The vertical alignment. 23 | @State var alignment: HashableVerticalAlignment = .top 24 | /// The time interval before it auto-dismisses. 25 | @State var timer: TimeInterval = 2 26 | /// Whether it should auto-dismiss or not. 27 | @State var shouldAutoDismiss: Bool = true 28 | /// Whether the drop view should be dismissed on drag or not. 29 | @State var shouldDismissOnDrag: Bool = true 30 | 31 | /// The underlying view. 32 | var body: some View { 33 | NavigationStack { 34 | Form { 35 | Section(header: Text("General")) { 36 | Picker("Vertical alignment", selection: $alignment) { 37 | Text("Top").tag(HashableVerticalAlignment.top) 38 | Text("Center").tag(HashableVerticalAlignment.center) 39 | Text("Bottom").tag(HashableVerticalAlignment.bottom) 40 | } 41 | Toggle("Drag to dismiss", isOn: $shouldDismissOnDrag) 42 | } 43 | 44 | Section(header: Text("Auto dismiss")) { 45 | Stepper( 46 | "Timer \(Int(timer))s", 47 | value: $timer, 48 | in: 2 ... 10 49 | ) 50 | .disabled(!shouldAutoDismiss) 51 | Toggle("Auto dimiss", isOn: $shouldAutoDismiss) 52 | } 53 | 54 | ControlGroup { 55 | Button { 56 | item = .one 57 | } label: { 58 | Text("Default DropView") 59 | } 60 | Button { 61 | item = .two 62 | } label: { 63 | Text("Custom View") 64 | } 65 | Button(role: .destructive) { 66 | item = nil 67 | } label: { 68 | Text("Dismiss") 69 | } 70 | } 71 | } 72 | .navigationTitle("DropView") 73 | } 74 | .drop( 75 | item: $item, 76 | alignment: alignment.alignment, 77 | dismissingAfter: shouldAutoDismiss ? timer : .greatestFiniteMagnitude, 78 | dismissingOnDrag: shouldDismissOnDrag 79 | ) { 80 | switch $0 { 81 | case .one: 82 | DropView( 83 | title: "DropView", 84 | subtitle: "github.com/sbertix/DropView" 85 | ) { 86 | Image(systemName: "hand.wave.fill") 87 | .imageScale(.large) 88 | .font(.headline) 89 | .foregroundColor(.secondary) 90 | } trailing: { 91 | Image(systemName: "star.circle.fill") 92 | .resizable() 93 | .frame(width: 40, height: 40) 94 | .foregroundColor(.accentColor) 95 | } 96 | default: 97 | VStack(alignment: .leading, spacing: 2) { 98 | Text("This is not a DropView") 99 | .font(.headline) 100 | .fixedSize(horizontal: false, vertical: true) 101 | Text( 102 | """ 103 | You can now use whatever you want inside of the `drop` view builder,\ 104 | getting position, transitions,\ 105 | animations and drag gesture behavior for free. 106 | """ 107 | ) 108 | .font(.footnote) 109 | .foregroundStyle(.secondary) 110 | .fixedSize(horizontal: false, vertical: true) 111 | } 112 | .multilineTextAlignment(.leading) 113 | .padding() 114 | .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12, style: .continuous)) 115 | .overlay( 116 | RoundedRectangle(cornerRadius: 12, style: .continuous) 117 | .strokeBorder(Color(.separator), lineWidth: 1) 118 | ) 119 | } 120 | } 121 | } 122 | } 123 | 124 | struct ContentView_Previews: PreviewProvider { 125 | static var previews: some View { 126 | ContentView() 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Example/DropViewExample/DropViewExampleApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropViewExampleApp.swift 3 | // Ciaone 4 | // 5 | // Created by Stefano Bertagno on 04/12/22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct DropViewExampleApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/DropViewExample/HashableVerticalAlignment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HashableVerticalAlignment.swift 3 | // Ciaone 4 | // 5 | // Created by Stefano Bertagno on 04/12/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public enum HashableVerticalAlignment: Hashable { 12 | case top 13 | case center 14 | case bottom 15 | 16 | var alignment: VerticalAlignment { 17 | switch self { 18 | case .top: return .top 19 | case .center: return .center 20 | case .bottom: return .bottom 21 | } 22 | } 23 | 24 | var edge: Edge { 25 | switch self { 26 | case .top: return .top 27 | case .center: return .trailing 28 | case .bottom: return .bottom 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Example/DropViewExample/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.7 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "DropView", 7 | platforms: [ 8 | .iOS(.v14), 9 | .macOS(.v11), 10 | .tvOS(.v14), 11 | .watchOS(.v7) 12 | ], 13 | products: [.library(name: "DropView", targets: ["DropView"])], 14 | targets: [.target(name: "DropView", dependencies: [])] 15 | ) 16 | -------------------------------------------------------------------------------- /Resources/notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbertix/DropView/b5bcf8b003fba094916c924c9600c7a4b1d04846/Resources/notification.png -------------------------------------------------------------------------------- /Sources/DropView/Environment Values/BackgroundColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackgroundColor.swift 3 | // DropView 4 | // 5 | // Created by Stefano Bertagno on 03/12/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// A `struct` defining a custom environment 12 | /// key used to handle background colors in 13 | /// drop views. 14 | private struct BackgroundColorEnvironmentKey: EnvironmentKey { 15 | /// The default value. 16 | static let defaultValue: Color = { 17 | #if canImport(UIKit) && !os(watchOS) && !os(tvOS) 18 | return .init(.secondarySystemBackground) 19 | #elseif canImport(UIKit) 20 | return .init(UIColor { 21 | switch $0.userInterfaceStyle { 22 | case .dark: 23 | return .init(red: 28 / 255, green: 28 / 255, blue: 30 / 255, alpha: 1) 24 | case .light, 25 | .unspecified: 26 | return .init(red: 242 / 255, green: 242 / 255, blue: 247 / 255, alpha: 1) 27 | @unknown default: 28 | return .init(red: 242 / 255, green: 242 / 255, blue: 247 / 255, alpha: 1) 29 | } 30 | }) 31 | #elseif canImport(AppKit) 32 | return .init(.controlBackgroundColor) 33 | #else 34 | // This should (theoretically) never be called. 35 | return .init(red: 242 / 255, green: 242 / 255, blue: 247 / 255) 36 | #endif 37 | }() 38 | } 39 | 40 | public extension EnvironmentValues { 41 | /// Enforce a custom drop view background color style. 42 | var dropViewBackgroundColor: Color { 43 | get { self[BackgroundColorEnvironmentKey.self] } 44 | set { self[BackgroundColorEnvironmentKey.self] = newValue } 45 | } 46 | } 47 | 48 | public extension View { 49 | /// Update the drop view background color style. 50 | /// 51 | /// - parameter backgroundColor: A valid `Color`. 52 | /// - returns: Some `View`. 53 | func dropViewBackgroundColor(_ backgroundColor: Color) -> some View { 54 | environment(\.dropViewBackgroundColor, backgroundColor) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/DropView/Environment Values/Balancing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Balancing.swift 3 | // DropView 4 | // 5 | // Created by Stefano Bertagno on 03/12/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// An `enum` listing available content 12 | /// balancing inside of a drop view. 13 | public enum Balancing { 14 | /// `content` is always centered in the 15 | /// overall drop view, by insuring `leading` 16 | /// and `trailing` have the same relative 17 | /// width. 18 | case `default` 19 | 20 | /// `content` is not centered, and `leading` 21 | /// and `trailing` are not guaranteed to 22 | /// have the same relative width. 23 | case compact 24 | } 25 | 26 | public extension EnvironmentValues { 27 | /// Enforce a custom drop view `Balancing` style. 28 | var dropViewBalancing: Balancing { 29 | get { self[BalancingEnvironmentKey.self] } 30 | set { self[BalancingEnvironmentKey.self] = newValue } 31 | } 32 | } 33 | 34 | public extension View { 35 | /// Update the drop view `Balancing` style. 36 | /// 37 | /// - parameter balancing: A valid `Balancing`. 38 | /// - returns: Some `View`. 39 | func dropViewBalancing(_ balancing: Balancing) -> some View { 40 | environment(\.dropViewBalancing, balancing) 41 | } 42 | } 43 | 44 | /// A `struct` defining a custom environment 45 | /// key used to handle balancing in drop views. 46 | private struct BalancingEnvironmentKey: EnvironmentKey { 47 | /// The default value. 48 | static let defaultValue: Balancing = .default 49 | } 50 | -------------------------------------------------------------------------------- /Sources/DropView/Environment Values/SeparatorColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SeparatorColor.swift 3 | // DropView 4 | // 5 | // Created by Stefano Bertagno on 03/12/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// A `struct` defining a custom environment 12 | /// key used to handle separator colors in 13 | /// drop views. 14 | private struct SeparatorColorEnvironmentKey: EnvironmentKey { 15 | /// The default value. 16 | static let defaultValue: Color = { 17 | #if canImport(UIKit) && !os(watchOS) && !os(tvOS) 18 | return .init(.quaternarySystemFill) 19 | #elseif canImport(UIKit) 20 | return .init(UIColor { 21 | switch $0.userInterfaceStyle { 22 | case .dark: 23 | return .init(red: 39 / 255, green: 39 / 255, blue: 41 / 255, alpha: 1) 24 | case .light, 25 | .unspecified: 26 | return .init(red: 234 / 255, green: 234 / 255, blue: 237 / 255, alpha: 1) 27 | @unknown default: 28 | return .init(red: 234 / 255, green: 234 / 255, blue: 237 / 255, alpha: 1) 29 | } 30 | }) 31 | #elseif canImport(AppKit) 32 | return .init(NSColor(name: nil) { 33 | switch $0.bestMatch(from: [.aqua, .darkAqua]) { 34 | case NSAppearance.Name.darkAqua: 35 | return .init(red: 39 / 255, green: 39 / 255, blue: 41 / 255, alpha: 1) 36 | default: 37 | return .init(red: 234 / 255, green: 234 / 255, blue: 237 / 255, alpha: 1) 38 | } 39 | }) 40 | #else 41 | // This should never be called. 42 | return .init(red: 234 / 255, green: 234 / 255, blue: 237 / 255) 43 | #endif 44 | }() 45 | } 46 | 47 | public extension EnvironmentValues { 48 | /// Enforce a custom drop view separator color style. 49 | var dropViewSeparatorColor: Color { 50 | get { self[SeparatorColorEnvironmentKey.self] } 51 | set { self[SeparatorColorEnvironmentKey.self] = newValue } 52 | } 53 | } 54 | 55 | public extension View { 56 | /// Update the drop view separator color style. 57 | /// 58 | /// - parameter separatorColor: A valid `Color`. 59 | /// - returns: Some `View`. 60 | func dropViewSeparatorColor(_ separatorColor: Color) -> some View { 61 | environment(\.dropViewSeparatorColor, separatorColor) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Sources/DropView/Environment Values/Shadow.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Shadow.swift 3 | // DropView 4 | // 5 | // Created by Stefano Bertagno on 03/12/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// A `struct` defining shadow-like 12 | /// properties for drop views. 13 | public struct Shadow { 14 | /// The shadow color. Defaults to `.black`. 15 | let color: Color 16 | /// The shadow radius. 17 | let radius: CGFloat 18 | /// The shadow x offset. Defaults to `0`. 19 | let x: CGFloat 20 | /// The shadow y offset. Defaults to `0`. 21 | let y: CGFloat 22 | 23 | /// Init. 24 | /// 25 | /// - parameters: 26 | /// - color: The shadow color. Defaults to `.black`. 27 | /// - radius: The shadow radius. 28 | /// - x: The shadow x offset. Defaults to `0`. 29 | /// - y: The shadow y offset. Defaults to `0`. 30 | public init( 31 | color: Color = .black, 32 | radius: CGFloat, 33 | x: CGFloat = 0, 34 | y: CGFloat = 0 35 | ) { 36 | self.color = color 37 | self.radius = radius 38 | self.x = x 39 | self.y = y 40 | } 41 | } 42 | 43 | public extension EnvironmentValues { 44 | /// Enforce a custom drop view shadow style. 45 | var dropViewShadow: Shadow { 46 | get { self[ShadowEnvironmentKey.self] } 47 | set { self[ShadowEnvironmentKey.self] = newValue } 48 | } 49 | } 50 | 51 | public extension View { 52 | /// Update the drop view separator shadow style. 53 | /// 54 | /// - parameter shadow: A valid `Shadow`. 55 | /// - returns: Some `View`. 56 | func dropViewShadow(_ shadow: Shadow) -> some View { 57 | environment(\.dropViewShadow, shadow) 58 | } 59 | 60 | /// Update the drop view separator shadow style. 61 | /// 62 | /// - parameters: 63 | /// - color: The shadow color. Defaults to `.black`. 64 | /// - radius: The shadow radius. 65 | /// - x: The shadow x offset. Defaults to `0`. 66 | /// - y: The shadow y offset. Defaults to `0`. 67 | /// - returns: Some `View`. 68 | func dropViewShadow( 69 | color: Color = .black, 70 | radius: CGFloat, 71 | x: CGFloat = 0, 72 | y: CGFloat = 0 73 | ) -> some View { 74 | dropViewShadow(.init(color: color, radius: radius, x: x, y: y)) 75 | } 76 | 77 | /// Apply a given `Shadow`. 78 | /// 79 | /// - parameter shadow: A valid `Shadow`. 80 | /// - returns: Some `View`. 81 | func shadow(_ shadow: Shadow) -> some View { 82 | self.shadow( 83 | color: shadow.color, 84 | radius: shadow.radius, 85 | x: shadow.x, 86 | y: shadow.y 87 | ) 88 | } 89 | } 90 | 91 | /// A `struct` defining a custom environment 92 | /// key used to handle shadows in drop views. 93 | private struct ShadowEnvironmentKey: EnvironmentKey { 94 | /// The default value. 95 | static let defaultValue: Shadow = .init( 96 | color: .black.opacity(0.15), 97 | radius: 18, 98 | x: 0, 99 | y: 0 100 | ) 101 | } 102 | -------------------------------------------------------------------------------- /Sources/DropView/View Modifiers/ItemDropViewPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ItemDropViewPresenter.swift 3 | // DropView 4 | // 5 | // Created by Stefano Bertagno on 04/12/22. 6 | // 7 | 8 | import Combine 9 | import Foundation 10 | import SwiftUI 11 | 12 | /// A `struct` defining a view modifier 13 | /// tasked with overlaying the drop view. 14 | private struct ItemDropViewPresenter: ViewModifier { 15 | /// The drag gesture translation. 16 | @GestureState var translation: CGFloat = 0 17 | 18 | /// A binding to whether it's being presented or not. 19 | @Binding var item: Item? 20 | /// The vertical alignment. 21 | let alignment: VerticalAlignment 22 | /// The auto-dismiss time interval. 23 | let timer: TimeInterval 24 | /// Whether it should dismiss the drop view on drag or not. 25 | let shouldDismissOnDrag: Bool 26 | /// The drop view factory. 27 | let dropView: (Item) -> C 28 | 29 | /// The associated transition edge. 30 | private var edge: Edge { 31 | switch alignment { 32 | case .center: return .leading 33 | case .bottom: return .bottom 34 | default: return .top 35 | } 36 | } 37 | 38 | /// Compose the view. 39 | /// 40 | /// - parameter content: Some `Content`. 41 | /// - returns: Some `View`. 42 | func body(content: Content) -> some View { 43 | content.overlay( 44 | item.flatMap { current in 45 | dropView(current) 46 | // Adjust the position based on 47 | // the user interaction. 48 | .offset( 49 | x: alignment == .center ? translation : 0, 50 | y: alignment == .center ? 0 : translation 51 | ) 52 | .gesture( 53 | // Drag-to-dismiss. 54 | !shouldDismissOnDrag 55 | ? nil 56 | : DragGesture() 57 | .updating($translation) { change, state, _ in 58 | switch alignment { 59 | case .center: state = min(change.translation.width, 0) 60 | case .bottom: state = max(change.translation.height, 0) 61 | default: state = min(change.translation.height, 0) 62 | } 63 | } 64 | .onEnded { _ in 65 | guard item?.id == current.id else { return } 66 | item = nil 67 | } 68 | ) 69 | .onReceive(Just(current.id).delay(for: .seconds(timer), scheduler: RunLoop.main)) { 70 | guard item?.id == $0 else { return } 71 | item = nil 72 | } 73 | .transition(.move(edge: edge).combined(with: .opacity)) 74 | .id(current.id) 75 | .animation(.easeInOut, value: alignment) 76 | }, 77 | alignment: .init(horizontal: .center, vertical: alignment) 78 | ) 79 | .animation(.interactiveSpring().speed(0.5), value: item?.id) 80 | } 81 | } 82 | 83 | public extension View { 84 | /// Overlay a drop view. 85 | /// 86 | /// - parameters: 87 | /// - isPresented: An optional `Identifiable` binding. 88 | /// - alignment: A valid `VerticalAlignment`. Defaults to `.top`. 89 | /// - timer: The time before it gets autodismissed. Defaults to `2`. 90 | /// - shouldDismissOnDrag: Whether dragging the drop view should dismiss it or not. Defaults to `true`. 91 | /// - content: The drop view factory. 92 | /// - returns: Some `View`. 93 | @ViewBuilder func drop( 94 | item: Binding, 95 | alignment: VerticalAlignment = .top, 96 | dismissingAfter timer: TimeInterval = 2, 97 | dismissingOnDrag shouldDismissOnDrag: Bool = true, 98 | @ViewBuilder content: @escaping (I) -> C 99 | ) -> some View { 100 | modifier(ItemDropViewPresenter( 101 | item: item, 102 | alignment: alignment, 103 | timer: timer, 104 | shouldDismissOnDrag: shouldDismissOnDrag, 105 | dropView: content 106 | )) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Sources/DropView/View Modifiers/ToggleDropViewPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ToggleDropViewPresenter.swift 3 | // DropView 4 | // 5 | // Created by Stefano Bertagno on 04/12/22. 6 | // 7 | 8 | import Combine 9 | import Foundation 10 | import SwiftUI 11 | 12 | /// A `struct` defining a view modifier 13 | /// tasked with overlaying the drop view. 14 | private struct ToggleDropViewPresenter: ViewModifier { 15 | /// The drag gesture translation. 16 | @GestureState var translation: CGFloat = 0 17 | 18 | /// A binding to whether it's being presented or not. 19 | @Binding var isPresented: Bool 20 | /// The vertical alignment. 21 | let alignment: VerticalAlignment 22 | /// The auto-dismiss time interval. 23 | let timer: TimeInterval 24 | /// Whether it should dismiss the drop view on drag or not. 25 | let shouldDismissOnDrag: Bool 26 | /// The drop view factory. 27 | let dropView: () -> C 28 | 29 | /// The associated transition edge. 30 | private var edge: Edge { 31 | switch alignment { 32 | case .center: return .leading 33 | case .bottom: return .bottom 34 | default: return .top 35 | } 36 | } 37 | 38 | /// Compose the view. 39 | /// 40 | /// - parameter content: Some `Content`. 41 | /// - returns: Some `View`. 42 | func body(content: Content) -> some View { 43 | content.overlay( 44 | isPresented 45 | ? dropView() 46 | // Adjust the position based on 47 | // the user interaction. 48 | .offset( 49 | x: alignment == .center ? translation : 0, 50 | y: alignment == .center ? 0 : translation 51 | ) 52 | .gesture( 53 | // Drag-to-dismiss. 54 | !shouldDismissOnDrag 55 | ? nil 56 | : DragGesture() 57 | .updating($translation) { change, state, _ in 58 | switch alignment { 59 | case .center: state = min(change.translation.width, 0) 60 | case .bottom: state = max(change.translation.height, 0) 61 | default: state = min(change.translation.height, 0) 62 | } 63 | } 64 | .onEnded { _ in 65 | isPresented = false 66 | } 67 | ) 68 | .onReceive(Just(()).delay(for: .seconds(timer), scheduler: RunLoop.main)) { isPresented = false } 69 | .transition(.move(edge: edge).combined(with: .opacity)) 70 | .animation(.easeInOut, value: alignment) 71 | : nil, 72 | alignment: .init(horizontal: .center, vertical: alignment) 73 | ) 74 | .animation(.interactiveSpring().speed(0.5), value: isPresented) 75 | } 76 | } 77 | 78 | public extension View { 79 | /// Overlay a drop view. 80 | /// 81 | /// - parameters: 82 | /// - isPresented: An optional `Bool` binding. 83 | /// - alignment: A valid `VerticalAlignment`. Defaults to `.top`. 84 | /// - timer: The time before it gets autodismissed. Defaults to `2`. 85 | /// - shouldDismissOnDrag: Whether dragging the drop view should dismiss it or not. Defaults to `true`. 86 | /// - content: The drop view factory. 87 | /// - returns: Some `View`. 88 | @ViewBuilder func drop( 89 | isPresented: Binding, 90 | alignment: VerticalAlignment = .top, 91 | dismissingAfter timer: TimeInterval = 2, 92 | dismissingOnDrag shouldDismissOnDrag: Bool = true, 93 | @ViewBuilder content: @escaping () -> C 94 | ) -> some View { 95 | modifier(ToggleDropViewPresenter( 96 | isPresented: isPresented, 97 | alignment: alignment, 98 | timer: timer, 99 | shouldDismissOnDrag: shouldDismissOnDrag, 100 | dropView: content 101 | )) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /Sources/DropView/Views/DropView+Init.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropView+Init.swift 3 | // DropView 4 | // 5 | // Created by Stefano Bertagno on 03/12/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | public extension DropView { 12 | // MARK: Empty factories 13 | 14 | /// Init. 15 | /// 16 | /// - parameters: 17 | /// - content: A valid `Content` factory. 18 | /// - trailing: A valid `Trailing` factory. 19 | init( 20 | @ViewBuilder content: () -> Content, 21 | @ViewBuilder trailing: () -> Trailing 22 | ) where Leading == EmptyView { 23 | self.init( 24 | content: content, 25 | leading: { EmptyView() }, 26 | trailing: trailing 27 | ) 28 | } 29 | 30 | /// Init. 31 | /// 32 | /// - parameters: 33 | /// - content: A valid `Content` factory. 34 | /// - leading: A valid `Leading` factory. 35 | init( 36 | @ViewBuilder content: () -> Content, 37 | @ViewBuilder leading: () -> Leading 38 | ) where Trailing == EmptyView { 39 | self.init( 40 | content: content, 41 | leading: leading, 42 | trailing: { EmptyView() } 43 | ) 44 | } 45 | 46 | /// Init. 47 | /// 48 | /// - parameter content: A valid `Content` factory. 49 | init(@ViewBuilder content: () -> Content) where Leading == EmptyView, Trailing == EmptyView { 50 | self.init( 51 | content: content, 52 | leading: { EmptyView() }, 53 | trailing: { EmptyView() } 54 | ) 55 | } 56 | 57 | // MARK: Title + subtitle 58 | 59 | /// Init. 60 | /// 61 | /// - parameters: 62 | /// - title: A valid `String`. 63 | /// - subtitle: An optional `String`. Defaults to `nil`. 64 | /// - leading: A valid `Leading` factory. 65 | /// - trailing: A valid `Trailing` factory. 66 | init( 67 | title: String, 68 | subtitle: String? = nil, 69 | @ViewBuilder leading: () -> Leading, 70 | @ViewBuilder trailing: () -> Trailing 71 | ) where Content == TupleView<(Text, Text?)> { 72 | self.init( 73 | content: { 74 | Text(title).font(.headline).foregroundColor(.primary) 75 | if let subtitle { Text(subtitle).font(.footnote).foregroundColor(.secondary) } 76 | }, 77 | leading: leading, 78 | trailing: trailing 79 | ) 80 | } 81 | 82 | /// Init. 83 | /// 84 | /// - parameters: 85 | /// - title: A valid `String`. 86 | /// - subtitle: An optional `String`. Defaults to `nil`. 87 | /// - trailing: A valid `Trailing` factory. 88 | init( 89 | title: String, 90 | subtitle: String? = nil, 91 | @ViewBuilder trailing: () -> Trailing 92 | ) where Content == TupleView<(Text, Text?)>, Leading == EmptyView { 93 | self.init( 94 | title: title, 95 | subtitle: subtitle, 96 | leading: { EmptyView() }, 97 | trailing: trailing 98 | ) 99 | } 100 | 101 | /// Init. 102 | /// 103 | /// - parameters: 104 | /// - title: A valid `String`. 105 | /// - subtitle: An optional `String`. Defaults to `nil`. 106 | /// - leading: A valid `Leading` factory. 107 | init( 108 | title: String, 109 | subtitle: String? = nil, 110 | @ViewBuilder leading: () -> Leading 111 | ) where Content == TupleView<(Text, Text?)>, Trailing == EmptyView { 112 | self.init( 113 | title: title, 114 | subtitle: subtitle, 115 | leading: leading, 116 | trailing: { EmptyView() } 117 | ) 118 | } 119 | 120 | /// Init. 121 | /// 122 | /// - parameters: 123 | /// - title: A valid `String`. 124 | /// - subtitle: An optional `String`. Defaults to `nil`. 125 | init( 126 | title: String, 127 | subtitle: String? = nil 128 | ) where Content == TupleView<(Text, Text?)>, Leading == EmptyView, Trailing == EmptyView { 129 | self.init( 130 | title: title, 131 | subtitle: subtitle, 132 | leading: { EmptyView() }, 133 | trailing: { EmptyView() } 134 | ) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /Sources/DropView/Views/DropView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DropView.swift 3 | // DropView 4 | // 5 | // Created by Stefano Bertagno on 03/12/22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// A `struct` defining the drop 12 | /// representation, together with its 13 | /// accessory views. 14 | public struct DropView: View { 15 | /// The current screen hairline width. 16 | @Environment(\.pixelLength) private var pixelLength 17 | /// The current layout direction. 18 | @Environment(\.layoutDirection) private var layoutDirection 19 | 20 | /// The current drop view background color. 21 | @Environment(\.dropViewBackgroundColor) private var backgroundColor 22 | /// The current drop view balancing. 23 | @Environment(\.dropViewBalancing) private var balancing 24 | /// The current drop view separator color. 25 | @Environment(\.dropViewSeparatorColor) private var separatorColor 26 | /// The current drop view shadow. 27 | @Environment(\.dropViewShadow) private var shadow 28 | 29 | /// The current accessory width. Defaults to `nil`. 30 | @State private var accessoryWidth: CGFloat? 31 | 32 | /// The padding between components. 33 | @ScaledMetric(relativeTo: .headline) private var horizontalSpacing: CGFloat = 12 34 | /// The padding between content components. 35 | @ScaledMetric(relativeTo: .footnote) private var verticalSpacing: CGFloat = 2 36 | /// The padding to the background. 37 | @ScaledMetric private var padding: CGFloat = 12.5 38 | 39 | /// The drop view content. 40 | private let content: Content 41 | /// The leading accessory view. 42 | private let leading: Leading 43 | /// The trailing accesory view. 44 | private let trailing: Trailing 45 | 46 | /// The underlying view. 47 | public var body: some View { 48 | HStack(spacing: horizontalSpacing) { 49 | if case .default = balancing, 50 | leading is EmptyView, 51 | !(trailing is EmptyView) { 52 | // Add balancing when there's no `leading`. 53 | Color.clear.frame(width: accessoryWidth ?? 0) 54 | } else { 55 | leading 56 | .fixedSize() 57 | .aspectRatio(contentMode: .fit) 58 | .frame(width: accessoryWidth, alignment: .leading) 59 | .background(GeometryReader { 60 | Color.clear.preference(key: NewDropViewWidthPreferenceKey.self, value: [$0.size.width]) 61 | }) 62 | } 63 | // `content` should be automatically 64 | // displayed vertically. 65 | VStack(spacing: verticalSpacing) { 66 | content.fixedSize() 67 | }.multilineTextAlignment(.center) 68 | 69 | if case .default = balancing, 70 | !(leading is EmptyView), 71 | trailing is EmptyView { 72 | // Add balancing when there's no `trailing`. 73 | Color.clear.frame(width: accessoryWidth ?? 0) 74 | } else { 75 | trailing 76 | .fixedSize() 77 | .aspectRatio(contentMode: .fit) 78 | .frame(width: accessoryWidth, alignment: .trailing) 79 | .background(GeometryReader { 80 | Color.clear.preference(key: NewDropViewWidthPreferenceKey.self, value: [$0.size.width]) 81 | }) 82 | } 83 | } 84 | .fixedSize(horizontal: false, vertical: true) 85 | // Handle changes to accessory widths. 86 | .onPreferenceChange(NewDropViewWidthPreferenceKey.self) { 87 | guard case .default = balancing else { return } 88 | // `content` should always be 89 | // centered with `.default` 90 | // balancing, so we need to make 91 | // sure `leading` and `trailing` have 92 | // the same zie. 93 | accessoryWidth = $0.max() 94 | } 95 | // Compose the rest of the view. 96 | .padding(padding) 97 | .background(backgroundColor) 98 | .overlay(Capsule().strokeBorder(separatorColor, lineWidth: pixelLength)) 99 | .clipShape(Capsule()) 100 | .shadow(shadow) 101 | } 102 | 103 | /// Init. 104 | /// 105 | /// - parameters: 106 | /// - content: A valid `Content` factory. 107 | /// - leading: A valid `Leading` factory. 108 | /// - trailing: A valid `Trailing` factory. 109 | public init( 110 | @ViewBuilder content: () -> Content, 111 | @ViewBuilder leading: () -> Leading, 112 | @ViewBuilder trailing: () -> Trailing 113 | ) { 114 | self.content = content() 115 | self.leading = leading() 116 | self.trailing = trailing() 117 | } 118 | } 119 | 120 | /// A `struct` defining the width of 121 | /// a drop view accessories. 122 | private struct NewDropViewWidthPreferenceKey: PreferenceKey { 123 | /// The default value. Defaults to empty. 124 | static var defaultValue: Set = [] 125 | /// Reduce subsequent values. 126 | static func reduce(value: inout Set, nextValue: () -> Set) { 127 | value.formUnion(nextValue()) 128 | } 129 | } 130 | 131 | #if DEBUG 132 | /// A `struct` defining the drop view preview context. 133 | struct Previews_DropView_Previews: PreviewProvider { // swiftlint:disable:this type_name 134 | static var previews: some View { 135 | DropView { 136 | Text("DropView") 137 | .font(.headline) 138 | Text("github.com/sbertix/DropView") 139 | .font(.footnote) 140 | .foregroundColor(.secondary) 141 | } leading: { 142 | Image(systemName: "hand.wave.fill") 143 | .imageScale(.large) 144 | .font(.headline) 145 | .foregroundColor(.secondary) 146 | } 147 | .padding(40) 148 | .previewLayout(.sizeThatFits) 149 | .previewDisplayName("Light") 150 | 151 | DropView { 152 | Text("DropView") 153 | .font(.headline) 154 | Text("github.com/sbertix/DropView") 155 | .font(.footnote) 156 | .foregroundColor(.secondary) 157 | } leading: { 158 | Image(systemName: "hand.wave.fill") 159 | .imageScale(.large) 160 | .font(.headline) 161 | .foregroundColor(.secondary) 162 | } trailing: { 163 | Image(systemName: "star.circle.fill") 164 | .resizable() 165 | .frame(width: 40, height: 40) 166 | .foregroundColor(.accentColor) 167 | } 168 | .dropViewBalancing(.compact) 169 | .preferredColorScheme(.dark) 170 | .padding(40) 171 | .previewLayout(.sizeThatFits) 172 | .previewDisplayName("Dark Compact") 173 | 174 | DropView( 175 | title: "DropView", 176 | subtitle: "github.com/sbertix/DropView" 177 | ) { 178 | Image(systemName: "hand.wave.fill") 179 | .imageScale(.large) 180 | .font(.headline) 181 | .foregroundColor(.secondary) 182 | } trailing: { 183 | Image(systemName: "star.circle.fill") 184 | .resizable() 185 | .frame(width: 40, height: 40) 186 | .foregroundColor(.accentColor) 187 | } 188 | .dropViewBackgroundColor(.yellow) 189 | .dropViewSeparatorColor(.blue) 190 | .dropViewShadow(color: .green, radius: 4, x: 2, y: 2) 191 | .environment(\.layoutDirection, .rightToLeft) 192 | .padding(40) 193 | .previewLayout(.sizeThatFits) 194 | .previewDisplayName("Right-to-Left Custom Colors/Shadow") 195 | } 196 | } 197 | #endif 198 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | swiftagram@bertagno.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | All contributes are always welcome. 4 | That said, we require some guidelines to be followed, in order for PRs to be merged. 5 | 6 | > The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL 7 | > NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and 8 | > "OPTIONAL" in this document are to be interpreted as described in 9 | > RFC 2119. 10 | 11 | ## Before Contributing 12 | 13 | - For **bugfixes** only, you MUST open a new [**issue**](https://github.com/sbertix/DropView/issues), if one on the topic does not exist already, before submitting your pull request. 14 | - You SHOULD rely on provided issue templates. 15 | - For **enhancements** only, you SHOULD open a new [**discussion**](https://github.com/sbertix/DropView/discussions), if one on the topic does not exist already, before submitting your pull request. 16 | - Discussions for small additive implementations are OPTIONAL. 17 | - Discussions for breaking changes are REQUIRED. 18 | - Wait for feedback before embarking on time-consuming projects. 19 | - Understand that consistency _always_ comes first. 20 | 21 | ## When Writing your Code 22 | 23 | - You MUST write your code so that it runs on `Swift 5.3`. 24 | - You MUST lint your code using [`swiftlint`](https://github.com/realm/SwiftLint). 25 | 26 | ## When Contributing 27 | 28 | - Your commits SHOULD be [signed](https://docs.github.com/en/github/authenticating-to-github/managing-commit-signature-verification/signing-commits). 29 | - **Commits pushing new features and CI implementations MUST be signed**. 30 | - Commits pushing bugfixes SHOULD be signed. 31 | - Other commits MAY be signed. 32 | - **Your commits MUST follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.2/) guidelines**. 33 | - The message MUST start with one of the following types: `chore`, `ci`, `feat`, `fix`, `perf` or `test`. 34 | - The message type MAY be immediately followed by a scope, in brackets, e.g. `(endpoints)`, `(docs)`, etc. 35 | - A non-empty message description MUST follow type and (optional) scope, begin with a lowercase character and always start with subjunctive verbs, e.g. _update_, _fix_, _add_, with no period at the end. 36 | - Commits MAY contain a single paragraph body, using regular word capitalization, but no period at the end. 37 | - Commits with breaking changes MUST contain a footer, separated from the body by a newline, if it exists, and describe the changes using the message format, preceded by `BREAKING CHANGE: `, e.g. `BREAKING CHANGE: remove upload endpoints` 38 | - Commits fixing a bug MUST contain a footer, separated from the body by a newline, if it exists, referencing the number of the issue their closing, preceded by `Closes `, e.g. `Closes #123`. 39 | - You SHOULD open `draft` pull requests as soon as you start working on them, in order to let the community know of your plans and avoid duplication. 40 | - **You MUST leave the pull request body empty**, as it will be populated automatically. 41 | - You MAY add an additional comment for context. 42 | - **Pull requests SHOULD only solve one problem: stick to the minimal set of changes**. 43 | - New code SHOULD come with new tests. 44 | - **Pull requests MUST always target `main` latest commit**. 45 | - Pull requests SHOULD have a linear commit history. 46 | - You SHOULD ask for a review as soon as you are done with your changes. 47 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Notification 2 | 3 |
4 | 5 | **DropView** is a **SwiftUI**-based library to display alerts inspired by the Apple Pencil and pasteboard stock ones. 6 | 7 |
8 | 9 | > What are some features I can expect from this library? 10 | 11 | - [x] Dark mode 12 | - [x] Interactive dismissal 13 | - [x] Dynamic font sizing 14 | - [x] Accessibility support 15 | - [x] Custom positioning (`.top`, `.bottom` and `.center`) 16 | 17 |

18 | 19 | ## Status 20 | [![Swift](https://img.shields.io/badge/Swift-5.7-%239872AB?style=flat&logo=swift)](https://swift.org) 21 | ![iOS](https://img.shields.io/badge/iOS-14.0-9872AB) 22 | ![macOS](https://img.shields.io/badge/macOS-11.0-9872AB) 23 | ![tvOS](https://img.shields.io/badge/tvOS-14.0-9872AB) 24 | ![watchOS](https://img.shields.io/badge/watchOS-7.0-9872AB) 25 |
26 | [![checks](https://github.com/sbertix/DropView/actions/workflows/push.yml/badge.svg?branch=main)](https://github.com/sbertix/DropView/actions/workflows/push.yml) 27 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/sbertix/DropView) 28 | 29 | You can find all changelogs directly under every [release](https://github.com/sbertix/DropView/releases). 30 | 31 | > What's next? 32 | 33 | [Milestones](https://github.com/sbertix/DropView/milestones) and [issues](https://github.com/sbertix/DropView/issues) are the best way to keep updated with active developement. 34 | 35 | Feel free to contribute by sending a [pull request](https://github.com/sbertix/DropView/pulls). 36 | Just remember to refer to our [guidelines](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md) beforehand. 37 | 38 |

39 | 40 | ## Installation 41 | ### Swift Package Manager (Xcode 11 and above) 42 | 1. Select `File`/`Swift Packages`/`Add Package Dependency…` from the menu. 43 | 1. Paste `https://github.com/sbertix/DropView.git`. 44 | 1. Follow the steps. 45 | 1. Add **DropView**. 46 | 47 | > Why not CocoaPods, or Carthage, or ~blank~? 48 | 49 | Supporting multiple _dependency managers_ makes maintaining a library exponentially more complicated and time consuming.\ 50 | Furthermore, with the integration of the **Swift Package Manager** in **Xcode 11** and above, we expect the need for alternative solutions to fade quickly. 51 | 52 |

53 | 54 | ## Usage 55 | 56 | **DropView** allows you to present alerts just like `sheet`s and `fullScreenCover`s. 57 | 58 |

Example 59 |

60 | 61 | ```swift 62 | import SwiftUI 63 | 64 | import DropView 65 | 66 | struct DropViewContainer: View { 67 | /// Whether it's presenting the drop view or not. 68 | @State var isPresented: Bool = false 69 | /// The time interval before it auto-dismisses. 70 | @State var seconds: TimeInterval = 2 71 | 72 | /// The underlying view. 73 | var body: some View { 74 | Form { 75 | Slider(value: $seconds, in: 2...10) { Text("Seconds before it auto-dismisses") } 76 | .padding(.horizontal) 77 | 78 | ControlGroup { 79 | Button { 80 | isPresented = true 81 | } label: { 82 | Text("Present") 83 | } 84 | Button(role: .destructive) { 85 | isPresented = false 86 | } label: { 87 | Text("Dismiss") 88 | } 89 | } 90 | } 91 | .drop(isPresented: $isPresented, alignment: .bottom, dismissingAfter: seconds) { 92 | DropView( 93 | title: "DropView", 94 | subtitle: "github.com/sbertix/DropView" 95 | ) { 96 | Image(systemName: "hand.wave.fill") 97 | .imageScale(.large) 98 | .font(.headline) 99 | .foregroundColor(.secondary) 100 | } trailing: { 101 | Image(systemName: "star.circle.fill") 102 | .resizable() 103 | .frame(width: 40, height: 40) 104 | .foregroundColor(.accentColor) 105 | } 106 | } 107 | } 108 | } 109 | ``` 110 |

111 | 112 |

113 | 114 | ## Special thanks 115 | 116 | > _Massive thanks to anyone contributing to [omaralbeik/Drops](https://github.com/omaralbeik/Drops) for the inspiration._ 117 | --------------------------------------------------------------------------------