├── .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 |
18 |
19 |
20 | ## SwiftUICalendarView
21 |
22 | 
23 | [](LICENSE)
24 | [](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
--------------------------------------------------------------------------------