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