├── Capsmall.ttf ├── capture_it.ttf ├── Assets.xcassets ├── Contents.json ├── 8.imageset │ ├── 8.png │ ├── 8@2x.png │ ├── 8@3x.png │ └── Contents.json ├── BG.imageset │ ├── Group.png │ ├── Group@2x.png │ ├── Group@3x.png │ └── Contents.json ├── BGLogo.imageset │ ├── BGLogo.png │ ├── BGLogo@2x.png │ ├── BGLogo@3x.png │ └── Contents.json ├── Standard.imageset │ ├── Shape.png │ ├── Shape@2x.png │ ├── Shape@3x.png │ └── Contents.json ├── overlayLogo.imageset │ ├── bg.png │ ├── bg@2x.png │ ├── bg@3x.png │ └── Contents.json ├── CountDown.imageset │ ├── Group.png │ ├── Group@2x.png │ ├── Group@3x.png │ └── Contents.json ├── Group 2.imageset │ ├── Group 2.png │ └── Contents.json ├── Interval.imageset │ ├── Group 2.png │ ├── Group 2@2x.png │ ├── Group 2@3x.png │ └── Contents.json ├── LinkToShop.imageset │ ├── Shape.png │ ├── Shape@2x.png │ ├── Shape@3x.png │ └── Contents.json ├── backButton.imageset │ ├── Shape.png │ ├── Shape@2x.png │ ├── Shape@3x.png │ └── Contents.json ├── Questions.imageset │ ├── Questions.png │ ├── Questions@2x.png │ ├── Questions@3x.png │ └── Contents.json ├── Recording.imageset │ ├── Recording.png │ ├── Recording@2x.png │ ├── Recording@3x.png │ └── Contents.json ├── TileAddRep.imageset │ ├── Group 2.png │ ├── Group 2@2x.png │ ├── Group 2@3x.png │ └── Contents.json ├── AppIcon.appiconset │ ├── AppIcon 5-6.png │ ├── appIcon@2x.png │ ├── AppIcon 5-6@2x.png │ ├── App Icon 7-9@3x.png │ ├── Spot Light 7-9@2x.png │ ├── Spot Light 7-9@3x.png │ ├── Spot Light Icon-1.png │ ├── Spot Light Icon@2x.png │ ├── Spot Light Icon@3x.png │ ├── Spot Light Icon@2x-1.png │ ├── Spot Light Icon@3x-1.png │ └── Contents.json ├── FlipCamera.imageset │ ├── FlipCamera.png │ ├── FlipCamera@2x.png │ ├── FlipCamera@3x.png │ └── Contents.json ├── RecordDelay.imageset │ ├── RecordDelay.png │ ├── RecordDelay@2x.png │ ├── RecordDelay@3x.png │ └── Contents.json ├── Rectangle 7.imageset │ ├── Rectangle 7.png │ ├── Rectangle 7@2x.png │ ├── Rectangle 7@3x.png │ └── Contents.json ├── RecordButton.imageset │ ├── RecordButton.png │ ├── RecordButton@2x.png │ ├── RecordButton@3x.png │ └── Contents.json ├── TileAddRound.imageset │ ├── TileAddRound.png │ ├── TileAddRound@2x.png │ ├── TileAddRound@3x.png │ └── Contents.json ├── cancelButton.imageset │ ├── Cancel button.png │ ├── Cancel button@2x.png │ ├── Cancel button@3x.png │ └── Contents.json └── HighVideoQuality.imageset │ ├── HighVideoQuality.png │ ├── HighVideoQuality@2x.png │ ├── HighVideoQuality@3x.png │ └── Contents.json ├── ufonts.com_folio-bold-condensed.ttf ├── Wodeo 2.xcodeproj ├── xcuserdata │ └── GazLong.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ ├── xcschememanagement.plist │ │ └── Wodeo 2.xcscheme └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ └── GazLong.xcuserdatad │ └── UserInterfaceState.xcuserstate ├── RoundedView.swift ├── BorderButton.swift ├── TimeTextLabel.swift ├── MainMenuCell.swift ├── WodeoProducts.swift ├── WodeoViewController.swift ├── RotatorView.swift ├── BallicticStashView.swift ├── CountDownWOD.swift ├── StandardWOD.swift ├── Wodeo 2 ├── Info.plist ├── AppDelegate.swift └── LaunchScreen.storyboard ├── WodeoModel.swift ├── InfoViewController.swift ├── IntervalWOD.swift ├── TabataResultsVC.swift ├── IntervalResultsVC.swift ├── StandardResultsVC.swift ├── CountDownResultsVC.swift ├── Spotlight.swift ├── UserSettings.swift ├── SpotlightTransitionController.swift ├── SettingsViewController.swift ├── TabataWOD.swift ├── SpotlightView.swift ├── TiledView.swift ├── CustomPhotoLibrary.swift ├── SpotlightViewController.swift ├── WodeoProductPage.swift ├── IntervalOverlay.swift ├── VideoMaker.swift ├── ResultsViewController.swift ├── StandardOverlay.swift ├── MainMenuTableViewController.swift ├── IAPHelper.swift ├── CountdownOverlay.swift ├── StopWatch.swift ├── TabataOverlay.swift ├── OverlayViewController.swift ├── IntervalSettingsVC.swift └── TabataSettingsVC.swift /Capsmall.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Capsmall.ttf -------------------------------------------------------------------------------- /capture_it.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/capture_it.ttf -------------------------------------------------------------------------------- /Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Assets.xcassets/8.imageset/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/8.imageset/8.png -------------------------------------------------------------------------------- /Assets.xcassets/8.imageset/8@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/8.imageset/8@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/8.imageset/8@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/8.imageset/8@3x.png -------------------------------------------------------------------------------- /ufonts.com_folio-bold-condensed.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/ufonts.com_folio-bold-condensed.ttf -------------------------------------------------------------------------------- /Assets.xcassets/BG.imageset/Group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/BG.imageset/Group.png -------------------------------------------------------------------------------- /Assets.xcassets/BG.imageset/Group@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/BG.imageset/Group@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/BG.imageset/Group@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/BG.imageset/Group@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/BGLogo.imageset/BGLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/BGLogo.imageset/BGLogo.png -------------------------------------------------------------------------------- /Assets.xcassets/Standard.imageset/Shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Standard.imageset/Shape.png -------------------------------------------------------------------------------- /Assets.xcassets/overlayLogo.imageset/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/overlayLogo.imageset/bg.png -------------------------------------------------------------------------------- /Assets.xcassets/BGLogo.imageset/BGLogo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/BGLogo.imageset/BGLogo@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/BGLogo.imageset/BGLogo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/BGLogo.imageset/BGLogo@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/CountDown.imageset/Group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/CountDown.imageset/Group.png -------------------------------------------------------------------------------- /Assets.xcassets/Group 2.imageset/Group 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Group 2.imageset/Group 2.png -------------------------------------------------------------------------------- /Assets.xcassets/Interval.imageset/Group 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Interval.imageset/Group 2.png -------------------------------------------------------------------------------- /Assets.xcassets/LinkToShop.imageset/Shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/LinkToShop.imageset/Shape.png -------------------------------------------------------------------------------- /Assets.xcassets/backButton.imageset/Shape.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/backButton.imageset/Shape.png -------------------------------------------------------------------------------- /Assets.xcassets/CountDown.imageset/Group@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/CountDown.imageset/Group@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/CountDown.imageset/Group@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/CountDown.imageset/Group@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/Interval.imageset/Group 2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Interval.imageset/Group 2@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/Interval.imageset/Group 2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Interval.imageset/Group 2@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/LinkToShop.imageset/Shape@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/LinkToShop.imageset/Shape@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/LinkToShop.imageset/Shape@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/LinkToShop.imageset/Shape@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/Questions.imageset/Questions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Questions.imageset/Questions.png -------------------------------------------------------------------------------- /Assets.xcassets/Recording.imageset/Recording.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Recording.imageset/Recording.png -------------------------------------------------------------------------------- /Assets.xcassets/Standard.imageset/Shape@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Standard.imageset/Shape@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/Standard.imageset/Shape@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Standard.imageset/Shape@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/TileAddRep.imageset/Group 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/TileAddRep.imageset/Group 2.png -------------------------------------------------------------------------------- /Assets.xcassets/backButton.imageset/Shape@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/backButton.imageset/Shape@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/backButton.imageset/Shape@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/backButton.imageset/Shape@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/overlayLogo.imageset/bg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/overlayLogo.imageset/bg@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/overlayLogo.imageset/bg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/overlayLogo.imageset/bg@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/AppIcon 5-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/AppIcon.appiconset/AppIcon 5-6.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/appIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/AppIcon.appiconset/appIcon@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/FlipCamera.imageset/FlipCamera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/FlipCamera.imageset/FlipCamera.png -------------------------------------------------------------------------------- /Assets.xcassets/TileAddRep.imageset/Group 2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/TileAddRep.imageset/Group 2@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/TileAddRep.imageset/Group 2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/TileAddRep.imageset/Group 2@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/AppIcon 5-6@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/AppIcon.appiconset/AppIcon 5-6@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/FlipCamera.imageset/FlipCamera@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/FlipCamera.imageset/FlipCamera@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/FlipCamera.imageset/FlipCamera@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/FlipCamera.imageset/FlipCamera@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/Questions.imageset/Questions@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Questions.imageset/Questions@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/Questions.imageset/Questions@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Questions.imageset/Questions@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/RecordDelay.imageset/RecordDelay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/RecordDelay.imageset/RecordDelay.png -------------------------------------------------------------------------------- /Assets.xcassets/Recording.imageset/Recording@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Recording.imageset/Recording@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/Recording.imageset/Recording@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Recording.imageset/Recording@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/Rectangle 7.imageset/Rectangle 7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Rectangle 7.imageset/Rectangle 7.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/App Icon 7-9@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/AppIcon.appiconset/App Icon 7-9@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/RecordButton.imageset/RecordButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/RecordButton.imageset/RecordButton.png -------------------------------------------------------------------------------- /Assets.xcassets/RecordDelay.imageset/RecordDelay@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/RecordDelay.imageset/RecordDelay@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/RecordDelay.imageset/RecordDelay@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/RecordDelay.imageset/RecordDelay@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/Rectangle 7.imageset/Rectangle 7@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Rectangle 7.imageset/Rectangle 7@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/Rectangle 7.imageset/Rectangle 7@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/Rectangle 7.imageset/Rectangle 7@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/TileAddRound.imageset/TileAddRound.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/TileAddRound.imageset/TileAddRound.png -------------------------------------------------------------------------------- /Assets.xcassets/cancelButton.imageset/Cancel button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/cancelButton.imageset/Cancel button.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Spot Light 7-9@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/AppIcon.appiconset/Spot Light 7-9@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Spot Light 7-9@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/AppIcon.appiconset/Spot Light 7-9@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Spot Light Icon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/AppIcon.appiconset/Spot Light Icon-1.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Spot Light Icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/AppIcon.appiconset/Spot Light Icon@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Spot Light Icon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/AppIcon.appiconset/Spot Light Icon@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/RecordButton.imageset/RecordButton@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/RecordButton.imageset/RecordButton@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/RecordButton.imageset/RecordButton@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/RecordButton.imageset/RecordButton@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/TileAddRound.imageset/TileAddRound@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/TileAddRound.imageset/TileAddRound@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/TileAddRound.imageset/TileAddRound@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/TileAddRound.imageset/TileAddRound@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/cancelButton.imageset/Cancel button@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/cancelButton.imageset/Cancel button@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/cancelButton.imageset/Cancel button@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/cancelButton.imageset/Cancel button@3x.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Spot Light Icon@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/AppIcon.appiconset/Spot Light Icon@2x-1.png -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Spot Light Icon@3x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/AppIcon.appiconset/Spot Light Icon@3x-1.png -------------------------------------------------------------------------------- /Assets.xcassets/HighVideoQuality.imageset/HighVideoQuality.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/HighVideoQuality.imageset/HighVideoQuality.png -------------------------------------------------------------------------------- /Assets.xcassets/HighVideoQuality.imageset/HighVideoQuality@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/HighVideoQuality.imageset/HighVideoQuality@2x.png -------------------------------------------------------------------------------- /Assets.xcassets/HighVideoQuality.imageset/HighVideoQuality@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Assets.xcassets/HighVideoQuality.imageset/HighVideoQuality@3x.png -------------------------------------------------------------------------------- /Wodeo 2.xcodeproj/xcuserdata/GazLong.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /Wodeo 2.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Wodeo 2.xcodeproj/project.xcworkspace/xcuserdata/GazLong.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrk-9/iOS-app_Wodeo-2/HEAD/Wodeo 2.xcodeproj/project.xcworkspace/xcuserdata/GazLong.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Assets.xcassets/Group 2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Group 2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Assets.xcassets/8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "8.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "8@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "8@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/BG.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Group.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Group@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Group@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/overlayLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "bg.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "bg@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "bg@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/BGLogo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "BGLogo.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "BGLogo@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "BGLogo@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/CountDown.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Group.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Group@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Group@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/LinkToShop.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Shape.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Shape@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Shape@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/Standard.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Shape.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Shape@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Shape@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/backButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Shape.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Shape@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Shape@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/Interval.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Group 2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Group 2@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Group 2@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/TileAddRep.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Group 2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Group 2@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Group 2@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/Questions.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Questions.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Questions@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Questions@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/Recording.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Recording.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Recording@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Recording@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/FlipCamera.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "FlipCamera.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "FlipCamera@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "FlipCamera@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/RecordDelay.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "RecordDelay.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "RecordDelay@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "RecordDelay@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/Rectangle 7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Rectangle 7.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Rectangle 7@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Rectangle 7@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/RecordButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "RecordButton.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "RecordButton@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "RecordButton@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/TileAddRound.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "TileAddRound.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "TileAddRound@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "TileAddRound@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/cancelButton.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Cancel button.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Cancel button@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Cancel button@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Assets.xcassets/HighVideoQuality.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "HighVideoQuality.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "HighVideoQuality@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "HighVideoQuality@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /RoundedView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundedView.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 15/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class RoundedView: UIView { 12 | 13 | override init(frame: CGRect) { 14 | super.init(frame: frame) 15 | setUp() 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | super.init(coder: aDecoder) 20 | setUp() 21 | } 22 | 23 | func setUp(){ 24 | layer.cornerRadius = 4.0 25 | layer.masksToBounds = true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Wodeo 2.xcodeproj/xcuserdata/GazLong.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Wodeo 2.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 9C1F63521C655F41008954A0 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /BorderButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BorderButton.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 29/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BorderButton: UIButton { 12 | 13 | override init(frame: CGRect) { 14 | super.init(frame: frame) 15 | commonInit() 16 | } 17 | 18 | required init?(coder aDecoder: NSCoder) { 19 | super.init(coder: aDecoder) 20 | commonInit() 21 | } 22 | 23 | 24 | func commonInit(){ 25 | 26 | layer.borderColor = UIColor(red: 240/255.0, green: 178/255.0, blue: 71/255.0, alpha: 1.0).CGColor 27 | layer.borderWidth = 1.5 28 | layer.cornerRadius = 5.0 29 | layer.masksToBounds = true 30 | } 31 | 32 | 33 | } -------------------------------------------------------------------------------- /TimeTextLabel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeTextLabel.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 19/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TimeTextLabel: CATextLayer { 12 | 13 | override init(layer: AnyObject) { 14 | super.init(layer: layer) 15 | } 16 | 17 | init(fontSize:CGFloat) { 18 | super.init() 19 | font = "Folio-BoldCondensed" 20 | self.fontSize = fontSize 21 | alignmentMode = kCAAlignmentLeft 22 | foregroundColor = UIColor(red: 243/255.0, green: 80/255.0, blue: 47/255.0, alpha: 1.0).CGColor 23 | backgroundColor = UIColor.clearColor().CGColor 24 | } 25 | 26 | required init?(coder aDecoder: NSCoder) { 27 | super.init(coder: aDecoder) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /MainMenuCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainMenuCell.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 15/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MainMenuCell: UITableViewCell { 12 | 13 | @IBOutlet weak var cellImage:UIImageView! 14 | @IBOutlet weak var title:UILabel! 15 | @IBOutlet weak var subTitle:UILabel! 16 | @IBOutlet weak var fadedBackgrondView:UIView! 17 | 18 | 19 | let backGroundView = UIView() 20 | 21 | 22 | override func awakeFromNib() { 23 | super.awakeFromNib() 24 | fadedBackgrondView.layer.cornerRadius = 15.0 25 | fadedBackgrondView.layer.masksToBounds = true 26 | 27 | 28 | let bgv = UIView(frame:bounds) 29 | bgv.layer.cornerRadius = 15.0 30 | bgv.backgroundColor = UIColor(red: 204/255.0, green: 204/255.0, blue: 204/255.0, alpha: 0.4) 31 | 32 | selectedBackgroundView = bgv 33 | 34 | } 35 | 36 | required init?(coder aDecoder: NSCoder) { 37 | super.init(coder: aDecoder) 38 | 39 | 40 | 41 | 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /WodeoProducts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WodeoProducts.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 28/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // Use enum as a simple namespace. (It has no cases so you can't instantiate it.) 12 | public enum WodeoProducts { 13 | 14 | /// TODO: Change this to whatever you set on iTunes connect 15 | private static let Prefix = "co.uk.ballisticstash.wodeo2." 16 | 17 | /// MARK: - Supported Product Identifiers 18 | public static let moreVideo = Prefix + "exvideo" 19 | 20 | 21 | // All of the products assembled into a set of product identifiers. 22 | private static let productIdentifiers: Set = [WodeoProducts.moreVideo] 23 | 24 | 25 | /// Static instance of IAPHelper that for rage products. 26 | public static let store = IAPHelper(productIdentifiers: WodeoProducts.productIdentifiers) 27 | } 28 | 29 | /// Return the resourcename for the product identifier. 30 | func resourceNameForProductIdentifier(productIdentifier: String) -> String? { 31 | return productIdentifier.componentsSeparatedByString(".").last 32 | } 33 | -------------------------------------------------------------------------------- /WodeoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WodeoViewController.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 16/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | class WodeoViewController: UIViewController { 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | 17 | 18 | 19 | 20 | } 21 | 22 | override func didReceiveMemoryWarning() { 23 | super.didReceiveMemoryWarning() 24 | // Dispose of any resources that can be recreated. 25 | } 26 | 27 | func orientationChanged() { 28 | 29 | } 30 | 31 | func currentOrientation() -> UIDeviceOrientation { 32 | 33 | switch UIDevice.currentDevice().orientation { 34 | case .LandscapeLeft: 35 | return .LandscapeRight 36 | case .LandscapeRight: 37 | return .LandscapeLeft 38 | case .PortraitUpsideDown: 39 | return .PortraitUpsideDown 40 | default: 41 | return .Portrait 42 | } 43 | } 44 | 45 | override func prefersStatusBarHidden() -> Bool { 46 | return true 47 | } 48 | 49 | override func shouldAutorotate() -> Bool { 50 | return false 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RotatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RotatorView.swift 3 | // Video Testing 4 | // 5 | // Created by Gareth Long on 12/02/2016. 6 | // Copyright © 2016 gazlongapps. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class RotatorView: UIView { 12 | 13 | func rotateViewsToMatchOrientation(orientation:UIDeviceOrientation){ 14 | 15 | let animationDuration = 0.5 16 | var rotation:CGFloat = 0.0 17 | 18 | switch orientation { 19 | 20 | case.LandscapeLeft:rotation = CGFloat(-M_PI_2) 21 | case.LandscapeRight:rotation = CGFloat(M_PI_2) 22 | case.Portrait: rotation = 0.0 23 | case.PortraitUpsideDown:rotation = CGFloat(2 * M_PI_2) 24 | default: rotation = 0.0 25 | } 26 | 27 | 28 | for childView in self.subviews { 29 | 30 | 31 | if !childView.isKindOfClass(UILabel) { 32 | UIView.animateWithDuration(animationDuration, delay: 0.0, usingSpringWithDamping:1.0, initialSpringVelocity: 2.0, options:.TransitionNone, animations:{ 33 | 34 | childView.transform = CGAffineTransformMakeRotation(rotation) 35 | 36 | }, completion:nil) 37 | } 38 | 39 | 40 | } 41 | 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /BallicticStashView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BallicticStashView.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 15/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol BallicticStashDelegate { 12 | func ballisticStachDidReturn() 13 | } 14 | 15 | class BallicticStashView: UIViewController,UIWebViewDelegate { 16 | 17 | @IBOutlet weak var webView: UIWebView! 18 | 19 | var delegate:BallicticStashDelegate? 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | let url = NSURL(string:"http://www.ballisticstash.co.uk/?v=79cba1185463") 25 | 26 | let request = NSURLRequest(URL:url!) 27 | 28 | webView.loadRequest(request) 29 | 30 | 31 | 32 | // Do any additional setup after loading the view. 33 | } 34 | @IBAction func backButtonPressed(sender: AnyObject) { 35 | delegate!.ballisticStachDidReturn() 36 | } 37 | 38 | func webViewDidFinishLoad(webView: UIWebView) { 39 | 40 | UIView.animateWithDuration(0.6, animations: { 41 | webView.alpha = 1.0 42 | }) 43 | 44 | } 45 | 46 | 47 | 48 | override func didReceiveMemoryWarning() { 49 | super.didReceiveMemoryWarning() 50 | // Dispose of any resources that can be recreated. 51 | } 52 | 53 | 54 | /* 55 | // MARK: - Navigation 56 | 57 | // In a storyboard-based application, you will often want to do a little preparation before navigation 58 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 59 | // Get the new view controller using segue.destinationViewController. 60 | // Pass the selected object to the new view controller. 61 | } 62 | */ 63 | 64 | override func prefersStatusBarHidden() -> Bool { 65 | return true 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /CountDownWOD.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | class CountDownWOD:WOD , StopWatchDelegate{ 4 | 5 | var stopWatch:StopWatch 6 | var rounds = [Round]() 7 | var totalWorkOutSeconds = TimeInterval() 8 | var countDownFromTime:Int = 0 9 | 10 | 11 | override init(with name: String) { 12 | stopWatch = StopWatch(type: StopWatchType.CountDown) 13 | super.init(with: name) 14 | 15 | 16 | } 17 | 18 | func startWorkOut() { 19 | 20 | stopWatch.delegate = self 21 | stopWatch.start() 22 | addRound() 23 | 24 | } 25 | 26 | func stopWorkOut(){ 27 | totalWorkOutSeconds = stopWatch.currentDuration 28 | stopWatch.stop() 29 | 30 | } 31 | 32 | 33 | 34 | func addRound(){ 35 | rounds.append(Round(startTime:stopWatch.currentDuration, roundNumber: rounds.count + 1)) 36 | } 37 | 38 | func addRep(){ 39 | //Get last round reps 40 | let cRound = rounds.last 41 | let timeStamp = stopWatch.currentDuration 42 | 43 | cRound!.reps.append(Rep(startTime:timeStamp, repNumber:cRound!.reps.count + 1)) 44 | } 45 | 46 | private func totalReps() -> Int { 47 | 48 | var repCount = 0 49 | for round in rounds { 50 | repCount += round.reps.count 51 | } 52 | return repCount 53 | 54 | } 55 | 56 | func setCountDownTime(time:Int){ 57 | stopWatch.countDownFromSeconds = time 58 | } 59 | 60 | //Stop watch delegate 61 | func stopWatchDidUpdateWithTimeInterval(t: TimeInterval) { 62 | delegate.wodStopWatchDidUpdateToValue(t.intervalString(false)) 63 | 64 | } 65 | 66 | func stopWatchDidCountDown(t: TimeInterval) { 67 | delegate.workOutDidEnd() 68 | } 69 | 70 | func stopWatchInLastThreeSeconds(t: TimeInterval) { 71 | 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /StandardWOD.swift: -------------------------------------------------------------------------------- 1 | 2 | 3 | class StandardWOD: WOD,StopWatchDelegate{ 4 | 5 | 6 | //Stop watch to time it 7 | var stopWatch:StopWatch 8 | var rounds = [Round]() 9 | var totalWorkOutSeconds = TimeInterval() 10 | 11 | 12 | override init(with name: String) { 13 | stopWatch = StopWatch(type: StopWatchType.CountUp) 14 | super.init(with: name) 15 | } 16 | 17 | 18 | //Public functions 19 | func startWorkOut() { 20 | stopWatch.delegate = self 21 | stopWatch.start() 22 | addRound() 23 | } 24 | 25 | func stopWorkOut(){ 26 | totalWorkOutSeconds = stopWatch.currentDuration 27 | stopWatch.stop() 28 | 29 | } 30 | 31 | //Work out data 32 | func addRound(){ 33 | let cDuration = stopWatch.currentDuration 34 | 35 | if let round = rounds.last { 36 | round.endTime = cDuration 37 | } 38 | 39 | rounds.append(Round(startTime:cDuration, roundNumber: rounds.count + 1)) 40 | } 41 | 42 | func addRep(){ 43 | //Get last round reps 44 | let cRound = rounds.last 45 | let timeStamp = stopWatch.currentDuration 46 | 47 | cRound!.reps.append(Rep(startTime:timeStamp, repNumber:cRound!.reps.count + 1)) 48 | } 49 | 50 | private func totalReps() -> Int { 51 | 52 | var repCount = 0 53 | for round in rounds { 54 | repCount += round.reps.count 55 | } 56 | return repCount 57 | 58 | } 59 | 60 | //Stop watch delegate 61 | func stopWatchDidUpdateWithTimeInterval(t: TimeInterval) { 62 | workOutTime = t.secondsAsDouble! 63 | delegate.wodStopWatchDidUpdateToValue(t.intervalString(false)) 64 | 65 | } 66 | 67 | func stopWatchDidCountDown(t: TimeInterval) { 68 | 69 | } 70 | 71 | func stopWatchInLastThreeSeconds(t: TimeInterval) { 72 | 73 | } 74 | 75 | 76 | } 77 | 78 | -------------------------------------------------------------------------------- /Wodeo 2/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 | APPL 17 | CFBundleShortVersionString 18 | 1.1 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 2 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSExceptionDomains 28 | 29 | www.ballisticstash.co.uk 30 | 31 | NSExceptionAllowsInsecureHTTPLoads 32 | 33 | NSExceptionMinimumTLSVersion 34 | TLSv1.1 35 | NSIncludesSubdomains 36 | 37 | 38 | 39 | 40 | UIAppFonts 41 | 42 | capture_it.ttf 43 | Capsmall.ttf 44 | 45 | UILaunchStoryboardName 46 | LaunchScreen 47 | UIMainStoryboardFile 48 | Main 49 | UIRequiredDeviceCapabilities 50 | 51 | armv7 52 | 53 | UIStatusBarHidden 54 | 55 | UISupportedInterfaceOrientations 56 | 57 | UIInterfaceOrientationPortrait 58 | 59 | UISupportedInterfaceOrientations~ipad 60 | 61 | UIInterfaceOrientationPortrait 62 | UIInterfaceOrientationPortraitUpsideDown 63 | UIInterfaceOrientationLandscapeLeft 64 | UIInterfaceOrientationLandscapeRight 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /Wodeo 2/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 05/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | 20 | /* 21 | for family: String in UIFont.familyNames() 22 | { 23 | print("\(family)") 24 | for names: String in UIFont.fontNamesForFamilyName(family) 25 | { 26 | print("== \(names)") 27 | } 28 | } 29 | */ 30 | 31 | 32 | return true 33 | 34 | } 35 | 36 | func applicationWillResignActive(application: UIApplication) { 37 | // 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. 38 | // 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. 39 | } 40 | 41 | func applicationDidEnterBackground(application: UIApplication) { 42 | // 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. 43 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 44 | } 45 | 46 | func applicationWillEnterForeground(application: UIApplication) { 47 | // 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. 48 | } 49 | 50 | func applicationDidBecomeActive(application: UIApplication) { 51 | // 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. 52 | } 53 | 54 | func applicationWillTerminate(application: UIApplication) { 55 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 56 | } 57 | 58 | 59 | 60 | } 61 | 62 | -------------------------------------------------------------------------------- /WodeoModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WodeoModel.swift 3 | // timerTesting 4 | // 5 | // Created by Gareth Long on 13/02/2016. 6 | // Copyright © 2016 gazlongapps. All rights reserved. 7 | // 8 | 9 | /* 10 | To be subclassed for each type of work out. 11 | 12 | */ 13 | 14 | 15 | 16 | import Foundation 17 | 18 | protocol WODDelegate { 19 | func wodStopWatchDidUpdateToValue(timeValue:String) 20 | func workOutDidEnd() 21 | } 22 | 23 | class Rep { 24 | 25 | var startTime:TimeInterval 26 | var endTime:TimeInterval? 27 | let repNumber:Int! 28 | 29 | init(startTime:TimeInterval,repNumber:Int){ 30 | self.startTime = startTime 31 | self.repNumber = repNumber 32 | } 33 | 34 | } 35 | 36 | class Round { 37 | 38 | var startTime:TimeInterval! 39 | var endTime:TimeInterval? 40 | var reps = [Rep]() 41 | var roundNumber:Int = 0 42 | 43 | 44 | init(startTime:TimeInterval,roundNumber:Int){ 45 | self.startTime = startTime 46 | self.roundNumber = roundNumber 47 | } 48 | 49 | //func title() -> String { 50 | 51 | // } 52 | 53 | func titleForRepAtIndex(index:Int) -> String { 54 | let repStartTime = reps[index].startTime 55 | var repEndTime = TimeInterval(secondsAsDouble:0.0) 56 | 57 | if index == reps.count - 1 { 58 | //Its the last rep so use the round end time 59 | if let et = endTime{ 60 | repEndTime = et 61 | }else{ 62 | repEndTime = TimeInterval(secondsAsDouble:0.0) 63 | } 64 | }else{ 65 | repEndTime = reps[index + 1].startTime 66 | } 67 | 68 | return "Rep: \(index + 1) (\(repStartTime.intervalString(true)) - \(repEndTime.intervalString(true)))" 69 | 70 | } 71 | } 72 | 73 | class PresetRound: Round { 74 | 75 | var presetDuration:Int! 76 | var loopNumber:Int! 77 | 78 | init(roundNumber:Int,presetDuration:Int,loopNumber:Int){ 79 | super.init(startTime:TimeInterval(secondsAsDouble: 0.0), roundNumber:roundNumber) 80 | self.presetDuration = presetDuration 81 | self.loopNumber = loopNumber 82 | } 83 | } 84 | 85 | 86 | class WOD { 87 | 88 | var totalVideoSecondsAllowed:Double = UserSettings.sharedInstance.getPurchasedVideo() ? 100000.0:300.0 89 | 90 | //Public variables 91 | var name:String 92 | var startDate:NSDate 93 | var countDownTime:Int = 0 94 | var delegate:WODDelegate! 95 | var workOutTime:Double = 0.0 96 | 97 | init(with name:String) { 98 | self.name = name 99 | self.startDate = NSDate() 100 | 101 | } 102 | 103 | } 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /InfoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InfoViewController.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 16/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class InfoViewController: SpotlightViewController { 12 | 13 | @IBOutlet var annotationViews: [UIView]! 14 | @IBOutlet var rotatorView:RotatorView! 15 | 16 | var stepIndex: Int = 0 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(InfoViewController.orientationChanged), name: UIDeviceOrientationDidChangeNotification, object: nil) 21 | delegate = self 22 | } 23 | 24 | func next(labelAnimated: Bool) { 25 | updateAnnotationView(labelAnimated) 26 | 27 | let screenSize = UIScreen.mainScreen().bounds.size 28 | switch stepIndex { 29 | case 0: 30 | spotlightView.appear(Spotlight.Oval(center: CGPointMake(screenSize.width - 34,30), diameter: 60)) 31 | case 1: 32 | spotlightView.move(Spotlight.Oval(center: CGPointMake(32,28), diameter: 60)) 33 | case 2: 34 | spotlightView.move(Spotlight.Oval(center: CGPointMake(screenSize.width - 45,screenSize.height - 50), diameter: 60)) 35 | case 3: 36 | dismissViewControllerAnimated(true, completion: nil) 37 | default: 38 | break 39 | } 40 | 41 | stepIndex += 1 42 | } 43 | 44 | func updateAnnotationView(animated: Bool) { 45 | annotationViews.enumerate().forEach { index, view in 46 | UIView .animateWithDuration(animated ? 0.25 : 0) { 47 | view.alpha = index == self.stepIndex ? 1 : 0 48 | } 49 | } 50 | } 51 | 52 | 53 | func orientationChanged() { 54 | self.rotatorView.rotateViewsToMatchOrientation(currentOrientation()) 55 | } 56 | 57 | func currentOrientation() -> UIDeviceOrientation { 58 | 59 | switch UIDevice.currentDevice().orientation { 60 | case .LandscapeLeft: 61 | return .LandscapeRight 62 | case .LandscapeRight: 63 | return .LandscapeLeft 64 | case .PortraitUpsideDown: 65 | return .PortraitUpsideDown 66 | default: 67 | return .Portrait 68 | } 69 | } 70 | 71 | override func prefersStatusBarHidden() -> Bool { 72 | return true 73 | } 74 | 75 | } 76 | 77 | extension InfoViewController: SpotlightViewControllerDelegate { 78 | func spotlightViewControllerWillPresent(viewController: SpotlightViewController, animated: Bool) { 79 | next(false) 80 | } 81 | 82 | func spotlightViewControllerTapped(viewController: SpotlightViewController, isInsideSpotlight: Bool) { 83 | next(true) 84 | } 85 | 86 | func spotlightViewControllerWillDismiss(viewController: SpotlightViewController, animated: Bool) { 87 | spotlightView.disappear() 88 | } 89 | } 90 | 91 | -------------------------------------------------------------------------------- /IntervalWOD.swift: -------------------------------------------------------------------------------- 1 | 2 | protocol IntervalWOD_Delegate { 3 | func intervalWODDidMoveToRoundWithName(name:String) 4 | } 5 | 6 | class IntervalWOD:WOD,StopWatchDelegate { 7 | 8 | 9 | var rounds = [PresetRound]() 10 | var stopWatch:StopWatch! 11 | var repStopWatch:StopWatch! 12 | var roundCounter:Int = 0 13 | var loopCounter:Int = 1 14 | var loops:Int = 1 15 | 16 | var intervalDelegate:IntervalWOD_Delegate? 17 | 18 | override init(with name: String) { 19 | stopWatch = StopWatch(type: StopWatchType.CountDown) 20 | repStopWatch = StopWatch(type: StopWatchType.CountUp) 21 | super.init(with: name) 22 | } 23 | 24 | func startWorkOut(){ 25 | repStopWatch.start() 26 | stopWatch.delegate = self 27 | nextInterval() 28 | } 29 | 30 | func stopWorkOut() { 31 | repStopWatch.stop() 32 | stopWatch.stop() 33 | } 34 | 35 | func addRep() -> String { 36 | //Get current round 37 | let round = rounds[roundCounter - 1] 38 | round.reps.append(Rep(startTime: repStopWatch.currentDuration, repNumber: round.reps.count + 1)) 39 | 40 | return "Rep: \(round.reps.count)" 41 | } 42 | 43 | private func nextInterval(){ 44 | 45 | stopWatch.stop() 46 | 47 | 48 | if roundCounter < rounds.count { 49 | 50 | if let it = intervalDelegate { 51 | it.intervalWODDidMoveToRoundWithName("Loop \(rounds[roundCounter].loopNumber) Round \(roundCounter + 1) of \(rounds.count)") 52 | } 53 | 54 | 55 | stopWatch.countDownFromSeconds = rounds[roundCounter].presetDuration 56 | roundCounter += 1 57 | stopWatch.start() 58 | }else{ 59 | delegate.workOutDidEnd() 60 | } 61 | } 62 | 63 | func totalWorkOutSeconds() -> Int { 64 | var retTotal = 0 65 | for r in rounds { 66 | retTotal += r.presetDuration 67 | } 68 | 69 | return retTotal 70 | } 71 | 72 | //Stop watch delegate 73 | func stopWatchDidCountDown(t: TimeInterval) { 74 | 75 | nextInterval() 76 | } 77 | 78 | func stopWatchDidUpdateWithTimeInterval(t: TimeInterval) { 79 | delegate.wodStopWatchDidUpdateToValue(t.intervalString(false)) 80 | } 81 | 82 | func stringForRound(index:Int,andRep:Int) -> String { 83 | 84 | let loopNumber = rounds[index].loopNumber 85 | let maxNumberOfLoops = rounds.last!.loopNumber 86 | 87 | let roundNumber = rounds[index].roundNumber 88 | let maxNumberOfRounds = rounds.count 89 | 90 | let repNumber = andRep + 1 91 | let maxReps = rounds[index].reps.count 92 | 93 | return "Loop \(loopNumber) of \(maxNumberOfLoops) | Round \(roundNumber) of \(maxNumberOfRounds) | Rep \(repNumber) of \(maxReps)" 94 | 95 | 96 | 97 | 98 | } 99 | 100 | 101 | 102 | } 103 | -------------------------------------------------------------------------------- /TabataResultsVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabataResultsVC.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 27/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | 13 | class TabataResultsVC: ResultsViewController { 14 | 15 | var tabataWOD:TabataWOD! 16 | 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | } 22 | 23 | override func viewWillAppear(animated: Bool) { 24 | 25 | workOutName.text = tabataWOD.name 26 | 27 | let formater = NSDateFormatter() 28 | formater.dateStyle = .FullStyle 29 | workOutDate.text = formater.stringFromDate(tabataWOD.startDate) 30 | 31 | } 32 | 33 | //Table view dataSource Methods 34 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 35 | return tabataWOD.rounds.count 36 | } 37 | 38 | func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 39 | return "Round \(section + 1)" 40 | } 41 | 42 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 43 | return tabataWOD.rounds[section].reps.count 44 | } 45 | 46 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 47 | 48 | let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) 49 | 50 | cell.textLabel!.text = tabataWOD.rounds[indexPath.section].titleForRepAtIndex(indexPath.row) 51 | 52 | return cell 53 | } 54 | 55 | func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 56 | return 50.0 57 | } 58 | 59 | func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool { 60 | return false 61 | 62 | } 63 | 64 | func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 65 | let titleLabel = UILabel(frame:CGRectMake(0.0,0.0,300.0,70.0)) 66 | titleLabel.font = UIFont(name:"Folio-BoldCondensed", size: 30.0) 67 | titleLabel.text = self.tableView(tableView, titleForHeaderInSection: section) 68 | titleLabel.textColor = UIColor(red: 240/255.0, green: 240/255.0, blue: 240/255.0, alpha: 1.0) 69 | 70 | let contView = UIView(frame: titleLabel.frame) 71 | contView.addSubview(titleLabel) 72 | 73 | return contView 74 | } 75 | 76 | override func applyVideoEffectsToComposition(composition: AVMutableVideoComposition, size: CGSize) { 77 | 78 | super.applyVideoEffectsToComposition(composition, size: size) 79 | 80 | overlayLayer.addSublayer(layerGenerator.timerView(size, forSeconds:tabataWOD.totalWorkOutSeconds(), withDelay: tabataWOD.countDownTime,countUp: false)) 81 | 82 | 83 | overlayLayer.addSublayer(layerGenerator.tabataWorkOutInfo(size, workOut: tabataWOD, withDelay: tabataWOD.countDownTime)) 84 | 85 | 86 | 87 | 88 | parentLayer.addSublayer(overlayLayer) 89 | composition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, inLayer: parentLayer) 90 | 91 | 92 | 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /IntervalResultsVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntervalResultsVC.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 26/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | 13 | class IntervalResultsVC: ResultsViewController { 14 | 15 | var intervalWOD:IntervalWOD! 16 | 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | } 22 | 23 | override func viewWillAppear(animated: Bool) { 24 | 25 | workOutName.text = intervalWOD.name 26 | 27 | let formater = NSDateFormatter() 28 | formater.dateStyle = .FullStyle 29 | workOutDate.text = formater.stringFromDate(intervalWOD.startDate) 30 | 31 | } 32 | 33 | //Table view dataSource Methods 34 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 35 | return intervalWOD.rounds.count 36 | } 37 | 38 | func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 39 | return "Round \(section + 1)" 40 | } 41 | 42 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 43 | return intervalWOD.rounds[section].reps.count 44 | } 45 | 46 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 47 | 48 | let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) 49 | 50 | cell.textLabel!.text = intervalWOD.rounds[indexPath.section].titleForRepAtIndex(indexPath.row) 51 | 52 | return cell 53 | } 54 | 55 | func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 56 | return 50.0 57 | } 58 | 59 | func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool { 60 | return false 61 | 62 | } 63 | 64 | func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 65 | let titleLabel = UILabel(frame:CGRectMake(0.0,0.0,300.0,70.0)) 66 | titleLabel.font = UIFont(name:"Folio-BoldCondensed", size: 30.0) 67 | titleLabel.text = self.tableView(tableView, titleForHeaderInSection: section) 68 | titleLabel.textColor = UIColor(red: 240/255.0, green: 240/255.0, blue: 240/255.0, alpha: 1.0) 69 | 70 | let contView = UIView(frame: titleLabel.frame) 71 | contView.addSubview(titleLabel) 72 | 73 | return contView 74 | } 75 | 76 | override func applyVideoEffectsToComposition(composition: AVMutableVideoComposition, size: CGSize) { 77 | 78 | super.applyVideoEffectsToComposition(composition, size: size) 79 | 80 | overlayLayer.addSublayer(layerGenerator.timerView(size, forSeconds:intervalWOD.totalWorkOutSeconds(), withDelay: intervalWOD.countDownTime,countUp: false)) 81 | 82 | 83 | overlayLayer.addSublayer(layerGenerator.intervalWorkOutInfo(size, workOut: intervalWOD, withDelay: intervalWOD.countDownTime)) 84 | 85 | 86 | 87 | 88 | parentLayer.addSublayer(overlayLayer) 89 | composition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, inLayer: parentLayer) 90 | 91 | 92 | 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /StandardResultsVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StandardResultsVC.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 19/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | class StandardResultsVC: ResultsViewController { 13 | 14 | var standardWorkout:StandardWOD! 15 | 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | } 21 | 22 | override func viewWillAppear(animated: Bool) { 23 | 24 | workOutName.text = standardWorkout.name 25 | 26 | let formater = NSDateFormatter() 27 | formater.dateStyle = .FullStyle 28 | workOutDate.text = formater.stringFromDate(standardWorkout.startDate) 29 | 30 | } 31 | 32 | //Table view dataSource Methods 33 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 34 | return standardWorkout.rounds.count 35 | } 36 | 37 | func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 38 | return "Round \(section + 1)" 39 | } 40 | 41 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 42 | return standardWorkout.rounds[section].reps.count 43 | } 44 | 45 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 46 | 47 | let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) 48 | 49 | cell.textLabel!.text = standardWorkout.rounds[indexPath.section].titleForRepAtIndex(indexPath.row) 50 | 51 | return cell 52 | } 53 | 54 | func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 55 | return 50.0 56 | } 57 | 58 | func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool { 59 | return false 60 | } 61 | 62 | 63 | func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 64 | let titleLabel = UILabel(frame:CGRectMake(0.0,0.0,300.0,70.0)) 65 | titleLabel.font = UIFont(name:"Folio-BoldCondensed", size: 30.0) 66 | titleLabel.text = self.tableView(tableView, titleForHeaderInSection: section) 67 | titleLabel.textColor = UIColor(red: 240/255.0, green: 240/255.0, blue: 240/255.0, alpha: 1.0) 68 | 69 | let contView = UIView(frame: titleLabel.frame) 70 | contView.addSubview(titleLabel) 71 | 72 | return contView 73 | } 74 | 75 | override func applyVideoEffectsToComposition(composition: AVMutableVideoComposition, size: CGSize) { 76 | 77 | super.applyVideoEffectsToComposition(composition, size: size) 78 | 79 | overlayLayer.addSublayer(layerGenerator.timerView(size, forSeconds:Int(standardWorkout.totalWorkOutSeconds.secondsAsDouble!), withDelay: standardWorkout.countDownTime,countUp: true)) 80 | 81 | overlayLayer.addSublayer(layerGenerator.standardWorkOutInfo(size, workOut:standardWorkout,withDelay:standardWorkout.countDownTime)) 82 | 83 | parentLayer.addSublayer(overlayLayer) 84 | composition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, inLayer: parentLayer) 85 | 86 | 87 | 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /CountDownResultsVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CountDownResultsVC.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 22/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | 13 | class CountDownResultsVC: ResultsViewController { 14 | 15 | var countDownWorkout:CountDownWOD! 16 | 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | } 22 | 23 | override func viewWillAppear(animated: Bool) { 24 | 25 | workOutName.text = countDownWorkout.name 26 | 27 | let formater = NSDateFormatter() 28 | formater.dateStyle = .FullStyle 29 | workOutDate.text = formater.stringFromDate(countDownWorkout.startDate) 30 | 31 | } 32 | 33 | //Table view dataSource Methods 34 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 35 | return countDownWorkout.rounds.count 36 | } 37 | 38 | func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 39 | return "Round \(section + 1)" 40 | } 41 | 42 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 43 | return countDownWorkout.rounds[section].reps.count 44 | } 45 | 46 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 47 | 48 | let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) 49 | 50 | cell.textLabel!.text = countDownWorkout.rounds[indexPath.section].titleForRepAtIndex(indexPath.row) 51 | 52 | return cell 53 | } 54 | 55 | func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { 56 | return 50.0 57 | } 58 | 59 | func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool { 60 | return false 61 | } 62 | 63 | func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 64 | let titleLabel = UILabel(frame:CGRectMake(0.0,0.0,300.0,70.0)) 65 | titleLabel.font = UIFont(name:"Folio-BoldCondensed", size: 30.0) 66 | titleLabel.text = self.tableView(tableView, titleForHeaderInSection: section) 67 | titleLabel.textColor = UIColor(red: 240/255.0, green: 240/255.0, blue: 240/255.0, alpha: 1.0) 68 | 69 | let contView = UIView(frame: titleLabel.frame) 70 | contView.addSubview(titleLabel) 71 | 72 | return contView 73 | } 74 | 75 | override func applyVideoEffectsToComposition(composition: AVMutableVideoComposition, size: CGSize) { 76 | 77 | super.applyVideoEffectsToComposition(composition, size: size) 78 | 79 | overlayLayer.addSublayer(layerGenerator.timerView(size, forSeconds:Int(countDownWorkout.totalWorkOutSeconds.secondsAsDouble!), withDelay: countDownWorkout.countDownTime,countUp: false)) 80 | 81 | overlayLayer.addSublayer(layerGenerator.countDownWorkOutInfo(size, workOut:countDownWorkout, withDelay:countDownWorkout.countDownTime)) 82 | 83 | 84 | parentLayer.addSublayer(overlayLayer) 85 | composition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, inLayer: parentLayer) 86 | 87 | 88 | 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /Spotlight.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Spotlight.swift 3 | // Gecco 4 | // 5 | // Created by yukiasai on 2016/01/17. 6 | // Copyright (c) 2016 yukiasai. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public protocol SpotlightType { 12 | var frame: CGRect { get } 13 | var center: CGPoint { get } 14 | var path: UIBezierPath { get } 15 | var infinitesmalPath: UIBezierPath { get } 16 | } 17 | 18 | public extension SpotlightType { 19 | var center: CGPoint { 20 | return CGPoint(x: frame.midX, y: frame.midY) 21 | } 22 | 23 | var infinitesmalPath: UIBezierPath { 24 | return UIBezierPath(roundedRect: CGRect(origin: center, size: CGSizeZero), cornerRadius: 0) 25 | } 26 | } 27 | 28 | public class Spotlight { 29 | public class Oval: SpotlightType { 30 | public var frame: CGRect 31 | public init(frame: CGRect) { 32 | self.frame = frame 33 | } 34 | 35 | public convenience init(center: CGPoint, diameter: CGFloat) { 36 | let frame = CGRectMake(center.x - diameter / 2, center.y - diameter / 2, diameter, diameter) 37 | self.init(frame: frame) 38 | } 39 | 40 | public convenience init(view: UIView, margin: CGFloat) { 41 | let origin = view.superview!.convertPoint(view.frame.origin, toCoordinateSpace: view.window!.screen.fixedCoordinateSpace) 42 | let center = CGPoint(x: origin.x + view.bounds.width / 2, y: origin.y + view.bounds.height / 2) 43 | let diameter = max(view.bounds.width, view.bounds.height) + margin * 2 44 | self.init(center: center, diameter: diameter) 45 | } 46 | 47 | public var path: UIBezierPath { 48 | return UIBezierPath(roundedRect: frame, cornerRadius: frame.width / 2) 49 | } 50 | } 51 | 52 | public class Rect: SpotlightType { 53 | public var frame: CGRect 54 | public init(frame: CGRect) { 55 | self.frame = frame 56 | } 57 | 58 | public init(center: CGPoint, size: CGSize) { 59 | let frame = CGRectMake(center.x - size.width / 2, center.y - size.height / 2, size.width, size.height) 60 | self.frame = frame 61 | } 62 | 63 | public init(view: UIView, margin: CGFloat) { 64 | let viewOrigin = view.superview!.convertPoint(view.frame.origin, toCoordinateSpace: view.window!.screen.fixedCoordinateSpace) 65 | let origin = CGPoint(x: viewOrigin.x - margin, y: viewOrigin.y - margin) 66 | let size = CGSize(width: view.bounds.width + margin * 2, height: view.bounds.height + margin * 2) 67 | self.frame = CGRect(origin: origin, size: size) 68 | } 69 | 70 | public var path: UIBezierPath { 71 | return UIBezierPath(roundedRect: frame, cornerRadius: 0) 72 | } 73 | } 74 | 75 | public class RoundedRect: Rect { 76 | public var cornerRadius: CGFloat 77 | public init(center: CGPoint, size: CGSize, cornerRadius: CGFloat) { 78 | self.cornerRadius = cornerRadius 79 | super.init(center: center, size: size) 80 | } 81 | 82 | public init(view: UIView, margin: CGFloat, cornerRadius: CGFloat) { 83 | self.cornerRadius = cornerRadius 84 | super.init(view: view, margin: margin) 85 | } 86 | 87 | public override var path: UIBezierPath { 88 | return UIBezierPath(roundedRect: frame, cornerRadius: cornerRadius) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Wodeo 2.xcodeproj/xcuserdata/GazLong.xcuserdatad/xcschemes/Wodeo 2.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 | -------------------------------------------------------------------------------- /UserSettings.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UserSettings.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 27/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | 14 | class UserSettings:NSObject { 15 | 16 | let UD_VideoDelay = "VideoDelay" 17 | let UD_VideoQuality = "VideoQuality" 18 | let UD_CameraFlip = "CameraFlip" 19 | let UD_StandardName = "StandardName" 20 | let UD_JudgeName = "JudgeName" 21 | 22 | //Products 23 | let UD_PurchasedVideo = "PerchacedVideo" 24 | let UD_userVideoMessageOptOut = "UserVideoMessageOptOut" 25 | let UD_lastTimeUserWasInformed = "LastTimeUserWasInformed" 26 | 27 | static let sharedInstance = UserSettings() 28 | let ud = NSUserDefaults.standardUserDefaults() 29 | 30 | 31 | //Video quality 32 | func saveVideoQuality(quality:VideoQuality){ 33 | ud.setObject(quality.rawValue, forKey: UD_VideoQuality) 34 | } 35 | 36 | func getVideoQuality() -> VideoQuality{ 37 | 38 | if let r = ud.objectForKey(UD_VideoQuality){ 39 | if let q = VideoQuality(rawValue:r as! String){ 40 | return q 41 | }else{ 42 | return VideoQuality.High 43 | } 44 | }else{ 45 | return VideoQuality.High 46 | } 47 | 48 | 49 | } 50 | 51 | //Delay Settings 52 | func saveDelay(delay:Int){ 53 | ud.setInteger(delay, forKey: UD_VideoDelay) 54 | } 55 | 56 | func getDelay() -> Int { 57 | 58 | let r = ud.integerForKey(UD_VideoDelay) 59 | if r > 0{ 60 | return r 61 | }else{ 62 | return 5 63 | } 64 | } 65 | 66 | //CameraFlip 67 | func saveCameraFlip(flip:CameraDevice){ 68 | ud.setObject(flip.rawValue, forKey: UD_CameraFlip) 69 | } 70 | 71 | func getCameraFlip() -> CameraDevice { 72 | if let s = ud.objectForKey(UD_CameraFlip){ 73 | if let r = CameraDevice(rawValue:s as! String) { 74 | return r 75 | }else{ 76 | return CameraDevice.Front 77 | } 78 | }else{ 79 | return CameraDevice.Front 80 | } 81 | } 82 | 83 | //Standard Name 84 | func saveStandardName(name:String){ 85 | ud.setObject(name, forKey: UD_StandardName) 86 | } 87 | 88 | func getStandardName() -> String { 89 | if let r = ud.objectForKey(UD_StandardName) { 90 | return r as! String 91 | }else{ 92 | return "" 93 | } 94 | } 95 | 96 | //Judge name 97 | func saveJudgeName(name:String){ 98 | ud.setObject(name, forKey: UD_JudgeName) 99 | } 100 | 101 | func getJudgeName() -> String { 102 | if let r = ud.objectForKey(UD_JudgeName){ 103 | return r as! String 104 | }else{ 105 | return "" 106 | } 107 | } 108 | 109 | 110 | //Purchaced Video 111 | func savePurchasedVideo(){ 112 | ud.setBool(true, forKey: UD_PurchasedVideo) 113 | } 114 | 115 | func getPurchasedVideo() -> Bool { 116 | return ud.boolForKey(UD_PurchasedVideo) 117 | } 118 | 119 | func saveUserVideoMessageOptOut(){ 120 | ud.setBool(true, forKey:UD_userVideoMessageOptOut) 121 | let dateValue = NSTimeIntervalSince1970 122 | ud.setDouble(dateValue, forKey: UD_lastTimeUserWasInformed) 123 | } 124 | 125 | 126 | func getUserVideoMessageOptOut() -> Bool{ 127 | let now = NSTimeIntervalSince1970 128 | if now - ud.doubleForKey(UD_lastTimeUserWasInformed) > 2592000{ 129 | return false 130 | }else{ 131 | return true 132 | } 133 | } 134 | 135 | 136 | } 137 | 138 | 139 | -------------------------------------------------------------------------------- /Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "Spot Light Icon-1.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "Spot Light Icon@2x-1.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Spot Light Icon@3x-1.png", 19 | "scale" : "3x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "Spot Light 7-9@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "40x40", 29 | "idiom" : "iphone", 30 | "filename" : "Spot Light 7-9@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "57x57", 35 | "idiom" : "iphone", 36 | "filename" : "AppIcon 5-6.png", 37 | "scale" : "1x" 38 | }, 39 | { 40 | "size" : "57x57", 41 | "idiom" : "iphone", 42 | "filename" : "AppIcon 5-6@2x.png", 43 | "scale" : "2x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "appIcon@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "App Icon 7-9@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "idiom" : "ipad", 59 | "size" : "29x29", 60 | "scale" : "1x" 61 | }, 62 | { 63 | "idiom" : "ipad", 64 | "size" : "29x29", 65 | "scale" : "2x" 66 | }, 67 | { 68 | "idiom" : "ipad", 69 | "size" : "40x40", 70 | "scale" : "1x" 71 | }, 72 | { 73 | "idiom" : "ipad", 74 | "size" : "40x40", 75 | "scale" : "2x" 76 | }, 77 | { 78 | "idiom" : "ipad", 79 | "size" : "50x50", 80 | "scale" : "1x" 81 | }, 82 | { 83 | "idiom" : "ipad", 84 | "size" : "50x50", 85 | "scale" : "2x" 86 | }, 87 | { 88 | "idiom" : "ipad", 89 | "size" : "72x72", 90 | "scale" : "1x" 91 | }, 92 | { 93 | "idiom" : "ipad", 94 | "size" : "72x72", 95 | "scale" : "2x" 96 | }, 97 | { 98 | "idiom" : "ipad", 99 | "size" : "76x76", 100 | "scale" : "1x" 101 | }, 102 | { 103 | "idiom" : "ipad", 104 | "size" : "76x76", 105 | "scale" : "2x" 106 | }, 107 | { 108 | "idiom" : "ipad", 109 | "size" : "83.5x83.5", 110 | "scale" : "2x" 111 | }, 112 | { 113 | "size" : "24x24", 114 | "idiom" : "watch", 115 | "scale" : "2x", 116 | "role" : "notificationCenter", 117 | "subtype" : "38mm" 118 | }, 119 | { 120 | "size" : "27.5x27.5", 121 | "idiom" : "watch", 122 | "scale" : "2x", 123 | "role" : "notificationCenter", 124 | "subtype" : "42mm" 125 | }, 126 | { 127 | "size" : "29x29", 128 | "idiom" : "watch", 129 | "filename" : "Spot Light Icon@2x.png", 130 | "role" : "companionSettings", 131 | "scale" : "2x" 132 | }, 133 | { 134 | "size" : "29x29", 135 | "idiom" : "watch", 136 | "filename" : "Spot Light Icon@3x.png", 137 | "role" : "companionSettings", 138 | "scale" : "3x" 139 | }, 140 | { 141 | "size" : "40x40", 142 | "idiom" : "watch", 143 | "scale" : "2x", 144 | "role" : "appLauncher", 145 | "subtype" : "38mm" 146 | }, 147 | { 148 | "size" : "86x86", 149 | "idiom" : "watch", 150 | "scale" : "2x", 151 | "role" : "quickLook", 152 | "subtype" : "38mm" 153 | }, 154 | { 155 | "size" : "98x98", 156 | "idiom" : "watch", 157 | "scale" : "2x", 158 | "role" : "quickLook", 159 | "subtype" : "42mm" 160 | } 161 | ], 162 | "info" : { 163 | "version" : 1, 164 | "author" : "xcode" 165 | } 166 | } -------------------------------------------------------------------------------- /SpotlightTransitionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpotlightTransitionController.swift 3 | // Gecco 4 | // 5 | // Created by yukiasai on 2016/01/19. 6 | // Copyright (c) 2016 yukiasai. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol SpotlightTransitionControllerDelegate: class { 12 | func spotlightTransitionWillPresent(controller: SpotlightTransitionController, transitionContext: UIViewControllerContextTransitioning) 13 | func spotlightTransitionWillDismiss(controller: SpotlightTransitionController, transitionContext: UIViewControllerContextTransitioning) 14 | } 15 | 16 | class SpotlightTransitionController: NSObject, UIViewControllerAnimatedTransitioning { 17 | var isPresent = false 18 | 19 | var delegate: SpotlightTransitionControllerDelegate? 20 | 21 | func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { 22 | return 0.25 23 | } 24 | 25 | func animateTransition(transitionContext: UIViewControllerContextTransitioning) { 26 | if isPresent { 27 | animateTransitionForPresent(transitionContext) 28 | } else { 29 | animateTransitionForDismiss(transitionContext) 30 | } 31 | } 32 | 33 | private func animateTransitionForPresent(transitionContext: UIViewControllerContextTransitioning) { 34 | guard let source = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey), 35 | let destination = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) else { 36 | fatalError() 37 | } 38 | transitionContext.containerView()?.insertSubview(destination.view, aboveSubview: source.view) 39 | 40 | destination.view.alpha = 0 41 | 42 | source.viewWillDisappear(true) 43 | destination.viewWillAppear(true) 44 | 45 | let duration = transitionDuration(transitionContext) 46 | CATransaction.begin() 47 | CATransaction.setCompletionBlock { 48 | transitionContext.completeTransition(true) 49 | } 50 | { // In transation 51 | UIView.animateWithDuration(duration, delay: 0, options: .CurveEaseInOut, 52 | animations: { 53 | destination.view.alpha = 1.0 54 | }, 55 | completion: { _ in 56 | destination.viewDidAppear(true) 57 | source.viewDidDisappear(true) 58 | } 59 | ) 60 | delegate?.spotlightTransitionWillPresent(self, transitionContext: transitionContext) 61 | }() 62 | CATransaction.commit() 63 | } 64 | 65 | private func animateTransitionForDismiss(transitionContext: UIViewControllerContextTransitioning) { 66 | guard let source = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey), 67 | let destination = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) else { 68 | fatalError() 69 | } 70 | 71 | source.viewWillDisappear(true) 72 | destination.viewWillAppear(true) 73 | 74 | let duration = transitionDuration(transitionContext) 75 | 76 | CATransaction.begin() 77 | CATransaction.setCompletionBlock { 78 | transitionContext.completeTransition(true) 79 | } 80 | { // In transation 81 | UIView.animateWithDuration(duration, delay: 0, options: .CurveEaseInOut, 82 | animations: { 83 | source.view.alpha = 0.0 84 | }, 85 | completion: { _ in 86 | destination.viewDidAppear(true) 87 | source.viewDidDisappear(true) 88 | } 89 | ) 90 | delegate?.spotlightTransitionWillDismiss(self, transitionContext: transitionContext) 91 | }() 92 | CATransaction.commit() 93 | } 94 | 95 | func animationEnded(transitionCompleted: Bool) { 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Wodeo 2/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /SettingsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsViewController.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 21/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SettingsViewController: UIViewController,ViewCanceling { 12 | 13 | var wod:WOD! 14 | var delegate:ViewCanceling! 15 | 16 | @IBOutlet weak var minuteLabel:UILabel! 17 | @IBOutlet weak var secondLabel:UILabel! 18 | @IBOutlet weak var minuteStepper:UIStepper! 19 | @IBOutlet weak var secondStepper:UIStepper! 20 | 21 | var minStepperPV:Double = 0.0 22 | var secStepperPV:Double = 0.0 23 | 24 | 25 | @IBAction func minuteStepperChanged(sender:UIStepper){ 26 | 27 | 28 | if shouldWarnUser(){ 29 | if sender.value > minStepperPV { 30 | sender.value -= 1 31 | } 32 | warnUser() 33 | }else{ 34 | minuteLabel.text = "\(Int(minuteStepper.value))" 35 | } 36 | } 37 | 38 | @IBAction func secondStepperChanged(sender:UIStepper){ 39 | if shouldWarnUser(){ 40 | if sender.value > secStepperPV{ 41 | sender.value -= 1 42 | } 43 | warnUser() 44 | }else{ 45 | secondLabel.text = "\(Int(secondStepper.value))" 46 | } 47 | } 48 | 49 | func shouldWarnUser() -> Bool{ 50 | 51 | let total = (minuteStepper.value * 60) + (secondStepper.value) 52 | 53 | if !UserSettings.sharedInstance.getPurchasedVideo() && total > wod.totalVideoSecondsAllowed { 54 | return true 55 | }else{ 56 | return false 57 | } 58 | } 59 | 60 | func warnUser(){ 61 | let infoView = UIAlertController(title: "5 Minute Limit", message: "If you like our app, unlock more minutes and help support future great features, with the 'Unlimited Video' purchase. (It's as cheap as they would allow us!)", preferredStyle: UIAlertControllerStyle.Alert) 62 | infoView.view.tintColor = UIColor(red: 240/255.0, green: 178/255.0, blue: 71/255.0, alpha: 1.0) 63 | 64 | let ok = UIAlertAction(title:"Lets Go!", style: UIAlertActionStyle.Default, handler:{ 65 | (alert:UIAlertAction!) -> Void in 66 | 67 | let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("WodeoStore") as! WodeoProductPage 68 | viewController.delegate = self 69 | self.presentViewController(viewController, animated: true, completion: nil) 70 | 71 | 72 | }) 73 | 74 | let no = UIAlertAction(title: "No thanks..", style: UIAlertActionStyle.Cancel, handler:{ (alert:UIAlertAction!) -> Void in 75 | }) 76 | 77 | infoView.addAction(ok) 78 | infoView.addAction(no) 79 | 80 | presentViewController(infoView, animated:true, completion:nil) 81 | } 82 | 83 | @IBAction func done(sender:UIButton) { 84 | performSegueWithIdentifier("goToCamera", sender: nil) 85 | } 86 | 87 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 88 | 89 | if segue.identifier == "goToCamera" { 90 | let vc = segue.destinationViewController as! VideoViewController 91 | let cWOD = wod as! CountDownWOD 92 | cWOD.setCountDownTime(countDownTime()) 93 | vc.workout = cWOD 94 | vc.delegate = self 95 | } 96 | 97 | } 98 | 99 | @IBAction func cancel(sender:UIButton) { 100 | delegate.viewDidCancel() 101 | } 102 | 103 | func countDownTime() -> Int { 104 | return Int((minuteStepper.value * 60) + (secondStepper.value)) 105 | } 106 | 107 | override func viewDidLoad() { 108 | super.viewDidLoad() 109 | 110 | // Do any additional setup after loading the view. 111 | } 112 | 113 | override func didReceiveMemoryWarning() { 114 | super.didReceiveMemoryWarning() 115 | // Dispose of any resources that can be recreated. 116 | } 117 | 118 | override func prefersStatusBarHidden() -> Bool { 119 | return true 120 | } 121 | 122 | func viewDidCancel() { 123 | dismissViewControllerAnimated(true, completion: nil) 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /TabataWOD.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabataWOD.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 27/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol TabataWOD_Delegate { 12 | func tabataWODDidMoveToRoundWithName(name:String) 13 | func tabataWODDidMoveToRestPeriod() 14 | func tabataWODDidMoveOutOfRestPeriod() 15 | } 16 | 17 | class TabataWOD:WOD,StopWatchDelegate { 18 | 19 | 20 | var rounds = [PresetRound]() 21 | var stopWatch:StopWatch! 22 | var repStopWatch:StopWatch! 23 | var roundCounter:Int = 0 24 | var loopCounter:Int = 1 25 | var loops:Int = 1 26 | var restPeriodBetweenRounds:Int = 0 27 | var isResting = false 28 | 29 | var tabataDelegate:TabataWOD_Delegate? 30 | 31 | override init(with name: String) { 32 | stopWatch = StopWatch(type: StopWatchType.CountDown) 33 | repStopWatch = StopWatch(type: StopWatchType.CountUp) 34 | super.init(with: name) 35 | } 36 | 37 | func startWorkOut(){ 38 | repStopWatch.start() 39 | stopWatch.delegate = self 40 | nextInterval() 41 | } 42 | 43 | func stopWorkOut() { 44 | repStopWatch.stop() 45 | stopWatch.stop() 46 | } 47 | 48 | func addRep() -> String { 49 | //Get current round 50 | let round = rounds[roundCounter - 1] 51 | round.reps.append(Rep(startTime: repStopWatch.currentDuration, repNumber: round.reps.count + 1)) 52 | 53 | return "Rep: \(round.reps.count)" 54 | } 55 | 56 | 57 | private func nextInterval(){ 58 | 59 | stopWatch.stop() 60 | 61 | if restPeriodBetweenRounds > 0 && !isResting && roundCounter > 0 && roundCounter < rounds.count { 62 | isResting = true 63 | stopWatch.countDownFromSeconds = restPeriodBetweenRounds 64 | stopWatch.start() 65 | 66 | if let td = tabataDelegate { 67 | td.tabataWODDidMoveToRestPeriod() 68 | }else{ 69 | print("Delagate not set") 70 | } 71 | return 72 | } 73 | 74 | if roundCounter < rounds.count { 75 | 76 | isResting = false 77 | 78 | if let it = tabataDelegate { 79 | it.tabataWODDidMoveToRoundWithName("Loop \(rounds[roundCounter].loopNumber) Round \(roundCounter + 1) of \(rounds.count)") 80 | } 81 | 82 | 83 | stopWatch.countDownFromSeconds = rounds[roundCounter].presetDuration 84 | roundCounter += 1 85 | 86 | if let td = tabataDelegate { 87 | td.tabataWODDidMoveOutOfRestPeriod() 88 | }else{ 89 | print("Delegate not set") 90 | } 91 | 92 | stopWatch.start() 93 | 94 | }else{ 95 | delegate.workOutDidEnd() 96 | } 97 | } 98 | 99 | func totalWorkOutSeconds() -> Int { 100 | var retTotal = 0 101 | for r in rounds { 102 | retTotal += r.presetDuration 103 | } 104 | 105 | //Add on the rest period 106 | let restPeriod = (rounds.count - 1) * restPeriodBetweenRounds 107 | retTotal += restPeriod 108 | 109 | return retTotal 110 | } 111 | 112 | //Stop watch delegate 113 | func stopWatchDidCountDown(t: TimeInterval) { 114 | 115 | nextInterval() 116 | } 117 | 118 | func stopWatchDidUpdateWithTimeInterval(t: TimeInterval) { 119 | delegate.wodStopWatchDidUpdateToValue(t.intervalString(false)) 120 | } 121 | 122 | func stringForRound(index:Int,andRep:Int) -> String { 123 | 124 | let loopNumber = rounds[index].loopNumber 125 | let maxNumberOfLoops = rounds.last!.loopNumber 126 | 127 | let roundNumber = rounds[index].roundNumber 128 | let maxNumberOfRounds = rounds.count 129 | 130 | let repNumber = andRep + 1 131 | let maxReps = rounds[index].reps.count 132 | 133 | return "Loop \(loopNumber) of \(maxNumberOfLoops) | Round \(roundNumber) of \(maxNumberOfRounds) | Rep \(repNumber) of \(maxReps)" 134 | 135 | 136 | 137 | 138 | } 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | } -------------------------------------------------------------------------------- /SpotlightView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpotlightView.swift 3 | // Gecco 4 | // 5 | // Created by yukiasai on 2016/01/16. 6 | // Copyright (c) 2016 yukiasai. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | public class SpotlightView: UIView { 12 | public static let defaultAnimateDuration: NSTimeInterval = 0.25 13 | 14 | private lazy var maskLayer: CAShapeLayer = { 15 | let layer = CAShapeLayer() 16 | layer.fillRule = kCAFillRuleEvenOdd 17 | layer.fillColor = UIColor.blackColor().CGColor 18 | return layer 19 | }() 20 | 21 | var spotlight: SpotlightType? 22 | 23 | public override init(frame: CGRect) { 24 | super.init(frame: frame) 25 | commonInit() 26 | } 27 | 28 | public required init?(coder aDecoder: NSCoder) { 29 | super.init(coder: aDecoder) 30 | commonInit() 31 | } 32 | 33 | private func commonInit() { 34 | layer.mask = maskLayer 35 | } 36 | 37 | public override func layoutSubviews() { 38 | super.layoutSubviews() 39 | 40 | maskLayer.frame = frame 41 | } 42 | 43 | public func appear(spotlight: SpotlightType, duration: NSTimeInterval = SpotlightView.defaultAnimateDuration) { 44 | maskLayer.addAnimation(appearAnimation(duration, spotlight: spotlight), forKey: nil) 45 | self.spotlight = spotlight 46 | } 47 | 48 | public func disappear(duration: NSTimeInterval = SpotlightView.defaultAnimateDuration) { 49 | maskLayer.addAnimation(disappearAnimation(duration), forKey: nil) 50 | } 51 | 52 | public func move(toSpotlight: SpotlightType, duration: NSTimeInterval = SpotlightView.defaultAnimateDuration, moveType: SpotlightMoveType = .Direct) { 53 | switch moveType { 54 | case .Direct: 55 | moveDirect(toSpotlight, duration: duration) 56 | case .Disappear: 57 | moveDisappear(toSpotlight, duration: duration) 58 | } 59 | } 60 | } 61 | 62 | extension SpotlightView { 63 | private func moveDirect(toSpotlight: SpotlightType, duration: NSTimeInterval = SpotlightView.defaultAnimateDuration) { 64 | maskLayer.addAnimation(moveAnimation(duration, toSpotlight: toSpotlight), forKey: nil) 65 | spotlight = toSpotlight 66 | } 67 | 68 | private func moveDisappear(toSpotlight: SpotlightType, duration: NSTimeInterval = SpotlightView.defaultAnimateDuration) { 69 | CATransaction.begin() 70 | CATransaction.setCompletionBlock { 71 | self.appear(toSpotlight, duration: duration) 72 | self.spotlight = toSpotlight 73 | } 74 | disappear(duration) 75 | CATransaction.commit() 76 | } 77 | 78 | private func maskPath(path: UIBezierPath) -> UIBezierPath { 79 | return [path].reduce(UIBezierPath(rect: frame)) { 80 | $0.appendPath($1) 81 | return $0 82 | } 83 | } 84 | 85 | private func appearAnimation(duration: NSTimeInterval, spotlight: SpotlightType) -> CAAnimation { 86 | let beginPath = maskPath(spotlight.infinitesmalPath) 87 | let endPath = maskPath(spotlight.path) 88 | return pathAnimation(duration, beginPath:beginPath, endPath: endPath) 89 | } 90 | 91 | private func disappearAnimation(duration: NSTimeInterval) -> CAAnimation { 92 | let endPath = maskPath(spotlight!.infinitesmalPath) 93 | return pathAnimation(duration, beginPath:nil, endPath: endPath) 94 | } 95 | 96 | private func moveAnimation(duration: NSTimeInterval, toSpotlight: SpotlightType) -> CAAnimation { 97 | let endPath = maskPath(toSpotlight.path) 98 | return pathAnimation(duration, beginPath:nil, endPath: endPath) 99 | } 100 | 101 | private func pathAnimation(duration: NSTimeInterval, beginPath: UIBezierPath?, endPath: UIBezierPath) -> CAAnimation { 102 | let animation = CABasicAnimation(keyPath: "path") 103 | animation.duration = duration 104 | animation.timingFunction = CAMediaTimingFunction(controlPoints: 0.66, 0, 0.33, 1) 105 | if let path = beginPath { 106 | animation.fromValue = path.CGPath 107 | } 108 | animation.toValue = endPath.CGPath 109 | animation.removedOnCompletion = false 110 | animation.fillMode = kCAFillModeForwards 111 | return animation 112 | } 113 | } 114 | 115 | public enum SpotlightMoveType { 116 | case Direct 117 | case Disappear 118 | } -------------------------------------------------------------------------------- /TiledView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TiledView.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 26/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable 12 | 13 | class TiledView: UIView { 14 | 15 | 16 | var backgroundLayer:CALayer! 17 | var topL:CATextLayer! 18 | var leftL:CATextLayer! 19 | var rightL:CATextLayer! 20 | var bottomL:CATextLayer! 21 | 22 | var top:Bool = false { 23 | didSet{ 24 | topL.hidden = top ? false:true 25 | bottomL.hidden = top ? true:false 26 | } 27 | } 28 | 29 | var instruction:String = "" { 30 | didSet{ 31 | topL.string = instruction 32 | leftL.string = instruction 33 | rightL.string = instruction 34 | bottomL.string = instruction 35 | } 36 | } 37 | 38 | 39 | 40 | override init(frame: CGRect) { 41 | super.init(frame: frame) 42 | 43 | backgroundLayer = CALayer() 44 | backgroundLayer.frame = frame 45 | backgroundLayer.cornerRadius = 20.0 46 | backgroundLayer.masksToBounds = true 47 | backgroundLayer.backgroundColor = UIColor(red: 54/255.0, green: 54/255.0, blue: 54/255.0, alpha: 0.2).CGColor 48 | backgroundLayer.borderWidth = 1.0 49 | backgroundLayer.borderColor = UIColor(red: 243/255.0, green: 80/255.0, blue: 47/255.0, alpha:0.4).CGColor 50 | layer.addSublayer(backgroundLayer) 51 | 52 | topL = CATextLayer() 53 | leftL = CATextLayer() 54 | rightL = CATextLayer() 55 | bottomL = CATextLayer() 56 | 57 | topL.alignmentMode = kCAAlignmentCenter 58 | leftL.alignmentMode = kCAAlignmentCenter 59 | rightL.alignmentMode = kCAAlignmentCenter 60 | bottomL.alignmentMode = kCAAlignmentCenter 61 | 62 | let op:Float = 0.5 63 | topL.opacity = op 64 | leftL.opacity = op 65 | rightL.opacity = op 66 | bottomL.opacity = op 67 | 68 | topL.font = "Folio-BoldCondensed" 69 | leftL.font = "Folio-BoldCondensed" 70 | rightL.font = "Folio-BoldCondensed" 71 | bottomL.font = "Folio-BoldCondensed" 72 | 73 | let fontSize:CGFloat = 25.0 74 | topL.fontSize = fontSize 75 | leftL.fontSize = fontSize 76 | rightL.fontSize = fontSize 77 | bottomL.fontSize = fontSize 78 | 79 | 80 | 81 | topL.foregroundColor = UIColor.yellowColor().CGColor 82 | leftL.foregroundColor = UIColor.yellowColor().CGColor 83 | rightL.foregroundColor = UIColor.yellowColor().CGColor 84 | bottomL.foregroundColor = UIColor.yellowColor().CGColor 85 | 86 | 87 | leftL.transform = CATransform3DRotate(leftL.transform, CGFloat(-M_PI_2), 0.0, 0.0, 1.0) 88 | rightL.transform = CATransform3DRotate(rightL.transform, CGFloat(M_PI_2), 0.0, 0.0, 1.0) 89 | 90 | topL.backgroundColor = UIColor.clearColor().CGColor 91 | leftL.backgroundColor = UIColor.clearColor().CGColor 92 | rightL.backgroundColor = UIColor.clearColor().CGColor 93 | bottomL.backgroundColor = UIColor.clearColor().CGColor 94 | 95 | 96 | layer.addSublayer(topL) 97 | layer.addSublayer(bottomL) 98 | layer.addSublayer(leftL) 99 | layer.addSublayer(rightL) 100 | 101 | 102 | 103 | } 104 | 105 | override func layoutSubviews() { 106 | super.layoutSubviews() 107 | backgroundLayer.frame = bounds 108 | 109 | 110 | let lh:CGFloat = 50.0 111 | let vm = bounds.size.width / 2.0 112 | let lo:CGFloat = lh / 2.0 113 | 114 | topL.bounds = CGRectMake(0.0, 0.0, bounds.size.width, lh) 115 | leftL.bounds = CGRectMake(0.0, 0.0, bounds.size.width, lh) 116 | rightL.bounds = CGRectMake(0.0, 0.0, bounds.size.width, lh) 117 | bottomL.bounds = CGRectMake(0.0, 0.0, bounds.size.width, lh) 118 | 119 | topL.position = CGPoint(x: vm, y:lo) 120 | leftL.position = CGPoint(x:lo, y: bounds.size.height / 2.0) 121 | rightL.position = CGPoint(x: bounds.size.width - lo, y: bounds.size.height / 2.0) 122 | bottomL.position = CGPoint(x:vm, y:bounds.size.height - lo) 123 | 124 | 125 | } 126 | 127 | required init?(coder aDecoder: NSCoder) { 128 | super.init(coder: aDecoder) 129 | } 130 | 131 | 132 | } 133 | -------------------------------------------------------------------------------- /CustomPhotoLibrary.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CustomPhotoLibrary.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 20/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import UIKit 12 | import AVFoundation 13 | import AssetsLibrary 14 | import Photos 15 | 16 | 17 | 18 | class CustomPhotoAlbum: NSObject { 19 | 20 | static let albumName = "Wodeo 2" 21 | 22 | static let sharedInstance = CustomPhotoAlbum() 23 | 24 | 25 | var assetCollection: PHAssetCollection! 26 | override init() { 27 | super.init() 28 | 29 | if let assetCollection = fetchAssetCollectionForAlbum() { 30 | self.assetCollection = assetCollection 31 | return 32 | } 33 | 34 | if PHPhotoLibrary.authorizationStatus() != PHAuthorizationStatus.Authorized { 35 | PHPhotoLibrary.requestAuthorization({ (status: PHAuthorizationStatus) -> Void in 36 | status 37 | }) 38 | } 39 | 40 | if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.Authorized { 41 | self.createAlbum() 42 | } else { 43 | PHPhotoLibrary.requestAuthorization(requestAuthorizationHandler) 44 | } 45 | } 46 | 47 | func requestAuthorizationHandler(status: PHAuthorizationStatus) { 48 | if PHPhotoLibrary.authorizationStatus() == PHAuthorizationStatus.Authorized { 49 | // ideally this ensures the creation of the photo album even if authorization wasn't prompted till after init was done 50 | print("trying again to create the album") 51 | self.createAlbum() 52 | } else { 53 | print("should really prompt the user to let them know it's failed") 54 | } 55 | } 56 | 57 | func createAlbum() { 58 | PHPhotoLibrary.sharedPhotoLibrary().performChanges({ 59 | PHAssetCollectionChangeRequest.creationRequestForAssetCollectionWithTitle(CustomPhotoAlbum.albumName) // create an asset collection with the album name 60 | }) { success, error in 61 | if success { 62 | self.assetCollection = self.fetchAssetCollectionForAlbum() 63 | } else { 64 | print("error \(error)") 65 | } 66 | } 67 | } 68 | 69 | func fetchAssetCollectionForAlbum() -> PHAssetCollection! { 70 | let fetchOptions = PHFetchOptions() 71 | fetchOptions.predicate = NSPredicate(format: "title = %@", CustomPhotoAlbum.albumName) 72 | let collection = PHAssetCollection.fetchAssetCollectionsWithType(.Album, subtype: .Any, options: fetchOptions) 73 | 74 | if let _: AnyObject = collection.firstObject { 75 | return collection.firstObject as! PHAssetCollection 76 | } 77 | return nil 78 | } 79 | 80 | func saveImage(image: UIImage, metadata: NSDictionary) { 81 | if assetCollection == nil { 82 | return // if there was an error upstream, skip the save 83 | } 84 | 85 | PHPhotoLibrary.sharedPhotoLibrary().performChanges({ 86 | let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(image) 87 | let assetPlaceHolder = assetChangeRequest.placeholderForCreatedAsset 88 | assetChangeRequest.creationDate = (metadata["DatePhotoTaken"] as! NSDate) 89 | let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection) 90 | albumChangeRequest!.addAssets([assetPlaceHolder!]) 91 | }, completionHandler: nil) 92 | } 93 | 94 | func saveVideo(videoURL:NSURL, metadata: NSDictionary){ 95 | if assetCollection == nil { 96 | return 97 | } 98 | 99 | PHPhotoLibrary.sharedPhotoLibrary().performChanges({ 100 | let assetChangeRequest = PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(videoURL) 101 | let assetPlaceHolder = assetChangeRequest!.placeholderForCreatedAsset 102 | assetChangeRequest!.creationDate = (metadata["DateVideoTaken"] as! NSDate) 103 | let albumChangeRequest = PHAssetCollectionChangeRequest(forAssetCollection: self.assetCollection) 104 | albumChangeRequest!.addAssets([assetPlaceHolder!]) 105 | }, completionHandler:{(finished:Bool,error:NSError?) in 106 | 107 | if let err = error { 108 | print("\(err.localizedDescription)") 109 | } 110 | 111 | }) 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /SpotlightViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SpotlightViewController.swift 3 | // Gecco 4 | // 5 | // Created by yukiasai on 2016/01/15. 6 | // Copyright (c) 2016 yukiasai. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @objc public protocol SpotlightViewControllerDelegate: class { 12 | optional func spotlightViewControllerWillPresent(viewController: SpotlightViewController, animated: Bool) 13 | optional func spotlightViewControllerWillDismiss(viewController: SpotlightViewController, animated: Bool) 14 | optional func spotlightViewControllerTapped(viewController: SpotlightViewController, isInsideSpotlight: Bool) 15 | } 16 | 17 | public class SpotlightViewController: UIViewController { 18 | 19 | public var delegate: SpotlightViewControllerDelegate? 20 | 21 | private lazy var transitionController: SpotlightTransitionController = { 22 | let controller = SpotlightTransitionController() 23 | controller.delegate = self 24 | return controller 25 | }() 26 | 27 | public let spotlightView = SpotlightView() 28 | public let contentView = UIView() 29 | 30 | public var alpha: CGFloat = 0.5 31 | 32 | override public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { 33 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 34 | commonInit() 35 | } 36 | 37 | required public init?(coder aDecoder: NSCoder) { 38 | super.init(coder: aDecoder) 39 | commonInit() 40 | } 41 | 42 | private func commonInit() { 43 | modalPresentationStyle = .OverCurrentContext 44 | transitioningDelegate = self 45 | } 46 | 47 | public override func viewDidLoad() { 48 | super.viewDidLoad() 49 | 50 | setupSpotlightView(alpha) 51 | setupContentView() 52 | setupTapGesture() 53 | 54 | view.backgroundColor = UIColor.clearColor() 55 | } 56 | 57 | public override func viewDidAppear(animated: Bool) { 58 | super.viewDidAppear(animated) 59 | } 60 | 61 | private func setupSpotlightView(alpha: CGFloat) { 62 | spotlightView.frame = view.bounds 63 | spotlightView.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: alpha) 64 | spotlightView.userInteractionEnabled = false 65 | view.insertSubview(spotlightView, atIndex: 0) 66 | view.addConstraints([NSLayoutAttribute.Top, .Bottom, .Left, .Right].map { 67 | NSLayoutConstraint(item: view, attribute: $0, relatedBy: .Equal, toItem: spotlightView, attribute: $0, multiplier: 1, constant: 0) 68 | }) 69 | } 70 | 71 | private func setupContentView() { 72 | contentView.frame = view.bounds 73 | contentView.backgroundColor = UIColor.clearColor() 74 | view.addSubview(contentView) 75 | view.addConstraints([NSLayoutAttribute.Top, .Bottom, .Left, .Right].map { 76 | NSLayoutConstraint(item: view, attribute: $0, relatedBy: .Equal, toItem: contentView, attribute: $0, multiplier: 1, constant: 0) 77 | }) 78 | } 79 | 80 | private func setupTapGesture() { 81 | let gesture = UITapGestureRecognizer(target: self, action: #selector(SpotlightViewController.viewTapped(_:))); 82 | view.addGestureRecognizer(gesture) 83 | } 84 | } 85 | 86 | extension SpotlightViewController { 87 | func viewTapped(gesture: UITapGestureRecognizer) { 88 | let touchPoint = gesture.locationInView(spotlightView) 89 | let isInside = spotlightView.spotlight?.frame.contains(touchPoint) ?? false 90 | delegate?.spotlightViewControllerTapped?(self, isInsideSpotlight: isInside) 91 | } 92 | } 93 | 94 | extension SpotlightViewController: SpotlightTransitionControllerDelegate { 95 | func spotlightTransitionWillPresent(controller: SpotlightTransitionController, transitionContext: UIViewControllerContextTransitioning) { 96 | delegate?.spotlightViewControllerWillPresent?(self, animated: transitionContext.isAnimated()) 97 | } 98 | 99 | func spotlightTransitionWillDismiss(controller: SpotlightTransitionController, transitionContext: UIViewControllerContextTransitioning) { 100 | delegate?.spotlightViewControllerWillDismiss?(self, animated: transitionContext.isAnimated()) 101 | } 102 | } 103 | 104 | extension SpotlightViewController: UIViewControllerTransitioningDelegate { 105 | public func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 106 | transitionController.isPresent = true 107 | return transitionController 108 | } 109 | 110 | public func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 111 | transitionController.isPresent = false 112 | return transitionController 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /WodeoProductPage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WodeoProductPage.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 28/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import StoreKit 11 | 12 | class WodeoProductPage: UIViewController { 13 | 14 | 15 | var delegate:ViewCanceling? 16 | 17 | @IBOutlet weak var tableView:UITableView! 18 | var products = [SKProduct]() 19 | lazy var refreshControl: UIRefreshControl = { 20 | let refreshControl = UIRefreshControl() 21 | refreshControl.tintColor = UIColor.whiteColor() 22 | refreshControl.addTarget(self, action:#selector(WodeoProductPage.reload), forControlEvents: UIControlEvents.ValueChanged) 23 | 24 | return refreshControl 25 | }() 26 | 27 | 28 | deinit { 29 | NSNotificationCenter.defaultCenter().removeObserver(self) 30 | } 31 | 32 | 33 | 34 | // priceFormatter is used to show proper, localized currency 35 | lazy var priceFormatter: NSNumberFormatter = { 36 | let pf = NSNumberFormatter() 37 | pf.formatterBehavior = .Behavior10_4 38 | pf.numberStyle = .CurrencyStyle 39 | return pf 40 | }() 41 | 42 | override func viewDidLoad() { 43 | super.viewDidLoad() 44 | 45 | self.tableView.addSubview(self.refreshControl) 46 | reload() 47 | refreshControl.beginRefreshing() 48 | 49 | // Subscribe to a notification that fires when a product is purchased. 50 | NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(WodeoProductPage.productPurchased(_:)), name: IAPHelperProductPurchasedNotification, object: nil) 51 | 52 | } 53 | 54 | // Fetch the products from iTunes connect, redisplay the table on successful completion 55 | func reload() { 56 | products = [] 57 | tableView.reloadData() 58 | WodeoProducts.store.requestProductsWithCompletionHandler { success, products in 59 | if success { 60 | self.products = products 61 | self.tableView.reloadData() 62 | } 63 | self.refreshControl.endRefreshing() 64 | } 65 | } 66 | 67 | // Restore purchases to this device. 68 | @IBAction func restoreTapped(sender: AnyObject) { 69 | WodeoProducts.store.restoreCompletedTransactions() 70 | } 71 | 72 | // Purchase the product 73 | func buyButtonTapped(button: UIButton) { 74 | let product = products[button.tag] 75 | WodeoProducts.store.purchaseProduct(product) 76 | } 77 | 78 | // When a product is purchased, this notification fires, redraw the correct row 79 | func productPurchased(notification: NSNotification) { 80 | let productIdentifier = notification.object as! String 81 | UserSettings.sharedInstance.savePurchasedVideo() 82 | for (index, product) in products.enumerate() { 83 | 84 | 85 | 86 | if product.productIdentifier == productIdentifier { 87 | tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: 0)], withRowAnimation: .Fade) 88 | break 89 | } 90 | } 91 | } 92 | 93 | 94 | //Admin 95 | override func prefersStatusBarHidden() -> Bool { 96 | return true 97 | } 98 | 99 | @IBAction func didCancel(sender:UIButton){ 100 | if let d = delegate{ 101 | d.viewDidCancel() 102 | } 103 | } 104 | 105 | } 106 | 107 | extension WodeoProductPage:UITableViewDelegate { 108 | 109 | } 110 | 111 | 112 | extension WodeoProductPage:UITableViewDataSource { 113 | 114 | func numberOfSectionsInTableView(tableView: UITableView) -> Int { 115 | return 1 116 | } 117 | 118 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 119 | return products.count 120 | } 121 | 122 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 123 | let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) 124 | 125 | let product = products[indexPath.row] 126 | cell.textLabel?.text = product.localizedTitle 127 | 128 | if WodeoProducts.store.isProductPurchased(product.productIdentifier) { 129 | cell.accessoryType = .Checkmark 130 | cell.accessoryView = nil 131 | cell.detailTextLabel?.text = "" 132 | } 133 | else if IAPHelper.canMakePayments() { 134 | priceFormatter.locale = product.priceLocale 135 | //cell.detailTextLabel?.text = priceFormatter.stringFromNumber(product.price) 136 | 137 | let button = BorderButton(frame: CGRect(x: 0, y: 0, width: 100.0, height: 37)) 138 | button.setTitleColor(view.tintColor, forState: .Normal) 139 | button.setTitle("\(priceFormatter.stringFromNumber(product.price)!)", forState: .Normal) 140 | button.tag = indexPath.row 141 | button.addTarget(self, action: #selector(WodeoProductPage.buyButtonTapped(_:)), forControlEvents: .TouchUpInside) 142 | cell.accessoryType = .None 143 | cell.accessoryView = button 144 | } else { 145 | cell.accessoryType = .None 146 | cell.accessoryView = nil 147 | cell.detailTextLabel?.text = "Not Available" 148 | } 149 | return cell } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /IntervalOverlay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntervalOverlay.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 22/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class IntervalOverlay: OverlayViewController ,WODDelegate,IntervalWOD_Delegate,ResultsControllerDelegate { 12 | 13 | var intervalWOD:IntervalWOD! 14 | @IBOutlet weak var mainRotatorView:RotatorView! 15 | var repCountLabel = TimeTextLabel(fontSize:30.0) 16 | var roundCountLabel = TimeTextLabel(fontSize: 30.0) 17 | //layout vars 18 | let counterLH:CGFloat = 35.0 19 | 20 | 21 | 22 | //View lifecycle 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | 27 | 28 | 29 | print("Interval layout is loading") 30 | 31 | intervalWOD = workOut as! IntervalWOD 32 | intervalWOD.delegate = self 33 | intervalWOD.intervalDelegate = self 34 | } 35 | 36 | override func viewDidDisappear(animated: Bool) { 37 | super.viewDidDisappear(animated) 38 | 39 | } 40 | 41 | 42 | //Stop watch delegate 43 | override func stopWatchDidCountDown(t: TimeInterval) { 44 | super.stopWatchDidCountDown(t) 45 | intervalWOD.startWorkOut() 46 | addButtonUI() 47 | 48 | } 49 | 50 | 51 | //Create and layout the UI 52 | override func createUI() { 53 | super.createUI() 54 | 55 | } 56 | 57 | func addButtonUI(){ 58 | 59 | let addRepButton = TiledView() 60 | addRepButton.frame = CGRectMake(0.0, 0.0, view.frame.size.width, view.frame.size.height) 61 | addRepButton.instruction = "Add Repp" 62 | addRepButton.top = true 63 | addRepButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action:#selector(IntervalOverlay.addRep(_:)))) 64 | mainRotatorView.addSubview(addRepButton) 65 | 66 | UIView.animateWithDuration(0.5, animations:{ 67 | self.mainRotatorView.alpha = 1.0 68 | }) 69 | } 70 | 71 | 72 | override func layOutForPortrait() { 73 | super.layOutForPortrait() 74 | repCountLabel.frame = CGRectMake(2.0, 0.0, fw,counterLH) 75 | roundCountLabel.frame = CGRectMake(2.0, counterLH,fw, counterLH) 76 | 77 | 78 | view.layer.addSublayer(repCountLabel) 79 | view.layer.addSublayer(roundCountLabel) 80 | } 81 | 82 | override func layOutForLandscapeLeft() { 83 | super.layOutForLandscapeLeft() 84 | 85 | let labelWidth = (fh/2) - 5.0 86 | 87 | repCountLabel.frame = CGRectMake(0.0, 0.0,labelWidth, counterLH) 88 | repCountLabel.alignmentMode = kCAAlignmentRight 89 | repCountLabel.position = CGPointMake(counterLH / 2,labelWidth/2) 90 | repCountLabel.transform = CATransform3DRotate(repCountLabel.transform, CGFloat(-M_PI_2), 0.0, 0.0, 1.0) 91 | 92 | roundCountLabel.frame = CGRectMake(0.0, 0.0,labelWidth, counterLH) 93 | roundCountLabel.position = CGPointMake(counterLH / 2,(fh - (labelWidth / 2)) - 5) 94 | roundCountLabel.transform = CATransform3DRotate(roundCountLabel.transform, CGFloat(-M_PI_2), 0.0, 0.0, 1.0) 95 | 96 | view.layer.addSublayer(repCountLabel) 97 | view.layer.addSublayer(roundCountLabel) 98 | 99 | 100 | 101 | } 102 | 103 | override func layOutForLandscapeRight() { 104 | super.layOutForLandscapeRight() 105 | 106 | let labelWidth = (fh/2) - 5.0 107 | 108 | repCountLabel.frame = CGRectMake(0.0, 0.0,labelWidth, counterLH) 109 | repCountLabel.alignmentMode = kCAAlignmentRight 110 | repCountLabel.position = CGPointMake(fw - (counterLH / 2),(fh - (labelWidth / 2)) - 5) 111 | repCountLabel.transform = CATransform3DRotate(repCountLabel.transform, CGFloat(M_PI_2), 0.0, 0.0, 1.0) 112 | 113 | roundCountLabel.frame = CGRectMake(0.0, 0.0,labelWidth, counterLH) 114 | roundCountLabel.position = CGPointMake(fw - (counterLH / 2),labelWidth/2) 115 | roundCountLabel.transform = CATransform3DRotate(roundCountLabel.transform, CGFloat(M_PI_2), 0.0, 0.0, 1.0) 116 | 117 | view.layer.addSublayer(repCountLabel) 118 | view.layer.addSublayer(roundCountLabel) 119 | 120 | 121 | mainRotatorView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2 * 2)) 122 | 123 | 124 | } 125 | 126 | 127 | 128 | //Wod delegate 129 | func wodStopWatchDidUpdateToValue(timeValue: String) { 130 | countDownLabel.string = timeValue 131 | } 132 | 133 | func workOutDidEnd() { 134 | intervalWOD.stopWorkOut() 135 | videoManager!.stopRecordingVideo { (videoURL, error) -> Void in 136 | } 137 | } 138 | 139 | //Interval WOD delegate 140 | func intervalWODDidMoveToRoundWithName(name: String) { 141 | repCountLabel.string = "" 142 | roundCountLabel.string = name 143 | } 144 | 145 | //Buttons 146 | func addRep(sender:UITapGestureRecognizer){ 147 | if sender.state == .Ended{ 148 | repCountLabel.string = intervalWOD.addRep() 149 | } 150 | 151 | } 152 | 153 | override func cancelButtonPressed(){ 154 | super.cancelButtonPressed() 155 | intervalWOD.stopWorkOut() 156 | 157 | } 158 | 159 | override func cameraManagerDidGenerateVideoAsset(url: NSURL) { 160 | 161 | super.cameraManagerDidGenerateVideoAsset(url) 162 | //Add overlay and set it as the video manager 163 | let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("IntervalResults") as! IntervalResultsVC 164 | presentViewController(viewController, animated: true, completion: nil) 165 | viewController.intervalWOD = intervalWOD 166 | viewController.videoToMakeURL = url 167 | viewController.delegate = self 168 | } 169 | 170 | func resultsDidCancel() { 171 | dismissViewControllerAnimated(true, completion:nil) 172 | } 173 | 174 | override func saveButtonPressed(){ 175 | countDownStopWatch.stop() 176 | let cWorkOut = workOut as! IntervalWOD 177 | cWorkOut.stopWorkOut() 178 | videoManager!.stopRecordingVideo { (videoURL, error) -> Void in 179 | 180 | } 181 | } 182 | 183 | 184 | } 185 | -------------------------------------------------------------------------------- /VideoMaker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VideoMaker.swift 3 | // Video Testing 4 | // 5 | // Created by Gareth Long on 06/02/2016. 6 | // Copyright © 2016 gazlongapps. All rights reserved. 7 | // 8 | 9 | import AVFoundation 10 | import AssetsLibrary 11 | import UIKit 12 | import Photos 13 | 14 | 15 | class VideoMaker: UIViewController { 16 | 17 | var videoToMakeURL:NSURL? 18 | var videoToMake:AVAsset? 19 | var exporter:AVAssetExportSession! 20 | 21 | func videoOutput() { 22 | 23 | videoToMake = AVAsset(URL: videoToMakeURL!) 24 | 25 | if (videoToMake == nil) { 26 | 27 | return 28 | } 29 | 30 | //This holds the different tracks of the video like audio and the layers 31 | let mixComposition = AVMutableComposition() 32 | 33 | let videoTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) 34 | 35 | do{ 36 | try videoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero,videoToMake!.duration), ofTrack: videoToMake!.tracksWithMediaType(AVMediaTypeVideo)[0], atTime: kCMTimeZero) 37 | }catch let error as NSError{ 38 | print("Error inserting time range on video track \(error.localizedDescription)") 39 | return 40 | }catch{ 41 | print("An unknown error occured") 42 | } 43 | 44 | 45 | //Add the audio 46 | let audioTrack1 = mixComposition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) 47 | 48 | do{ 49 | try audioTrack1.insertTimeRange(CMTimeRangeMake(kCMTimeZero,videoToMake!.duration), ofTrack: videoToMake!.tracksWithMediaType(AVMediaTypeAudio)[0], atTime: kCMTimeZero) 50 | }catch let error as NSError{ 51 | print("Error inserting time range on video track \(error.localizedDescription)") 52 | return 53 | }catch{ 54 | print("An unknown error occured") 55 | } 56 | 57 | 58 | //Make the instructions for the other layers 59 | let mainInstrucation = AVMutableVideoCompositionInstruction() 60 | mainInstrucation.timeRange = CMTimeRangeMake(kCMTimeZero, videoToMake!.duration) 61 | 62 | //Create the layer instructions 63 | let videoLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack) 64 | let videoAssetTrack = videoToMake!.tracksWithMediaType(AVMediaTypeVideo)[0] 65 | 66 | 67 | let assetInfo = orientationFromTransform(videoAssetTrack.preferredTransform) 68 | // sort size it in respect to the video orientation. 69 | 70 | videoLayerInstruction.setTransform(videoAssetTrack.preferredTransform, atTime: kCMTimeZero) 71 | videoLayerInstruction.setOpacity(0.0, atTime:videoToMake!.duration) 72 | 73 | //Add the instructions 74 | mainInstrucation.layerInstructions = [videoLayerInstruction] 75 | let mainCompositionInst = AVMutableVideoComposition() 76 | 77 | var naturalSize:CGSize 78 | if assetInfo.isPortrait { 79 | naturalSize = CGSizeMake(videoAssetTrack.naturalSize.height, videoAssetTrack.naturalSize.width); 80 | }else{ 81 | naturalSize = videoAssetTrack.naturalSize 82 | } 83 | 84 | let renderWidth = naturalSize.width 85 | let renderHeight = naturalSize.height 86 | 87 | mainCompositionInst.renderSize = CGSizeMake(renderWidth, renderHeight) 88 | mainCompositionInst.instructions = [mainInstrucation] 89 | mainCompositionInst.frameDuration = CMTimeMake(1, 30); 90 | 91 | 92 | 93 | 94 | //So now the main composition has been created add the video affects 95 | applyVideoEffectsToComposition(mainCompositionInst, size: naturalSize) 96 | 97 | let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory,.UserDomainMask, true) 98 | let documentsDirectory = paths[0] 99 | let random = Int(arc4random_uniform(1000)) 100 | let url = NSURL(fileURLWithPath:documentsDirectory).URLByAppendingPathComponent("FinalVideo\(random).mov") 101 | 102 | //Create the exporter 103 | exporter = AVAssetExportSession(asset: mixComposition, presetName:AVAssetExportPresetHighestQuality) 104 | 105 | exporter.outputURL = url 106 | exporter.outputFileType = AVFileTypeMPEG4 107 | exporter.shouldOptimizeForNetworkUse = true 108 | exporter.videoComposition = mainCompositionInst 109 | 110 | 111 | //Perform the export 112 | exporter!.exportAsynchronouslyWithCompletionHandler() { 113 | dispatch_async(dispatch_get_main_queue(), { () -> Void in 114 | self.exportDidFinish(self.exporter!) 115 | }) 116 | } 117 | } 118 | 119 | func orientationFromTransform(transform: CGAffineTransform) -> (orientation: UIImageOrientation, isPortrait: Bool) { 120 | var assetOrientation = UIImageOrientation.Up 121 | var isPortrait = false 122 | if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 { 123 | assetOrientation = .Right 124 | isPortrait = true 125 | } else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 { 126 | assetOrientation = .Left 127 | isPortrait = true 128 | } else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 { 129 | assetOrientation = .Up 130 | } else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 { 131 | assetOrientation = .Down 132 | } 133 | return (assetOrientation, isPortrait) 134 | } 135 | 136 | 137 | func exportDidFinish(session: AVAssetExportSession) { 138 | switch session.status { 139 | case AVAssetExportSessionStatus.Failed: 140 | print("Failed \(session.error)") 141 | case AVAssetExportSessionStatus.Cancelled: 142 | print("Cancelled \(session.error)") 143 | default: 144 | print("complete") 145 | print("\(session.outputURL)") 146 | saveFileAtURLToPhotoLibrary(session.outputURL!) 147 | } 148 | 149 | } 150 | 151 | func saveFileAtURLToPhotoLibrary(url:NSURL){ 152 | let lib = CustomPhotoAlbum() 153 | lib.saveVideo(url,metadata: ["DateVideoTaken":NSDate()]) 154 | } 155 | 156 | func applyVideoEffectsToComposition(composition: AVMutableVideoComposition, size: CGSize) { 157 | 158 | 159 | 160 | 161 | 162 | } 163 | 164 | 165 | } -------------------------------------------------------------------------------- /ResultsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResultsViewController.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 18/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | protocol ResultsControllerDelegate { 13 | func resultsDidCancel() 14 | } 15 | 16 | class ResultsViewController: VideoMaker,UITableViewDelegate,UITableViewDataSource,UITextFieldDelegate { 17 | 18 | 19 | 20 | //View outlets 21 | @IBOutlet weak var workOutDate: UILabel! 22 | @IBOutlet weak var workOutName: UILabel! 23 | @IBOutlet weak var carriedOutByTextfield: UITextField! 24 | @IBOutlet weak var judgedByTextField:UITextField! 25 | @IBOutlet weak var tableView: UITableView! 26 | 27 | @IBOutlet weak var progressLabel: UILabel! 28 | @IBOutlet weak var progressView: UIProgressView! 29 | 30 | 31 | var overlayLayer:CALayer! 32 | var parentLayer:CALayer! 33 | var videoLayer:CALayer! 34 | var layerGenerator = VideoOverlayGenerator() 35 | 36 | var delegate:ResultsControllerDelegate? 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | 41 | if UserSettings.sharedInstance.getStandardName() != "" { 42 | carriedOutByTextfield.text = UserSettings.sharedInstance.getStandardName() 43 | } 44 | 45 | if UserSettings.sharedInstance.getJudgeName() != "" { 46 | judgedByTextField.text = UserSettings.sharedInstance.getJudgeName() 47 | } 48 | } 49 | 50 | 51 | //Textfield delegate 52 | func textFieldShouldReturn(textField: UITextField) -> Bool { 53 | textField.resignFirstResponder() 54 | return true 55 | } 56 | 57 | //Table view dataSource Methods 58 | func numberOfSectionsInTableView(tableView: UITableView) -> Int { 59 | return 1 60 | } 61 | 62 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 63 | return 1 64 | } 65 | 66 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 67 | 68 | let cell = tableView.dequeueReusableCellWithIdentifier("menuCell", forIndexPath: indexPath) 69 | 70 | 71 | return cell 72 | } 73 | 74 | 75 | 76 | //Button actions 77 | @IBAction func cancelButton(sender: AnyObject) { 78 | 79 | let optionMenu = UIAlertController(title:"Are you sure, the video will be lost?", message: "", preferredStyle:UIAlertControllerStyle.ActionSheet) 80 | let harmful = UIAlertAction(title: "Delete", style: UIAlertActionStyle.Destructive, handler:{ 81 | (alert:UIAlertAction!) -> Void in 82 | self.delegate!.resultsDidCancel() 83 | }) 84 | let cancel = UIAlertAction(title:"Cancel", style: UIAlertActionStyle.Cancel, handler: nil) 85 | 86 | optionMenu.addAction(harmful) 87 | optionMenu.addAction(cancel) 88 | 89 | presentViewController(optionMenu, animated: true, completion: nil) 90 | 91 | 92 | } 93 | @IBAction func saveVideo(sender: AnyObject) { 94 | 95 | if carriedOutByTextfield.text != "" { 96 | UserSettings.sharedInstance.saveStandardName(carriedOutByTextfield.text!) 97 | } 98 | 99 | if judgedByTextField.text != "" { 100 | UserSettings.sharedInstance.saveJudgeName(judgedByTextField.text!) 101 | } 102 | 103 | 104 | let infoView = UIAlertController(title: "Create video", message: "Your video will be saved to your video library in a folder called 'Wodeo 2'. This may take some time depending on video length and quality.", preferredStyle: UIAlertControllerStyle.Alert) 105 | 106 | let ok = UIAlertAction(title:"Lets Go!", style: UIAlertActionStyle.Default, handler:{ 107 | 108 | (alert:UIAlertAction!) -> Void in 109 | 110 | self.showWorkingsOut() 111 | self.videoOutput() 112 | }) 113 | 114 | infoView.addAction(ok) 115 | 116 | presentViewController(infoView, animated:true, completion:nil) 117 | 118 | 119 | } 120 | 121 | func showWorkingsOut(){ 122 | workOutName.hidden = true 123 | workOutDate.hidden = true 124 | progressLabel.hidden = false 125 | progressView.hidden = false 126 | 127 | _ = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: #selector(ResultsViewController.progressTimerUpdated(_:)), userInfo: nil, repeats:true) 128 | 129 | 130 | 131 | } 132 | 133 | func progressTimerUpdated(timer:NSTimer){ 134 | progressView.progress = exporter.progress 135 | 136 | let frmt = NSNumberFormatter() 137 | frmt.numberStyle = NSNumberFormatterStyle.PercentStyle 138 | let currentPercentage = frmt.stringFromNumber(NSNumber(float:exporter.progress)) 139 | progressLabel.text = "Creating your video: \(currentPercentage!)" 140 | 141 | if exporter.progress == 1.0 { 142 | timer.invalidate() 143 | progressLabel.text = "Done!" 144 | delegate!.resultsDidCancel() 145 | } 146 | 147 | 148 | } 149 | 150 | //Admin 151 | override func prefersStatusBarHidden() -> Bool { 152 | return true 153 | } 154 | 155 | override func applyVideoEffectsToComposition(composition: AVMutableVideoComposition, size: CGSize) { 156 | 157 | parentLayer = CALayer() 158 | parentLayer.frame = CGRectMake(0.0, 0.0, size.width, size.height) 159 | videoLayer = CALayer() 160 | videoLayer.frame = CGRectMake(0.0, 0.0, size.width, size.height) 161 | parentLayer.addSublayer(videoLayer) 162 | overlayLayer = CALayer() 163 | overlayLayer.frame = CGRectMake(0, 0, size.width, size.height) 164 | overlayLayer.masksToBounds = true 165 | 166 | 167 | overlayLayer.addSublayer(layerGenerator.logo(size)) 168 | overlayLayer.addSublayer(layerGenerator.workOutAndDate(size, withText:"\(workOutName.text!) \(workOutDate.text!)")) 169 | 170 | if carriedOutByTextfield.text != "" { 171 | overlayLayer.addSublayer(layerGenerator.carriedOutBy(size, withText:"Carried out by: \(carriedOutByTextfield.text!)")) 172 | } 173 | 174 | if judgedByTextField.text != "" { 175 | overlayLayer.addSublayer(layerGenerator.judgedBy(size, withText:"Judged by: \(judgedByTextField.text!)")) 176 | } 177 | 178 | overlayLayer.addSublayer(layerGenerator.mainBackingLayer(size)) 179 | 180 | 181 | } 182 | 183 | func stringForWorkOurDetails() -> String { 184 | return "OverRide" 185 | } 186 | 187 | 188 | 189 | } 190 | -------------------------------------------------------------------------------- /StandardOverlay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StandardOverlay.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 16/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | class StandardOverlay: OverlayViewController,WODDelegate,ResultsControllerDelegate { 13 | 14 | 15 | //Down cast the work out to the current work out 16 | var standardWorkOut:StandardWOD! 17 | 18 | @IBOutlet weak var topView:RotatorView! 19 | @IBOutlet weak var bottomView:RotatorView! 20 | 21 | //Views specific to Standard overlay 22 | var repCountLabel = TimeTextLabel(fontSize: 30.0) 23 | var roundCountLabel = TimeTextLabel(fontSize: 30.0) 24 | 25 | //layout vars 26 | let counterLH:CGFloat = 35.0 27 | 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | standardWorkOut = workOut as! StandardWOD 33 | standardWorkOut.delegate = self 34 | } 35 | 36 | // override func viewDidAppear(animated: Bool) { 37 | // super.viewDidAppear(animated) 38 | // view.sendSubviewToBack(testView) 39 | // } 40 | 41 | 42 | override func layOutForPortrait() { 43 | super.layOutForPortrait() 44 | repCountLabel.frame = CGRectMake(fw * 0.6, 0.0, fw * 0.4,counterLH) 45 | roundCountLabel.frame = CGRectMake(fw * 0.6, counterLH, fw * 0.6, counterLH) 46 | 47 | view.layer.addSublayer(repCountLabel) 48 | view.layer.addSublayer(roundCountLabel) 49 | } 50 | 51 | override func layOutForLandscapeLeft() { 52 | super.layOutForLandscapeLeft() 53 | 54 | repCountLabel.frame = CGRectMake(0.0, 0.0, fw * 0.4, counterLH) 55 | repCountLabel.position = CGPointMake(counterLH / 2,fh / 2) 56 | repCountLabel.transform = CATransform3DRotate(repCountLabel.transform, CGFloat(-M_PI_2), 0.0, 0.0, 1.0) 57 | 58 | roundCountLabel.frame = CGRectMake(0.0, 0.0, fw * 0.4, counterLH) 59 | roundCountLabel.position = CGPointMake(counterLH / 2,fw * 0.4) 60 | roundCountLabel.transform = CATransform3DRotate(roundCountLabel.transform, CGFloat(-M_PI_2), 0.0, 0.0, 1.0) 61 | 62 | 63 | view.layer.addSublayer(repCountLabel) 64 | view.layer.addSublayer(roundCountLabel) 65 | 66 | 67 | } 68 | 69 | override func layOutForLandscapeRight() { 70 | super.layOutForLandscapeRight() 71 | 72 | repCountLabel.frame = CGRectMake(0.0, 0.0, fw * 0.4, counterLH) 73 | repCountLabel.position = CGPointMake(fw - (counterLH / 2),fh / 2) 74 | repCountLabel.transform = CATransform3DRotate(repCountLabel.transform, CGFloat(M_PI_2), 0.0, 0.0, 1.0) 75 | 76 | roundCountLabel.frame = CGRectMake(0.0, 0.0, fw * 0.4, counterLH) 77 | roundCountLabel.position = CGPointMake(fw - (counterLH / 2),fh * 0.8) 78 | roundCountLabel.transform = CATransform3DRotate(roundCountLabel.transform, CGFloat(M_PI_2), 0.0, 0.0, 1.0) 79 | 80 | 81 | view.layer.addSublayer(repCountLabel) 82 | view.layer.addSublayer(roundCountLabel) 83 | 84 | topView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2 * 2)) 85 | bottomView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2 * 2)) 86 | 87 | } 88 | 89 | override func cancelButtonPressed(){ 90 | super.cancelButtonPressed() 91 | standardWorkOut.stopWorkOut() 92 | } 93 | 94 | 95 | override func stopWatchDidCountDown(t: TimeInterval) { 96 | super.stopWatchDidCountDown(t) 97 | //the count down timer has stopped so add the count up timer 98 | standardWorkOut.startWorkOut() 99 | addButtonUI() 100 | } 101 | 102 | func addButtonUI(){ 103 | 104 | let buffer:CGFloat = 20.0 105 | 106 | let addRepButton = TiledView(frame: CGRectMake(0.0, 0.0, topView.frame.size.width - buffer, topView.frame.size.height - buffer)) 107 | addRepButton.center = CGPointMake(topView.frame.size.width / 2, topView.frame.size.height / 2) 108 | addRepButton.instruction = "Add Rep" 109 | addRepButton.top = true 110 | addRepButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action:#selector(StandardOverlay.addRep(_:)))) 111 | topView.addSubview(addRepButton) 112 | 113 | let addRoundButton = TiledView(frame:CGRectMake(0.0, 0.0, bottomView.frame.size.width - buffer, bottomView.frame.size.height - buffer)) 114 | addRoundButton.top = false 115 | addRoundButton.frame = CGRectMake(0.0, 0.0, bottomView.frame.size.width - buffer, bottomView.frame.size.height - buffer) 116 | addRoundButton.center = CGPointMake(bottomView.frame.size.width / 2, bottomView.frame.size.height / 2) 117 | addRoundButton.instruction = "Add Round" 118 | addRoundButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action:#selector(StandardOverlay.addRound(_:)))) 119 | bottomView.addSubview(addRoundButton) 120 | 121 | 122 | 123 | UIView.animateWithDuration(0.5, animations:{ 124 | self.topView.alpha = 1.0 125 | self.bottomView.alpha = 1.0 126 | }) 127 | } 128 | 129 | 130 | func addRep(sender:UITapGestureRecognizer){ 131 | standardWorkOut.addRep() 132 | repCountLabel.string = "Reps:\(standardWorkOut.rounds.last!.reps.count)" 133 | } 134 | 135 | func addRound(sender:UIGestureRecognizer){ 136 | standardWorkOut.addRound() 137 | roundCountLabel.string = "Rounds:\(standardWorkOut.rounds.count)" 138 | repCountLabel.string = "Reps:\(standardWorkOut.rounds.last!.reps.count)" 139 | } 140 | 141 | //Wod delegate methods 142 | func wodStopWatchDidUpdateToValue(timeValue: String) { 143 | if standardWorkOut.workOutTime > workOut!.totalVideoSecondsAllowed{ 144 | countDownStopWatch.stop() 145 | let cWorkOut = workOut as! StandardWOD 146 | cWorkOut.stopWorkOut() 147 | videoManager!.stopRecordingVideo { (videoURL, error) -> Void in 148 | 149 | } 150 | 151 | } 152 | countDownLabel.string = timeValue 153 | } 154 | 155 | override func cameraManagerDidGenerateVideoAsset(url: NSURL) { 156 | 157 | super.cameraManagerDidGenerateVideoAsset(url) 158 | //Add overlay and set it as the video manager 159 | let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("resultsScreen") as! StandardResultsVC 160 | presentViewController(viewController, animated: true, completion: nil) 161 | viewController.standardWorkout = standardWorkOut 162 | viewController.videoToMakeURL = url 163 | viewController.delegate = self 164 | } 165 | 166 | func resultsDidCancel() { 167 | dismissViewControllerAnimated(false, completion: { 168 | 169 | self.delegate!.overlayDidCancel() 170 | 171 | }) 172 | } 173 | 174 | func workOutDidEnd() { 175 | print("Work out ended") 176 | } 177 | 178 | 179 | } 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /MainMenuTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainMenuTableViewController.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 15/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ViewCanceling { 12 | func viewDidCancel() 13 | } 14 | 15 | class MainMenuTableViewController: UIViewController ,UITableViewDataSource,UITableViewDelegate,BallicticStashDelegate,ViewCanceling { 16 | 17 | @IBOutlet weak var tableView:UITableView! 18 | 19 | var menuItems = [(String,String,String)]() 20 | 21 | var wodToSend:WOD? 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | // Uncomment the following line to preserve selection between presentations 27 | // self.clearsSelectionOnViewWillAppear = false 28 | //tableView.backgroundView = UIImageView(image: UIImage(named: "bg")) 29 | tableView.contentInset = UIEdgeInsetsMake(9.0, 0.0, 0.0, 0.0) 30 | 31 | menuItems.append(("Standard","STANDARD","FOR TIME")) 32 | menuItems.append(("CountDown","COUNTDOWN","AMRAP")) 33 | menuItems.append(("Interval","INTERVAL","")) 34 | menuItems.append(("8","TABATA","")) 35 | 36 | 37 | 38 | } 39 | 40 | func ballisticStachDidReturn() { 41 | dismissViewControllerAnimated(true, completion: nil) 42 | } 43 | 44 | override func didReceiveMemoryWarning() { 45 | super.didReceiveMemoryWarning() 46 | // Dispose of any resources that can be recreated. 47 | } 48 | 49 | // MARK: - Table view data source 50 | 51 | func numberOfSectionsInTableView(tableView: UITableView) -> Int { 52 | // #warning Incomplete implementation, return the number of sections 53 | return 1 54 | } 55 | 56 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 57 | // #warning Incomplete implementation, return the number of rows 58 | return menuItems.count 59 | } 60 | 61 | 62 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 63 | 64 | let cell = tableView.dequeueReusableCellWithIdentifier("menuCell", forIndexPath: indexPath) as! MainMenuCell 65 | 66 | cell.title.text = menuItems[indexPath.row].1 67 | cell.subTitle.text = menuItems[indexPath.row].2 68 | cell.cellImage.image = UIImage(named: menuItems[indexPath.row].0) 69 | 70 | return cell 71 | } 72 | 73 | 74 | 75 | func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool { 76 | return true 77 | } 78 | 79 | //MARK: - Table view delegate 80 | func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 81 | tableView.deselectRowAtIndexPath(indexPath, animated: false) 82 | 83 | switch indexPath.row { 84 | case 0: 85 | wodToSend = StandardWOD(with: "STANDARD") 86 | performSegueWithIdentifier("videoViewcontroller", sender: nil) 87 | 88 | case 1: 89 | wodToSend = CountDownWOD(with:"AMRAP") 90 | performSegueWithIdentifier("countDownSettings", sender: nil) 91 | 92 | case 2: 93 | wodToSend = IntervalWOD(with:"INTERVAL") 94 | performSegueWithIdentifier("interval", sender: nil) 95 | 96 | case 3: 97 | wodToSend = TabataWOD(with: "TABATA") 98 | performSegueWithIdentifier("tabata", sender: nil) 99 | default:print("No cell selected") 100 | 101 | } 102 | } 103 | 104 | // MARK: - Navigation 105 | 106 | override func shouldPerformSegueWithIdentifier(identifier: String, sender: AnyObject?) -> Bool { 107 | switch identifier { 108 | case "videoViewcontroller": 109 | if !UserSettings.sharedInstance.getUserVideoMessageOptOut(){ 110 | let infoView = UIAlertController(title: "5 Minute Limit", message: "If you like our app, unlock more minutes and help support future great features, with the 'Unlimited Video' purchase. (It's as cheap as they would allow us!)", preferredStyle: UIAlertControllerStyle.Alert) 111 | infoView.view.tintColor = UIColor(red: 240/255.0, green: 178/255.0, blue: 71/255.0, alpha: 1.0) 112 | 113 | let ok = UIAlertAction(title:"Lets Go!", style: UIAlertActionStyle.Default, handler:{ 114 | (alert:UIAlertAction!) -> Void in 115 | self.performSegueWithIdentifier("wodeoProducts", sender: nil) 116 | }) 117 | 118 | let no = UIAlertAction(title: "No thanks..", style: UIAlertActionStyle.Cancel, handler:{ (alert:UIAlertAction!) -> Void in 119 | UserSettings.sharedInstance.saveUserVideoMessageOptOut() 120 | self.performSegueWithIdentifier("videoViewcontroller", sender: nil) 121 | }) 122 | 123 | infoView.addAction(ok) 124 | infoView.addAction(no) 125 | 126 | presentViewController(infoView, animated:true, completion:nil) 127 | return false 128 | }else{ 129 | return true 130 | } 131 | 132 | default: 133 | return true 134 | } 135 | } 136 | 137 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 138 | super.prepareForSegue(segue, sender: sender) 139 | 140 | if !shouldPerformSegueWithIdentifier(segue.identifier!, sender: sender){ 141 | return 142 | } 143 | 144 | switch segue.identifier! { 145 | case "wodeoProducts" :print("Going to products") 146 | let vc = segue.destinationViewController as! WodeoProductPage 147 | vc.delegate = self 148 | 149 | case "videoViewcontroller": 150 | 151 | let vc = segue.destinationViewController as! VideoViewController 152 | vc.workout = wodToSend 153 | vc.delegate = self 154 | 155 | case "countDownSettings" : 156 | let vc = segue.destinationViewController as! SettingsViewController 157 | vc.wod = wodToSend 158 | vc.delegate = self 159 | 160 | case "interval" : 161 | let vc = segue.destinationViewController as! IntervalSettingsVC 162 | vc.wod = wodToSend 163 | vc.delegate = self 164 | 165 | case "tabata" : 166 | let vc = segue.destinationViewController as! TabataSettingsVC 167 | vc.wod = wodToSend 168 | vc.delegate = self 169 | 170 | default:print("Dont know what was picked") 171 | } 172 | 173 | 174 | } 175 | 176 | //View cancelling delegate 177 | func viewDidCancel() { 178 | dismissViewControllerAnimated(true, completion:nil) 179 | } 180 | 181 | 182 | override func prefersStatusBarHidden() -> Bool { 183 | return true 184 | } 185 | 186 | override func shouldAutorotate() -> Bool { 187 | return false 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /IAPHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IAPHelper.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 28/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import StoreKit 10 | 11 | /// Notification that is generated when a product is purchased. 12 | public let IAPHelperProductPurchasedNotification = "IAPHelperProductPurchasedNotification" 13 | 14 | /// Product identifiers are unique strings registered on the app store. 15 | public typealias ProductIdentifier = String 16 | 17 | /// Completion handler called when products are fetched. 18 | public typealias RequestProductsCompletionHandler = (success: Bool, products: [SKProduct]) -> () 19 | 20 | 21 | /// A Helper class for In-App-Purchases, it can fetch products, tell you if a product has been purchased, 22 | /// purchase products, and restore purchases. Uses NSUserDefaults to cache if a product has been purchased. 23 | public class IAPHelper : NSObject { 24 | 25 | /// MARK: - Private Properties 26 | 27 | // Used to keep track of the possible products and which ones have been purchased. 28 | private let productIdentifiers: Set 29 | private var purchasedProductIdentifiers = Set() 30 | 31 | // Used by SKProductsRequestDelegate 32 | private var productsRequest: SKProductsRequest? 33 | private var completionHandler: RequestProductsCompletionHandler? 34 | 35 | /// MARK: - User facing API 36 | 37 | /// Initialize the helper. Pass in the set of ProductIdentifiers supported by the app. 38 | public init(productIdentifiers: Set) { 39 | self.productIdentifiers = productIdentifiers 40 | super.init() 41 | SKPaymentQueue.defaultQueue().addTransactionObserver(self) 42 | 43 | 44 | for productIdentifier in productIdentifiers { 45 | let purchased = NSUserDefaults.standardUserDefaults().boolForKey(productIdentifier) 46 | if purchased { 47 | purchasedProductIdentifiers.insert(productIdentifier) 48 | print("Previously purchased: \(productIdentifier)") 49 | } else { 50 | print("Not purchased: \(productIdentifier)") 51 | } 52 | } 53 | } 54 | 55 | public class func canMakePayments() -> Bool { 56 | return SKPaymentQueue.canMakePayments() 57 | } 58 | 59 | 60 | /// Gets the list of SKProducts from the Apple server calls the handler with the list of products. 61 | public func requestProductsWithCompletionHandler(handler: RequestProductsCompletionHandler) { 62 | completionHandler = handler 63 | productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers) 64 | productsRequest?.delegate = self 65 | productsRequest?.start() 66 | } 67 | 68 | /// Initiates purchase of a product. 69 | public func purchaseProduct(product: SKProduct) { 70 | print("Buying \(product.productIdentifier)...") 71 | let payment = SKPayment(product: product) 72 | SKPaymentQueue.defaultQueue().addPayment(payment) 73 | } 74 | 75 | /// Given the product identifier, returns true if that product has been purchased. 76 | public func isProductPurchased(productIdentifier: ProductIdentifier) -> Bool { 77 | return purchasedProductIdentifiers.contains(productIdentifier) 78 | } 79 | 80 | /// If the state of whether purchases have been made is lost (e.g. the 81 | /// user deletes and reinstalls the app) this will recover the purchases. 82 | public func restoreCompletedTransactions() { 83 | SKPaymentQueue.defaultQueue().restoreCompletedTransactions() 84 | } 85 | } 86 | 87 | // MARK: - SKProductsRequestDelegate 88 | 89 | extension IAPHelper: SKProductsRequestDelegate { 90 | public func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) { 91 | print("Loaded list of products...") 92 | let products = response.products 93 | completionHandler?(success: true, products: products) 94 | clearRequest() 95 | 96 | // debug printing 97 | for p in products { 98 | print("Found product: \(p.productIdentifier) \(p.localizedTitle) \(p.price.floatValue)") 99 | } 100 | } 101 | 102 | public func request(request: SKRequest, didFailWithError error: NSError) { 103 | print("Failed to load list of products.") 104 | print("Error: \(error)") 105 | clearRequest() 106 | } 107 | 108 | private func clearRequest() { 109 | productsRequest = nil 110 | completionHandler = nil 111 | } 112 | } 113 | 114 | extension IAPHelper: SKPaymentTransactionObserver { 115 | /// This is a function called by the payment queue, not to be called directly. 116 | /// For each transaction act accordingly, save in the purchased cache, issue notifications, 117 | /// mark the transaction as complete. 118 | public func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { 119 | for transaction in transactions { 120 | switch (transaction.transactionState) { 121 | case .Purchased: 122 | completeTransaction(transaction) 123 | break 124 | case .Failed: 125 | failedTransaction(transaction) 126 | break 127 | case .Restored: 128 | restoreTransaction(transaction) 129 | break 130 | case .Deferred: 131 | break 132 | case .Purchasing: 133 | break 134 | } 135 | } 136 | 137 | } 138 | 139 | private func completeTransaction(transaction: SKPaymentTransaction) { 140 | print("completeTransaction...") 141 | provideContentForProductIdentifier(transaction.payment.productIdentifier) 142 | SKPaymentQueue.defaultQueue().finishTransaction(transaction) 143 | } 144 | 145 | private func restoreTransaction(transaction: SKPaymentTransaction) { 146 | let productIdentifier = transaction.originalTransaction!.payment.productIdentifier 147 | print("restoreTransaction... \(productIdentifier)") 148 | provideContentForProductIdentifier(productIdentifier) 149 | SKPaymentQueue.defaultQueue().finishTransaction(transaction) 150 | } 151 | 152 | // Helper: Saves the fact that the product has been purchased and posts a notification. 153 | private func provideContentForProductIdentifier(productIdentifier: String) { 154 | purchasedProductIdentifiers.insert(productIdentifier) 155 | NSUserDefaults.standardUserDefaults().setBool(true, forKey: productIdentifier) 156 | NSUserDefaults.standardUserDefaults().synchronize() 157 | NSNotificationCenter.defaultCenter().postNotificationName(IAPHelperProductPurchasedNotification, object: productIdentifier) 158 | } 159 | 160 | private func failedTransaction(transaction: SKPaymentTransaction) { 161 | print("failedTransaction...") 162 | if transaction.error!.code != SKErrorCode.PaymentCancelled.rawValue { 163 | print("Transaction error: \(transaction.error!.localizedDescription)") 164 | } 165 | SKPaymentQueue.defaultQueue().finishTransaction(transaction) 166 | } 167 | } 168 | 169 | -------------------------------------------------------------------------------- /CountdownOverlay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CountdownOverlay.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 21/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CountdownOverlay: OverlayViewController ,WODDelegate , ResultsControllerDelegate{ 12 | 13 | //Downcast the work out to the current workout 14 | var countDownWorkOut:CountDownWOD! 15 | 16 | 17 | 18 | @IBOutlet weak var topView:RotatorView! 19 | @IBOutlet weak var bottomView:RotatorView! 20 | 21 | //Views specific to Standard overlay 22 | var repCountLabel = TimeTextLabel(fontSize: 30.0) 23 | var roundCountLabel = TimeTextLabel(fontSize: 30.0) 24 | 25 | //layout vars 26 | let counterLH:CGFloat = 35.0 27 | 28 | 29 | override func viewDidLoad() { 30 | super.viewDidLoad() 31 | 32 | countDownWorkOut = workOut as! CountDownWOD 33 | countDownWorkOut.delegate = self 34 | } 35 | 36 | // override func viewDidAppear(animated: Bool) { 37 | // super.viewDidAppear(animated) 38 | // view.sendSubviewToBack(testView) 39 | // } 40 | 41 | override func layOutForPortrait() { 42 | super.layOutForPortrait() 43 | repCountLabel.frame = CGRectMake(2.0, 0.0, fw,counterLH) 44 | roundCountLabel.frame = CGRectMake(2.0, counterLH,fw, counterLH) 45 | 46 | 47 | view.layer.addSublayer(repCountLabel) 48 | view.layer.addSublayer(roundCountLabel) } 49 | 50 | override func layOutForLandscapeLeft() { 51 | super.layOutForLandscapeLeft() 52 | 53 | let labelWidth = (fh/2) - 5.0 54 | 55 | repCountLabel.frame = CGRectMake(0.0, 0.0,labelWidth, counterLH) 56 | repCountLabel.alignmentMode = kCAAlignmentRight 57 | repCountLabel.position = CGPointMake(counterLH / 2,labelWidth/2) 58 | repCountLabel.transform = CATransform3DRotate(repCountLabel.transform, CGFloat(-M_PI_2), 0.0, 0.0, 1.0) 59 | 60 | roundCountLabel.frame = CGRectMake(0.0, 0.0,labelWidth, counterLH) 61 | roundCountLabel.position = CGPointMake(counterLH / 2,(fh - (labelWidth / 2)) - 5) 62 | roundCountLabel.transform = CATransform3DRotate(roundCountLabel.transform, CGFloat(-M_PI_2), 0.0, 0.0, 1.0) 63 | 64 | view.layer.addSublayer(repCountLabel) 65 | view.layer.addSublayer(roundCountLabel) 66 | 67 | 68 | 69 | } 70 | 71 | override func layOutForLandscapeRight() { 72 | super.layOutForLandscapeRight() 73 | 74 | let labelWidth = (fh/2) - 5.0 75 | 76 | repCountLabel.frame = CGRectMake(0.0, 0.0,labelWidth, counterLH) 77 | repCountLabel.alignmentMode = kCAAlignmentRight 78 | repCountLabel.position = CGPointMake(fw - (counterLH / 2),(fh - (labelWidth / 2)) - 5) 79 | repCountLabel.transform = CATransform3DRotate(repCountLabel.transform, CGFloat(M_PI_2), 0.0, 0.0, 1.0) 80 | 81 | roundCountLabel.frame = CGRectMake(0.0, 0.0,labelWidth, counterLH) 82 | roundCountLabel.position = CGPointMake(fw - (counterLH / 2),labelWidth/2) 83 | roundCountLabel.transform = CATransform3DRotate(roundCountLabel.transform, CGFloat(M_PI_2), 0.0, 0.0, 1.0) 84 | 85 | view.layer.addSublayer(repCountLabel) 86 | view.layer.addSublayer(roundCountLabel) 87 | 88 | 89 | topView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2 * 2)) 90 | bottomView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2 * 2)) 91 | 92 | 93 | } 94 | 95 | override func cancelButtonPressed(){ 96 | super.cancelButtonPressed() 97 | countDownWorkOut.stopWorkOut() 98 | } 99 | 100 | 101 | override func stopWatchDidCountDown(t: TimeInterval) { 102 | super.stopWatchDidCountDown(t) 103 | //the count down timer has stopped so add the count up timer 104 | countDownWorkOut.startWorkOut() 105 | addButtonUI() 106 | } 107 | 108 | func addButtonUI(){ 109 | 110 | let buffer:CGFloat = 20.0 111 | 112 | let addRepButton = TiledView() 113 | addRepButton.frame = CGRectMake(0.0, 0.0, topView.frame.size.width - buffer, topView.frame.size.height - buffer) 114 | addRepButton.center = CGPointMake(topView.frame.size.width / 2, topView.frame.size.height / 2) 115 | addRepButton.instruction = "Add Rep" 116 | addRepButton.top = true 117 | addRepButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action:#selector(CountdownOverlay.addRep(_:)))) 118 | topView.addSubview(addRepButton) 119 | 120 | let addRoundButton = TiledView() 121 | addRoundButton.frame = CGRectMake(0.0, 0.0, bottomView.frame.size.width - buffer, bottomView.frame.size.height - buffer) 122 | addRoundButton.center = CGPointMake(bottomView.frame.size.width / 2, bottomView.frame.size.height / 2) 123 | addRoundButton.instruction = "Add Round" 124 | addRoundButton.top = false 125 | addRoundButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action:#selector(CountdownOverlay.addRound(_:)))) 126 | bottomView.addSubview(addRoundButton) 127 | 128 | 129 | 130 | 131 | UIView.animateWithDuration(0.5, animations:{ 132 | self.topView.alpha = 1.0 133 | self.bottomView.alpha = 1.0 134 | }) 135 | } 136 | 137 | 138 | func addRep(sender:UITapGestureRecognizer){ 139 | countDownWorkOut.addRep() 140 | repCountLabel.string = "Reps:\(countDownWorkOut.rounds.last!.reps.count)" 141 | } 142 | 143 | func addRound(sender:UITapGestureRecognizer){ 144 | countDownWorkOut.addRound() 145 | roundCountLabel.string = "Rounds:\(countDownWorkOut.rounds.count)" 146 | repCountLabel.string = "Reps:\(countDownWorkOut.rounds.last!.reps.count)" 147 | } 148 | 149 | //Wod delegate methods 150 | func wodStopWatchDidUpdateToValue(timeValue: String) { 151 | countDownLabel.string = (timeValue) 152 | } 153 | 154 | func workOutDidEnd() { 155 | countDownStopWatch.stop() 156 | let cWorkOut = workOut as! CountDownWOD 157 | cWorkOut.stopWorkOut() 158 | videoManager!.stopRecordingVideo { (videoURL, error) -> Void in 159 | 160 | } 161 | 162 | } 163 | 164 | override func cameraManagerDidGenerateVideoAsset(url: NSURL) { 165 | 166 | super.cameraManagerDidGenerateVideoAsset(url) 167 | //Add overlay and set it as the video manager 168 | let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("countDownResults") as! CountDownResultsVC 169 | presentViewController(viewController, animated: true, completion: nil) 170 | viewController.countDownWorkout = countDownWorkOut 171 | viewController.videoToMakeURL = url 172 | viewController.delegate = self 173 | } 174 | 175 | override func saveButtonPressed(){ 176 | countDownStopWatch.stop() 177 | let cWorkOut = workOut as! CountDownWOD 178 | cWorkOut.stopWorkOut() 179 | videoManager!.stopRecordingVideo { (videoURL, error) -> Void in 180 | 181 | } 182 | } 183 | 184 | 185 | func resultsDidCancel() { 186 | dismissViewControllerAnimated(false, completion: { 187 | 188 | self.delegate!.overlayDidCancel() 189 | 190 | }) 191 | } 192 | 193 | 194 | } 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /StopWatch.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeTest.swift 3 | // timerTesting 4 | // 5 | // Created by Gareth Long on 13/02/2016. 6 | // Copyright © 2016 gazlongapps. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import AVFoundation 11 | 12 | 13 | extension Int { 14 | 15 | func timesThrough() -> String{ 16 | if self == 1 { 17 | return "1 time through" 18 | }else{ 19 | return "\(self) times through" 20 | } 21 | } 22 | 23 | } 24 | 25 | extension Double { 26 | 27 | /// Rounds the double to decimal places value 28 | func stringForTimeInterval(withMicroSeconds:Bool) -> String { 29 | 30 | var retString = "" 31 | let startTime = self 32 | let baseTime = NSDate(timeIntervalSince1970: startTime) 33 | let dateFormatter = NSDateFormatter() 34 | 35 | 36 | if withMicroSeconds { 37 | 38 | dateFormatter.dateFormat = "HH:mm:ss.SS" 39 | dateFormatter.timeZone = NSTimeZone(forSecondsFromGMT:0) 40 | //Remove the blank components 41 | let slipDownString = dateFormatter.stringFromDate(baseTime).componentsSeparatedByString(":") 42 | 43 | for part in slipDownString { 44 | if part != "00" { 45 | retString += part 46 | if part.rangeOfString(".") == nil { 47 | retString += ":" 48 | } 49 | } 50 | } 51 | 52 | }else{ 53 | 54 | dateFormatter.dateFormat = "HH:mm:ss" 55 | dateFormatter.timeZone = NSTimeZone(forSecondsFromGMT:0) 56 | 57 | //Remove the blank components 58 | var slipDownString = dateFormatter.stringFromDate(baseTime).componentsSeparatedByString(":") 59 | 60 | //trim out the initial zero's 61 | 62 | 63 | 64 | 65 | 66 | //mark leading zeros for removal 67 | for i in 0.. String { 109 | if let secs = secondsAsDouble { 110 | return secs.stringForTimeInterval(withMilliseconds) 111 | }else { 112 | return "Time Interval Set" 113 | } 114 | } 115 | 116 | } 117 | 118 | enum StopWatchType { 119 | case CountUp 120 | case CountDown 121 | } 122 | 123 | protocol StopWatchDelegate { 124 | 125 | func stopWatchDidUpdateWithTimeInterval(t:TimeInterval) 126 | func stopWatchDidCountDown(t:TimeInterval) 127 | } 128 | 129 | 130 | class StopWatch:NSObject { 131 | 132 | var startTime:CFAbsoluteTime! 133 | var timer:NSTimer? 134 | let type:StopWatchType 135 | var timerStarted = false 136 | var countDownFromSeconds:Int? 137 | var delegate:StopWatchDelegate! 138 | var inLastThreeSeconds = false 139 | var speechSynth = AVSpeechSynthesizer() 140 | //let myUtterence = AVSpeechUtterance(string:"3......2......1") 141 | 142 | var currentDuration:TimeInterval { 143 | if timerStarted { 144 | let currentTime = CFAbsoluteTimeGetCurrent() 145 | return TimeInterval(secondsAsDouble:currentTime - startTime!) 146 | }else{ 147 | return TimeInterval(secondsAsDouble: 0.0) 148 | } 149 | 150 | } 151 | 152 | init(type:StopWatchType){ 153 | self.type = type 154 | } 155 | 156 | func stop(){ 157 | 158 | if speechSynth.speaking { 159 | 160 | speechSynth.stopSpeakingAtBoundary(.Immediate) 161 | } 162 | 163 | if let t = timer { 164 | t.invalidate() 165 | } 166 | } 167 | 168 | func start(){ 169 | inLastThreeSeconds = false 170 | startTime = CFAbsoluteTimeGetCurrent() 171 | timer = NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector:#selector(StopWatch.update), userInfo: nil, repeats: true) 172 | timerStarted = true 173 | } 174 | 175 | func update(){ 176 | switch type { 177 | case .CountUp:updateCurrentDuration() 178 | case .CountDown:updateTimeRemaining() 179 | } 180 | } 181 | 182 | 183 | func delay(seconds seconds: Double, completion:()->()) { 184 | let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64( Double(NSEC_PER_SEC) * seconds )) 185 | 186 | dispatch_after(popTime, dispatch_get_main_queue()) { 187 | completion() 188 | } 189 | } 190 | 191 | var speaking:Bool = false 192 | func countDown() { 193 | speaking = true 194 | let priority = QOS_CLASS_USER_INITIATED 195 | dispatch_async(dispatch_get_global_queue(priority, 0)) { 196 | 197 | 198 | let utterence = AVSpeechUtterance(string:"3") 199 | self.speechSynth.speakUtterance(utterence) 200 | 201 | self.delay(seconds: 1, completion: { 202 | let utterence = AVSpeechUtterance(string:"2") 203 | self.speechSynth.speakUtterance(utterence) 204 | }) 205 | 206 | self.delay(seconds: 2, completion: { 207 | let utterence = AVSpeechUtterance(string:"1") 208 | self.speechSynth.speakUtterance(utterence) 209 | 210 | }) 211 | 212 | 213 | } 214 | } 215 | 216 | func updateTimeRemaining() { 217 | if timerStarted { 218 | let currentTime = CFAbsoluteTimeGetCurrent() 219 | 220 | if let cdt = countDownFromSeconds { 221 | let ct = Double(cdt) - (currentTime - startTime!) 222 | 223 | if ct <= 0 { 224 | self.speaking = false 225 | delegate.stopWatchDidCountDown(TimeInterval(secondsAsDouble:0.0)) 226 | }else{ 227 | 228 | if ct <= 4 && !speaking { 229 | countDown() 230 | } 231 | 232 | delegate.stopWatchDidUpdateWithTimeInterval(TimeInterval(secondsAsDouble:ct)) 233 | } 234 | 235 | }else{ 236 | print("Count down time not set") 237 | } 238 | }else{ 239 | print("Timer not started") 240 | } 241 | 242 | } 243 | func updateCurrentDuration() { 244 | if timerStarted { 245 | let currentTime = CFAbsoluteTimeGetCurrent() 246 | if delegate != nil { 247 | delegate.stopWatchDidUpdateWithTimeInterval(TimeInterval(secondsAsDouble:currentTime - startTime!)) 248 | } 249 | }else{ 250 | print("Timer not started") 251 | } 252 | } 253 | 254 | } -------------------------------------------------------------------------------- /TabataOverlay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabataOverlay.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 27/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TabataOverlay: OverlayViewController ,WODDelegate,TabataWOD_Delegate,ResultsControllerDelegate { 12 | 13 | var tabataWOD:TabataWOD! 14 | @IBOutlet weak var mainRotatorView:RotatorView! 15 | var repCountLabel = TimeTextLabel(fontSize:30.0) 16 | var roundCountLabel = TimeTextLabel(fontSize: 30.0) 17 | var restingLabel = TimeTextLabel(fontSize: 100.0) 18 | //layout vars 19 | let counterLH:CGFloat = 35.0 20 | 21 | var isResting = false 22 | 23 | 24 | 25 | //View lifecycle 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | 30 | 31 | 32 | print("Interval layout is loading") 33 | 34 | tabataWOD = workOut as! TabataWOD 35 | tabataWOD.delegate = self 36 | tabataWOD.tabataDelegate = self 37 | } 38 | 39 | override func viewDidDisappear(animated: Bool) { 40 | super.viewDidDisappear(animated) 41 | 42 | } 43 | 44 | 45 | //Stop watch delegate 46 | override func stopWatchDidCountDown(t: TimeInterval) { 47 | super.stopWatchDidCountDown(t) 48 | tabataWOD.startWorkOut() 49 | addButtonUI() 50 | 51 | } 52 | 53 | 54 | //Create and layout the UI 55 | override func createUI() { 56 | super.createUI() 57 | 58 | } 59 | 60 | func addButtonUI(){ 61 | 62 | let addRepButton = TiledView() 63 | addRepButton.frame = CGRectMake(0.0, 0.0, view.frame.size.width, view.frame.size.height) 64 | addRepButton.instruction = "Add Rep" 65 | addRepButton.top = true 66 | addRepButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action:#selector(TabataOverlay.addRep(_:)))) 67 | mainRotatorView.addSubview(addRepButton) 68 | 69 | restingLabel.string = "WORK" 70 | 71 | UIView.animateWithDuration(0.5, animations:{ 72 | self.mainRotatorView.alpha = 1.0 73 | }) 74 | } 75 | 76 | 77 | override func layOutForPortrait() { 78 | super.layOutForPortrait() 79 | repCountLabel.frame = CGRectMake(2.0, 0.0, fw,counterLH) 80 | roundCountLabel.frame = CGRectMake(2.0, counterLH,fw, counterLH) 81 | restingLabel.frame = CGRectMake(0.0, fh - 200.0, fw, 200.0) 82 | restingLabel.alignmentMode = kCAAlignmentCenter 83 | 84 | 85 | view.layer.addSublayer(restingLabel) 86 | view.layer.addSublayer(repCountLabel) 87 | view.layer.addSublayer(roundCountLabel) 88 | } 89 | 90 | override func layOutForLandscapeLeft() { 91 | super.layOutForLandscapeLeft() 92 | 93 | let labelWidth = (fh/2) - 5.0 94 | 95 | repCountLabel.frame = CGRectMake(0.0, 0.0,labelWidth, counterLH) 96 | repCountLabel.alignmentMode = kCAAlignmentRight 97 | repCountLabel.position = CGPointMake(counterLH / 2,labelWidth/2) 98 | repCountLabel.transform = CATransform3DRotate(repCountLabel.transform, CGFloat(-M_PI_2), 0.0, 0.0, 1.0) 99 | 100 | roundCountLabel.frame = CGRectMake(0.0, 0.0,labelWidth, counterLH) 101 | roundCountLabel.position = CGPointMake(counterLH / 2,(fh - (labelWidth / 2)) - 5) 102 | roundCountLabel.transform = CATransform3DRotate(roundCountLabel.transform, CGFloat(-M_PI_2), 0.0, 0.0, 1.0) 103 | 104 | restingLabel.frame = CGRectMake(0.0,0.0, fw, 200.0) 105 | restingLabel.position = CGPointMake(fw - 100.0, fh/2) 106 | restingLabel.alignmentMode = kCAAlignmentCenter 107 | restingLabel.transform = CATransform3DRotate(restingLabel.transform, CGFloat(-M_PI_2), 0.0, 0.0, 1.0) 108 | 109 | view.layer.addSublayer(restingLabel) 110 | 111 | view.layer.addSublayer(repCountLabel) 112 | view.layer.addSublayer(roundCountLabel) 113 | 114 | 115 | 116 | } 117 | 118 | override func layOutForLandscapeRight() { 119 | super.layOutForLandscapeRight() 120 | 121 | let labelWidth = (fh/2) - 5.0 122 | 123 | repCountLabel.frame = CGRectMake(0.0, 0.0,labelWidth, counterLH) 124 | repCountLabel.alignmentMode = kCAAlignmentRight 125 | repCountLabel.position = CGPointMake(fw - (counterLH / 2),(fh - (labelWidth / 2)) - 5) 126 | repCountLabel.transform = CATransform3DRotate(repCountLabel.transform, CGFloat(M_PI_2), 0.0, 0.0, 1.0) 127 | 128 | roundCountLabel.frame = CGRectMake(0.0, 0.0,labelWidth, counterLH) 129 | roundCountLabel.position = CGPointMake(fw - (counterLH / 2),labelWidth/2) 130 | roundCountLabel.transform = CATransform3DRotate(roundCountLabel.transform, CGFloat(M_PI_2), 0.0, 0.0, 1.0) 131 | 132 | restingLabel.frame = CGRectMake(0.0,0.0, fw, 200.0) 133 | restingLabel.position = CGPointMake(100.0, fh/2) 134 | restingLabel.alignmentMode = kCAAlignmentCenter 135 | restingLabel.transform = CATransform3DRotate(restingLabel.transform, CGFloat(M_PI_2), 0.0, 0.0, 1.0) 136 | 137 | view.layer.addSublayer(restingLabel) 138 | 139 | 140 | view.layer.addSublayer(repCountLabel) 141 | view.layer.addSublayer(roundCountLabel) 142 | 143 | 144 | mainRotatorView.transform = CGAffineTransformMakeRotation(CGFloat(M_PI_2 * 2)) 145 | 146 | 147 | } 148 | 149 | 150 | 151 | //Wod delegate 152 | func wodStopWatchDidUpdateToValue(timeValue: String) { 153 | countDownLabel.string = timeValue 154 | } 155 | 156 | func workOutDidEnd() { 157 | tabataWOD.stopWorkOut() 158 | videoManager!.stopRecordingVideo { (videoURL, error) -> Void in 159 | } 160 | } 161 | 162 | //Tabata WOD delegate 163 | func tabataWODDidMoveToRoundWithName(name: String) { 164 | repCountLabel.string = "" 165 | roundCountLabel.string = name 166 | } 167 | 168 | func tabataWODDidMoveToRestPeriod() { 169 | isResting = true 170 | restingLabel.string = "REST" 171 | restingLabel.foregroundColor = UIColor.redColor().CGColor 172 | 173 | } 174 | 175 | func tabataWODDidMoveOutOfRestPeriod() { 176 | isResting = false 177 | restingLabel.string = "WORK" 178 | restingLabel.foregroundColor = UIColor.greenColor().CGColor 179 | } 180 | 181 | 182 | 183 | 184 | 185 | //Buttons 186 | func addRep(sender:UITapGestureRecognizer){ 187 | 188 | if !isResting { 189 | if sender.state == .Ended{ 190 | repCountLabel.string = tabataWOD.addRep() 191 | } 192 | } 193 | 194 | } 195 | 196 | override func cancelButtonPressed(){ 197 | super.cancelButtonPressed() 198 | tabataWOD.stopWorkOut() 199 | 200 | } 201 | 202 | override func cameraManagerDidGenerateVideoAsset(url: NSURL) { 203 | 204 | super.cameraManagerDidGenerateVideoAsset(url) 205 | //Add overlay and set it as the video manager 206 | let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("TabataResultsVC") as! TabataResultsVC 207 | presentViewController(viewController, animated: true, completion: nil) 208 | viewController.tabataWOD = tabataWOD 209 | viewController.videoToMakeURL = url 210 | viewController.delegate = self 211 | } 212 | 213 | func resultsDidCancel() { 214 | dismissViewControllerAnimated(true, completion:nil) 215 | } 216 | 217 | override func saveButtonPressed(){ 218 | countDownStopWatch.stop() 219 | let cWorkOut = workOut as! TabataWOD 220 | cWorkOut.stopWorkOut() 221 | videoManager!.stopRecordingVideo { (videoURL, error) -> Void in 222 | 223 | } 224 | } 225 | 226 | } 227 | -------------------------------------------------------------------------------- /OverlayViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OverlayViewController.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 16/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AVFoundation 11 | 12 | protocol OverlayDelegate { 13 | 14 | func overlayDidCancel() 15 | func overlayDidSave() 16 | 17 | } 18 | 19 | class OverlayViewController: UIViewController,StopWatchDelegate,CameraManagerDelegate { 20 | 21 | 22 | var videoManager:CameraManager? 23 | 24 | //Layout variables 25 | let sbw:CGFloat = 60.0 26 | let sbh:CGFloat = 60.0 27 | let slh:CGFloat = 70.0 28 | var fw:CGFloat { 29 | return view.frame.size.width 30 | } 31 | var fh:CGFloat { 32 | return view.frame.size.height 33 | } 34 | 35 | //Overlay settings 36 | var delegate:OverlayDelegate? 37 | var workOut:WOD? 38 | var videoSettings:VideoSettings! 39 | var hasCountDown:Bool = false 40 | 41 | //Overlay views 42 | var cancelButton:UIButton! 43 | var saveButton:UIButton! 44 | var countDownLabel = TimeTextLabel(fontSize: 30.0) 45 | //var testView:UIImageView! 46 | 47 | var countDownStopWatch = StopWatch(type:StopWatchType.CountDown) 48 | 49 | 50 | 51 | 52 | // Lay out functions 53 | func layOutForPortrait() { 54 | 55 | // testView.frame = view.bounds 56 | 57 | // view.addSubview(testView) 58 | 59 | 60 | saveButton.frame = CGRectMake(fw - sbw,fh-sbh,sbw,sbh) 61 | cancelButton.frame = CGRectMake(0.0, fh-sbh, sbw, sbh) 62 | 63 | countDownLabel.frame = CGRectMake(0.0, 0.0, fw,fw * 0.5) 64 | countDownLabel.position = CGPointMake(fw / 2,fh / 2) 65 | 66 | view.addSubview(saveButton) 67 | view.addSubview(cancelButton) 68 | view.layer.addSublayer(countDownLabel) 69 | 70 | addPulsing() 71 | } 72 | 73 | func layOutForLandscapeLeft(){ 74 | 75 | saveButton.frame = CGRectMake(fw - sbw,0.0,sbw,sbh) 76 | cancelButton.frame = CGRectMake(fw - sbw,fh - sbh, sbw, sbh) 77 | 78 | countDownLabel.frame = CGRectMake(0.0, 0.0, fw,fw * 0.5) 79 | countDownLabel.position = CGPointMake(fw / 2,fh / 2) 80 | countDownLabel.transform = CATransform3DRotate(countDownLabel.transform, CGFloat(-M_PI_2), 0.0, 0.0, 1.0) 81 | 82 | view.addSubview(saveButton) 83 | view.addSubview(cancelButton) 84 | view.layer.addSublayer(countDownLabel) 85 | 86 | addPulsing() 87 | } 88 | 89 | func layOutForLandscapeRight(){ 90 | 91 | saveButton.frame = CGRectMake(0.0,fh - sbh,sbw,sbh) 92 | cancelButton.frame = CGRectMake(0.0,0.0, sbw, sbh) 93 | 94 | countDownLabel.frame = CGRectMake(0.0, 0.0,fw,fw * 0.5) 95 | countDownLabel.position = CGPointMake(fw / 2,fh / 2) 96 | countDownLabel.transform = CATransform3DRotate(countDownLabel.transform, CGFloat(M_PI_2), 0.0, 0.0, 1.0) 97 | 98 | view.addSubview(saveButton) 99 | view.addSubview(cancelButton) 100 | view.layer.addSublayer(countDownLabel) 101 | 102 | addPulsing() 103 | 104 | } 105 | 106 | func createUI(){ 107 | 108 | 109 | // testView = UIImageView(image: UIImage(named: "bg2.png")) 110 | 111 | saveButton = UIButton(type:.Custom) 112 | saveButton.setTitle("", forState: .Normal) 113 | saveButton.setImage(UIImage(named:"Recording")!, forState: .Normal) 114 | saveButton.addTarget(self, action: #selector(OverlayViewController.saveButtonPressed), forControlEvents:.TouchUpInside) 115 | 116 | cancelButton = UIButton(type:.Custom) 117 | cancelButton.setTitle("", forState: .Normal) 118 | cancelButton.setImage(UIImage(named:"cancelButton")!, forState: .Normal) 119 | cancelButton.addTarget(self, action: #selector(OverlayViewController.cancelButtonPressed), forControlEvents:.TouchUpInside) 120 | 121 | countDownLabel = TimeTextLabel(fontSize: 30.0) 122 | countDownLabel.fontSize = fw * 0.25 123 | countDownLabel.alignmentMode = kCAAlignmentCenter 124 | 125 | } 126 | 127 | 128 | func initiateCountDownTimer(){ 129 | countDownStopWatch.delegate = self 130 | countDownStopWatch.countDownFromSeconds = videoSettings.videoDelay 131 | countDownStopWatch.start() 132 | } 133 | 134 | 135 | //Stop watch timer delegate methods for countdown timer 136 | func stopWatchDidCountDown(t: TimeInterval) { 137 | countDownStopWatch.stop() 138 | } 139 | 140 | func stopWatchDidUpdateWithTimeInterval(t: TimeInterval) { 141 | countDownLabel.string = t.intervalString(false) 142 | } 143 | 144 | 145 | //Button methods 146 | 147 | func saveButtonPressed(){ 148 | countDownStopWatch.stop() 149 | let cWorkOut = workOut as! StandardWOD 150 | cWorkOut.stopWorkOut() 151 | videoManager!.stopRecordingVideo { (videoURL, error) -> Void in 152 | 153 | } 154 | } 155 | 156 | func cameraManagerDidGenerateVideoAsset(url: NSURL) { 157 | print("Got URL of imgae : \(url.absoluteString)") 158 | } 159 | 160 | func cancelButtonPressed(){ 161 | 162 | countDownStopWatch.stop() 163 | self.delegate!.overlayDidCancel() 164 | } 165 | 166 | 167 | //Initialization 168 | override func viewDidAppear(animated: Bool) { 169 | super.viewDidAppear(animated) 170 | 171 | //Lay out depending on the orientation of the screen 172 | switch videoSettings.videoOrientation! { 173 | case .Portrait:layOutForPortrait() 174 | case .LandscapeLeft:layOutForLandscapeLeft() 175 | case .LandscapeRight:layOutForLandscapeRight() 176 | default: print("Orientation not set when moving from video to over lay") 177 | } 178 | 179 | initiateCountDownTimer() 180 | } 181 | 182 | override func viewDidLoad() { 183 | super.viewDidLoad() 184 | hasCountDown = videoSettings.videoDelay > 0 185 | //Create all the buttons ready to be layed out later 186 | createUI() 187 | } 188 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { 189 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 190 | commonInit() 191 | } 192 | 193 | required init?(coder aDecoder: NSCoder) { 194 | super.init(coder: aDecoder) 195 | commonInit() 196 | } 197 | 198 | private func commonInit() { 199 | modalPresentationStyle = .OverCurrentContext 200 | } 201 | 202 | 203 | //View animtions 204 | func addPulsing() { 205 | 206 | let alphaChange = CAKeyframeAnimation(keyPath:"opacity") 207 | alphaChange.duration = 2.0 208 | alphaChange.values = [1.0,0.2,1.0] 209 | alphaChange.keyTimes = [0.0,0.4,0.8] 210 | alphaChange.removedOnCompletion = false 211 | alphaChange.repeatCount = HUGE 212 | alphaChange.calculationMode = kCAAnimationPaced 213 | 214 | let pulsing = CAKeyframeAnimation(keyPath: "transform.scale") 215 | 216 | pulsing.duration = 2.0 217 | 218 | pulsing.values = [1.0,0.8,1.0] 219 | pulsing.keyTimes = [0.0,0.4,0.8] 220 | 221 | pulsing.removedOnCompletion = false 222 | pulsing.repeatCount = HUGE 223 | pulsing.calculationMode = kCAAnimationPaced 224 | 225 | //set background color 226 | saveButton.tintColor = UIColor.redColor() 227 | let layer = saveButton.layer 228 | layer.addAnimation(pulsing, forKey: "pulsing") 229 | layer.addAnimation(alphaChange, forKey: "alpha") 230 | } 231 | 232 | 233 | //Adimin 234 | override func shouldAutorotate() -> Bool { 235 | return false 236 | } 237 | 238 | override func prefersStatusBarHidden() -> Bool { 239 | return true 240 | } 241 | 242 | 243 | override func didReceiveMemoryWarning() { 244 | super.didReceiveMemoryWarning() 245 | // Dispose of any resources that can be recreated. 246 | } 247 | 248 | 249 | 250 | 251 | 252 | } 253 | -------------------------------------------------------------------------------- /IntervalSettingsVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntervalSettingsVC.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 22/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | 13 | class IntervalSettingsVC: UIViewController,UITableViewDataSource,UITableViewDelegate,ViewCanceling { 14 | 15 | var wod:WOD! 16 | var delegate:ViewCanceling? 17 | 18 | var rounds = [PresetRound]() 19 | 20 | @IBOutlet weak var loopLabel:UILabel! 21 | @IBOutlet weak var roundLabel:UILabel! 22 | @IBOutlet weak var loopStepper:UIStepper! 23 | @IBOutlet weak var roundStepper:UIStepper! 24 | @IBOutlet weak var tableView:UITableView! 25 | 26 | 27 | var loopStepperPV:Double = 1.0 28 | var roundStepperPV:Double = 1.0 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | let round = PresetRound(roundNumber: 1, presetDuration: 30,loopNumber:1) 34 | rounds.append(round) 35 | 36 | // Do any additional setup after loading the view. 37 | } 38 | 39 | override func didReceiveMemoryWarning() { 40 | super.didReceiveMemoryWarning() 41 | // Dispose of any resources that can be recreated. 42 | } 43 | 44 | 45 | @IBAction func loopStepperChanged(sender:UIStepper){ 46 | if shouldWarnUser(){ 47 | if sender.value > loopStepperPV{ 48 | sender.value -= 1 49 | } 50 | warnUser() 51 | }else{ 52 | loopLabel.text = Int(sender.value).timesThrough() 53 | } 54 | 55 | } 56 | 57 | func warnUser(){ 58 | let infoView = UIAlertController(title: "5 Minute Limit", message: "If you like our app, unlock more minutes and help support future great features, with the 'Unlimited Video' purchase. (It's as cheap as they would allow us!)", preferredStyle: UIAlertControllerStyle.Alert) 59 | infoView.view.tintColor = UIColor(red: 240/255.0, green: 178/255.0, blue: 71/255.0, alpha: 1.0) 60 | 61 | let ok = UIAlertAction(title:"Lets Go!", style: UIAlertActionStyle.Default, handler:{ 62 | (alert:UIAlertAction!) -> Void in 63 | 64 | let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("WodeoStore") as! WodeoProductPage 65 | viewController.delegate = self 66 | self.presentViewController(viewController, animated: true, completion: nil) 67 | 68 | 69 | }) 70 | 71 | let no = UIAlertAction(title: "No thanks..", style: UIAlertActionStyle.Cancel, handler:{ (alert:UIAlertAction!) -> Void in 72 | }) 73 | 74 | infoView.addAction(ok) 75 | infoView.addAction(no) 76 | 77 | presentViewController(infoView, animated:true, completion:nil) 78 | } 79 | 80 | @IBAction func roundStepperChanged(sender:UIStepper){ 81 | 82 | if sender.value < roundStepperPV { 83 | roundLabel.text = "\(Int(sender.value))" 84 | 85 | if Int(sender.value) == rounds.count { 86 | return 87 | } 88 | 89 | if Int(sender.value) <= rounds.count { 90 | rounds.removeLast() 91 | }else{ 92 | let newRound = PresetRound(roundNumber:rounds.count + 1, presetDuration: 30,loopNumber:1) 93 | rounds.append(newRound) 94 | } 95 | 96 | tableView.reloadData() 97 | roundStepperPV = sender.value 98 | return 99 | 100 | } 101 | 102 | 103 | if shouldWarnUser(){ 104 | if sender.value > roundStepperPV { 105 | sender.value -= 1 106 | } 107 | warnUser() 108 | }else{ 109 | 110 | roundLabel.text = "\(Int(sender.value))" 111 | 112 | if Int(sender.value) == rounds.count { 113 | return 114 | } 115 | 116 | if Int(sender.value) <= rounds.count { 117 | rounds.removeLast() 118 | }else{ 119 | let newRound = PresetRound(roundNumber:rounds.count + 1, presetDuration: 30,loopNumber:1) 120 | rounds.append(newRound) 121 | } 122 | 123 | tableView.reloadData() 124 | roundStepperPV = sender.value 125 | } 126 | } 127 | 128 | @IBAction func cancel(sender:AnyObject){ 129 | if let del = delegate { 130 | del.viewDidCancel() 131 | }else{ 132 | print("Delegate not set") 133 | } 134 | } 135 | 136 | @IBAction func go(sender:AnyObject){ 137 | 138 | 139 | 140 | performSegueWithIdentifier("goToVideo", sender: nil) 141 | 142 | } 143 | 144 | 145 | //UITableView Delegate 146 | func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool { 147 | return false 148 | } 149 | 150 | 151 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 152 | 153 | let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) 154 | 155 | let stepper = UIStepper(frame:CGRectMake(cell.frame.width - 102,(cell.frame.height / 2) - 14.5,94,29)) 156 | stepper.maximumValue = 10000 157 | stepper.minimumValue = 1 158 | stepper.wraps = false 159 | stepper.addTarget(self, action:#selector(IntervalSettingsVC.cellStepperChanged(_:)), forControlEvents:.ValueChanged) 160 | stepper.tag = indexPath.row 161 | stepper.tintColor = UIColor(red: 240/255.0, green: 178/255.0, blue: 71/255.0, alpha: 1.0) 162 | 163 | 164 | cell.addSubview(stepper) 165 | 166 | cell.textLabel!.text = "Round \(rounds[indexPath.row].roundNumber)" 167 | 168 | let roundDuration = rounds[indexPath.row].presetDuration 169 | 170 | stepper.value = Double(roundDuration) 171 | 172 | if roundDuration <= 59 { 173 | cell.detailTextLabel!.text = "\(Double(rounds[indexPath.row].presetDuration).stringForTimeInterval(false)) seconds" 174 | }else{ 175 | cell.detailTextLabel!.text = "\(Double(rounds[indexPath.row].presetDuration).stringForTimeInterval(false))" 176 | } 177 | 178 | return cell 179 | 180 | } 181 | 182 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 183 | return rounds.count 184 | } 185 | 186 | func numberOfSectionsInTableView(tableView: UITableView) -> Int { 187 | return 1 188 | } 189 | 190 | func cellStepperChanged(sender:UIStepper) { 191 | rounds[sender.tag].presetDuration = Int(sender.value) 192 | tableView.reloadData() 193 | } 194 | 195 | func shouldWarnUser() -> Bool { 196 | var totalRoundSeconds = 0 197 | for r in rounds { 198 | totalRoundSeconds += r.presetDuration 199 | } 200 | 201 | return (Double(totalRoundSeconds) * loopStepper.value) > wod.totalVideoSecondsAllowed ? true : false 202 | } 203 | 204 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 205 | 206 | if segue.identifier == "goToVideo" { 207 | let vc = segue.destinationViewController as! VideoViewController 208 | vc.delegate = self 209 | let intWOD = wod as! IntervalWOD 210 | 211 | 212 | intWOD.rounds = gererateRounds() 213 | intWOD.loops = Int(loopStepper.value) 214 | vc.workout = intWOD 215 | 216 | 217 | 218 | var counter = 1 219 | for r in intWOD.rounds { 220 | print("Round \(counter) Loop = \(r.loopNumber)") 221 | counter += 1 222 | } 223 | 224 | } 225 | } 226 | 227 | func gererateRounds() -> [PresetRound] { 228 | 229 | var loopCounter = 0 230 | var roundCounter = 0 231 | var proccessedRounds = [PresetRound]() 232 | 233 | 234 | while loopCounter < Int(loopStepper.value) { 235 | for r in rounds { 236 | proccessedRounds.append(PresetRound(roundNumber: r.roundNumber, presetDuration:r.presetDuration, loopNumber: loopCounter + 1)) 237 | roundCounter += 1 238 | } 239 | loopCounter += 1 240 | } 241 | 242 | return proccessedRounds 243 | 244 | } 245 | 246 | override func prefersStatusBarHidden() -> Bool { 247 | return true 248 | } 249 | 250 | func viewDidCancel() { 251 | dismissViewControllerAnimated(true, completion: nil) 252 | } 253 | 254 | } 255 | -------------------------------------------------------------------------------- /TabataSettingsVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TabataSettingsVC.swift 3 | // Wodeo 2 4 | // 5 | // Created by Gareth Long on 27/02/2016. 6 | // Copyright © 2016 Elliott Brown. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class TabataSettingsVC: UIViewController,UITableViewDataSource,UITableViewDelegate,ViewCanceling { 12 | 13 | var wod:WOD! 14 | var delegate:ViewCanceling? 15 | 16 | var rounds = [PresetRound]() 17 | 18 | @IBOutlet weak var loopLabel:UILabel! 19 | @IBOutlet weak var roundLabel:UILabel! 20 | @IBOutlet weak var loopStepper:UIStepper! 21 | @IBOutlet weak var roundStepper:UIStepper! 22 | @IBOutlet weak var restLabel:UILabel! 23 | @IBOutlet weak var restStepper:UIStepper! 24 | @IBOutlet weak var tableView:UITableView! 25 | 26 | 27 | var restStepperPV = 0.0 28 | var loopStepperPV = 0.0 29 | var roundStepperPV = 0.0 30 | 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | var counter = 0 36 | while counter < 8 { 37 | let round = PresetRound(roundNumber:counter + 1, presetDuration: 20,loopNumber:1) 38 | rounds.append(round) 39 | counter += 1 40 | } 41 | 42 | restStepper.value = 10 43 | roundStepper.value = 8 44 | 45 | // Do any additional setup after loading the view. 46 | } 47 | 48 | override func didReceiveMemoryWarning() { 49 | super.didReceiveMemoryWarning() 50 | // Dispose of any resources that can be recreated. 51 | } 52 | 53 | 54 | @IBAction func restStepperChanged(sender:UIStepper){ 55 | 56 | if sender.value < restStepperPV{ 57 | restLabel.text = "\(Int(sender.value))" 58 | restStepperPV = sender.value 59 | return 60 | } 61 | 62 | if shouldWarnUser(){ 63 | if sender.value > restStepperPV { 64 | sender.value -= 1 65 | restStepperPV = sender.value 66 | } 67 | warnUser() 68 | }else{ 69 | restLabel.text = "\(Int(sender.value))" 70 | restStepperPV = sender.value 71 | } 72 | 73 | 74 | } 75 | 76 | 77 | @IBAction func loopStepperChanged(sender:UIStepper){ 78 | 79 | if sender.value < loopStepperPV{ 80 | loopLabel.text = Int(sender.value).timesThrough() 81 | loopStepperPV = sender.value 82 | return 83 | } 84 | 85 | if shouldWarnUser(){ 86 | if sender.value > loopStepperPV { 87 | sender.value -= 1 88 | } 89 | warnUser() 90 | }else{ 91 | loopLabel.text = Int(sender.value).timesThrough() 92 | loopStepperPV = sender.value 93 | } 94 | 95 | } 96 | 97 | @IBAction func roundStepperChanged(sender:UIStepper){ 98 | 99 | if sender.value < roundStepperPV{ 100 | roundLabel.text = "\(Int(sender.value))" 101 | 102 | if Int(sender.value) == rounds.count { 103 | return 104 | } 105 | 106 | if Int(sender.value) <= rounds.count { 107 | rounds.removeLast() 108 | }else{ 109 | let newRound = PresetRound(roundNumber:rounds.count + 1, presetDuration: 20,loopNumber:1) 110 | rounds.append(newRound) 111 | } 112 | 113 | tableView.reloadData() 114 | roundStepperPV = sender.value 115 | return 116 | } 117 | 118 | if shouldWarnUser(){ 119 | if sender.value > roundStepperPV { 120 | sender.value -= 1 121 | } 122 | warnUser() 123 | }else{ 124 | 125 | roundLabel.text = "\(Int(sender.value))" 126 | 127 | if Int(sender.value) == rounds.count { 128 | return 129 | } 130 | 131 | if Int(sender.value) <= rounds.count { 132 | rounds.removeLast() 133 | }else{ 134 | let newRound = PresetRound(roundNumber:rounds.count + 1, presetDuration: 20,loopNumber:1) 135 | rounds.append(newRound) 136 | } 137 | 138 | tableView.reloadData() 139 | roundStepperPV = sender.value 140 | } 141 | } 142 | 143 | func shouldWarnUser() -> Bool { 144 | var totalSecondsInRounds = 0 145 | for r in rounds { 146 | totalSecondsInRounds += r.presetDuration 147 | } 148 | 149 | let totalRest = restStepper.value * (Double(rounds.count) - 1) 150 | 151 | let total = Double(totalRest + Double(totalSecondsInRounds)) * loopStepper.value 152 | 153 | return total > wod.totalVideoSecondsAllowed ? true : false 154 | 155 | } 156 | 157 | func warnUser(){ 158 | let infoView = UIAlertController(title: "5 Minute Limit", message: "If you like our app, unlock more minutes and help support future great features, with the 'Unlimited Video' purchase. (It's as cheap as they would allow us!)", preferredStyle: UIAlertControllerStyle.Alert) 159 | infoView.view.tintColor = UIColor(red: 240/255.0, green: 178/255.0, blue: 71/255.0, alpha: 1.0) 160 | 161 | let ok = UIAlertAction(title:"Lets Go!", style: UIAlertActionStyle.Default, handler:{ 162 | (alert:UIAlertAction!) -> Void in 163 | 164 | let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("WodeoStore") as! WodeoProductPage 165 | viewController.delegate = self 166 | self.presentViewController(viewController, animated: true, completion: nil) 167 | 168 | 169 | }) 170 | 171 | let no = UIAlertAction(title: "No thanks..", style: UIAlertActionStyle.Cancel, handler:{ (alert:UIAlertAction!) -> Void in 172 | }) 173 | 174 | infoView.addAction(ok) 175 | infoView.addAction(no) 176 | 177 | presentViewController(infoView, animated:true, completion:nil) 178 | } 179 | 180 | 181 | @IBAction func cancel(sender:AnyObject){ 182 | if let del = delegate { 183 | del.viewDidCancel() 184 | }else{ 185 | print("Delegate not set") 186 | } 187 | } 188 | 189 | @IBAction func go(sender:AnyObject){ 190 | 191 | 192 | 193 | performSegueWithIdentifier("goToVideo", sender: nil) 194 | 195 | } 196 | 197 | 198 | //UITableView Delegate 199 | 200 | func tableView(tableView: UITableView, shouldHighlightRowAtIndexPath indexPath: NSIndexPath) -> Bool { 201 | return false 202 | } 203 | 204 | func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 205 | 206 | let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) 207 | 208 | let stepper = UIStepper(frame:CGRectMake(cell.frame.width - 102,(cell.frame.height / 2) - 14.5,94,29)) 209 | stepper.maximumValue = 10000 210 | stepper.minimumValue = 1 211 | stepper.wraps = false 212 | stepper.addTarget(self, action:#selector(TabataSettingsVC.cellStepperChanged(_:)), forControlEvents:.ValueChanged) 213 | stepper.tag = indexPath.row 214 | stepper.tintColor = UIColor(red: 240/255.0, green: 178/255.0, blue: 71/255.0, alpha: 1.0) 215 | 216 | 217 | cell.addSubview(stepper) 218 | 219 | cell.textLabel!.text = "Round \(rounds[indexPath.row].roundNumber)" 220 | 221 | let roundDuration = rounds[indexPath.row].presetDuration 222 | 223 | stepper.value = Double(roundDuration) 224 | 225 | if roundDuration <= 59 { 226 | cell.detailTextLabel!.text = "\(Double(rounds[indexPath.row].presetDuration).stringForTimeInterval(false)) seconds" 227 | }else{ 228 | cell.detailTextLabel!.text = "\(Double(rounds[indexPath.row].presetDuration).stringForTimeInterval(false))" 229 | } 230 | 231 | return cell 232 | 233 | } 234 | 235 | func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 236 | return rounds.count 237 | } 238 | 239 | func numberOfSectionsInTableView(tableView: UITableView) -> Int { 240 | return 1 241 | } 242 | 243 | func cellStepperChanged(sender:UIStepper) { 244 | rounds[sender.tag].presetDuration = Int(sender.value) 245 | tableView.reloadData() 246 | } 247 | 248 | 249 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 250 | 251 | if segue.identifier == "goToVideo" { 252 | let vc = segue.destinationViewController as! VideoViewController 253 | vc.delegate = self 254 | let intWOD = wod as! TabataWOD 255 | 256 | 257 | intWOD.rounds = gererateRounds() 258 | intWOD.loops = Int(loopStepper.value) 259 | intWOD.restPeriodBetweenRounds = Int(restStepper.value) 260 | vc.workout = intWOD 261 | 262 | 263 | 264 | var counter = 1 265 | for r in intWOD.rounds { 266 | print("Round \(counter) Loop = \(r.loopNumber)") 267 | counter += 1 268 | } 269 | 270 | } 271 | } 272 | 273 | func gererateRounds() -> [PresetRound] { 274 | 275 | var loopCounter = 0 276 | var roundCounter = 0 277 | var proccessedRounds = [PresetRound]() 278 | 279 | 280 | while loopCounter < Int(loopStepper.value) { 281 | for r in rounds { 282 | proccessedRounds.append(PresetRound(roundNumber: r.roundNumber, presetDuration:r.presetDuration, loopNumber: loopCounter + 1)) 283 | roundCounter += 1 284 | } 285 | loopCounter += 1 286 | } 287 | 288 | return proccessedRounds 289 | 290 | } 291 | 292 | override func prefersStatusBarHidden() -> Bool { 293 | return true 294 | } 295 | 296 | func viewDidCancel() { 297 | dismissViewControllerAnimated(true, completion: nil) 298 | } 299 | 300 | } 301 | --------------------------------------------------------------------------------