├── README.txt ├── icon.png ├── icon.psd ├── reverseapft ├── Images.xcassets │ ├── Contents.json │ ├── ch14.dataset │ │ ├── fm21_20.pdf │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Icon.png │ │ ├── Icon-40.png │ │ ├── Icon-72.png │ │ ├── Icon-76.png │ │ ├── Icon@2x.png │ │ ├── Icon-40@2x.png │ │ ├── Icon-40@3x.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-72@2x.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-83.5@2x.png │ │ ├── Icon-Small-50.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ ├── ios-marketing.png │ │ ├── Icon-Small-50@2x.png │ │ ├── NotificationIcon@2x.png │ │ ├── NotificationIcon@3x.png │ │ ├── NotificationIcon~ipad.png │ │ ├── NotificationIcon~ipad@2x.png │ │ └── Contents.json │ ├── star-fill.imageset │ │ ├── star-rate.pdf │ │ └── Contents.json │ ├── calc.imageset │ │ ├── icons8-calculator-30.png │ │ ├── icons8-calculator-60.png │ │ ├── icons8-calculator-90.png │ │ └── Contents.json │ ├── docs.imageset │ │ ├── icons8-documents-30.png │ │ ├── icons8-documents-60.png │ │ ├── icons8-documents-90.png │ │ └── Contents.json │ ├── play.imageset │ │ ├── icons8-play-filled-30.png │ │ ├── icons8-play-filled-60.png │ │ ├── icons8-play-filled-90.png │ │ └── Contents.json │ ├── stop.imageset │ │ ├── icons8-stop-filled-30.png │ │ ├── icons8-stop-filled-60.png │ │ ├── icons8-stop-filled-90.png │ │ └── Contents.json │ ├── manual.imageset │ │ ├── icons8-user-manual-30.png │ │ ├── icons8-user-manual-60.png │ │ ├── icons8-user-manual-90.png │ │ └── Contents.json │ ├── star.imageset │ │ ├── icons8-christmas-star-30.png │ │ ├── icons8-christmas-star-60.png │ │ ├── icons8-christmas-star-90.png │ │ └── Contents.json │ ├── download.imageset │ │ ├── icons8-downloading-updates-30.png │ │ ├── icons8-downloading-updates-60.png │ │ ├── icons8-downloading-updates-90.png │ │ └── Contents.json │ └── rev.imageset │ │ ├── icons8-reversed-numerical-sorting-filled-30.png │ │ ├── icons8-reversed-numerical-sorting-filled-60.png │ │ ├── icons8-reversed-numerical-sorting-filled-90.png │ │ └── Contents.json ├── Settings.bundle │ ├── en.lproj │ │ └── Root.strings │ ├── Root.plist │ └── Licenses.plist ├── Globals.swift ├── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.xib ├── Info.plist ├── SRCTabBarController.swift ├── UIViewController+extras.swift ├── AppDelegate.swift ├── DocViewController.swift ├── CalcViewController.swift ├── ReverseCalcViewController.swift ├── Storyboard.storyboard ├── Scores.swift └── InstructionsViewController.swift ├── reverseapft.xcodeproj ├── xcuserdata │ └── zac.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── zac.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ ├── reverseapft2.xcscheme │ │ └── reverseapft.xcscheme └── project.pbxproj └── reverseapftTests ├── Info.plist ├── runTests.swift ├── SitupTest.swift └── PushupTest.swift /README.txt: -------------------------------------------------------------------------------- 1 | "Attribution-NonCommercial-ShareAlike 4.0 International" license 2 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/icon.png -------------------------------------------------------------------------------- /icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/icon.psd -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /reverseapft/Settings.bundle/en.lproj/Root.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Settings.bundle/en.lproj/Root.strings -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/ch14.dataset/fm21_20.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/ch14.dataset/fm21_20.pdf -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon.png -------------------------------------------------------------------------------- /reverseapft.xcodeproj/xcuserdata/zac.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-40.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-72.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon@2x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-40@2x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-40@3x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-72@2x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/star-fill.imageset/star-rate.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/star-fill.imageset/star-rate.pdf -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-Small-50.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/ios-marketing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/ios-marketing.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/Icon-Small-50@2x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/calc.imageset/icons8-calculator-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/calc.imageset/icons8-calculator-30.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/calc.imageset/icons8-calculator-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/calc.imageset/icons8-calculator-60.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/calc.imageset/icons8-calculator-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/calc.imageset/icons8-calculator-90.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/docs.imageset/icons8-documents-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/docs.imageset/icons8-documents-30.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/docs.imageset/icons8-documents-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/docs.imageset/icons8-documents-60.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/docs.imageset/icons8-documents-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/docs.imageset/icons8-documents-90.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/play.imageset/icons8-play-filled-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/play.imageset/icons8-play-filled-30.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/play.imageset/icons8-play-filled-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/play.imageset/icons8-play-filled-60.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/play.imageset/icons8-play-filled-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/play.imageset/icons8-play-filled-90.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/stop.imageset/icons8-stop-filled-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/stop.imageset/icons8-stop-filled-30.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/stop.imageset/icons8-stop-filled-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/stop.imageset/icons8-stop-filled-60.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/stop.imageset/icons8-stop-filled-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/stop.imageset/icons8-stop-filled-90.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/manual.imageset/icons8-user-manual-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/manual.imageset/icons8-user-manual-30.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/manual.imageset/icons8-user-manual-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/manual.imageset/icons8-user-manual-60.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/manual.imageset/icons8-user-manual-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/manual.imageset/icons8-user-manual-90.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/NotificationIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/NotificationIcon@2x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/NotificationIcon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/NotificationIcon@3x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/NotificationIcon~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/NotificationIcon~ipad.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/star.imageset/icons8-christmas-star-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/star.imageset/icons8-christmas-star-30.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/star.imageset/icons8-christmas-star-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/star.imageset/icons8-christmas-star-60.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/star.imageset/icons8-christmas-star-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/star.imageset/icons8-christmas-star-90.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/NotificationIcon~ipad@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/AppIcon.appiconset/NotificationIcon~ipad@2x.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/download.imageset/icons8-downloading-updates-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/download.imageset/icons8-downloading-updates-30.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/download.imageset/icons8-downloading-updates-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/download.imageset/icons8-downloading-updates-60.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/download.imageset/icons8-downloading-updates-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/download.imageset/icons8-downloading-updates-90.png -------------------------------------------------------------------------------- /reverseapft.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/rev.imageset/icons8-reversed-numerical-sorting-filled-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/rev.imageset/icons8-reversed-numerical-sorting-filled-30.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/rev.imageset/icons8-reversed-numerical-sorting-filled-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/rev.imageset/icons8-reversed-numerical-sorting-filled-60.png -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/rev.imageset/icons8-reversed-numerical-sorting-filled-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft/Images.xcassets/rev.imageset/icons8-reversed-numerical-sorting-filled-90.png -------------------------------------------------------------------------------- /reverseapft.xcodeproj/project.xcworkspace/xcuserdata/zac.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twodayslate/APFT-Calculator-and-Resources/master/reverseapft.xcodeproj/project.xcworkspace/xcuserdata/zac.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/ch14.dataset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | }, 6 | "data" : [ 7 | { 8 | "idiom" : "universal", 9 | "filename" : "fm21_20.pdf", 10 | "universal-type-identifier" : "com.adobe.pdf" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/calc.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-calculator-30.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-calculator-60.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-calculator-90.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/docs.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-documents-30.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-documents-60.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-documents-90.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/manual.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-user-manual-30.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-user-manual-60.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-user-manual-90.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/star.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-christmas-star-30.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-christmas-star-60.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-christmas-star-90.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/download.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-downloading-updates-30.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-downloading-updates-60.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-downloading-updates-90.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/star-fill.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "star-rate.pdf" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "1x" 10 | }, 11 | { 12 | "idiom" : "universal", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "idiom" : "universal", 17 | "scale" : "3x" 18 | } 19 | ], 20 | "info" : { 21 | "version" : 1, 22 | "author" : "xcode" 23 | }, 24 | "properties" : { 25 | "template-rendering-intent" : "template" 26 | } 27 | } -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/rev.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-reversed-numerical-sorting-filled-30.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-reversed-numerical-sorting-filled-60.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-reversed-numerical-sorting-filled-90.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/play.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-play-filled-30.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-play-filled-60.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-play-filled-90.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | }, 23 | "properties" : { 24 | "template-rendering-intent" : "template" 25 | } 26 | } -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/stop.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icons8-stop-filled-30.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icons8-stop-filled-60.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "icons8-stop-filled-90.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | }, 23 | "properties" : { 24 | "template-rendering-intent" : "template" 25 | } 26 | } -------------------------------------------------------------------------------- /reverseapft/Globals.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Globals.swift 3 | // reverseapft 4 | // 5 | // Created by Zachary Gorak on 2/20/18. 6 | // Copyright © 2018 twodayslate. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func printd(_ items: Any..., callingFunction : String = #function, file : String = #file, line : Int = #line) { 12 | #if DEVELOP || DEBUG || !PRODCTN 13 | let s = items.map { String(describing: $0) }.joined(separator: " ") 14 | let shortfile = file.split(separator: ".").dropLast().last! 15 | let shortfunction = callingFunction.split(separator: "(").first! 16 | Swift.print("[\(shortfile)/\(shortfunction):\(line)] " + s) 17 | #endif 18 | } 19 | -------------------------------------------------------------------------------- /reverseapftTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /reverseapft.xcodeproj/xcuserdata/zac.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | reverseapft.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | reverseapft2.xcscheme_^#shared#^_ 13 | 14 | orderHint 15 | 1 16 | 17 | 18 | SuppressBuildableAutocreation 19 | 20 | 118BF02119EDF13E00B66C26 21 | 22 | primary 23 | 24 | 25 | 118BF03619EDF13E00B66C26 26 | 27 | primary 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /reverseapftTests/runTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // reverseapftTests.swift 3 | // reverseapftTests 4 | // 5 | // Created by twodayslate on 10/14/14. 6 | // Copyright (c) 2014 twodayslate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class runTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testScore() { 25 | Score.age = 21 26 | Score.gender = .male 27 | XCTAssertEqual(Score.score(forRun: 1200), 100) 28 | XCTAssertEqual(Score.score(forRun: 1299), 100) 29 | XCTAssertEqual(Score.score(forRun: 1300), 100) 30 | XCTAssertEqual(Score.score(forRun: 1301), 99) 31 | XCTAssertEqual(Score.score(forRun: 1306), 99) 32 | XCTAssertEqual(Score.score(forRun: 1307), 97) 33 | XCTAssertEqual(Score.score(forRun: 1312), 97) 34 | XCTAssertEqual(Score.score(forRun: 1400), 86) 35 | XCTAssertEqual(Score.score(forRun: 1500), 72) 36 | XCTAssertEqual(Score.score(forRun: 1806), 30) 37 | XCTAssertEqual(Score.score(forRun: 1800), 31) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /reverseapft/Settings.bundle/Root.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StringsTable 6 | Root 7 | PreferenceSpecifiers 8 | 9 | 10 | Type 11 | PSGroupSpecifier 12 | Title 13 | Sounds 14 | FooterText 15 | A restart may be required 16 | 17 | 18 | Type 19 | PSToggleSwitchSpecifier 20 | Title 21 | Respect Mute Switch 22 | Key 23 | respect_mute 24 | DefaultValue 25 | 26 | 27 | 28 | Type 29 | PSGroupSpecifier 30 | Title 31 | About 32 | 33 | 34 | Type 35 | PSTitleValueSpecifier 36 | Title 37 | Version 38 | Key 39 | version 40 | DefaultValue 41 | - 42 | 43 | 44 | Type 45 | PSChildPaneSpecifier 46 | File 47 | Licenses 48 | Title 49 | Acknowledgements & Licenses 50 | Key 51 | license 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /reverseapft/Base.lproj/Main.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 | -------------------------------------------------------------------------------- /reverseapft/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | APFT 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIcons 12 | 13 | CFBundleIcons~ipad 14 | 15 | CFBundleIdentifier 16 | $(PRODUCT_BUNDLE_IDENTIFIER) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | reverseAPFT 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | $(MARKETING_VERSION) 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | $(CURRENT_PROJECT_VERSION) 29 | LSRequiresIPhoneOS 30 | 31 | UIApplicationSceneManifest 32 | 33 | UIApplicationSupportsMultipleScenes 34 | 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UIRequiresFullScreen 43 | 44 | UIStatusBarStyle 45 | UIStatusBarStyleDefault 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | UISupportedInterfaceOrientations~ipad 54 | 55 | UIInterfaceOrientationPortrait 56 | UIInterfaceOrientationPortraitUpsideDown 57 | UIInterfaceOrientationLandscapeLeft 58 | UIInterfaceOrientationLandscapeRight 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /reverseapftTests/SitupTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SitupsTest.swift 3 | // reverseapftTests 4 | // 5 | // Created by Zachary Gorak on 3/2/18. 6 | // Copyright © 2018 twodayslate. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class SitupTests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func testScore() { 24 | Score.age = 21 25 | Score.gender = .male 26 | var score = Score.score(forSitups: 79) 27 | XCTAssert(score == 100, "79: \(score) != 100") 28 | score = Score.score(forSitups: 78) 29 | XCTAssert(score == 100, "78: \(score) != 100") 30 | score = Score.score(forSitups: 77) 31 | XCTAssert(score == 98, "77: \(score) != 98") 32 | score = Score.score(forSitups: 76) 33 | XCTAssert(score == 97, "76: \(score) != 97") 34 | 35 | score = Score.score(forSitups: 75) 36 | XCTAssert(score == 95, "75: \(score) != 95") 37 | 38 | score = Score.score(forSitups: 74) 39 | XCTAssert(score == 94, "74: \(score) != 94") 40 | 41 | score = Score.score(forSitups: 73) 42 | XCTAssert(score == 92, "73: \(score) != 92") 43 | 44 | score = Score.score(forSitups: 68) 45 | XCTAssert(score == 84, "68: \(score) != 84") 46 | score = Score.score(forSitups: 35) 47 | XCTAssert(score == 31, "35: \(score) != 31") 48 | score = Score.score(forSitups: 34) 49 | XCTAssert(score == 30, "34: \(score) != 30") 50 | XCTAssert(Score.score(forSitups: 33) == 28) 51 | XCTAssert(Score.score(forSitups: 23) == 12) 52 | } 53 | 54 | func testSitups() { 55 | Score.age = 21 56 | Score.gender = .male 57 | XCTAssert(Score.situps(forScore: 100) == 78) 58 | XCTAssert(Score.situps(forScore: 99) == 78) 59 | XCTAssert(Score.situps(forScore: 98) == 77) 60 | XCTAssert(Score.situps(forScore: 97) == 76) 61 | XCTAssert(Score.situps(forScore: 96) == 76) 62 | XCTAssert(Score.situps(forScore: 95) == 75) 63 | XCTAssert(Score.situps(forScore: 94) == 74) 64 | 65 | XCTAssert(Score.situps(forScore: 30) == 34) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /reverseapft/SRCTabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SRCTabBarController.swift 3 | // reverseapft 4 | // 5 | // Created by Zachary Gorak on 11/6/18. 6 | // Copyright © 2018 twodayslate. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import StoreKit 12 | 13 | class SRCTabBarController: UITabBarController, UITabBarControllerDelegate, SKStoreProductViewControllerDelegate { 14 | var acftStoreProduct: SKStoreProductViewController? 15 | 16 | func createAcftStoreProduct(force: Bool = false) { 17 | if (self.acftStoreProduct == nil || force) { 18 | self.acftStoreProduct = SKStoreProductViewController() 19 | self.acftStoreProduct?.delegate = self 20 | let params = [ 21 | SKStoreProductParameterITunesItemIdentifier:1439376176 22 | ] 23 | self.acftStoreProduct!.loadProduct(withParameters: params, completionBlock: { status, _ in 24 | printd("done loading store product", status) 25 | if(!status) { 26 | self.createAcftStoreProduct(force: true) 27 | } 28 | }) 29 | } 30 | } 31 | 32 | func productViewControllerDidFinish(_ viewController: SKStoreProductViewController) { 33 | viewController.dismiss(animated: true, completion: nil) 34 | self.createAcftStoreProduct(force: true) // if it isn't recreated then the status bar covers up the navigation 35 | } 36 | 37 | func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool { 38 | return viewController.tabBarItem.tag != 666 39 | } 40 | 41 | override func viewDidLoad() { 42 | super.viewDidLoad() 43 | self.createAcftStoreProduct() 44 | 45 | if #available(iOS 13.0, *) { 46 | self.view.backgroundColor = UIColor.systemBackground 47 | } else { 48 | self.view.backgroundColor = .white 49 | } 50 | } 51 | 52 | override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) { 53 | if(item.tag == 666) { 54 | //let app = URL(string: "itms-apps://itunes.apple.com/app/id/1439376176?mt=8") 55 | //let link = URL(string: "https://itunes.apple.com/us/app/acft-calculator-and-resources/id1439376176?mt=8") 56 | self.createAcftStoreProduct() 57 | self.acftStoreProduct?.popoverPresentationController?.sourceView = self.selectedViewController?.view 58 | self.present(self.acftStoreProduct!, animated: true, completion: nil) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /reverseapft/UIViewController+extras.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIViewController.swift 3 | // ec3730 4 | // 5 | // Created by Zachary Gorak on 8/27/18. 6 | // Copyright © 2018 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | 13 | /** 14 | * Reference: https://stackoverflow.com/questions/45399178/extend-ios-11-safe-area-to-include-the-keyboard 15 | */ 16 | extension UIViewController { 17 | 18 | func startAvoidingKeyboard() { 19 | NotificationCenter.default.addObserver(self, 20 | selector: #selector(_onKeyboardFrameWillChangeNotificationReceived(_:)), 21 | name: UIResponder.keyboardWillChangeFrameNotification, 22 | object: nil) 23 | } 24 | 25 | func stopAvoidingKeyboard() { 26 | NotificationCenter.default.removeObserver(self, 27 | name: UIResponder.keyboardWillChangeFrameNotification, 28 | object: nil) 29 | } 30 | 31 | @objc private func _onKeyboardFrameWillChangeNotificationReceived(_ notification: Notification) { 32 | if #available(iOS 11.0, *) { 33 | 34 | guard let userInfo = notification.userInfo, 35 | let keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { 36 | return 37 | } 38 | 39 | let keyboardFrameInView = view.convert(keyboardFrame, from: nil) 40 | let safeAreaFrame = view.safeAreaLayoutGuide.layoutFrame.insetBy(dx: 0, dy: -additionalSafeAreaInsets.bottom) 41 | let intersection = safeAreaFrame.intersection(keyboardFrameInView) 42 | 43 | let animationDuration: TimeInterval = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0 44 | let animationCurveRawNSN = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber 45 | let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue 46 | let animationCurve = UIView.AnimationOptions(rawValue: animationCurveRaw) 47 | 48 | UIView.animate(withDuration: animationDuration, delay: 0, options: animationCurve, animations: { 49 | self.additionalSafeAreaInsets.bottom = intersection.height 50 | self.view.layoutIfNeeded() 51 | }, completion: nil) 52 | } 53 | } 54 | 55 | @objc func dismissKeyboard() { 56 | self.resignFirstResponder() 57 | self.view.endEditing(false) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /reverseapft/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /reverseapftTests/PushupTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PushupsTest.swift 3 | // reverseapftTests 4 | // 5 | // Created by Zachary Gorak on 3/2/18. 6 | // Copyright © 2018 twodayslate. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class PushupTests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func testScore() { 24 | Score.age = 21 25 | Score.gender = .male 26 | var score = Score.score(forPushups: 72) 27 | XCTAssert(score == 100, "72: \(score) != 100") 28 | XCTAssert(Score.score(forPushups: 71) == 100) 29 | XCTAssert(Score.score(forPushups: 70) == 99) 30 | score = Score.score(forPushups: 69) 31 | XCTAssert(score == 97, "69: \(score) != 97") 32 | score = Score.score(forPushups: 68) 33 | XCTAssert(score == 96, "68: \(score) != 96") 34 | score = Score.score(forPushups: 67) 35 | XCTAssert(score == 94, "67: \(score) != 94") 36 | XCTAssert(Score.score(forPushups: 21) == 31) 37 | XCTAssert(Score.score(forPushups: 20) == 30) 38 | } 39 | 40 | func testPushups() { 41 | Score.age = 21 42 | Score.gender = .male 43 | XCTAssert(Score.pushups(forScore: 100) == 71, "\(Score.pushups(forScore: 100))") 44 | XCTAssert(Score.pushups(forScore: 99) == 70, "\(Score.pushups(forScore: 99))") 45 | XCTAssert(Score.pushups(forScore: 98) == 70, "\(Score.pushups(forScore: 98))") 46 | XCTAssert(Score.pushups(forScore: 97) == 69, "\(Score.pushups(forScore: 97))") 47 | XCTAssert(Score.pushups(forScore: 96) == 68, "\(Score.pushups(forScore: 96))") 48 | XCTAssert(Score.pushups(forScore: 95) == 68, "\(Score.pushups(forScore: 95))") 49 | XCTAssert(Score.pushups(forScore: 94) == 67, "\(Score.pushups(forScore: 93))") 50 | XCTAssert(Score.pushups(forScore: 93) == 66, "\(Score.pushups(forScore: 92))") 51 | XCTAssert(Score.pushups(forScore: 92) == 65, "\(Score.pushups(forScore: 91))") 52 | 53 | XCTAssert(Score.pushups(forScore: 38) == 26, "\(Score.pushups(forScore: 38))") 54 | XCTAssert(Score.pushups(forScore: 37) == 25, "\(Score.pushups(forScore: 37))") 55 | XCTAssert(Score.pushups(forScore: 36) == 25, "\(Score.pushups(forScore: 36))") 56 | XCTAssert(Score.pushups(forScore: 35) == 24, "\(Score.pushups(forScore: 35))") 57 | XCTAssert(Score.pushups(forScore: 34) == 23, "\(Score.pushups(forScore: 34))") 58 | XCTAssert(Score.pushups(forScore: 33) == 23, "\(Score.pushups(forScore: 33))") 59 | XCTAssert(Score.pushups(forScore: 32) == 22, "\(Score.pushups(forScore: 32))") 60 | XCTAssert(Score.pushups(forScore: 31) == 21, "\(Score.pushups(forScore: 31))") 61 | XCTAssert(Score.pushups(forScore: 30) == 20, "\(Score.pushups(forScore: 30))") 62 | XCTAssert(Score.pushups(forScore: 9) == 5, "\(Score.pushups(forScore: 9))") 63 | XCTAssert(Score.pushups(forScore: 8) == 5, "\(Score.pushups(forScore: 8))") 64 | XCTAssert(Score.pushups(forScore: 0) == 0, "\(Score.pushups(forScore: 0))") 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /reverseapft.xcodeproj/xcshareddata/xcschemes/reverseapft2.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /reverseapft/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "40x40", 5 | "filename" : "Icon-40.png", 6 | "idiom" : "ipad", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "40x40", 11 | "filename" : "Icon-40@2x.png", 12 | "idiom" : "ipad", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "60x60", 17 | "filename" : "Icon-60@2x.png", 18 | "idiom" : "iphone", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "72x72", 23 | "filename" : "Icon-72.png", 24 | "idiom" : "ipad", 25 | "scale" : "1x" 26 | }, 27 | { 28 | "size" : "72x72", 29 | "filename" : "Icon-72@2x.png", 30 | "idiom" : "ipad", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "76x76", 35 | "filename" : "Icon-76.png", 36 | "idiom" : "ipad", 37 | "scale" : "1x" 38 | }, 39 | { 40 | "size" : "76x76", 41 | "filename" : "Icon-76@2x.png", 42 | "idiom" : "ipad", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "50x50", 47 | "filename" : "Icon-Small-50.png", 48 | "idiom" : "ipad", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "50x50", 53 | "filename" : "Icon-Small-50@2x.png", 54 | "idiom" : "ipad", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "29x29", 59 | "filename" : "Icon-Small.png", 60 | "idiom" : "iphone", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "29x29", 65 | "filename" : "Icon-Small@2x.png", 66 | "idiom" : "iphone", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "57x57", 71 | "filename" : "Icon.png", 72 | "idiom" : "iphone", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "57x57", 77 | "filename" : "Icon@2x.png", 78 | "idiom" : "iphone", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "29x29", 83 | "filename" : "Icon-Small@3x.png", 84 | "idiom" : "iphone", 85 | "scale" : "3x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "filename" : "Icon-40@3x.png", 90 | "idiom" : "iphone", 91 | "scale" : "3x" 92 | }, 93 | { 94 | "size" : "60x60", 95 | "filename" : "Icon-60@3x.png", 96 | "idiom" : "iphone", 97 | "scale" : "3x" 98 | }, 99 | { 100 | "size" : "40x40", 101 | "filename" : "Icon-40@2x.png", 102 | "idiom" : "iphone", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "29x29", 107 | "filename" : "Icon-Small.png", 108 | "idiom" : "ipad", 109 | "scale" : "1x" 110 | }, 111 | { 112 | "size" : "29x29", 113 | "filename" : "Icon-Small@2x.png", 114 | "idiom" : "ipad", 115 | "scale" : "2x" 116 | }, 117 | { 118 | "size" : "83.5x83.5", 119 | "filename" : "Icon-83.5@2x.png", 120 | "idiom" : "ipad", 121 | "scale" : "2x" 122 | }, 123 | { 124 | "size" : "20x20", 125 | "filename" : "NotificationIcon@2x.png", 126 | "idiom" : "iphone", 127 | "scale" : "2x" 128 | }, 129 | { 130 | "size" : "20x20", 131 | "filename" : "NotificationIcon@3x.png", 132 | "idiom" : "iphone", 133 | "scale" : "3x" 134 | }, 135 | { 136 | "size" : "20x20", 137 | "filename" : "NotificationIcon~ipad.png", 138 | "idiom" : "ipad", 139 | "scale" : "1x" 140 | }, 141 | { 142 | "size" : "20x20", 143 | "filename" : "NotificationIcon~ipad@2x.png", 144 | "idiom" : "ipad", 145 | "scale" : "2x" 146 | }, 147 | { 148 | "size" : "1024x1024", 149 | "filename" : "ios-marketing.png", 150 | "idiom" : "ios-marketing", 151 | "scale" : "1x" 152 | } 153 | ], 154 | "info" : { 155 | "author" : "xcode", 156 | "version" : 1 157 | } 158 | } -------------------------------------------------------------------------------- /reverseapft/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // reverseapft 4 | // 5 | // Created by twodayslate on 10/14/14. 6 | // Copyright (c) 2014 twodayslate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | 20 | printd("inside application") 21 | 22 | self.window = UIWindow(frame: UIScreen.main.bounds) 23 | if #available(iOS 13.0, *) { 24 | self.window?.backgroundColor = UIColor.black 25 | } else { 26 | self.window?.backgroundColor = .white 27 | } 28 | 29 | let nav = SRCTabBarController() 30 | nav.delegate = nav 31 | 32 | let calc = CalcViewController() 33 | calc.tabBarItem = UITabBarItem(title: "Calculator", image: UIImage(named: "calc"), tag: 0) 34 | 35 | let revCalc = ReverseCalcViewController() 36 | revCalc.tabBarItem = UITabBarItem(title: "Reverse Calculator", image: UIImage(named: "rev"), tag: 1) 37 | 38 | let docs = DocNavigationController() 39 | docs.tabBarItem = UITabBarItem(title: "Documents", image: UIImage(named: "docs"), tag: 2) 40 | 41 | let acftLink = UIViewController() 42 | acftLink.tabBarItem = UITabBarItem(title: "ACFT", image: UIImage(named: "star"), tag: 666) 43 | 44 | let instructions = InstructionsViewController() 45 | instructions.tabBarItem = UITabBarItem(title: "Instructions", image: UIImage(named: "manual"), tag: 3) 46 | 47 | nav.viewControllers = [calc, revCalc, instructions, docs, acftLink] 48 | 49 | self.window!.rootViewController = nav 50 | 51 | self.window!.makeKeyAndVisible() 52 | 53 | self.update_preferences() 54 | 55 | return true 56 | } 57 | 58 | func update_preferences() { 59 | let version: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String 60 | let build: String = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String 61 | UserDefaults.standard.set(version + "-" + build, forKey: "version") 62 | } 63 | 64 | func applicationWillResignActive(_ application: UIApplication) { 65 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 66 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 67 | } 68 | 69 | func applicationDidEnterBackground(_ application: UIApplication) { 70 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 71 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 72 | } 73 | 74 | func applicationWillEnterForeground(_ application: UIApplication) { 75 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 76 | } 77 | 78 | func applicationDidBecomeActive(_ application: UIApplication) { 79 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 80 | } 81 | 82 | func applicationWillTerminate(_ application: UIApplication) { 83 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 84 | } 85 | 86 | 87 | } 88 | 89 | -------------------------------------------------------------------------------- /reverseapft.xcodeproj/xcshareddata/xcschemes/reverseapft.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /reverseapft/DocViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // acft 4 | // 5 | // Created by Zachary Gorak on 10/15/18. 6 | // Copyright © 2018 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import PDFKit 11 | 12 | class DocNavigationController: UINavigationController { 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | self.viewControllers = [DocTableViewController()] 16 | } 17 | } 18 | 19 | class DocTableViewCell: UITableViewCell { 20 | let subtitleCell = UITableViewCell(style: .subtitle, reuseIdentifier: nil) 21 | let value1Cell = UITableViewCell(style: .value1, reuseIdentifier: nil) 22 | 23 | init() { 24 | super.init(style: .default, reuseIdentifier: "doc") 25 | 26 | self.heightAnchor.constraint(equalToConstant: self.frame.height).isActive = true 27 | 28 | let stack = UIStackView() 29 | stack.spacing = 10.0 30 | stack.axis = .horizontal 31 | stack.translatesAutoresizingMaskIntoConstraints = false 32 | stack.distribution = .fill 33 | self.contentView.addSubview(stack) 34 | 35 | self.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": stack])) 36 | self.contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": stack])) 37 | 38 | let leftStack = UIStackView() 39 | leftStack.translatesAutoresizingMaskIntoConstraints = false 40 | leftStack.spacing = 10.0 // you don't want this if there is no subtitle 41 | leftStack.axis = .vertical 42 | leftStack.distribution = .fill 43 | stack.addArrangedSubview(leftStack) 44 | 45 | self.title.font = self.subtitleCell.textLabel?.font 46 | self.title.textColor = self.subtitleCell.textLabel?.textColor 47 | self.title.tintColor = self.subtitleCell.textLabel?.tintColor 48 | leftStack.addArrangedSubview(self.title) 49 | 50 | self.subtitle.font = self.subtitleCell.detailTextLabel?.font 51 | self.subtitle.textColor = self.subtitleCell.detailTextLabel?.textColor 52 | self.subtitle.tintColor = self.subtitleCell.detailTextLabel?.tintColor 53 | leftStack.addArrangedSubview(self.subtitle) 54 | 55 | self.date.font = self.value1Cell.detailTextLabel?.font 56 | self.date.textColor = self.value1Cell.detailTextLabel?.textColor 57 | self.date.tintColor = self.value1Cell.detailTextLabel?.tintColor 58 | self.date.textAlignment = .right 59 | stack.addArrangedSubview(self.date) 60 | } 61 | 62 | let date = UILabel() 63 | let title = UILabel() 64 | let subtitle = UILabel() 65 | 66 | required init?(coder aDecoder: NSCoder) { 67 | fatalError("init(coder:) has not been implemented") 68 | } 69 | } 70 | 71 | class DocTableViewController: UITableViewController { 72 | 73 | override func viewDidLoad() { 74 | super.viewDidLoad() 75 | // Do any additional setup after loading the view, typically from a nib. 76 | self.title = "Documents" 77 | 78 | if #available(iOS 13.0, *) { 79 | self.view.backgroundColor = UIColor.systemBackground 80 | } else { 81 | self.view.backgroundColor = .white 82 | } 83 | } 84 | 85 | override func numberOfSections(in tableView: UITableView) -> Int { 86 | return 1 87 | } 88 | 89 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 90 | // TODO: do this automagically 91 | //https://stackoverflow.com/questions/36378001/is-it-possible-to-count-pictures-in-asset-catalog-with-particular-prefix 92 | return 1 93 | } 94 | 95 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 96 | let cell = DocTableViewCell() 97 | 98 | if indexPath.row == 0 { 99 | cell.title.text = "FM 21-20" 100 | cell.subtitle.text = "Chapter 14" 101 | cell.date.text = "October 1998" 102 | } 103 | return cell 104 | } 105 | 106 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 107 | let name = "ch14" 108 | self.navigationController?.pushViewController(DocViewController(name: name), animated: true) 109 | } 110 | } 111 | 112 | class DocViewController: UIViewController { 113 | 114 | var name: String? 115 | 116 | init(name: String) { 117 | self.name = name 118 | super.init(nibName: nil, bundle: nil) 119 | self.title = "ARMY PHYSICAL FITNESS TEST" 120 | } 121 | 122 | required init?(coder aDecoder: NSCoder) { 123 | fatalError("init(coder:) has not been implemented") 124 | } 125 | 126 | override func viewDidLoad() { 127 | super.viewDidLoad() 128 | // Do any additional setup after loading the view, typically from a nib. 129 | if let data = NSDataAsset(name: (self.name)!) 130 | { 131 | let doc = PDFDocument(data: data.data) 132 | let v = PDFView(frame: self.view.frame) 133 | self.view.addSubview(v) 134 | v.document = doc 135 | 136 | self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(share)) 137 | } else { 138 | // show some error 139 | let warn = UILabel(frame: self.view.frame) 140 | warn.textAlignment = .center 141 | warn.text = "File not found!" 142 | warn.translatesAutoresizingMaskIntoConstraints = false 143 | self.view.addSubview(warn) 144 | self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": warn])) 145 | self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": warn])) 146 | } 147 | 148 | } 149 | 150 | @objc func share() { 151 | if let data = NSDataAsset(name: (self.name)!) 152 | { 153 | let shareSheet = UIActivityViewController(activityItems: [data.data], applicationActivities: []) 154 | self.present(shareSheet, animated: true, completion: nil) 155 | } 156 | 157 | } 158 | 159 | 160 | } 161 | -------------------------------------------------------------------------------- /reverseapft/CalcViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // acft 4 | // 5 | // Created by Zachary Gorak on 10/15/18. 6 | // Copyright © 2018 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CalcViewController: UIViewController, UIScrollViewDelegate { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | 17 | if #available(iOS 13.0, *) { 18 | self.view.backgroundColor = UIColor.systemBackground 19 | } else { 20 | self.view.backgroundColor = .white 21 | } 22 | 23 | //TODO: add gradient to top 24 | scrollview.translatesAutoresizingMaskIntoConstraints = false 25 | scrollview.delegate = self 26 | self.view.addSubview(scrollview) 27 | self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": scrollview])) 28 | self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": scrollview])) 29 | 30 | stack.axis = NSLayoutConstraint.Axis.vertical 31 | //stack.alignment = UIStackViewAlignment.Fill 32 | //stack.distribution = UIStackViewDistribution.FillProportionally 33 | stack.spacing = 10 34 | stack.translatesAutoresizingMaskIntoConstraints = false 35 | let topConstraint = NSLayoutConstraint( 36 | item: stack, 37 | attribute: NSLayoutConstraint.Attribute.topMargin, 38 | relatedBy: NSLayoutConstraint.Relation.equal, 39 | toItem: scrollview, 40 | attribute: NSLayoutConstraint.Attribute.topMargin, 41 | multiplier: 1.0, 42 | constant: 0) 43 | let leftConstraint = NSLayoutConstraint( 44 | item: stack, 45 | attribute: NSLayoutConstraint.Attribute.leading, 46 | relatedBy: NSLayoutConstraint.Relation.equal, 47 | toItem: scrollview, 48 | attribute: NSLayoutConstraint.Attribute.leading, 49 | multiplier: 1.0, 50 | constant: 0) 51 | let rightConstraint = NSLayoutConstraint( 52 | item: stack, 53 | attribute: NSLayoutConstraint.Attribute.trailing, 54 | relatedBy: NSLayoutConstraint.Relation.equal, 55 | toItem: scrollview, 56 | attribute: NSLayoutConstraint.Attribute.trailing, 57 | multiplier: 1.0, 58 | constant: 0) 59 | let widthCon = NSLayoutConstraint( 60 | item: stack, 61 | attribute: NSLayoutConstraint.Attribute.width, 62 | relatedBy: NSLayoutConstraint.Relation.equal, 63 | toItem: scrollview, 64 | attribute: NSLayoutConstraint.Attribute.width, 65 | multiplier: 1.0, 66 | constant: 0) 67 | scrollview.addSubview(stack) 68 | scrollview.addConstraint(widthCon) 69 | scrollview.addConstraint(topConstraint) 70 | scrollview.addConstraint(leftConstraint) 71 | scrollview.addConstraint(rightConstraint) 72 | 73 | //TODO: get from previous run 74 | segment.selectedSegmentIndex = UserDefaults().integer(forKey: "gender") 75 | segment.addTarget(self, action: #selector(updateValues), for: UIControl.Event.valueChanged) 76 | stack.addArrangedSubview(segment) 77 | 78 | self.addSeperator(stack) 79 | 80 | self.addScoreInputRow(stack, label: ageInput, title: "Age") 81 | ageInput.keyboardType = .numberPad 82 | ageInput.placeholder = "21" 83 | self.addDoneButton(ageInput) 84 | let agepl = UserDefaults().integer(forKey: "age") 85 | if agepl > 0 86 | { 87 | ageInput.text = String(agepl) 88 | } 89 | 90 | self.addSeperator(stack) 91 | 92 | self.addScoreInputRow(stack, label: pushupInput, title: "Pushups (reps)") 93 | pushupInput.keyboardType = .numberPad 94 | pushupInput.placeholder = "42" 95 | self.addDoneButton(pushupInput) 96 | if let releasepupl = UserDefaults().string(forKey: "calc_pu") { 97 | pushupInput.text = releasepupl 98 | } 99 | self.addScoreRow(stack, label: pushupScore, title: "Score") 100 | self.addSeperator(stack) 101 | 102 | self.addScoreInputRow(stack, label: situpInput, title: "Situps (reps)") 103 | situpInput.keyboardType = .numberPad 104 | situpInput.placeholder = "53" 105 | self.addDoneButton(situpInput) 106 | if let releasepupl = UserDefaults().string(forKey: "calc_situps") { 107 | situpInput.text = releasepupl 108 | } 109 | self.addScoreRow(stack, label: situpScore, title: "Score") 110 | self.addSeperator(stack) 111 | 112 | self.addScoreInputRow(stack, label: runInput, title: "2-Mile Run (min:sec)") 113 | runInput.keyboardType = .numbersAndPunctuation 114 | runInput.placeholder = "15:32" 115 | self.addDoneButton(runInput) 116 | if let releasepupl = UserDefaults().string(forKey: "calc_run") { 117 | runInput.text = releasepupl 118 | } 119 | self.addScoreRow(stack, label: runScore, title: "Score") 120 | self.addSeperator(stack) 121 | 122 | self.addScoreRow(stack, label: totalScore, title: "Total Score") 123 | 124 | self.updateValues() 125 | 126 | self.startAvoidingKeyboard() 127 | } 128 | 129 | func addScoreInputRow(_ stackView: UIStackView, label: UITextField, title: String) { 130 | let row = UIStackView() 131 | let _title = UILabel() 132 | _title.text = title 133 | label.textAlignment = .right 134 | row.addArrangedSubview(_title) 135 | row.addArrangedSubview(label) 136 | stackView.addArrangedSubview(row) 137 | } 138 | 139 | func addScoreRow(_ stackView: UIStackView, label: UILabel, title: String){ 140 | let deadliftRow = UIStackView() 141 | let deadliftLabel = UILabel() 142 | deadliftLabel.text = title 143 | deadliftRow.addArrangedSubview(deadliftLabel) 144 | label.text = "-" 145 | label.textAlignment = .right 146 | label.textColor = UIColor.systemGray 147 | deadliftRow.addArrangedSubview(label) 148 | stackView.addArrangedSubview(deadliftRow) 149 | } 150 | 151 | let segment = UISegmentedControl(items: ["MALE", "FEMALE"]) 152 | let scrollview = UIScrollView() 153 | let stack = UIStackView() 154 | let topPicker = UIPickerView() 155 | let bottomPicker = UIPickerView() 156 | 157 | let ageInput = UITextField() 158 | let pushupScore = UILabel() 159 | let situpScore = UILabel() 160 | let situpInput = UITextField() 161 | 162 | let pushupInput = UITextField() 163 | 164 | let runScore = UILabel() 165 | let runInput = UITextField() 166 | let totalScore = UILabel() 167 | 168 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 169 | print("scrolling") 170 | self.dismissKeyboard() 171 | } 172 | 173 | @objc func updateValues() { 174 | Score.gender = Gender(rawValue: segment.selectedSegmentIndex) ?? .male 175 | if let a_c = Int(ageInput.text ?? "") 176 | { 177 | Score.age = a_c 178 | } else { 179 | ageInput.text = "" 180 | Score.age = 21 181 | } 182 | UserDefaults().set(segment.selectedSegmentIndex, forKey: "gender") 183 | UserDefaults().synchronize() 184 | 185 | // reset label colors 186 | 187 | pushupScore.textColor = UIColor.systemGray 188 | situpScore.textColor = UIColor.systemGray 189 | runScore.textColor = UIColor.systemGray 190 | totalScore.textColor = UIColor.systemGray 191 | 192 | var didFail = false 193 | 194 | var _releasePUScore = Int(pushupInput.text ?? "0") ?? 0 195 | if pushupInput.text?.isEmpty ?? true { 196 | _releasePUScore = Int(pushupInput.placeholder ?? "0") ?? 0 197 | } 198 | let pushupReps = Score.score(forPushups: _releasePUScore) 199 | pushupScore.text = String(pushupReps) 200 | 201 | var _situpScore = Int(situpInput.text ?? "0") ?? 0 202 | if situpInput.text?.isEmpty ?? true { 203 | _situpScore = Int(situpInput.placeholder ?? "0") ?? 0 204 | } 205 | let situpReps = Score.score(forSitups: _situpScore) 206 | situpScore.text = String(situpReps) 207 | 208 | var _runText = runInput.text ?? "0" 209 | if runInput.text?.isEmpty ?? true { 210 | _runText = runInput.placeholder ?? "0" 211 | } 212 | _runText = _runText.replacingOccurrences(of: ":", with: "") 213 | _runText = _runText.replacingOccurrences(of: "min", with: "") 214 | _runText = _runText.replacingOccurrences(of: "sec", with: "") 215 | _runText = _runText.replacingOccurrences(of: " ", with: "") 216 | _runText = _runText.replacingOccurrences(of: "minutes", with: "") 217 | _runText = _runText.replacingOccurrences(of: "seconds", with: "") 218 | let runTime = Score.score(forRun: Int(_runText) ?? 0) 219 | runScore.text = String(runTime) 220 | 221 | totalScore.text = String(pushupReps + situpReps + runTime) 222 | 223 | if (pushupReps < 60) { 224 | pushupScore.textColor = UIColor.red 225 | didFail = true 226 | } 227 | if (situpReps < 60) { 228 | situpScore.textColor = UIColor.red 229 | didFail = true 230 | } 231 | if (runTime < 60) { 232 | runScore.textColor = UIColor.red 233 | didFail = true 234 | } 235 | 236 | if(didFail) { 237 | totalScore.textColor = UIColor.red 238 | } 239 | } 240 | 241 | func addDoneButton(_ label: UITextField) { 242 | let helperBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 44)) 243 | let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done, target: self, action: #selector(dismissKeyboard)) 244 | let flexibleSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: self, action: nil) 245 | 246 | helperBar.setItems([flexibleSpace, doneButton], animated: true) 247 | label.inputAccessoryView = helperBar 248 | label.addTarget(self, action: #selector(updateValues), for: UIControl.Event.editingChanged) 249 | } 250 | 251 | override func viewDidLayoutSubviews() { 252 | super.viewDidLayoutSubviews() 253 | print("Laying out", self.view.frame, self.stack.frame, self.stack.frame.height) 254 | self.scrollview.contentSize = CGSize(width: self.stack.frame.width, height: self.stack.frame.height + (UIFont.systemFontSize * 2)) // NO idea why if I don't have extra space at the bottom things get cut off 255 | } 256 | 257 | func addSeperator(_ stackView: UIStackView) { 258 | let border = UIView() 259 | //border.layer.backgroundColor = UIColor.systemGray.cgColor 260 | border.backgroundColor = UIColor.systemGray 261 | let borderHeight_three = NSLayoutConstraint( 262 | item: border, 263 | attribute: NSLayoutConstraint.Attribute.height, 264 | relatedBy: NSLayoutConstraint.Relation.equal, 265 | toItem: stack, 266 | attribute: NSLayoutConstraint.Attribute.height, 267 | multiplier: 0.0, 268 | constant: 1) 269 | stackView.addArrangedSubview(border) 270 | stackView.addConstraint(borderHeight_three) 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /reverseapft/ReverseCalcViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // reverseapft 4 | // 5 | // Created by twodayslate on 10/14/14. 6 | // Copyright (c) 2014 twodayslate. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ReverseCalcViewController: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UIScrollViewDelegate { 12 | 13 | var situpPicker : UIPickerView! = nil 14 | var pushupPicker : UIPickerView! = nil 15 | var runPicker : UIPickerView! = nil 16 | var valueLabel : UILabel! = nil 17 | var segment : UISegmentedControl! = nil 18 | var runLabel : UILabel! = nil 19 | var pushupLabel : UILabel! = nil 20 | var situpLabel : UILabel! = nil 21 | var inputAge : UITextField! = nil 22 | let prefs : UserDefaults = UserDefaults() 23 | 24 | var scrollview : UIScrollView! = nil 25 | var stack : UIStackView! = nil 26 | 27 | override func viewDidLoad() { 28 | super.viewDidLoad() 29 | 30 | // show/hide status bar 31 | // https://stackoverflow.com/questions/46543470/hide-status-bar-swift-4 32 | // let statusBar: UIView = UIApplication.shared.value(forKey: "statusBar") as! UIView 33 | // statusBar.isHidden = false 34 | 35 | if #available(iOS 13.0, *) { 36 | self.view.backgroundColor = UIColor.systemBackground 37 | } else { 38 | self.view.backgroundColor = .white 39 | } 40 | 41 | scrollview = UIScrollView() 42 | //TODO: add gradient to top 43 | scrollview.translatesAutoresizingMaskIntoConstraints = false 44 | scrollview.delegate = self 45 | self.view.addSubview(scrollview) 46 | self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": scrollview])) 47 | self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": scrollview])) 48 | 49 | stack = UIStackView() 50 | stack.axis = NSLayoutConstraint.Axis.vertical 51 | //stack.alignment = UIStackViewAlignment.Fill 52 | //stack.distribution = UIStackViewDistribution.FillProportionally 53 | stack.spacing = 10 54 | stack.translatesAutoresizingMaskIntoConstraints = false 55 | let topConstraint = NSLayoutConstraint( 56 | item: stack, 57 | attribute: NSLayoutConstraint.Attribute.topMargin, 58 | relatedBy: NSLayoutConstraint.Relation.equal, 59 | toItem: scrollview, 60 | attribute: NSLayoutConstraint.Attribute.topMargin, 61 | multiplier: 1.0, 62 | constant: 0) 63 | let leftConstraint = NSLayoutConstraint( 64 | item: stack, 65 | attribute: NSLayoutConstraint.Attribute.leading, 66 | relatedBy: NSLayoutConstraint.Relation.equal, 67 | toItem: scrollview, 68 | attribute: NSLayoutConstraint.Attribute.leading, 69 | multiplier: 1.0, 70 | constant: 0) 71 | let rightConstraint = NSLayoutConstraint( 72 | item: stack, 73 | attribute: NSLayoutConstraint.Attribute.trailing, 74 | relatedBy: NSLayoutConstraint.Relation.equal, 75 | toItem: scrollview, 76 | attribute: NSLayoutConstraint.Attribute.trailing, 77 | multiplier: 1.0, 78 | constant: 0) 79 | let widthCon = NSLayoutConstraint( 80 | item: stack, 81 | attribute: NSLayoutConstraint.Attribute.width, 82 | relatedBy: NSLayoutConstraint.Relation.equal, 83 | toItem: scrollview, 84 | attribute: NSLayoutConstraint.Attribute.width, 85 | multiplier: 1.0, 86 | constant: 0) 87 | scrollview.addSubview(stack) 88 | scrollview.addConstraint(widthCon) 89 | scrollview.addConstraint(topConstraint) 90 | scrollview.addConstraint(leftConstraint) 91 | scrollview.addConstraint(rightConstraint) 92 | 93 | prefs.register(defaults: [ 94 | "gender":0, 95 | "age":21, 96 | "pushups": 30, 97 | "situps": 30, 98 | "run":30]) 99 | 100 | segment = UISegmentedControl(items: ["MALE", "FEMALE"]); 101 | //TODO: get from previous run 102 | segment.selectedSegmentIndex = prefs.integer(forKey: "gender") 103 | segment.addTarget(self, action: #selector(ReverseCalcViewController.updateChanged), for: UIControl.Event.valueChanged) 104 | stack.addArrangedSubview(segment) 105 | 106 | let border_zero = UIView() 107 | //border_zero.layer.backgroundColor = UIColor.systemGray.cgColor 108 | border_zero.backgroundColor = UIColor.systemGray 109 | let borderHeight_zero = NSLayoutConstraint( 110 | item: border_zero, 111 | attribute: NSLayoutConstraint.Attribute.height, 112 | relatedBy: NSLayoutConstraint.Relation.equal, 113 | toItem: stack, 114 | attribute: NSLayoutConstraint.Attribute.height, 115 | multiplier: 0.0, 116 | constant: 1) 117 | stack.addArrangedSubview(border_zero) 118 | stack.addConstraint(borderHeight_zero) 119 | 120 | let row_one = UIStackView() 121 | let nameLabel_one = UILabel() 122 | nameLabel_one.text = "Age" 123 | row_one.addArrangedSubview(nameLabel_one) 124 | inputAge = UITextField() 125 | inputAge.keyboardType = UIKeyboardType.numberPad 126 | inputAge.text = String(prefs.integer(forKey: "age")) 127 | inputAge.placeholder = "21" 128 | 129 | inputAge.textAlignment = NSTextAlignment.right 130 | inputAge.addTarget(self, action: #selector(ReverseCalcViewController.updateChanged), for: UIControl.Event.editingChanged) 131 | // you have to set the frame size otherwise the buttons will not work 132 | let flexibleSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: self, action: nil) 133 | let helperBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 44)) 134 | let doneButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.done, target: self, action: #selector(self.dismissKeyboard)) 135 | helperBar.setItems([flexibleSpace, doneButton], animated: true) 136 | inputAge.inputAccessoryView = helperBar 137 | row_one.addArrangedSubview(inputAge) 138 | let inputCon = NSLayoutConstraint( 139 | item: inputAge, 140 | attribute: NSLayoutConstraint.Attribute.width, 141 | relatedBy: NSLayoutConstraint.Relation.lessThanOrEqual, 142 | toItem: row_one, 143 | attribute: NSLayoutConstraint.Attribute.width, 144 | multiplier: 1.0, 145 | constant: 0) 146 | row_one.addConstraint(inputCon) 147 | stack.addArrangedSubview(row_one) 148 | 149 | let border = UIView() 150 | //border.layer.backgroundColor = UIColor.systemGray.cgColor 151 | border.backgroundColor = UIColor.systemGray 152 | border.clipsToBounds = true 153 | border.isHidden = false 154 | let borderHeight = NSLayoutConstraint( 155 | item: border, 156 | attribute: NSLayoutConstraint.Attribute.height, 157 | relatedBy: NSLayoutConstraint.Relation.equal, 158 | toItem: stack, 159 | attribute: NSLayoutConstraint.Attribute.height, 160 | multiplier: 0.0, 161 | constant: 1) 162 | stack.addArrangedSubview(border) 163 | stack.addConstraint(borderHeight) 164 | 165 | 166 | let row_two = UIStackView() 167 | let column_one = UIStackView() 168 | column_one.axis = NSLayoutConstraint.Axis.vertical 169 | let nameLabel_two = UILabel() 170 | nameLabel_two.text = "Pushups" 171 | nameLabel_two.textAlignment = NSTextAlignment.center 172 | column_one.addArrangedSubview(nameLabel_two) 173 | pushupPicker = UIPickerView() 174 | pushupPicker.delegate = self 175 | pushupPicker.dataSource = self 176 | //TODO: get from previous run 177 | pushupPicker.selectRow(prefs.integer(forKey: "pushups"), inComponent: 0, animated: true) 178 | column_one.addArrangedSubview(pushupPicker) 179 | row_two.addArrangedSubview(column_one) 180 | let thirdWidthConstraint_one = NSLayoutConstraint( 181 | item: column_one, 182 | attribute: NSLayoutConstraint.Attribute.width, 183 | relatedBy: NSLayoutConstraint.Relation.equal, 184 | toItem: row_two, 185 | attribute: NSLayoutConstraint.Attribute.width, 186 | multiplier: (1.0/3.0), 187 | constant: 0) 188 | row_two.addConstraint(thirdWidthConstraint_one) 189 | 190 | let column_two = UIStackView() 191 | column_two.axis = NSLayoutConstraint.Axis.vertical 192 | let nameLabel_three = UILabel() 193 | nameLabel_three.text = "Situps" 194 | nameLabel_three.textAlignment = NSTextAlignment.center 195 | column_two.addArrangedSubview(nameLabel_three) 196 | situpPicker = UIPickerView() 197 | situpPicker.delegate = self 198 | situpPicker.dataSource = self 199 | //TODO: get from previous run 200 | situpPicker.selectRow(prefs.integer(forKey: "situps"), inComponent: 0, animated: true) 201 | column_two.addArrangedSubview(situpPicker) 202 | row_two.addArrangedSubview(column_two) 203 | let thirdWidthConstraint_two = NSLayoutConstraint( 204 | item: column_two, 205 | attribute: NSLayoutConstraint.Attribute.width, 206 | relatedBy: NSLayoutConstraint.Relation.equal, 207 | toItem: row_two, 208 | attribute: NSLayoutConstraint.Attribute.width, 209 | multiplier: (1.0/3.0), 210 | constant: 0) 211 | row_two.addConstraint(thirdWidthConstraint_two) 212 | 213 | let column_three = UIStackView() 214 | column_three.axis = NSLayoutConstraint.Axis.vertical 215 | let nameLabel_four = UILabel() 216 | nameLabel_four.text = "Run" 217 | nameLabel_four.textAlignment = NSTextAlignment.center 218 | column_three.addArrangedSubview(nameLabel_four) 219 | runPicker = UIPickerView() 220 | runPicker.delegate = self 221 | runPicker.dataSource = self 222 | //TODO: get from previous run 223 | runPicker.selectRow(prefs.integer(forKey: "run"), inComponent: 0, animated: true) 224 | column_three.addArrangedSubview(runPicker) 225 | row_two.addArrangedSubview(column_three) 226 | let thirdWidthConstraint_three = NSLayoutConstraint( 227 | item: column_three, 228 | attribute: NSLayoutConstraint.Attribute.width, 229 | relatedBy: NSLayoutConstraint.Relation.equal, 230 | toItem: row_two, 231 | attribute: NSLayoutConstraint.Attribute.width, 232 | multiplier: (1.0/3.0), 233 | constant: 0) 234 | row_two.addConstraint(thirdWidthConstraint_three) 235 | 236 | stack.addArrangedSubview(row_two) //pickers 237 | 238 | let border_two = UIView() 239 | //border_two.layer.backgroundColor = UIColor.systemGray.cgColor 240 | border_two.backgroundColor = UIColor.systemGray 241 | let borderHeight_two = NSLayoutConstraint( 242 | item: border_two, 243 | attribute: NSLayoutConstraint.Attribute.height, 244 | relatedBy: NSLayoutConstraint.Relation.equal, 245 | toItem: stack, 246 | attribute: NSLayoutConstraint.Attribute.height, 247 | multiplier: 0.0, 248 | constant: 1) 249 | stack.addArrangedSubview(border_two) 250 | stack.addConstraint(borderHeight_two) 251 | 252 | let row_three = UIStackView() 253 | let nameLabel_five = UILabel() 254 | nameLabel_five.text = "Total Score" 255 | row_three.addArrangedSubview(nameLabel_five) 256 | valueLabel = UILabel() 257 | valueLabel.text = "-" 258 | valueLabel.textAlignment = NSTextAlignment.right 259 | valueLabel.textColor = UIColor.systemGray 260 | row_three.addArrangedSubview(valueLabel) 261 | stack.addArrangedSubview(row_three) 262 | 263 | let border_three = UIView() 264 | border_three.layer.backgroundColor = UIColor.systemGray.cgColor 265 | border_three.backgroundColor = UIColor.systemGray 266 | let borderHeight_three = NSLayoutConstraint( 267 | item: border_three, 268 | attribute: NSLayoutConstraint.Attribute.height, 269 | relatedBy: NSLayoutConstraint.Relation.equal, 270 | toItem: stack, 271 | attribute: NSLayoutConstraint.Attribute.height, 272 | multiplier: 0.0, 273 | constant: 1) 274 | stack.addArrangedSubview(border_three) 275 | stack.addConstraint(borderHeight_three) 276 | 277 | let row_four = UIStackView() 278 | let nameLabel_six = UILabel() 279 | nameLabel_six.text = "Pushups" 280 | row_four.addArrangedSubview(nameLabel_six) 281 | pushupLabel = UILabel() 282 | pushupLabel.text = "-" 283 | pushupLabel.textAlignment = NSTextAlignment.right 284 | pushupLabel.textColor = UIColor.systemGray 285 | row_four.addArrangedSubview(pushupLabel) 286 | stack.addArrangedSubview(row_four) 287 | 288 | let border_four = UIView() 289 | border_four.layer.backgroundColor = UIColor.systemGray.cgColor 290 | border_four.backgroundColor = UIColor.systemGray 291 | let borderHeight_four = NSLayoutConstraint( 292 | item: border_four, 293 | attribute: NSLayoutConstraint.Attribute.height, 294 | relatedBy: NSLayoutConstraint.Relation.equal, 295 | toItem: stack, 296 | attribute: NSLayoutConstraint.Attribute.height, 297 | multiplier: 0.0, 298 | constant: 1) 299 | stack.addArrangedSubview(border_four) 300 | stack.addConstraint(borderHeight_four) 301 | 302 | let row_five = UIStackView() 303 | let nameLabel_seven = UILabel() 304 | nameLabel_seven.text = "Situps" 305 | row_five.addArrangedSubview(nameLabel_seven) 306 | situpLabel = UILabel() 307 | situpLabel.text = "-" 308 | situpLabel.textAlignment = NSTextAlignment.right 309 | situpLabel.textColor = UIColor.systemGray 310 | row_five.addArrangedSubview(situpLabel) 311 | stack.addArrangedSubview(row_five) 312 | 313 | let border_five = UIView() 314 | border_five.layer.backgroundColor = UIColor.systemGray.cgColor 315 | border_five.backgroundColor = UIColor.systemGray 316 | let borderHeight_five = NSLayoutConstraint( 317 | item: border_five, 318 | attribute: NSLayoutConstraint.Attribute.height, 319 | relatedBy: NSLayoutConstraint.Relation.equal, 320 | toItem: stack, 321 | attribute: NSLayoutConstraint.Attribute.height, 322 | multiplier: 0.0, 323 | constant: 1) 324 | stack.addArrangedSubview(border_five) 325 | stack.addConstraint(borderHeight_five) 326 | 327 | let row_six = UIStackView() 328 | let nameLabel_eight = UILabel() 329 | nameLabel_eight.text = "Run" 330 | row_six.addArrangedSubview(nameLabel_eight) 331 | runLabel = UILabel() 332 | runLabel.text = "-" 333 | runLabel.textAlignment = NSTextAlignment.right 334 | runLabel.textColor = UIColor.systemGray 335 | row_six.addArrangedSubview(runLabel) 336 | stack.addArrangedSubview(row_six) 337 | 338 | self.updateChanged() 339 | } 340 | 341 | override func viewDidLayoutSubviews() { 342 | super.viewDidLayoutSubviews() 343 | printd("self.view.frame", self.view.frame) 344 | self.scrollview.contentSize = CGSize(width: stack.frame.width, height: stack.frame.height + UIApplication.shared.statusBarFrame.size.height+40) 345 | } 346 | 347 | func scrollViewDidScroll(_ scrollView: UIScrollView) { 348 | self.dismissKeyboard() 349 | } 350 | 351 | @objc override func dismissKeyboard() { 352 | super.dismissKeyboard() 353 | updateChanged() 354 | } 355 | 356 | override func didReceiveMemoryWarning() { 357 | super.didReceiveMemoryWarning() 358 | // Dispose of any resources that can be recreated. 359 | } 360 | 361 | @objc func updateChanged() { 362 | self.pickerView(pushupPicker, didSelectRow: pushupPicker.selectedRow(inComponent: 0), inComponent: 0) 363 | self.pickerView(situpPicker, didSelectRow: situpPicker.selectedRow(inComponent: 0), inComponent: 0) 364 | self.pickerView(runPicker, didSelectRow: runPicker.selectedRow(inComponent: 0), inComponent: 0) 365 | } 366 | 367 | func numberOfComponents(in pickerView: UIPickerView) -> Int { 368 | return 1 369 | } 370 | 371 | func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { 372 | return 71 373 | } 374 | 375 | func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { 376 | return String(row + 30) 377 | } 378 | 379 | func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { 380 | let newValue : Int = row + 30; 381 | let score = row + 30 382 | printd("row = \(row); score = \(newValue)") 383 | let sum = runPicker.selectedRow(inComponent: 0) + pushupPicker.selectedRow(inComponent: 0) + situpPicker.selectedRow(inComponent: 0) + 90 384 | printd("New sum = \(sum)") 385 | valueLabel.text = String(sum) 386 | 387 | switch pickerView { 388 | case pushupPicker: 389 | prefs.set(row, forKey: "pushups") 390 | break 391 | case situpPicker: 392 | prefs.set(row, forKey: "situps") 393 | break 394 | case runPicker: 395 | prefs.set(row, forKey: "run") 396 | break 397 | default: 398 | break 399 | } 400 | 401 | prefs.set(segment.selectedSegmentIndex, forKey: "gender") 402 | Score.gender = Gender(rawValue: segment.selectedSegmentIndex)! 403 | printd("gender = \(Score.gender)") 404 | 405 | let age : Int = Int(inputAge.text ?? "21") ?? 21 406 | if age == 21 { 407 | inputAge.text = "" //placeholder is 21 408 | } 409 | prefs.set(age, forKey: "age") 410 | Score.age = age 411 | printd("age = \(Score.age)") 412 | 413 | switch pickerView { 414 | case runPicker: 415 | printd("Changing run value") 416 | runLabel.text = String(Score.runTime(forScore: score)) 417 | let index = runLabel.text?.index((runLabel.text?.startIndex)!, offsetBy: 2) 418 | runLabel.text?.insert(":", at: index!) 419 | break 420 | case situpPicker: 421 | printd("Changing situp value") 422 | situpLabel.text = String(Score.situps(forScore: score)) 423 | break 424 | default: 425 | printd("Changing pushup value") 426 | pushupLabel.text = String(Score.pushups(forScore: score)) 427 | break 428 | } 429 | } 430 | } 431 | 432 | -------------------------------------------------------------------------------- /reverseapft/Storyboard.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 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 78 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 120 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 141 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 162 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /reverseapft/Settings.bundle/Licenses.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | StringsTable 6 | Root 7 | PreferenceSpecifiers 8 | 9 | 10 | Type 11 | PSGroupSpecifier 12 | Title 13 | APFT 14 | FooterText 15 | This application and its affiliates are not responsible for personal performance on the APFT and/or related activities. Always read the latest official documentation and ask your chain of command if you have any questions or concerns. Use this application at our own risk. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 18 | 19 | 20 | Type 21 | PSGroupSpecifier 22 | Title 23 | Icons8 24 | FooterText 25 | https://icons8.com/license/ 26 | 27 | Icons by Icons8 and used freely. 28 | 29 | 30 | Type 31 | PSGroupSpecifier 32 | Title 33 | Attribution-NonCommercial-ShareAlike 4.0 International 34 | FooterText 35 | Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible. 36 | 37 | ### Using Creative Commons Public Licenses 38 | 39 | Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. 40 | 41 | * __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors). 42 | 43 | * __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees). 44 | 45 | ## Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License 46 | 47 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 48 | 49 | ### Section 1 – Definitions. 50 | 51 | a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 52 | 53 | b. __Adapter's License__ means the license You apply to Your Copyright and Similar Rights in Your contributions to Adapted Material in accordance with the terms and conditions of this Public License. 54 | 55 | c. __BY-NC-SA Compatible License__ means a license listed at [creativecommons.org/compatiblelicenses](http://creativecommons.org/compatiblelicenses), approved by Creative Commons as essentially the equivalent of this Public License. 56 | 57 | d. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 58 | 59 | e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 60 | 61 | f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 62 | 63 | g. __License Elements__ means the license attributes listed in the name of a Creative Commons Public License. The License Elements of this Public License are Attribution, NonCommercial, and ShareAlike. 64 | 65 | h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 66 | 67 | i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 68 | 69 | h. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License. 70 | 71 | i. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. 72 | 73 | j. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 74 | 75 | k. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 76 | 77 | l. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 78 | 79 | ### Section 2 – Scope. 80 | 81 | a. ___License grant.___ 82 | 83 | 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 84 | 85 | A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and 86 | 87 | B. produce, reproduce, and Share Adapted Material for NonCommercial purposes only. 88 | 89 | 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 90 | 91 | 3. __Term.__ The term of this Public License is specified in Section 6(a). 92 | 93 | 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 94 | 95 | 5. __Downstream recipients.__ 96 | 97 | A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 98 | 99 | B. __Additional offer from the Licensor – Adapted Material.__ Every recipient of Adapted Material from You automatically receives an offer from the Licensor to exercise the Licensed Rights in the Adapted Material under the conditions of the Adapter’s License You apply. 100 | 101 | C. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 102 | 103 | 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 104 | 105 | b. ___Other rights.___ 106 | 107 | 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 108 | 109 | 2. Patent and trademark rights are not licensed under this Public License. 110 | 111 | 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. 112 | 113 | ### Section 3 – License Conditions. 114 | 115 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 116 | 117 | a. ___Attribution.___ 118 | 119 | 1. If You Share the Licensed Material (including in modified form), You must: 120 | 121 | A. retain the following if it is supplied by the Licensor with the Licensed Material: 122 | 123 | i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 124 | 125 | ii. a copyright notice; 126 | 127 | iii. a notice that refers to this Public License; 128 | 129 | iv. a notice that refers to the disclaimer of warranties; 130 | 131 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 132 | 133 | B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 134 | 135 | C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 136 | 137 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 138 | 139 | 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 140 | 141 | b. ___ShareAlike.___ 142 | 143 | In addition to the conditions in Section 3(a), if You Share Adapted Material You produce, the following conditions also apply. 144 | 145 | 1. The Adapter’s License You apply must be a Creative Commons license with the same License Elements, this version or later, or a BY-NC-SA Compatible License. 146 | 147 | 2. You must include the text of, or the URI or hyperlink to, the Adapter's License You apply. You may satisfy this condition in any reasonable manner based on the medium, means, and context in which You Share Adapted Material. 148 | 149 | 3. You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, Adapted Material that restrict exercise of the rights granted under the Adapter's License You apply. 150 | 151 | ### Section 4 – Sui Generis Database Rights. 152 | 153 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 154 | 155 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only; 156 | 157 | b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material, including for purposes of Section 3(b); and 158 | 159 | c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 160 | 161 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 162 | 163 | ### Section 5 – Disclaimer of Warranties and Limitation of Liability. 164 | 165 | a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__ 166 | 167 | b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__ 168 | 169 | c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 170 | 171 | ### Section 6 – Term and Termination. 172 | 173 | a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 174 | 175 | b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 176 | 177 | 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 178 | 179 | 2. upon express reinstatement by the Licensor. 180 | 181 | For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 182 | 183 | c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 184 | 185 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 186 | 187 | ### Section 7 – Other Terms and Conditions. 188 | 189 | a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 190 | 191 | b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 192 | 193 | ### Section 8 – Interpretation. 194 | 195 | a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 196 | 197 | b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 198 | 199 | c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 200 | 201 | d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. 202 | 203 | > Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. 204 | > 205 | > Creative Commons may be contacted at creativecommons.org 206 | 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /reverseapft.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 118BF02819EDF13E00B66C26 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118BF02719EDF13E00B66C26 /* AppDelegate.swift */; }; 11 | 118BF02A19EDF13E00B66C26 /* ReverseCalcViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118BF02919EDF13E00B66C26 /* ReverseCalcViewController.swift */; }; 12 | 118BF02F19EDF13E00B66C26 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 118BF02E19EDF13E00B66C26 /* Images.xcassets */; }; 13 | 118BF03219EDF13E00B66C26 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 118BF03019EDF13E00B66C26 /* LaunchScreen.xib */; }; 14 | 118BF03E19EDF13E00B66C26 /* runTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118BF03D19EDF13E00B66C26 /* runTests.swift */; }; 15 | F60A6AFD21924520009F073E /* SRCTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F60A6AFC21924520009F073E /* SRCTabBarController.swift */; }; 16 | F60A6AFF21927F6E009F073E /* DocViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F60A6AFE21927F6E009F073E /* DocViewController.swift */; }; 17 | F63D799921977BE600CCBAD3 /* CalcViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F63D799821977BE600CCBAD3 /* CalcViewController.swift */; }; 18 | F63D799B21977CB000CCBAD3 /* UIViewController+extras.swift in Sources */ = {isa = PBXBuildFile; fileRef = F63D799A21977CB000CCBAD3 /* UIViewController+extras.swift */; }; 19 | F655AE0921974312000A459A /* InstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F655AE0821974312000A459A /* InstructionsViewController.swift */; }; 20 | F655AE0B219743FD000A459A /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = F655AE0A219743FD000A459A /* Settings.bundle */; }; 21 | F6632C54204A09FC00B73607 /* PushupTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6632C52204A09FC00B73607 /* PushupTest.swift */; }; 22 | F6632C55204A09FC00B73607 /* SitupTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6632C53204A09FC00B73607 /* SitupTest.swift */; }; 23 | F6957DFD203B6189003FFE3E /* Scores.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6957DFC203B6189003FFE3E /* Scores.swift */; }; 24 | F6957DFF203D047E003FFE3E /* Globals.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6957DFE203D047E003FFE3E /* Globals.swift */; }; 25 | F6957E01203E6961003FFE3E /* Scores.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6957DFC203B6189003FFE3E /* Scores.swift */; }; 26 | F6957E02203E69CB003FFE3E /* Globals.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6957DFE203D047E003FFE3E /* Globals.swift */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXContainerItemProxy section */ 30 | 118BF03819EDF13E00B66C26 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = 118BF01A19EDF13E00B66C26 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = 118BF02119EDF13E00B66C26; 35 | remoteInfo = reverseapft; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 118BF02219EDF13E00B66C26 /* reverseapft.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = reverseapft.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 118BF02619EDF13E00B66C26 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 118BF02719EDF13E00B66C26 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 43 | 118BF02919EDF13E00B66C26 /* ReverseCalcViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReverseCalcViewController.swift; sourceTree = ""; }; 44 | 118BF02E19EDF13E00B66C26 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 45 | 118BF03119EDF13E00B66C26 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 46 | 118BF03719EDF13E00B66C26 /* reverseapftTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = reverseapftTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 118BF03C19EDF13E00B66C26 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | 118BF03D19EDF13E00B66C26 /* runTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = runTests.swift; sourceTree = ""; }; 49 | F60A6AFC21924520009F073E /* SRCTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCTabBarController.swift; sourceTree = ""; }; 50 | F60A6AFE21927F6E009F073E /* DocViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocViewController.swift; sourceTree = ""; }; 51 | F63D799821977BE600CCBAD3 /* CalcViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CalcViewController.swift; sourceTree = ""; }; 52 | F63D799A21977CB000CCBAD3 /* UIViewController+extras.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+extras.swift"; sourceTree = ""; }; 53 | F655AE0821974312000A459A /* InstructionsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstructionsViewController.swift; sourceTree = ""; }; 54 | F655AE0A219743FD000A459A /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; 55 | F6632C52204A09FC00B73607 /* PushupTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushupTest.swift; sourceTree = ""; }; 56 | F6632C53204A09FC00B73607 /* SitupTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SitupTest.swift; sourceTree = ""; }; 57 | F6957DFC203B6189003FFE3E /* Scores.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scores.swift; sourceTree = ""; }; 58 | F6957DFE203D047E003FFE3E /* Globals.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Globals.swift; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | 118BF01F19EDF13E00B66C26 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | 118BF03419EDF13E00B66C26 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | ); 74 | runOnlyForDeploymentPostprocessing = 0; 75 | }; 76 | /* End PBXFrameworksBuildPhase section */ 77 | 78 | /* Begin PBXGroup section */ 79 | 118BF01919EDF13E00B66C26 = { 80 | isa = PBXGroup; 81 | children = ( 82 | 118BF02419EDF13E00B66C26 /* reverseapft */, 83 | 118BF03A19EDF13E00B66C26 /* reverseapftTests */, 84 | 118BF02319EDF13E00B66C26 /* Products */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | 118BF02319EDF13E00B66C26 /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 118BF02219EDF13E00B66C26 /* reverseapft.app */, 92 | 118BF03719EDF13E00B66C26 /* reverseapftTests.xctest */, 93 | ); 94 | name = Products; 95 | sourceTree = ""; 96 | }; 97 | 118BF02419EDF13E00B66C26 /* reverseapft */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 118BF02719EDF13E00B66C26 /* AppDelegate.swift */, 101 | F60A6AFE21927F6E009F073E /* DocViewController.swift */, 102 | F60A6AFC21924520009F073E /* SRCTabBarController.swift */, 103 | F6957DFE203D047E003FFE3E /* Globals.swift */, 104 | 118BF02919EDF13E00B66C26 /* ReverseCalcViewController.swift */, 105 | F63D799A21977CB000CCBAD3 /* UIViewController+extras.swift */, 106 | F63D799821977BE600CCBAD3 /* CalcViewController.swift */, 107 | F6957DFC203B6189003FFE3E /* Scores.swift */, 108 | F655AE0821974312000A459A /* InstructionsViewController.swift */, 109 | 118BF02E19EDF13E00B66C26 /* Images.xcassets */, 110 | F655AE0A219743FD000A459A /* Settings.bundle */, 111 | 118BF03019EDF13E00B66C26 /* LaunchScreen.xib */, 112 | 118BF02519EDF13E00B66C26 /* Supporting Files */, 113 | ); 114 | path = reverseapft; 115 | sourceTree = ""; 116 | }; 117 | 118BF02519EDF13E00B66C26 /* Supporting Files */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 118BF02619EDF13E00B66C26 /* Info.plist */, 121 | ); 122 | name = "Supporting Files"; 123 | sourceTree = ""; 124 | }; 125 | 118BF03A19EDF13E00B66C26 /* reverseapftTests */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 118BF03D19EDF13E00B66C26 /* runTests.swift */, 129 | F6632C52204A09FC00B73607 /* PushupTest.swift */, 130 | F6632C53204A09FC00B73607 /* SitupTest.swift */, 131 | 118BF03B19EDF13E00B66C26 /* Supporting Files */, 132 | ); 133 | path = reverseapftTests; 134 | sourceTree = ""; 135 | }; 136 | 118BF03B19EDF13E00B66C26 /* Supporting Files */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 118BF03C19EDF13E00B66C26 /* Info.plist */, 140 | ); 141 | name = "Supporting Files"; 142 | sourceTree = ""; 143 | }; 144 | /* End PBXGroup section */ 145 | 146 | /* Begin PBXNativeTarget section */ 147 | 118BF02119EDF13E00B66C26 /* reverseapft */ = { 148 | isa = PBXNativeTarget; 149 | buildConfigurationList = 118BF04119EDF13E00B66C26 /* Build configuration list for PBXNativeTarget "reverseapft" */; 150 | buildPhases = ( 151 | 118BF01E19EDF13E00B66C26 /* Sources */, 152 | 118BF01F19EDF13E00B66C26 /* Frameworks */, 153 | 118BF02019EDF13E00B66C26 /* Resources */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | ); 159 | name = reverseapft; 160 | productName = reverseapft; 161 | productReference = 118BF02219EDF13E00B66C26 /* reverseapft.app */; 162 | productType = "com.apple.product-type.application"; 163 | }; 164 | 118BF03619EDF13E00B66C26 /* reverseapftTests */ = { 165 | isa = PBXNativeTarget; 166 | buildConfigurationList = 118BF04419EDF13E00B66C26 /* Build configuration list for PBXNativeTarget "reverseapftTests" */; 167 | buildPhases = ( 168 | 118BF03319EDF13E00B66C26 /* Sources */, 169 | 118BF03419EDF13E00B66C26 /* Frameworks */, 170 | 118BF03519EDF13E00B66C26 /* Resources */, 171 | ); 172 | buildRules = ( 173 | ); 174 | dependencies = ( 175 | 118BF03919EDF13E00B66C26 /* PBXTargetDependency */, 176 | ); 177 | name = reverseapftTests; 178 | productName = reverseapftTests; 179 | productReference = 118BF03719EDF13E00B66C26 /* reverseapftTests.xctest */; 180 | productType = "com.apple.product-type.bundle.unit-test"; 181 | }; 182 | /* End PBXNativeTarget section */ 183 | 184 | /* Begin PBXProject section */ 185 | 118BF01A19EDF13E00B66C26 /* Project object */ = { 186 | isa = PBXProject; 187 | attributes = { 188 | LastSwiftUpdateCheck = 0730; 189 | LastUpgradeCheck = 1010; 190 | ORGANIZATIONNAME = twodayslate; 191 | TargetAttributes = { 192 | 118BF02119EDF13E00B66C26 = { 193 | CreatedOnToolsVersion = 6.0.1; 194 | DevelopmentTeam = C6L3992RFB; 195 | LastSwiftMigration = 1010; 196 | ProvisioningStyle = Automatic; 197 | }; 198 | 118BF03619EDF13E00B66C26 = { 199 | CreatedOnToolsVersion = 6.0.1; 200 | LastSwiftMigration = 1010; 201 | TestTargetID = 118BF02119EDF13E00B66C26; 202 | }; 203 | }; 204 | }; 205 | buildConfigurationList = 118BF01D19EDF13E00B66C26 /* Build configuration list for PBXProject "reverseapft" */; 206 | compatibilityVersion = "Xcode 3.2"; 207 | developmentRegion = English; 208 | hasScannedForEncodings = 0; 209 | knownRegions = ( 210 | English, 211 | en, 212 | Base, 213 | ); 214 | mainGroup = 118BF01919EDF13E00B66C26; 215 | productRefGroup = 118BF02319EDF13E00B66C26 /* Products */; 216 | projectDirPath = ""; 217 | projectRoot = ""; 218 | targets = ( 219 | 118BF02119EDF13E00B66C26 /* reverseapft */, 220 | 118BF03619EDF13E00B66C26 /* reverseapftTests */, 221 | ); 222 | }; 223 | /* End PBXProject section */ 224 | 225 | /* Begin PBXResourcesBuildPhase section */ 226 | 118BF02019EDF13E00B66C26 /* Resources */ = { 227 | isa = PBXResourcesBuildPhase; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | 118BF03219EDF13E00B66C26 /* LaunchScreen.xib in Resources */, 231 | F655AE0B219743FD000A459A /* Settings.bundle in Resources */, 232 | 118BF02F19EDF13E00B66C26 /* Images.xcassets in Resources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | 118BF03519EDF13E00B66C26 /* Resources */ = { 237 | isa = PBXResourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | }; 243 | /* End PBXResourcesBuildPhase section */ 244 | 245 | /* Begin PBXSourcesBuildPhase section */ 246 | 118BF01E19EDF13E00B66C26 /* Sources */ = { 247 | isa = PBXSourcesBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | F60A6AFD21924520009F073E /* SRCTabBarController.swift in Sources */, 251 | F6957DFF203D047E003FFE3E /* Globals.swift in Sources */, 252 | 118BF02A19EDF13E00B66C26 /* ReverseCalcViewController.swift in Sources */, 253 | F60A6AFF21927F6E009F073E /* DocViewController.swift in Sources */, 254 | F63D799B21977CB000CCBAD3 /* UIViewController+extras.swift in Sources */, 255 | F6957DFD203B6189003FFE3E /* Scores.swift in Sources */, 256 | F63D799921977BE600CCBAD3 /* CalcViewController.swift in Sources */, 257 | F655AE0921974312000A459A /* InstructionsViewController.swift in Sources */, 258 | 118BF02819EDF13E00B66C26 /* AppDelegate.swift in Sources */, 259 | ); 260 | runOnlyForDeploymentPostprocessing = 0; 261 | }; 262 | 118BF03319EDF13E00B66C26 /* Sources */ = { 263 | isa = PBXSourcesBuildPhase; 264 | buildActionMask = 2147483647; 265 | files = ( 266 | F6632C54204A09FC00B73607 /* PushupTest.swift in Sources */, 267 | F6632C55204A09FC00B73607 /* SitupTest.swift in Sources */, 268 | F6957E01203E6961003FFE3E /* Scores.swift in Sources */, 269 | 118BF03E19EDF13E00B66C26 /* runTests.swift in Sources */, 270 | F6957E02203E69CB003FFE3E /* Globals.swift in Sources */, 271 | ); 272 | runOnlyForDeploymentPostprocessing = 0; 273 | }; 274 | /* End PBXSourcesBuildPhase section */ 275 | 276 | /* Begin PBXTargetDependency section */ 277 | 118BF03919EDF13E00B66C26 /* PBXTargetDependency */ = { 278 | isa = PBXTargetDependency; 279 | target = 118BF02119EDF13E00B66C26 /* reverseapft */; 280 | targetProxy = 118BF03819EDF13E00B66C26 /* PBXContainerItemProxy */; 281 | }; 282 | /* End PBXTargetDependency section */ 283 | 284 | /* Begin PBXVariantGroup section */ 285 | 118BF03019EDF13E00B66C26 /* LaunchScreen.xib */ = { 286 | isa = PBXVariantGroup; 287 | children = ( 288 | 118BF03119EDF13E00B66C26 /* Base */, 289 | ); 290 | name = LaunchScreen.xib; 291 | sourceTree = ""; 292 | }; 293 | /* End PBXVariantGroup section */ 294 | 295 | /* Begin XCBuildConfiguration section */ 296 | 118BF03F19EDF13E00B66C26 /* Debug */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ALWAYS_SEARCH_USER_PATHS = NO; 300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 301 | CLANG_CXX_LIBRARY = "libc++"; 302 | CLANG_ENABLE_MODULES = YES; 303 | CLANG_ENABLE_OBJC_ARC = YES; 304 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 305 | CLANG_WARN_BOOL_CONVERSION = YES; 306 | CLANG_WARN_COMMA = YES; 307 | CLANG_WARN_CONSTANT_CONVERSION = YES; 308 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 309 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 310 | CLANG_WARN_EMPTY_BODY = YES; 311 | CLANG_WARN_ENUM_CONVERSION = YES; 312 | CLANG_WARN_INFINITE_RECURSION = YES; 313 | CLANG_WARN_INT_CONVERSION = YES; 314 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 315 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 316 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 317 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 318 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 319 | CLANG_WARN_STRICT_PROTOTYPES = YES; 320 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 321 | CLANG_WARN_UNREACHABLE_CODE = YES; 322 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 323 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 324 | COPY_PHASE_STRIP = NO; 325 | ENABLE_STRICT_OBJC_MSGSEND = YES; 326 | ENABLE_TESTABILITY = YES; 327 | GCC_C_LANGUAGE_STANDARD = gnu99; 328 | GCC_DYNAMIC_NO_PIC = NO; 329 | GCC_NO_COMMON_BLOCKS = YES; 330 | GCC_OPTIMIZATION_LEVEL = 0; 331 | GCC_PREPROCESSOR_DEFINITIONS = ( 332 | "DEBUG=1", 333 | "$(inherited)", 334 | ); 335 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 336 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 337 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 338 | GCC_WARN_UNDECLARED_SELECTOR = YES; 339 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 340 | GCC_WARN_UNUSED_FUNCTION = YES; 341 | GCC_WARN_UNUSED_VARIABLE = YES; 342 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 343 | MTL_ENABLE_DEBUG_INFO = YES; 344 | ONLY_ACTIVE_ARCH = YES; 345 | SDKROOT = iphoneos; 346 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 347 | SWIFT_VERSION = 4.0; 348 | TARGETED_DEVICE_FAMILY = "1,2"; 349 | }; 350 | name = Debug; 351 | }; 352 | 118BF04019EDF13E00B66C26 /* Release */ = { 353 | isa = XCBuildConfiguration; 354 | buildSettings = { 355 | ALWAYS_SEARCH_USER_PATHS = NO; 356 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 357 | CLANG_CXX_LIBRARY = "libc++"; 358 | CLANG_ENABLE_MODULES = YES; 359 | CLANG_ENABLE_OBJC_ARC = YES; 360 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 361 | CLANG_WARN_BOOL_CONVERSION = YES; 362 | CLANG_WARN_COMMA = YES; 363 | CLANG_WARN_CONSTANT_CONVERSION = YES; 364 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 365 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 366 | CLANG_WARN_EMPTY_BODY = YES; 367 | CLANG_WARN_ENUM_CONVERSION = YES; 368 | CLANG_WARN_INFINITE_RECURSION = YES; 369 | CLANG_WARN_INT_CONVERSION = YES; 370 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 371 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 372 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 373 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 374 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 375 | CLANG_WARN_STRICT_PROTOTYPES = YES; 376 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 377 | CLANG_WARN_UNREACHABLE_CODE = YES; 378 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 379 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 380 | COPY_PHASE_STRIP = YES; 381 | ENABLE_NS_ASSERTIONS = NO; 382 | ENABLE_STRICT_OBJC_MSGSEND = YES; 383 | GCC_C_LANGUAGE_STANDARD = gnu99; 384 | GCC_NO_COMMON_BLOCKS = YES; 385 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 386 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 387 | GCC_WARN_UNDECLARED_SELECTOR = YES; 388 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 389 | GCC_WARN_UNUSED_FUNCTION = YES; 390 | GCC_WARN_UNUSED_VARIABLE = YES; 391 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 392 | MTL_ENABLE_DEBUG_INFO = NO; 393 | SDKROOT = iphoneos; 394 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 395 | SWIFT_VERSION = 4.0; 396 | TARGETED_DEVICE_FAMILY = "1,2"; 397 | VALIDATE_PRODUCT = YES; 398 | }; 399 | name = Release; 400 | }; 401 | 118BF04219EDF13E00B66C26 /* Debug */ = { 402 | isa = XCBuildConfiguration; 403 | buildSettings = { 404 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 405 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = ""; 406 | CODE_SIGN_IDENTITY = "iPhone Developer"; 407 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 408 | CODE_SIGN_STYLE = Automatic; 409 | CURRENT_PROJECT_VERSION = 1; 410 | DEVELOPMENT_TEAM = C6L3992RFB; 411 | INFOPLIST_FILE = reverseapft/Info.plist; 412 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 413 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 414 | MARKETING_VERSION = 2.5.1; 415 | PRODUCT_BUNDLE_IDENTIFIER = "com.twodayslate.$(PRODUCT_NAME:rfc1034identifier)"; 416 | PRODUCT_NAME = "$(TARGET_NAME)"; 417 | PROVISIONING_PROFILE = ""; 418 | PROVISIONING_PROFILE_SPECIFIER = ""; 419 | SWIFT_VERSION = 4.2; 420 | }; 421 | name = Debug; 422 | }; 423 | 118BF04319EDF13E00B66C26 /* Release */ = { 424 | isa = XCBuildConfiguration; 425 | buildSettings = { 426 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 427 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = ""; 428 | CODE_SIGN_IDENTITY = "iPhone Distribution"; 429 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 430 | CODE_SIGN_STYLE = Automatic; 431 | CURRENT_PROJECT_VERSION = 1; 432 | DEVELOPMENT_TEAM = C6L3992RFB; 433 | INFOPLIST_FILE = reverseapft/Info.plist; 434 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 435 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 436 | MARKETING_VERSION = 2.5.1; 437 | PRODUCT_BUNDLE_IDENTIFIER = "com.twodayslate.$(PRODUCT_NAME:rfc1034identifier)"; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | PROVISIONING_PROFILE = ""; 440 | PROVISIONING_PROFILE_SPECIFIER = ""; 441 | SWIFT_VERSION = 4.2; 442 | }; 443 | name = Release; 444 | }; 445 | 118BF04519EDF13E00B66C26 /* Debug */ = { 446 | isa = XCBuildConfiguration; 447 | buildSettings = { 448 | BUNDLE_LOADER = "$(TEST_HOST)"; 449 | FRAMEWORK_SEARCH_PATHS = ( 450 | "$(SDKROOT)/Developer/Library/Frameworks", 451 | "$(inherited)", 452 | ); 453 | GCC_PREPROCESSOR_DEFINITIONS = ( 454 | "DEBUG=1", 455 | "$(inherited)", 456 | ); 457 | INFOPLIST_FILE = reverseapftTests/Info.plist; 458 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 459 | PRODUCT_BUNDLE_IDENTIFIER = "com.twodayslate.$(PRODUCT_NAME:rfc1034identifier)"; 460 | PRODUCT_NAME = "$(TARGET_NAME)"; 461 | SWIFT_VERSION = 4.2; 462 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/reverseapft.app/reverseapft"; 463 | }; 464 | name = Debug; 465 | }; 466 | 118BF04619EDF13E00B66C26 /* Release */ = { 467 | isa = XCBuildConfiguration; 468 | buildSettings = { 469 | BUNDLE_LOADER = "$(TEST_HOST)"; 470 | FRAMEWORK_SEARCH_PATHS = ( 471 | "$(SDKROOT)/Developer/Library/Frameworks", 472 | "$(inherited)", 473 | ); 474 | INFOPLIST_FILE = reverseapftTests/Info.plist; 475 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 476 | PRODUCT_BUNDLE_IDENTIFIER = "com.twodayslate.$(PRODUCT_NAME:rfc1034identifier)"; 477 | PRODUCT_NAME = "$(TARGET_NAME)"; 478 | SWIFT_VERSION = 4.2; 479 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/reverseapft.app/reverseapft"; 480 | }; 481 | name = Release; 482 | }; 483 | /* End XCBuildConfiguration section */ 484 | 485 | /* Begin XCConfigurationList section */ 486 | 118BF01D19EDF13E00B66C26 /* Build configuration list for PBXProject "reverseapft" */ = { 487 | isa = XCConfigurationList; 488 | buildConfigurations = ( 489 | 118BF03F19EDF13E00B66C26 /* Debug */, 490 | 118BF04019EDF13E00B66C26 /* Release */, 491 | ); 492 | defaultConfigurationIsVisible = 0; 493 | defaultConfigurationName = Release; 494 | }; 495 | 118BF04119EDF13E00B66C26 /* Build configuration list for PBXNativeTarget "reverseapft" */ = { 496 | isa = XCConfigurationList; 497 | buildConfigurations = ( 498 | 118BF04219EDF13E00B66C26 /* Debug */, 499 | 118BF04319EDF13E00B66C26 /* Release */, 500 | ); 501 | defaultConfigurationIsVisible = 0; 502 | defaultConfigurationName = Release; 503 | }; 504 | 118BF04419EDF13E00B66C26 /* Build configuration list for PBXNativeTarget "reverseapftTests" */ = { 505 | isa = XCConfigurationList; 506 | buildConfigurations = ( 507 | 118BF04519EDF13E00B66C26 /* Debug */, 508 | 118BF04619EDF13E00B66C26 /* Release */, 509 | ); 510 | defaultConfigurationIsVisible = 0; 511 | defaultConfigurationName = Release; 512 | }; 513 | /* End XCConfigurationList section */ 514 | }; 515 | rootObject = 118BF01A19EDF13E00B66C26 /* Project object */; 516 | } 517 | -------------------------------------------------------------------------------- /reverseapft/Scores.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Scores.swift 3 | // reverseapft 4 | // 5 | // Created by Zachary Gorak on 2/19/18. 6 | // Copyright © 2018 twodayslate. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Gender : Int, CustomStringConvertible { 12 | case male 13 | case female 14 | 15 | static func from(value: String) -> Gender { 16 | if value.lowercased() == "female" { 17 | return .female 18 | } 19 | return .male 20 | } 21 | 22 | var description: String { 23 | get { 24 | switch self { 25 | case .female: return "FEMALE" 26 | case .male: return "MALE" 27 | } 28 | } 29 | } 30 | } 31 | 32 | class Score { 33 | public var gender: Gender = .male 34 | public var age: Int = 22 35 | 36 | public init() { 37 | 38 | } 39 | 40 | public convenience init(age: Int, gender: Gender) { 41 | self.init() 42 | self.gender = gender 43 | self.age = age 44 | } 45 | 46 | static var gender: Gender = .male 47 | static var age: Int = 22 48 | 49 | public static func runScore(minutes: Int, seconds: Int) -> Int { 50 | return score(forRun: minutes*100+seconds) 51 | } 52 | 53 | public static func index(forAge age: Int) -> Int { 54 | if age < 22 { 55 | return 0 56 | } 57 | if age < 27 { 58 | return 1 59 | } 60 | if age < 32 { 61 | return 2 62 | } 63 | if age < 37 { 64 | return 3 65 | } 66 | if age < 42 { 67 | return 4 68 | } 69 | if age < 47 { 70 | return 5 71 | } 72 | return 6 73 | } 74 | 75 | static func pushupRequirement(forScore score: Int) -> Int { 76 | let index = score - 30 77 | return pushups[Score.gender.rawValue][Score.index(forAge: Score.age)][index] 78 | } 79 | 80 | static func score3(forPushups pushups: Int) -> Int { 81 | let index = Score.index(forPushups: pushups) 82 | let rar = Score.pushups[Score.gender.rawValue][Score.index(forAge: Score.age)] 83 | if rar.count >= index { 84 | return 100 85 | } 86 | return rar[index] 87 | } 88 | 89 | /// this is the array of the number of pushups 90 | private static let pushupCount = 91 | [05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 92 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 93 | 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 94 | 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 95 | 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 96 | 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97 | 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 98 | 75,76, 77] 99 | 100 | /// returns the number of pushups required for a given score 101 | static func pushups(forScore score : Int) -> Int { 102 | let rar = Score.pushups[Score.gender.rawValue][Score.index(forAge: Score.age)] 103 | if score <= 0 { return 0 } 104 | for (i, val) in rar.enumerated() { 105 | if val >= score { 106 | return Score.pushupCount[i] 107 | } 108 | } 109 | return Score.pushupCount[rar.count] 110 | } 111 | 112 | /// returns the score for the given number of pushups 113 | public static func score(forPushups pushups: Int) -> Int { 114 | let ageRar = Score.pushups[Score.gender.rawValue][Score.index(forAge: Score.age)] 115 | var index = Score.index(forPushups: pushups) 116 | if index > ageRar.count-1 { 117 | index = ageRar.count-1 118 | } 119 | //print("scoreForPushups", pushups, Score.index(forPushups: pushups), ageRar, ageRar.count, index) 120 | return ageRar[index] 121 | 122 | } 123 | 124 | private static func index(forPushups pushups: Int) -> Int { 125 | for (i, val) in Score.pushupCount.enumerated() { 126 | if pushups <= val { 127 | return i 128 | } 129 | } 130 | return pushupCount.count-1 131 | } 132 | 133 | /// this is the array for a score for the number of pushups based on pushupRar 134 | private static let pushups = [ 135 | // male 136 | [ 137 | [ // 17-21 (male) - pushups 138 | // 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 139 | 09, 10, 12, 13, 14, 16, 17, 19, 20, 21, 140 | // 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 141 | 23, 24, 26, 27, 28, 30, 31, 32, 34, 35, 142 | // 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 143 | 37, 38, 39, 41, 42, 43, 45, 46, 48, 49, 144 | // 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 145 | 50, 52, 53, 54, 56, 57, 59, 60, 61, 63, 146 | // 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 147 | 64, 66, 67, 68, 70, 71, 72, 74, 75, 77, 148 | // 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 149 | 78, 79, 81, 82, 83, 85, 86, 88, 89, 90, 150 | // 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 151 | 92, 93, 94, 96, 97, 99, 100 152 | ], 153 | [ // 22-27 (male) - pushups 154 | // 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 155 | 20, 21, 22, 23, 25, 26, 27, 28, 29, 30, 156 | // 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 157 | 31, 33, 34, 35, 36, 37, 38, 39, 41, 42, 158 | // 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 159 | 43, 44, 45, 46, 47, 49, 50, 51, 52, 53, 160 | // 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 161 | 54, 55, 57, 58, 59, 60, 61, 62, 63, 65, 162 | // 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 163 | 66, 67, 68, 69, 70, 71, 73, 74, 75, 76, 164 | // 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 165 | 77, 78, 79, 81, 82, 83, 84, 85, 86, 87, 166 | // 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 167 | 89, 90, 91, 92, 93, 94, 95, 97, 98, 99, 168 | // 75, 76, 77 169 | 100 170 | ], 171 | [ // 27-31 (male) - pushups 172 | // 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 173 | 24, 25, 26, 27, 28, 29, 31, 32, 33, 34, 174 | // 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 175 | 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 176 | // 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 177 | 45, 46, 47, 48, 49, 50, 52, 53, 54, 55, 178 | // 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 179 | 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 180 | // 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 181 | 66, 67, 68, 69, 71, 72, 73, 74, 75, 76, 182 | // 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 183 | 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 184 | // 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 185 | 87, 88, 89, 91, 92, 93, 94, 95, 96, 97, 186 | // 75, 76, 77 187 | 98, 99, 100 188 | ], 189 | [ // 32-36 (male) - pushups 190 | // 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 191 | 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 192 | // 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 193 | 38, 39, 41, 42, 43, 44, 45, 46, 47, 48, 194 | // 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 195 | 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 196 | // 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 197 | 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 198 | // 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 199 | 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 200 | // 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 201 | 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 202 | // 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 203 | 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 204 | // 75, 76, 77 205 | 100 206 | ], 207 | [ // 37- 41 208 | // 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 209 | 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 210 | // 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 211 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 212 | // 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 213 | 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 214 | // 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 215 | 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 216 | // 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 217 | 71, 72, 73, 74, 75, 76, 77, 78, 79, 81, 218 | // 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 219 | 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 220 | // 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 221 | 92, 93, 94, 95, 96, 97, 98, 99, 100 222 | ], 223 | [ // 42-46 224 | // 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 225 | 32, 33, 34, 36, 37, 38, 39, 40, 41, 42, 226 | // 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 227 | 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 228 | // 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 229 | 54, 56, 57, 58, 59, 60, 61, 62, 63, 64, 230 | // 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 231 | 66, 67, 68, 69, 70, 71, 72, 73, 74, 76, 232 | // 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 233 | 77, 78, 79, 80, 81, 82, 83, 84, 86, 87, 234 | // 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 235 | 88, 89, 90, 91, 92, 93, 94, 96, 97, 98, 236 | // 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 237 | 99, 100 238 | ], 239 | [ // 47-51 240 | // 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 241 | 36, 38, 39, 40, 41, 42, 45, 46, 47, 48, 242 | // 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 243 | 49, 50, 51, 52, 53, 54, 55, 56, 58, 59, 244 | // 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 245 | 60, 61, 62, 64, 65, 66, 67, 68, 69, 71, 246 | // 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 247 | 72, 73, 74, 75, 76, 78, 79, 80, 81, 82, 248 | // 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 249 | 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 250 | // 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 251 | 95, 96, 98, 99, 100 252 | ], 253 | [ // 52+ 254 | // 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 255 | 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 256 | // 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 257 | 54, 56, 57, 58, 59, 60, 61, 62, 63, 64, 258 | // 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 259 | 66, 67, 68, 69, 70, 71, 72, 73, 74, 76, 260 | // 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 261 | 77, 78, 79, 80, 81, 82, 83, 84, 86, 87, 262 | // 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 263 | 88, 89, 90, 91, 92, 94, 95, 96, 97, 98, 264 | // 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 265 | 99, 100 266 | ] 267 | ], 268 | // female 269 | [ 270 | [ // >21 271 | // 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 272 | 36, 37, 39, 41, 43, 44, 46, 58, 50, 51, 273 | // 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 274 | 53, 55, 57, 58, 60, 62, 63, 65, 67, 69, 275 | // 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 276 | 70, 72, 74, 76, 77, 79, 81, 83, 84, 86, 277 | // 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 278 | 88, 90, 91, 93, 95, 97, 98, 100 279 | ], 280 | [ // 22-26 281 | // 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 282 | 43, 45, 45, 48, 49, 49, 50, 52, 54, 56, 283 | // 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 284 | 57, 59, 60, 61, 63, 64, 66, 67, 68, 70, 285 | // 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 286 | 71, 72, 74, 75, 77, 78, 79, 81, 82, 83, 287 | // 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 288 | 85, 86, 88, 89, 90, 92, 93, 94, 96, 97, 289 | // 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 290 | 99, 100 291 | ], 292 | [ // 27-31 293 | 45, 47, 48, 49, 49, 50, 52, 54, 55, 56, 294 | 58, 59, 60, 61, 62, 64, 65, 66, 67, 68, 295 | 70, 71, 72, 73, 75, 76, 77, 78, 79, 81, 296 | 82, 83, 84, 85, 87, 88, 89, 90, 92, 93, 297 | 94, 95, 96, 98, 99, 100 298 | ], 299 | [ // 32-36 300 | 47, 48, 49, 49, 50, 52, 54, 58, 58, 59, 301 | 60, 61, 63, 64, 65, 67, 68, 69, 71, 72, 302 | 73, 75, 76, 77, 79, 80, 81, 83, 84, 85, 303 | 87, 88, 89, 91, 92, 93, 95, 96, 97, 99, 304 | 100 305 | ], 306 | [ // 37-41 307 | 48, 50, 51, 53, 54, 56, 57, 59, 60, 61, 308 | 63, 64, 66, 67, 69, 70, 72, 73, 75, 76, 309 | 78, 79, 81, 82, 84, 85, 87, 88, 90, 91, 310 | 93, 94, 96, 97, 99, 100 311 | ], 312 | [ // 42-46 313 | 49, 50, 52, 54, 55, 57, 58, 50, 62, 63, 314 | 65, 66, 68, 70, 71, 73, 64, 76, 78, 79, 315 | 81, 82, 84, 86, 87, 89, 90, 92, 94, 95, 316 | 97, 98, 100 317 | ], 318 | [ // 47-51 319 | 52, 53, 55, 57, 58, 60, 62, 63, 65, 67, 320 | 68, 70, 72, 73, 75, 77, 78, 80, 82, 83, 321 | 85, 87, 88, 90, 92, 93, 95, 97, 98, 100 322 | ], 323 | [ // 52+ 324 | 53, 55, 56, 58, 60, 62, 64, 65, 67, 69, 325 | 71, 73, 75, 76, 78, 80, 82, 84, 85, 87, 326 | 89, 91, 93, 95, 96, 89, 100 327 | ] 328 | ] 329 | ] 330 | 331 | /// returns the number of pushups required for a given score 332 | static func situps(forScore score : Int) -> Int { 333 | let rar = Score.situps[Score.index(forAge: Score.age)] 334 | if score <= 0 { return 0 } 335 | for (i, val) in rar.enumerated() { 336 | if val >= score { 337 | return Score.situpCount[i] 338 | } 339 | } 340 | return Score.situpCount[rar.count] 341 | } 342 | 343 | /// returns the score for the given number of pushups 344 | public static func score(forSitups pushups: Int) -> Int { 345 | let ageRar = Score.situps[Score.index(forAge: Score.age)] 346 | var index = Score.index(forSitups: pushups) 347 | if index > ageRar.count-1 { 348 | index = ageRar.count-1 349 | } 350 | //print("scoreForPushups", pushups, Score.index(forPushups: pushups), ageRar, ageRar.count, index) 351 | return ageRar[index] 352 | } 353 | 354 | private static func index(forSitups pushups: Int) -> Int { 355 | for (i, val) in Score.situpCount.enumerated() { 356 | if pushups <= val { 357 | return i 358 | } 359 | } 360 | return pushupCount.count-1 361 | } 362 | 363 | 364 | private static let situpCount = [ 365 | 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 366 | 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 367 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 368 | 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 369 | 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 370 | 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 371 | 81, 82 372 | ] 373 | 374 | private static let situps = [ 375 | [ // 17-21 (male/female) - situps 376 | 09, 10, 12, 14, 15, 17, 18, 20, 22, 23, 377 | 25, 26, 28, 30, 31, 33, 34, 36, 38, 39, 378 | 41, 42, 44, 45, 47, 49, 50, 52, 54, 55, 379 | 57, 58, 60, 62, 63, 65, 66, 68, 70, 71, 380 | 73, 74, 76, 78, 79, 81, 82, 84, 87, 88, 381 | 89, 90, 92, 94, 95, 97, 98, 100 382 | ], 383 | [ // 22-26 (male/female) - situps 384 | 21, 23, 24, 25, 27, 28, 29, 31, 32, 33, 385 | 35, 36, 37, 39, 40, 41, 43, 44, 45, 47, 386 | 48, 49, 50, 52, 53, 55, 56, 57, 59, 60, 387 | 61, 63, 64, 65, 67, 68, 69, 71, 72, 73, 388 | 75, 76, 77, 79, 80, 81, 83, 84, 85, 87, 389 | 88, 89, 91, 92, 93, 95, 96, 97, 99, 100 390 | ], 391 | [ // 27-31 (male/female) - situps 392 | 34, 35, 36, 37, 38, 39, 41, 42, 43, 44, 393 | 45, 46, 47, 48, 49, 50, 51, 52, 54, 55, 394 | 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 395 | 66, 68, 69, 70, 71, 72, 73, 74, 75, 76, 396 | 77, 78, 79, 81, 82, 83, 84, 85, 86, 87, 397 | 88, 89, 90, 91, 92, 94, 95, 96, 97, 98, 398 | 99, 100 399 | ], 400 | [ 401 | 35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 402 | 47, 48, 49, 50, 52, 53, 54, 55, 56, 58, 403 | 59, 60, 61, 62, 64, 65, 66, 67, 68, 69, 404 | 71, 72, 73, 74, 75, 76, 78, 79, 80, 81, 405 | 82, 84, 85, 86, 87, 88, 89, 91, 92, 93, 406 | 94, 95, 96, 98, 99, 100 407 | ], 408 | [ 409 | 42, 43, 44, 45, 46, 47, 48, 49, 50, 52, 410 | 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 411 | 63, 64, 65, 66, 67, 68, 69, 71, 72, 73, 412 | 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 413 | 84, 85, 86, 87, 88, 89, 91, 92, 93, 94, 414 | 95, 96, 97, 98, 99, 100 415 | ], 416 | [ 417 | 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 418 | 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 419 | 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 420 | 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 421 | 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 422 | 99, 100 423 | ], 424 | [ 425 | 50, 51, 52, 53, 54, 56, 57, 58, 59, 60, 426 | 61, 62, 63, 64, 66, 67, 68, 69, 70, 71, 427 | 72, 73, 74, 76, 77, 78, 79, 80, 81, 82, 428 | 83, 84, 86, 87, 88, 89, 90, 91, 92, 93, 429 | 94, 96, 97, 98, 99, 100 430 | ], 431 | [ 432 | 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 433 | 63, 64, 65, 66, 67, 68, 69, 71, 72, 73, 434 | 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 435 | 84, 85, 86, 87, 88, 89, 91, 92, 93, 94, 436 | 95, 96, 97, 98, 99, 100 437 | ] 438 | ] 439 | 440 | /// returns the number of pushups required for a given score 441 | static func runTime(forScore score : Int) -> Int { 442 | let rar = Score.run[self.gender.rawValue][Score.index(forAge: Score.age)] 443 | let timeRar = Score.gender == .male ? Score.maleRunCount : Score.femaleRunCount 444 | if score <= 0 { return 0 } 445 | for (i, val) in rar.enumerated() { 446 | if val >= score { 447 | return timeRar[i] 448 | } 449 | } 450 | return timeRar[timeRar.count] 451 | } 452 | 453 | /// returns the score for the given number of pushups 454 | public static func score(forRun time: Int) -> Int { 455 | print("getting score for: ", time) 456 | let rar = Score.run[self.gender.rawValue][Score.index(forAge: Score.age)] 457 | let index = max(0, min(Score.index(forRun: time), rar.count-1)) 458 | print(rar, index, rar.count) 459 | return rar[index] 460 | } 461 | 462 | private static func index(forRun time: Int) -> Int { 463 | let rar = Score.gender == .male ? Score.maleRunCount : Score.femaleRunCount 464 | print(rar) 465 | for (i, val) in rar.enumerated() { 466 | if time == val { 467 | print(time, ">=", val, ": ", i) 468 | return i 469 | } 470 | if time > val { 471 | return i-1 472 | } 473 | } 474 | print("never greater") 475 | return rar.count-1 476 | } 477 | 478 | private static let maleRunCount = [ 479 | 2042, 2036, 2030, 2024, 2018, 2012, 2006, 480 | 2000, 1954, 1948, 1942, 1936, 1930, 1924, 481 | 1918, 1912, 1906, 1900, 1854, 1848, 1842, 482 | 1836, 1830, 1824, 1818, 1812, 1806, 1800, 483 | 1754, 1748, 1742, 1736, 1730, 1724, 1718, 484 | 1712, 1706, 1700, 1654, 1648, 1642, 1636, 485 | 1630, 1624, 1618, 1612, 1606, 1600, 1554, 486 | 1548, 1542, 1536, 1530, 1524, 1518, 1512, 487 | 1506, 1500, 1454, 1448, 1442, 1436, 1430, 488 | 1424, 1418, 1412, 1406, 1400, 1354, 1348, 489 | 1342, 1336, 1330, 1324, 1318, 1312, 1306, 490 | 1300, 1254 491 | ] 492 | 493 | private static let femaleRunCount = [ 494 | 2042, 2036, 2030, 2024, 2018, 2012, 2006, 495 | 2000, 1954, 1948, 1942, 1936, 1930, 1924, 496 | 1918, 1912, 1906, 1900, 1854, 1848, 1842, 497 | 1836, 1830, 1824, 1818, 1812, 1806, 1800, 498 | 1754, 1748, 1742, 1736, 1730, 1724, 1718, 499 | 1712, 1706, 1700, 1654, 1648, 1642, 1636, 500 | 1630, 1624, 1618, 1612, 1606, 1600, 1554, 501 | 1548, 1542, 1536, 1530 502 | ] 503 | 504 | private static let run = [ 505 | [ // male 506 | [ 507 | // 2042, 2036, 2030, 2024, 2018, 2012, 2006, 508 | 0000, 0000, 0000, 0000, 0000, 0001, 0002, 509 | // 2000, 1954, 1948, 1942, 1936, 1930, 1924, 510 | 0003, 0005, 0006, 0008, 0009, 0010, 0012, 511 | 0013, 0014, 0017, 0018, 0019, 0020, 0021, 512 | 0023, 0024, 0026, 0027, 0028, 0030, 0031, 513 | 0032, 0034, 0035, 0037, 0038, 0039, 0041, 514 | 0042, 0043, 0045, 0046, 0048, 0049, 0050, 515 | 0052, 0053, 0054, 0056, 0057, 0059, 0060, 516 | 0061, 0063, 0064, 0066, 0067, 0068, 0070, 517 | 0071, 0072, 0074, 0075, 0077, 0078, 0079, 518 | 0081, 0082, 0083, 0085, 0086, 0088, 0089, 519 | // 1342, 1336, 1330, 1324, 1318, 1312, 1306, 520 | 0090, 0092, 0093, 0094, 0096, 0097, 0099, 521 | 0100 522 | ], 523 | [ 524 | 0014, 0016, 0017, 0018, 0019, 0020, 0021, 525 | 0022, 0023, 0024, 0026, 0027, 0028, 0029, 526 | 0030, 0031, 0032, 0033, 0034, 0036, 0037, 527 | 0038, 0039, 0040, 0041, 0042, 0043, 0044, 528 | 0046, 0047, 0048, 0049, 0050, 0051, 0052, 529 | 0053, 0054, 0056, 0057, 0058, 0059, 0060, 530 | 0061, 0062, 0063, 0064, 0066, 0067, 0068, 531 | 0069, 0070, 0071, 0072, 0073, 0074, 0076, 532 | 0077, 0078, 0079, 0080, 0081, 0082, 0083, 533 | 0084, 0086, 0087, 0088, 0089, 0090, 0091, 534 | 0092, 0093, 0094, 0096, 0097, 0098, 0099, 535 | 00100 536 | ], 537 | [ 538 | 0019, 0020, 0021, 0022, 0023, 0024, 0025, 539 | 0028, 0029, 0030, 0031, 0032, 0033, 0034, 540 | 0035, 0036, 0037, 0038, 0039, 0041, 0042, 541 | 0043, 0044, 0045, 0046, 0047, 0048, 0049, 542 | 0050, 0051, 0052, 0054, 0055, 0056, 0057, 543 | 0058, 0059, 0060, 0061, 0062, 0063, 0064, 544 | 0065, 0066, 0068, 0069, 0070, 0071, 0072, 545 | 0073, 0074, 0075, 0076, 0077, 0078, 0079, 546 | 0081, 0082, 0083, 0084, 0085, 0086, 0087, 547 | 0088, 0089, 0090, 0091, 0092, 0094, 0095, 548 | 0096, 0097, 0098, 0099, 00100 549 | ], 550 | [ 551 | 0033, 0034, 0035, 0035, 0036, 0037, 0038, 552 | 0039, 0040, 0041, 0042, 0043, 0044, 0045, 553 | 0045, 0046, 0047, 0048, 0049, 0050, 0051, 554 | 0052, 0053, 0054, 0055, 0055, 0056, 0057, 555 | 0058, 0059, 0060, 0061, 0062, 0063, 0064, 556 | 0065, 0065, 0066, 0067, 0068, 0069, 0070, 557 | 0071, 0072, 0073, 0074, 0075, 0075, 0076, 558 | 0077, 0078, 0079, 0080, 0081, 0082, 0083, 559 | 0084, 0085, 0085, 0086, 0087, 0088, 0089, 560 | 0090, 0091, 0092, 0093, 0094, 0095, 0095, 561 | 0096, 0097, 0098, 0099, 00100 562 | ], 563 | [ 564 | 0040, 0040, 0041, 0042, 0043, 0044, 0045, 565 | 0046, 0046, 0047, 0048, 0049, 0050, 0051, 566 | 0051, 0052, 0053, 0054, 0055, 0056, 0057, 567 | 0057, 0058, 0059, 0060, 0061, 0062, 0063, 568 | 0063, 0064, 0065, 0066, 0067, 0068, 0069, 569 | 0069, 0070, 0071, 0072, 0073, 0074, 0074, 570 | 0075, 0076, 0077, 0078, 0079, 0080, 0080, 571 | 0081, 0082, 0083, 0084, 0085, 0086, 0086, 572 | 0087, 0088, 0089, 0090, 0091, 0091, 0092, 573 | 0093, 0094, 0095, 0096, 0097, 0097, 0098, 574 | 0099, 00100 575 | ], 576 | [ 577 | 0043, 0043, 0044, 0045, 0046, 0047, 0048, 578 | 0049, 0050, 0050, 0051, 0052, 0053, 0054, 0055, 0056, 0057, 0057, 0058, 0059, 0060, 0061, 0062, 0063, 0063, 0064, 0065, 0066, 0067, 0068, 0069, 0070, 0070, 0071, 0072, 0073, 0074, 0075, 0076, 0077, 0077, 0078, 0079, 0080, 0081, 0082, 0083, 0083, 0084, 0085, 0086, 0087, 0088, 0089, 0089, 0090, 0091, 0092, 0093, 0094, 0095, 0096, 0097, 0097, 0098, 0099, 00100 579 | ], 580 | [ 581 | 0051, 0051, 0052, 0053, 0054, 0055, 0055, 582 | 0056, 0057, 0058, 0058, 0059, 0060, 0061, 0062, 0062, 0063, 0064, 0065, 0065, 0066, 0067, 0068, 0069, 0069, 0070, 0071, 0072, 0073, 0073, 0074, 0075, 0076, 0076, 0077, 0078, 0079, 0080, 0080, 0081, 0082, 0083, 0084, 0084, 0085, 0086, 0087, 0087, 0088, 0089, 0090, 0091, 0091, 0092, 0093, 0094, 0095, 0095, 0096, 0097, 0098, 0098, 0099, 00100 583 | ], 584 | [ 585 | 0053, 0054, 0055, 0055, 0056, 0057, 0058, 586 | 0058, 0059, 0060, 0061, 0062, 0062, 0063, 0064, 0065, 0065, 0066, 0067, 0068, 0069, 0069, 0070, 0071, 0072, 0073, 0073, 0074, 0075, 0076, 0076, 0077, 0078, 0079, 0080, 0080, 0081, 0082, 0083, 0084, 0084, 0085, 0086, 0087, 0087, 0088, 0089, 0090, 0091, 0091, 0092, 0093, 0094, 0095, 0095, 0096, 0097, 0098, 0098, 0099, 00100 587 | ] 588 | ], 589 | [ // female 590 | [ 591 | 0038, 0039, 0041, 0042, 0043, 0044, 0045, 592 | 0047, 0048, 0049, 0050, 0052, 0053, 0054, 593 | 0055, 0056, 0058, 0059, 0060, 0061, 0062, 594 | 0064, 0065, 0066, 0067, 0068, 0070, 0071, 595 | 0072, 0073, 0075, 0076, 0077, 0078, 0079, 596 | 0081, 0082, 0083, 0084, 0085, 0087, 0088, 597 | 0089, 0090, 0092, 0093, 0094, 0095, 0096, 0098, 0099, 00100 598 | ], 599 | [ 600 | 0049, 0050, 0051, 0052, 0053, 0054, 0055, 601 | 0056, 0057, 0058, 0059, 0060, 0061, 0062, 0063, 0064, 0065, 0066, 0067, 0068, 0069, 0070, 0071, 0072, 0073, 0074, 0075, 0076, 0077, 0078, 0079, 0080, 0081, 0082, 0083, 0084, 0085, 0086, 0087, 0088, 0089, 0090, 0091, 0092, 0093, 0094, 0095, 0096, 0097, 0098, 0099, 00100 602 | ], 603 | [ 604 | 0058, 0059, 0060, 0061, 0062, 0063, 0063, 0064, 0065, 0066, 0067, 0068, 0069, 0069, 0070, 0071, 0072, 0073, 0074, 0074, 0075, 0076, 0077, 0078, 0079, 0080, 0080, 0081, 0082, 0083, 0084, 0085, 0086, 0086, 0087, 0088, 0089, 0090, 0091, 0091, 0092, 0093, 0094, 0095, 0096, 0097, 0097, 0098, 0099, 00100 605 | ], 606 | [ 607 | 0067, 0068, 0068, 0069, 0070, 0070, 0071, 0072, 0072, 0073, 0074, 0074, 0075, 0076, 0077, 0077, 0078, 0079, 0079, 0080, 0081, 0081, 0082, 0083, 0083, 0084, 0085, 0086, 0086, 0087, 0088, 0088, 0089, 0090, 0090, 0091, 0092, 0092, 0093, 0094, 0094, 0095, 0096, 0097, 0097, 0098, 0099, 0099, 00100 608 | ], 609 | [ 610 | 0074, 0075, 0075, 0076, 0077, 0078, 0078, 0079, 0080, 0080, 0081, 0082, 0082, 0083, 0084, 0085, 0085, 0086, 0087, 0087, 0088, 0089, 0089, 0090, 0091, 0092, 0092, 0093, 0094, 0094, 0095, 0096, 0096, 0097, 0098, 0099, 0099, 00100 611 | ], 612 | [ 613 | 0079, 0080, 0080, 0081, 0082, 0082, 0083, 0083, 0084, 0085, 0085, 0086, 0087, 0087, 0088, 0089, 0089, 0090, 0090, 0091, 0092, 0092, 0093, 0094, 0094, 0095, 0096, 0096, 0097, 0097, 0098, 0099, 0099, 00100 614 | ], 615 | [ 616 | 0081, 0081, 0082, 0082, 0083, 0084, 0084, 0085, 0086, 0086, 0087, 0087, 0088, 0089, 0089, 0090, 0091, 0091, 0092, 0092, 0093, 0094, 0094, 0095, 0096, 0096, 0097, 0097, 0098, 0099, 0099, 00100 617 | ], 618 | [ 619 | 0087, 0088, 0089, 0090, 0090, 0091, 0092, 0093, 0093, 0094, 0095, 0096, 0096, 0097, 0098, 0099, 0099, 00100 620 | ] 621 | ] 622 | ] 623 | 624 | } 625 | -------------------------------------------------------------------------------- /reverseapft/InstructionsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InstructionsViewController.swift 3 | // acft 4 | // 5 | // Created by Zachary Gorak on 10/26/18. 6 | // Copyright © 2018 Zachary Gorak. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import AVKit 12 | import AVFoundation 13 | import WebKit 14 | 15 | 16 | // https://www.military.com/sites/default/files/2018-09/Field%20testing%20manual.pdf 17 | let speech = [ 18 | "YOU ARE ABOUT TO TAKE THE ARMY PHYSICAL FITNESS TEST, A TEST THAT WILL MEASURE YOUR MUSCULAR ENDURANCE AND CARDIORESPIRATORY FITNESS. THE RESULTS OF THIS TEST WILL GIVE YOU AND YOUR COMMANDERS AN INDICATION OF YOUR STATE OF FITNESS AND WILL ACT AS A GUIDE IN DETERMINING YOUR PHYSICAL TRAINING NEEDS. LISTEN CLOSELY TO THE TEST INSTRUCTIONS, AND DO THE BEST YOU CAN ON EACH OF THE EVENTS.", 19 | "THE PUSH-UP EVENT MEASURES THE ENDURANCE OF THE CHEST, SHOULDER, AND TRICEPS MUSCLES. ON THE COMMAND 'GET SET,' ASSUME THE FRONT-LEANING REST POSITION BY PLACING YOUR HANDS WHERE THEY ARE COMFORTABLE FOR YOU. YOUR FEET MAY BE TOGETHER OR UP TO 12 INCHES APART. WHEN VIEWED FROM THE SIDE, YOUR BODY SHOULD FORM A GENERALLY STRAIGHT LINE FROM YOUR SHOULDERS TO YOUR ANKLES. ON THE COMMAND 'GO,' BEGIN THE PUSH-UP BY BENDING YOUR ELBOWS AND LOWERING YOUR ENTIRE BODY AS A SINGLE UNIT UNTIL YOUR UPPER ARMS ARE AT LEAST PARALLEL TO THE GROUND. THEN, RETURN TO THE STARTING POSITION BY RAISING YOUR ENTIRE BODY UNTIL YOUR ARMS ARE FULLY EXTENDED. YOUR BODY MUST REMAIN RIGID IN A GENERALLY STRAIGHT LINE AND MOVE AS A UNIT WHILE PERFORMING EACH REPETITION. AT THE END OF EACH REPETITION, THE SCORER WILL STATE THE NUMBER OF REPETITIONS YOU HAVE COMPLETED CORRECTLY. IF YOU FAIL TO KEEP YOUR BODY GENERALLY STRAIGHT, TO LOWER YOUR WHOLE BODY UNTIL YOUR UPPER ARMS ARE AT LEAST PARALLEL TO THE GROUND, OR TO EXTEND YOUR ARMS COMPLETELY, THAT REPETITION WILL NOT COUNT, AND THE SCORER WILL REPEAT THE NUMBER OF THE LAST CORRECTLY PERFORMED REPETITION. IF YOU FAIL TO PERFORM THE FIRST TEN PUSH-UPS CORRECTLY, THE SCORER WILL TELL YOU TO GO TO YOUR KNEES AND WILL EXPLAIN TO YOU WHAT YOUR MISTAKES ARE. YOU WILL THEN BE SENT TO THE END OF THE LINE TO BE RETESTED. AFTER THE FIRST 10 PUSH-UPS HAVE BEEN PERFORMED AND COUNTED, HOWEVER, NO RESTARTS ARE ALLOWED. THE TEST WILL CONTINUE, AND ANY INCORRECTLY PERFORMED PUSH-UPS WILL NOT BE COUNTED. AN ALTERED, FRONT-LEANING REST POSITION IS THE ONLY AUTHORIZED REST POSITION. THAT IS, YOU MAY SAG IN THE MIDDLE OR FLEX YOUR BACK. WHEN FLEXING YOUR BACK, YOU MAY BEND YOUR KNEES, BUT NOT TO SUCH AN EXTENT THAT YOU ARE SUPPORTING MOST OF YOUR BODY WEIGHT WITH YOUR LEGS. IF THIS OCCURS, YOUR PERFORMANCE WILL BE TERMINATED. YOU MUST RETURN TO, AND PAUSE IN, THE CORRECT STARTING POSITION BEFORE CONTINUING. IF YOU REST ON THE GROUND OR RAISE EITHER HAND OR FOOT FROM THE GROUND, YOUR PERFORMANCE WILL BE TERMINATED. YOU MAY REPOSITION YOUR HANDS AND/OR FEET DURING THE EVENT AS LONG AS THEY REMAIN IN CONTACT WITH THE GROUND AT ALL TIMES. CORRECT PERFORMANCE IS IMPORTANT. YOU WILL HAVE TWO MINUTES IN WHICH TO DO AS MANY PUSH-UPS AS YOU CAN. WATCH THIS DEMONSTRATION.", 20 | "WHAT ARE YOUR QUESTIONS?", 21 | "THE SIT-UP EVENT MEASURES THE ENDURANCE OF THE ABDOMINAL AND HIP-FLEXOR MUSCLES. ON THE COMMAND 'GET SET', ASSUME THE STARTING POSITION BY LYING ON YOUR BACK WITH YOUR KNEES BENT AT A 90-DEGREE ANGLE. YOUR FEET MAY BE TOGETHER OR UP TO 12 INCHES APART. ANOTHER PERSON WILL HOLD YOUR ANKLES WITH THE HANDS ONLY. NO OTHER METHOD OF BRACING OR HOLDING THE FEET IS AUTHORIZED. THE HEEL IS THE ONLY PART OF YOUR FOOT THAT MUST STAY IN CONTACT WITH THE GROUND. YOUR FINGERS MUST BE INTERLOCKED BEHIND YOUR HEAD AND THE BACKS OF YOUR HANDS MUST TOUCH THE GROUND. YOUR ARMS AND ELBOWS NEED NOT TOUCH THE GROUND. ON THE COMMAND 'GO', BEGIN RAISING YOUR UPPER BODY FORWARD TO, OR BEYOND, THE VERTICAL POSITION. THE VERTICAL POSITION MEANS THAT THE BASE OF YOUR NECK IS ABOVE THE BASE OF YOUR SPINE. AFTER YOU HAVE REACHED OR SURPASSED THE VERTICAL POSITION, LOWER YOUR BODY UNTIL THE BOTTOM OF YOUR SHOULDER BLADES TOUCH THE GROUND. YOUR HEAD, HANDS, ARMS, OR ELBOWS DO NOT HAVE TO TOUCH THE GROUND. AT THE END OF EACH REPETITION, THE SCORER WILL STATE THE NUMBER OF SIT-UPS YOU HAVE CORRECTLY COMPLETED. A REPETITION WILL NOT COUNT IF YOU FAIL TO REACH THE VERTICAL POSITION, FAIL TO KEEP YOUR FINGERS INTERLOCKED BEHIND YOUR HEAD, ARCH OR BOW YOUR BACK AND RAISE YOUR BUTTOCKS OFF THE GROUND TO RAISE YOUR UPPER BODY, OR LET YOUR KNEES EXCEED A 90-DEGREE ANGLE. IF A REPETITION DOES NOT COUNT, THE SCORER WILL REPEAT THE NUMBER OF YOUR LAST CORRECTLY PERFORMED SIT-UP. THE UP POSITION IS THE ONLY AUTHORIZED REST POSITION. IF YOU STOP AND REST IN THE DOWN (STARTING) POSITION, THE EVENT WILL BE TERMINATED. AS LONG AS YOU MAKE A CONTINUOUS PHYSICAL EFFORT TO SIT UP, THE EVENT WILL NOT BE TERMINATED. YOU MAY NOT USE YOUR HANDS OR ANY OTHER MEANS TO PULL OR PUSH YOURSELF UP TO THE UP (RESTING) POSITION OR TO HOLD YOURSELF IN THE REST POSITION. IF YOU DO SO, YOUR PERFORMANCE IN THE EVENT WILL BE TERMINATED. CORRECT PERFORMANCE IS IMPORTANT. YOU WILL HAVE TWO MINUTES TO PERFORM AS MANY SIT-UPS AS YOU CAN. WATCH THIS DEMONSTRATION.", 22 | "WHAT ARE YOUR QUESTIONS?", 23 | "THE TWO-MILE RUN IS USED TO ASSESS YOUR AEROBIC FITNESS AND YOUR LEG MUSCLES' ENDURANCE. YOU MUST COMPLETE THE RUN WITHOUT ANY PHYSICAL HELP. AT THE START, ALL SOLDIERS WILL LINE UP BEHIND THE STARTING LINE. ON THE COMMAND 'GO,' THE CLOCK WILL START. YOU WILL BEGIN RUNNING AT YOUR OWN PACE.", 24 | "YOU ARE BEING TESTED ON YOUR ABILITY TO COMPLETE THE 2-MILE COURSE IN THE SHORTEST TIME POSSIBLE. ALTHOUGH WALKING IS AUTHORIZED, IT IS STRONGLY DISCOURAGED. IF YOU ARE PHYSICALLY HELPED IN ANY WAY (FOR EXAMPLE, PULLED, PUSHED, PICKED UP, AND/OR CARRIED) OR LEAVE THE DESIGNATED RUNNING COURSE FOR ANY REASON, YOU WILL BE DISQUALIFIED. (IT IS LEGAL TO PACE A SOLDIER DURING THE 2-MILE RUN. AS LONG AS THERE IS NO PHYSICAL CONTACT WITH THE PACED SOLDIER AND IT DOES NOT PHYSICALLY HINDER OTHER SOLDIERS TAKING THE TEST, THE PRACTICE OF RUNNING AHEAD OF, ALONG SIDE OF, OR BEHIND THE TESTED SOLDIER, WHILE SERVING AS A PACER, IS PERMITTED. CHEERING OR CALLING OUT THE ELAPSED TIME IS ALSO PERMITTED.) THE NUMBER ON YOUR CHEST IS FOR IDENTIFICATION. YOU MUST MAKE SURE IT IS VISIBLE AT ALL TIMES. TURN IN YOUR NUMBER WHEN YOU FINISH THE RUN. THEN, GO TO THE AREA DESIGNATED FOR THE COOL-DOWN AND STRETCH. DO NOT STAY NEAR THE SCORERS OR THE FINISH LINE AS THIS MAY INTERFERE WITH THE TESTING.", 25 | "WHAT ARE YOUR QUESTIONS ON THIS EVENT?" 26 | ] 27 | 28 | 29 | // https://www.appcoda.com/text-to-speech-ios-tutorial/ 30 | 31 | class PlayPauseButton: UIButton { 32 | var paragraphs: [UILabel]? 33 | 34 | private let systemButton = UIButton(type: .system) 35 | private let playIcon = UIImage(named: "play")?.withRenderingMode(.alwaysTemplate) 36 | private let stopIcon = UIImage(named: "stop")?.withRenderingMode(.alwaysTemplate) 37 | 38 | init() { 39 | super.init(frame: CGRect.zero) 40 | 41 | self.tintColor = systemButton.tintColor 42 | self.setTitleColor(systemButton.titleColor(for: .normal), for: .normal) 43 | self.setImage(playIcon, for: .normal) 44 | } 45 | 46 | required init?(coder aDecoder: NSCoder) { 47 | fatalError("init(coder:) has not been implemented") 48 | } 49 | 50 | var isPlaying: Bool = false 51 | { 52 | didSet { 53 | print("did set", self.isPlaying) 54 | if(self.isPlaying) { 55 | //self.setTitle("Stop", for: .normal) 56 | self.setImage(stopIcon, for: .normal) 57 | } else { 58 | //self.setTitle("Play", for: .normal) 59 | self.setImage(playIcon, for: .normal) 60 | } 61 | } 62 | } 63 | } 64 | 65 | class SpeechUtterance: AVSpeechUtterance { 66 | var label: UILabel? 67 | var controlButton: PlayPauseButton? 68 | } 69 | 70 | class InstructionsViewController: UIViewController, UIScrollViewDelegate, AVSpeechSynthesizerDelegate { 71 | 72 | let label = UILabel() 73 | let speechSynthesizer = AVSpeechSynthesizer() 74 | let scrollview = UIScrollView() 75 | let stack = UIStackView() 76 | var lastPressedButton: PlayPauseButton? 77 | 78 | var request: NSBundleResourceRequest { 79 | willSet { 80 | /* this should only happen if we have an error since we can't do miltiple beginAccessingResources */ 81 | self.request.endAccessingResources() 82 | } 83 | } 84 | var hasAddedVideos = false 85 | var shouldAddVideos = false { 86 | didSet { 87 | self.reloadView() 88 | } 89 | } 90 | var isLoading = false { 91 | didSet { 92 | if(self.isViewLoaded) { 93 | DispatchQueue.main.async { 94 | if(self.isLoading) { 95 | self.downloadVideosButton.isEnabled = false 96 | self.downloadStatus.startAnimating() 97 | } else { 98 | self.downloadVideosButton.isEnabled = true 99 | self.downloadStatus.stopAnimating() 100 | } 101 | } 102 | } 103 | } 104 | } 105 | 106 | func reloadView() { 107 | if(self.isViewLoaded) { 108 | DispatchQueue.main.async { 109 | print("reloading view") 110 | 111 | if self.shouldAddVideos { 112 | if(self.addAllVideos()) 113 | { 114 | self.downloadStack.removeFromSuperview() 115 | self.hasAddedVideos = true 116 | } else { 117 | let alert = UIAlertController(title: "Error", message: "Unable to add videos", preferredStyle: .alert) 118 | alert.addAction(UIAlertAction(title: "Okay", style: .destructive, handler: { _ in 119 | self.reloadView() 120 | })) 121 | self.present(alert, animated: true, completion: nil) 122 | self.request = InstructionsViewController.newRequest 123 | self.shouldAddVideos = false 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | private static var newRequest: NSBundleResourceRequest { 131 | var s = Set(InstructionsViewController.videos) 132 | s.insert("video") 133 | return NSBundleResourceRequest(tags: s) 134 | } 135 | 136 | let downloadStack = UIStackView() 137 | let downloadVideosButton = UIButton(type: .system) 138 | let downloadStatus = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.gray) 139 | 140 | static let videos = ["deadlift", "powerthrow", "pushup", "sprint", "legtuck", "run"] 141 | let videos = InstructionsViewController.videos 142 | 143 | init() { 144 | self.request = InstructionsViewController.newRequest 145 | 146 | super.init(nibName: nil, bundle: nil) 147 | 148 | /* 149 | self.preloadVideos() 150 | 151 | NotificationCenter.default.addObserver(self, selector: #selector(lowdisk), name: .NSBundleResourceRequestLowDiskSpace, object: nil) 152 | */ 153 | } 154 | 155 | @objc func lowdisk() { 156 | print("low disk") 157 | if(self.isViewLoaded && self.isLoading) { 158 | DispatchQueue.main.async { 159 | let alert = UIAlertController(title: "Low disk", message: "Warning, you are running out of disk space", preferredStyle: .alert) 160 | alert.addAction(UIAlertAction(title: "Okay", style: .destructive, handler: { _ in 161 | self.reloadView() 162 | })) 163 | self.present(alert, animated: true, completion: nil) 164 | } 165 | } 166 | } 167 | 168 | required init?(coder aDecoder: NSCoder) { 169 | fatalError("init(coder:) has not been implemented") 170 | } 171 | 172 | private func preloadVideos() { 173 | print("preloading") 174 | self.isLoading = true 175 | self.request.conditionallyBeginAccessingResources(completionHandler: { resourceAvailable in 176 | if (resourceAvailable) { 177 | print("can begin access resources in preload", resourceAvailable) 178 | self.isLoading = false 179 | self.shouldAddVideos = true 180 | } 181 | else { 182 | self.request.beginAccessingResources(completionHandler: {error in 183 | self.isLoading = false 184 | if(error == nil) { 185 | print("can begin to access resources in preload 2") 186 | self.shouldAddVideos = true 187 | } else { 188 | print("failed to access resources from preload") 189 | self.request = InstructionsViewController.newRequest 190 | } 191 | }) 192 | } 193 | }) 194 | } 195 | 196 | @objc func loadVideos() { 197 | self.reloadView() 198 | 199 | if(!self.isLoading && !self.shouldAddVideos && !self.hasAddedVideos) { 200 | self.isLoading = true 201 | self.request.loadingPriority = NSBundleResourceRequestLoadingPriorityUrgent 202 | self.request.conditionallyBeginAccessingResources(completionHandler: { resourceAvailable in 203 | if (!resourceAvailable) { 204 | self.request.beginAccessingResources(completionHandler: { error in 205 | self.isLoading = false 206 | if let e = error { 207 | self.request = InstructionsViewController.newRequest 208 | 209 | let alert = UIAlertController(title: "Error", message: e.localizedDescription, preferredStyle: .alert) 210 | alert.addAction(UIAlertAction(title: "Okay", style: .destructive, handler: { _ in 211 | self.reloadView() 212 | })) 213 | alert.addAction(UIAlertAction(title: "Try Again", style: .default, handler: {_ in self.loadVideos()})) 214 | DispatchQueue.main.async { 215 | self.present(alert, animated: true, completion: nil) 216 | } 217 | } else { 218 | print("can begin to access resources 2") 219 | self.shouldAddVideos = true 220 | } 221 | }) 222 | } 223 | else{ 224 | self.shouldAddVideos = true 225 | self.isLoading = false 226 | } 227 | }) 228 | } 229 | } 230 | 231 | @objc func defaultsChanged(){ 232 | print("defaults did change", UserDefaults.standard.bool(forKey: "loud_sound")) 233 | 234 | if UserDefaults.standard.bool(forKey: "respect_mute") { 235 | do { 236 | try AVAudioSession.sharedInstance().setActive(false) 237 | } 238 | catch let error as NSError { 239 | print("Error: Could not setActive to true: \(error), \(error.userInfo)") 240 | } 241 | } else { 242 | do { 243 | try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: .spokenAudio, options: .mixWithOthers) 244 | } 245 | catch let error as NSError { 246 | print("Error: Could not set audio category: \(error), \(error.userInfo)") 247 | } 248 | 249 | do { 250 | try AVAudioSession.sharedInstance().setActive(true) 251 | } 252 | catch let error as NSError { 253 | print("Error: Could not setActive to true: \(error), \(error.userInfo)") 254 | } 255 | } 256 | } 257 | 258 | override func viewDidLoad() { 259 | super.viewDidLoad() 260 | 261 | NotificationCenter.default.addObserver(self, selector: #selector(defaultsChanged), name: UserDefaults.didChangeNotification, object: nil) 262 | 263 | if #available(iOS 13.0, *) { 264 | self.view.backgroundColor = UIColor.systemBackground 265 | } else { 266 | self.view.backgroundColor = .white 267 | } 268 | 269 | scrollview.translatesAutoresizingMaskIntoConstraints = false 270 | scrollview.delegate = self 271 | self.view.addSubview(scrollview) 272 | self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": scrollview])) 273 | self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[scrollview]-|", options: .alignAllCenterY, metrics: nil, views: ["scrollview": scrollview])) 274 | 275 | stack.axis = NSLayoutConstraint.Axis.vertical 276 | //stack.alignment = UIStackViewAlignment.Fill 277 | //stack.distribution = UIStackViewDistribution.FillProportionally 278 | stack.spacing = 10 279 | stack.translatesAutoresizingMaskIntoConstraints = false 280 | let topConstraint = NSLayoutConstraint( 281 | item: stack, 282 | attribute: NSLayoutConstraint.Attribute.topMargin, 283 | relatedBy: NSLayoutConstraint.Relation.equal, 284 | toItem: scrollview, 285 | attribute: NSLayoutConstraint.Attribute.topMargin, 286 | multiplier: 1.0, 287 | constant: 0) 288 | let leftConstraint = NSLayoutConstraint( 289 | item: stack, 290 | attribute: NSLayoutConstraint.Attribute.leading, 291 | relatedBy: NSLayoutConstraint.Relation.equal, 292 | toItem: scrollview, 293 | attribute: NSLayoutConstraint.Attribute.leading, 294 | multiplier: 1.0, 295 | constant: 0) 296 | let rightConstraint = NSLayoutConstraint( 297 | item: stack, 298 | attribute: NSLayoutConstraint.Attribute.trailing, 299 | relatedBy: NSLayoutConstraint.Relation.equal, 300 | toItem: scrollview, 301 | attribute: NSLayoutConstraint.Attribute.trailing, 302 | multiplier: 1.0, 303 | constant: 0) 304 | let widthCon = NSLayoutConstraint( 305 | item: stack, 306 | attribute: NSLayoutConstraint.Attribute.width, 307 | relatedBy: NSLayoutConstraint.Relation.equal, 308 | toItem: scrollview, 309 | attribute: NSLayoutConstraint.Attribute.width, 310 | multiplier: 1.0, 311 | constant: 0) 312 | scrollview.addSubview(stack) 313 | scrollview.addConstraint(widthCon) 314 | scrollview.addConstraint(topConstraint) 315 | scrollview.addConstraint(leftConstraint) 316 | scrollview.addConstraint(rightConstraint) 317 | 318 | let playAll = PlayPauseButton() 319 | playAll.setTitle("All", for: .normal) 320 | playAll.addTarget(self, action: #selector(play), for: .touchDown) 321 | playAll.paragraphs = [] 322 | stack.addArrangedSubview(playAll) 323 | 324 | for paragraph in speech { 325 | let button = PlayPauseButton() 326 | button.setTitle("Section", for: .normal) 327 | button.addTarget(self, action: #selector(play), for: .touchDown) 328 | stack.addArrangedSubview(button) 329 | let pLabel = UILabel() 330 | playAll.paragraphs?.append(pLabel) 331 | button.paragraphs = [pLabel] 332 | pLabel.text = paragraph 333 | pLabel.numberOfLines = 0 334 | stack.addArrangedSubview(pLabel) 335 | } 336 | /* 337 | if(!self.addAllVideos()) { 338 | print("adding download button") 339 | downloadStatus.hidesWhenStopped = true 340 | downloadVideosButton.setTitle("Download Videos (207.3 MB)", for: .normal) 341 | downloadVideosButton.setImage(UIImage(named: "download")?.withRenderingMode(.alwaysTemplate), for: .normal) 342 | downloadVideosButton.imageView?.contentMode = .scaleAspectFit 343 | downloadVideosButton.addTarget(self, action: #selector(loadVideos), for: .touchDown) 344 | downloadStack.addArrangedSubview(downloadVideosButton) 345 | downloadStack.addArrangedSubview(downloadStatus) 346 | stack.insertArrangedSubview(downloadStack, at: 0) 347 | 348 | if(self.isLoading) { 349 | self.downloadVideosButton.isEnabled = false 350 | self.downloadStatus.startAnimating() 351 | } 352 | }*/ 353 | } 354 | 355 | func addAllVideos() -> Bool { 356 | var finalStatus = true 357 | var addedVideoControllers = [AVPlayerViewController]() 358 | 359 | if(!self.hasAddedVideos) 360 | { 361 | var videoIndex = 0 362 | 363 | for (i, subview) in self.stack.arrangedSubviews.enumerated() 364 | { 365 | if let label = subview as? UILabel 366 | { 367 | if let labelText = label.text 368 | { 369 | if (labelText.hasPrefix("ACFT EVENT #") || labelText.hasPrefix("ACFT TEST EVENT #")) 370 | { 371 | if videoIndex < videos.count { 372 | print("adding video", self.videos[videoIndex], i) 373 | if let view = self.addVideo(self.videos[videoIndex], atIndex: i+videoIndex-1) { 374 | addedVideoControllers.append(view) 375 | } else { 376 | print("failed to add a video") 377 | finalStatus = false 378 | break 379 | } 380 | videoIndex = videoIndex + 1 381 | } 382 | } 383 | } 384 | } 385 | } 386 | } 387 | 388 | if(!finalStatus) { 389 | for controller in addedVideoControllers { 390 | controller.removeFromParent() 391 | controller.view.removeFromSuperview() 392 | } 393 | } 394 | 395 | return finalStatus 396 | } 397 | 398 | func addVideo(_ name: String, atIndex index: Int = -1) -> AVPlayerViewController? 399 | { 400 | guard let path = self.request.bundle.path(forResource: name, ofType:"mp4") else { 401 | print(name, "not found") 402 | return nil 403 | } 404 | 405 | let videoURL:URL = URL(fileURLWithPath: path) 406 | let avPlayer = AVPlayer(url: videoURL) 407 | let avPlayerController = AVPlayerViewController() 408 | avPlayerController.showsPlaybackControls = true 409 | avPlayerController.player = avPlayer 410 | //avPlayerController.view.frame = self.view.frame 411 | //avPlayerController.view.translatesAutoresizingMaskIntoConstraints = false 412 | self.addChild(avPlayerController) 413 | //avPlayer.play() 414 | if(index == -1) { 415 | stack.addArrangedSubview(avPlayerController.view) 416 | } else { 417 | stack.insertArrangedSubview(avPlayerController.view, at: index) 418 | } 419 | //avPlayerController.view.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 1.0).isActive = true 420 | avPlayerController.view.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 0.3).isActive = true 421 | 422 | return avPlayerController 423 | } 424 | 425 | func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didStart utterance: AVSpeechUtterance) { 426 | if let su = utterance as? SpeechUtterance { 427 | su.controlButton?.isPlaying = true 428 | } 429 | } 430 | 431 | func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didContinue utterance: AVSpeechUtterance) { 432 | if let su = utterance as? SpeechUtterance { 433 | su.controlButton?.isPlaying = true 434 | } 435 | } 436 | 437 | func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didPause utterance: AVSpeechUtterance) { 438 | if let su = utterance as? SpeechUtterance { 439 | su.label?.attributedText = NSAttributedString(string: utterance.speechString) 440 | su.controlButton?.isPlaying = false 441 | } 442 | } 443 | 444 | func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, willSpeakRangeOfSpeechString characterRange: NSRange, utterance: AVSpeechUtterance) { 445 | if let su = utterance as? SpeechUtterance { 446 | print("is a speech utterance") 447 | let mutableAttributedString = NSMutableAttributedString(string: utterance.speechString) 448 | mutableAttributedString.addAttribute(.foregroundColor, value: UIColor(displayP3Red: 246.0/255.0, green: 193.0/255.0, blue: 67.0/255.0, alpha: 1.0), range: characterRange) 449 | su.label?.attributedText = mutableAttributedString 450 | } 451 | 452 | } 453 | 454 | func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) { 455 | print("did cancel") 456 | if let su = utterance as? SpeechUtterance { 457 | su.label?.attributedText = NSAttributedString(string: utterance.speechString) 458 | su.controlButton?.isPlaying = false 459 | } 460 | } 461 | 462 | func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) { 463 | print("did finish") 464 | if let su = utterance as? SpeechUtterance { 465 | su.label?.attributedText = NSAttributedString(string: utterance.speechString) 466 | su.controlButton?.isPlaying = false 467 | } 468 | } 469 | 470 | override func viewDidLayoutSubviews() { 471 | super.viewDidLayoutSubviews() 472 | //print("Laying out", self.view.frame, self.stack.frame, self.stack.frame.height) 473 | self.scrollview.contentSize = CGSize(width: self.stack.frame.width, height: self.stack.frame.height + (UIFont.systemFontSize * 2)) // NO idea why if I don't have extra space at the bottom things get cut off 474 | } 475 | 476 | @objc func play(_ sender: PlayPauseButton) { 477 | print("Clicked play/pause", speechSynthesizer.isSpeaking, speechSynthesizer.isPaused) 478 | 479 | speechSynthesizer.stopSpeaking(at: .immediate) 480 | 481 | if (!sender.isPlaying) 482 | { 483 | if let paragraphs = sender.paragraphs { 484 | for paragraph in paragraphs { 485 | let speechUtterance = SpeechUtterance(string: paragraph.text ?? "") 486 | speechUtterance.label = paragraph 487 | speechUtterance.controlButton = sender 488 | //speechUtterance.rate = AVSpeechUtteranceDefaultSpeechRate * 1.01 489 | //speechUtterance.voice = AVSpeechSynthesisVoice(language: "en-US") 490 | speechSynthesizer.delegate = self 491 | speechSynthesizer.speak(speechUtterance) 492 | } 493 | } 494 | } 495 | 496 | lastPressedButton = sender 497 | } 498 | 499 | // func scrollViewDidScroll(_ scrollView: UIScrollView) { 500 | // //print("scrolling") 501 | // self.dismissKeyboard() 502 | // } 503 | } 504 | --------------------------------------------------------------------------------