├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── bug_report.md ├── pull_request_template.md └── workflows │ ├── build.yml │ ├── build_master.yml │ ├── deploycocoapod.yml │ ├── issues_tracker.yml │ └── release_build.yml ├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── CalendarExampleView ├── CalendarExampleView.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── swiftpm │ │ │ └── Package.resolved │ └── xcshareddata │ │ └── xcschemes │ │ └── CalendarExampleView.xcscheme └── CalendarExampleView │ ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── CalendarExampleViewApp.swift │ ├── ContentView.swift │ └── Preview Content │ └── Preview Assets.xcassets │ └── Contents.json ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Sources └── CalendarView │ ├── CalendarView.swift │ ├── Common │ ├── CalendarBackground.swift │ ├── CalendarDefine.swift │ └── CalendarViewOption.swift │ ├── Components │ ├── CalendarView+ConfigObject.swift │ ├── CalendarView+MakeData.swift │ ├── CalendarView+RootBuilder.swift │ ├── CalendarViewMode.swift │ └── CalendarWeekday.swift │ └── Utils │ ├── Calendar+Extension.swift │ ├── Date+Extension.swift │ ├── DateFormatter+Extension.swift │ ├── Locale+Extension.swift │ ├── RootBuilder.swift │ └── View+Extension.swift ├── SwiftUICalendarView.podspec ├── Tests └── CalendarViewTests │ └── CalendarViewTests.swift └── screenshot.gif /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | buy_me_a_coffee: iletai 3 | custom: ["https://www.paypal.me/iletai"] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## CalendarView SwiftUI 2 | 3 | **Reason:** 4 | 5 | **Changes:** 6 | 7 | * What's change? 8 | * Why change? 9 | 10 | **Impact:** 11 | 12 | - [ ] Build Passing? 13 | - [ ] Build On Real Device? 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Project 2 | 3 | on: 4 | workflow_call: 5 | secrets: 6 | token: 7 | required: true 8 | 9 | pull_request: 10 | branches: 11 | - "master" 12 | 13 | jobs: 14 | createinfo: 15 | name: Create Some Infomation PR 16 | runs-on: ubuntu-latest 17 | continue-on-error: true 18 | steps: 19 | - name: Checkout Repository 20 | uses: actions/checkout@v4 21 | - name: PR Github Control 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | PR_NUMBER: ${{ github.event.number }} 25 | run: | 26 | gh pr edit $PR_NUMBER --add-assignee "${{ github.repository_owner }}" 27 | gh pr edit $PR_NUMBER --add-label "enhancement" 28 | checklist: 29 | name: Checklist Checker 30 | runs-on: ubuntu-latest 31 | continue-on-error: true 32 | steps: 33 | - name: Checkout Repository 34 | uses: actions/checkout@v4 35 | - name: Pull Request Checklist Checker 36 | uses: venkatsarvesh/pr-tasks-completed-action@v1.0.0 37 | with: 38 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 39 | 40 | - name: Response Status Checklist If Pass 41 | if: success() 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | run: | 45 | gh pr edit ${{ github.event.number }} --remove-label "failedchecklist" 46 | gh pr edit ${{ github.event.number }} --add-label "passchecklist" 47 | - name: Response Status Checklist 48 | if: failure() 49 | env: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | COMMENT_BODY: "# Check List Not Yet Finish ✅! 52 | 53 | See more at: ${{ github.event.pull_request.html_url }}/checks 54 | " 55 | PR_NUMBER: ${{ github.event.number }} 56 | run: | 57 | # add failed checklist label 58 | gh pr edit ${{ github.event.number }} --remove-label "passchecklist" 59 | gh pr edit ${{ github.event.number }} --add-label "failedchecklist" 60 | 61 | checklint: 62 | name: Check Lint 63 | runs-on: ubuntu-latest 64 | steps: 65 | - name: Checkout Repository 66 | uses: actions/checkout@v4 67 | - name: Check SwiftLint 68 | uses: norio-nomura/action-swiftlint@3.2.1 69 | with: 70 | args: --strict 71 | build: 72 | name: Setup Enviroment ${{ matrix.swift }} on ${{ matrix.os }} 73 | needs: 74 | - checklint 75 | - checklist 76 | runs-on: ${{ matrix.os }} 77 | strategy: 78 | matrix: 79 | os: [macos-14] 80 | swift: ["5.9"] 81 | 82 | steps: 83 | - name: Checkout Repository 84 | uses: actions/checkout@v4 85 | - name: Cache Swift dependencies 86 | uses: actions/cache@v4 87 | with: 88 | path: .build 89 | key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} 90 | restore-keys: | 91 | ${{ runner.os }}-spm- 92 | 93 | - name: Set Swift Version 94 | uses: swift-actions/setup-swift@v1 95 | with: 96 | swift-version: ${{ matrix.swift }} 97 | 98 | - name: Build 99 | id: build 100 | run: swift build 101 | 102 | - name: Run tests 103 | run: swift test --enable-code-coverage -v 104 | 105 | - name: Setup Xcode 106 | uses: maxim-lobanov/setup-xcode@v1.4.0 107 | with: 108 | xcode-version: latest-stable 109 | 110 | - name: Get TestResult 111 | run: xcodebuild -scheme CalendarView -destination 'platform=iOS Simulator,name=iPhone 12' -resultBundlePath TestResults test 112 | 113 | - uses: kishikawakatsumi/xcresulttool@v1 114 | if: success() || failure() 115 | with: 116 | path: TestResults.xcresult 117 | show-passed-tests: false 118 | show-code-coverage: false 119 | upload-bundles: never 120 | 121 | # - name: Gather code coverage 122 | # run: xcrun llvm-cov export -format="lcov" .build/debug/CalendarViewPackageTests.xctest/Contents/MacOS/CalendarViewPackageTests -instr-profile .build/debug/codecov/default.profdata > coverage_report.lcov 123 | 124 | # - name: Upload coverage to Codecov 125 | # uses: codecov/codecov-action@v2 126 | # with: 127 | # token: ${{ secrets.CODECOV_TOKEN }} 128 | # fail_ci_if_error: fail 129 | # files: ./coverage_report.lcov 130 | # verbose: true 131 | 132 | - name: Return Status Test Fail 133 | if: failure() 134 | env: 135 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 136 | COMMENT_BODY: "😭 Unit Test Was Failed! :exclamation: 137 | 138 | See more at: ${{ github.event.pull_request.html_url }}/checks 139 | " 140 | PR_NUMBER: ${{ github.event.number }} 141 | run: | 142 | # add lable pr failed checklist 143 | gh pr edit ${{ github.event.number }} --add-label "failedchecklist" 144 | 145 | - name: Return Status Test Success 146 | if: success() 147 | env: 148 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 149 | COMMENT_BODY: "✅ Unit Test Success!" 150 | PR_NUMBER: ${{ github.event.number }} 151 | run: | 152 | # --edit-last 153 | gh pr comment $PR_NUMBER --body "$COMMENT_BODY" --edit-last || gh pr comment $PR_NUMBER --body "$COMMENT_BODY" 154 | 155 | allowgithub: 156 | name: PR Approve 157 | needs: 158 | - build 159 | - createinfo 160 | runs-on: ubuntu-latest 161 | steps: 162 | - name: Checkout Repository 163 | uses: actions/checkout@v4 164 | - name: Approved PR 165 | if: success() 166 | env: 167 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 168 | # Approved PR 169 | run: | 170 | gh pr review ${{ github.event.number }} --approve -b ":octocat: LGTM! :octocat:" 171 | - name: Allow and Merge When Succes 172 | if: success() 173 | env: 174 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 175 | REPO_OWNER: ${{ github.repository_owner }} 176 | # Using CLI Merge Pull Request 177 | run: | 178 | # Check if the PR is create by owner this repo 179 | if [[ "${{ github.event.pull_request.user.login }}" == "${REPO_OWNER}" ]]; then 180 | # gh pr merge ${{ github.event.number }} --squash --auto 181 | echo "OK" 182 | fi 183 | createtaggithub: 184 | name: Create Tag Release 185 | needs: allowgithub 186 | runs-on: ${{ matrix.os }} 187 | strategy: 188 | matrix: 189 | os: 190 | - macos-14 191 | swift: 192 | - "5.9" 193 | steps: 194 | - name: Checkout Repository 195 | uses: actions/checkout@v4 196 | # - name: Build executable for release 197 | # env: 198 | # PRODUCT_NAME: ${{ secrets.PRODUCT_NAME }} 199 | # run: swift build -c release --arch arm64 --arch x86_64 --product ${{ secrets.PRODUCT_NAME }} 200 | - name: Create Tag And Zip File 201 | if: success() && github.event.pull_request.user.login == github.repository_owner 202 | env: 203 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 204 | PRODUCT_NAME: ${{ secrets.PRODUCT_NAME}} 205 | run: | 206 | # get lastest release tag with type lastest gh 207 | LASTEST_TAG=$(gh release list --exclude-drafts --limit 1 --exclude-pre-releases --json tagName | jq -r '.[0].tagName') 208 | # Generate new tag from lastest tag up 1 209 | NEW_TAG=$(echo $LASTEST_TAG | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g') 210 | # Create pre-release from new tag 211 | # Check exist tag 212 | if [[ $(gh release list --json tagName | jq -r '.[].tagName' | grep -w $NEW_TAG) ]]; then 213 | echo "Tag $NEW_TAG is exist" 214 | gh pr comment ${{ github.event.number }} --body " 🚀 Exist Tag \`$NEW_TAG\` was created and waiting for release!" 215 | exit 0 216 | fi 217 | gh release create $NEW_TAG -t "Release $NEW_TAG" -n "Release $NEW_TAG" --generate-notes --prerelease 218 | # Add comments new tag was create and waiting for release. New tag in block code markdown with url 219 | # get link to new pre-release tag 220 | NEW_TAG_URL=$(gh release view $NEW_TAG --json html_url | jq -r '.html_url') 221 | gh pr comment ${{ github.event.number }} --body "🚀 New Tag \`$NEW_TAG\` was created and waiting for release! [Release Note]($NEW_TAG_URL)" 222 | -------------------------------------------------------------------------------- /.github/workflows/build_master.yml: -------------------------------------------------------------------------------- 1 | name: Build Master Branch 2 | 3 | on: 4 | workflow_call: 5 | secrets: 6 | token: 7 | required: true 8 | 9 | push: 10 | branches: 11 | - "master" 12 | 13 | jobs: 14 | build: 15 | name: Enviroment ${{ matrix.swift }} on ${{ matrix.os }} 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | matrix: 19 | os: [macos-14] 20 | swift: ["5.9"] 21 | 22 | steps: 23 | - name: Checkout Repository 24 | uses: actions/checkout@v4 25 | - name: Cache Swift dependencies 26 | uses: actions/cache@v4 27 | with: 28 | path: .build 29 | key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} 30 | restore-keys: | 31 | ${{ runner.os }}-spm- 32 | 33 | 34 | - name: Set Swift Version 35 | uses: swift-actions/setup-swift@v1 36 | with: 37 | swift-version: ${{ matrix.swift }} 38 | 39 | 40 | deploytag: 41 | name: Setup pod 42 | needs: build 43 | runs-on: ${{ matrix.os }} 44 | continue-on-error: true 45 | strategy: 46 | matrix: 47 | os: 48 | - macos-14 49 | swift: 50 | - "5.9" 51 | steps: 52 | - name: Checkout Repository 53 | uses: actions/checkout@v4 54 | # Cache cocoadpod 55 | - name: Cache Cocoapod 56 | uses: actions/cache@v4 57 | with: 58 | path: ~/.cocoapods 59 | key: ${{ runner.os }}-cocoapods-${{ hashFiles('**/Podfile.lock') }} 60 | restore-keys: | 61 | ${{ runner.os }}-cocoapods- 62 | - name: Install Cocoapod 63 | run: gem install cocoapods 64 | - name: Compress Push To Cocoapods 65 | continue-on-error: true 66 | run: | 67 | set -eo pipefail 68 | pod lib lint --allow-warnings 69 | pod trunk push --allow-warnings 70 | exit 0 71 | env: 72 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPOD_TOKEN }} 73 | -------------------------------------------------------------------------------- /.github/workflows/deploycocoapod.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Version To Cocoapods 2 | 3 | on: 4 | workflow_call: 5 | secrets: 6 | token: 7 | required: true 8 | release: 9 | types: [published] 10 | jobs: 11 | deploytag: 12 | name: Setup Tag 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | matrix: 16 | os: 17 | - macos-14 18 | swift: 19 | - "5.9" 20 | steps: 21 | - name: Checkout Repository 22 | uses: actions/checkout@v4 23 | - name: Install Cocoapod 24 | run: gem install cocoapods 25 | - name: Compress Push To Cocoapods 26 | run: | 27 | set -eo pipefail 28 | pod lib lint --allow-warnings 29 | # Set version source pod spec 30 | version=$(echo "${GITHUB_REF}" | sed -e "s/^refs\/tags\/v//") 31 | sed -i '' "s/s.version = '.*'/s.version = '${version}'/" Pod/Source/YourPod.podspec 32 | pod trunk push --allow-warnings 33 | env: 34 | COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPOD_TOKEN }} 35 | informstatus: 36 | name: Inform Status Release Deploy To Cocoapods 37 | needs: deploytag 38 | runs-on: ubuntu-latest 39 | # Check status of deploytag job with sucess and failure 40 | if: ${{ needs.deploytag.result == 'success' }} 41 | steps: 42 | - name: Send Slack Message 43 | uses: rtCamp/action-slack-notify@v2 44 | with: 45 | status: ${{ job.status }} 46 | author_name: ${{ github.actor }} 47 | author_icon: ${{ github.actor }} 48 | title: ${{ github.event_name }} 49 | text: ${{ github.event_name }} - ${{ github.sha }} 50 | fields: ${{ job.status }} 51 | color: ${{ job.status }} 52 | author_link: ${{ github.event.sender.html_url }} 53 | footer: ${{ github.event.repository.full_name }} 54 | footer_icon: ${{ github.event.repository.owner.avatar_url }} 55 | ts: ${{ github.run_id }} 56 | env: 57 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 58 | informstatusfailure: 59 | name: Inform Status Release Deploy To Cocoapods 60 | needs: deploytag 61 | runs-on: ubuntu-latest 62 | # Check status of deploytag job with sucess and failure 63 | if: ${{ needs.deploytag.result == 'failure' }} 64 | steps: 65 | - name: Send Slack Message 66 | uses: rtCamp/action-slack-notify@v2 67 | with: 68 | status: ${{ job.status }} 69 | author_name: ${{ github.actor }} 70 | author_icon: ${{ github.actor }} 71 | title: ${{ github.event_name }} 72 | text: ${{ github.event_name }} - ${{ github.sha }} 73 | fields: ${{ job.status }} 74 | color: ${{ job.status }} 75 | author_link: ${{ github.event.sender.html_url }} 76 | footer: ${{ github.event.repository.full_name }} 77 | footer_icon: ${{ github.event.repository.owner.avatar_url }} 78 | ts: ${{ github.run_id }} 79 | env: 80 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 81 | -------------------------------------------------------------------------------- /.github/workflows/issues_tracker.yml: -------------------------------------------------------------------------------- 1 | name: Tracker Issues CalendarView. 2 | 3 | on: 4 | issues: 5 | types: [opened, edited] 6 | 7 | jobs: 8 | example_gemini: 9 | name: Example Usable 10 | runs-on: macos-latest 11 | steps: 12 | - name: Get yarn cache directory path 13 | id: yarn-cache-dir-path 14 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)" 15 | 16 | - name: Cache yarn dependencies 17 | id: checkout 18 | uses: actions/cache@v4 19 | with: 20 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 21 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 22 | restore-keys: | 23 | ${{ runner.os }}-yarn- 24 | 25 | - name: Cache npm dependencies 26 | uses: actions/cache@v4 27 | with: 28 | path: '~/.npm' 29 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 30 | restore-keys: | 31 | ${{ runner.os }}-node- 32 | 33 | - uses: actions/checkout@v4 34 | - uses: actions/setup-node@v4 35 | with: 36 | node-version: '20.x' 37 | - name: Install NPM dependencies 38 | run: npm i @google/generative-ai 39 | 40 | - name: Using Scripts 41 | id: scriptgemini 42 | uses: actions/github-script@v7 43 | continue-on-error: true 44 | env: 45 | APIKEY: ${{ secrets.GEMINI_API_KEY }} 46 | with: 47 | script: | 48 | const modelName = { 49 | model: ["gemini-pro"], 50 | generationConfig: { temperature: 0 }, 51 | }; 52 | const { GenerativeModel } = require("@google/generative-ai"); 53 | const model = new GenerativeModel(process.env.APIKEY, modelName); 54 | const { data: availableLabels } = await github.rest.issues.listLabelsForRepo({ 55 | owner: context.repo.owner, 56 | repo: context.repo.repo, 57 | issue_number: context.payload.issue.number, 58 | }); 59 | const prompt = ` 60 | You have a role to manage a GitHub repository. Given an issue information (subject and body), choose suitable labels to it from the labels available for the repository. 61 | Use the following format: 62 | LABELS: "the names of the chosen labels, each name must not be surrounded double quotes, separated by a comma" 63 | Only use the following labels: 64 | \`\`\` 65 | ${availableLabels.map((label) => label.name).join(", ")} 66 | \`\`\` 67 | 68 | ## ISSUE ## 69 | SUBJECT: ${context.payload.issue.title} 70 | BODY: ${context.payload.issue.body} 71 | `; 72 | const result = await model.generateContent(prompt); 73 | const labels = /LABELS\: (.+)/g.exec(result.response.text()); 74 | const label = labels[1].trim().split(/,\s*/); 75 | console.log(label); 76 | return label 77 | - name: Add Labels 78 | uses: actions/github-script@v7 79 | env: 80 | github-token: ${{ secrets.GITHUB_TOKEN }} 81 | RESULT_GEMINI: ${{ steps.scriptgemini.outputs.result }} 82 | with: 83 | script: | 84 | await github.rest.issues.createComment({ 85 | owner: context.repo.owner, 86 | repo: context.repo.repo, 87 | issue_number: context.payload.issue.number, 88 | body: `The labels are ${JSON.parse(process.env.RESULT_GEMINI)}` 89 | }); 90 | await github.rest.issues.addLabels({ 91 | issue_number: context.issue.number, 92 | owner: context.repo.owner, 93 | repo: context.repo.repo, 94 | labels: JSON.parse(process.env.RESULT_GEMINI) 95 | }); 96 | -------------------------------------------------------------------------------- /.github/workflows/release_build.yml: -------------------------------------------------------------------------------- 1 | name: Release Tag 2 | 3 | on: 4 | workflow_call: 5 | secrets: 6 | token: 7 | required: true 8 | push: 9 | tags: 10 | - v*.*.* 11 | jobs: 12 | create_tag: 13 | name: Setup Tag 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: 18 | - macos-14 19 | swift: 20 | - "5.9" 21 | steps: 22 | - name: Checkout Repository 23 | uses: actions/checkout@v4 24 | - name: Build executable for release 25 | run: swift build -c release --arch arm64 --arch x86_64 --product CalendarView 26 | - name: Compress archive 27 | run: tar -czf ${{ github.ref_name }}.tar.gz -C 28 | .build/apple/Products/Release CalendarView.swiftmodule 29 | - name: Release 30 | uses: softprops/action-gh-release@v2 31 | with: 32 | files: ${{ github.ref_name }}.tar.gz 33 | token: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CalendarExampleView/CalendarExampleView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 60; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 16ADF1552B123778003ACF1B /* SwiftDate in Frameworks */ = {isa = PBXBuildFile; productRef = 16ADF1542B123778003ACF1B /* SwiftDate */; }; 11 | 9182C7482B1076A700F4DE85 /* CalendarExampleViewApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9182C7472B1076A700F4DE85 /* CalendarExampleViewApp.swift */; }; 12 | 9182C74A2B1076A700F4DE85 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9182C7492B1076A700F4DE85 /* ContentView.swift */; }; 13 | 9182C74C2B1076AA00F4DE85 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9182C74B2B1076AA00F4DE85 /* Assets.xcassets */; }; 14 | 9182C74F2B1076AA00F4DE85 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9182C74E2B1076AA00F4DE85 /* Preview Assets.xcassets */; }; 15 | 9182C7572B10771F00F4DE85 /* CalendarView in Frameworks */ = {isa = PBXBuildFile; productRef = 9182C7562B10771F00F4DE85 /* CalendarView */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 9182C7442B1076A700F4DE85 /* CalendarExampleView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CalendarExampleView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 9182C7472B1076A700F4DE85 /* CalendarExampleViewApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarExampleViewApp.swift; sourceTree = ""; }; 21 | 9182C7492B1076A700F4DE85 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 22 | 9182C74B2B1076AA00F4DE85 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | 9182C74E2B1076AA00F4DE85 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 24 | /* End PBXFileReference section */ 25 | 26 | /* Begin PBXFrameworksBuildPhase section */ 27 | 9182C7412B1076A700F4DE85 /* Frameworks */ = { 28 | isa = PBXFrameworksBuildPhase; 29 | buildActionMask = 2147483647; 30 | files = ( 31 | 16ADF1552B123778003ACF1B /* SwiftDate in Frameworks */, 32 | 9182C7572B10771F00F4DE85 /* CalendarView in Frameworks */, 33 | ); 34 | runOnlyForDeploymentPostprocessing = 0; 35 | }; 36 | /* End PBXFrameworksBuildPhase section */ 37 | 38 | /* Begin PBXGroup section */ 39 | 9182C73B2B1076A700F4DE85 = { 40 | isa = PBXGroup; 41 | children = ( 42 | 9182C7462B1076A700F4DE85 /* CalendarExampleView */, 43 | 9182C7452B1076A700F4DE85 /* Products */, 44 | ); 45 | sourceTree = ""; 46 | }; 47 | 9182C7452B1076A700F4DE85 /* Products */ = { 48 | isa = PBXGroup; 49 | children = ( 50 | 9182C7442B1076A700F4DE85 /* CalendarExampleView.app */, 51 | ); 52 | name = Products; 53 | sourceTree = ""; 54 | }; 55 | 9182C7462B1076A700F4DE85 /* CalendarExampleView */ = { 56 | isa = PBXGroup; 57 | children = ( 58 | 9182C7472B1076A700F4DE85 /* CalendarExampleViewApp.swift */, 59 | 9182C7492B1076A700F4DE85 /* ContentView.swift */, 60 | 9182C74B2B1076AA00F4DE85 /* Assets.xcassets */, 61 | 9182C74D2B1076AA00F4DE85 /* Preview Content */, 62 | ); 63 | path = CalendarExampleView; 64 | sourceTree = ""; 65 | }; 66 | 9182C74D2B1076AA00F4DE85 /* Preview Content */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | 9182C74E2B1076AA00F4DE85 /* Preview Assets.xcassets */, 70 | ); 71 | path = "Preview Content"; 72 | sourceTree = ""; 73 | }; 74 | /* End PBXGroup section */ 75 | 76 | /* Begin PBXNativeTarget section */ 77 | 9182C7432B1076A700F4DE85 /* CalendarExampleView */ = { 78 | isa = PBXNativeTarget; 79 | buildConfigurationList = 9182C7522B1076AA00F4DE85 /* Build configuration list for PBXNativeTarget "CalendarExampleView" */; 80 | buildPhases = ( 81 | 9182C7402B1076A700F4DE85 /* Sources */, 82 | 9182C7412B1076A700F4DE85 /* Frameworks */, 83 | 9182C7422B1076A700F4DE85 /* Resources */, 84 | ); 85 | buildRules = ( 86 | ); 87 | dependencies = ( 88 | ); 89 | name = CalendarExampleView; 90 | packageProductDependencies = ( 91 | 9182C7562B10771F00F4DE85 /* CalendarView */, 92 | 16ADF1542B123778003ACF1B /* SwiftDate */, 93 | ); 94 | productName = CalendarExampleView; 95 | productReference = 9182C7442B1076A700F4DE85 /* CalendarExampleView.app */; 96 | productType = "com.apple.product-type.application"; 97 | }; 98 | /* End PBXNativeTarget section */ 99 | 100 | /* Begin PBXProject section */ 101 | 9182C73C2B1076A700F4DE85 /* Project object */ = { 102 | isa = PBXProject; 103 | attributes = { 104 | BuildIndependentTargetsInParallel = 1; 105 | LastSwiftUpdateCheck = 1500; 106 | LastUpgradeCheck = 1500; 107 | TargetAttributes = { 108 | 9182C7432B1076A700F4DE85 = { 109 | CreatedOnToolsVersion = 15.0; 110 | }; 111 | }; 112 | }; 113 | buildConfigurationList = 9182C73F2B1076A700F4DE85 /* Build configuration list for PBXProject "CalendarExampleView" */; 114 | compatibilityVersion = "Xcode 14.0"; 115 | developmentRegion = en; 116 | hasScannedForEncodings = 0; 117 | knownRegions = ( 118 | en, 119 | Base, 120 | ); 121 | mainGroup = 9182C73B2B1076A700F4DE85; 122 | packageReferences = ( 123 | 9182C7552B10771F00F4DE85 /* XCLocalSwiftPackageReference ".." */, 124 | 16ADF1532B123778003ACF1B /* XCRemoteSwiftPackageReference "SwiftDate" */, 125 | ); 126 | productRefGroup = 9182C7452B1076A700F4DE85 /* Products */; 127 | projectDirPath = ""; 128 | projectRoot = ""; 129 | targets = ( 130 | 9182C7432B1076A700F4DE85 /* CalendarExampleView */, 131 | ); 132 | }; 133 | /* End PBXProject section */ 134 | 135 | /* Begin PBXResourcesBuildPhase section */ 136 | 9182C7422B1076A700F4DE85 /* Resources */ = { 137 | isa = PBXResourcesBuildPhase; 138 | buildActionMask = 2147483647; 139 | files = ( 140 | 9182C74F2B1076AA00F4DE85 /* Preview Assets.xcassets in Resources */, 141 | 9182C74C2B1076AA00F4DE85 /* Assets.xcassets in Resources */, 142 | ); 143 | runOnlyForDeploymentPostprocessing = 0; 144 | }; 145 | /* End PBXResourcesBuildPhase section */ 146 | 147 | /* Begin PBXSourcesBuildPhase section */ 148 | 9182C7402B1076A700F4DE85 /* Sources */ = { 149 | isa = PBXSourcesBuildPhase; 150 | buildActionMask = 2147483647; 151 | files = ( 152 | 9182C74A2B1076A700F4DE85 /* ContentView.swift in Sources */, 153 | 9182C7482B1076A700F4DE85 /* CalendarExampleViewApp.swift in Sources */, 154 | ); 155 | runOnlyForDeploymentPostprocessing = 0; 156 | }; 157 | /* End PBXSourcesBuildPhase section */ 158 | 159 | /* Begin XCBuildConfiguration section */ 160 | 9182C7502B1076AA00F4DE85 /* Debug */ = { 161 | isa = XCBuildConfiguration; 162 | buildSettings = { 163 | ALWAYS_SEARCH_USER_PATHS = NO; 164 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 165 | CLANG_ANALYZER_NONNULL = YES; 166 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 167 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 168 | CLANG_ENABLE_MODULES = YES; 169 | CLANG_ENABLE_OBJC_ARC = YES; 170 | CLANG_ENABLE_OBJC_WEAK = YES; 171 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 172 | CLANG_WARN_BOOL_CONVERSION = YES; 173 | CLANG_WARN_COMMA = YES; 174 | CLANG_WARN_CONSTANT_CONVERSION = YES; 175 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 176 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 177 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 178 | CLANG_WARN_EMPTY_BODY = YES; 179 | CLANG_WARN_ENUM_CONVERSION = YES; 180 | CLANG_WARN_INFINITE_RECURSION = YES; 181 | CLANG_WARN_INT_CONVERSION = YES; 182 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 183 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 184 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 185 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 186 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 187 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 188 | CLANG_WARN_STRICT_PROTOTYPES = YES; 189 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 190 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 191 | CLANG_WARN_UNREACHABLE_CODE = YES; 192 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 193 | COPY_PHASE_STRIP = NO; 194 | DEBUG_INFORMATION_FORMAT = dwarf; 195 | ENABLE_STRICT_OBJC_MSGSEND = YES; 196 | ENABLE_TESTABILITY = YES; 197 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 198 | GCC_C_LANGUAGE_STANDARD = gnu17; 199 | GCC_DYNAMIC_NO_PIC = NO; 200 | GCC_NO_COMMON_BLOCKS = YES; 201 | GCC_OPTIMIZATION_LEVEL = 0; 202 | GCC_PREPROCESSOR_DEFINITIONS = ( 203 | "DEBUG=1", 204 | "$(inherited)", 205 | ); 206 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 207 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 208 | GCC_WARN_UNDECLARED_SELECTOR = YES; 209 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 210 | GCC_WARN_UNUSED_FUNCTION = YES; 211 | GCC_WARN_UNUSED_VARIABLE = YES; 212 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 213 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 214 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 215 | MTL_FAST_MATH = YES; 216 | ONLY_ACTIVE_ARCH = YES; 217 | SDKROOT = iphoneos; 218 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 219 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 220 | }; 221 | name = Debug; 222 | }; 223 | 9182C7512B1076AA00F4DE85 /* Release */ = { 224 | isa = XCBuildConfiguration; 225 | buildSettings = { 226 | ALWAYS_SEARCH_USER_PATHS = NO; 227 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 228 | CLANG_ANALYZER_NONNULL = YES; 229 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 230 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 231 | CLANG_ENABLE_MODULES = YES; 232 | CLANG_ENABLE_OBJC_ARC = YES; 233 | CLANG_ENABLE_OBJC_WEAK = YES; 234 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 235 | CLANG_WARN_BOOL_CONVERSION = YES; 236 | CLANG_WARN_COMMA = YES; 237 | CLANG_WARN_CONSTANT_CONVERSION = YES; 238 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 239 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 240 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 241 | CLANG_WARN_EMPTY_BODY = YES; 242 | CLANG_WARN_ENUM_CONVERSION = YES; 243 | CLANG_WARN_INFINITE_RECURSION = YES; 244 | CLANG_WARN_INT_CONVERSION = YES; 245 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 246 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 247 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 248 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 249 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 250 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 251 | CLANG_WARN_STRICT_PROTOTYPES = YES; 252 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 253 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 254 | CLANG_WARN_UNREACHABLE_CODE = YES; 255 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 256 | COPY_PHASE_STRIP = NO; 257 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 258 | ENABLE_NS_ASSERTIONS = NO; 259 | ENABLE_STRICT_OBJC_MSGSEND = YES; 260 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 261 | GCC_C_LANGUAGE_STANDARD = gnu17; 262 | GCC_NO_COMMON_BLOCKS = YES; 263 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 264 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 265 | GCC_WARN_UNDECLARED_SELECTOR = YES; 266 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 267 | GCC_WARN_UNUSED_FUNCTION = YES; 268 | GCC_WARN_UNUSED_VARIABLE = YES; 269 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 270 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 271 | MTL_ENABLE_DEBUG_INFO = NO; 272 | MTL_FAST_MATH = YES; 273 | SDKROOT = iphoneos; 274 | SWIFT_COMPILATION_MODE = wholemodule; 275 | VALIDATE_PRODUCT = YES; 276 | }; 277 | name = Release; 278 | }; 279 | 9182C7532B1076AA00F4DE85 /* Debug */ = { 280 | isa = XCBuildConfiguration; 281 | buildSettings = { 282 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 283 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 284 | CODE_SIGN_STYLE = Automatic; 285 | CURRENT_PROJECT_VERSION = 1; 286 | DEVELOPMENT_ASSET_PATHS = "\"CalendarExampleView/Preview Content\""; 287 | DEVELOPMENT_TEAM = AP58YLHQ2S; 288 | ENABLE_PREVIEWS = YES; 289 | GENERATE_INFOPLIST_FILE = YES; 290 | INFOPLIST_KEY_CFBundleDisplayName = CalendarExample; 291 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle"; 292 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 293 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 294 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 295 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 296 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 297 | LD_RUNPATH_SEARCH_PATHS = ( 298 | "$(inherited)", 299 | "@executable_path/Frameworks", 300 | ); 301 | MARKETING_VERSION = 1.0; 302 | PRODUCT_BUNDLE_IDENTIFIER = com.ietai.calendarView.CalendarExampleView; 303 | PRODUCT_NAME = "$(TARGET_NAME)"; 304 | SWIFT_EMIT_LOC_STRINGS = YES; 305 | SWIFT_VERSION = 5.0; 306 | TARGETED_DEVICE_FAMILY = "1,2"; 307 | }; 308 | name = Debug; 309 | }; 310 | 9182C7542B1076AA00F4DE85 /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 314 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 315 | CODE_SIGN_STYLE = Automatic; 316 | CURRENT_PROJECT_VERSION = 1; 317 | DEVELOPMENT_ASSET_PATHS = "\"CalendarExampleView/Preview Content\""; 318 | DEVELOPMENT_TEAM = AP58YLHQ2S; 319 | ENABLE_PREVIEWS = YES; 320 | GENERATE_INFOPLIST_FILE = YES; 321 | INFOPLIST_KEY_CFBundleDisplayName = CalendarExample; 322 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.lifestyle"; 323 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 324 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 325 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 326 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 327 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 328 | LD_RUNPATH_SEARCH_PATHS = ( 329 | "$(inherited)", 330 | "@executable_path/Frameworks", 331 | ); 332 | MARKETING_VERSION = 1.0; 333 | PRODUCT_BUNDLE_IDENTIFIER = com.ietai.calendarView.CalendarExampleView; 334 | PRODUCT_NAME = "$(TARGET_NAME)"; 335 | SWIFT_EMIT_LOC_STRINGS = YES; 336 | SWIFT_VERSION = 5.0; 337 | TARGETED_DEVICE_FAMILY = "1,2"; 338 | }; 339 | name = Release; 340 | }; 341 | /* End XCBuildConfiguration section */ 342 | 343 | /* Begin XCConfigurationList section */ 344 | 9182C73F2B1076A700F4DE85 /* Build configuration list for PBXProject "CalendarExampleView" */ = { 345 | isa = XCConfigurationList; 346 | buildConfigurations = ( 347 | 9182C7502B1076AA00F4DE85 /* Debug */, 348 | 9182C7512B1076AA00F4DE85 /* Release */, 349 | ); 350 | defaultConfigurationIsVisible = 0; 351 | defaultConfigurationName = Release; 352 | }; 353 | 9182C7522B1076AA00F4DE85 /* Build configuration list for PBXNativeTarget "CalendarExampleView" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | 9182C7532B1076AA00F4DE85 /* Debug */, 357 | 9182C7542B1076AA00F4DE85 /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | /* End XCConfigurationList section */ 363 | 364 | /* Begin XCLocalSwiftPackageReference section */ 365 | 9182C7552B10771F00F4DE85 /* XCLocalSwiftPackageReference ".." */ = { 366 | isa = XCLocalSwiftPackageReference; 367 | relativePath = ..; 368 | }; 369 | /* End XCLocalSwiftPackageReference section */ 370 | 371 | /* Begin XCRemoteSwiftPackageReference section */ 372 | 16ADF1532B123778003ACF1B /* XCRemoteSwiftPackageReference "SwiftDate" */ = { 373 | isa = XCRemoteSwiftPackageReference; 374 | repositoryURL = "https://github.com/malcommac/SwiftDate"; 375 | requirement = { 376 | kind = upToNextMajorVersion; 377 | minimumVersion = 7.0.0; 378 | }; 379 | }; 380 | /* End XCRemoteSwiftPackageReference section */ 381 | 382 | /* Begin XCSwiftPackageProductDependency section */ 383 | 16ADF1542B123778003ACF1B /* SwiftDate */ = { 384 | isa = XCSwiftPackageProductDependency; 385 | package = 16ADF1532B123778003ACF1B /* XCRemoteSwiftPackageReference "SwiftDate" */; 386 | productName = SwiftDate; 387 | }; 388 | 9182C7562B10771F00F4DE85 /* CalendarView */ = { 389 | isa = XCSwiftPackageProductDependency; 390 | productName = CalendarView; 391 | }; 392 | /* End XCSwiftPackageProductDependency section */ 393 | }; 394 | rootObject = 9182C73C2B1076A700F4DE85 /* Project object */; 395 | } 396 | -------------------------------------------------------------------------------- /CalendarExampleView/CalendarExampleView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CalendarExampleView/CalendarExampleView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CalendarExampleView/CalendarExampleView.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swiftdate", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/malcommac/SwiftDate.git", 7 | "state" : { 8 | "branch" : "master", 9 | "revision" : "5d943224c3bb173e6ecf27295611615eba90c80e" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /CalendarExampleView/CalendarExampleView.xcodeproj/xcshareddata/xcschemes/CalendarExampleView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 42 | 44 | 50 | 51 | 52 | 53 | 59 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /CalendarExampleView/CalendarExampleView/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 | -------------------------------------------------------------------------------- /CalendarExampleView/CalendarExampleView/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 | -------------------------------------------------------------------------------- /CalendarExampleView/CalendarExampleView/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CalendarExampleView/CalendarExampleView/CalendarExampleViewApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarExampleViewApp.swift 3 | // CalendarExampleView 4 | // 5 | // Created by iletai on 24/11/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct CalendarExampleViewApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CalendarExampleView/CalendarExampleView/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // CalendarExampleView 4 | // 5 | // Created by iletai on 24/11/2023. 6 | // 7 | 8 | import SwiftUI 9 | import CalendarView 10 | import SwiftDate 11 | 12 | struct ContentView: View { 13 | @State var isShowHeader = false 14 | @State var isShowDateOut = false 15 | @State var firstWeekDate = CalendarWeekday.monday 16 | @State var isShowDivider = false 17 | @State var viewMode = CalendarViewMode.year(.full) 18 | @State private var selectedDate = Date() 19 | @State private var colorDay = Color.white 20 | @State var listSelectedDate = [Date]() 21 | @State var isHightLightToDay = true 22 | 23 | var fontDate: Font { 24 | switch viewMode { 25 | case .month, 26 | .week, 27 | .single: 28 | return .footnote 29 | case .year(let yearDisplayMode): 30 | switch yearDisplayMode { 31 | case .compact: 32 | return .system(size: 8, weight: .regular) 33 | case .full: 34 | return .footnote.weight(.semibold) 35 | } 36 | } 37 | } 38 | 39 | var heightCellCalendar: CGFloat { 40 | switch viewMode { 41 | case .month, 42 | .week, 43 | .single: 44 | return 30.0 45 | case .year(let yearDisplayMode): 46 | switch yearDisplayMode { 47 | case .compact: 48 | return 20.0 49 | case .full: 50 | return 30.0 51 | } 52 | } 53 | } 54 | 55 | var body: some View { 56 | VStack { 57 | ScrollView { 58 | VStack { 59 | CalendarView( 60 | date: selectedDate 61 | , dateView: { date in 62 | VStack { 63 | Text(date.dayName) 64 | .font(fontDate) 65 | .foregroundColor( 66 | Calendar.current.isDateInWeekend(date) ? .red : .black 67 | ) 68 | } 69 | .frameInfinity() 70 | .frame(height: heightCellCalendar) 71 | .background(listSelectedDate.contains(date) ? .cyan : .clear) 72 | }, headerView: { date in 73 | HStack { 74 | ForEach(date, id: \.self) { 75 | Text($0.weekDayShortName.uppercased()) 76 | .font(fontDate) 77 | .foregroundColor( 78 | Calendar.current.isDateInWeekend($0) ? .red : .black 79 | ) 80 | .frame(maxWidth: .infinity) 81 | } 82 | } 83 | }, dateOutView: { date in 84 | VStack { 85 | Text(date.dayName) 86 | .font(fontDate) 87 | .foregroundColor( 88 | Calendar.current.isDateInWeekend(date) ? .red.opacity(0.4) : .gray 89 | ) 90 | } 91 | .frameInfinity() 92 | .frame(height: heightCellCalendar) 93 | .background(listSelectedDate.contains(date) ? .cyan : .clear) 94 | } 95 | ) 96 | .enableHeader(isShowHeader) 97 | .enableDateOut(isShowDateOut) 98 | .firstWeekDay(firstWeekDate) 99 | .calendarLocate(locale: Locales.vietnamese.toLocale()) 100 | .setViewMode(viewMode) 101 | .rowsSpacing(8) 102 | .columnSpacing(8) 103 | .background(.visible(12, .gray.opacity(0.1))) 104 | .onDraggingEnded { direction, viewMode in 105 | if direction == .forward { 106 | withAnimation(.easeInOut) { 107 | selectedDate = selectedDate.dateAt( 108 | viewMode == .month ? .nextMonth : .nextWeek 109 | ).date 110 | } 111 | } else { 112 | withAnimation(.easeInOut) { 113 | selectedDate = selectedDate.dateAt( 114 | viewMode == .month ? .prevMonth : .prevWeek 115 | ).date 116 | } 117 | } 118 | } 119 | .onSelectDate(onSelectedDate) 120 | .enableDivider(isShowDivider) 121 | .enableHighlightToDay(isHightLightToDay) 122 | .marginDefault() 123 | .allowsTightening(true) 124 | Spacer() 125 | 126 | } 127 | .frame(maxWidth: .infinity) 128 | } 129 | VStack { 130 | listButtonDemo 131 | .padding() 132 | Picker("Mode", selection: $viewMode) { 133 | ForEach(CalendarViewMode.allCases, id: \.self) { option in 134 | Text(String(describing: option).uppercased()) 135 | } 136 | } 137 | .pickerStyle(.segmented) 138 | .frame(width: 300) 139 | .padding() 140 | } 141 | } 142 | } 143 | 144 | var listButtonDemo: some View { 145 | Grid(horizontalSpacing: 8.0, verticalSpacing: 8.0) { 146 | GridRow { 147 | Button { 148 | isShowHeader.toggle() 149 | } label: { 150 | Text("Header") 151 | } 152 | Button { 153 | isShowDivider.toggle() 154 | } label: { 155 | Text("Divider") 156 | } 157 | 158 | Button { 159 | isShowDateOut.toggle() 160 | } label: { 161 | Text("DateOut") 162 | } 163 | } 164 | GridRow { 165 | Button { 166 | firstWeekDate = CalendarWeekday.allCases.randomElement()! 167 | } label: { 168 | Text("WeekDate") 169 | } 170 | Button { 171 | let nextMonth = Calendar.gregorian.date(byAdding: .month, value: 1, to: selectedDate) 172 | selectedDate = nextMonth! 173 | } label: { 174 | Text("Next") 175 | } 176 | Button { 177 | isHightLightToDay.toggle() 178 | } label: { 179 | Text("ToDay") 180 | } 181 | } 182 | } 183 | .buttonStyle(.bordered) 184 | .maxWidthAble() 185 | } 186 | 187 | func onSelectedDate(_ date: Date) { 188 | if listSelectedDate.contains(date) { 189 | listSelectedDate.removeAll { $0 == date } 190 | } else { 191 | listSelectedDate.append(date) 192 | } 193 | } 194 | } 195 | 196 | #Preview { 197 | ContentView() 198 | } 199 | 200 | extension CalendarWeekday: CaseIterable { 201 | static public var allCases: [CalendarWeekday] { 202 | [.friday, .monday, .saturday, .thursday, .wednesday, .sunday, .tuesday] 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /CalendarExampleView/CalendarExampleView/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tài Lê 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.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "swiftdate", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/malcommac/SwiftDate.git", 7 | "state" : { 8 | "revision" : "5d943224c3bb173e6ecf27295611615eba90c80e", 9 | "version" : "7.0.0" 10 | } 11 | } 12 | ], 13 | "version" : 2 14 | } 15 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "CalendarView", 8 | platforms: [ 9 | .macOS(.v13), .iOS(.v17) 10 | ], 11 | products: [ 12 | // Products define the executables and libraries a package produces, making them visible to other packages. 13 | .library( 14 | name: "CalendarView", 15 | targets: ["CalendarView"] 16 | ) 17 | ], 18 | dependencies: [ 19 | .package(url: "https://github.com/malcommac/SwiftDate.git", from: "7.0.0") 20 | ], 21 | targets: [ 22 | // Targets are the basic building blocks of a package, defining a module or a test suite. 23 | // Targets can depend on other targets in this package and products from dependencies. 24 | .target( 25 | name: "CalendarView", 26 | dependencies: [ 27 | .product(name: "SwiftDate", package: "SwiftDate") 28 | ] 29 | ), 30 | .testTarget( 31 | name: "CalendarViewTests", 32 | dependencies: ["CalendarView"] 33 | ) 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

Calendar For SwiftUI

4 |

5 | 6 |

7 | 8 | 9 | 10 |

11 | 12 | > [!NOTE] 13 | > SwiftUI is a component for creating a calendar view with SwiftUI Framework. 14 | > Build a Calendar By Pure SwiftUI with SwiftDate Library for calculator date. SwiftUICalendarView is a Swift Package for building and displaying a simple calendar interface in SwiftUI. This library provides an easy way to integrate a calendar into your app. 15 | 16 |

17 | github-banner 18 |

19 | 20 | ## SwiftUICalendarView 21 | 22 | ![Swift](https://img.shields.io/badge/Swift-5.9-orange.svg) 23 | [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) 24 | [![Build Project](https://github.com/iletai/SwiftUICalendarView/actions/workflows/build_master.yml/badge.svg?branch=master)](https://github.com/iletai/SwiftUICalendarView/actions/workflows/build_master.yml) 25 | 26 | ## Requirements 27 | 28 | > [!IMPORTANT] 29 | > | **Platforms** | **Minimum Swift Version** | 30 | > |:----------|:----------| 31 | > | iOS 16+ | 5.9 | 32 | 33 | 34 | 35 | ## Installation 36 | 37 | #### Swift Package Manager 38 | 39 | To integrate SwiftUICalendarView into your project, add the GitHub URL to the dependencies section in your Package.swift file: 40 | 41 | ```swift 42 | dependencies: [ 43 | .package(url: "https://github.com/iletai/SwiftUICalendarView.git", from: "v1.4.11"), 44 | ], 45 | targets: [ 46 | .target(name: "YourTarget", dependencies: ["CalendarView"]), 47 | ] 48 | ``` 49 | 50 | #### Cocoapods 51 | Cocoapods is a dependency manager for Swift and Objective-C Cocoa projects that helps to scale them elegantly. 52 | 53 | [Installation steps](https://cocoapods.org/pods/SwiftUICalendarView): 54 | - Install CocoaPods 1.10.0 (or later) 55 | - Add CocoaPods dependency into your `Podfile` 56 | 57 | ```shell 58 | target 'MyApp' do 59 | pod 'SwiftUICalendarView' 60 | end 61 | ``` 62 | 63 | 64 | ### Feature Support: 65 | - Calendar Mode: Week, Month, Year 66 | - First WeekDay 67 | - Show Date Out 68 | - Pin Header Calendar 69 | - Allow Custom Owner Calendar Date View 70 | 71 | 72 | ### Usable Example Calendar View 73 | 74 | > [!WARNING] 75 | > Because with mindset don't want to related method reload of Obseverble SwiftUI. 76 | > Let control owner application reload with `@State` `@StateObject` or `Obsever` by themself 77 | 78 | ```swift 79 | import SwiftUI 80 | import CalendarView 81 | import SwiftDate 82 | 83 | struct ContentView: View { 84 | @State var isShowHeader = false 85 | @State var isShowDateOut = false 86 | @State var firstWeekDate = 1 87 | @State var viewMode = CalendarViewMode.year 88 | @State private var selectedDate = Date() 89 | @State var listSelectedDate = [Date]() 90 | 91 | var body: some View { 92 | VStack { 93 | CalendarView( 94 | date: selectedDate 95 | , dateView: { date in 96 | VStack { 97 | Text(date.dayName) 98 | .font(.footnote) 99 | .fontWeight(.semibold) 100 | .foregroundColor( 101 | Calendar.current.isDateInWeekend(date) ? .red : .black 102 | ) 103 | } 104 | .frame(maxWidth: .infinity) 105 | .frame(height: 30) 106 | .background(listSelectedDate.contains(date) ? .cyan : .clear) 107 | }, headerView: { date in 108 | VStack { 109 | Text(date.weekDayShortName) 110 | .font(.footnote) 111 | .fontWeight(.bold) 112 | .foregroundColor( 113 | Calendar.current.isDateInWeekend(date) ? .red : .black 114 | ) 115 | } 116 | }, dateOutView: { date in 117 | Text(DateFormatter.day.string(from: date)) 118 | .font(.footnote) 119 | .foregroundColor(.gray) 120 | }, 121 | onSelectedDate: onSelectedDate 122 | ) 123 | ``` 124 | 125 | ### Customizing the Interface 126 | You can customize the calendar's interface using properties like accentColor, selectedDateColor, and disabledDateColor,... 127 | ```swift 128 | .enableHeader(isShowHeader) 129 | .enableDateOut(isShowDateOut) 130 | .firstWeekDay(firstWeekDate) 131 | .calendarLocate(locale: Locales.vietnamese.toLocale()) 132 | .enablePinedView(.sectionHeaders) 133 | .setViewMode(viewMode) 134 | .rowsSpacing(0) 135 | .columnSpacing(0) 136 | .backgroundCalendar(.visible(20, .gray.opacity(0.3))) 137 | .onDraggingEnded { 138 | selectedDate = selectedDate.nextWeekday(.friday) 139 | } 140 | ``` 141 | 142 | ### Example: 143 | > [!NOTE] 144 | > For example using this repository, please help to see more at: https://github.com/iletai/SwiftUICalendarView/tree/master/CalendarExampleView 145 | 146 | If you find a bug or have a way to improve the library, create an Issue or propose a Pull Request. We welcome contributions from the community. 147 | 148 | ### License 149 | SwiftUICalendarView is released under the MIT License. See details in LICENSE. 150 | -------------------------------------------------------------------------------- /Sources/CalendarView/CalendarView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarView.swift 3 | // CalendarView 4 | // 5 | // Created by iletai on 24/11/2023. 6 | // 7 | 8 | import Foundation 9 | import SwiftDate 10 | import SwiftUI 11 | 12 | /** 13 | A SwiftUI view that represents a calendar. 14 | 15 | Use the `CalendarView` struct to display a calendar with customizable date views, header views, and date out views. 16 | 17 | - Parameters: 18 | - DateView: The type of view to use for displaying individual dates. 19 | - HeaderView: The type of view to use for displaying the header containing the month and year. 20 | - DateOutView: The type of view to use for displaying dates outside the current month. 21 | 22 | - Note: The `CalendarView` struct is a generic type that takes three type parameters: `DateView`, `HeaderView`, 23 | and `DateOutView`. 24 | - These type parameters determine the types of views used for displaying dates, 25 | headers, and dates outside the current month, respectively. 26 | 27 | - Note: The `CalendarView` struct conforms to the `View` protocol, which means it can be used as a view in SwiftUI. 28 | 29 | - Note: The `CalendarView` struct is annotated with the `@MainActor` attribute, 30 | which ensures that the view is always accessed from the main actor's context. 31 | 32 | - Note: The `CalendarView` struct has several associated type aliases: `OnSelectedDate`, 33 | `OnEndDragAction`, `YearData`, `MonthDateData`, and `WeekDataData` 34 | . These aliases are used to define the types of closures and data structures used by the `CalendarView`. 35 | 36 | - Note: The `CalendarView` struct has several properties, including 37 | `date`, `calendarOptions`, `dateView`, `headerView`, `dateOutView`, `pinedHeaderView`, `onSelected`, 38 | `isGestureFinished`, and `onDraggingEnded`. 39 | These properties control the behavior and appearance of the calendar view. 40 | 41 | - Note: The `CalendarView` struct has a `body` 42 | property that returns a `View` representing the content of the calendar view. 43 | 44 | - Note: The `CalendarView` struct uses a `ScrollView` and a `LazyVGrid` to display the calendar's content. 45 | The `ScrollView` provides scrolling behavior, while the `LazyVGrid` arranges the date views in a grid layout. 46 | 47 | - Note: The `CalendarView` struct uses a `DragGesture` to detect swipe gestures on the calendar view. 48 | When a swipe gesture is detected, 49 | the `onDraggingEnded` closure is called with the direction of the swipe and the current view mode of the calendar. 50 | 51 | - Note: The `CalendarView` struct provides several modifiers, 52 | such as `marginDefault()`, `background(_:)`, `simultaneousGesture(_:)`, 53 | `scrollIndicators(_:)`, `scrollDisabled(_:)`, and `frameInfinity()`, 54 | that can be used to customize the appearance and behavior of the calendar view. 55 | */ 56 | @MainActor 57 | public struct CalendarView< 58 | DateView: View, 59 | HeaderView: View, 60 | DateOutView: View 61 | >: View { 62 | // MARK: - Type Aliases 63 | /// A closure type that is called when a date is selected. 64 | public typealias OnSelectedDate = (Date) -> Void 65 | 66 | /// A closure type that is called when dragging ends. 67 | public typealias OnEndDragAction = (Direction, CalendarViewMode) -> Void 68 | 69 | /// A type alias for a dictionary that stores the dates for each year. 70 | typealias YearData = [Date: [Date]] 71 | 72 | /// A type alias for an array of dates for a specific month. 73 | typealias MonthDateData = [Date] 74 | 75 | /// A type alias for an array of dates for a specific week. 76 | typealias WeekDataData = [Date] 77 | 78 | // MARK: - Properties 79 | 80 | /// The selected date. 81 | var date = Date() 82 | 83 | /// The options for the calendar view. 84 | var calendarOptions: CalendarViewOption 85 | 86 | // MARK: - View Builder 87 | 88 | /// The view builder for the date view. 89 | let dateView: (Date) -> DateView 90 | 91 | /// The view builder for the header view. 92 | let headerView: ([Date]) -> HeaderView 93 | 94 | /// The view builder for the date out view. 95 | let dateOutView: (Date) -> DateOutView 96 | 97 | /// The pinned header view. 98 | var pinedHeaderView = PinnedScrollableViews() 99 | 100 | /// The closure that is called when a date is selected. 101 | var onSelected: OnSelectedDate = { _ in } 102 | 103 | /// The gesture state for tracking the dragging state. 104 | @GestureState var isGestureFinished = true 105 | 106 | /// The closure that is called when dragging ends. 107 | var onDraggingEnded: OnEndDragAction? 108 | 109 | /// The swipe gesture for navigating between dates. 110 | private var swipeGesture: some Gesture { 111 | DragGesture( 112 | minimumDistance: CalendarDefine.kDistaneSwipeBack, 113 | coordinateSpace: .global 114 | ) 115 | .updating($isGestureFinished) { _, state, _ in 116 | state = false 117 | } 118 | .onEnded { endedGesture in 119 | if (endedGesture.location.x - endedGesture.startLocation.x) > 0 { 120 | onDraggingEnded?(.backward, calendarOptions.viewMode) 121 | } else { 122 | onDraggingEnded?(.forward, calendarOptions.viewMode) 123 | } 124 | } 125 | } 126 | 127 | // MARK: - Initializer 128 | 129 | /** 130 | Initializes a new calendar view. 131 | 132 | - Parameters: 133 | - date: The selected date. 134 | - dateView: The view builder for the date view. 135 | - headerView: The view builder for the header view. 136 | - dateOutView: The view builder for the date out view. 137 | */ 138 | public init( 139 | date: Date, 140 | @ViewBuilder dateView: @escaping (Date) -> DateView, 141 | @ViewBuilder headerView: @escaping ([Date]) -> HeaderView, 142 | @ViewBuilder dateOutView: @escaping (Date) -> DateOutView 143 | ) { 144 | self.dateView = dateView 145 | self.headerView = headerView 146 | self.dateOutView = dateOutView 147 | self.date = date 148 | self.calendarOptions = .defaultOption 149 | } 150 | 151 | // MARK: - Main Body View 152 | public var body: some View { 153 | LazyVGrid( 154 | columns: columnGridLayout, 155 | spacing: calendarOptions.spacingBetweenDay, 156 | pinnedViews: pinedHeaderView 157 | ) { 158 | bodyContentView 159 | } 160 | .highPriorityGesture(swipeGesture) 161 | .marginDefault() 162 | .background(backgroundCalendar) 163 | .animation(calendarOptions.swapAnimation, value: calendarOptions.viewMode) 164 | } 165 | } 166 | 167 | // MARK: - ViewBuilder Private API 168 | /** 169 | A SwiftUI view representing a calendar view. 170 | 171 | This view displays a calendar with different view modes such as month, year, week, and single day. 172 | The calendar view can be customized with various options 173 | such as showing headers, dividers, highlighting today's date, and more. 174 | 175 | - Author: Tai Le 176 | - Version: 1.4.8 177 | */ 178 | extension CalendarView { 179 | /** 180 | Returns the body content view based on the current view mode. 181 | 182 | The body content view is determined by the `calendarOptions.viewMode` property. 183 | - Returns: A SwiftUI `View` representing the body content. 184 | */ 185 | @ViewBuilder 186 | private var bodyContentView: some View { 187 | switch calendarOptions.viewMode { 188 | case .month: 189 | monthContentView() 190 | .transition(calendarOptions.swapRight) 191 | case .year(let displayMode): 192 | switch displayMode { 193 | case .compact: 194 | yearContentCompactView() 195 | .transition(calendarOptions.swapRight) 196 | case .full: 197 | yearContentView() 198 | .transition(calendarOptions.swapLeft) 199 | } 200 | case .week: 201 | calendarWeekView() 202 | .transition(calendarOptions.swapLeft) 203 | case .single: 204 | singleDayContentView() 205 | .transition(calendarOptions.swapLeft) 206 | } 207 | } 208 | 209 | /** 210 | Returns the year content view. 211 | 212 | The year content view displays the calendar organized by months. 213 | - Returns: A SwiftUI `View` representing the year content. 214 | */ 215 | @ViewBuilder 216 | fileprivate func yearContentView() -> some View { 217 | ForEach(yearData.keys.sorted(), id: \.self) { month in 218 | Section( 219 | header: 220 | LazyVStack(alignment: .leading) { 221 | monthTitle(for: month) 222 | Divider() 223 | .allowVisibleWith(calendarOptions.isShowDivider) 224 | .padding(.bottom, 4) 225 | headerView(headerDates) 226 | } 227 | .maxWidthAble() 228 | ) { 229 | ForEach( 230 | yearData[month, default: []], 231 | id: \.self 232 | ) { date in 233 | if date.compare(.isSameMonth(month)) { 234 | dateView(date) 235 | .hightLightToDayView(date.isToday && calendarOptions.isShowHightLightToDay) 236 | } else { 237 | dateOutView(date) 238 | .allowVisibleWith(calendarOptions.isShowDateOut) 239 | } 240 | } 241 | } 242 | } 243 | } 244 | 245 | @ViewBuilder 246 | fileprivate func yearContentCompactView() -> some View { 247 | ForEach(yearData.keys.sorted(), id: \.self) { month in 248 | LazyVStack(alignment: .leading, spacing: .zero) { 249 | monthTitle(for: month) 250 | Divider() 251 | .allowVisibleWith(calendarOptions.isShowDivider) 252 | .padding(.bottom, 2) 253 | LazyVGrid( 254 | columns: Array( 255 | repeating: GridItem(.flexible(), spacing: 0, alignment: .top), 256 | count: CalendarDefine.kWeekDays 257 | ), 258 | alignment: .leading, 259 | spacing: .zero 260 | ) { 261 | ForEach( 262 | yearData[month, default: []], 263 | id: \.self 264 | ) { date in 265 | if date.compare(.isSameMonth(month)) { 266 | dateView(date) 267 | .hightLightToDayView(date.isToday && calendarOptions.isShowHightLightToDay) 268 | } else { 269 | dateOutView(date) 270 | .allowVisibleWith(calendarOptions.isShowDateOut) 271 | } 272 | } 273 | } 274 | } 275 | } 276 | } 277 | 278 | /** 279 | Returns the month title view for a given month. 280 | 281 | The month title view displays the name of the month and the year. 282 | - Parameter month: The month for which to display the title. 283 | - Returns: A SwiftUI `View` representing the month title. 284 | */ 285 | @ViewBuilder 286 | fileprivate func monthTitle(for month: Date) -> some View { 287 | HStack { 288 | Spacer() 289 | Text( 290 | month.toFormat( 291 | "MMM", 292 | locale: calendarOptions.calendar.locale 293 | ) 294 | .uppercased() 295 | ).font(fontTitle) 296 | Spacer() 297 | } 298 | .allowVisibleWith(calendarOptions.isShowHeader) 299 | } 300 | 301 | /** 302 | Returns the background calendar view. 303 | 304 | The background calendar view displays the background color and corner radius of the calendar. 305 | - Returns: A SwiftUI `View` representing the background calendar. 306 | */ 307 | @ViewBuilder 308 | fileprivate var backgroundCalendar: some View { 309 | if case let .visible(conner, backgroundColor) = calendarOptions.backgroundStatus { 310 | backgroundColor.withRounderConner(conner) 311 | } 312 | } 313 | 314 | /** 315 | Returns the calendar week view. 316 | 317 | The calendar week view displays the calendar organized by weeks. 318 | - Returns: A SwiftUI `View` representing the calendar week view. 319 | */ 320 | @ViewBuilder 321 | fileprivate func calendarWeekView() -> some View { 322 | Section(header: weekDayAndMonthView) { 323 | ForEach(weekData, id: \.self) { date in 324 | if date.compare(.isSameWeek(self.date)) { 325 | dateView(date) 326 | .hightLightToDayView(date.isToday && calendarOptions.isShowHightLightToDay) 327 | } else { 328 | dateOutView(date) 329 | .allowVisibleWith(calendarOptions.isShowDateOut) 330 | } 331 | } 332 | } 333 | } 334 | 335 | /** 336 | Returns the month content view. 337 | 338 | The month content view displays the calendar organized by months. 339 | - Returns: A SwiftUI `View` representing the month content. 340 | */ 341 | @ViewBuilder 342 | fileprivate func monthContentView() -> some View { 343 | Section(header: weekDayAndMonthView) { 344 | ForEach(monthData, id: \.self) { date in 345 | if date.compare(.isSameMonth(self.date)) { 346 | dateView(date) 347 | .hightLightToDayView(date.isToday && calendarOptions.isShowHightLightToDay) 348 | } else { 349 | dateOutView(date) 350 | .allowVisibleWith(calendarOptions.isShowDateOut) 351 | } 352 | } 353 | } 354 | } 355 | 356 | /** 357 | Returns the week day and month view. 358 | 359 | The week day and month view displays the month title, divider, and header view. 360 | - Returns: A SwiftUI `View` representing the week day and month view. 361 | */ 362 | @ViewBuilder 363 | private var weekDayAndMonthView: some View { 364 | VStack { 365 | monthTitle(for: date) 366 | Divider() 367 | .allowVisibleWith(calendarOptions.isShowDivider) 368 | .padding(.bottom, 4) 369 | headerView(headerDates) 370 | } 371 | } 372 | 373 | /** 374 | Returns the single day content view. 375 | 376 | The single day content view displays the header view and the date view for a single day. 377 | - Returns: A SwiftUI `View` representing the single day content. 378 | */ 379 | @ViewBuilder 380 | private func singleDayContentView() -> some View { 381 | VStack { 382 | headerView(headerDates) 383 | Divider() 384 | .allowVisibleWith(calendarOptions.isShowDivider) 385 | dateView(date) 386 | .hightLightToDayView(date.isToday && calendarOptions.isShowHightLightToDay) 387 | } 388 | .maxWidthAble() 389 | .maxHeightAble() 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /Sources/CalendarView/Common/CalendarBackground.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarBackground.swift 3 | // 4 | // 5 | // Created by Lê Quang Trọng Tài on 5/4/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// An enumeration representing the background options for a calendar view. 12 | public enum CalendarBackground { 13 | /// The background is hidden. 14 | case hidden 15 | /// The background is visible with a specific opacity and color. 16 | case visible(CGFloat, Color) 17 | /// The background is an image with the specified name. 18 | case image(String) 19 | } 20 | -------------------------------------------------------------------------------- /Sources/CalendarView/Common/CalendarDefine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Lê Quang Trọng Tài on 11/27/23. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct CalendarDefine { 11 | static let kWeekDays = 7 12 | static let kDistaneSwipeBack = 100.0 13 | } 14 | -------------------------------------------------------------------------------- /Sources/CalendarView/Common/CalendarViewOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarViewOption.swift 3 | // 4 | // 5 | // Created by Lê Quang Trọng Tài on 5/4/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftDate 10 | import SwiftUI 11 | 12 | /// A struct that represents the options for a calendar view. 13 | public struct CalendarViewOption { 14 | public var isShowHeader: Bool 15 | public var isShowDateOut: Bool 16 | public var isShowDivider: Bool 17 | public var isShowHightLightToDay: Bool 18 | public var calendar: Calendar 19 | public var backgroundStatus: CalendarBackground 20 | public var spacingBetweenDay: CGFloat 21 | public var viewMode: CalendarViewMode 22 | public var spaceBetweenColumns: CGFloat 23 | public var compactMonthCount = 3 24 | public var locateForCalendar: Locale 25 | public let swapAnimation: Animation = .easeInOut(duration: 0.3) 26 | public let swapRight = AnyTransition.asymmetric( 27 | insertion: .scale(scale: 1.5, anchor: .center), 28 | removal: .opacity 29 | ) 30 | public let swapLeft = AnyTransition.asymmetric( 31 | insertion: .scale(scale: 1.5, anchor: .center), 32 | removal: .opacity 33 | ) 34 | 35 | /// Initializes a new instance of `CalendarViewOption` with default values. 36 | init() { 37 | self.isShowHeader = true 38 | self.isShowDateOut = true 39 | self.isShowDivider = true 40 | self.isShowHightLightToDay = true 41 | self.calendar = .gregorian 42 | self.backgroundStatus = .hidden 43 | self.spacingBetweenDay = 8.0 44 | self.viewMode = .year(.full) 45 | self.spaceBetweenColumns = 8.0 46 | self.locateForCalendar = Locale.vietnam.toLocale() 47 | } 48 | } 49 | 50 | extension CalendarViewOption { 51 | /// The default `CalendarViewOption` instance. 52 | public static var defaultOption: CalendarViewOption { 53 | var options = CalendarViewOption() 54 | options.backgroundStatus = .hidden 55 | options.calendar = .gregorian 56 | options.isShowHightLightToDay = true 57 | options.isShowDateOut = true 58 | options.isShowHeader = true 59 | options.spaceBetweenColumns = 8.0 60 | options.spacingBetweenDay = 8.0 61 | options.viewMode = .year(.full) 62 | options.isShowDivider = true 63 | options.compactMonthCount = 3 64 | options.locateForCalendar = Locale.vietnam.toLocale() 65 | return options 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Sources/CalendarView/Components/CalendarView+ConfigObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarView+ConfigObject.swift 3 | // 4 | // 5 | // Created by iletai on 20/03/2024. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension CalendarView { 12 | /// `Direction` determines the direction of the swipe gesture 13 | public enum Direction { 14 | /// Swiping from left to right 15 | case forward 16 | /// Swiping from right to left 17 | case backward 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Sources/CalendarView/Components/CalendarView+MakeData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarView+MakeData.swift 3 | // 4 | // 5 | // Created by Lê Quang Trọng Tài on 1/1/24. 6 | // 7 | 8 | import Foundation 9 | import SwiftDate 10 | import SwiftUI 11 | 12 | extension CalendarView { 13 | /// Generates an array of dates based on the given parameters. 14 | /// 15 | /// - Parameters: 16 | /// - date: The base date. 17 | /// - withComponent: The calendar component to generate dates for. Default is `.month`. 18 | /// - dateComponents: The date components to increment by. 19 | /// - Returns: An array of dates. 20 | func generateDates( 21 | date: Date, 22 | withComponent: Calendar.Component = .month, 23 | dateComponents: DateComponents 24 | ) -> [Date] { 25 | SwiftDate.defaultRegion = Region(calendar: calendarOptions.calendar) 26 | let dateStart = date.dateAtStartOf(withComponent) 27 | let dateEnd = date.dateAtEndOf(withComponent) 28 | let dateStartRegion = DateInRegion( 29 | dateStart.dateAt(.startOfWeek), 30 | region: .currentIn(calendar: calendarOptions.calendar) 31 | ) 32 | let dateEndRegion = DateInRegion( 33 | dateEnd.dateAt(.endOfWeek), 34 | region: .currentIn(calendar: calendarOptions.calendar) 35 | ) 36 | let dates = DateInRegion.enumerateDates( 37 | from: dateStartRegion, 38 | to: dateEndRegion, 39 | increment: dateComponents 40 | ).map { $0.date } 41 | return dates 42 | } 43 | } 44 | 45 | // MARK: - Data For Calendar 46 | extension CalendarView { 47 | /// Computes the year data for the calendar. 48 | var yearData: YearData { 49 | DateInRegion.enumerateDates( 50 | from: date.startOfYear(calendarOptions.calendar), 51 | to: date.endOfYear(calendarOptions.calendar), 52 | increment: DateComponents(month: 1) 53 | ) 54 | .map { 55 | $0.date 56 | } 57 | .reduce(into: [:]) { month, date in 58 | month[date] = generateDates( 59 | date: date.startOfMonth(calendarOptions.calendar).date, 60 | dateComponents: CalendarViewMode.month.dateComponent 61 | ) 62 | } 63 | } 64 | 65 | /// Computes the grid layout for the calendar columns. 66 | var columnGridLayout: [GridItem] { 67 | switch calendarOptions.viewMode { 68 | case .single: 69 | return Array( 70 | repeating: GridItem(.flexible()), 71 | count: 1 72 | ) 73 | case .year(let mode): 74 | switch mode { 75 | case .compact: 76 | return Array( 77 | repeating: GridItem(.flexible(), spacing: calendarOptions.spaceBetweenColumns, alignment: .top), 78 | count: calendarOptions.compactMonthCount 79 | ) 80 | case .full: 81 | return Array( 82 | repeating: GridItem(.flexible(), spacing: calendarOptions.spaceBetweenColumns, alignment: .top), 83 | count: CalendarDefine.kWeekDays 84 | ) 85 | } 86 | default: 87 | return Array( 88 | repeating: GridItem(.flexible(), spacing: calendarOptions.spaceBetweenColumns, alignment: .top), 89 | count: CalendarDefine.kWeekDays 90 | ) 91 | } 92 | } 93 | 94 | /// Computes the month data for the calendar. 95 | var monthData: MonthDateData { 96 | generateDates( 97 | date: date, 98 | withComponent: .month, 99 | dateComponents: DateComponents(day: 1) 100 | ) 101 | } 102 | 103 | /// Computes the week data for the calendar. 104 | var weekData: WeekDataData { 105 | generateDates( 106 | date: date, 107 | withComponent: .weekOfMonth, 108 | dateComponents: DateComponents(day: 1) 109 | ) 110 | } 111 | 112 | /// Computes the header dates for the calendar. 113 | var headerDates: [Date] { 114 | switch calendarOptions.viewMode { 115 | case .month, .week: 116 | return Array(monthData.prefix(CalendarDefine.kWeekDays)) 117 | case .year: 118 | return Array( 119 | yearData[date.dateAtStartOf(.year), default: []].prefix(CalendarDefine.kWeekDays) 120 | ) 121 | case .single: 122 | return [date] 123 | } 124 | } 125 | 126 | var fontTitle: Font { 127 | switch calendarOptions.viewMode { 128 | case .month, 129 | .single, 130 | .year(.full): 131 | return .footnote.bold() 132 | case .year(.compact): 133 | return .system(size: 10, weight: .semibold) 134 | default: return .footnote.bold() 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Sources/CalendarView/Components/CalendarView+RootBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarView+RootBuilder.swift 3 | // 4 | // 5 | // Created by iletai on 24/11/2023. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | @MainActor 12 | extension CalendarView: RootBuilder { 13 | /// Enables or disables the header in the calendar view. 14 | /// - Parameter isEnable: A boolean value indicating whether to enable or disable the header. 15 | /// - Returns: An instance of `Self` with the updated configuration. 16 | public func enableHeader(_ isEnable: Bool) -> Self { 17 | mutating(\.calendarOptions.isShowHeader, value: isEnable) 18 | } 19 | 20 | /// Enables or disables the display of dates outside the current month in the calendar view. 21 | /// - Parameter isShow: A boolean value indicating whether to show or hide dates outside the current month. 22 | /// - Returns: An instance of `Self` with the updated configuration. 23 | public func enableDateOut(_ isShow: Bool) -> Self { 24 | mutating(\.calendarOptions.isShowDateOut, value: isShow) 25 | } 26 | 27 | /// Sets the background style for the calendar view. 28 | /// - Parameter status: The background style for the calendar view. 29 | /// - Returns: An instance of `Self` with the updated configuration. 30 | public func background(_ status: CalendarBackground) -> Self { 31 | mutating(\.calendarOptions.backgroundStatus, value: status) 32 | } 33 | 34 | /// Sets the spacing between rows in the calendar view. 35 | /// - Parameter spacing: The spacing between rows. 36 | /// - Returns: An instance of `Self` with the updated configuration. 37 | public func rowsSpacing(_ spacing: CGFloat) -> Self { 38 | mutating(\.calendarOptions.spacingBetweenDay, value: spacing) 39 | } 40 | 41 | /// Sets the spacing between columns in the calendar view. 42 | /// - Parameter spacing: The spacing between columns. 43 | /// - Returns: An instance of `Self` with the updated configuration. 44 | public func columnSpacing(_ spacing: CGFloat) -> Self { 45 | mutating(\.calendarOptions.spaceBetweenColumns, value: spacing) 46 | } 47 | 48 | /// Sets the first day of the week in the calendar view. 49 | /// - Parameter first: The index of the first day of the week (1 for Sunday, 2 for Monday, etc.). 50 | /// - Returns: An instance of `Self` with the updated configuration. 51 | public func firstWeekDay(_ first: CalendarWeekday) -> Self { 52 | mutating(\.calendarOptions.calendar.firstWeekday, value: first.rawValue) 53 | } 54 | 55 | /// Sets the locale for the calendar view. 56 | /// - Parameter locale: The locale to be used for the calendar view. 57 | /// - Returns: An instance of `Self` with the updated configuration. 58 | public func calendarLocate(locale: Locale) -> Self { 59 | mutating(\.calendarOptions.calendar.locale, value: locale) 60 | } 61 | 62 | /// Sets the view mode for the calendar view. 63 | /// - Parameter mode: The view mode for the calendar view. 64 | /// - Returns: An instance of `Self` with the updated configuration. 65 | public func setViewMode(_ mode: CalendarViewMode) -> Self { 66 | mutating(\.calendarOptions.viewMode, value: mode) 67 | } 68 | 69 | /// Sets the callback for when dragging ends in the calendar view. 70 | /// - Parameter callback: A closure that takes a `Direction` parameter and has no return value. 71 | /// - Returns: An instance of `Self` with the updated configuration. 72 | public func onDraggingEnded(_ callback: OnEndDragAction?) -> Self { 73 | mutating(\.onDraggingEnded, value: callback) 74 | } 75 | 76 | /// Enables the pinned view in the calendar view. 77 | /// - Parameter view: The pinned view to be enabled. 78 | /// - Returns: An instance of `Self` with the updated configuration. 79 | public func enablePinedView(_ view: PinnedScrollableViews) -> Self { 80 | mutating(\.pinedHeaderView, value: [view]) 81 | } 82 | 83 | /// Sets a callback closure to be executed when a date is selected in the calendar view. 84 | /// - Parameter callback: The closure to be executed when a date is selected. 85 | /// - Returns: The modified `CalendarView+RootBuilder` instance. 86 | public func onSelectDate(_ callback: @escaping OnSelectedDate) -> Self { 87 | mutating(\.onSelected, value: callback) 88 | } 89 | 90 | /// Enables or disables the divider in the calendar view. 91 | /// - Parameter isEnable: A boolean value indicating whether the divider should be enabled or disabled. 92 | /// - Returns: The modified `CalendarView+RootBuilder` instance. 93 | public func enableDivider(_ isEnable: Bool) -> Self { 94 | mutating(\.calendarOptions.isShowDivider, value: isEnable) 95 | } 96 | 97 | /// Enables or disables the highlight effect on the current day in the calendar view. 98 | /// - Parameter isEnable: A boolean value indicating whether the highlight effect should be enabled or disabled. 99 | /// - Returns: The modified `CalendarView+RootBuilder` instance. 100 | public func enableHighlightToDay(_ isEnable: Bool) -> Self { 101 | mutating(\.calendarOptions.isShowHightLightToDay, value: isEnable) 102 | } 103 | 104 | /// Sets the initial date to be displayed in the calendar view. 105 | /// - Parameter date: The initial date to be displayed. 106 | /// - Returns: The modified `CalendarView+RootBuilder` instance. 107 | func setDate(_ date: Date) -> Self { 108 | mutating(\.date, value: date) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/CalendarView/Components/CalendarViewMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewMode.swift 3 | // 4 | // 5 | // Created by tailqt on 28/11/2023. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import SwiftDate 11 | 12 | /// Represents the different modes of the calendar view. 13 | public enum CalendarViewMode: CaseIterable, Hashable { 14 | public static var allCases: [CalendarViewMode] { 15 | [ 16 | CalendarViewMode.year(.full), 17 | CalendarViewMode.year(.compact), 18 | CalendarViewMode.month, 19 | CalendarViewMode.week, 20 | CalendarViewMode.single 21 | ] 22 | } 23 | 24 | case month 25 | case week 26 | case year(YearDisplayMode) 27 | case single 28 | 29 | /// The corresponding `Calendar.Component` for each mode. 30 | var component: Calendar.Component { 31 | switch self { 32 | case .month: 33 | return .month 34 | case .week: 35 | return .weekOfMonth 36 | case .year: 37 | return .year 38 | case .single: 39 | return .day 40 | } 41 | } 42 | 43 | /// The corresponding `DateComponents` for each mode. 44 | var dateComponent: DateComponents { 45 | switch self { 46 | case .month, .single, .week: 47 | return DateComponents(day: 1) 48 | case .year: 49 | return DateComponents(month: 1) 50 | } 51 | } 52 | 53 | /// Determines if scrolling is enabled for the mode. 54 | var enableScroll: Bool { 55 | switch self { 56 | case .month, .week, .single: 57 | return false 58 | case .year: 59 | return true 60 | } 61 | } 62 | 63 | /// Determines if scrolling is disabled for the mode. 64 | var isDisableScroll: Bool { !enableScroll } 65 | 66 | /// The visibility of the scroll indicator for the mode. 67 | var enableScrollIndicator: ScrollIndicatorVisibility { 68 | switch self { 69 | case .month, .week, .single: 70 | return .never 71 | case .year: 72 | return .visible 73 | } 74 | } 75 | 76 | /// The type of date related to the mode. 77 | var dateRelatedType: DateRelatedType { 78 | switch self { 79 | case .month: 80 | return .nextMonth 81 | case .week: 82 | return .nextWeek 83 | case .year: 84 | return .nextYear 85 | case .single: 86 | return .nextWeekday(.sunday) 87 | } 88 | } 89 | } 90 | 91 | extension CalendarViewMode: Equatable {} 92 | 93 | public enum YearDisplayMode { 94 | case compact 95 | case full 96 | } 97 | -------------------------------------------------------------------------------- /Sources/CalendarView/Components/CalendarWeekday.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CalendarView+WeekDay.swift 3 | // 4 | // 5 | // Created by iletai on 27/03/2024. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Represents the days of the week in a calendar. 11 | public enum CalendarWeekday: Int, CustomStringConvertible { 12 | case sunday = 1 13 | case monday 14 | case tuesday 15 | case wednesday 16 | case thursday 17 | case friday 18 | case saturday 19 | 20 | /// A textual representation of the weekday. 21 | public var description: String { 22 | switch self { 23 | case .sunday: 24 | return "sunday" 25 | case .monday: 26 | return "monday" 27 | case .tuesday: 28 | return "tuesday" 29 | case .wednesday: 30 | return "wednesday" 31 | case .thursday: 32 | return "thursday" 33 | case .friday: 34 | return "friday" 35 | case .saturday: 36 | return "saturday" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/CalendarView/Utils/Calendar+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Calendar+Extension.swift 3 | // 4 | // 5 | // Created by iletai on 24/11/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | extension Calendar { 11 | public static var gregorian: Calendar { 12 | Calendar(identifier: .gregorian) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Sources/CalendarView/Utils/Date+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Extension.swift 3 | // 4 | // 5 | // Created by Lê Quang Trọng Tài on 11/26/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftDate 10 | 11 | /// Extension for `Date` providing additional utility methods. 12 | extension Date { 13 | /// Returns the name of the week day for the date. 14 | public var weekDayName: String { 15 | let dateFormatter = DateFormatter() 16 | dateFormatter.dateFormat = "EEEE" 17 | dateFormatter.locale = Locales.vietnamese.toLocale() 18 | return dateFormatter.string(from: self) 19 | } 20 | 21 | /// Returns the short name of the week day for the date. 22 | public var weekDayShortName: String { 23 | let dateFormatter = DateFormatter() 24 | dateFormatter.dateFormat = "EE" 25 | dateFormatter.locale = Locales.vietnamese.toLocale() 26 | return dateFormatter.string(from: self) 27 | } 28 | 29 | /// Returns the day name for the date. 30 | public var dayName: String { 31 | let dateFormatter = DateFormatter() 32 | dateFormatter.dateFormat = "d" 33 | dateFormatter.locale = Locales.vietnamese.toLocale() 34 | return dateFormatter.string(from: self) 35 | } 36 | 37 | /// Returns the start of the year for the date in the specified calendar. 38 | /// 39 | /// - Parameter inCalendar: The calendar to use. 40 | /// - Returns: The start of the year for the date. 41 | public func startOfYear(_ inCalendar: Calendar) -> DateInRegion { 42 | currentDateInRegion(inCalendar).dateAtStartOf(.year) 43 | } 44 | 45 | /// Returns the start of the month for the date in the specified calendar. 46 | /// 47 | /// - Parameter inCalendar: The calendar to use. 48 | /// - Returns: The start of the month for the date. 49 | public func startOfMonth(_ inCalendar: Calendar) -> DateInRegion { 50 | currentDateInRegion(inCalendar).dateAtStartOf(.month) 51 | } 52 | 53 | /// Returns the end of the year for the date in the specified calendar. 54 | /// 55 | /// - Parameter inCalendar: The calendar to use. 56 | /// - Returns: The end of the year for the date. 57 | public func endOfYear(_ inCalendar: Calendar) -> DateInRegion { 58 | currentDateInRegion(inCalendar).dateAtEndOf(.year) 59 | } 60 | 61 | /// Returns the current date in the specified calendar. 62 | /// 63 | /// - Parameter calendar: The calendar to use. 64 | /// - Returns: The current date in the specified calendar. 65 | public func currentDateInRegion(_ calendar: Calendar) -> DateInRegion { 66 | DateInRegion(self, region: .currentIn(calendar: calendar)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Sources/CalendarView/Utils/DateFormatter+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DateFormatter.swift 3 | // 4 | // 5 | // Created by iletai on 24/11/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | public extension DateFormatter { 11 | static let monthAndYear: DateFormatter = { 12 | let formatter = DateFormatter() 13 | formatter.setLocalizedDateFormatFromTemplate("MMMM yyyy") 14 | return formatter 15 | }() 16 | 17 | static var day: DateFormatter { 18 | let formatter = DateFormatter() 19 | formatter.dateFormat = "d" 20 | return formatter 21 | } 22 | 23 | static var weekDay: DateFormatter { 24 | let formatter = DateFormatter() 25 | formatter.dateFormat = "EEEEE" 26 | return formatter 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/CalendarView/Utils/Locale+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Locale+Extension.swift 3 | // 4 | // 5 | // Created by Lê Quang Trọng Tài on 12/30/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftDate 10 | 11 | extension Locale { 12 | static var vietnam: Locale { 13 | Locales.vietnamese.toLocale() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sources/CalendarView/Utils/RootBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootBuilder.swift 3 | // 4 | // 5 | // Created by iletai on 24/11/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | /// A protocol that defines a builder for the root object. 11 | public protocol RootBuilder {} 12 | 13 | extension RootBuilder { 14 | /// A method that creates a new instance of the builder by mutating a value at the specified key path. 15 | /// 16 | /// - Parameters: 17 | /// - keyPath: The key path to the value that needs to be mutated. 18 | /// - value: The new value to be assigned to the key path. 19 | /// - Returns: A new instance of the builder with the mutated value. 20 | public func mutating( 21 | _ keyPath: WritableKeyPath, 22 | value: T 23 | ) -> Self { 24 | var newReference = self 25 | newReference[keyPath: keyPath] = value 26 | return newReference 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sources/CalendarView/Utils/View+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // View+Extension.swift 3 | // 4 | // 5 | // Created by Lê Quang Trọng Tài on 12/31/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension View { 12 | /// Sets the maximum width of the view to infinity. 13 | /// 14 | /// - Returns: A modified view with the maximum width set to infinity. 15 | public func maxWidthAble() -> some View { 16 | frame(maxWidth: .infinity) 17 | } 18 | 19 | /// Sets the maximum height of the view to infinity. 20 | /// 21 | /// - Returns: A modified view with the maximum height set to infinity. 22 | public func maxHeightAble() -> some View { 23 | frame(maxHeight: .infinity) 24 | } 25 | 26 | /// Sets both the maximum width and maximum height of the view to infinity. 27 | /// 28 | /// - Returns: A modified view with both the maximum width and maximum height set to infinity. 29 | public func frameInfinity() -> some View { 30 | maxWidthAble().maxHeightAble() 31 | } 32 | 33 | /// Sets the padding of the view to a default value of 16 on all sides. 34 | /// 35 | /// - Returns: A modified view with the padding set to a default value of 16 on all sides. 36 | public func marginDefault() -> some View { 37 | padding(.all, 16) 38 | } 39 | 40 | /// Clips the view to the specified corner radius. 41 | /// 42 | /// - Parameter radius: The corner radius to apply to the view. 43 | /// - Returns: A modified view with the specified corner radius applied. 44 | public func withRounderConner(_ radius: CGFloat) -> some View { 45 | clipShape(RoundedRectangle(cornerRadius: radius)) 46 | } 47 | } 48 | 49 | extension View { 50 | /** 51 | Highlights the day view if it is today. 52 | 53 | This modifier applies a background color to the view if the date is today. 54 | - Parameters: 55 | - isToday: A boolean value indicating whether the date is today. 56 | - color: The color to use for highlighting. Default is `.orange`. 57 | - Returns: A modified SwiftUI `View` with the highlighting applied. 58 | */ 59 | @ViewBuilder 60 | func hightLightToDayView( 61 | _ isToday: Bool, 62 | _ color: Color = .orange 63 | ) -> some View { 64 | if isToday { 65 | background(color.clipShape(Circle())) 66 | } else { 67 | self 68 | } 69 | } 70 | 71 | /** 72 | Sets the visibility of the view based on a boolean value. 73 | 74 | This modifier sets the opacity of the view to 1.0 if the `isAllow` parameter is `true`, otherwise sets it to 0.0. 75 | - Parameter isAllow: A boolean value indicating whether the view should be visible. 76 | - Returns: A modified SwiftUI `View` with the visibility set. 77 | */ 78 | @ViewBuilder 79 | func allowVisibleWith(_ isAllow: Bool) -> some View { 80 | opacity(isAllow ? 1.0 : 0.0) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /SwiftUICalendarView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'SwiftUICalendarView' 3 | s.summary = 'Create fully customizable calendar in no time. Keep your code clean' 4 | s.description = <<-DESC 5 | SwiftUICalendarView is a free and open-source library in SwiftUI to make calendar. 6 | DESC 7 | 8 | s.version = '1.4.11' 9 | s.platform = :ios 10 | s.ios.deployment_target = '16.0' # Updated deployment target to a valid iOS version 11 | s.swift_version = '5.9' 12 | s.dependency 'SwiftDate', '~> 7.0.0' 13 | 14 | s.source_files = 'Sources/CalendarView/**/*.{swift}' # Updated source_files pattern to match the correct file path 15 | s.frameworks = 'SwiftUI', 'Foundation' 16 | 17 | s.homepage = 'https://github.com/iletai/SwiftUICalendarView.git' 18 | s.license = { :type => 'MIT', :file => 'LICENSE' } 19 | s.author = { 'Le Quang Trong Tai' => 'iletai@hotmail.com' } 20 | s.source = { :git => 'https://github.com/iletai/SwiftUICalendarView.git', :tag => 'v' + s.version.to_s } 21 | end 22 | -------------------------------------------------------------------------------- /Tests/CalendarViewTests/CalendarViewTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CalendarView 3 | 4 | final class CalendarViewTests: XCTestCase { 5 | func testExample() throws { 6 | // XCTest Documentation 7 | // https://developer.apple.com/documentation/xctest 8 | 9 | // Defining Test Cases and Test Methods 10 | // https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iletai/SwiftUICalendarView/fb761a8db5864312b8fa9676d92f8a34e232bb51/screenshot.gif --------------------------------------------------------------------------------