├── .gitignore ├── CreditCardFont ├── CREDC___.otf ├── Credit Card (MacPS) │ ├── CrediCar │ └── Credit Card ├── Credit Card (MacTT) ├── CreditCard_ReadMe.rtf ├── Credit_Card_blank.jpg ├── Icon ├── chip.jpg ├── hologram.jpg └── visa.jpg ├── README.md ├── TypeYourCard.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcuserdata │ │ └── alexej_ne.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ ├── alexej_ne.xcuserdatad │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ │ └── xcschememanagement.plist │ └── alexejnenastev.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist └── TypeYourCard ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── Base.lproj └── LaunchScreen.storyboard ├── ContentView.swift ├── Font └── CREDC___.ttf ├── Info.plist ├── Models ├── PayCard.swift ├── PayCardField.swift └── PayService.swift ├── Preview Content └── Preview Assets.xcassets │ └── Contents.json ├── SceneDelegate.swift ├── Swift+ └── String+.swift ├── SwiftUI+ ├── Color+.swift └── Font+Card.swift ├── SwiftUI+TypeYouCard ├── Animation+TypeYourCard.swift ├── AnyTransition+SlideRightToLeft.swift └── LinearGradient+ActionButton.swift └── TypeCardScene ├── TypeCardEnvironment.swift ├── TypeCardScene.swift └── Views ├── ActionButton.swift ├── MasterCardLogoView.swift ├── PayCardFieldInputView.swift ├── PayCardView.swift ├── PayServiceLogoView.swift ├── PlaceholderText.swift └── ResponderableTextField.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## MacOS 6 | \.DS_Store 7 | 8 | ## Build generated 9 | build/ 10 | DerivedData/ 11 | 12 | ## Various settings 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata/ 22 | 23 | ## Other 24 | *.moved-aside 25 | *.xccheckout 26 | *.xcscmblueprint 27 | 28 | ## Obj-C/Swift specific 29 | *.hmap 30 | *.ipa 31 | *.dSYM.zip 32 | *.dSYM 33 | 34 | ## Playgrounds 35 | timeline.xctimeline 36 | playground.xcworkspace 37 | 38 | # Swift Package Manager 39 | # 40 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 41 | # Packages/ 42 | # Package.pins 43 | # Package.resolved 44 | .build/ 45 | 46 | # CocoaPods 47 | # 48 | # We recommend against adding the Pods directory to your .gitignore. However 49 | # you should judge for yourself, the pros and cons are mentioned at: 50 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 51 | # 52 | # Pods/ 53 | # 54 | # Add this line if you want to avoid checking in source code from the Xcode workspace 55 | # *.xcworkspace 56 | 57 | # Carthage 58 | # 59 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 60 | # Carthage/Checkouts 61 | 62 | Carthage/Build 63 | 64 | # Accio dependency management 65 | Dependencies/ 66 | .accio/ 67 | 68 | # fastlane 69 | # 70 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 71 | # screenshots whenever they are needed. 72 | # For more information about the recommended setup visit: 73 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 74 | 75 | fastlane/report.xml 76 | fastlane/Preview.html 77 | fastlane/screenshots/**/*.png 78 | fastlane/test_output 79 | 80 | # Code Injection 81 | # 82 | # After new code Injection tools there's a generated folder /iOSInjectionProject 83 | # https://github.com/johnno1962/injectionforxcode 84 | 85 | iOSInjectionProject/ 86 | -------------------------------------------------------------------------------- /CreditCardFont/CREDC___.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexejn/TypeYouCard/bc858363ed5a3b8ad9eaa36c34449335ecb2f7af/CreditCardFont/CREDC___.otf -------------------------------------------------------------------------------- /CreditCardFont/Credit Card (MacPS)/CrediCar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexejn/TypeYouCard/bc858363ed5a3b8ad9eaa36c34449335ecb2f7af/CreditCardFont/Credit Card (MacPS)/CrediCar -------------------------------------------------------------------------------- /CreditCardFont/Credit Card (MacPS)/Credit Card: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexejn/TypeYouCard/bc858363ed5a3b8ad9eaa36c34449335ecb2f7af/CreditCardFont/Credit Card (MacPS)/Credit Card -------------------------------------------------------------------------------- /CreditCardFont/Credit Card (MacTT): -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexejn/TypeYouCard/bc858363ed5a3b8ad9eaa36c34449335ecb2f7af/CreditCardFont/Credit Card (MacTT) -------------------------------------------------------------------------------- /CreditCardFont/CreditCard_ReadMe.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1038\cocoasubrtf250 2 | {\fonttbl\f0\fnil\fcharset0 Verdana;} 3 | {\colortbl;\red255\green255\blue255;\red255\green0\blue0;\red36\green36\blue36;\red83\green83\blue83; 4 | } 5 | \paperw11900\paperh16840\margl1440\margr1440\vieww12220\viewh11800\viewkind0 6 | \deftab720 7 | \pard\pardeftab720\ql\qnatural 8 | 9 | \f0\b\fs32 \cf2 CREDIT CARD 10 | \b0\fs24 \cf0 \ 11 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab720\ql\qnatural 12 | \cf0 \ 13 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardeftab720\ql\qnatural\pardirnatural 14 | 15 | \fs28 \cf0 K-Type \'a9 2009 \'95 http://www.k-type.com/ \'95 keith@k-type.com\ 16 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab720\ql\qnatural 17 | \cf0 \ 18 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardeftab720\ql\qnatural\pardirnatural 19 | \cf0 Credit Card is an ALL CAPITALS font for simulating bank cards (and to suggest a context of banking, finance, membership or security).\ 20 | \ 21 | The number keys produce the bigger, squarer digits of the 16 figure card number.\ 22 | \ 23 | There is no lowercase in this font. Instead, the small numerals used for validity dates fill the lowercase letter keys, 1 > 9 being at a > i.\ 24 | \ 25 | In deference to their functional origins, most characters are monospaced and devoid of pair kerning. Also, many of the accented capital letters are reduced in vertical size to help accommodate the diacritic.\ 26 | \ 27 | Emboss in Photoshop to simulate raised type - Layer > Layer Style > Bevel and Emboss\ 28 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab720\ql\qnatural 29 | 30 | \fs24 \cf0 \ 31 | \ 32 | \pard\pardeftab720\ql\qnatural 33 | 34 | \b\fs32 \cf2 K-TYPE LICENCE AGREEMENT 35 | \fs36 \cf0 \ 36 | \pard\pardeftab720\sl360\slmult1\ql\qnatural 37 | 38 | \b0\fs18 \cf0 \ 39 | \pard\pardeftab720\ql\qnatural 40 | 41 | \fs28 \cf2 FREEBIES (Unlicensed) 42 | \fs24 \cf3 \ 43 | K-Type Freebies are free for personal use and do not require a licence.\ 44 | However, Freebies used for Commercial and Entrepreneurial purposes need to be licensed at the same rates as Pay Fonts. \cf0 Freebie Commercial Licences, Enterprise Licences and Custom Fonts are available from {\field{\*\fldinst{HYPERLINK "mailto:keith@k-type.com"}}{\fldrslt \cf4 \ul \ulc4 keith@k-type.com}}\cf3 \ 45 | 46 | \fs18 \ 47 | 48 | \fs28 \cf2 COMMERCIAL LICENCE (Normal Licence) 49 | \fs24 \cf3 \ 50 | Purchasing a K-Type font grants you non-exclusive rights to use the font commercially on paper, on film, online and embedded in documents.\ 51 | The software may be stored on up to five workstations and output devices.\ 52 | You cannot legally give the font to others or install it on their machines (with the exception of co-workers and your service bureau).\ 53 | To place any K-Type font within other media will require an additional Enterprise Licence.\ 54 | 55 | \fs18 \ 56 | 57 | \fs28 \cf2 ENTERPRISE LICENCE (Extended Licence) 58 | \fs24 \cf3 \ 59 | For Entrepreneurial use on an unlimited number of workstations and output devices, and where a font file is incorporated into a software product, each K-Type font will require an Enterprise Licence.\ 60 | An Enterprise Licence is indefinite, but not exclusive.\ 61 | 62 | \fs18 \ 63 | 64 | \fs28 \cf2 CUSTOM FONTS 65 | \fs24 \cf3 \ 66 | Commissioning a K-Type Custom Font grants you ownership upon payment \'96 sole rights to use the font autonomously, exclusively and indefinitely.\ 67 | 68 | \fs18 \ 69 | 70 | \i\fs24 \cf2 Fonts are supplied "as is". Every effort is made to ensure that the files will behave properly, and whilst mistakes and omissions are corrected cheerfully and quickly, K-Type will not be liable for losses incurred by software failure.\ 71 | \ 72 | \pard\pardeftab720\ql\qnatural 73 | 74 | \i0\b\fs32 \cf2 \ 75 | K-TYPE 76 | \b0\fs24 \cf0 \ 77 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab720\ql\qnatural 78 | \cf0 \ 79 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardeftab720\ql\qnatural\pardirnatural 80 | 81 | \fs28 \cf0 K-Type fonts are inexpensive, quality typefaces supplied in Truetype (Windows and Mac varieties), OpenType, and Mac Postscript file formats. \ 82 | \ 83 | K-Type aims to provide a more personal service than most foundries, and are increasingly widening the scope of our fonts. In addition to the full set of standard Latin characters, a large selection of accented characters are included. Polish and Lithuanian accented characters are already included in many K-Type fonts. \ 84 | \ 85 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab720\ql\qnatural 86 | \cf0 Please email {\field{\*\fldinst{HYPERLINK "mailto:keith@k-type.com"}}{\fldrslt \cf4 \ul \ulc4 keith@k-type.com}} if you require additional characters. There is usually no charge to existing customers for adding accented characters. Other additions, such as company logos or signatures will be chargeable.\ 87 | \ 88 | \ 89 | \pard\pardeftab720\ql\qnatural 90 | 91 | \b\fs32 \cf2 INSTALLING FONTS 92 | \b0\fs24 \cf0 \ 93 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardeftab720\ql\qnatural 94 | \cf0 \ 95 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardeftab720\ql\qnatural\pardirnatural 96 | 97 | \fs28 \cf0 Fonts are placed in your operating system's 98 | \i Fonts 99 | \i0 folder and will be made available to all the applications or programs you use.\ 100 | \ 101 | \pard\pardeftab720\ql\qnatural 102 | \cf2 WINDOWS\cf0 \ 103 | \pard\pardeftab720\ql\qnatural 104 | 105 | \fs24 \cf0 Put the font files (.ttf, .otf) into C:\\Windows\\Fonts, or right-click on the font files > Install 106 | \fs28 \ 107 | \pard\pardeftab720\ql\qnatural 108 | \cf2 MAC\cf0 \ 109 | \pard\pardeftab720\ql\qnatural 110 | 111 | \fs24 \cf0 Put the font files (.otf, Mac Truetype, Mac Postscript) into /Library/Fonts} -------------------------------------------------------------------------------- /CreditCardFont/Credit_Card_blank.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexejn/TypeYouCard/bc858363ed5a3b8ad9eaa36c34449335ecb2f7af/CreditCardFont/Credit_Card_blank.jpg -------------------------------------------------------------------------------- /CreditCardFont/Icon : -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexejn/TypeYouCard/bc858363ed5a3b8ad9eaa36c34449335ecb2f7af/CreditCardFont/Icon -------------------------------------------------------------------------------- /CreditCardFont/chip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexejn/TypeYouCard/bc858363ed5a3b8ad9eaa36c34449335ecb2f7af/CreditCardFont/chip.jpg -------------------------------------------------------------------------------- /CreditCardFont/hologram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexejn/TypeYouCard/bc858363ed5a3b8ad9eaa36c34449335ecb2f7af/CreditCardFont/hologram.jpg -------------------------------------------------------------------------------- /CreditCardFont/visa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexejn/TypeYouCard/bc858363ed5a3b8ad9eaa36c34449335ecb2f7af/CreditCardFont/visa.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Awesome SwiftUI in action 2 | 3 | Form for providing paycard. Fully powered by SwiftUI. 4 | 5 | 6 | 7 | Inspired by https://gfycat.com/ru/heftyhelpfulfluke 8 | -------------------------------------------------------------------------------- /TypeYourCard.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 030882A322C3A9250006C0CD /* String+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030882A222C3A9250006C0CD /* String+.swift */; }; 11 | 030882A622C3AF560006C0CD /* CREDC___.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 030882A522C3AF3E0006C0CD /* CREDC___.ttf */; }; 12 | 030882A822C3B2BE0006C0CD /* Font+Card.swift in Sources */ = {isa = PBXBuildFile; fileRef = 030882A722C3B2BE0006C0CD /* Font+Card.swift */; }; 13 | 031CA59622C35EBF00D5AD9D /* Color+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031CA59522C35EBF00D5AD9D /* Color+.swift */; }; 14 | 031CA59A22C3604100D5AD9D /* LinearGradient+ActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031CA59922C3604100D5AD9D /* LinearGradient+ActionButton.swift */; }; 15 | 031CA59D22C3615700D5AD9D /* Animation+TypeYourCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031CA59C22C3615700D5AD9D /* Animation+TypeYourCard.swift */; }; 16 | 031CA59F22C3673A00D5AD9D /* PayServiceLogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031CA59E22C3673A00D5AD9D /* PayServiceLogoView.swift */; }; 17 | 032FEB0222C51E540091A810 /* MasterCardLogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 032FEB0122C51E540091A810 /* MasterCardLogoView.swift */; }; 18 | 0333352422C0FADA0025472E /* TypeCardEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0333352322C0FADA0025472E /* TypeCardEnvironment.swift */; }; 19 | 0333352622C10DE20025472E /* TypeCardScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0333352522C10DE20025472E /* TypeCardScene.swift */; }; 20 | 0333352B22C123A10025472E /* PayCardFieldInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0333352A22C123A10025472E /* PayCardFieldInputView.swift */; }; 21 | 0333352D22C134310025472E /* ActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0333352C22C134310025472E /* ActionButton.swift */; }; 22 | 0343043422C385F0002BE9FC /* PayService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0343043322C385F0002BE9FC /* PayService.swift */; }; 23 | 0373994522C373AC00B35C43 /* PayCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373994422C373AC00B35C43 /* PayCard.swift */; }; 24 | 0373994722C3742700B35C43 /* AnyTransition+SlideRightToLeft.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0373994622C3742700B35C43 /* AnyTransition+SlideRightToLeft.swift */; }; 25 | 0374334822C215B600180D28 /* PayCardField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0374334722C215B600180D28 /* PayCardField.swift */; }; 26 | 0374334B22C244F100180D28 /* PlaceholderText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0374334A22C244F100180D28 /* PlaceholderText.swift */; }; 27 | 6D47EDC522B6B58D005E61D1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D47EDC422B6B58D005E61D1 /* AppDelegate.swift */; }; 28 | 6D47EDC722B6B58D005E61D1 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D47EDC622B6B58D005E61D1 /* SceneDelegate.swift */; }; 29 | 6D47EDC922B6B58D005E61D1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D47EDC822B6B58D005E61D1 /* ContentView.swift */; }; 30 | 6D47EDCB22B6B590005E61D1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D47EDCA22B6B590005E61D1 /* Assets.xcassets */; }; 31 | 6D47EDCE22B6B590005E61D1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6D47EDCD22B6B590005E61D1 /* Preview Assets.xcassets */; }; 32 | 6D47EDD122B6B590005E61D1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6D47EDCF22B6B590005E61D1 /* LaunchScreen.storyboard */; }; 33 | 6D47EDD922B6B659005E61D1 /* PayCardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D47EDD822B6B659005E61D1 /* PayCardView.swift */; }; 34 | 6D52A8EB22C425EB007A3DCC /* ResponderableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D52A8EA22C425EB007A3DCC /* ResponderableTextField.swift */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | 030882A222C3A9250006C0CD /* String+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+.swift"; sourceTree = ""; }; 39 | 030882A522C3AF3E0006C0CD /* CREDC___.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "CREDC___.ttf"; sourceTree = ""; }; 40 | 030882A722C3B2BE0006C0CD /* Font+Card.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Font+Card.swift"; sourceTree = ""; }; 41 | 031CA59522C35EBF00D5AD9D /* Color+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+.swift"; sourceTree = ""; }; 42 | 031CA59922C3604100D5AD9D /* LinearGradient+ActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LinearGradient+ActionButton.swift"; sourceTree = ""; }; 43 | 031CA59C22C3615700D5AD9D /* Animation+TypeYourCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Animation+TypeYourCard.swift"; sourceTree = ""; }; 44 | 031CA59E22C3673A00D5AD9D /* PayServiceLogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayServiceLogoView.swift; sourceTree = ""; }; 45 | 032FEB0122C51E540091A810 /* MasterCardLogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterCardLogoView.swift; sourceTree = ""; }; 46 | 0333352322C0FADA0025472E /* TypeCardEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeCardEnvironment.swift; sourceTree = ""; }; 47 | 0333352522C10DE20025472E /* TypeCardScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeCardScene.swift; sourceTree = ""; }; 48 | 0333352A22C123A10025472E /* PayCardFieldInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayCardFieldInputView.swift; sourceTree = ""; }; 49 | 0333352C22C134310025472E /* ActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButton.swift; sourceTree = ""; }; 50 | 0343043322C385F0002BE9FC /* PayService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayService.swift; sourceTree = ""; }; 51 | 0373994422C373AC00B35C43 /* PayCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayCard.swift; sourceTree = ""; }; 52 | 0373994622C3742700B35C43 /* AnyTransition+SlideRightToLeft.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AnyTransition+SlideRightToLeft.swift"; sourceTree = ""; }; 53 | 0374334722C215B600180D28 /* PayCardField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayCardField.swift; sourceTree = ""; }; 54 | 0374334A22C244F100180D28 /* PlaceholderText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderText.swift; sourceTree = ""; }; 55 | 6D47EDC122B6B58D005E61D1 /* TypeYourCard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TypeYourCard.app; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 6D47EDC422B6B58D005E61D1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 57 | 6D47EDC622B6B58D005E61D1 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 58 | 6D47EDC822B6B58D005E61D1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 59 | 6D47EDCA22B6B590005E61D1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 60 | 6D47EDCD22B6B590005E61D1 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 61 | 6D47EDD022B6B590005E61D1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 62 | 6D47EDD222B6B590005E61D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | 6D47EDD822B6B659005E61D1 /* PayCardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PayCardView.swift; sourceTree = ""; }; 64 | 6D52A8EA22C425EB007A3DCC /* ResponderableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponderableTextField.swift; sourceTree = ""; }; 65 | /* End PBXFileReference section */ 66 | 67 | /* Begin PBXFrameworksBuildPhase section */ 68 | 6D47EDBE22B6B58D005E61D1 /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | ); 73 | runOnlyForDeploymentPostprocessing = 0; 74 | }; 75 | /* End PBXFrameworksBuildPhase section */ 76 | 77 | /* Begin PBXGroup section */ 78 | 030882A122C3A9040006C0CD /* Swift+ */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 030882A222C3A9250006C0CD /* String+.swift */, 82 | ); 83 | path = "Swift+"; 84 | sourceTree = ""; 85 | }; 86 | 031CA59722C35F0800D5AD9D /* Views */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 0374334A22C244F100180D28 /* PlaceholderText.swift */, 90 | 0333352A22C123A10025472E /* PayCardFieldInputView.swift */, 91 | 6D47EDD822B6B659005E61D1 /* PayCardView.swift */, 92 | 0333352C22C134310025472E /* ActionButton.swift */, 93 | 031CA59E22C3673A00D5AD9D /* PayServiceLogoView.swift */, 94 | 032FEB0122C51E540091A810 /* MasterCardLogoView.swift */, 95 | 6D52A8EA22C425EB007A3DCC /* ResponderableTextField.swift */, 96 | ); 97 | name = Views; 98 | path = TypeCardScene/Views; 99 | sourceTree = ""; 100 | }; 101 | 031CA59822C35FE000D5AD9D /* SwiftUI+ */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 031CA59522C35EBF00D5AD9D /* Color+.swift */, 105 | 030882A722C3B2BE0006C0CD /* Font+Card.swift */, 106 | ); 107 | path = "SwiftUI+"; 108 | sourceTree = ""; 109 | }; 110 | 031CA59B22C3604800D5AD9D /* SwiftUI+TypeYouCard */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 031CA59922C3604100D5AD9D /* LinearGradient+ActionButton.swift */, 114 | 031CA59C22C3615700D5AD9D /* Animation+TypeYourCard.swift */, 115 | 0373994622C3742700B35C43 /* AnyTransition+SlideRightToLeft.swift */, 116 | ); 117 | path = "SwiftUI+TypeYouCard"; 118 | sourceTree = ""; 119 | }; 120 | 0333352722C113760025472E /* Font */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 030882A522C3AF3E0006C0CD /* CREDC___.ttf */, 124 | ); 125 | path = Font; 126 | sourceTree = ""; 127 | }; 128 | 0374334522C2156E00180D28 /* Views */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | ); 132 | path = Views; 133 | sourceTree = ""; 134 | }; 135 | 0374334922C215FF00180D28 /* Models */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 0374334722C215B600180D28 /* PayCardField.swift */, 139 | 0373994422C373AC00B35C43 /* PayCard.swift */, 140 | 0343043322C385F0002BE9FC /* PayService.swift */, 141 | ); 142 | path = Models; 143 | sourceTree = ""; 144 | }; 145 | 03E8BE8F22C35DF2006A88BB /* TypeCardScene */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 0333352522C10DE20025472E /* TypeCardScene.swift */, 149 | 0333352322C0FADA0025472E /* TypeCardEnvironment.swift */, 150 | ); 151 | path = TypeCardScene; 152 | sourceTree = ""; 153 | }; 154 | 6D47EDB822B6B58D005E61D1 = { 155 | isa = PBXGroup; 156 | children = ( 157 | 6D47EDC322B6B58D005E61D1 /* TypeYourCard */, 158 | 6D47EDC222B6B58D005E61D1 /* Products */, 159 | ); 160 | sourceTree = ""; 161 | }; 162 | 6D47EDC222B6B58D005E61D1 /* Products */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | 6D47EDC122B6B58D005E61D1 /* TypeYourCard.app */, 166 | ); 167 | name = Products; 168 | sourceTree = ""; 169 | }; 170 | 6D47EDC322B6B58D005E61D1 /* TypeYourCard */ = { 171 | isa = PBXGroup; 172 | children = ( 173 | 030882A122C3A9040006C0CD /* Swift+ */, 174 | 031CA59822C35FE000D5AD9D /* SwiftUI+ */, 175 | 031CA59B22C3604800D5AD9D /* SwiftUI+TypeYouCard */, 176 | 031CA59722C35F0800D5AD9D /* Views */, 177 | 03E8BE8F22C35DF2006A88BB /* TypeCardScene */, 178 | 0374334922C215FF00180D28 /* Models */, 179 | 0374334522C2156E00180D28 /* Views */, 180 | 0333352722C113760025472E /* Font */, 181 | 6D47EDC422B6B58D005E61D1 /* AppDelegate.swift */, 182 | 6D47EDC622B6B58D005E61D1 /* SceneDelegate.swift */, 183 | 6D47EDC822B6B58D005E61D1 /* ContentView.swift */, 184 | 6D47EDCA22B6B590005E61D1 /* Assets.xcassets */, 185 | 6D47EDCF22B6B590005E61D1 /* LaunchScreen.storyboard */, 186 | 6D47EDD222B6B590005E61D1 /* Info.plist */, 187 | 6D47EDCC22B6B590005E61D1 /* Preview Content */, 188 | ); 189 | path = TypeYourCard; 190 | sourceTree = ""; 191 | }; 192 | 6D47EDCC22B6B590005E61D1 /* Preview Content */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | 6D47EDCD22B6B590005E61D1 /* Preview Assets.xcassets */, 196 | ); 197 | path = "Preview Content"; 198 | sourceTree = ""; 199 | }; 200 | /* End PBXGroup section */ 201 | 202 | /* Begin PBXNativeTarget section */ 203 | 6D47EDC022B6B58D005E61D1 /* TypeYourCard */ = { 204 | isa = PBXNativeTarget; 205 | buildConfigurationList = 6D47EDD522B6B590005E61D1 /* Build configuration list for PBXNativeTarget "TypeYourCard" */; 206 | buildPhases = ( 207 | 6D47EDBD22B6B58D005E61D1 /* Sources */, 208 | 6D47EDBE22B6B58D005E61D1 /* Frameworks */, 209 | 6D47EDBF22B6B58D005E61D1 /* Resources */, 210 | ); 211 | buildRules = ( 212 | ); 213 | dependencies = ( 214 | ); 215 | name = TypeYourCard; 216 | productName = TypeYourCard; 217 | productReference = 6D47EDC122B6B58D005E61D1 /* TypeYourCard.app */; 218 | productType = "com.apple.product-type.application"; 219 | }; 220 | /* End PBXNativeTarget section */ 221 | 222 | /* Begin PBXProject section */ 223 | 6D47EDB922B6B58D005E61D1 /* Project object */ = { 224 | isa = PBXProject; 225 | attributes = { 226 | LastSwiftUpdateCheck = 1100; 227 | LastUpgradeCheck = 1100; 228 | ORGANIZATIONNAME = "Alexej Nenastev"; 229 | TargetAttributes = { 230 | 6D47EDC022B6B58D005E61D1 = { 231 | CreatedOnToolsVersion = 11.0; 232 | }; 233 | }; 234 | }; 235 | buildConfigurationList = 6D47EDBC22B6B58D005E61D1 /* Build configuration list for PBXProject "TypeYourCard" */; 236 | compatibilityVersion = "Xcode 9.3"; 237 | developmentRegion = en; 238 | hasScannedForEncodings = 0; 239 | knownRegions = ( 240 | en, 241 | Base, 242 | ); 243 | mainGroup = 6D47EDB822B6B58D005E61D1; 244 | productRefGroup = 6D47EDC222B6B58D005E61D1 /* Products */; 245 | projectDirPath = ""; 246 | projectRoot = ""; 247 | targets = ( 248 | 6D47EDC022B6B58D005E61D1 /* TypeYourCard */, 249 | ); 250 | }; 251 | /* End PBXProject section */ 252 | 253 | /* Begin PBXResourcesBuildPhase section */ 254 | 6D47EDBF22B6B58D005E61D1 /* Resources */ = { 255 | isa = PBXResourcesBuildPhase; 256 | buildActionMask = 2147483647; 257 | files = ( 258 | 6D47EDD122B6B590005E61D1 /* LaunchScreen.storyboard in Resources */, 259 | 6D47EDCE22B6B590005E61D1 /* Preview Assets.xcassets in Resources */, 260 | 6D47EDCB22B6B590005E61D1 /* Assets.xcassets in Resources */, 261 | 030882A622C3AF560006C0CD /* CREDC___.ttf in Resources */, 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | }; 265 | /* End PBXResourcesBuildPhase section */ 266 | 267 | /* Begin PBXSourcesBuildPhase section */ 268 | 6D47EDBD22B6B58D005E61D1 /* Sources */ = { 269 | isa = PBXSourcesBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | 6D52A8EB22C425EB007A3DCC /* ResponderableTextField.swift in Sources */, 273 | 031CA59622C35EBF00D5AD9D /* Color+.swift in Sources */, 274 | 031CA59F22C3673A00D5AD9D /* PayServiceLogoView.swift in Sources */, 275 | 030882A822C3B2BE0006C0CD /* Font+Card.swift in Sources */, 276 | 032FEB0222C51E540091A810 /* MasterCardLogoView.swift in Sources */, 277 | 0374334822C215B600180D28 /* PayCardField.swift in Sources */, 278 | 0373994722C3742700B35C43 /* AnyTransition+SlideRightToLeft.swift in Sources */, 279 | 0374334B22C244F100180D28 /* PlaceholderText.swift in Sources */, 280 | 030882A322C3A9250006C0CD /* String+.swift in Sources */, 281 | 0333352422C0FADA0025472E /* TypeCardEnvironment.swift in Sources */, 282 | 031CA59A22C3604100D5AD9D /* LinearGradient+ActionButton.swift in Sources */, 283 | 0343043422C385F0002BE9FC /* PayService.swift in Sources */, 284 | 0333352622C10DE20025472E /* TypeCardScene.swift in Sources */, 285 | 6D47EDC522B6B58D005E61D1 /* AppDelegate.swift in Sources */, 286 | 6D47EDC722B6B58D005E61D1 /* SceneDelegate.swift in Sources */, 287 | 0333352B22C123A10025472E /* PayCardFieldInputView.swift in Sources */, 288 | 6D47EDD922B6B659005E61D1 /* PayCardView.swift in Sources */, 289 | 0373994522C373AC00B35C43 /* PayCard.swift in Sources */, 290 | 0333352D22C134310025472E /* ActionButton.swift in Sources */, 291 | 031CA59D22C3615700D5AD9D /* Animation+TypeYourCard.swift in Sources */, 292 | 6D47EDC922B6B58D005E61D1 /* ContentView.swift in Sources */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | /* End PBXSourcesBuildPhase section */ 297 | 298 | /* Begin PBXVariantGroup section */ 299 | 6D47EDCF22B6B590005E61D1 /* LaunchScreen.storyboard */ = { 300 | isa = PBXVariantGroup; 301 | children = ( 302 | 6D47EDD022B6B590005E61D1 /* Base */, 303 | ); 304 | name = LaunchScreen.storyboard; 305 | sourceTree = ""; 306 | }; 307 | /* End PBXVariantGroup section */ 308 | 309 | /* Begin XCBuildConfiguration section */ 310 | 6D47EDD322B6B590005E61D1 /* Debug */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ALWAYS_SEARCH_USER_PATHS = NO; 314 | CLANG_ANALYZER_NONNULL = YES; 315 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 316 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 317 | CLANG_CXX_LIBRARY = "libc++"; 318 | CLANG_ENABLE_MODULES = YES; 319 | CLANG_ENABLE_OBJC_ARC = YES; 320 | CLANG_ENABLE_OBJC_WEAK = YES; 321 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 322 | CLANG_WARN_BOOL_CONVERSION = YES; 323 | CLANG_WARN_COMMA = YES; 324 | CLANG_WARN_CONSTANT_CONVERSION = YES; 325 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 326 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 327 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 328 | CLANG_WARN_EMPTY_BODY = YES; 329 | CLANG_WARN_ENUM_CONVERSION = YES; 330 | CLANG_WARN_INFINITE_RECURSION = YES; 331 | CLANG_WARN_INT_CONVERSION = YES; 332 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 333 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 334 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 335 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 336 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 337 | CLANG_WARN_STRICT_PROTOTYPES = YES; 338 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 339 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 340 | CLANG_WARN_UNREACHABLE_CODE = YES; 341 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 342 | COPY_PHASE_STRIP = NO; 343 | DEBUG_INFORMATION_FORMAT = dwarf; 344 | ENABLE_STRICT_OBJC_MSGSEND = YES; 345 | ENABLE_TESTABILITY = YES; 346 | GCC_C_LANGUAGE_STANDARD = gnu11; 347 | GCC_DYNAMIC_NO_PIC = NO; 348 | GCC_NO_COMMON_BLOCKS = YES; 349 | GCC_OPTIMIZATION_LEVEL = 0; 350 | GCC_PREPROCESSOR_DEFINITIONS = ( 351 | "DEBUG=1", 352 | "$(inherited)", 353 | ); 354 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 355 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 356 | GCC_WARN_UNDECLARED_SELECTOR = YES; 357 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 358 | GCC_WARN_UNUSED_FUNCTION = YES; 359 | GCC_WARN_UNUSED_VARIABLE = YES; 360 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 361 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 362 | MTL_FAST_MATH = YES; 363 | ONLY_ACTIVE_ARCH = YES; 364 | SDKROOT = iphoneos; 365 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 366 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 367 | }; 368 | name = Debug; 369 | }; 370 | 6D47EDD422B6B590005E61D1 /* Release */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ALWAYS_SEARCH_USER_PATHS = NO; 374 | CLANG_ANALYZER_NONNULL = YES; 375 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 376 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 377 | CLANG_CXX_LIBRARY = "libc++"; 378 | CLANG_ENABLE_MODULES = YES; 379 | CLANG_ENABLE_OBJC_ARC = YES; 380 | CLANG_ENABLE_OBJC_WEAK = YES; 381 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 382 | CLANG_WARN_BOOL_CONVERSION = YES; 383 | CLANG_WARN_COMMA = YES; 384 | CLANG_WARN_CONSTANT_CONVERSION = YES; 385 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 386 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 387 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 388 | CLANG_WARN_EMPTY_BODY = YES; 389 | CLANG_WARN_ENUM_CONVERSION = YES; 390 | CLANG_WARN_INFINITE_RECURSION = YES; 391 | CLANG_WARN_INT_CONVERSION = YES; 392 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 393 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 394 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 395 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 396 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 397 | CLANG_WARN_STRICT_PROTOTYPES = YES; 398 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 399 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 400 | CLANG_WARN_UNREACHABLE_CODE = YES; 401 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 402 | COPY_PHASE_STRIP = NO; 403 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 404 | ENABLE_NS_ASSERTIONS = NO; 405 | ENABLE_STRICT_OBJC_MSGSEND = YES; 406 | GCC_C_LANGUAGE_STANDARD = gnu11; 407 | GCC_NO_COMMON_BLOCKS = YES; 408 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 409 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 410 | GCC_WARN_UNDECLARED_SELECTOR = YES; 411 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 412 | GCC_WARN_UNUSED_FUNCTION = YES; 413 | GCC_WARN_UNUSED_VARIABLE = YES; 414 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 415 | MTL_ENABLE_DEBUG_INFO = NO; 416 | MTL_FAST_MATH = YES; 417 | SDKROOT = iphoneos; 418 | SWIFT_COMPILATION_MODE = wholemodule; 419 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 420 | VALIDATE_PRODUCT = YES; 421 | }; 422 | name = Release; 423 | }; 424 | 6D47EDD622B6B590005E61D1 /* Debug */ = { 425 | isa = XCBuildConfiguration; 426 | buildSettings = { 427 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 428 | CODE_SIGN_STYLE = Automatic; 429 | DEVELOPMENT_ASSET_PATHS = "TypeYourCard/Preview\\ Content"; 430 | DEVELOPMENT_TEAM = 3BVL9BNASU; 431 | ENABLE_PREVIEWS = YES; 432 | INFOPLIST_FILE = TypeYourCard/Info.plist; 433 | LD_RUNPATH_SEARCH_PATHS = ( 434 | "$(inherited)", 435 | "@executable_path/Frameworks", 436 | ); 437 | PRODUCT_BUNDLE_IDENTIFIER = com.someSwift.TypeYourCard; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_VERSION = 5.0; 440 | TARGETED_DEVICE_FAMILY = 1; 441 | }; 442 | name = Debug; 443 | }; 444 | 6D47EDD722B6B590005E61D1 /* Release */ = { 445 | isa = XCBuildConfiguration; 446 | buildSettings = { 447 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 448 | CODE_SIGN_STYLE = Automatic; 449 | DEVELOPMENT_ASSET_PATHS = "TypeYourCard/Preview\\ Content"; 450 | DEVELOPMENT_TEAM = 3BVL9BNASU; 451 | ENABLE_PREVIEWS = YES; 452 | INFOPLIST_FILE = TypeYourCard/Info.plist; 453 | LD_RUNPATH_SEARCH_PATHS = ( 454 | "$(inherited)", 455 | "@executable_path/Frameworks", 456 | ); 457 | PRODUCT_BUNDLE_IDENTIFIER = com.someSwift.TypeYourCard; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | SWIFT_VERSION = 5.0; 460 | TARGETED_DEVICE_FAMILY = 1; 461 | }; 462 | name = Release; 463 | }; 464 | /* End XCBuildConfiguration section */ 465 | 466 | /* Begin XCConfigurationList section */ 467 | 6D47EDBC22B6B58D005E61D1 /* Build configuration list for PBXProject "TypeYourCard" */ = { 468 | isa = XCConfigurationList; 469 | buildConfigurations = ( 470 | 6D47EDD322B6B590005E61D1 /* Debug */, 471 | 6D47EDD422B6B590005E61D1 /* Release */, 472 | ); 473 | defaultConfigurationIsVisible = 0; 474 | defaultConfigurationName = Release; 475 | }; 476 | 6D47EDD522B6B590005E61D1 /* Build configuration list for PBXNativeTarget "TypeYourCard" */ = { 477 | isa = XCConfigurationList; 478 | buildConfigurations = ( 479 | 6D47EDD622B6B590005E61D1 /* Debug */, 480 | 6D47EDD722B6B590005E61D1 /* Release */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | /* End XCConfigurationList section */ 486 | }; 487 | rootObject = 6D47EDB922B6B58D005E61D1 /* Project object */; 488 | } 489 | -------------------------------------------------------------------------------- /TypeYourCard.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TypeYourCard.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TypeYourCard.xcodeproj/project.xcworkspace/xcuserdata/alexej_ne.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexejn/TypeYouCard/bc858363ed5a3b8ad9eaa36c34449335ecb2f7af/TypeYourCard.xcodeproj/project.xcworkspace/xcuserdata/alexej_ne.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /TypeYourCard.xcodeproj/xcuserdata/alexej_ne.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /TypeYourCard.xcodeproj/xcuserdata/alexej_ne.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TypeYourCard.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TypeYourCard.xcodeproj/xcuserdata/alexejnenastev.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /TypeYourCard.xcodeproj/xcuserdata/alexejnenastev.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TypeYourCard.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /TypeYourCard/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TypeYourCard 4 | // 5 | // Created by Alexej Nenastev on 16.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | func applicationWillTerminate(_ application: UIApplication) { 22 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 23 | } 24 | 25 | // MARK: UISceneSession Lifecycle 26 | 27 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 28 | // Called when a new scene session is being created. 29 | // Use this method to select a configuration to create the new scene with. 30 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 31 | } 32 | 33 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 34 | // Called when the user discards a scene session. 35 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 36 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 37 | } 38 | 39 | 40 | } 41 | 42 | -------------------------------------------------------------------------------- /TypeYourCard/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /TypeYourCard/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TypeYourCard/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /TypeYourCard/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // TypeYourCard 4 | // 5 | // Created by Alexej Nenastev on 16.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView : View { 12 | var body: some View { 13 | VStack { 14 | Text("alexejn/TypeYouCard") 15 | TypeCardScene().padding(.top, -2) 16 | } 17 | } 18 | } 19 | 20 | 21 | #if DEBUG 22 | struct ContentView_Previews : PreviewProvider { 23 | static var previews: some View { 24 | ContentView() 25 | } 26 | } 27 | #endif 28 | -------------------------------------------------------------------------------- /TypeYourCard/Font/CREDC___.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexejn/TypeYouCard/bc858363ed5a3b8ad9eaa36c34449335ecb2f7af/TypeYourCard/Font/CREDC___.ttf -------------------------------------------------------------------------------- /TypeYourCard/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIAppFonts 24 | 25 | CREDC___.ttf 26 | 27 | UIApplicationSceneManifest 28 | 29 | UIApplicationSupportsMultipleScenes 30 | 31 | UISceneConfigurations 32 | 33 | UIWindowSceneSessionRoleApplication 34 | 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UISceneConfigurationName 39 | Default Configuration 40 | UISceneDelegateClassName 41 | $(PRODUCT_MODULE_NAME).SceneDelegate 42 | 43 | 44 | 45 | 46 | UILaunchStoryboardName 47 | LaunchScreen 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | 56 | UISupportedInterfaceOrientations~ipad 57 | 58 | UIInterfaceOrientationPortrait 59 | UIInterfaceOrientationPortraitUpsideDown 60 | UIInterfaceOrientationLandscapeLeft 61 | UIInterfaceOrientationLandscapeRight 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /TypeYourCard/Models/PayCard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PayCard.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 26.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | struct PayCard { 11 | let number: String 12 | let holder: String 13 | let validThru: Date 14 | let cvv: Int 15 | } 16 | 17 | 18 | extension PayCard { 19 | 20 | static func tryGetValidNumber(_ str: String) -> String? { 21 | let number = String(str.onlyNumeric.prefix(16)) 22 | return number.count == 16 ? number : nil 23 | } 24 | 25 | static func tryGetCardHolder(_ str: String) -> String? { 26 | let holder = str.uppercased().onlyLetter.trimmingCharacters(in: .whitespaces) 27 | return holder.count > 3 ? holder : nil 28 | } 29 | 30 | static func tryGetValidThru(_ str: String) -> Date? { 31 | let validThru = str.filter(allowedSymbols: "1234567890/") 32 | let dateFormatter = DateFormatter() 33 | dateFormatter.dateFormat = "MM/YY" 34 | return dateFormatter.date(from: validThru) 35 | } 36 | 37 | static func tryGetValidCVV(_ str: String) -> Int? { 38 | let cvv = String(str.onlyNumeric.prefix(3)) 39 | guard cvv.count == 3 else { return nil} 40 | return Int(cvv) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /TypeYourCard/Models/PayCardField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PayCardField.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 25.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | import SwiftUI 9 | 10 | enum PayCardField: CaseIterable { 11 | case number 12 | case holder 13 | case validThru 14 | case cvv 15 | } 16 | -------------------------------------------------------------------------------- /TypeYourCard/Models/PayService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PayService.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 26.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | enum PayService { 10 | case MasterCard 11 | } 12 | -------------------------------------------------------------------------------- /TypeYourCard/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /TypeYourCard/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // BCSUnieverseDemoApp 4 | // 5 | // Created by alexej_ne on 15.01.2020. 6 | // Copyright © 2020 BCS. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = ContentView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /TypeYourCard/Swift+/String+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 26.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | import Foundation 9 | 10 | extension String { 11 | 12 | public var onlyNumeric: String { filter(allowedSymbols: "0123456789") } 13 | 14 | public var onlyLetter: String { filter(allowedSymbols: " QWERTYUIOPASDFGHJKLZXCVBNM'") } 15 | 16 | public func filter(allowedSymbols: String ) -> String { 17 | let characterSet = CharacterSet(charactersIn: allowedSymbols).inverted 18 | return components(separatedBy: characterSet) 19 | .joined() 20 | } 21 | } 22 | 23 | extension String { 24 | subscript(_ range: CountableRange) -> String { 25 | guard range.lowerBound <= self.count else { return "" } 26 | 27 | let idx1 = index(startIndex, offsetBy: max(0, range.lowerBound)) 28 | let idx2 = index(startIndex, offsetBy: min(self.count, range.upperBound)) 29 | return String(self[idx1.. Element? { 35 | if let elementIndex = self.firstIndex(of: element), elementIndex < self.endIndex { 36 | return self[index(after: elementIndex)] 37 | } 38 | return nil 39 | } 40 | 41 | func before(_ element: Element) -> Element? { 42 | if let elementIndex = self.firstIndex(of: element), elementIndex > self.startIndex { 43 | return self[index(elementIndex, offsetBy: -1)] 44 | } 45 | return nil 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /TypeYourCard/SwiftUI+/Color+.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SwiftUI+.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 26.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | prefix operator ⋮ 12 | prefix func ⋮(hex:UInt32) -> Color { 13 | return Color(hex) 14 | } 15 | 16 | extension Color { 17 | init(_ hex: UInt32, opacity:Double = 1.0) { 18 | let red = Double((hex & 0xff0000) >> 16) / 255.0 19 | let green = Double((hex & 0xff00) >> 8) / 255.0 20 | let blue = Double((hex & 0xff) >> 0) / 255.0 21 | self.init(.sRGB, red: red, green: green, blue: blue, opacity: opacity) 22 | } 23 | } 24 | 25 | let hexColor:(UInt32) -> (Color) = { 26 | return Color($0) 27 | } 28 | -------------------------------------------------------------------------------- /TypeYourCard/SwiftUI+/Font+Card.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Font+Card.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 26.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension Font { 12 | static var cardRegularField = Font.custom("CreditCard", size: 17) 13 | static var cardNumberField = Font.custom("CreditCard", size: 29) 14 | } 15 | -------------------------------------------------------------------------------- /TypeYourCard/SwiftUI+TypeYouCard/Animation+TypeYourCard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Animation+TypeYouCard.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 26.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension Animation { 12 | struct FlipOtherSide { 13 | private static let duration: Double = 0.3 14 | 15 | static var hideSideWhileFlip: Animation { 16 | Animation.linear(duration: 0.001).delay(duration/2) 17 | } 18 | 19 | static var flip: Animation { 20 | Animation.linear(duration: duration) 21 | } 22 | } 23 | 24 | static var withoutAnimation: Animation { 25 | Animation.linear(duration: 0) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TypeYourCard/SwiftUI+TypeYouCard/AnyTransition+SlideRightToLeft.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AnyTransition+SlideRightToLeft.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 26.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | import SwiftUI 9 | 10 | extension AnyTransition { 11 | static var slideRightToLeft: AnyTransition { 12 | let slide = AnyTransition.asymmetric(insertion: AnyTransition.move(edge: .trailing), 13 | removal: AnyTransition.move(edge: .leading)) 14 | return slide.combined(with: .opacity) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TypeYourCard/SwiftUI+TypeYouCard/LinearGradient+ActionButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LinearGradient+TypeYouCard.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 26.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | import SwiftUI 9 | 10 | extension LinearGradient { 11 | static var forActionButton: LinearGradient { 12 | LinearGradient(gradient: .init(colors: [Color(0x6547B4), Color(0xA82786)]), 13 | startPoint: .init(x: 0, y: 0.2), 14 | endPoint: .init(x: 1, y: 0.9)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /TypeYourCard/TypeCardScene/TypeCardEnvironment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TypeCardEnvironment.swift 3 | // TypeCardEnvironment 4 | // 5 | // Created by alexej_ne on 24.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Combine 11 | 12 | final class TypeCardEnvironment: ObservableObject { 13 | let didChange = PassthroughSubject() 14 | private func notify() { didChange.send(self) } 15 | 16 | var currentInputField: PayCardField = .number { didSet { notify() } } 17 | 18 | var number: String = "" { 19 | didSet { 20 | guard oldValue != number else { return } 21 | cardNumberDidSet(oldValue: oldValue) 22 | payService = number.count > 3 ? .MasterCard : nil 23 | notify() 24 | } 25 | } 26 | 27 | var holderName: String = "" { 28 | didSet { 29 | guard oldValue != holderName else { return } 30 | self.holderName = holderName.uppercased().onlyLetter 31 | notify() 32 | } 33 | } 34 | 35 | var validThru: String = "" { 36 | didSet { 37 | guard oldValue != validThru else { return } 38 | validThruDidSet(oldValue: oldValue) 39 | notify() 40 | } 41 | } 42 | 43 | var cvv: String = "" { 44 | didSet { 45 | guard oldValue != cvv else { return } 46 | self.cvv = cvv.onlyNumeric[0..<3] 47 | notify() 48 | } 49 | } 50 | 51 | var payService: PayService? = nil { didSet { notify() } } 52 | 53 | func clear() { 54 | currentInputField = .number 55 | holderName = "" 56 | validThru = "" 57 | cvv = "" 58 | payService = nil 59 | number = "" 60 | } 61 | 62 | var canGoNext: Bool { 63 | return isInputDataValid(for: currentInputField) 64 | } 65 | 66 | func goToNextField() { 67 | guard let toField = currentInputField.nextForInput else { return } 68 | currentInputField = toField 69 | } 70 | 71 | func goToPreviousField() { 72 | guard let toField = currentInputField.prevForInput else { return } 73 | currentInputField = toField 74 | } 75 | 76 | func goToIfCan(field: PayCardField) { 77 | guard field != currentInputField else { return } 78 | 79 | if field.isFirstInput { 80 | currentInputField = field 81 | } else { 82 | guard let prev = field.prevForInput, isInputDataValid(for: prev) else { return } 83 | currentInputField = field 84 | } 85 | } 86 | 87 | private func isInputDataValid(for field: PayCardField) -> Bool { 88 | switch field { 89 | case .number: return PayCard.tryGetValidNumber(number) != nil 90 | case .holder: return PayCard.tryGetCardHolder(holderName) != nil 91 | case .validThru: return PayCard.tryGetValidThru(validThru) != nil 92 | case .cvv: return PayCard.tryGetValidCVV(cvv) != nil 93 | } 94 | } 95 | } 96 | 97 | // On PayCard 98 | extension TypeCardEnvironment { 99 | var onCardNumberPart1: String { number.onlyNumeric[0..<4].fixFontIssue() } 100 | var onCardNumberPart2: String { number.onlyNumeric[4..<8].fixFontIssue() } 101 | var onCardNumberPart3: String { number.onlyNumeric[8..<12].fixFontIssue() } 102 | var onCardNumberPart4: String { number.onlyNumeric[12..<16].fixFontIssue() } 103 | 104 | var onCardCardholerName: String { holderName.uppercased().onlyLetter } 105 | var onCardValidThru: String { validThru.filter(allowedSymbols: "1234567890/")[0..<5].fixFontIssue() } 106 | var onCardCVV: String { cvv[0..<3].fixFontIssue() } 107 | } 108 | 109 | fileprivate extension TypeCardEnvironment { 110 | func cardNumberDidSet(oldValue: String) { 111 | var tempNumb = number 112 | 113 | if oldValue == "\(tempNumb) " { 114 | number = tempNumb 115 | } else { 116 | var resultStr = "" 117 | tempNumb = tempNumb.onlyNumeric[0..<16] 118 | 119 | let partsCount = tempNumb.count / 4 120 | for _ in 0...partsCount { 121 | let part = tempNumb.prefix(4) 122 | resultStr = "\(resultStr) \(part)" 123 | if tempNumb.count >= 4 { 124 | tempNumb.removeFirst(4) 125 | } 126 | } 127 | 128 | number = resultStr.trimmingCharacters(in: .whitespaces) 129 | } 130 | } 131 | 132 | func validThruDidSet(oldValue: String) { 133 | var onCard = validThru.onlyNumeric[0..<5] 134 | 135 | if oldValue == "\(validThru)/" { 136 | self.validThru = "\(onCard.prefix(1))" 137 | } else if onCard.count > 1 { 138 | let part1 = "\(onCard.prefix(2))" 139 | onCard.removeFirst(2) 140 | let part2 = onCard 141 | self.validThru = "\(part1)/\(part2)" 142 | } else { 143 | self.validThru = onCard 144 | } 145 | } 146 | } 147 | 148 | fileprivate extension String { 149 | 150 | func fixFontIssue() -> String { 151 | let replacments: [Character: Character] = [ 152 | "1" : "a", 153 | "2" : "b", 154 | "3" : "c", 155 | "4" : "d", 156 | "5" : "e", 157 | "6" : "f", 158 | "7" : "g", 159 | "8" : "h", 160 | "9" : "i", 161 | "0" : "k", 162 | ] 163 | 164 | return String(self.map { replacments[$0] ?? $0 }) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /TypeYourCard/TypeCardScene/TypeCardScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TypeYourCardScene.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 24.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension PayCardField { 12 | 13 | var isFirstInput: Bool { 14 | self == PayCardField.allCases.first 15 | } 16 | 17 | var isLastInput: Bool { 18 | self == PayCardField.allCases.last 19 | } 20 | 21 | var buttonTitle: String { 22 | isLastInput ? "DONE" : "Next" 23 | } 24 | 25 | var nextForInput: PayCardField? { 26 | PayCardField.allCases.after(self) 27 | } 28 | 29 | var prevForInput: PayCardField? { 30 | PayCardField.allCases.before(self) 31 | } 32 | } 33 | 34 | struct TypeCardScene : View { 35 | @ObservedObject var environment = TypeCardEnvironment() 36 | 37 | private var textFieldBinding: Binding { 38 | switch environment.currentInputField { 39 | case .number: return $environment.number 40 | case .holder: return $environment.holderName 41 | case .cvv: return $environment.cvv 42 | case .validThru: return $environment.validThru 43 | } 44 | } 45 | 46 | var body: some View { 47 | VStack(alignment: .center, spacing: 12) { 48 | PayCardView().layoutPriority(1) 49 | HStack { 50 | Text("Type in you card details:") 51 | .font(.system(size: 25)) 52 | .fontWeight(.bold) 53 | Spacer() 54 | } 55 | VStack(alignment: .trailing, spacing: 8) { 56 | PayCardFieldInputView(field: environment.currentInputField, 57 | binding: textFieldBinding, 58 | isFirstResponder: true) 59 | .transition(.slideRightToLeft) 60 | HStack { 61 | Button(action: goNext) { ActionButton(title: self.environment.currentInputField.buttonTitle) } 62 | .opacity( environment.canGoNext ? 1 : 0.6) 63 | } 64 | } 65 | Spacer() 66 | }.padding(20) 67 | .environmentObject(environment).onAppear { 68 | self.environment.clear() 69 | } 70 | 71 | } 72 | 73 | private func goNext() { 74 | guard environment.canGoNext else { return } 75 | 76 | if environment.currentInputField.isLastInput { 77 | withAnimation { environment.clear() } 78 | } else { 79 | withAnimation { environment.goToNextField() } 80 | } 81 | } 82 | } 83 | 84 | 85 | #if DEBUG 86 | struct TypeYourCardScene_Previews : PreviewProvider { 87 | static var environment: TypeCardEnvironment { 88 | let env = TypeCardEnvironment() 89 | env.number = "4124" 90 | return env 91 | } 92 | 93 | 94 | static var previews: some View { 95 | TypeCardScene(environment: environment) 96 | .padding(20) 97 | } 98 | } 99 | #endif 100 | -------------------------------------------------------------------------------- /TypeYourCard/TypeCardScene/Views/ActionButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActionButton.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 24.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ActionButton : View { 12 | var title: String 13 | 14 | private var shape: some View { 15 | GeometryReader { geometry in 16 | Path { path in 17 | let height = geometry.size.height 18 | let frame = geometry.frame(in: .local) 19 | let bigCornerRadius = height/2 20 | 21 | 22 | path.addRoundedRect(in: frame, 23 | cornerSize: CGSize(width: bigCornerRadius, 24 | height: bigCornerRadius), 25 | style: .circular, 26 | transform: .identity) 27 | 28 | 29 | let halfFrame = CGRect(x: frame.midX, y: frame.minY, width: frame.width / 2, height: frame.height / 1.6) 30 | let smallCornerRadius = halfFrame.height / 4 31 | 32 | path.addRoundedRect(in: halfFrame, 33 | cornerSize: CGSize(width: smallCornerRadius, 34 | height: smallCornerRadius), 35 | style: .circular, 36 | transform: .identity) 37 | }.fill(LinearGradient.forActionButton) 38 | } 39 | } 40 | 41 | var body: some View { 42 | ZStack { 43 | shape.shadow(color: Color.black.opacity(0.2), radius: 3, x: 0, y: 3) 44 | Text(title) 45 | .font(.headline) 46 | 47 | } 48 | .aspectRatio(216/117, contentMode: .fit) 49 | .frame(height: 50) 50 | .foregroundColor(.white) 51 | 52 | } 53 | } 54 | 55 | #if DEBUG 56 | struct ActionButton_Previews : PreviewProvider { 57 | static var previews: some View { 58 | ActionButton(title: "Next") 59 | } 60 | } 61 | #endif 62 | -------------------------------------------------------------------------------- /TypeYourCard/TypeCardScene/Views/MasterCardLogoView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MasterCardLogoView.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 27.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | fileprivate extension Animation { 12 | static var leftArcAppearance: Animation { 13 | Animation.linear(duration: 5) 14 | } 15 | } 16 | 17 | //extension AnyTransition { 18 | // static var slideRightToLeft: AnyTransition { 19 | // let slide = AnyTransition.asymmetric(insertion: AnyTransition.move(edge: .trailing), 20 | // removal: AnyTransition.move(edge: .leading)) 21 | // return slide.combined(with: .opacity) 22 | // } 23 | //} 24 | 25 | fileprivate extension Animation { 26 | static var circleDraw: Animation { 27 | Animation.easeInOut(duration: 1).delay(0.2) 28 | } 29 | 30 | static var fillColor: Animation { 31 | Animation.linear(duration: 0.5).delay(1.5) 32 | } 33 | } 34 | 35 | struct CircleShape: Shape { 36 | var endPath: Double 37 | func path(in rect: CGRect) -> Path { 38 | 39 | var path = Path() 40 | let radius = rect.height / 2 41 | let center = CGPoint(x: radius, y: rect.midY) 42 | path.addArc(center: center, 43 | radius: radius, 44 | startAngle: .degrees(0), 45 | endAngle: .degrees(endPath), 46 | clockwise: false) 47 | 48 | return path 49 | } 50 | 51 | var animatableData: AnimatablePair { 52 | get { .init(endPath, 0) } 53 | set { endPath = newValue.first } 54 | } 55 | 56 | } 57 | 58 | struct CircleView: View { 59 | var visible: Bool = false 60 | var color: Color 61 | var animationDelay: Double = 0 62 | var opacity: Double = 0.6 63 | 64 | var body: some View { 65 | ZStack { 66 | Circle() 67 | .fill(color) 68 | .opacity(visible ? opacity : 0) 69 | .animation(.fillColor) 70 | 71 | CircleShape(endPath: self.visible ? 360 : 0) 72 | .stroke(color, lineWidth: 1) 73 | .animation(Animation.circleDraw.delay(animationDelay)) 74 | .rotationEffect(.degrees(self.visible ? 460 : 0), anchor: UnitPoint.center) 75 | }.aspectRatio(1, contentMode: .fit) 76 | } 77 | } 78 | 79 | struct MasterCardLogoView : View { 80 | @State private var visible: Bool = false 81 | var height: CGFloat = 30 82 | private let aspectRatio: CGFloat = 1.64 83 | private var offset: CGFloat { return height / (2 * aspectRatio)} 84 | 85 | var body: some View { 86 | ZStack{ 87 | Circle() 88 | .fill(Color(0xEA011A)) 89 | .opacity(visible ? 1 : 0) 90 | .animation(.fillColor) 91 | .frame(height: height) 92 | .offset(x: -offset, y: 0) 93 | 94 | CircleView(visible: self.visible, 95 | color: Color(0xEA011A) ) 96 | .zIndex(1) 97 | .offset(x: -offset, y: 0) 98 | 99 | CircleView(visible: self.visible, 100 | color: Color(0xF79F19), 101 | animationDelay: 0.3, 102 | opacity: 1) 103 | .offset(x: offset, y: 0) 104 | }.padding(0) 105 | .frame(width: height * aspectRatio, height: height) 106 | .onAppear() { 107 | if self.visible { 108 | self.visible.toggle() 109 | } else { 110 | withAnimation { self.visible.toggle() 111 | } 112 | } 113 | } 114 | } 115 | } 116 | 117 | #if DEBUG 118 | struct MasterCardLogoView_Previews : PreviewProvider { 119 | static var previews: some View { 120 | MasterCardLogoView(height: 100) 121 | } 122 | } 123 | #endif 124 | -------------------------------------------------------------------------------- /TypeYourCard/TypeCardScene/Views/PayCardFieldInputView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CardInputView.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 24.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | fileprivate extension PayCardField { 12 | var title: String { 13 | switch self { 14 | case .number: return "Card Number" 15 | case .holder: return "Cardholder Name" 16 | case .validThru: return "Valid Thru" 17 | case .cvv: return "Security Code (CVV)" 18 | } 19 | } 20 | 21 | var keyboardType: UIKeyboardType { 22 | switch self { 23 | case .holder: return .default 24 | default: return .numberPad 25 | } 26 | } 27 | } 28 | 29 | struct PayCardFieldInputView : View { 30 | var field: PayCardField 31 | var binding: Binding 32 | var isFirstResponder: Bool 33 | 34 | var body: some View { 35 | VStack(alignment: HorizontalAlignment.leading, spacing: 6) { 36 | Text(field.title) 37 | .font(.system(size: 15)) 38 | .opacity(0.5) 39 | 40 | ResponderableTextField(text: binding, 41 | isFirstResponder: isFirstResponder, 42 | keyboardType: field.keyboardType) 43 | .padding(10) 44 | .frame(height: 50) 45 | .border(Color.black, width: 0.3) 46 | .cornerRadius(10) 47 | 48 | }.id(field.title) 49 | } 50 | } 51 | 52 | #if DEBUG 53 | struct PayCardFieldInputView_Previews : PreviewProvider { 54 | static var previews: some View { 55 | PayCardFieldInputView(field: .number, binding: .constant("1234"), isFirstResponder: true).padding(20) 56 | } 57 | } 58 | #endif 59 | -------------------------------------------------------------------------------- /TypeYourCard/TypeCardScene/Views/PayCardView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CardView.swift 3 | // TypeYourCard 4 | // 5 | // Created by Alexej Nenastev on 16.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct GoldBorder: ViewModifier { 12 | let visible: Bool 13 | let cornerRadius: CGFloat = 10 14 | var padding: EdgeInsets = .init(top: 2, leading: 6, bottom: 5, trailing: 6) 15 | 16 | func body(content: Content) -> some View { 17 | content 18 | .padding(padding) 19 | .border(Color(0xC0AE5B), 20 | width: visible ? 2 : 0) 21 | .cornerRadius(cornerRadius) 22 | .padding(EdgeInsets(top: -padding.top, 23 | leading: -padding.leading, 24 | bottom: -padding.bottom, 25 | trailing: -padding.trailing)) 26 | 27 | } 28 | } 29 | //MARK: Front side 30 | fileprivate struct FrontCardSide: View { 31 | @EnvironmentObject var environment: TypeCardEnvironment 32 | 33 | var body: some View { 34 | VStack { 35 | HStack { 36 | Text(" ").frame(height: 30) 37 | Spacer() 38 | PayServiceLogoView() 39 | } 40 | Spacer(minLength: 6) 41 | HStack { 42 | PlaceholderText(placeholder: "XXXX", text: environment.onCardNumberPart1) 43 | Spacer() 44 | PlaceholderText(placeholder: "XXXX", text: environment.onCardNumberPart2) 45 | Spacer() 46 | PlaceholderText(placeholder: "XXXX", text: environment.onCardNumberPart3) 47 | Spacer() 48 | PlaceholderText(placeholder: "XXXX", text: environment.onCardNumberPart4) 49 | } 50 | .modifier(GoldBorder(visible: environment.currentInputField == PayCardField.number)) 51 | .font(.cardNumberField) 52 | .foregroundColor(.white) 53 | .onTapGesture { self.goTo(field: .number) } 54 | Spacer(minLength: 6) 55 | HStack { 56 | VStack(alignment: .leading, spacing: 3) { 57 | Text("CARDHOLDER NAME") 58 | .font(Font.custom("Lucida Grande", size: 13)) 59 | PlaceholderText(placeholder: "NAME SURNAME", 60 | text: environment.onCardCardholerName, 61 | showPlaceholderWhileTyping: false) 62 | .font(.cardRegularField) 63 | .modifier(GoldBorder(visible: environment.currentInputField == PayCardField.holder)) 64 | .onTapGesture { self.goTo(field: .holder) } 65 | } 66 | Spacer() 67 | VStack(alignment: .trailing, spacing: 3) { 68 | Text("VALID THRU") 69 | .font(Font.custom("Lucida Grande", size: 13)) 70 | PlaceholderText(placeholder: "MM/YY", text: environment.onCardValidThru) 71 | .font(.cardRegularField) 72 | .modifier(GoldBorder(visible: environment.currentInputField == PayCardField.validThru)) 73 | .onTapGesture { self.goTo(field: .validThru) } 74 | } 75 | } 76 | .foregroundColor(Color.white) 77 | }.padding(18) 78 | .animation(Animation.FlipOtherSide.hideSideWhileFlip) 79 | } 80 | 81 | private func goTo(field: PayCardField) { 82 | withAnimation { self.environment.goToIfCan(field: field) } 83 | } 84 | } 85 | 86 | //MARK: Back side 87 | fileprivate struct BackCardSide: View { 88 | @EnvironmentObject var environment: TypeCardEnvironment 89 | 90 | var body: some View { 91 | VStack(spacing: 15) { 92 | Color.black.frame(height: 60) 93 | .padding([.leading, .trailing], -18) 94 | .padding(.top, 11) 95 | HStack { 96 | Color.gray.frame(width: 240, height: 40, alignment: .center) 97 | ZStack { 98 | Color.white 99 | PlaceholderText(placeholder: "XXX", text: environment.onCardCVV) 100 | .font(.cardRegularField) 101 | .padding(.bottom, 3) 102 | }.frame(width: 40, height: 32) 103 | .cornerRadius(6) 104 | .modifier(GoldBorder(visible: true, 105 | padding: .init(top: 5, leading: 5, bottom: 5, trailing: 5))) 106 | Spacer() 107 | } 108 | Spacer() 109 | HStack { 110 | Spacer() 111 | PayServiceLogoView() 112 | .animation(nil) 113 | } 114 | }.padding(18) 115 | .rotation3DEffect(.degrees(-180), axis: (x: 0, y: 1, z: 0)) 116 | .animation(Animation.FlipOtherSide.hideSideWhileFlip) 117 | .onTapGesture(perform: environment.goToPreviousField) 118 | 119 | } 120 | } 121 | 122 | //MARK: PayCard 123 | struct PayCardView : View { 124 | @EnvironmentObject var environment: TypeCardEnvironment 125 | 126 | private var isBackSide: Bool { environment.currentInputField == .cvv } 127 | 128 | var body: some View { 129 | ZStack { 130 | BackCardSide().opacity(isBackSide ? 1 : 0) 131 | FrontCardSide().opacity(isBackSide ? 0 : 1) 132 | } 133 | .background(Color(0x191622)) 134 | .cornerRadius(16) 135 | .aspectRatio(1.46, contentMode: .fit) //1.46 original 136 | .shadow(radius: 10) 137 | .rotation3DEffect(.degrees(isBackSide ? -180 : 0), axis: (x: 0, y: 1, z: 0)) 138 | .animation(Animation.FlipOtherSide.flip) 139 | } 140 | } 141 | 142 | #if DEBUG 143 | struct CardView_Previews : PreviewProvider { 144 | static var environment: TypeCardEnvironment { 145 | let env = TypeCardEnvironment() 146 | env.number = "4124" 147 | return env 148 | } 149 | 150 | static var previews: some View { 151 | PayCardView().padding(20).environmentObject(environment) 152 | 153 | } 154 | } 155 | #endif 156 | -------------------------------------------------------------------------------- /TypeYourCard/TypeCardScene/Views/PayServiceLogoView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PayServiceLogo.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 26.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | extension PayService { 12 | 13 | var view: some View { 14 | switch self { 15 | case .MasterCard: return MasterCardLogoView(height: 30) 16 | } 17 | } 18 | } 19 | 20 | 21 | struct PayServiceLogoView : View { 22 | @EnvironmentObject var typeCardEnvironment: TypeCardEnvironment 23 | 24 | private var name: String { 25 | if let service = typeCardEnvironment.payService { 26 | return "\(service)" 27 | } else { 28 | return "Unknown" 29 | } 30 | } 31 | 32 | var body: some View { 33 | ZStack { 34 | if typeCardEnvironment.payService != nil { 35 | typeCardEnvironment.payService!.view 36 | } else { 37 | EmptyView().frame(height: 30) 38 | } 39 | 40 | } 41 | } 42 | } 43 | 44 | #if DEBUG 45 | struct PayServiceLogoView_Previews : PreviewProvider { 46 | static var environment: TypeCardEnvironment { 47 | let env = TypeCardEnvironment() 48 | env.number = "4124" 49 | return env 50 | } 51 | 52 | static var previews: some View { 53 | PayServiceLogoView().environmentObject(environment) 54 | } 55 | } 56 | #endif 57 | -------------------------------------------------------------------------------- /TypeYourCard/TypeCardScene/Views/PlaceholderText.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlaceholderText.swift 3 | // TypeYourCard 4 | // 5 | // Created by alexej_ne on 25.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Combine 11 | 12 | struct PlaceholderText : View { 13 | 14 | private let placeholder: String 15 | private let text: String 16 | private let showBorder: Bool 17 | 18 | init(placeholder: String, text: String, showBorder: Bool = false, showPlaceholderWhileTyping: Bool = true) { 19 | if showPlaceholderWhileTyping { 20 | let count = placeholder.count - text.count 21 | self.placeholder = String(placeholder.suffix( count > 0 ? count : 0 )) 22 | } else { 23 | self.placeholder = text.count > 0 ? "" : placeholder 24 | } 25 | self.text = text 26 | self.showBorder = showBorder 27 | print("TEXT ", text) 28 | print("placeholder ", placeholder) 29 | } 30 | 31 | 32 | var body: some View { 33 | HStack(spacing: 0) { 34 | Text(text) 35 | Text(placeholder) 36 | .opacity(0.3) 37 | }.padding(0) 38 | .animation(nil) 39 | } 40 | } 41 | 42 | 43 | #if DEBUG 44 | struct PlaceholderText_Previews : PreviewProvider { 45 | static var previews: some View { 46 | PlaceholderText(placeholder: "NAME LASTNAME", text: "ALEX") 47 | } 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /TypeYourCard/TypeCardScene/Views/ResponderableTextField.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResponderableTextField.swift 3 | // TypeYourCard 4 | // 5 | // Created by Alexej Nenastev on 27.06.2019. 6 | // Copyright © 2019 Alexej Nenastev. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ResponderableTextField: UIViewRepresentable { 12 | 13 | class Coordinator: NSObject, UITextFieldDelegate { 14 | 15 | @Binding var text: String 16 | var didBecomeFirstResponder = false 17 | 18 | init(text: Binding) { 19 | self._text = text 20 | } 21 | 22 | func textFieldDidChangeSelection(_ textField: UITextField) { 23 | text = textField.text ?? "" 24 | } 25 | } 26 | 27 | 28 | @Binding var text: String 29 | var isFirstResponder: Bool = false 30 | var keyboardType: UIKeyboardType = .default 31 | 32 | func makeUIView(context: UIViewRepresentableContext) -> UITextField { 33 | let textField = UITextField(frame: .zero) 34 | textField.delegate = context.coordinator 35 | textField.keyboardType = keyboardType 36 | return textField 37 | } 38 | 39 | func makeCoordinator() -> ResponderableTextField.Coordinator { 40 | return Coordinator(text: $text) 41 | } 42 | 43 | func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext) { 44 | uiView.text = text 45 | if isFirstResponder && !context.coordinator.didBecomeFirstResponder { 46 | uiView.becomeFirstResponder() 47 | context.coordinator.didBecomeFirstResponder = true 48 | } 49 | } 50 | } 51 | --------------------------------------------------------------------------------