├── .gitignore
├── Info.plist
├── LICENSE
├── Preview Content
└── Preview Assets.xcassets
│ └── Contents.json
├── README.md
├── SwiftAppTemplate.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
├── xcshareddata
│ └── xcschemes
│ │ ├── SwiftAppTemplate(Debug).xcscheme
│ │ └── SwiftAppTemplate(Release).xcscheme
└── xcuserdata
│ └── shuru.xcuserdatad
│ └── xcschemes
│ └── xcschememanagement.plist
├── SwiftAppTemplate
├── Configuration
│ ├── AppConfig.swift
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── Dummy-Use&Delete
│ ├── APIUsecase
│ │ ├── WeatherData.swift
│ │ ├── WeatherDetailScreen.swift
│ │ ├── WeatherScreen.swift
│ │ ├── WeatherService.swift
│ │ └── WeatherViewModel.swift
│ ├── EditUserDetailsScreen.swift
│ ├── ProfileScreen.swift
│ ├── SettingsScreen.swift
│ ├── SettingsViewModel.swift
│ └── WebScreen.swift
├── GoogleService-Info.plist
├── LaunchApp.swift
├── Managers
│ ├── AnalyticsManager.swift
│ └── AuthenticationManager.swift
├── Models
│ └── User.swift
├── Network
│ ├── APIEndpoints.swift
│ ├── AppError.swift
│ └── NetworkManager.swift
├── Resources
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIconDebug.appiconset
│ │ │ ├── AppIconDebug-1024.png
│ │ │ ├── AppIconDebug-20.png
│ │ │ ├── AppIconDebug-20@2x.png
│ │ │ ├── AppIconDebug-20@3x.png
│ │ │ ├── AppIconDebug-29.png
│ │ │ ├── AppIconDebug-29@2x.png
│ │ │ ├── AppIconDebug-29@3x.png
│ │ │ ├── AppIconDebug-40.png
│ │ │ ├── AppIconDebug-40@2x.png
│ │ │ ├── AppIconDebug-40@3x.png
│ │ │ ├── AppIconDebug-60@2x.png
│ │ │ ├── AppIconDebug-60@3x.png
│ │ │ ├── AppIconDebug-76.png
│ │ │ ├── AppIconDebug-76@2x.png
│ │ │ ├── AppIconDebug-83.5@2x.png
│ │ │ └── Contents.json
│ │ ├── AppIconRelease.appiconset
│ │ │ ├── AppIconRelease-1024.png
│ │ │ ├── AppIconRelease-20.png
│ │ │ ├── AppIconRelease-20@2x.png
│ │ │ ├── AppIconRelease-20@3x.png
│ │ │ ├── AppIconRelease-29.png
│ │ │ ├── AppIconRelease-29@2x.png
│ │ │ ├── AppIconRelease-29@3x.png
│ │ │ ├── AppIconRelease-40.png
│ │ │ ├── AppIconRelease-40@2x.png
│ │ │ ├── AppIconRelease-40@3x.png
│ │ │ ├── AppIconRelease-60@2x.png
│ │ │ ├── AppIconRelease-60@3x.png
│ │ │ ├── AppIconRelease-76.png
│ │ │ ├── AppIconRelease-76@2x.png
│ │ │ ├── AppIconRelease-83.5@2x.png
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── Images
│ │ │ ├── Contents.json
│ │ │ ├── onboarding_1.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── onboarding_1.png
│ │ │ └── onboarding_2.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── onboarding_2.png
│ │ ├── LaunchScreen
│ │ │ ├── Contents.json
│ │ │ └── logo.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ └── logo.png
│ │ ├── backgroundColor.colorset
│ │ │ └── Contents.json
│ │ └── textColor.colorset
│ │ │ └── Contents.json
│ ├── Fonts
│ │ ├── NotoSans-Bold.ttf
│ │ ├── NotoSans-Medium.ttf
│ │ ├── NotoSans-Regular.ttf
│ │ └── NotoSans-SemiBold.ttf
│ ├── ar.lproj
│ │ └── Localizable.strings
│ ├── en.lproj
│ │ └── Localizable.strings
│ ├── es.lproj
│ │ └── Localizable.strings
│ ├── fr.lproj
│ │ └── Localizable.strings
│ ├── hi.lproj
│ │ └── Localizable.strings
│ └── zh-Hans.lproj
│ │ └── Localizable.strings
├── ReusableViews
│ ├── CardView.swift
│ ├── ConfirmationSheet.swift
│ ├── CustomBottomSheetView.swift
│ ├── CustomTextField.swift
│ ├── CustomTitleTextFieldView.swift
│ ├── DatePickerPopover.swift
│ ├── Header.swift
│ ├── LoaderView.swift
│ ├── PickerView.swift
│ ├── PrimaryButton.swift
│ ├── TextButton.swift
│ ├── TextKeyValueView.swift
│ └── UserInfoView.swift
├── Screens
│ ├── Authentication
│ │ └── AuthorizationScreen.swift
│ ├── MainScreen.swift
│ ├── MainTabView
│ │ ├── MainTabCoordinator.swift
│ │ ├── TabItem.swift
│ │ ├── TabViews
│ │ │ ├── Tab1Screen.swift
│ │ │ ├── Tab2Screen.swift
│ │ │ ├── Tab3Screen.swift
│ │ │ ├── Tab4Screen.swift
│ │ │ └── Tab5Screen.swift
│ │ └── ViewModels
│ │ │ └── MainTabViewModel.swift
│ ├── Onboarding
│ │ └── OnboardingScreen.swift
│ ├── Root
│ │ ├── RootCoordinator.swift
│ │ └── RootViewModel.swift
│ ├── Settings
│ │ └── SettingsView.swift
│ ├── SideMenu
│ │ └── SideMenuView.swift
│ ├── Splash
│ │ └── SplashScreen.swift
│ ├── TermsAndConditions
│ │ └── TermsAndConditionsScreen.swift
│ ├── UserDetails
│ │ └── UserDetailsScreen.swift
│ └── WebView.swift
├── Services
│ └── UserService.swift
└── Utils
│ ├── AppConfig.swift
│ ├── AppStrings.swift
│ ├── AppearancePreference.swift
│ ├── Colors.swift
│ ├── Constants.swift
│ ├── ErrorHandler.swift
│ ├── Fonts.swift
│ ├── KeyChainStorage.swift
│ ├── UserPreferences.swift
│ └── Utility.swift
└── create_swift_app.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | #Script
2 | *.sh
3 |
4 | # Xcode
5 | .DS_Store
6 | */.DS_Store
7 | *.swp
8 | *.xcuserdatad
9 | */DerivedData/
10 | */.idea/
11 | */xcuserdata/
12 |
13 | # CocoaPods
14 | Pods/
15 | Podfile.lock
16 |
17 | # Carthage
18 | Carthage/Checkouts
19 | Carthage/Build
20 |
21 | # Swift Package Manager
22 | .build/
23 | Packages/
24 |
25 | # Compiled source
26 | *.o
27 | *.link
28 | *.swiftdoc
29 | *.swiftmodule
30 |
31 | # Generated files
32 | *.hmap
33 | *.ipa
34 | *.dSYM.zip
35 | *.bcsymbolmap
36 |
37 | # Xcode user-specific files
38 | *.mode1v3
39 | *.mode2v3
40 | *.perspectivev3
41 | xcuserdata/
42 |
43 | # Android
44 | local.properties
45 |
46 | # fastlane
47 | fastlane/report.xml
48 | fastlane/Preview.html
49 | fastlane/screenshots
50 | fastlane/test_output
51 |
52 | # IDE (e.g., AppCode, Rider)
53 | .idea/
54 |
55 | # Mac
56 | .DS_Store
57 |
58 | # Playgrounds
59 | .playground
60 |
61 | # Swift Package Manager
62 | .build/
63 |
64 | # CocoaPods
65 | /Pods
66 | Podfile.lock
67 |
68 | # Carthage
69 | /Carthage
70 | /Carthage/Checkouts
71 |
72 | # Accio dependency manager
73 | /.accio/
74 |
75 | # Bitcode
76 | *.bitcode
77 |
78 | # Various settings
79 | *.hmap
80 | # *.xcworkspace
81 |
82 | # Package directories
83 | .packages
84 | .package.resolved
85 |
86 | # SPM manifest
87 | # Package.resolved
88 |
89 | # Xcode
90 | xcuserdata/
91 |
92 | # macOS
93 | .DS_Store
94 |
95 | # Xcode per-user config
96 | *.xcuserdatad
97 |
98 | # Xcode workspace
99 | #*.xcworkspace
100 |
101 | # Xcode project
102 | #*.xcodeproj
103 |
104 | # Swift Package Manager
105 | .build/
106 |
107 | # Mint
108 | /Mintfile
109 |
110 | # Temporary files
111 | .DS_Store
112 |
113 | # Package directories
114 | .packages
115 |
116 | # Code owners
117 | CODEOWNERS
118 |
119 |
--------------------------------------------------------------------------------
/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BASE_URL
6 | $(BASE_URL)
7 | EXAMPLE_KEY
8 | $(EXAMPLE_KEY)
9 | UIAppFonts
10 |
11 | NotoSans-Regular.ttf
12 | NotoSans-Medium.ttf
13 | NotoSans-SemiBold.ttf
14 | NotoSans-Bold.ttf
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Shuru Technologies Pte Ltd
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 |
--------------------------------------------------------------------------------
/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iOSKickstart
2 | An iOS app boilerplate generator built with SwiftUI. This tool is designed for creating new iOS apps by taking configuration on command line, with a basic screen flows: Splash > Authorization/Login-SignUp > User Details > Terms & Conditions > Onboarding (Carousel) > Main Tab Screens. These screens use dummy content. Adding new screens or deleting existing ones can be easily managed, and the app code follows the idiomatic swift & iOS guidelines.
3 |
4 | ## Get Started
5 |
6 | **Just use one command and create new iOS App**
7 |
8 | ```
9 |
10 | bash <(curl -fsSL "https://raw.githubusercontent.com/shurutech/iOSKickstart/main/create_swift_app.sh") -i
11 |
12 | ```
13 |
14 | - Copy the above command and paste into terminal.
15 | - This process will prompt you for input, such as
16 | - App's name
17 | - Whether a sidebar is required (y for yes or n for no)
18 | - The number of tabs needed in the created app (between 2 to 5)
19 | - Whether the Terms and Conditions and Onboarding screens are required.
20 | - Additional language support(localization) in app is required or not
21 | - Dark/light mode feature in settings screen is required or not
22 | - New app will be created in Desktop folder
23 |
24 | ## Demo Video
25 |
26 |
27 | https://github.com/shurutech/iOSKickstart/assets/127201055/4eb34ee0-f1c0-419a-8055-61cd0e2902ff
28 |
29 |
30 |
31 | Requirements:
32 |
33 | - Xcode 15+
34 | - MacOS
35 | - Basic iOS development knowledge
36 |
37 |
38 |
39 |
40 | Post App Creation
41 |
42 | After creating your app, follow these steps:
43 |
44 | - Open the newly created app in Xcode and check the Configuration Folder. Update the values of variables such as APP_NAME, APP_BUNDLE_ID, and BASE_URL in the Debug and Release configuration files as per your project. Note that different APP_BUNDLE_IDs are used for debug and release modes. To create a single app for both modes, ensure both bundle IDs are the same.
45 | - Update Launcher icon and Splash logo as per App display. Icons and images can be updated from Assets file located in Resources folder.
46 | - GoogleService-Info.plist Update: v0.0.2 has added feature of crashlytics and analytics with integration of firebase SDK. So you need your registered on firebase. Update values of GoogleService-Info.plist with your app's configuration.
47 | - Dummy-Use&Delete Folder: This folder contains example files used in TabScreens and for API flow use cases. For networking or API use cases, the Open Weather API is utilized for fetching weather data in the app. Use these files for reference, then delete them later.
48 |
49 |
50 |
51 | ## New Features (v0.0.2)
52 |
53 | - **UserDetails Screen**: Added UserDetails screen where users can provide details like name, country, date of birth, gender, language, etc. These details can be modified from the Settings screen.
54 |
55 | - **Dark/Light Mode Toggler**: Added a dark/light mode toggler in the Settings screen to allow users to switch between dark and light modes.
56 |
57 | - **Localization Support**: Added localization support with Hindi, Spanish, French, Chinese, and Arabic languages.
58 |
59 | - **Crashlytics and Analytics with Firebase**: Integrated Crashlytics and analytics support using Firebase for improved app monitoring and analytics.
60 |
61 | > **Note**: Before using Firebase services, you need to create your Firebase iOS app and update the values in the GoogleService-Info.plist file with your Firebase project's configuration.
62 |
63 |
64 | ## Next Steps/Features
65 | We plan to continue building after the initial release and look forward to the feedback from the community. As of now we have following features planned out for next releases.
66 | - Add different authentication methods like auth0, supabase, custom api and allow developers to select one from CLI as preferred
67 | - Accept main tab names and icons from CLI
68 | - Provide ability to choose primary and seconday app theme colors
69 | - Add different chat support tool integrations and ability to select one from CLI
70 | - Add different payment provider integrations and ability to select one from CLI
71 | - Make the app template more generic based on developers feedback
72 |
73 | ## Contribution Guidelines
74 |
75 | Thank you for your interest in contributing to our iOSKickstart project! We value the contributions of each developer and encourage you to share your ideas, improvements, and fixes with us. To ensure a smooth collaboration process, please follow these guidelines.
76 |
77 | Before you begin:
78 | - Make sure you have a GitHub account.
79 | - Familiarize yourself with the project by reading the README, exploring the issues, and understanding the app's architecture and coding standards.
80 |
81 | ## How to Contribute
82 |
83 | ### Reporting Bugs
84 |
85 | Before reporting a bug, please:
86 | - Check the issue tracker to ensure the bug hasn't already been reported.
87 | - If the issue is unreported, create a new issue, providing:
88 | - A clear title and description.
89 | - Steps to reproduce the bug.
90 | - Expected behavior and what actually happened.
91 | - Any relevant error messages or screenshots.
92 |
93 | ### Suggesting Enhancements
94 |
95 | We love to receive suggestions for enhancements! Please:
96 | - First, check if the enhancement has already been suggested.
97 | - If not, open a new issue, describing the enhancement and why it would be beneficial.
98 |
99 | ### Pull Requests
100 |
101 | Ready to contribute code? Follow these steps:
102 | 1. **Fork the repository** - Create your own fork of the project.
103 | 2. **Create a new branch** for your changes - Keep your branch focused on a single feature or bug fix.
104 | 3. **Commit your changes** - Write clear, concise commit messages that explain your changes.
105 | 4. **Follow the coding standards** - Ensure your code adheres to the coding standards used throughout the project.
106 | 5. **Write tests** - If possible, write tests to cover the new functionality or bug fix.
107 | 7. **Submit a pull request** - Provide a clear description of the problem and solution. Include the relevant issue number if applicable.
108 |
109 | ## Conduct
110 |
111 | We are committed to providing a welcoming and inspiring community for all. By participating in this project, you are expected to uphold our Code of Conduct, which promotes respect and collaboration.
112 |
113 | ## App Folders & Files
114 | APP ENTRY POINT:
115 | - LaunchApp.swift: The starting point of the app when the user clicks the app icon. It navigates to the first view using RootCoordinator, without any heavy components.
116 |
117 | **Screens Folder**
118 |
119 | - RootCoordinator: Determines the flow of screens (Splash, Authorization, Terms and Conditions, Onboarding, Main Tabs). It uses RootViewModel for logic, handling the walkthrough flow once and saving state in UserPreferences (local storage).
120 |
121 | - MainTabCoordinator: Contains all main tabs (e.g., Tab1, Tab2...). Each tab has its own content and screens (e.g., Tab1Screen, Tab2Screen...). Includes a side menu bar accessible from the top-left menu button. https://medium.com/geekculture/side-menu-in-ios-swiftui-9fe1b69fc487
122 |
123 | **ReusableViews Folder**
124 | - Contains all subviews or components used in multiple screens, e.g., PrimaryButtonView or a list's single item view.
125 |
126 | **Managers Folder**
127 | - Contains Manager classes for handling specific app-level functions, e.g., AuthenticationManager for managing Auth0 functionality (login/signup/logout).
128 |
129 | ## NETWORKING:
130 |
131 | **Network Folder**
132 | - Utilizes the Alamofire package for REST API requests. The NetworkManager class handles API requests, including success and error management. All the API endpoints are included in APIEndpoints enum.
133 |
134 | **Service Folder**
135 | - Contains classes for specific services making API calls, e.g., UserService for user-related operations. Services call the network class's request method and are used in screen ViewModel classes.
136 |
137 | **Models Folder**
138 | - Contains data structures for mapping JSON data used with API requests and responses.
139 |
140 |
141 | ## APP UTILITY:
142 |
143 | **Utils Folder**
144 | - Contains utility classes/structs for app-level use, such as Fonts, Colors, Constants, etc. Includes KeyChainStorage for encrypted local data storage and UserPreferences for unencrypted local storage.
145 |
146 | **Resources Folder**
147 | - All the resource files like images, fonts, strings are kept in this folder.
148 | - Images/icons for different device resolutions, App icon, colors for different themes like Dark/Light are managed in Assets file.
149 | - Localizable have static Texts used in app. Also used to create static app strings in different languages.
150 |
151 |
152 | **Configuration Folder**
153 | - Contains app configuration files for Debug and Release modes, including environment-specific variables. These variables are mapped in the Info.plist file, allowing changes to app name, icon, etc., for different configurations.
154 | - For more details on managing Xcode configurations: https://www.appcoda.com/xcconfig-guide/
155 |
156 |
157 | ## Questions?
158 |
159 | Feel free to contact the project maintainers if you have any questions or need further guidance on contributing.
160 |
161 | Thank you for contributing to our iOSKickstart project! Your efforts help make our project better for everyone.
162 |
163 | ## License
164 | iOSKickstart is [MIT licensed](https://github.com/shurutech/iOSKickstart/blob/main/LICENSE).
165 |
--------------------------------------------------------------------------------
/SwiftAppTemplate.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftAppTemplate.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftAppTemplate.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "abseil-cpp-binary",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/google/abseil-cpp-binary.git",
7 | "state" : {
8 | "revision" : "748c7837511d0e6a507737353af268484e1745e2",
9 | "version" : "1.2024011601.1"
10 | }
11 | },
12 | {
13 | "identity" : "alamofire",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/Alamofire/Alamofire",
16 | "state" : {
17 | "revision" : "3dc6a42c7727c49bf26508e29b0a0b35f9c7e1ad",
18 | "version" : "5.8.1"
19 | }
20 | },
21 | {
22 | "identity" : "app-check",
23 | "kind" : "remoteSourceControl",
24 | "location" : "https://github.com/google/app-check.git",
25 | "state" : {
26 | "revision" : "7d2688de038d5484866d835acb47b379722d610e",
27 | "version" : "10.19.0"
28 | }
29 | },
30 | {
31 | "identity" : "firebase-ios-sdk",
32 | "kind" : "remoteSourceControl",
33 | "location" : "https://github.com/firebase/firebase-ios-sdk.git",
34 | "state" : {
35 | "revision" : "42eae77a0af79e9c3f41df04a23c76f05cfdda77",
36 | "version" : "10.24.0"
37 | }
38 | },
39 | {
40 | "identity" : "googleappmeasurement",
41 | "kind" : "remoteSourceControl",
42 | "location" : "https://github.com/google/GoogleAppMeasurement.git",
43 | "state" : {
44 | "revision" : "51ba746a9d51a4bd0774b68499b0c73ef6e8570d",
45 | "version" : "10.24.0"
46 | }
47 | },
48 | {
49 | "identity" : "googledatatransport",
50 | "kind" : "remoteSourceControl",
51 | "location" : "https://github.com/google/GoogleDataTransport.git",
52 | "state" : {
53 | "revision" : "a637d318ae7ae246b02d7305121275bc75ed5565",
54 | "version" : "9.4.0"
55 | }
56 | },
57 | {
58 | "identity" : "googleutilities",
59 | "kind" : "remoteSourceControl",
60 | "location" : "https://github.com/google/GoogleUtilities.git",
61 | "state" : {
62 | "revision" : "26c898aed8bed13b8a63057ee26500abbbcb8d55",
63 | "version" : "7.13.1"
64 | }
65 | },
66 | {
67 | "identity" : "grpc-binary",
68 | "kind" : "remoteSourceControl",
69 | "location" : "https://github.com/google/grpc-binary.git",
70 | "state" : {
71 | "revision" : "e9fad491d0673bdda7063a0341fb6b47a30c5359",
72 | "version" : "1.62.2"
73 | }
74 | },
75 | {
76 | "identity" : "gtm-session-fetcher",
77 | "kind" : "remoteSourceControl",
78 | "location" : "https://github.com/google/gtm-session-fetcher.git",
79 | "state" : {
80 | "revision" : "0382ca27f22fb3494cf657d8dc356dc282cd1193",
81 | "version" : "3.4.1"
82 | }
83 | },
84 | {
85 | "identity" : "interop-ios-for-google-sdks",
86 | "kind" : "remoteSourceControl",
87 | "location" : "https://github.com/google/interop-ios-for-google-sdks.git",
88 | "state" : {
89 | "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648",
90 | "version" : "100.0.0"
91 | }
92 | },
93 | {
94 | "identity" : "keychain-swift",
95 | "kind" : "remoteSourceControl",
96 | "location" : "https://github.com/evgenyneu/keychain-swift.git",
97 | "state" : {
98 | "revision" : "d108a1fa6189e661f91560548ef48651ed8d93b9",
99 | "version" : "20.0.0"
100 | }
101 | },
102 | {
103 | "identity" : "leveldb",
104 | "kind" : "remoteSourceControl",
105 | "location" : "https://github.com/firebase/leveldb.git",
106 | "state" : {
107 | "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1",
108 | "version" : "1.22.5"
109 | }
110 | },
111 | {
112 | "identity" : "nanopb",
113 | "kind" : "remoteSourceControl",
114 | "location" : "https://github.com/firebase/nanopb.git",
115 | "state" : {
116 | "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1",
117 | "version" : "2.30910.0"
118 | }
119 | },
120 | {
121 | "identity" : "promises",
122 | "kind" : "remoteSourceControl",
123 | "location" : "https://github.com/google/promises.git",
124 | "state" : {
125 | "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac",
126 | "version" : "2.4.0"
127 | }
128 | },
129 | {
130 | "identity" : "swift-protobuf",
131 | "kind" : "remoteSourceControl",
132 | "location" : "https://github.com/apple/swift-protobuf.git",
133 | "state" : {
134 | "revision" : "9f0c76544701845ad98716f3f6a774a892152bcb",
135 | "version" : "1.26.0"
136 | }
137 | }
138 | ],
139 | "version" : 2
140 | }
141 |
--------------------------------------------------------------------------------
/SwiftAppTemplate.xcodeproj/xcshareddata/xcschemes/SwiftAppTemplate(Debug).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 |
--------------------------------------------------------------------------------
/SwiftAppTemplate.xcodeproj/xcshareddata/xcschemes/SwiftAppTemplate(Release).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 |
--------------------------------------------------------------------------------
/SwiftAppTemplate.xcodeproj/xcuserdata/shuru.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Promises (Playground) 1.xcscheme
8 |
9 | isShown
10 |
11 | orderHint
12 | 3
13 |
14 | Promises (Playground) 2.xcscheme
15 |
16 | isShown
17 |
18 | orderHint
19 | 4
20 |
21 | Promises (Playground).xcscheme
22 |
23 | isShown
24 |
25 | orderHint
26 | 2
27 |
28 | SwiftAppTemplate(Debug).xcscheme_^#shared#^_
29 |
30 | orderHint
31 | 0
32 |
33 | SwiftAppTemplate(Release).xcscheme_^#shared#^_
34 |
35 | orderHint
36 | 1
37 |
38 |
39 | SuppressBuildableAutocreation
40 |
41 | 6B6775882B4537E20014DAC3
42 |
43 | primary
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Configuration/AppConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppConfig.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 08/01/24.
6 | //
7 | import Foundation
8 |
9 | enum AppConfig {
10 |
11 | private static let configDict: [String: Any] = {
12 | guard let dict = Bundle.main.infoDictionary else {
13 | fatalError("info.plist not found")
14 | }
15 |
16 | return dict
17 | }()
18 |
19 | static let BASE_URL: URL = {
20 | guard let urlString = configDict[Constants.BASE_URL] as? String else {
21 | fatalError("base url not found")
22 | }
23 |
24 | guard let url = URL(string: urlString) else {
25 | fatalError("invalid url")
26 | }
27 |
28 | return url
29 | }()
30 |
31 | static let EXAMPLE_KEY : String = {
32 | guard let key = configDict[Constants.EXAMPLE_KEY] as? String else {
33 | fatalError("example key not found")
34 | }
35 |
36 | return key
37 | }()
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Configuration/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Debug.xcconfig
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 08/01/24.
6 | //
7 |
8 | // Configuration settings file format documentation can be found at:
9 | // https://help.apple.com/xcode/#/dev745c5c974
10 |
11 | APP_NAME = SwiftAppTemplate Debug
12 | APP_ICON = AppIconDebug
13 | APP_BUNDLE_ID = com.shurutech.SwiftAppTemplateDebug
14 | APP_VERSION = 1.0.0
15 |
16 | BASE_URL = https:/$()/api.openweathermap.org
17 | EXAMPLE_KEY = 123455_debug
18 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Configuration/Release.xcconfig:
--------------------------------------------------------------------------------
1 | //
2 | // Release.xcconfig
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 08/01/24.
6 | //
7 |
8 | // Configuration settings file format documentation can be found at:
9 | // https://help.apple.com/xcode/#/dev745c5c974
10 |
11 | APP_NAME = SwiftAppTemplate
12 | APP_ICON = AppIconRelease
13 | APP_BUNDLE_ID = com.shurutech.SwiftAppTemplate
14 | APP_VERSION = 1.0.0
15 |
16 | BASE_URL = https:/$()/api.openweathermap.org
17 | EXAMPLE_KEY = 123455_release
18 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Dummy-Use&Delete/APIUsecase/WeatherData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WeatherData.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 11/01/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct WeatherData: Codable, Hashable {
11 |
12 | let coord: Coordinates
13 | let weather: [Weather]
14 | let base: String
15 | let main: Main
16 | let visibility: Int
17 | let wind: Wind
18 | let rain: Rain?
19 | let clouds: Clouds
20 | let dt: Int
21 | let sys: Sys
22 | let timezone: Int
23 | let id: Int
24 | let name: String
25 | let cod: Int
26 | }
27 |
28 | // Sub-structures
29 | struct Coordinates: Codable, Hashable {
30 | let lon: Double
31 | let lat: Double
32 | }
33 |
34 | struct Weather: Codable, Hashable {
35 | let id: Int
36 | let main: String
37 | let description: String
38 | let icon: String
39 | }
40 |
41 | struct Main: Codable, Hashable {
42 | let temp: Double
43 | let feelsLike: Double
44 | let tempMin: Double
45 | let tempMax: Double
46 | let pressure: Int
47 | let humidity: Int
48 | let seaLevel: Int?
49 | let grndLevel: Int?
50 |
51 | enum CodingKeys: String, CodingKey {
52 | case temp, pressure, humidity
53 | case feelsLike = "feels_like"
54 | case tempMin = "temp_min"
55 | case tempMax = "temp_max"
56 | case seaLevel = "sea_level"
57 | case grndLevel = "grnd_level"
58 | }
59 | }
60 |
61 | struct Wind: Codable, Hashable {
62 | let speed: Double
63 | let deg: Int
64 | let gust: Double?
65 | }
66 |
67 | struct Rain: Codable, Hashable {
68 | let oneHour: Double
69 |
70 | enum CodingKeys: String, CodingKey {
71 | case oneHour = "1h"
72 | }
73 | }
74 |
75 | struct Clouds: Codable, Hashable {
76 | let all: Int
77 | }
78 |
79 | struct Sys: Codable, Hashable {
80 | let type: Int
81 | let id: Int
82 | let country: String
83 | let sunrise: Int
84 | let sunset: Int
85 | }
86 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Dummy-Use&Delete/APIUsecase/WeatherDetailScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WeatherDetailScreen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 10/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct WeatherDetailScreen: View {
11 | var weatherData: WeatherData
12 | @Environment(\.dismiss) var dismiss
13 |
14 | var body: some View {
15 | VStack{
16 | Header(text: LocalizedStringKey(getLocalString(weatherData.name) + " " + getLocalString(AppStrings.Weather)), hasBackButton: true, onBackArrowClick: {
17 | dismiss()
18 | })
19 | Spacer()
20 | infoText(text: AppStrings.TemperatureIs, info: "\(kelvinToCelsius(kelvinTemp: weatherData.main.temp).description) °C")
21 | infoText(text: AppStrings.RealFeelIs, info: "\(kelvinToCelsius(kelvinTemp: weatherData.main.feelsLike).description) °C")
22 | infoText(text: AppStrings.MaxItWillGoIs, info: "\(kelvinToCelsius(kelvinTemp: weatherData.main.tempMax).description) °C")
23 | infoText(text: AppStrings.MinItWillFallIs, info: "\(kelvinToCelsius(kelvinTemp: weatherData.main.tempMin).description) °C")
24 | infoText(text: AppStrings.YouCanSeeAsFarAs, info: "\(weatherData.visibility/1000) km")
25 | infoText(text: AppStrings.ThePressureYouBeFeelingIs, info: "\(weatherData.main.pressure) hectopascal")
26 | Spacer()
27 | }
28 | .padding()
29 | .onAppear {
30 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
31 | }
32 | }
33 |
34 | func infoText(text: LocalizedStringKey, info: String) -> some View{
35 | VStack{
36 | (Text(text) + Text(" \(info)"))
37 | .font(.notoSansBold16)
38 | .frame(maxWidth: .infinity)
39 | Divider()
40 | }
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Dummy-Use&Delete/APIUsecase/WeatherScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WeatherScreen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 03/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct WeatherScreen: View {
11 | @State var isInfoScreenPresented = false
12 | @StateObject var viewModel: WeatherViewModel = WeatherViewModel()
13 |
14 | var body: some View {
15 | VStack{
16 | ScrollView(showsIndicators: false){
17 | VStack(spacing: 20){
18 | if(!viewModel.isDataLoading){
19 | ForEach(viewModel.weatherData, id: \.self){ data in
20 | CardView(title: getLocalString(data.name) ,
21 | subTitle: makeDescription(weatherItem: data))
22 | .onTapGesture {
23 | viewModel.selectedCardIndex = viewModel.weatherData.firstIndex(of: data)!
24 | isInfoScreenPresented = true
25 | }
26 | }.fullScreenCover(isPresented: $isInfoScreenPresented, content: {
27 | WeatherDetailScreen(weatherData: viewModel.weatherData[viewModel.selectedCardIndex])
28 | })
29 | }
30 | }
31 | .padding(.top)
32 | .padding(.horizontal, 20)
33 | }
34 | .alert(isPresented: Binding(
35 | get: { viewModel.apiError != nil },
36 | set: { _ in viewModel.apiError = nil }
37 | )) {
38 | Alert(
39 | title: Text(AppStrings.Error),
40 | message: Text(viewModel.apiError?.localizedDescription ?? "\(AppStrings.ErrorMessage)"),
41 | dismissButton: .default(Text(AppStrings.OK))
42 | )
43 | }
44 | }
45 | .frame(maxWidth: .infinity)
46 | .loader(viewModel.isDataLoading)
47 | .onAppear{
48 | getWeatherData()
49 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
50 | }
51 | }
52 |
53 | //MARK: - Functions
54 |
55 | private func getWeatherData() {
56 | Task { @MainActor in
57 | await viewModel.getWeatherData()
58 | }
59 | }
60 |
61 | private func makeDescription(weatherItem: WeatherData) -> String{
62 | var description = ""
63 |
64 | description += getLocalString(AppStrings.Summary) + ": " + "\(weatherItem.weather[0].description) \n"
65 | description += getLocalString(AppStrings.Temperature) + ": " + "\(kelvinToCelsius(kelvinTemp: weatherItem.main.temp)) °C \n"
66 | description += getLocalString(AppStrings.Humidity) + ": " + "\(weatherItem.main.humidity)%"
67 |
68 | return description
69 | }
70 |
71 | }
72 |
73 | #Preview {
74 | WeatherScreen()
75 | }
76 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Dummy-Use&Delete/APIUsecase/WeatherService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WeatherService.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 10/01/24.
6 | //
7 |
8 | import Foundation
9 |
10 | class WeatherService {
11 |
12 | func getWeather(city: String, appId: String) async throws -> WeatherData {
13 | try await NetworkManager.shared.request(.getWeather(city: city, appId: appId), type: WeatherData.self)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Dummy-Use&Delete/APIUsecase/WeatherViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WeatherViewModel.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 11/01/24.
6 | //
7 |
8 | import Foundation
9 |
10 | @MainActor
11 | class WeatherViewModel : ObservableObject {
12 | @Published var weatherData: [WeatherData] = []
13 | @Published var isDataLoading = false
14 | @Published var selectedCardIndex = 0
15 | @Published var apiError: AppError?
16 | let cities = ["Delhi", "Jaipur", "Mumbai", "Chennai", "Bengaluru", "Kolkata"]
17 |
18 | func getWeatherData() async {
19 | isDataLoading = true
20 | weatherData.removeAll()
21 | for city in cities {
22 | do {
23 | let data = try await WeatherService().getWeather(city: city, appId: Constants.weatherAppId)
24 | weatherData.append(data)
25 | }
26 | catch {
27 | ErrorHandler.logError(message: "No weather data", error: error)
28 | apiError = error as? AppError
29 | }
30 | }
31 | isDataLoading = false
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Dummy-Use&Delete/EditUserDetailsScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EditUserDetailsScreen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 16/04/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct EditUserDetailsScreen: View {
11 | @State private var name: String = ""
12 | @State private var email: String = ""
13 | @State private var selectedGender: Gender = .male
14 | @State private var dateOfBirth = Date()
15 | @State private var showingDatePicker = false
16 | @State private var selectedCountry: String = ""
17 | private var startDate = Calendar.current.date(byAdding: .year, value: -100, to: Date()) ?? Date()
18 | @Environment(\.dismiss) var dismiss
19 | @State private var showConfirmation: Bool = false
20 | @State private var isConfirmationGiven: Bool = false
21 |
22 | var body: some View {
23 | ZStack {
24 | VStack {
25 | Header(text: AppStrings.UpdateUserDetails, hasBackButton: true, onBackArrowClick: {
26 | AnalyticsManager.logButtonClickEvent(buttonType: .back, label: "")
27 | dismiss()
28 | })
29 |
30 | CustomTitleTextFieldView(label: AppStrings.Name, placeholder: AppStrings.NamePlaceHolder, inputText: $name)
31 | .padding(.vertical, 20)
32 |
33 | CustomTitleTextFieldView(label: AppStrings.Email, placeholder: AppStrings.EmailPlaceHolder, inputText: $email)
34 |
35 | GenderView(selectedGender: $selectedGender)
36 | .padding(.vertical, 20)
37 |
38 | CustomTitleTextFieldView(label: AppStrings.DateOfBirth, placeholder: AppStrings.DateOfBirthPlaceHolder, inputText: Binding.constant(formatDate(dateOfBirth)))
39 | .onTapGesture {
40 | self.showingDatePicker = true
41 | }
42 | .popover(isPresented: $showingDatePicker, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) {
43 | DatePickerPopover(isPresented: $showingDatePicker, dateSelection: $dateOfBirth, title: AppStrings.DateOfBirthPlaceHolder, doneButtonLabel: AppStrings.Done)
44 | }
45 |
46 |
47 | CountryView(selectedCountry: $selectedCountry)
48 | .padding(.vertical, 20)
49 |
50 | CustomTitleTextFieldView(label: AppStrings.SelectLanguage, placeholder: AppStrings.SelectLanguagePlaceHolder, inputText: Binding.constant(userLanguage))
51 | .onTapGesture {
52 | openDeviceSettings()
53 | }
54 |
55 | Spacer()
56 |
57 | TextButton(onClick: {
58 | if hasEnteredAllDetails() {
59 | AnalyticsManager.logButtonClickEvent(buttonType: .primary, label: "Update")
60 | let userProperties = ["name": name, "email": email, "gender": selectedGender, "country": selectedCountry, "language": userLanguage] as [String : Any]
61 | AnalyticsManager.logCustomEvent(eventType: .updateUser, properties: userProperties)
62 | showConfirmation = true
63 | }
64 | }, text: AppStrings.Update)
65 | }
66 | .padding(20)
67 |
68 | Spacer()
69 |
70 | bottomSheet
71 | }
72 | .onChange(of: isConfirmationGiven) { isConfirmationGiven in
73 | if(isConfirmationGiven){
74 | saveUserDetails(name: name, email: email, dob: dateOfBirth, gender: selectedGender, country: selectedCountry, language: userLanguage)
75 | dismiss()
76 | }
77 | }
78 | .onAppear(){
79 | initializeDetails()
80 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
81 | }
82 |
83 | }
84 |
85 |
86 | private var bottomSheet: some View {
87 | CustomBottomSheetView(isOpen: $showConfirmation, maxHeight: 250, content: {
88 | ConfirmationSheet(isConfirmationGiven: $isConfirmationGiven, isOpen: $showConfirmation, title: AppStrings.SaveUserInfoBottomSheetTitle, subTitle: AppStrings.SaveUserInfoBottomSheetSubTitle)
89 | })
90 | }
91 |
92 | private func hasEnteredAllDetails() -> Bool {
93 | return !name.isEmpty && !selectedGender.rawValue.isEmpty && !dateOfBirth.description.isEmpty && !selectedCountry.isEmpty
94 | }
95 |
96 | private func initializeDetails() {
97 | let user = UserPreferences.shared.getUser()
98 | name = user?.name ?? ""
99 | email = user?.email ?? ""
100 | selectedGender = Gender(rawValue: user?.gender ?? Gender.male.rawValue) ?? Gender.male
101 | let dob = user?.dob ?? formatDate(Date())
102 | dateOfBirth = dob.formattedDate(format: "dd-MM-yyyy") ?? Date()
103 | selectedCountry = user?.country ?? ""
104 | }
105 |
106 | }
107 |
108 | #Preview {
109 | EditUserDetailsScreen()
110 | }
111 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Dummy-Use&Delete/ProfileScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ProfileScreen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 03/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ProfileScreen: View {
11 | @State var presentSettingsPage = false
12 | @State private var userName: String = ""
13 | @State private var userEmail: String = ""
14 |
15 | var body: some View {
16 | NavigationView{
17 | VStack{
18 | UserInfoView(name: $userName, email: $userEmail)
19 | HStack{
20 | Image(systemName: "gearshape")
21 | .resizable()
22 | .frame(width: 20, height: 20)
23 | Text(AppStrings.Settings)
24 | .font(.notoSansRegular16)
25 | }
26 | .onTapGesture {
27 | AnalyticsManager.logButtonClickEvent(buttonType: .secondary, label: "Settings")
28 | presentSettingsPage = true
29 | }
30 | .fullScreenCover(isPresented: $presentSettingsPage, onDismiss: {
31 | updateUserInfo()
32 | }, content: {
33 | SettingsScreen()
34 | })
35 | .foregroundColor(.secondaryLightBlue)
36 | Spacer()
37 | }.padding(.horizontal, 25)
38 | }
39 | .onAppear{
40 | updateUserInfo()
41 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
42 | }
43 | }
44 |
45 | func updateUserInfo(){
46 | userName = UserPreferences.shared.getUser()?.name ?? ""
47 | userEmail = UserPreferences.shared.getUser()?.email ?? ""
48 | }
49 | }
50 |
51 | #Preview {
52 | ProfileScreen()
53 | }
54 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Dummy-Use&Delete/SettingsScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingsView.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 08/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SettingsScreen: View {
11 |
12 | // MARK: - Attributes
13 | @State var presentEditInfoScreen = false
14 | @StateObject var viewModel: SettingsViewModel = SettingsViewModel()
15 | @Environment(\.dismiss) var dismiss
16 | @State private var selectedMode = Preferences.appearanceMode
17 |
18 |
19 | // MARK: - Views
20 |
21 | var body: some View {
22 | ZStack{
23 | VStack(spacing: 30){
24 |
25 | Header(text: AppStrings.Settings, hasBackButton: true, onBackArrowClick: {dismiss()})
26 |
27 | userDetailsView
28 |
29 | updateButtonView
30 |
31 | AppearanceSelectionView(selectedMode: $selectedMode)
32 | .padding(.top, 20)
33 |
34 | bottomButtons
35 |
36 | }.padding()
37 |
38 | if(viewModel.currentBottomSheetType != nil){
39 | bottomSheet
40 | }
41 | }
42 | .navigationBarBackButtonHidden(true)
43 | .onAppear{
44 | viewModel.setUp()
45 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
46 | }
47 | }
48 |
49 | var userDetailsView: some View {
50 | VStack(spacing: 20) {
51 | TitleValueView(title: AppStrings.Name, value: viewModel.userName)
52 |
53 | TitleValueView(title: AppStrings.Email, value: viewModel.userEmail)
54 |
55 | TitleValueView(title: AppStrings.Gender, value: getLocalString(viewModel.gender))
56 |
57 | TitleValueView(title: AppStrings.DateOfBirth, value: viewModel.dob)
58 |
59 | TitleValueView(title: AppStrings.Country, value: getLocalString(viewModel.country))
60 |
61 | TitleValueView(title: AppStrings.Language, value: userLanguage)
62 | }
63 | }
64 |
65 | var updateButtonView: some View {
66 | TextButton(onClick: {
67 | AnalyticsManager.logButtonClickEvent(buttonType: .primary, label: "Update")
68 | presentEditInfoScreen = true
69 | }, text: AppStrings.Update)
70 | .fullScreenCover(isPresented: $presentEditInfoScreen, onDismiss: {
71 | viewModel.setUp()
72 | }, content: {
73 | EditUserDetailsScreen()
74 | })
75 | }
76 |
77 | var bottomButtons: some View{
78 | VStack{
79 | Spacer()
80 | HStack{
81 | TextButton(onClick: {
82 | AnalyticsManager.logButtonClickEvent(buttonType: .secondary, label: "Logout")
83 | viewModel.currentBottomSheetType = .logout
84 | }, text: AppStrings.Logout, style: .outline, color: .orange)
85 | TextButton(onClick: {
86 | AnalyticsManager.logButtonClickEvent(buttonType: .secondary, label: "Delete Account")
87 | viewModel.currentBottomSheetType = .delete
88 | }, text: AppStrings.DeleteAccount, style: .outline, color: .red)
89 | }
90 | }
91 | }
92 |
93 | var bottomSheet: some View{
94 |
95 | @State var isOpen = Binding(
96 | get: { viewModel.currentBottomSheetType != nil },
97 | set: { if !$0 { viewModel.currentBottomSheetType = nil } }
98 | )
99 |
100 | return CustomBottomSheetView(isOpen: isOpen, maxHeight: viewModel.currentBottomSheetType!.sheetSize, content: {
101 | if viewModel.currentBottomSheetType != nil {
102 | ConfirmationSheet(isConfirmationGiven: $viewModel.isConfirmationGiven, isOpen: isOpen, title: viewModel.currentBottomSheetType!.title, subTitle: viewModel.currentBottomSheetType!.subTitle)
103 | }
104 | })
105 | }
106 | }
107 |
108 | struct TitleValueView: View {
109 | let title: LocalizedStringKey
110 | let value: String
111 | var body: some View {
112 | HStack {
113 | Text(title)
114 | Spacer()
115 | Text(value)
116 | }
117 | .foregroundColor(.text)
118 | }
119 | }
120 |
121 | struct AppearanceSelectionView: View {
122 | @Binding var selectedMode: AppearanceMode
123 |
124 | var body: some View {
125 | HStack {
126 | Text(AppStrings.Appearance)
127 | Spacer()
128 | Picker(AppStrings.Appearance, selection: $selectedMode) {
129 | Text(AppStrings.Light).tag(AppearanceMode.light)
130 | Text(AppStrings.Dark).tag(AppearanceMode.dark)
131 | }
132 | .pickerStyle(SegmentedPickerStyle())
133 | .onChange(of: selectedMode) { newValue in
134 | Preferences.appearanceMode = newValue
135 | }
136 | }
137 | .foregroundColor(.text)
138 | }
139 | }
140 |
141 |
142 | #Preview {
143 | SettingsScreen()
144 | }
145 |
146 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Dummy-Use&Delete/SettingsViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SettingsViewModel.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 16/01/24.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | class SettingsViewModel: ObservableObject{
12 |
13 | enum BottomSheet{
14 | case logout
15 | case delete
16 |
17 | var title: LocalizedStringKey {
18 | switch self {
19 | case .logout:
20 | AppStrings.LogoutBottomSheetTitle
21 | case .delete:
22 | AppStrings.DeleteAccountBottomSheetTitle
23 | }
24 | }
25 |
26 | var subTitle: LocalizedStringKey {
27 | switch self {
28 | case .logout:
29 | AppStrings.LogoutBottomSheetSubTitle
30 | case .delete:
31 | AppStrings.DeleteAccountBottomSheetSubTitle
32 | }
33 | }
34 |
35 | var sheetSize: CGFloat {
36 | switch self {
37 | case .logout:
38 | 300
39 | case .delete:
40 | 300
41 | }
42 | }
43 |
44 | }
45 |
46 | @Published var userName: String = ""
47 | @Published var userEmail: String = ""
48 | @Published var gender: String = ""
49 | @Published var dob: String = ""
50 | @Published var country = ""
51 | @Published var isConfirmationGiven = false{
52 | didSet{
53 | if(isConfirmationGiven){
54 | handleConfirmation()
55 | currentBottomSheetType = nil
56 | }
57 | }
58 | }
59 | @Published var currentBottomSheetType: BottomSheet?
60 |
61 |
62 | func handleConfirmation() {
63 | switch currentBottomSheetType {
64 | case .logout:
65 | performLogout()
66 | case .delete:
67 | deleteAccount()
68 | case nil:
69 | ErrorHandler.logError(message: "No bottom sheet found", error: AppError.genericError)
70 | }
71 | }
72 |
73 | func performLogout() {
74 | Task { @MainActor in
75 | do {
76 | try await AuthenticationManager.shared.logout()
77 | }
78 | catch {
79 | ErrorHandler.logError(message: "Error while logging out.", error: error)
80 | }
81 | }
82 | }
83 |
84 | func deleteAccount() {
85 | Task { @MainActor in
86 | do {
87 | try await AuthenticationManager.shared.deleteAccount()
88 | }
89 | catch {
90 | ErrorHandler.logError(message: "Error while deleting account.", error: error)
91 | }
92 | }
93 | }
94 |
95 |
96 | func setUp(){
97 | let user = UserPreferences.shared.getUser()
98 | userName = user?.name ?? ""
99 | userEmail = user?.email ?? ""
100 | gender = user?.gender ?? Gender.male.rawValue
101 | dob = user?.dob ?? ""
102 | country = user?.country ?? ""
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Dummy-Use&Delete/WebScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebScreen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 03/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct WebScreen: View {
11 | @State private var isLoading = true
12 | @State private var showError = false
13 | var urlString = "https://shurutech.com/"
14 |
15 | var body: some View {
16 | VStack{
17 | WebView(urlString: urlString, isLoading: $isLoading, showError: $showError)
18 | }
19 | .loader(isLoading)
20 | .alert(isPresented: $showError){
21 | Alert(
22 | title: Text("Error"),
23 | message: Text("Cannot load the webpage. Something went wrong."),
24 | dismissButton: .default(Text("OK"))
25 | )
26 | }
27 | }
28 | }
29 |
30 | #Preview {
31 | WebScreen()
32 | }
33 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/GoogleService-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | API_KEY
6 | AIzaSyD2M6N-TDYpDaPGgoS08Yk4SfGW4v1uA2k
7 | GCM_SENDER_ID
8 | dummy
9 | PLIST_VERSION
10 | 1
11 | BUNDLE_ID
12 | com.shurutech.SwiftAppTemplateDebug
13 | PROJECT_ID
14 | dummy
15 | STORAGE_BUCKET
16 | dummy
17 | IS_ADS_ENABLED
18 |
19 | IS_ANALYTICS_ENABLED
20 |
21 | IS_APPINVITE_ENABLED
22 |
23 | IS_GCM_ENABLED
24 |
25 | IS_SIGNIN_ENABLED
26 |
27 | GOOGLE_APP_ID
28 | 1:1234567890:ios:abcd1234abcd1234
29 |
30 |
31 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/LaunchApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LaunchApp.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 03/01/24.
6 | //
7 |
8 | import SwiftUI
9 | import Firebase
10 |
11 |
12 | @main
13 | struct LaunchApp: App {
14 | @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
15 |
16 |
17 | init() {
18 | Preferences.applyAppearance(Preferences.appearanceMode)
19 | }
20 |
21 | var body: some Scene {
22 | WindowGroup {
23 | RootCoordinator()
24 | }
25 | }
26 | }
27 |
28 | class AppDelegate: UIResponder, UIApplicationDelegate {
29 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
30 | FirebaseApp.configure()
31 | return true
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Managers/AnalyticsManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnalyticsManager.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 25/04/24.
6 | //
7 |
8 | import Foundation
9 | import FirebaseAnalytics
10 |
11 | enum ButtonType: String {
12 | case primary = "Primary"
13 | case secondary = "Secondary"
14 | case text = "Text"
15 | case back = "Back"
16 | case navigationTab = "Navigation Tab"
17 | }
18 |
19 | enum EventType: String {
20 | case login = "login"
21 | case updateUser = "update_user"
22 | case buttonClick = "button_click"
23 | }
24 |
25 | struct BaseEvent {
26 | let eventType: String
27 | let eventProperties: [String: Any]
28 | }
29 |
30 | class AnalyticsManager {
31 | static let shared = AnalyticsManager()
32 |
33 | private init() {}
34 |
35 | static func logCustomEvent(eventType: EventType, properties: [String: Any]) {
36 | let event = BaseEvent(eventType: eventType.rawValue, eventProperties: properties)
37 | shared.track(event: event)
38 | }
39 |
40 | func track(event: BaseEvent) {
41 | Analytics.logEvent(event.eventType, parameters: event.eventProperties)
42 | }
43 |
44 | static func logButtonClickEvent(buttonType: ButtonType, label: String) {
45 | let event = BaseEvent(
46 | eventType: EventType.buttonClick.rawValue,
47 | eventProperties: ["button_type": buttonType.rawValue, "label": label]
48 | )
49 | AnalyticsManager.shared.track(event: event)
50 | }
51 |
52 | static func logScreenView(screenName: String, screenClass: String? = nil) {
53 | let parameters: [String: Any] = [
54 | AnalyticsParameterScreenName: screenName,
55 | AnalyticsParameterScreenClass: screenClass ?? screenName
56 | ]
57 | Analytics.logEvent(AnalyticsEventScreenView, parameters: parameters)
58 | }
59 | }
60 |
61 |
62 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Managers/AuthenticationManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthenticationManager.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 03/01/24.
6 | //
7 |
8 | import Foundation
9 |
10 | @MainActor
11 | class AuthenticationManager : ObservableObject {
12 | static let shared = AuthenticationManager()
13 | private init() {}
14 |
15 | // MARK: - Attributes
16 | @Published var isAuthenticated = UserPreferences.shared.isAuthenticated
17 |
18 |
19 | // MARK: - Functions
20 |
21 | func login(user: User) async throws {
22 | let authToken = "dummy_token_qwertyuiopasdfghjklzxcvbnm"
23 |
24 | if(KeyChainStorage.shared.setAuthToken(authToken)){
25 | UserPreferences.shared.saveUser(user: User(email: user.email, password: user.password))
26 | KeyChainStorage.shared.setPassword(user.password)
27 | UserPreferences.shared.isAuthenticated = true
28 | isAuthenticated = true
29 | AnalyticsManager.logCustomEvent(eventType: EventType.login, properties: ["email": user.email])
30 | }
31 | else {
32 | ErrorHandler.logError(message: "Error while login", error: AppError.tokenStoringFailed)
33 | }
34 | }
35 |
36 | func logout() async throws {
37 | UserPreferences.shared.isAuthenticated = false
38 | isAuthenticated = false
39 | }
40 |
41 | func deleteAccount() async throws {
42 | isAuthenticated = false
43 | UserPreferences.shared.deleteAllUserDefaults()
44 | KeyChainStorage.shared.deleteAllKey()
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Models/User.swift:
--------------------------------------------------------------------------------
1 | //
2 | // User.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Ashok Choudhary on 03/01/24.
6 | //
7 |
8 | import Foundation
9 |
10 | struct User: Codable{
11 | let name: String?
12 | let email: String
13 | let password: String
14 | let gender: String?
15 | let dob: String?
16 | let country: String?
17 | let language: String?
18 |
19 |
20 | init(email: String, password: String) {
21 | self.email = email
22 | self.password = password
23 | self.name = nil
24 | self.dob = nil
25 | self.gender = nil
26 | self.country = nil
27 | self.language = nil
28 | }
29 |
30 | init(name: String, email: String, password: String, dob: String, gender: String, country: String, language: String) {
31 | self.email = email
32 | self.password = password
33 | self.name = name
34 | self.dob = dob
35 | self.gender = gender
36 | self.country = country
37 | self.language = language
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Network/APIEndpoints.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ApiEndpoint.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Ashok Choudhary on 03/01/24.
6 | //
7 | import Alamofire
8 |
9 | enum APIEndpoints {
10 | case getWeather(city: String, appId: String)
11 | case updateUser(user: User)
12 |
13 | var data: (HTTPMethod, String) {
14 | switch self {
15 | case .getWeather: return (.get, "/data/2.5/weather")
16 | case .updateUser: return (.put, "/api/v1/users")
17 | }
18 | }
19 |
20 | // MARK: - Parameters
21 |
22 | var urlParameters: [String:String?] {
23 | switch self {
24 | case .getWeather(let city, let appId):
25 | return ["q": city, "appid": appId]
26 | default:
27 | return [:]
28 | }
29 | }
30 |
31 | var parameters: Encodable? {
32 | switch self{
33 | case .updateUser(let user):
34 | return user
35 | default:
36 | return nil
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Network/AppError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APIError.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 10/01/24.
6 | //
7 |
8 | import Foundation
9 |
10 |
11 | enum AppError: Error, Identifiable {
12 | var id: String { localizedDescription }
13 |
14 | case apiError(String)
15 | case networkError(Error)
16 | case serverError(String)
17 | case decodingError(Error)
18 | case otherError(String)
19 | case genericError
20 | case unauthorizedAccess
21 | case failedToLoadToken
22 | case tokenStoringFailed
23 |
24 |
25 | var localizedDescription: String {
26 | switch self {
27 | case .apiError(let message):
28 | return message
29 | case .networkError(let error):
30 | return "Network error: \(error.localizedDescription)"
31 | case .serverError(let message):
32 | return "Server error - \(message)"
33 | case .decodingError(let error):
34 | return "Data decoding error: \(error.localizedDescription)"
35 | case .otherError(let message):
36 | return message
37 | case .genericError:
38 | return "Something went wrong! Please try again."
39 | case .unauthorizedAccess:
40 | return "Access denied."
41 | case .failedToLoadToken:
42 | return "Something went wrong! Please try again."
43 | case .tokenStoringFailed:
44 | return "Failed to store token."
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Network/NetworkManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NetworkManager.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Ashok Choudhary on 03/01/24.
6 | //
7 | import Alamofire
8 | import Foundation
9 |
10 | struct ErrorResponse: Codable {
11 | let detail: String
12 | }
13 |
14 | class NetworkManager {
15 | static let shared = NetworkManager()
16 | private init() { }
17 |
18 | // MARK: - Attributes
19 |
20 | private let session: Session = .default
21 |
22 |
23 | // MARK: - Requests
24 |
25 | func request(_ api: APIEndpoints, type: T.Type) async throws -> T {
26 | let data = try await request(api)
27 |
28 | do {
29 | let decoder = JSONDecoder()
30 | return try decoder.decode(T.self, from: data)
31 | }
32 | catch {
33 | ErrorHandler.logError(message: "Decoding error (\(api.data.0), \(api.data.1))", error: error)
34 | throw AppError.decodingError(error)
35 | }
36 | }
37 |
38 | @discardableResult
39 | func request(_ api: APIEndpoints) async throws -> Data {
40 | try await withCheckedThrowingContinuation { continuation in
41 | request(api) { result in
42 | if case .failure(let error) = result {
43 | ErrorHandler.logError(message: "Request failed (\(api.data.0), \(api.data.1))", error: error)
44 | }
45 |
46 | continuation.resume(with: result)
47 | }
48 | }
49 | }
50 |
51 | func request(_ api: APIEndpoints, completion: @escaping (Result) -> Void) {
52 | guard let authToken = KeyChainStorage.shared.getAuthToken() else {
53 | completion(.failure(AppError.failedToLoadToken))
54 | return
55 | }
56 |
57 | // url
58 | let baseURL = AppConfig.BASE_URL.absoluteString
59 | let url = baseURL + api.data.1
60 |
61 | // parameters
62 | var parameters: [String:String] = [:]
63 |
64 | for (key, value) in api.urlParameters {
65 | if let value = value?.nilIfEmpty {
66 | parameters[key] = value
67 | }
68 | }
69 |
70 | var urlComponents = URLComponents(string: url)!
71 |
72 | // construct full url
73 | if(!parameters.isEmpty){
74 | let queryItems = parameters.map { URLQueryItem(name: $0.key, value: $0.value) }
75 | urlComponents.queryItems = queryItems
76 | }
77 |
78 | // headers
79 | var headers: HTTPHeaders = [:]
80 | headers.add(name: "Authorization", value: "Bearer \(authToken)")
81 |
82 | session.request(urlComponents, method: api.data.0, parameters: getParamsEncoded(params: api.parameters), encoding: JSONEncoding.default, headers: headers)
83 | .validate()
84 | .responseData { response in
85 | switch response.result {
86 | case .success(let data):
87 | completion(.success(data))
88 | case .failure(let error):
89 | self.handleFailure(error: error, response: response, completion: completion)
90 | }
91 | }
92 | #if DEBUG
93 | .cURLDescription { print("---\n\($0)\n---") }
94 | #endif
95 | }
96 |
97 |
98 | // MARK: - Helpers
99 |
100 | private func handleFailure(error: Error, response: AFDataResponse, completion: @escaping (Result) -> Void) {
101 | if let httpResponse = response.response, httpResponse.statusCode == 401 {
102 | self.handleUnauthorizedAccessError()
103 | completion(.failure(AppError.unauthorizedAccess))
104 | }
105 | else if let data = response.data {
106 | do {
107 | let errorResponse = try JSONDecoder().decode(ErrorResponse.self, from: data)
108 | let errorMessage = errorResponse.detail
109 | completion(.failure(.serverError(errorMessage)))
110 | } catch {
111 | completion(.failure(.networkError(error)))
112 | }
113 | }
114 | else {
115 | completion(.failure(.networkError(error)))
116 | }
117 | }
118 |
119 | private func handleUnauthorizedAccessError() {
120 | Task { @MainActor in
121 | try await AuthenticationManager.shared.logout()
122 | }
123 | }
124 |
125 |
126 | private func getParamsEncoded(params: Encodable?) -> Parameters? {
127 | guard let params = params else { return nil }
128 |
129 | let encoder = JSONEncoder()
130 | encoder.dateEncodingStrategy = .iso8601
131 | let data = try! encoder.encode(params)
132 | let encodedParams = try! JSONSerialization.jsonObject(with: data, options: []) as? Parameters
133 | return encodedParams
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/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 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-1024.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-20.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-20@2x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-20@3x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-29.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-29@2x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-29@3x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-40.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-40@2x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-40@3x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-60@2x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-60@3x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-76.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-76@2x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/AppIconDebug-83.5@2x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconDebug.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AppIconDebug-20@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "AppIconDebug-20@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "AppIconDebug-29@2x.png",
17 | "idiom" : "iphone",
18 | "scale" : "2x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "AppIconDebug-29@3x.png",
23 | "idiom" : "iphone",
24 | "scale" : "3x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "AppIconDebug-40@2x.png",
29 | "idiom" : "iphone",
30 | "scale" : "2x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "AppIconDebug-40@3x.png",
35 | "idiom" : "iphone",
36 | "scale" : "3x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "AppIconDebug-60@2x.png",
41 | "idiom" : "iphone",
42 | "scale" : "2x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "filename" : "AppIconDebug-60@3x.png",
47 | "idiom" : "iphone",
48 | "scale" : "3x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "AppIconDebug-20.png",
53 | "idiom" : "ipad",
54 | "scale" : "1x",
55 | "size" : "20x20"
56 | },
57 | {
58 | "filename" : "AppIconDebug-20@2x.png",
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "AppIconDebug-29.png",
65 | "idiom" : "ipad",
66 | "scale" : "1x",
67 | "size" : "29x29"
68 | },
69 | {
70 | "filename" : "AppIconDebug-29@2x.png",
71 | "idiom" : "ipad",
72 | "scale" : "2x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "AppIconDebug-40.png",
77 | "idiom" : "ipad",
78 | "scale" : "1x",
79 | "size" : "40x40"
80 | },
81 | {
82 | "filename" : "AppIconDebug-40@2x.png",
83 | "idiom" : "ipad",
84 | "scale" : "2x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "AppIconDebug-76.png",
89 | "idiom" : "ipad",
90 | "scale" : "1x",
91 | "size" : "76x76"
92 | },
93 | {
94 | "filename" : "AppIconDebug-76@2x.png",
95 | "idiom" : "ipad",
96 | "scale" : "2x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "AppIconDebug-83.5@2x.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "83.5x83.5"
104 | },
105 | {
106 | "filename" : "AppIconDebug-1024.png",
107 | "idiom" : "ios-marketing",
108 | "scale" : "1x",
109 | "size" : "1024x1024"
110 | }
111 | ],
112 | "info" : {
113 | "author" : "xcode",
114 | "version" : 1
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-1024.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-20.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-20@2x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-20@3x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-29.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-29@2x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-29@3x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-40.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-40@2x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-40@3x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-60@2x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-60@3x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-76.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-76@2x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/AppIconRelease-83.5@2x.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/AppIconRelease.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "AppIconRelease-20@2x.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "AppIconRelease-20@3x.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "AppIconRelease-29@2x.png",
17 | "idiom" : "iphone",
18 | "scale" : "2x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "AppIconRelease-29@3x.png",
23 | "idiom" : "iphone",
24 | "scale" : "3x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "AppIconRelease-40@2x.png",
29 | "idiom" : "iphone",
30 | "scale" : "2x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "AppIconRelease-40@3x.png",
35 | "idiom" : "iphone",
36 | "scale" : "3x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "AppIconRelease-60@2x.png",
41 | "idiom" : "iphone",
42 | "scale" : "2x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "filename" : "AppIconRelease-60@3x.png",
47 | "idiom" : "iphone",
48 | "scale" : "3x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "AppIconRelease-20.png",
53 | "idiom" : "ipad",
54 | "scale" : "1x",
55 | "size" : "20x20"
56 | },
57 | {
58 | "filename" : "AppIconRelease-20@2x.png",
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "20x20"
62 | },
63 | {
64 | "filename" : "AppIconRelease-29.png",
65 | "idiom" : "ipad",
66 | "scale" : "1x",
67 | "size" : "29x29"
68 | },
69 | {
70 | "filename" : "AppIconRelease-29@2x.png",
71 | "idiom" : "ipad",
72 | "scale" : "2x",
73 | "size" : "29x29"
74 | },
75 | {
76 | "filename" : "AppIconRelease-40.png",
77 | "idiom" : "ipad",
78 | "scale" : "1x",
79 | "size" : "40x40"
80 | },
81 | {
82 | "filename" : "AppIconRelease-40@2x.png",
83 | "idiom" : "ipad",
84 | "scale" : "2x",
85 | "size" : "40x40"
86 | },
87 | {
88 | "filename" : "AppIconRelease-76.png",
89 | "idiom" : "ipad",
90 | "scale" : "1x",
91 | "size" : "76x76"
92 | },
93 | {
94 | "filename" : "AppIconRelease-76@2x.png",
95 | "idiom" : "ipad",
96 | "scale" : "2x",
97 | "size" : "76x76"
98 | },
99 | {
100 | "filename" : "AppIconRelease-83.5@2x.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "83.5x83.5"
104 | },
105 | {
106 | "filename" : "AppIconRelease-1024.png",
107 | "idiom" : "ios-marketing",
108 | "scale" : "1x",
109 | "size" : "1024x1024"
110 | }
111 | ],
112 | "info" : {
113 | "author" : "xcode",
114 | "version" : 1
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/Images/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/Images/onboarding_1.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "onboarding_1.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/Images/onboarding_1.imageset/onboarding_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/Images/onboarding_1.imageset/onboarding_1.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/Images/onboarding_2.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "onboarding_2.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/Images/onboarding_2.imageset/onboarding_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/Images/onboarding_2.imageset/onboarding_2.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/LaunchScreen/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/LaunchScreen/logo.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "logo.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/LaunchScreen/logo.imageset/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Assets.xcassets/LaunchScreen/logo.imageset/logo.png
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/backgroundColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "255",
9 | "green" : "255",
10 | "red" : "255"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0",
27 | "green" : "0",
28 | "red" : "0"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Assets.xcassets/textColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0",
9 | "green" : "0",
10 | "red" : "0"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "255",
27 | "green" : "255",
28 | "red" : "255"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Fonts/NotoSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Fonts/NotoSans-Bold.ttf
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Fonts/NotoSans-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Fonts/NotoSans-Medium.ttf
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Fonts/NotoSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Fonts/NotoSans-Regular.ttf
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/Fonts/NotoSans-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shurutech/iOSKickstart/dcff341fce9479fedf8958325f917b3a19f4f1ce/SwiftAppTemplate/Resources/Fonts/NotoSans-SemiBold.ttf
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/ar.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | SwiftAppTemplate
4 |
5 | Created by Vijay Goswami on 05/01/24.
6 |
7 | */
8 |
9 | //MARK: - AuthorizationScreen
10 | "Login/SignUp" = "تسجيل الدخول/الاشتراك";
11 | "Login" = "تسجيل الدخول";
12 | "Password" = "كلمة المرور";
13 |
14 | //MARK: - AcceptTermsAndConditionsScreen
15 | "Terms&Conditions" = "الشروط والأحكام";
16 | "ReadAndAcceptTerms&Conditions" = "اقرأ ووافق على الشروط والأحكام";
17 | "DummyTermsAndConditions" = "أنا أدخل إلى قالب تطبيق iOS.\nسأحسّن تطبيقي وفقًا لاحتياجاتي.\n...";
18 |
19 | //MARK: - OnboardingScreen
20 | "Onboarding" = "التهيئة";
21 | "Next" = "التالي";
22 | "GetStarted" = "ابدأ";
23 |
24 | //MARK: - Settings
25 | "Settings" = "الإعدادات";
26 | "Name" = "الاسم";
27 | "Email" = "البريد الإلكتروني";
28 | "Gender" = "الجنس";
29 | "DateOfBirth" = "تاريخ الميلاد";
30 | "Country" = "البلد";
31 | "Language" = "اللغة";
32 | "Logout" = "تسجيل الخروج";
33 | "DeleteAccount" = "حذف الحساب";
34 | "Update" = "تحديث";
35 | "Appearance" = "المظهر";
36 | "Light" = "فاتح";
37 | "Dark" = "داكن";
38 | "LogoutBottomSheetTitle" = "هل أنت متأكد من أنك تريد تسجيل الخروج؟";
39 | "DeleteAccountBottomSheetTitle" = "سيؤدي ذلك إلى حذف جميع بيانات المستخدم الخاصة بك. هل أنت متأكد؟";
40 | "LogoutBottomSheetSubTitle" = "سيتم تسجيل خروجك، وسيتعين عليك إدخال بيانات اعتمادك مرة أخرى لاستخدام التطبيق.";
41 | "DeleteAccountBottomSheetSubTitle" = "هذا خيار لا رجوع فيه. لن يتم استرداد البيانات المستخدم المحذوفة بمجرد حذفها. تقدم بحذر.";
42 |
43 | //MARK: - User Details
44 | "UpdateUserDetails" = "تحديث تفاصيل المستخدم";
45 | "NamePlaceHolder" = "اسمك";
46 | "EmailPlaceHolder" = "عنوان بريدك الإلكتروني";
47 | "GenderPlaceHolder" = "جنسك";
48 | "DateOfBirthPlaceHolder" = "اختر تاريخ الميلاد";
49 | "SelectCountry" = "اختر البلد";
50 | "SelectCountryPlaceHolder" = "اختر بلدك";
51 | "SelectLanguage" = "اختر اللغة";
52 | "SelectLanguagePlaceHolder" = "اختر لغتك";
53 | "Done" = "تم";
54 | "Update" = "تحديث";
55 | "SaveUserInfoBottomSheetTitle" = "هل تريد الحفظ؟";
56 | "SaveUserInfoBottomSheetSubTitle" = "يرجى التأكد من جميع التفاصيل قبل الحفظ.";
57 | "UserDetails" = "تفاصيل المستخدم";
58 | "Continue" = "استمرار";
59 |
60 | //MARK: - Details
61 | "Details" = "التفاصيل";
62 |
63 | "Cancel" = "إلغاء";
64 | "YesSure" = "نعم، بالتأكيد.";
65 | "Tab" = "علامة التبويب";
66 |
67 | "Male" = "ذكر";
68 | "Female" = "أنثى";
69 | "Other" = "آخر";
70 |
71 | "Weather" = "الطقس";
72 | "Summary" = "ملخص";
73 | "Temperature" = "درجة الحرارة";
74 | "Humidity" = "الرطوبة";
75 |
76 | "TemperatureIs" = "درجة الحرارة هي";
77 | "RealFeelIs" = "الإحساس الفعلي هو";
78 | "MaxItWillGoIs" = "أقصى ما ستصل إليه هو";
79 | "MinItWillFallIs" = "الحد الأدنى الذي ستنخفض إليه هو";
80 | "YouCanSeeAsFarAs" = "يمكنك الرؤية لمسافة";
81 | "ThePressureYouBeFeelingIs" = "الضغط الذي ستشعر به هو";
82 |
83 | "Delhi" = "دلهي";
84 | "Jaipur" = "جايبور";
85 | "Mumbai" = "مومباي";
86 | "Chennai" = "تشيناي";
87 | "Bengaluru" = "بنغالورو";
88 | "Kolkata" = "كولكاتا";
89 |
90 | "India" = "الهند";
91 | "USA" = "الولايات المتحدة الأمريكية";
92 | "UK" = "المملكة المتحدة";
93 | "France" = "فرنسا";
94 | "China" = "الصين";
95 | "UAE" = "الإمارات العربية المتحدة";
96 |
97 | "Error" = "خطأ";
98 | "ErrorMessage" = "حدث خطأ غير معروف";
99 | "OK" = "حسنًا";
100 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/en.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | SwiftAppTemplate
4 |
5 | Created by Vijay Goswami on 05/01/24.
6 |
7 | */
8 |
9 | //MARK: - AuthorizationScreen
10 | "Login/SignUp" = "Login/SignUp";
11 | "Login" = "Login";
12 | "Password" = "Password";
13 |
14 | //MARK: - AcceptTermsAndConditionsScreen
15 | "Terms&Conditions" = "Terms & Conditions";
16 | "ReadAndAcceptTerms&Conditions" = "Read and accept Terms & Conditions";
17 | "DummyTermsAndConditions" = "I am entering into an iOS app template.\nI will improve my app as per my needs.\n...";
18 |
19 | //MARK: - OnboardingScreen
20 | "Onboarding" = "Onboarding";
21 | "Next" = "Next";
22 | "GetStarted" = "Get Started";
23 |
24 | //MARK: - Settings
25 | "Settings" = "Settings";
26 | "Name" = "Name";
27 | "Email" = "Email";
28 | "Gender" = "Gender";
29 | "DateOfBirth" = "Date Of Birth";
30 | "Country" = "Country";
31 | "Language" = "Language";
32 | "Logout" = "Logout";
33 | "DeleteAccount" = "Delete Account";
34 | "Update" = "Update";
35 | "Appearance" = "Appearance";
36 | "Light" = "Light";
37 | "Dark" = "Dark";
38 | "LogoutBottomSheetTitle" = "Are you sure that you want to logout?";
39 | "DeleteAccountBottomSheetTitle" = "This will delete all your user data. Are you sure?";
40 | "LogoutBottomSheetSubTitle" = "This will log you out, and you'll have to input your credentials again to use the app.";
41 | "DeleteAccountBottomSheetSubTitle" = "This is an irreversible choice. The deleted user data will not get recovered once deleted. Proceed with caution.";
42 |
43 | //MARK: - User Details
44 | "UpdateUserDetails" = "Update User Details";
45 | "NamePlaceHolder" = "Your name";
46 | "EmailPlaceHolder" = "Your email address";
47 | "GenderPlaceHolder" = "Your gender";
48 | "DateOfBirthPlaceHolder" = "Select dob";
49 | "SelectCountry" = "Select Country";
50 | "SelectCountryPlaceHolder" = "Select your country";
51 | "SelectLanguage" = "Select Language";
52 | "SelectLanguagePlaceHolder" = "Select your language";
53 | "Done" = "Done";
54 | "Update" = "Update";
55 | "SaveUserInfoBottomSheetTitle" = "Do you want to save?";
56 | "SaveUserInfoBottomSheetSubTitle" = "Please confirm all details before saving.";
57 | "UserDetails" = "User Details";
58 | "Continue" = "Continue";
59 |
60 |
61 | //MARK: - Details
62 | "Details" = "Details";
63 |
64 | "Cancel" = "Cancel";
65 | "YesSure" = "Yes, sure.";
66 | "Tab" = "Tab";
67 |
68 | "Male" = "Male";
69 | "Female" = "Female";
70 | "Other" = "Other";
71 |
72 | "Weather" = "Weather";
73 | "Summary" = "Summary";
74 | "Temperature" = "Temperature";
75 | "Humidity" = "Humidity";
76 |
77 | "TemperatureIs" = "Temperature is";
78 | "RealFeelIs" = "Real feel is";
79 | "MaxItWillGoIs" = "Max it will go is";
80 | "MinItWillFallIs" = "Min it will fall is";
81 | "YouCanSeeAsFarAs" = "You can see as far as";
82 | "ThePressureYouBeFeelingIs" = "The pressure you'd be feeling is";
83 |
84 | "Delhi" = "Delhi";
85 | "Jaipur" = "Jaipur";
86 | "Mumbai" = "Mumbai";
87 | "Chennai" = "Chennai";
88 | "Bengaluru" = "Bengaluru";
89 | "Kolkata" = "Kolkata";
90 |
91 | "India" = "India";
92 | "USA" = "USA";
93 | "UK" = "UK";
94 | "France" = "France";
95 | "China" = "China";
96 | "UAE" = "UAE";
97 |
98 | "Error" = "Error";
99 | "ErrorMessage" = "An unknown error occurred";
100 | "OK" = "OK";
101 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/es.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | SwiftAppTemplate
4 |
5 | Created by Vijay Goswami on 05/01/24.
6 |
7 | */
8 |
9 | //MARK: - AuthorizationScreen
10 | "Login/SignUp" = "Iniciar sesión/Registrarse";
11 | "Login" = "Iniciar sesión";
12 | "Password" = "Contraseña";
13 |
14 | //MARK: - AcceptTermsAndConditionsScreen
15 | "Terms&Conditions" = "Términos y Condiciones";
16 | "ReadAndAcceptTerms&Conditions" = "Leer y aceptar los Términos y Condiciones";
17 | "DummyTermsAndConditions" = "Estoy ingresando en una plantilla de aplicación iOS.\nMejoraré mi aplicación según mis necesidades.\n...";
18 |
19 | //MARK: - OnboardingScreen
20 | "Onboarding" = "Incorporación";
21 | "Next" = "Siguiente";
22 | "GetStarted" = "Comenzar";
23 |
24 | //MARK: - Settings
25 | "Settings" = "Configuración";
26 | "Name" = "Nombre";
27 | "Email" = "Correo electrónico";
28 | "Gender" = "Género";
29 | "DateOfBirth" = "Fecha de nacimiento";
30 | "Country" = "País";
31 | "Language" = "Idioma";
32 | "Logout" = "Cerrar sesión";
33 | "DeleteAccount" = "Eliminar cuenta";
34 | "Update" = "Actualizar";
35 | "Appearance" = "Apariencia";
36 | "Light" = "Claro";
37 | "Dark" = "Oscuro";
38 | "LogoutBottomSheetTitle" = "¿Estás seguro de que quieres cerrar sesión?";
39 | "DeleteAccountBottomSheetTitle" = "Esto eliminará todos tus datos de usuario. ¿Estás seguro?";
40 | "LogoutBottomSheetSubTitle" = "Esto te cerrará la sesión, y tendrás que ingresar tus credenciales de nuevo para usar la app.";
41 | "DeleteAccountBottomSheetSubTitle" = "Esta es una elección irreversible. Los datos de usuario eliminados no se podrán recuperar una vez eliminados. Procede con precaución.";
42 |
43 | //MARK: - User Details
44 | "UpdateUserDetails" = "Actualizar detalles del usuario";
45 | "NamePlaceHolder" = "Tu nombre";
46 | "EmailPlaceHolder" = "Tu dirección de correo";
47 | "GenderPlaceHolder" = "Tu género";
48 | "DateOfBirthPlaceHolder" = "Selecciona fecha de nacimiento";
49 | "SelectCountry" = "Seleccionar país";
50 | "SelectCountryPlaceHolder" = "Selecciona tu país";
51 | "SelectLanguage" = "Seleccionar idioma";
52 | "SelectLanguagePlaceHolder" = "Selecciona tu idioma";
53 | "Done" = "Hecho";
54 | "Update" = "Actualizar";
55 | "SaveUserInfoBottomSheetTitle" = "¿Quieres guardar?";
56 | "SaveUserInfoBottomSheetSubTitle" = "Por favor, confirma todos los detalles antes de guardar.";
57 | "UserDetails" = "Detalles del usuario";
58 | "Continue" = "Continuar";
59 |
60 | //MARK: - Details
61 | "Details" = "Detalles";
62 |
63 | "Cancel" = "Cancelar";
64 | "YesSure" = "Sí, claro.";
65 | "Tab" = "Pestaña";
66 |
67 | "Male" = "Masculino";
68 | "Female" = "Femenino";
69 | "Other" = "Otro";
70 |
71 | "Weather" = "Clima";
72 | "Summary" = "Resumen";
73 | "Temperature" = "Temperatura";
74 | "Humidity" = "Humedad";
75 |
76 | "TemperatureIs" = "La temperatura es";
77 | "RealFeelIs" = "La sensación real es";
78 | "MaxItWillGoIs" = "El máximo al que llegará es";
79 | "MinItWillFallIs" = "El mínimo al que descenderá es";
80 | "YouCanSeeAsFarAs" = "Puedes ver hasta";
81 | "ThePressureYouBeFeelingIs" = "La presión que sentirás es";
82 |
83 | "Delhi" = "Delhi";
84 | "Jaipur" = "Jaipur";
85 | "Mumbai" = "Mumbai";
86 | "Chennai" = "Chennai";
87 | "Bengaluru" = "Bengaluru";
88 | "Kolkata" = "Kolkata";
89 |
90 | "India" = "India";
91 | "USA" = "Estados Unidos";
92 | "UK" = "Reino Unido";
93 | "France" = "Francia";
94 | "China" = "China";
95 | "UAE" = "Emiratos Árabes Unidos";
96 |
97 | "Error" = "Error";
98 | "ErrorMessage" = "Ocurrió un error desconocido";
99 | "OK" = "OK";
100 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/fr.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | SwiftAppTemplate
4 |
5 | Created by Vijay Goswami on 05/01/24.
6 |
7 | */
8 |
9 | //MARK: - AuthorizationScreen
10 | "Login/SignUp" = "Connexion/Inscription";
11 | "Login" = "Connexion";
12 | "Password" = "Mot de passe";
13 |
14 | //MARK: - AcceptTermsAndConditionsScreen
15 | "Terms&Conditions" = "Conditions Générales";
16 | "ReadAndAcceptTerms&Conditions" = "Lire et accepter les Conditions Générales";
17 | "DummyTermsAndConditions" = "Je pénètre dans un modèle d'application iOS.\nJe vais améliorer mon application selon mes besoins.\n...";
18 |
19 | //MARK: - OnboardingScreen
20 | "Onboarding" = "Intégration";
21 | "Next" = "Suivant";
22 | "GetStarted" = "Commencer";
23 |
24 | //MARK: - Settings
25 | "Settings" = "Paramètres";
26 | "Name" = "Nom";
27 | "Email" = "E-mail";
28 | "Gender" = "Genre";
29 | "DateOfBirth" = "Date de naissance";
30 | "Country" = "Pays";
31 | "Language" = "Langue";
32 | "Logout" = "Déconnexion";
33 | "DeleteAccount" = "Supprimer le compte";
34 | "Update" = "Mettre à jour";
35 | "Appearance" = "Apparence";
36 | "Light" = "Clair";
37 | "Dark" = "Sombre";
38 | "LogoutBottomSheetTitle" = "Êtes-vous sûr de vouloir vous déconnecter ?";
39 | "DeleteAccountBottomSheetTitle" = "Cela supprimera toutes vos données utilisateur. Êtes-vous sûr ?";
40 | "LogoutBottomSheetSubTitle" = "Cela vous déconnectera, et vous devrez saisir à nouveau vos identifiants pour utiliser l'application.";
41 | "DeleteAccountBottomSheetSubTitle" = "C'est un choix irréversible. Les données utilisateur supprimées ne seront pas récupérées une fois supprimées. Procédez avec prudence.";
42 |
43 | //MARK: - User Details
44 | "UpdateUserDetails" = "Mettre à jour les détails de l'utilisateur";
45 | "NamePlaceHolder" = "Votre nom";
46 | "EmailPlaceHolder" = "Votre adresse e-mail";
47 | "GenderPlaceHolder" = "Votre genre";
48 | "DateOfBirthPlaceHolder" = "Sélectionnez la date de naissance";
49 | "SelectCountry" = "Sélectionner le pays";
50 | "SelectCountryPlaceHolder" = "Sélectionnez votre pays";
51 | "SelectLanguage" = "Sélectionner la langue";
52 | "SelectLanguagePlaceHolder" = "Sélectionnez votre langue";
53 | "Done" = "Terminé";
54 | "Update" = "Mettre à jour";
55 | "SaveUserInfoBottomSheetTitle" = "Voulez-vous enregistrer ?";
56 | "SaveUserInfoBottomSheetSubTitle" = "Veuillez confirmer tous les détails avant de sauvegarder.";
57 | "UserDetails" = "Détails de l'utilisateur";
58 | "Continue" = "Continuer";
59 |
60 | //MARK: - Details
61 | "Details" = "Détails";
62 |
63 | "Cancel" = "Annuler";
64 | "YesSure" = "Oui, bien sûr.";
65 | "Tab" = "Onglet";
66 |
67 | "Male" = "Homme";
68 | "Female" = "Femme";
69 | "Other" = "Autre";
70 |
71 | "Weather" = "Météo";
72 | "Summary" = "Résumé ";
73 | "Temperature" = "Température ";
74 | "Humidity" = "Humidité ";
75 |
76 | "TemperatureIs" = "La température est";
77 | "RealFeelIs" = "La sensation réelle est";
78 | "MaxItWillGoIs" = "Le maximum atteignable est";
79 | "MinItWillFallIs" = "Le minimum descendra à";
80 | "YouCanSeeAsFarAs" = "Vous pouvez voir aussi loin que";
81 | "ThePressureYouBeFeelingIs" = "La pression que vous ressentirez est";
82 |
83 | "Delhi" = "Delhi";
84 | "Jaipur" = "Jaipur";
85 | "Mumbai" = "Mumbai";
86 | "Chennai" = "Chennai";
87 | "Bengaluru" = "Bengaluru";
88 | "Kolkata" = "Kolkata";
89 |
90 | "India" = "Inde";
91 | "USA" = "États-Unis";
92 | "UK" = "Royaume-Uni";
93 | "France" = "France";
94 | "China" = "Chine";
95 | "UAE" = "Émirats Arabes Unis";
96 |
97 | "Error" = "Erreur";
98 | "ErrorMessage" = "Une erreur inconnue s'est produite";
99 | "OK" = "OK";
100 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/hi.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | SwiftAppTemplate
4 |
5 | Created by Vijay Goswami on 05/01/24.
6 |
7 | */
8 |
9 | ///MARK: - AuthorizationScreen
10 | "Login/SignUp" = "लॉगिन/साइनअप";
11 | "Login" = "लॉगिन";
12 | "Password" = "पासवर्ड";
13 |
14 | //MARK: - AcceptTermsAndConditionsScreen
15 | "Terms&Conditions" = "नियम व शर्तें";
16 | "ReadAndAcceptTerms&Conditions" = "नियम और शर्तों को पढ़ें और स्वीकार करें";
17 | "DummyTermsAndConditions" = "मैं एक iOS ऐप टेम्पलेट में प्रवेश कर रहा हूं।\nमैं अपनी आवश्यकतानुसार अपने ऐप में सुधार करूंगा।\n...";
18 |
19 | //MARK: - OnboardingScreen
20 | "Onboarding" = "ऑनबोर्डिंग";
21 | "Next" = "अगला";
22 | "GetStarted" = "शुरू करें";
23 |
24 | //MARK: - Settings
25 | "Settings" = "सेटिंग्स";
26 | "Name" = "नाम";
27 | "Email" = "ईमेल";
28 | "Gender" = "लिंग";
29 | "DateOfBirth" = "जन्म तिथि";
30 | "Country" = "देश";
31 | "Language" = "भाषा";
32 | "Logout" = "लॉगआउट";
33 | "DeleteAccount" = "खाता हटाएं";
34 | "Update" = "अपडेट";
35 | "Appearance" = "दृश्य स्वरूप";
36 | "Light" = "हल्का";
37 | "Dark" = "गहरा";
38 | "LogoutBottomSheetTitle" = "क्या आप वाकई में लॉगआउट करना चाहते हैं?";
39 | "DeleteAccountBottomSheetTitle" = "इससे आपका सारा डेटा हट जाएगा। क्या आप वाकई में सुनिश्चित हैं?";
40 | "LogoutBottomSheetSubTitle" = "इससे आप लॉगआउट हो जाएंगे, और ऐप का उपयोग करने के लिए फिर से अपने विवरण दर्ज करने होंगे।";
41 | "DeleteAccountBottomSheetSubTitle" = "यह एक अपरिवर्तनीय चयन है। एक बार हटाए जाने के बाद हटाया गया डेटा वापस नहीं आएगा। सावधानी से आगे बढ़ें।";
42 |
43 | //MARK: - User Details
44 | "UpdateUserDetails" = "उपयोगकर्ता विवरण अपडेट करें";
45 | "NamePlaceHolder" = "आपका नाम";
46 | "EmailPlaceHolder" = "आपका ईमेल पता";
47 | "GenderPlaceHolder" = "आपका लिंग";
48 | "DateOfBirthPlaceHolder" = "जन्म तिथि चुनें";
49 | "SelectCountry" = "देश चुनें";
50 | "SelectCountryPlaceHolder" = "अपना देश चुनें";
51 | "SelectLanguage" = "भाषा चुनें";
52 | "SelectLanguagePlaceHolder" = "अपनी भाषा चुनें";
53 | "Done" = "हो गया";
54 | "Update" = "अपडेट करें";
55 | "SaveUserInfoBottomSheetTitle" = "क्या आप सहेजना चाहते हैं?";
56 | "SaveUserInfoBottomSheetSubTitle" = "कृपया सहेजने से पहले सभी विवरणों की पुष्टि करें।";
57 | "UserDetails" = "उपयोगकर्ता विवरण";
58 | "Continue" = "जारी रखें";
59 |
60 |
61 | //MARK: - Details
62 | "Details" = "विवरण";
63 |
64 | "Cancel" = "रद्द करें";
65 | "YesSure" = "हाँ, निश्चित";
66 | "Tab" = "टैब";
67 |
68 | "Male" = "पुरुष";
69 | "Female" = "महिला";
70 | "Other" = "अन्य";
71 |
72 | "Weather" = "मौसम";
73 | "Summary" = "सारांश";
74 | "Temperature" = "तापमान";
75 | "Humidity" = "आर्द्रता";
76 |
77 | "TemperatureIs" = "तापमान है";
78 | "RealFeelIs" = "वास्तविक अनुभव है";
79 | "MaxItWillGoIs" = "अधिकतम यह जाएगा";
80 | "MinItWillFallIs" = "न्यूनतम यह गिरेगा";
81 | "YouCanSeeAsFarAs" = "आप जितना देख सकते हैं";
82 | "ThePressureYouBeFeelingIs" = "आप जो दबाव महसूस करेंगे वह है";
83 |
84 | "Delhi" = "दिल्ली";
85 | "Jaipur" = "जयपुर";
86 | "Mumbai" = "मुंबई";
87 | "Chennai" = "चेन्नई";
88 | "Bengaluru" = "बेंगलुरु";
89 | "Kolkata" = "कोलकाता";
90 |
91 | "India" = "भारत";
92 | "USA" = "अमेरिका";
93 | "UK" = "यूनाइटेड किंगडम";
94 | "France" = "फ्रांस";
95 | "China" = "चीन";
96 | "UAE" = "संयुक्त अरब अमीरात";
97 |
98 | "Error" = "त्रुटि";
99 | "ErrorMessage" = "एक अज्ञात त्रुटि हुई है";
100 | "OK" = "ठीक है";
101 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Resources/zh-Hans.lproj/Localizable.strings:
--------------------------------------------------------------------------------
1 | /*
2 | Localizable.strings
3 | SwiftAppTemplate
4 |
5 | Created by Vijay Goswami on 05/01/24.
6 |
7 | */
8 |
9 | //MARK: - AuthorizationScreen
10 | "Login/SignUp" = "登录/注册";
11 | "Login" = "登录";
12 | "Password" = "密码";
13 |
14 | //MARK: - AcceptTermsAndConditionsScreen
15 | "Terms&Conditions" = "条款和条件";
16 | "ReadAndAcceptTerms&Conditions" = "阅读并接受条款和条件";
17 | "DummyTermsAndConditions" = "我正在进入一个iOS应用模板。\n我将根据我的需求改进我的应用。\n...";
18 |
19 | //MARK: - OnboardingScreen
20 | "Onboarding" = "新手引导";
21 | "Next" = "下一步";
22 | "GetStarted" = "开始使用";
23 |
24 | //MARK: - Settings
25 | "Settings" = "设置";
26 | "Name" = "姓名";
27 | "Email" = "电子邮件";
28 | "Gender" = "性别";
29 | "DateOfBirth" = "出生日期";
30 | "Country" = "国家";
31 | "Language" = "语言";
32 | "Logout" = "登出";
33 | "DeleteAccount" = "删除账户";
34 | "Update" = "更新";
35 | "Appearance" = "外观";
36 | "Light" = "浅色模式";
37 | "Dark" = "深色模式";
38 | "LogoutBottomSheetTitle" = "您确定要退出登录吗?";
39 | "DeleteAccountBottomSheetTitle" = "这将删除您的所有用户数据。您确定吗?";
40 | "LogoutBottomSheetSubTitle" = "这将使您退出登录,您将需要再次输入您的凭据才能使用应用。";
41 | "DeleteAccountBottomSheetSubTitle" = "这是不可逆的选择。一旦删除,删除的用户数据将无法恢复。请谨慎操作。";
42 |
43 | //MARK: - User Details
44 | "UpdateUserDetails" = "更新用户详情";
45 | "NamePlaceHolder" = "您的姓名";
46 | "EmailPlaceHolder" = "您的电子邮件地址";
47 | "GenderPlaceHolder" = "您的性别";
48 | "DateOfBirthPlaceHolder" = "选择出生日期";
49 | "SelectCountry" = "选择国家";
50 | "SelectCountryPlaceHolder" = "选择您的国家";
51 | "SelectLanguage" = "选择语言";
52 | "SelectLanguagePlaceHolder" = "选择您的语言";
53 | "Done" = "完成";
54 | "Update" = "更新";
55 | "SaveUserInfoBottomSheetTitle" = "您想要保存吗?";
56 | "SaveUserInfoBottomSheetSubTitle" = "保存前请确认所有细节。";
57 | "UserDetails" = "用户详情";
58 | "Continue" = "继续";
59 |
60 | //MARK: - Details
61 | "Details" = "详情";
62 |
63 | "Cancel" = "取消";
64 | "YesSure" = "是的,当然。";
65 | "Tab" = "标签";
66 |
67 | "Male" = "男";
68 | "Female" = "女";
69 | "Other" = "其他";
70 |
71 | "Weather" = "天气";
72 | "Summary" = "概述";
73 | "Temperature" = "温度";
74 | "Humidity" = "湿度";
75 |
76 | "TemperatureIs" = "温度是";
77 | "RealFeelIs" = "体感温度是";
78 | "MaxItWillGoIs" = "最高温度是";
79 | "MinItWillFallIs" = "最低温度是";
80 | "YouCanSeeAsFarAs" = "能见度达到";
81 | "ThePressureYouBeFeelingIs" = "您将感受到的气压是";
82 |
83 | "Delhi" = "德里";
84 | "Jaipur" = "斋浦尔";
85 | "Mumbai" = "孟买";
86 | "Chennai" = "钦奈";
87 | "Bengaluru" = "班加罗尔";
88 | "Kolkata" = "加尔各答";
89 |
90 | "India" = "印度";
91 | "USA" = "美国";
92 | "UK" = "英国";
93 | "France" = "法国";
94 | "China" = "中国";
95 | "UAE" = "阿拉伯联合酋长国";
96 |
97 | "Error" = "错误";
98 | "ErrorMessage" = "发生未知错误";
99 | "OK" = "确定";
100 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/CardView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CardView.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 04/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CardView: View {
11 | var title = "Title"
12 | var subTitle = "SubTitle"
13 | var backgroundColor = Color.background
14 | var cornerRadius = 10.0
15 | var shadowRadius = 5.0
16 | var infoAction: () -> Void = {}
17 |
18 | var body: some View {
19 | HStack{
20 | VStack(spacing: 20){
21 | Text(title)
22 | .font(.notoSansBold20)
23 | .foregroundColor(.primaryNavyBlue)
24 | .frame(maxWidth: .infinity, alignment: .leading)
25 | Text(subTitle)
26 | .font(.notoSansMedium16)
27 | .frame(maxWidth: .infinity, alignment: .leading)
28 | .foregroundColor(.secondaryLightBlue)
29 | .multilineTextAlignment(.leading)
30 | }
31 | Image(systemName: "info.circle")
32 | .resizable()
33 | .frame(width: 25, height: 25)
34 | .foregroundColor(.secondaryLightBlue)
35 | }
36 | .padding()
37 | .background(backgroundColor)
38 | .cornerRadius(cornerRadius)
39 | .shadow(color: Color.gray.opacity(0.5), radius: shadowRadius)
40 | }
41 | }
42 |
43 | #Preview {
44 | CardView(infoAction: {})
45 | }
46 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/ConfirmationSheet.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConfirmationSheet.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 12/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ConfirmationSheet: View {
11 | @Binding var isConfirmationGiven: Bool
12 | @Binding var isOpen: Bool
13 | var title: LocalizedStringKey = "Are you sure?"
14 | var subTitle: LocalizedStringKey = "Are you really really sure that you want to go ahead with this action. It can have permanent consequences?"
15 |
16 |
17 | var body: some View {
18 | VStack{
19 | Text(title)
20 | .fixedSize(horizontal: false, vertical: true)
21 | .multilineTextAlignment(.center)
22 | .font(.notoSansBold24)
23 | Text(subTitle)
24 | .fixedSize(horizontal: false, vertical: true)
25 | .multilineTextAlignment(.center)
26 | .font(.notoSansMedium12)
27 | .padding()
28 | HStack{
29 | TextButton(onClick: {
30 | isOpen.toggle()
31 | }, text: AppStrings.Cancel, style: .outline, color: .primaryNavyBlue)
32 | TextButton(onClick: {
33 | isConfirmationGiven.toggle()
34 | isOpen.toggle()
35 | }, text: AppStrings.YesSure, style: .filled, color: .primaryNavyBlue)
36 | }.padding(.bottom, 20)
37 | }.padding()
38 | }
39 | }
40 |
41 | #Preview {
42 | ConfirmationSheet(isConfirmationGiven: Binding.constant(false), isOpen: Binding.constant(true))
43 | }
44 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/CustomBottomSheetView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomBottomSheetView.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 12/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CustomBottomSheetView: View {
11 | @Binding var isOpen: Bool
12 | @Binding var isLoaderVisible: Bool
13 | let maxHeight: CGFloat
14 | let content: Content
15 | var showIndicator: Bool
16 |
17 |
18 | @GestureState private var translation: CGFloat = 0
19 |
20 | init(isOpen: Binding, isLoaderVisible: Binding = Binding.constant(false), maxHeight: CGFloat, showIndicator: Bool = true, @ViewBuilder content: () -> Content) {
21 | self._isOpen = isOpen
22 | self._isLoaderVisible = isLoaderVisible
23 | if(maxHeight == .infinity){
24 | self.maxHeight = UIScreen.main.bounds.height - 50
25 | } else {
26 | self.maxHeight = maxHeight
27 | }
28 | self.content = content()
29 | self.showIndicator = showIndicator
30 | }
31 |
32 | var body: some View {
33 |
34 | ZStack{
35 | Color.black.opacity(isOpen ? 0.5 : 0)
36 | .edgesIgnoringSafeArea(.all)
37 | .onTapGesture {
38 | self.isOpen = false
39 | }
40 |
41 | GeometryReader { geometry in
42 | ZStack{
43 | VStack {
44 | if(self.showIndicator){
45 | Image(systemName: "minus.rectangle.fill")
46 | .resizable()
47 | .foregroundColor(.gray.opacity(0.8))
48 | .frame(width: 50, height: 10)
49 | .cornerRadius(30)
50 | .padding(.top, 10)
51 | }
52 | self.content
53 | }
54 | .background(Color.background)
55 | .cornerRadius(20)
56 | .frame(width: geometry.size.width, height: self.maxHeight, alignment: .top)
57 | .frame(height: geometry.size.height, alignment: .bottom)
58 | .offset(y: self.isOpen ? 50 : geometry.size.height)
59 | .offset(y: max(min(self.translation, self.maxHeight), 0))
60 | .animation(.interactiveSpring(), value: isOpen)
61 | .gesture(
62 | DragGesture().updating(self.$translation) { value, state, _ in
63 | state = value.translation.height
64 | }.onEnded { value in
65 | let snapDistance = self.maxHeight * 0.25
66 | guard abs(value.translation.height) > snapDistance else {
67 | return
68 | }
69 | self.isOpen = value.translation.height < 0
70 | }
71 | )
72 | }
73 | }
74 | }
75 | }
76 | }
77 |
78 | #Preview {
79 | CustomBottomSheetView(isOpen: Binding.constant(true), maxHeight: 300, content: {})
80 | }
81 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/CustomTextField.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomTextField.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 09/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CustomTextField: View {
11 | @Binding var inputText: String
12 | var placeholder: LocalizedStringKey
13 | var cornerRadius: CGFloat
14 | var borderColor: Color
15 |
16 | var body: some View {
17 | ZStack(alignment: .leading) {
18 | if inputText.isEmpty {
19 | Text(placeholder)
20 | .foregroundColor(.gray)
21 | .padding(.horizontal, 8)
22 | }
23 |
24 | TextField("", text: $inputText)
25 | .foregroundColor(.text)
26 | .padding(.horizontal, 8)
27 | .padding(.vertical, 12)
28 | .background(
29 | RoundedRectangle(cornerRadius: cornerRadius)
30 | .stroke(borderColor, lineWidth: 1)
31 | )
32 | }
33 | }
34 | }
35 |
36 | #Preview {
37 | CustomTextField(inputText: Binding.constant(""), placeholder: "Enter Text", cornerRadius: 8, borderColor: Color.blue)
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/CustomTitleTextFieldView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomTitleTextFieldView.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 25/04/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CustomTitleTextFieldView: View {
11 | var label: LocalizedStringKey
12 | var placeholder: LocalizedStringKey
13 | @Binding var inputText: String
14 |
15 | var body: some View {
16 | VStack(alignment: .leading) {
17 | Text(label)
18 | .font(.notoSansMedium16)
19 | .foregroundColor(.primaryNavyBlue)
20 | CustomTextField(inputText: $inputText, placeholder: placeholder, cornerRadius: 10, borderColor: .primaryNavyBlue)
21 | }
22 | }
23 | }
24 |
25 | #Preview {
26 | CustomTitleTextFieldView(label: "Title", placeholder: "Enter title", inputText: Binding.constant(""))
27 | }
28 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/DatePickerPopover.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DatePickerPopover.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 25/04/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 |
11 | struct DatePickerPopover: View {
12 | @Binding var isPresented: Bool
13 | @Binding var dateSelection: Date
14 | let title: LocalizedStringKey
15 | let doneButtonLabel: LocalizedStringKey
16 |
17 | var body: some View {
18 | VStack {
19 | DatePicker(
20 | title,
21 | selection: $dateSelection,
22 | displayedComponents: .date
23 | )
24 | .datePickerStyle(GraphicalDatePickerStyle())
25 | .labelsHidden()
26 | .frame(maxHeight: 400)
27 |
28 | Button(doneButtonLabel) {
29 | self.isPresented = false
30 | }
31 | .padding()
32 | }
33 | .padding()
34 | }
35 | }
36 |
37 | #Preview {
38 | DatePickerPopover(isPresented: Binding.constant(true), dateSelection: Binding.constant(Date()), title: "Title", doneButtonLabel: "Done")
39 | }
40 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/Header.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Header.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 04/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct Header : View {
11 | var text: LocalizedStringKey
12 | var hasBackButton: Bool = false
13 | var onBackArrowClick: () -> Void = {}
14 |
15 | var body: some View {
16 | ZStack(alignment: .leading) {
17 | if(hasBackButton) {
18 | Button(action: onBackArrowClick) {
19 | Image(systemName: "arrow.backward")
20 | .foregroundColor(.primaryNavyBlue)
21 | }
22 | }
23 | Text(text)
24 | .frame(maxWidth: .infinity, alignment: .center)
25 | .font(.notoSansBold24)
26 | .foregroundColor(.primaryNavyBlue)
27 | }
28 | }
29 | }
30 |
31 | #Preview {
32 | Header(text: "Heading", hasBackButton: true, onBackArrowClick: {})
33 | }
34 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/LoaderView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LoaderView.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 09/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct LoaderView: View {
11 | var isLoading = true
12 |
13 | var body: some View {
14 | if(isLoading) {
15 | VStack {
16 | ProgressView()
17 | }
18 | .padding(40)
19 | .background(Color.background)
20 | .cornerRadius(10)
21 | .frame(maxWidth: .infinity, maxHeight: .infinity)
22 | .background(Color.gray.opacity(0.5)
23 | .edgesIgnoringSafeArea(.all))
24 | }
25 | }
26 | }
27 |
28 | struct LoaderViewModifier: ViewModifier {
29 | var isLoading: Bool
30 |
31 | func body(content: Content) -> some View {
32 | content
33 | .overlay(LoaderView(isLoading: isLoading))
34 | }
35 | }
36 |
37 | extension View {
38 | func loader(_ value : Bool) -> some View {
39 | modifier(LoaderViewModifier(isLoading: value))
40 | }
41 | }
42 |
43 | #Preview {
44 | Rectangle()
45 | .loader(true)
46 | }
47 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/PickerView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PickerView.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 25/04/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct CountryView: View {
11 | @Binding var selectedCountry: String
12 |
13 | var body: some View {
14 | VStack(alignment: .leading){
15 | HStack {
16 | Text(AppStrings.SelectCountry)
17 | .font(.notoSansMedium16)
18 | .foregroundColor(.primaryNavyBlue)
19 | Picker(AppStrings.SelectCountryPlaceHolder, selection: $selectedCountry){
20 | ForEach(Constants.countriesOptions, id: \.self){
21 | country in
22 | Text(getLocalString(country))
23 | }
24 | }
25 | .pickerStyle(DefaultPickerStyle())
26 |
27 | Spacer()
28 | }
29 | }
30 | }
31 | }
32 |
33 | struct GenderView: View {
34 | @Binding var selectedGender: Gender
35 |
36 | var body: some View {
37 | VStack(alignment: .leading){
38 | Text(AppStrings.Gender)
39 | .font(.notoSansMedium16)
40 | .foregroundColor(.primaryNavyBlue)
41 | Picker(AppStrings.GenderPlaceHolder, selection: $selectedGender) {
42 | ForEach(Gender.allCases) { gender in
43 | Text(getLocalString(gender.rawValue)).tag(gender)
44 | }
45 | }
46 | .pickerStyle(SegmentedPickerStyle())
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/PrimaryButton.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PrimaryButton.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 03/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct PrimaryButton: View {
11 | let label: String
12 | let onClick: () -> Void
13 | let isEnable: Bool
14 |
15 | // MARK: - Views
16 | var body: some View {
17 | button
18 | .padding(.horizontal, 20)
19 | }
20 |
21 | var button : some View {
22 | Button(action: { onClick() }, label: {
23 | Text(label)
24 | .applyButtonModifier(isEnabled: isEnable)
25 | })
26 | }
27 | }
28 |
29 | extension View {
30 | func applyButtonModifier(isEnabled: Bool) -> some View {
31 | modifier(ButtonModifier(isEnabled: isEnabled))
32 | }
33 | }
34 |
35 | struct ButtonModifier : ViewModifier {
36 | var isEnabled: Bool
37 |
38 | func body(content: Content) -> some View {
39 | content
40 | .foregroundColor(.white)
41 | .padding(12)
42 | .frame(maxWidth: .infinity)
43 | .background(isEnabled ? Color.blue : Color.gray)
44 | .cornerRadius(10)
45 | .shadow(radius: 10)
46 | }
47 | }
48 |
49 | #Preview {
50 | PrimaryButton(label: "Button", onClick: {}, isEnable: true)
51 | }
52 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/TextButton.swift:
--------------------------------------------------------------------------------
1 | // TextButton.swift
2 | // SwiftAppTemplate
3 | //
4 | // Created by Vijay Goswami on 05/01/24.
5 | //
6 |
7 | import SwiftUI
8 |
9 | struct TextButton : View {
10 | enum Style {
11 | case filled, outline, textOnly
12 | }
13 |
14 | // MARK: - Attributes
15 | var onClick: () -> Void
16 | var text: LocalizedStringKey
17 | var style: Style = .filled
18 | var color: Color = .primaryNavyBlue
19 |
20 | // MARK: - Views
21 | var body: some View {
22 | Button(action: onClick){
23 | Text(text)
24 | .font(.notoSansMedium12)
25 | .frame(maxWidth: .infinity)
26 | .padding(14)
27 | .foregroundColor(style == .filled ? .white : color)
28 | .background(style == .filled ? color : .clear)
29 | .cornerRadius(50)
30 | .overlay(
31 | RoundedRectangle(cornerRadius: 50)
32 | .inset(by: 0.75)
33 | .stroke(style == .outline ? color : .clear, lineWidth: 1.5)
34 | )
35 | .contentShape(Rectangle())
36 |
37 | }
38 | .buttonStyle(.plain)
39 | }
40 | }
41 |
42 | #Preview {
43 | VStack{
44 | TextButton(onClick: {}, text: "Click Me", style: .filled, color: .primaryNavyBlue)
45 | TextButton(onClick: {}, text: "Click Me", style: .outline, color: .primaryNavyBlue)
46 | TextButton(onClick: {}, text: "Click Me", style: .textOnly, color: .primaryNavyBlue)
47 | HStack{
48 | TextButton(onClick: {}, text: "Click Me", style: .outline, color: .primaryNavyBlue)
49 | TextButton(onClick: {}, text: "Click Me", style: .filled, color: .primaryNavyBlue)
50 | }
51 | }
52 | .padding()
53 | }
54 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/TextKeyValueView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TextKeyValueView.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 09/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct TextKeyValueView: View {
11 | var key: LocalizedStringKey
12 | var value: String
13 |
14 | var body: some View {
15 | HStack{
16 | Text(key) + Text(":")
17 | Text(value)
18 | }.frame(alignment: .leading)
19 | }
20 | }
21 |
22 | #Preview {
23 | TextKeyValueView(key:"Name", value: "User Name")
24 | }
25 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/ReusableViews/UserInfoView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserInfoView.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 12/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct UserInfoView: View{
11 | @Binding var name: String
12 | @Binding var email: String
13 |
14 | var body: some View{
15 | VStack{
16 | Image(systemName: "person.crop.rectangle.fill")
17 | .resizable()
18 | .frame(width: 150, height: 100)
19 | .clipShape(Circle())
20 | TextKeyValueView(key: AppStrings.Name, value: name)
21 | TextKeyValueView(key: AppStrings.Email, value: email)
22 | }
23 | .foregroundColor(.primaryNavyBlue)
24 | }
25 | }
26 |
27 | #Preview {
28 | UserInfoView(name: Binding.constant("name"), email: Binding.constant("email"))
29 | }
30 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/Authentication/AuthorizationScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthenticationScreen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 03/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AuthorizationScreen: View {
11 | @State var password: String = ""
12 | @State var email: String = ""
13 |
14 | //MARK: - Views
15 |
16 | var body: some View {
17 | ZStack {
18 | VStack {
19 | Header(text: AppStrings.LoginScreenTitle)
20 |
21 | CustomTitleTextFieldView(label: AppStrings.Email, placeholder: AppStrings.EmailPlaceHolder, inputText: $email)
22 | .padding(.bottom, 20)
23 |
24 | passwordField
25 |
26 | Spacer()
27 | TextButton(onClick: onLoginButtonClick, text: AppStrings.Login, color: canLogin() ? .primaryNavyBlue : .gray)
28 | }
29 | .padding()
30 | }
31 | .onAppear {
32 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
33 | }
34 | }
35 |
36 | var passwordField : some View {
37 | VStack(alignment: .leading) {
38 | Text(AppStrings.Password)
39 | .font(.notoSansMedium16)
40 | .foregroundColor(.primaryNavyBlue)
41 | SecureField(AppStrings.Password, text: $password)
42 | .padding(14)
43 | .overlay(
44 | RoundedRectangle(cornerRadius: 10)
45 | .stroke(Color.primaryNavyBlue, lineWidth: 1)
46 | )
47 | }
48 | }
49 |
50 | var rectangle: some View {
51 | return overlay (
52 | RoundedRectangle(cornerRadius: 10)
53 | .stroke(Color.primaryNavyBlue, lineWidth: 1)
54 | )
55 | }
56 |
57 | //MARK: - Functions
58 |
59 | private func login() {
60 | Task { @MainActor in
61 | do {
62 | try await AuthenticationManager.shared.login(user: User(email: email, password: password))
63 | }
64 | catch {
65 | ErrorHandler.logError(message: "Error while logging in.", error: error)
66 | }
67 | }
68 | }
69 |
70 | private func onLoginButtonClick() {
71 | if(!canLogin()){
72 | return
73 | }
74 | AnalyticsManager.logButtonClickEvent(buttonType: ButtonType.primary, label: "Login")
75 | login()
76 | }
77 |
78 | private func canLogin() -> Bool{
79 | return !email.isEmpty && !password.isEmpty
80 | }
81 | }
82 |
83 | #Preview {
84 | AuthorizationScreen()
85 | }
86 |
87 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/MainScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainScreen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 03/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MainScreen: View {
11 | var body: some View {
12 | Text("Main Screen")
13 | }
14 | }
15 |
16 | #Preview {
17 | MainScreen()
18 | }
19 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/MainTabView/MainTabCoordinator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainTabCoordinator.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 03/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct MainTabCoordinator: View {
11 | @StateObject var viewModel: MainTabViewModel = MainTabViewModel()
12 | @State var presentSideMenu: Bool = false
13 | var edgeTransition: AnyTransition = .move(edge: .leading)
14 |
15 |
16 | var body: some View {
17 | ZStack(alignment: .top){
18 | VStack{
19 | topSideMenu
20 |
21 | tabView
22 | }
23 |
24 | if (presentSideMenu) {
25 | SideMenuView(selectedSideMenuTab: $viewModel.selectedTab, presentSideMenu: $presentSideMenu)
26 | .transition(edgeTransition)
27 | .animation(.easeInOut, value: presentSideMenu)
28 | }
29 | }
30 | }
31 |
32 | var tabView: some View{
33 | TabView(selection: $viewModel.selectedTab,
34 | content: {
35 | Tab1Screen().tabItem { TabItem(title: getLocalString("Tab")+"1", icon: "1.circle.fill") }.tag(Tab.tab1)
36 | Tab2Screen().tabItem { TabItem(title: getLocalString("Tab")+"2", icon: "2.circle.fill") }.tag(Tab.tab2)
37 | })
38 | .accentColor(.primaryNavyBlue)
39 | .onAppear{
40 | UITabBar.appearance().unselectedItemTintColor = UIColor(Color.secondaryLightBlue)
41 | }
42 | }
43 |
44 | var topSideMenu: some View{
45 | HStack{
46 | Button{
47 | withAnimation{
48 | AnalyticsManager.logButtonClickEvent(buttonType: .secondary, label: "Side menu")
49 | presentSideMenu = true
50 | }
51 | } label: {
52 | Image(systemName: "line.3.horizontal")
53 | .resizable()
54 | .frame(width: 32, height: 24)
55 | }
56 | .foregroundColor(.primaryNavyBlue)
57 | .padding()
58 | Spacer()
59 | }
60 | }
61 | }
62 |
63 | #Preview {
64 | MainTabCoordinator()
65 | }
66 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/MainTabView/TabItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TabItem.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 03/01/24.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | struct TabItem: View {
12 | var title: String
13 | var icon: String
14 |
15 | var body: some View{
16 | VStack{
17 | ZStack{
18 | Image(systemName: icon)
19 | .resizable()
20 | .frame(width: 40, height: 40)
21 | }
22 | Text(title)
23 | .font(.notoSansRegular20)
24 | }
25 | }
26 | }
27 |
28 | #Preview {
29 | TabItem(title: "Home", icon: "house.circle.fill")
30 | }
31 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/MainTabView/TabViews/Tab1Screen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tab1Screen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 23/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct Tab1Screen: View {
11 | var body: some View {
12 | VStack {
13 | Text(AppStrings.Tab) + Text("1")
14 | WeatherScreen() // Use and Delete Screen , This should be removed
15 | }
16 | .onAppear {
17 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
18 | }
19 | }
20 | }
21 |
22 | #Preview {
23 | Tab1Screen()
24 | }
25 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/MainTabView/TabViews/Tab2Screen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tab2Screen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 23/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct Tab2Screen: View {
11 | var body: some View {
12 | VStack {
13 | Text(AppStrings.Tab) + Text("2")
14 | ProfileScreen() // Use and Delete Screen , This should be removed
15 | }
16 | .onAppear {
17 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
18 | }
19 | }
20 | }
21 |
22 | #Preview {
23 | Tab2Screen()
24 | }
25 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/MainTabView/TabViews/Tab3Screen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tab3Screen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 23/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct Tab3Screen: View {
11 | var body: some View {
12 | VStack {
13 | Text(AppStrings.Tab) + Text("3")
14 | WebScreen() // Use and Delete Screen , This should be removed
15 | }
16 | .onAppear {
17 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
18 | }
19 | }
20 | }
21 |
22 | #Preview {
23 | Tab3Screen()
24 | }
25 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/MainTabView/TabViews/Tab4Screen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tab4Screen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 23/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct Tab4Screen: View {
11 | var body: some View {
12 | VStack {
13 | Text(AppStrings.Tab) + Text("4")
14 | }
15 | .onAppear {
16 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
17 | }
18 | }
19 | }
20 |
21 | #Preview {
22 | Tab4Screen()
23 | }
24 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/MainTabView/TabViews/Tab5Screen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Tab5Screen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 23/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct Tab5Screen: View {
11 | var body: some View {
12 | VStack {
13 | Text(AppStrings.Tab) + Text("5")
14 | }
15 | .onAppear {
16 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
17 | }
18 | }
19 | }
20 |
21 | #Preview {
22 | Tab5Screen()
23 | }
24 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/MainTabView/ViewModels/MainTabViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainTabViewModel.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 03/01/24.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | class MainTabViewModel: ObservableObject {
12 | @Published var selectedTab: Tab = .tab1
13 | }
14 |
15 | enum Tab: CaseIterable{
16 | case tab1
17 | case tab2
18 | case tab3
19 | case tab4
20 | case tab5
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/Onboarding/OnboardingScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OnboardingScreen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 03/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct OnboardingScreen: View {
11 | let images: [Image] = [
12 | Image(.onboarding1),
13 | Image(.onboarding2)
14 | ]
15 | let onCompleted: () -> Void
16 | let onBackPressed: () -> Void
17 |
18 | @State private var selectedTab = 0
19 |
20 | var buttonText: LocalizedStringKey {
21 | selectedTab == (images.count-1) ? AppStrings.GetStarted : AppStrings.Next
22 | }
23 |
24 | //MARK: - Views
25 |
26 | var body: some View {
27 | ZStack {
28 | VStack {
29 | Header(text: AppStrings.Onboarding
30 | , hasBackButton: true
31 | ,onBackArrowClick: {
32 | AnalyticsManager.logButtonClickEvent(buttonType: .back, label: "")
33 | onBackPressed() }
34 | )
35 |
36 | pageView
37 |
38 | TextButton(onClick: onNextButtonPressed, text: buttonText)
39 | }
40 | .padding()
41 | }
42 | .onAppear {
43 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
44 | }
45 | }
46 |
47 | var pageView: some View {
48 | TabView(selection: $selectedTab) {
49 |
50 | ForEach(0..())) -> some View{
70 | Button{
71 | action()
72 | } label: {
73 | VStack(alignment: .leading){
74 | HStack(spacing: 20){
75 | Rectangle()
76 | .fill(isSelected ? Color.primaryNavyBlue : .clear)
77 | .frame(width: 5)
78 |
79 | Image(systemName: imageName)
80 | .resizable()
81 | .renderingMode(.template)
82 | .foregroundColor(isSelected ? Color.primaryNavyBlue : .gray)
83 | .frame(width: 26, height: 26)
84 |
85 | Text(title)
86 | .font(.notoSansBold16)
87 | .foregroundColor(isSelected ? Color.primaryNavyBlue : .gray)
88 | Spacer()
89 | }
90 | }
91 | }
92 | .frame(height: 50)
93 | .background(
94 | LinearGradient(colors: [isSelected ? .primaryNavyBlue.opacity(0.5) : .clear, .clear], startPoint: .leading, endPoint: .trailing)
95 | )
96 | }
97 | }
98 |
99 |
100 | #Preview {
101 | SideMenuView(selectedSideMenuTab: Binding.constant(Tab.tab1), presentSideMenu: Binding.constant(true))
102 | }
103 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/Splash/SplashScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SplashScreen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 04/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct SplashScreen: View {
11 | var body: some View {
12 | ZStack {
13 | Color.primaryNavyBlue.ignoresSafeArea(.all)
14 | VStack {
15 | Image(.logo)
16 | .resizable()
17 | .scaledToFit()
18 | .frame(width: 100)
19 | }
20 | .padding()
21 | }
22 | .onAppear {
23 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
24 | }
25 | }
26 | }
27 |
28 | #Preview {
29 | SplashScreen()
30 | }
31 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/TermsAndConditions/TermsAndConditionsScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TermsAndConditionsScreen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 03/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct TermsAndConditionsScreen: View {
11 | let onCompleted: () -> Void
12 | @State var isTermsSelected: Bool = false
13 | @State var isLoading: Bool = true
14 | @State var showError: Bool = false
15 |
16 | //MARK: - Views
17 |
18 | var body: some View {
19 | ZStack {
20 | VStack {
21 | Header(text: AppStrings.TermsAndConditions)
22 |
23 | Text(AppStrings.DummyTermsAndConditions)
24 | .padding(.top, 50)
25 |
26 | termsView
27 |
28 | Spacer()
29 |
30 | TextButton(onClick: { onNextPressed() }, text: AppStrings.Next, color: canGoNext() ? .primaryNavyBlue : .gray)
31 | }
32 | }
33 | .padding()
34 | .onAppear {
35 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
36 | }
37 | }
38 |
39 | var termsView: some View {
40 | Button(action: {
41 | isTermsSelected.toggle()
42 | }) {
43 | HStack(spacing: 12) {
44 | Image(systemName: isTermsSelected ? "checkmark.square" : "square")
45 | .resizable()
46 | .scaledToFit()
47 | .frame(width: 20)
48 | .foregroundColor(.primaryNavyBlue)
49 |
50 | Text(AppStrings.ReadAndAcceptTermsConditions)
51 | .font(.notoSansMedium16)
52 | .foregroundColor(.primaryNavyBlue)
53 | }
54 |
55 | }
56 | .padding(.top, 30)
57 | .frame(maxWidth: .infinity, alignment: .leading)
58 | }
59 |
60 | //MARK: - Functions
61 |
62 | private func canGoNext() -> Bool {
63 | return isTermsSelected
64 | }
65 |
66 | private func onNextPressed() {
67 | if(!canGoNext()) {
68 | AnalyticsManager.logButtonClickEvent(buttonType: ButtonType.primary, label: "Next")
69 | return
70 | }
71 | onCompleted()
72 | }
73 | }
74 |
75 | #Preview {
76 | TermsAndConditionsScreen(onCompleted: {})
77 | }
78 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/UserDetails/UserDetailsScreen.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserDetailsScreen.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 12/04/24.
6 | //
7 |
8 | import SwiftUI
9 | import Foundation
10 |
11 | struct UserDetailsScreen: View {
12 | @State var name: String = ""
13 | @State private var selectedGender: Gender = .male
14 | @State private var dateOfBirth = Date()
15 | @State private var showingDatePicker = false
16 | @State var selectedCountry: String = "India"
17 | var startDate = Calendar.current.date(byAdding: .year, value: -100, to: Date()) ?? Date()
18 | let onCompleted: () -> Void
19 |
20 |
21 | var body: some View {
22 | VStack {
23 | Header(text: AppStrings.UserDetails)
24 |
25 | CustomTitleTextFieldView(label: AppStrings.Name, placeholder: AppStrings.NamePlaceHolder, inputText: $name)
26 | .padding(.vertical, 20)
27 |
28 |
29 | GenderView(selectedGender: $selectedGender)
30 |
31 | CustomTitleTextFieldView(label: AppStrings.DateOfBirth, placeholder: AppStrings.DateOfBirthPlaceHolder, inputText: Binding.constant(formatDate(dateOfBirth)))
32 | .onTapGesture {
33 | self.showingDatePicker = true
34 | }
35 | .popover(isPresented: $showingDatePicker, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) {
36 | DatePickerPopover(isPresented: $showingDatePicker, dateSelection: $dateOfBirth, title: AppStrings.DateOfBirthPlaceHolder, doneButtonLabel: AppStrings.Done)
37 | }
38 | .padding(.vertical, 20)
39 |
40 | CountryView(selectedCountry: $selectedCountry)
41 |
42 | CustomTitleTextFieldView(label: AppStrings.SelectLanguage, placeholder: AppStrings.SelectLanguagePlaceHolder, inputText: Binding.constant(userLanguage))
43 | .onTapGesture {
44 | openDeviceSettings()
45 | }
46 | .padding(.vertical, 20)
47 |
48 | Spacer()
49 |
50 | TextButton(onClick: {
51 | if hasEnteredAllDetails() {
52 | AnalyticsManager.logButtonClickEvent(buttonType: ButtonType.primary, label: "Continue")
53 |
54 | saveUserDetails(name: name, email: UserPreferences.shared.getUser()?.email ?? "", dob: dateOfBirth, gender: selectedGender, country: selectedCountry, language: userLanguage)
55 |
56 | onCompleted()
57 | }
58 | }, text: AppStrings.Continue)
59 | }
60 | .padding(20)
61 | .onAppear {
62 | AnalyticsManager.logScreenView(screenName: String(describing: Self.self))
63 | }
64 | Spacer()
65 | }
66 |
67 | func hasEnteredAllDetails() -> Bool {
68 | return !name.isEmpty && !selectedGender.rawValue.isEmpty && !dateOfBirth.description.isEmpty && !selectedCountry.isEmpty && !selectedCountry.isEmpty
69 | }
70 | }
71 |
72 | #Preview {
73 | UserDetailsScreen(onCompleted: {})
74 | }
75 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Screens/WebView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WebView.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 04/01/24.
6 | //
7 |
8 | import SwiftUI
9 | import WebKit
10 |
11 | struct WebView: UIViewRepresentable {
12 | let urlString: String
13 | @Binding var isLoading: Bool
14 | @Binding var showError: Bool
15 |
16 | func makeUIView(context: Context) -> WKWebView {
17 | let webView = WKWebView()
18 | webView.navigationDelegate = context.coordinator
19 |
20 | if let url = URL(string: urlString) {
21 | let request = URLRequest(url: url)
22 | webView.load(request)
23 | }
24 |
25 | return webView
26 | }
27 |
28 | func updateUIView(_ uiView: WKWebView, context: Context) {}
29 |
30 | func makeCoordinator() -> Coordinator {
31 | Coordinator(parent: self)
32 | }
33 |
34 | class Coordinator: NSObject, WKNavigationDelegate {
35 | var parent: WebView
36 |
37 | init(parent: WebView) {
38 | self.parent = parent
39 | }
40 |
41 | func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
42 | // WebView finished loading
43 | parent.isLoading = false
44 | }
45 |
46 | func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
47 | // WebView failed to load
48 | parent.isLoading = false
49 | parent.showError = true
50 | }
51 |
52 | func webView(_ webView: WKWebView, didFailProvisionalLoadWithError error: Error) {
53 | // WebView failed to load
54 | parent.isLoading = false
55 | parent.showError = true
56 | }
57 |
58 | func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
59 | // WebView failed to load
60 | parent.isLoading = false
61 | parent.showError = true
62 | }
63 |
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Services/UserService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserService.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 10/01/24.
6 | //
7 |
8 | import Foundation
9 |
10 | class UserService {
11 | static func updateUser(user: User) async throws -> User {
12 | try await NetworkManager.shared.request(.updateUser(user: user), type: User.self)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Utils/AppConfig.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppConfig.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 08/01/24.
6 | //
7 | import Foundation
8 |
9 | enum AppConfig {
10 |
11 | private static let configDict: [String: Any] = {
12 | guard let dict = Bundle.main.infoDictionary else {
13 | fatalError("info.plist not found")
14 | }
15 |
16 | return dict
17 | }()
18 |
19 | static let BASE_URL: URL = {
20 | guard let urlString = configDict[Constants.BASE_URL] as? String else {
21 | fatalError("base url not found")
22 | }
23 |
24 | guard let url = URL(string: urlString) else {
25 | fatalError("invalid url")
26 | }
27 |
28 | return url
29 | }()
30 |
31 | static let EXAMPLE_KEY : String = {
32 | guard let key = configDict[Constants.EXAMPLE_KEY] as? String else {
33 | fatalError("example key not found")
34 | }
35 |
36 | return key
37 | }()
38 | }
39 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Utils/AppStrings.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppStrings.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 25/04/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct AppStrings {
11 | //MARK: - AuthorizationScreen
12 | static let LoginScreenTitle: LocalizedStringKey = "Login/SignUp"
13 | static let Login: LocalizedStringKey = "Login"
14 | static let Password: LocalizedStringKey = "Password"
15 |
16 | //MARK: - AcceptTermsAndConditionsScreen
17 | static let TermsAndConditions: LocalizedStringKey = "Terms&Conditions"
18 | static let DummyTermsAndConditions: LocalizedStringKey = "DummyTermsAndConditions"
19 | static let ReadAndAcceptTermsConditions: LocalizedStringKey = "ReadAndAcceptTerms&Conditions"
20 |
21 | //MARK: - OnboardingScreen
22 | static let Onboarding: LocalizedStringKey = "Onboarding"
23 | static let Next: LocalizedStringKey = "Next"
24 | static let GetStarted: LocalizedStringKey = "GetStarted"
25 |
26 | //MARK: - Settings
27 | static let Settings: LocalizedStringKey = "Settings"
28 | static let Name: LocalizedStringKey = "Name"
29 | static let Email: LocalizedStringKey = "Email"
30 | static let Gender: LocalizedStringKey = "Gender"
31 | static let DateOfBirth: LocalizedStringKey = "DateOfBirth"
32 | static let Country: LocalizedStringKey = "Country"
33 | static let Language: LocalizedStringKey = "Language"
34 | static let Logout: LocalizedStringKey = "Logout"
35 | static let DeleteAccount: LocalizedStringKey = "DeleteAccount"
36 | static let Update: LocalizedStringKey = "Update"
37 | static let Appearance: LocalizedStringKey = "Appearance"
38 | static let Light: LocalizedStringKey = "Light"
39 | static let Dark: LocalizedStringKey = "Dark"
40 | static let LogoutBottomSheetTitle: LocalizedStringKey = "LogoutBottomSheetTitle"
41 | static let DeleteAccountBottomSheetTitle: LocalizedStringKey = "DeleteAccountBottomSheetTitle"
42 | static let LogoutBottomSheetSubTitle: LocalizedStringKey = "LogoutBottomSheetSubTitle"
43 | static let DeleteAccountBottomSheetSubTitle: LocalizedStringKey = "DeleteAccountBottomSheetSubTitle"
44 |
45 | //MARK: - User Details
46 | static let UpdateUserDetails: LocalizedStringKey = "UpdateUserDetails"
47 | static let NamePlaceHolder: LocalizedStringKey = "NamePlaceHolder"
48 | static let EmailPlaceHolder: LocalizedStringKey = "EmailPlaceHolder"
49 | static let GenderPlaceHolder: LocalizedStringKey = "GenderPlaceHolder"
50 | static let DateOfBirthPlaceHolder: LocalizedStringKey = "DateOfBirthPlaceHolder"
51 | static let SelectCountry: LocalizedStringKey = "SelectCountry"
52 | static let SelectCountryPlaceHolder: LocalizedStringKey = "SelectCountryPlaceHolder"
53 | static let SelectLanguage: LocalizedStringKey = "SelectLanguage"
54 | static let SelectLanguagePlaceHolder: LocalizedStringKey = "SelectLanguagePlaceHolder"
55 | static let Done: LocalizedStringKey = "Done"
56 | static let SaveUserInfoBottomSheetTitle: LocalizedStringKey = "SaveUserInfoBottomSheetTitle"
57 | static let SaveUserInfoBottomSheetSubTitle: LocalizedStringKey = "SaveUserInfoBottomSheetSubTitle"
58 | static let UserDetails: LocalizedStringKey = "UserDetails"
59 | static let Continue: LocalizedStringKey = "Continue"
60 |
61 | //MARK: - Details
62 | static let Details: LocalizedStringKey = "Details"
63 | static let Cancel: LocalizedStringKey = "Cancel"
64 | static let YesSure: LocalizedStringKey = "YesSure"
65 | static let Tab: LocalizedStringKey = "Tab"
66 |
67 | //MARK: - Gender
68 | static let Male: LocalizedStringKey = "Male"
69 | static let Female: LocalizedStringKey = "Female"
70 | static let Other: LocalizedStringKey = "Other"
71 |
72 | //MARK: - Weather
73 | static let Weather: String = "Weather"
74 | static let Summary: String = "Summary"
75 | static let Temperature: String = "Temperature"
76 | static let Humidity: String = "Humidity"
77 | static let TemperatureIs: LocalizedStringKey = "TemperatureIs"
78 | static let RealFeelIs: LocalizedStringKey = "RealFeelIs"
79 | static let MaxItWillGoIs: LocalizedStringKey = "MaxItWillGoIs"
80 | static let MinItWillFallIs: LocalizedStringKey = "MinItWillFallIs"
81 | static let YouCanSeeAsFarAs: LocalizedStringKey = "YouCanSeeAsFarAs"
82 | static let ThePressureYouBeFeelingIs: LocalizedStringKey = "ThePressureYouBeFeelingIs"
83 |
84 | //MARK: - Cities
85 | static let Delhi: LocalizedStringKey = "Delhi"
86 | static let Jaipur: LocalizedStringKey = "Jaipur"
87 | static let Mumbai: LocalizedStringKey = "Mumbai"
88 | static let Chennai: LocalizedStringKey = "Chennai"
89 | static let Bengaluru: LocalizedStringKey = "Bengaluru"
90 | static let Kolkata: LocalizedStringKey = "Kolkata"
91 |
92 | static let Error: LocalizedStringKey = "Error"
93 | static let ErrorMessage: LocalizedStringKey = "ErrorMessage"
94 | static let OK: LocalizedStringKey = "OK"
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Utils/AppearancePreference.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppearancePreference.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 17/04/24.
6 | //
7 |
8 | import Foundation
9 | import SwiftUI
10 |
11 | enum AppearanceMode: String {
12 | case light, dark
13 | }
14 |
15 | struct Preferences {
16 | static var appearanceMode: AppearanceMode {
17 | get {
18 | let storedValue = UserPreferences.shared.selectedAppearance
19 | return AppearanceMode(rawValue: storedValue) ?? .light
20 | }
21 | set {
22 | UserPreferences.shared.selectedAppearance = newValue.rawValue
23 | applyAppearance(newValue)
24 | }
25 | }
26 |
27 | static func applyAppearance(_ mode: AppearanceMode) {
28 | DispatchQueue.main.async {
29 | if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
30 | windowScene.windows.forEach { window in
31 | switch mode {
32 | case .light:
33 | window.overrideUserInterfaceStyle = .light
34 | case .dark:
35 | window.overrideUserInterfaceStyle = .dark
36 | }
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Utils/Colors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Colors.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 08/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 |
11 | extension Color {
12 |
13 | //Add colors here
14 | static let primaryNavyBlue = fromHexString("#144580")
15 | static let secondaryLightBlue = fromHexString("#50cfff")
16 |
17 | // other colors
18 |
19 | static func fromHexString(_ hex:String) -> Color {
20 | let scanner = Scanner(string: hex)
21 | _ = scanner.scanString("#")
22 |
23 | var rgbValue: UInt64 = 0
24 | scanner.scanHexInt64(&rgbValue)
25 |
26 | let r = Double((rgbValue & 0xFF0000) >> 16) / 255.0
27 | let g = Double((rgbValue & 0x00FF00) >> 8) / 255.0
28 | let b = Double(rgbValue & 0x0000FF) / 255.0
29 |
30 | return Color(red: r, green: g, blue: b)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Utils/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 08/01/24.
6 | //
7 |
8 | struct Constants {
9 | static let BASE_URL = "BASE_URL"
10 | static let EXAMPLE_KEY = "EXAMPLE_KEY"
11 |
12 | static let weatherAppId = "1d8b7e6f3849be9a808176f247698ec3"
13 |
14 | static let countriesOptions = ["India", "USA", "UK", "France", "China", "UAE", "Other"]
15 | }
16 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Utils/ErrorHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ErrorHandler.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 10/01/24.
6 | //
7 |
8 | import FirebaseCrashlytics
9 |
10 |
11 | class ErrorHandler {
12 |
13 | static func logError(message: String, error: Error) {
14 | Crashlytics.crashlytics().log(message)
15 |
16 | let nsError = error as NSError
17 | Crashlytics.crashlytics().record(error: nsError)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Utils/Fonts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Fonts.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 08/01/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | extension Font {
11 |
12 | static let notoSansRegular12: Font = Font.custom("NotoSans-Regular", size: 12)
13 | static let notoSansRegular16: Font = Font.custom("NotoSans-Regular", size: 16)
14 | static let notoSansRegular20: Font = Font.custom("NotoSans-Regular", size: 20)
15 | static let notoSansRegular24: Font = Font.custom("NotoSans-Regular", size: 24)
16 |
17 | static let notoSansMedium12: Font = Font.custom("NotoSans-Medium", size: 12)
18 | static let notoSansMedium16: Font = Font.custom("NotoSans-Medium", size: 16)
19 | static let notoSansMedium20: Font = Font.custom("NotoSans-Medium", size: 20)
20 | static let notoSansMedium24: Font = Font.custom("NotoSans-Medium", size: 24)
21 |
22 | static let notoSansSemiBold12: Font = Font.custom("NotoSans-SemiBold", size: 12)
23 | static let notoSansSemiBold16: Font = Font.custom("NotoSans-SemiBold", size: 16)
24 | static let notoSansSemiBold20: Font = Font.custom("NotoSans-SemiBold", size: 20)
25 | static let notoSansSemiBold24: Font = Font.custom("NotoSans-SemiBold", size: 24)
26 |
27 | static let notoSansBold12: Font = Font.custom("NotoSans-Bold", size: 12)
28 | static let notoSansBold16: Font = Font.custom("NotoSans-Bold", size: 16)
29 | static let notoSansBold20: Font = Font.custom("NotoSans-Bold", size: 20)
30 | static let notoSansBold24: Font = Font.custom("NotoSans-Bold", size: 24)
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Utils/KeyChainStorage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KeyChainStorage.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 10/01/24.
6 | //
7 |
8 | import KeychainSwift
9 |
10 | class KeyChainStorage {
11 | static let shared = KeyChainStorage()
12 |
13 | private init() {}
14 |
15 | private let keychainInstance = KeychainSwift()
16 |
17 | private let AUTH_TOKEN: String = "AUTH_TOKEN"
18 | private let PASSWORD: String = "PASSWORD"
19 |
20 | func setAuthToken(_ value: String) -> Bool {
21 | return keychainInstance.set(value, forKey: AUTH_TOKEN)
22 | }
23 |
24 | func getAuthToken() -> String? {
25 | keychainInstance.get(AUTH_TOKEN)
26 | }
27 |
28 | func setPassword(_ value: String) {
29 | keychainInstance.set(value, forKey: PASSWORD)
30 | }
31 |
32 | func getPassword() -> String {
33 | keychainInstance.get(PASSWORD)!
34 | }
35 |
36 | func deleteAllKey(){
37 | keychainInstance.delete(AUTH_TOKEN)
38 | keychainInstance.delete(PASSWORD)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Utils/UserPreferences.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UserPreferences.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Gunveer Sandhu on 10/01/24.
6 | //
7 |
8 | import Foundation
9 |
10 | class UserPreferences{
11 |
12 | static let shared = UserPreferences()
13 |
14 | private let defaults = UserDefaults.standard
15 |
16 | private init() {}
17 |
18 | enum Keys {
19 | static let user = "user"
20 | static let isAuthenticated = "isAuthenticated"
21 | static let isProfileComplete = "isProfileComplete"
22 | static let isPrivacyPolicyAccepted = "isPrivacyPolicyAccepted"
23 | static let isOnboardingCompleted = "isOnboardingCompleted"
24 | static let selectedAppearance = "selectedAppearance"
25 | }
26 |
27 | func deleteAllUserDefaults() {
28 | let domain = Bundle.main.bundleIdentifier!
29 | defaults.removePersistentDomain(forName: domain)
30 | defaults.synchronize()
31 | }
32 |
33 | func saveUser(user: User) {
34 | let encoder = JSONEncoder()
35 | if let encoded = try? encoder.encode(user) {
36 | defaults.set(encoded, forKey: Keys.user)
37 | defaults.synchronize()
38 | }
39 | }
40 |
41 | func getUser() -> User? {
42 | if let savedUser = defaults.object(forKey: Keys.user) as? Data {
43 | let decoder = JSONDecoder()
44 | if let loadedUser = try? decoder.decode(User.self, from: savedUser) {
45 | return loadedUser
46 | }
47 | }
48 | return nil
49 | }
50 |
51 | var isAuthenticated: Bool {
52 | set{
53 | defaults.setValue(newValue, forKey: Keys.isAuthenticated)
54 | }
55 | get{
56 | return defaults.bool(forKey: Keys.isAuthenticated)
57 | }
58 | }
59 |
60 | var isProfileComplete: Bool {
61 | set{
62 | defaults.setValue(newValue, forKey: Keys.isProfileComplete)
63 | }
64 | get{
65 | return defaults.bool(forKey: Keys.isProfileComplete)
66 | }
67 | }
68 |
69 | var isPrivacyPolicyAccepted: Bool {
70 | set{
71 | defaults.setValue(newValue, forKey: Keys.isPrivacyPolicyAccepted)
72 | }
73 | get{
74 | return defaults.bool(forKey: Keys.isPrivacyPolicyAccepted)
75 | }
76 | }
77 |
78 | var isOnboardingCompleted: Bool {
79 | set{
80 | defaults.setValue(newValue, forKey: Keys.isOnboardingCompleted)
81 | }
82 | get{
83 | return defaults.bool(forKey: Keys.isOnboardingCompleted)
84 | }
85 | }
86 |
87 | var selectedAppearance: String {
88 | set{
89 | defaults.setValue(newValue, forKey: Keys.selectedAppearance)
90 | }
91 | get{
92 | return defaults.string(forKey: Keys.selectedAppearance) ?? AppearanceMode.light.rawValue
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/SwiftAppTemplate/Utils/Utility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Utility.swift
3 | // SwiftAppTemplate
4 | //
5 | // Created by Vijay Goswami on 10/01/24.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | enum Gender: String, CaseIterable, Identifiable {
12 | case male = "Male"
13 | case female = "Female"
14 | case other = "Other"
15 |
16 | var id: String { self.rawValue }
17 | }
18 |
19 | var userLanguage: String = {
20 | guard let languageCode = Locale.current.languageCode,
21 | let languageName = Locale.current.localizedString(forLanguageCode: languageCode) else {
22 | return "English"
23 | }
24 | return languageName
25 | }()
26 |
27 | func saveUserDetails(name: String, email: String, dob: Date, gender: Gender, country: String, language: String){
28 | let user = User(name: name, email: email, password: KeyChainStorage.shared.getPassword(), dob: formatDate(dob), gender: gender.rawValue, country: country, language: language)
29 | UserPreferences.shared.saveUser(user: user)
30 | }
31 |
32 | func removeFocusFromTextField(){
33 | UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
34 | }
35 |
36 | func kelvinToCelsius(kelvinTemp: Double) -> Double {
37 | let celsius = kelvinTemp - 273.15
38 | return celsius.rounded(toPlaces: 2)
39 | }
40 |
41 | func formatDate(_ date: Date) -> String {
42 | let formatter = DateFormatter()
43 | formatter.dateFormat = "dd-MM-yyyy"
44 | return formatter.string(from: date)
45 | }
46 |
47 | func openDeviceSettings() {
48 | guard let url = URL(string: UIApplication.openSettingsURLString) else {
49 | return
50 | }
51 | UIApplication.shared.open(url)
52 | }
53 |
54 | extension Double {
55 | func formattedAsIntegerOrDecimal() -> String {
56 | return self.truncatingRemainder(dividingBy: 1) == 0 ? String(format: "%.0f", self) : String(self)
57 | }
58 |
59 | func rounded(toPlaces places: Int) -> Double {
60 | let multiplier = pow(10.0, Double(places))
61 | return (self * multiplier).rounded() / multiplier
62 | }
63 | }
64 |
65 | extension String {
66 | var nilIfEmpty: String? {
67 | self == "" ? nil : self
68 | }
69 |
70 | func formattedDate(format: String) -> Date? {
71 | let formatter = DateFormatter()
72 | formatter.dateFormat = format
73 | return formatter.date(from: self)
74 | }
75 | }
76 |
77 | extension String? {
78 | func isNotNullOrEmpty() -> Bool{
79 | return !(self == nil || self!.isEmpty)
80 | }
81 | }
82 |
83 | func getLocalString(_ key: String) -> String {
84 | return NSLocalizedString(key, comment: "")
85 | }
86 |
--------------------------------------------------------------------------------
/create_swift_app.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Ensure execution permissions for the script
4 | chmod +x "$0"
5 |
6 | # Store the list of installed gems before installation
7 | before_install=$(gem list --local | cut -d" " -f1)
8 |
9 | # Check if the xcodeproj is installed, if not install it
10 | if ! gem list -i xcodeproj > /dev/null 2>&1; then
11 | echo "Installing xcodeproj..."
12 | gem install xcodeproj --user-install
13 | XCODEPROJ_INSTALLED=true
14 | else
15 | echo "xcodeproj gem is already installed."
16 | fi
17 |
18 | # Store the list of installed gems after installation
19 | after_install=$(gem list --local | cut -d" " -f1)
20 |
21 | # Find the newly installed gems
22 | newly_installed=$(comm -13 <(echo "$before_install" | sort) <(echo "$after_install" | sort))
23 |
24 | # Get new app name from user
25 | echo -e "\033[31mEnter the new app name: \033[0m\c"
26 | read NEW_APP_NAME
27 | if [ -z "$NEW_APP_NAME" ]; then
28 | echo "Error: New app name cannot be empty."
29 | exit 1
30 | fi
31 |
32 | OLD_APP_NAME="SwiftAppTemplate"
33 | DESTINATION_PATH="$HOME/Desktop/$NEW_APP_NAME"
34 |
35 | # Create the destination directory if it doesn't exist
36 | mkdir -p "$DESTINATION_PATH"
37 |
38 | git clone https://github.com/shurutech/iOSKickstart.git "$DESTINATION_PATH"
39 |
40 | # Navigate to the cloned directory
41 | cd "$DESTINATION_PATH" || exit
42 |
43 | # Rename the .xcodeproj directory
44 | mv "$OLD_APP_NAME.xcodeproj" "$NEW_APP_NAME.xcodeproj"
45 |
46 | # Rename the main project folder containing Swift files and views
47 | mv "$OLD_APP_NAME" "$NEW_APP_NAME"
48 |
49 | # Check if .git directory exists and remove it
50 | if [ -d ".git" ]; then
51 | rm -rf .git
52 | echo "Unlinked project from Git repository."
53 | else
54 | echo "Project is not linked to a Git repository."
55 | fi
56 |
57 | function delete_lines() {
58 | if [ "$#" -lt 2 ]; then
59 | echo "Usage: delete_lines "
60 | return 1
61 | fi
62 |
63 | file_path="$1"
64 | shift
65 | line_numbers=("$@")
66 |
67 | if [ ! -f "$file_path" ]; then
68 | echo "Error: File not found - $file_path"
69 | return 1
70 | fi
71 |
72 | sed_args=""
73 | for arg in "${line_numbers[@]}"; do
74 | if [[ "$arg" == *-* ]]; then
75 | # Handle ranges
76 | start=$(echo "$arg" | cut -d'-' -f1)
77 | end=$(echo "$arg" | cut -d'-' -f2)
78 | sed_args="${sed_args}${start},${end}d; "
79 | else
80 | # Individual line numbers
81 | sed_args="${sed_args}${arg}d; "
82 | fi
83 | done
84 |
85 | sed_args="${sed_args%??}" # Remove the trailing "; "
86 |
87 | sed -i.bak "$sed_args" "$file_path"
88 | echo "Lines ${line_numbers[*]} deleted from $file_path"
89 | }
90 |
91 |
92 |
93 | # Define the paths to your configuration files
94 | DEBUG_CONFIG_FILE="$DESTINATION_PATH/$NEW_APP_NAME/Configuration/Debug.xcconfig"
95 | RELEASE_CONFIG_FILE="$DESTINATION_PATH/$NEW_APP_NAME/Configuration/Release.xcconfig"
96 |
97 | # Define the new values for APP_NAME and APP_BUNDLE_ID
98 | NEW_APP_BUNDLE_ID="com.example.$NEW_APP_NAME"
99 | OLD_BUNDLE_ID="com.shurutech.$OLD_APP_NAME"
100 |
101 | # Update APP_NAME and APP_BUNDLE_ID in the Debug Configuration
102 | sed -i '' "s/$OLD_BUNDLE_ID"Debug"/$NEW_APP_BUNDLE_ID"Debug"/g" "$DEBUG_CONFIG_FILE"
103 | sed -i '' "s/$OLD_APP_NAME Debug/$NEW_APP_NAME Debug/g" "$DEBUG_CONFIG_FILE"
104 |
105 | # Update APP_NAME and APP_BUNDLE_ID in the Release Configuration
106 | sed -i '' "s/$OLD_BUNDLE_ID/$NEW_APP_BUNDLE_ID/g" "$RELEASE_CONFIG_FILE"
107 | sed -i '' "s/$OLD_APP_NAME/$NEW_APP_NAME/g" "$RELEASE_CONFIG_FILE"
108 |
109 | # Ask the user to include the side menu or not
110 | echo -e "\033[31mDo you require a side menu? (Yn): \033[0m\c"
111 | read REQUIRE_SIDE_MENU
112 | REMOVE_SIDE_MENU=false
113 |
114 | if [ "$REQUIRE_SIDE_MENU" = "n" ] || [ "$REQUIRE_SIDE_MENU" = "N" ]; then
115 | REMOVE_SIDE_MENU=true
116 | pathOfSideMenuFolder="$DESTINATION_PATH/$NEW_APP_NAME/Screens/SideMenu"
117 |
118 | echo "Removing folder: $pathOfSideMenuFolder"
119 | rm -rf "$pathOfSideMenuFolder"
120 |
121 | pathToMainCoordinator="$DESTINATION_PATH/$NEW_APP_NAME/Screens/MainTabView/MainTabCoordinator.swift"
122 |
123 |
124 | # Check and update permissions
125 | if [ ! -r "$pathToMainCoordinator" ]; then
126 | chmod +r "$pathToMainCoordinator"
127 | fi
128 |
129 | #Removing from MainTabCoordinator
130 | delete_lines "$pathToMainCoordinator" 12 19 24-28 44-60
131 |
132 | elif [ "$REQUIRE_SIDE_MENU" = "y" ] || [ "$REQUIRE_SIDE_MENU" = "Y" ]; then
133 | echo "Side menu will be added."
134 | else
135 | echo "Incorrect input given."
136 | fi
137 |
138 | # Ask user to enter number of tabs required in app
139 | echo -e "\033[31mEnter the number of tabs between 2 to 5: \033[0m\c"
140 | read NUM_TABS
141 |
142 | if [ "$NUM_TABS" -ge 2 ] && [ "$NUM_TABS" -le 5 ]; then
143 | echo "Continuing with $NUM_TABS tabs."
144 | else
145 | echo "Invalid number of tabs. Please enter a number between 2 and 5."
146 | exit 1
147 | fi
148 |
149 | declare -a TAB_TITLES
150 | declare -a TAB_ICONS
151 |
152 | for ((i=1; i<=NUM_TABS; i++)); do
153 | TAB_TITLES[$i]="Tab$i"
154 | TAB_ICONS[$i]="$i.circle.fill"
155 | done
156 |
157 | TAB_ENUM_CODE="enum Tab: CaseIterable{"
158 | TAB_VIEW_CODE="TabView(selection: \$viewModel.selectedTab, content: {"
159 |
160 | for ((i=1; i<=NUM_TABS; i++)); do
161 | title=${TAB_TITLES[$i]}
162 | icon=${TAB_ICONS[$i]}
163 | viewName="${title}Screen()"
164 | TAB_ENUM_CODE+="
165 | case $(echo $title | awk '{print tolower($0)}')"
166 | TAB_VIEW_CODE+="
167 | $viewName.tabItem { TabItem(title: \"$title\", icon: \"$icon\") }.tag(Tab.$(echo $title | awk '{print tolower($0)}'))"
168 | done
169 |
170 | TAB_VIEW_CODE+="
171 | })"
172 | TAB_ENUM_CODE+="
173 | }"
174 |
175 | SWIFT_FILE_PATH="$DESTINATION_PATH/$NEW_APP_NAME/Screens/MainTabView/MainTabCoordinator.swift"
176 | SWIFT_VIEW_MODEL_FILE_PATH="$DESTINATION_PATH/$NEW_APP_NAME/Screens/MainTabView/ViewModels/MainTabViewModel.swift"
177 |
178 | # Write the new tab view code to a temporary file
179 | echo "$TAB_VIEW_CODE" > tab_view_temp.txt
180 | echo "$TAB_ENUM_CODE" > tab_enum_temp.txt
181 |
182 | # Find the start line of the old TabView code
183 | START_LINE=$(grep -n "TabView(selection: \$viewModel.selectedTab," "$SWIFT_FILE_PATH" | head -1 | cut -d: -f1)
184 | START_LINE_ENUM=$(grep -n "enum Tab: CaseIterable{" "$SWIFT_VIEW_MODEL_FILE_PATH" | head -1 | cut -d: -f1)
185 |
186 |
187 | # Find the end line of the old TabView code, looking for the line with '})'
188 | END_LINE=$(awk -v start="$START_LINE" 'NR > start && /}\)/ {print NR; exit}' "$SWIFT_FILE_PATH")
189 | END_LINE_ENUM=$(awk -v start="$START_LINE_ENUM" 'NR > start && /}/ {print NR; exit}' "$SWIFT_VIEW_MODEL_FILE_PATH")
190 |
191 |
192 | # Delete the old TabView section
193 | sed -i '' "${START_LINE},${END_LINE}d" "$SWIFT_FILE_PATH"
194 | sed -i '' "${START_LINE_ENUM},${END_LINE_ENUM}d" "$SWIFT_VIEW_MODEL_FILE_PATH"
195 |
196 | # Insert the new TabView code before the original start line
197 | awk -v line="$START_LINE" -v file="tab_view_temp.txt" 'NR==line{system("cat " file)} {print}' "$SWIFT_FILE_PATH" > temp.swift && mv temp.swift "$SWIFT_FILE_PATH"
198 | awk -v line="$START_LINE_ENUM" -v file="tab_enum_temp.txt" 'NR==line{system("cat " file)} {print}' "$SWIFT_VIEW_MODEL_FILE_PATH" > temp.swift && mv temp.swift "$SWIFT_VIEW_MODEL_FILE_PATH"
199 |
200 | # Remove the temporary file
201 | rm tab_view_temp.txt
202 | rm tab_enum_temp.txt
203 |
204 | for ((i=5; i>NUM_TABS; i--)); do
205 | rm -f "$DESTINATION_PATH/$NEW_APP_NAME/Screens/MainTabView/TabViews/Tab${i}Screen.swift"
206 | done
207 |
208 |
209 |
210 |
211 | # Ask the user to include the tnc screen or not
212 | echo -e "\033[31mDo you require a terms and condition screen? (Yn): \033[0m\c"
213 | read REQUIRE_TNC_SCREEN
214 | REMOVE_TNC_SCREEN=false
215 |
216 | if [ "$REQUIRE_TNC_SCREEN" = "n" ] || [ "$REQUIRE_TNC_SCREEN" = "N" ]; then
217 | REMOVE_TNC_SCREEN=true
218 | pathOfTncFolder="$DESTINATION_PATH/$NEW_APP_NAME/Screens/TermsAndConditions"
219 |
220 | echo "Removing folder: $pathOfTncFolder"
221 | rm -rf "$pathOfTncFolder"
222 |
223 | pathToRootCoordinator="$DESTINATION_PATH/$NEW_APP_NAME/Screens/Root/RootCoordinator.swift"
224 | pathToRootViewModel="$DESTINATION_PATH/$NEW_APP_NAME/Screens/Root/RootViewModel.swift"
225 | pathToOnBoardingScreen="$DESTINATION_PATH/$NEW_APP_NAME/Screens/Onboarding/OnboardingScreen.swift"
226 |
227 |
228 | # Check and update permissions
229 | if [ ! -r "$pathToRootCoordinator" ] || [ ! -r "$pathToRootViewModel" ] || [ ! -r "$pathToOnBoardingScreen" ]; then
230 | # echo "Adding read permissions to $pathToMainCoordinator"
231 | chmod +r "$pathToRootCoordinator"
232 | chmod +r "$pathToRootViewModel"
233 | chmod +r "$pathToOnBoardingScreen"
234 | fi
235 |
236 | #Removing from root coordinator
237 | delete_lines "$pathToRootCoordinator" 15 41-42 45 54 82-84
238 |
239 | #Removing from RootViewModel
240 | delete_lines "$pathToRootViewModel" 17 35 46-51
241 |
242 | #Removing from OnBoardingScreen
243 | delete_lines "$pathToOnBoardingScreen" 30-33
244 |
245 | elif [ "$REQUIRE_TNC_SCREEN" = "y" ] || [ "$REQUIRE_TNC_SCREEN" = "Y" ]; then
246 | echo "Terms and conditions screen will be added."
247 | else
248 | echo "Incorrect input given."
249 | fi
250 |
251 |
252 | # Ask the user to include the onboarding screen or not
253 | echo -e "\033[31mDo you require an onboarding screen? (Yn): \033[0m\c"
254 | read REQUIRE_ONBOARDING_SCREEN
255 | REMOVE_ONBOARDING_SCREEN=false
256 |
257 | if [ "$REQUIRE_ONBOARDING_SCREEN" = "n" ] || [ "$REQUIRE_ONBOARDING_SCREEN" = "N" ]; then
258 | REMOVE_ONBOARDING_SCREEN=true
259 | pathOfOnboardingFolder="$DESTINATION_PATH/$NEW_APP_NAME/Screens/Onboarding"
260 |
261 | echo "Removing folder: $pathOfOnboardingFolder"
262 | rm -rf "$pathOfOnboardingFolder"
263 |
264 | pathToRootCoordinator="$DESTINATION_PATH/$NEW_APP_NAME/Screens/Root/RootCoordinator.swift"
265 | pathToRootViewModel="$DESTINATION_PATH/$NEW_APP_NAME/Screens/Root/RootViewModel.swift"
266 |
267 | # Check and update permissions
268 | if [ ! -r "$pathToRootCoordinator" ] || [ ! -r "$pathToRootViewModel" ]; then
269 | # echo "Adding read permissions to $pathToMainCoordinator"
270 | chmod +r "$pathToRootCoordinator"
271 | chmod +r "$pathToRootViewModel"
272 | fi
273 |
274 | #Removing from rootCoordinator
275 | if $REMOVE_TNC_SCREEN; then
276 | delete_lines "$pathToRootCoordinator" 15 40-42 50 77-79
277 | else
278 | delete_lines "$pathToRootCoordinator" 16 43-46 55 85-87
279 | fi
280 |
281 | #Removing from RootViewModel
282 | if $REMOVE_TNC_SCREEN; then
283 | delete_lines "$pathToRootViewModel" 17 34 45-50
284 | else
285 | delete_lines "$pathToRootViewModel" 18 36 53-58
286 | fi
287 |
288 |
289 | elif [ "$REQUIRE_ONBOARDING_SCREEN" = "y" ] || [ "$REQUIRE_ONBOARDING_SCREEN" = "Y" ]; then
290 | echo "Onboarding screen will be added."
291 | else
292 | echo "Incorrect input given."
293 | fi
294 |
295 |
296 | # Ask about localization support
297 | echo -e "\033[32mApp has additional language support - French, Hindi, Spanish, Chinese, Arabic. \033[0m"
298 | echo -e "\033[31mDo you want to disable additional language support? (Y/n): \033[0m\c"
299 | read disable_localization
300 | if [ "$disable_localization" = "Y" ] || [ "$disable_localization" = "y" ]; then
301 | LOCALIZATION_DISABLED=true
302 | else
303 | LOCALIZATION_DISABLED=false
304 | fi
305 |
306 | # Function to disable localization
307 | function disable_localization() {
308 |
309 | local app_path="$1"
310 | echo "Disabling additional language support..."
311 |
312 | find "$app_path" -name "*.lproj" ! -name "en.lproj" -exec rm -rf {} \;
313 | echo "Additional language support has been disabled."
314 |
315 | pathToUserDetailsScreen="$DESTINATION_PATH/$NEW_APP_NAME/Screens/UserDetails/UserDetailsScreen.swift"
316 | pathToEditUserDetailsScreen="$DESTINATION_PATH/$NEW_APP_NAME/Dummy-Use&Delete/EditUserDetailsScreen.swift"
317 | pathToSettingsScreen="$DESTINATION_PATH/$NEW_APP_NAME/Dummy-Use&Delete/SettingsScreen.swift"
318 |
319 | # Check and update permissions
320 | if [ ! -r "$pathToUserDetailsScreen" ] || [ ! -r "$pathToEditUserDetailsScreen" ] || [ ! -r "$pathToSettingsScreen" ]; then
321 | chmod +r "$pathToUserDetailsScreen"
322 | chmod +r "$pathToEditUserDetailsScreen"
323 | chmod +r "$pathToSettingsScreen"
324 | fi
325 |
326 | delete_lines "$pathToUserDetailsScreen" 42-46
327 |
328 | delete_lines "$pathToEditUserDetailsScreen" 50-53
329 |
330 | delete_lines "$pathToSettingsScreen" 61
331 | }
332 |
333 | if $LOCALIZATION_DISABLED; then
334 | disable_localization "$DESTINATION_PATH"
335 | fi
336 |
337 |
338 | # Ask the user to include the Appearance dark/light feature in from settings
339 | echo -e "\033[31mDo you want to enable the dark/light appearance feature in the settings screen? (Yn): \033[0m\c"
340 | read REQUIRE_APPEARANCE_SCREEN
341 | REMOVE_APPEARANCE_SCREEN=false
342 |
343 | if [ "$REQUIRE_APPEARANCE_SCREEN" = "n" ] || [ "$REQUIRE_APPEARANCE_SCREEN" = "N" ]; then
344 | REMOVE_APPEARANCE_SCREEN=true
345 |
346 | pathToSettings="$DESTINATION_PATH/$NEW_APP_NAME/Dummy-Use&Delete/SettingsScreen.swift"
347 | pathToLaunchApp="$DESTINATION_PATH/$NEW_APP_NAME/LaunchApp.swift"
348 | pathToUserPreferences="$DESTINATION_PATH/$NEW_APP_NAME/Utils/UserPreferences.swift"
349 |
350 | if [ ! -r "$pathToSettings" ] || [ ! -r "$pathToLaunchApp" ]; then
351 | chmod +r "$pathToSettings"
352 | chmod +r "$pathToLaunchApp"
353 | fi
354 |
355 | delete_lines "$pathToSettings" 16 31-32 121-139
356 |
357 | delete_lines "$pathToLaunchApp" 17-19
358 |
359 | delete_lines "$pathToUserPreferences" 24 87-94
360 |
361 | elif [ "$REQUIRE_APPEARANCE_SCREEN" = "y" ] || [ "$REQUIRE_APPEARANCE_SCREEN" = "Y" ]; then
362 | echo "Appearance screen will be added."
363 | else
364 | echo "Incorrect input given."
365 | fi
366 |
367 |
368 |
369 | # Execute Ruby script
370 | ruby << RUBY_SCRIPT
371 | require 'xcodeproj'
372 |
373 | old_project_name = "$OLD_APP_NAME"
374 | new_project_name = "$NEW_APP_NAME"
375 | remove_side_menu = $REMOVE_SIDE_MENU
376 | remove_tnc_screen = $REMOVE_TNC_SCREEN
377 | remove_onboarding_screen = $REMOVE_ONBOARDING_SCREEN
378 | remove_appearance_feature = $REMOVE_APPEARANCE_SCREEN
379 | localization_disabled = $LOCALIZATION_DISABLED
380 | num_tab = $NUM_TABS
381 | project_path = "#{Dir.pwd}/#{new_project_name}.xcodeproj"
382 |
383 | project = Xcodeproj::Project.open(project_path)
384 |
385 | def rename_group(group, old_name, new_name)
386 | group.children.each do |child|
387 | if child.is_a?(Xcodeproj::Project::Object::PBXGroup) && child.path == old_name
388 | puts "Renaming group from '#{old_name}' to '#{new_name}'"
389 | child.path = new_name
390 | break
391 | end
392 | end
393 | end
394 |
395 |
396 | def remove_folder(group, folder_name)
397 | group.children.dup.each do |child|
398 | if child.is_a?(Xcodeproj::Project::Object::PBXGroup)
399 | if child.path == folder_name
400 | child.remove_from_project
401 | return true
402 | else
403 | removed = remove_folder(child, folder_name)
404 | return true if removed
405 | end
406 | end
407 | end
408 | false
409 | end
410 |
411 | def remove_group(group, target, file_names = nil, group_name = nil, file_in_group = nil)
412 | group.children.dup.each do |child|
413 | if child.is_a?(Xcodeproj::Project::Object::PBXGroup)
414 | if group_name && child.name == group_name
415 | child.children.each do |inner_child|
416 | if inner_child.is_a?(Xcodeproj::Project::Object::PBXFileReference) && (file_in_group.nil? || inner_child.path == file_in_group)
417 | target.source_build_phase.remove_file_reference(inner_child)
418 | inner_child.remove_from_project
419 | end
420 | end
421 | group.remove_child(child) unless file_in_group
422 | else
423 | remove_group(child, target, file_names, group_name, file_in_group)
424 | end
425 | elsif child.is_a?(Xcodeproj::Project::Object::PBXFileReference) && file_names&.include?(File.basename(child.real_path.to_s))
426 | target.source_build_phase.remove_file_reference(child)
427 | child.remove_from_project
428 | end
429 | end
430 | end
431 |
432 | # Update target and product names
433 | project.targets.each do |target|
434 | puts "Old target name: #{target.name}"
435 | target.name = new_project_name
436 | puts "New target name: #{target.name}"
437 |
438 | target.build_configurations.each do |config|
439 | puts "Old product name: #{config.build_settings['PRODUCT_NAME']}"
440 | config.build_settings['PRODUCT_NAME'] = new_project_name
441 | puts "New product name: #{config.build_settings['PRODUCT_NAME']}"
442 |
443 | # Update productName to new project name
444 | if config.build_settings.key?('PRODUCT_NAME')
445 | config.build_settings['PRODUCT_NAME'] = new_project_name
446 | end
447 | end
448 |
449 | target.source_build_phase.files_references.each do |file_ref|
450 | if file_ref.path == 'SideMenuView.swift' && remove_side_menu == true
451 | target.source_build_phase.remove_file_reference(file_ref)
452 | end
453 | if file_ref.path == 'TermsAndConditionsScreen.swift' && remove_tnc_screen == true
454 | target.source_build_phase.remove_file_reference(file_ref)
455 | end
456 | if file_ref.path == 'OnboardingScreen.swift' && remove_onboarding_screen == true
457 | target.source_build_phase.remove_file_reference(file_ref)
458 | end
459 | if file_ref.path == 'AppearancePreference.swift' && remove_appearance_feature == true
460 | target.source_build_phase.remove_file_reference(file_ref)
461 | end
462 |
463 | (5).downto(num_tab+1).each do |i|
464 | if file_ref.path == "Tab#{i}Screen.swift"
465 | target.source_build_phase.remove_file_reference(file_ref)
466 | end
467 | end
468 | end
469 | end
470 |
471 | ## TabScreen names that should be removed
472 | file_names_to_remove = (num_tab+1..5).map { |i| "Tab#{i}Screen.swift" }
473 |
474 | project.targets.each do |target|
475 | project.main_group.children.each do |child|
476 | if child.is_a?(Xcodeproj::Project::Object::PBXGroup)
477 | remove_group(child, target, file_names_to_remove)
478 | if remove_side_menu == true
479 | remove_group(child, target, "SideMenuView.swift")
480 | end
481 | if remove_tnc_screen == true
482 | remove_group(child, target, "TermsAndConditionsScreen.swift")
483 | end
484 | if remove_onboarding_screen == true
485 | remove_group(child, target, "OnboardingScreen.swift")
486 | end
487 | if remove_appearance_feature == true
488 | remove_group(child, target, "AppearancePreference.swift")
489 | end
490 | end
491 | end
492 | end
493 |
494 | if remove_side_menu == true
495 | remove_folder(project.main_group, "SideMenu")
496 | end
497 | if remove_tnc_screen == true
498 | remove_folder(project.main_group, "TermsAndConditions")
499 | end
500 | if remove_onboarding_screen == true
501 | remove_folder(project.main_group, "Onboarding")
502 | end
503 |
504 | if localization_disabled == true
505 | locales_to_remove = ["ar", "zh-Hans", "es", "fr", "hi"]
506 |
507 | locales_to_remove.each do |locale|
508 | project.root_object.known_regions.reject! { |region| region == locale }
509 |
510 | project.files.each do |file|
511 | if file.path.match(/#{locale}.lproj/)
512 | file.remove_from_project
513 | end
514 | end
515 | end
516 | end
517 |
518 | # Rename a specific group (change 'SpecificGroupName' to the name of the group you want to rename)
519 | rename_group(project.main_group, old_project_name, new_project_name)
520 |
521 | # Update the file references inside the project
522 | project.files.each do |file|
523 | file.path = file.path.gsub(old_project_name, new_project_name) if file.path.include?(old_project_name)
524 | end
525 |
526 | # Fetch the new target
527 | new_target = project.targets.find { |t| t.name == new_project_name }
528 | unless new_target
529 | puts "New target '#{new_project_name}' not found."
530 | exit 1
531 | end
532 |
533 | # Update scheme names and their Buildable References
534 | schemes_path = "#{project_path}/xcshareddata/xcschemes"
535 | Dir.glob("#{schemes_path}/*.xcscheme").each do |scheme_path|
536 | scheme = Xcodeproj::XCScheme.new(scheme_path)
537 |
538 | # Update Buildable References for actions that have buildable_product_runnable
539 | [scheme.launch_action, scheme.profile_action].each do |action|
540 | if action && action.respond_to?(:buildable_product_runnable) && action.buildable_product_runnable
541 | action.buildable_product_runnable.buildable_reference.set_reference_target(new_target, true, project)
542 | end
543 | end
544 |
545 | # Handle TestAction separately if needed
546 | if scheme.test_action
547 | # Update TestAction buildable references here if necessary
548 | end
549 |
550 | # Save the updated scheme
551 | scheme.save!
552 | end
553 |
554 | # Update scheme names
555 | Dir.glob("#{schemes_path}/*.xcscheme").each do |scheme_path|
556 | scheme_name = File.basename(scheme_path, ".xcscheme")
557 | new_scheme_name = scheme_name.gsub(old_project_name, new_project_name)
558 | File.rename(scheme_path, "#{schemes_path}/#{new_scheme_name}.xcscheme")
559 | end
560 |
561 | # Update productName in PBXNativeTarget section
562 | project.native_targets.each do |native_target|
563 | puts "native target -- '#{native_target}'"
564 | if native_target.name == new_project_name
565 | puts "Updating productName for target '#{new_project_name}'"
566 | native_target.build_configuration_list.build_configurations.each do |config|
567 | config.build_settings['PRODUCT_NAME'] = new_project_name
568 | end
569 | end
570 | end
571 |
572 | # Inspect the "new_project_name" target
573 | target = project.targets.find { |t| t.name == new_project_name }
574 | target.product_name = new_project_name
575 |
576 | # Save the project before renaming the directory
577 | project.save
578 | puts "Project directory, scheme, and project updated."
579 | RUBY_SCRIPT
580 |
581 | # Remove xcodeproj, if installed
582 | if [ "$XCODEPROJ_INSTALLED" = true ]; then
583 | echo "Removing xcodeproj..."
584 | gem uninstall xcodeproj -x
585 |
586 | else
587 | echo "xcodeproj gem was already installed, not removing."
588 | fi
589 |
590 | if [ -n "$newly_installed" ]; then
591 | echo "Uninstalling newly installed gems..."
592 | gem uninstall $newly_installed
593 | echo "Uninstallation complete."
594 | fi
595 |
596 | # Remove the create_swift_app.sh script
597 | rm -f "$DESTINATION_PATH/create_swift_app.sh"
598 |
599 | echo -e "\033[32mProject $NEW_APP_NAME created successfully. \033[0m\n"
600 |
601 |
--------------------------------------------------------------------------------