├── .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 | --------------------------------------------------------------------------------