├── Example ├── SupportingFiles │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── main.m │ ├── Tattle-UI-Prefix.pch │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ └── Tattle-UI-Info.plist └── Classes │ ├── AppDelegate.h │ ├── FirstVC │ ├── FirstVC.h │ ├── FirstVC.m │ └── FirstVC.xib │ ├── SecondVC │ ├── SecondVC.h │ ├── SecondVC.m │ └── SecondVC.xib │ └── AppDelegate.m ├── Screenshot ├── Scribble.png ├── ShotImage.png ├── ShareViaMail.png ├── AudioRecordPlay.png └── Thumbnail │ ├── Scribble.png │ ├── ShotImage.png │ ├── ShareViaMail.png │ └── AudioRecordPlay.png ├── TattleControl ├── Resources │ ├── gif_3D.gif │ ├── play~ipad.png │ ├── share~ipad.png │ ├── stop~ipad.png │ ├── play@2x~ipad.png │ ├── play~iphone.png │ ├── record~ipad.png │ ├── share~iphone.png │ ├── stop@2x~ipad.png │ ├── stop~iphone.png │ ├── play@2x~iphone.png │ ├── record@2x~ipad.png │ ├── record~iphone.png │ ├── share@2x~ipad.png │ ├── share@2x~iphone.png │ ├── stop@2x~iphone.png │ └── record@2x~iphone.png ├── ScribblePathPoint.m ├── ScribblePathPoint.h ├── TLogControlMacro.h ├── UIImage+GiffAnimation.h ├── UIController+SnapShotButton.h ├── TConstants.h ├── TPopupView.h ├── TConstants.m ├── MovableEditorView.h ├── ScribCapControl.h ├── CommonMacro.h ├── TFileManager.h ├── Scribble.h ├── SnapShotView.h ├── TattleControl.h ├── ScribCapControl.m ├── ScribbleEraseView.h ├── MovableEditorView.xib ├── MovableEditorViewiPad.xib ├── TPopupView.m ├── UIController+SnapShotButton.m ├── Scribble.m ├── TAudioManager.h ├── UIImage+GiffAnimation.m ├── TPopupView.xib ├── TFileManager.m ├── ScribbleEraseView.m ├── SnapShotView.m ├── TAudioManager.m └── MovableEditorView.m ├── .gitignore ├── Example-Swift ├── SupportingFiles │ ├── Tattle-UI-Swift-Bridging-Header.h │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ └── Info.plist └── Classes │ ├── SecondVC │ ├── SecondVC.swift │ └── SecondVC.xib │ ├── FirstVC │ ├── FirstVC.swift │ └── FirstVC.xib │ └── AppDelegate.swift ├── LICENSE └── README.md /Example/SupportingFiles/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Screenshot/Scribble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/Screenshot/Scribble.png -------------------------------------------------------------------------------- /Screenshot/ShotImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/Screenshot/ShotImage.png -------------------------------------------------------------------------------- /Screenshot/ShareViaMail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/Screenshot/ShareViaMail.png -------------------------------------------------------------------------------- /Screenshot/AudioRecordPlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/Screenshot/AudioRecordPlay.png -------------------------------------------------------------------------------- /Screenshot/Thumbnail/Scribble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/Screenshot/Thumbnail/Scribble.png -------------------------------------------------------------------------------- /Screenshot/Thumbnail/ShotImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/Screenshot/Thumbnail/ShotImage.png -------------------------------------------------------------------------------- /TattleControl/Resources/gif_3D.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/gif_3D.gif -------------------------------------------------------------------------------- /Screenshot/Thumbnail/ShareViaMail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/Screenshot/Thumbnail/ShareViaMail.png -------------------------------------------------------------------------------- /TattleControl/Resources/play~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/play~ipad.png -------------------------------------------------------------------------------- /TattleControl/Resources/share~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/share~ipad.png -------------------------------------------------------------------------------- /TattleControl/Resources/stop~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/stop~ipad.png -------------------------------------------------------------------------------- /Screenshot/Thumbnail/AudioRecordPlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/Screenshot/Thumbnail/AudioRecordPlay.png -------------------------------------------------------------------------------- /TattleControl/Resources/play@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/play@2x~ipad.png -------------------------------------------------------------------------------- /TattleControl/Resources/play~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/play~iphone.png -------------------------------------------------------------------------------- /TattleControl/Resources/record~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/record~ipad.png -------------------------------------------------------------------------------- /TattleControl/Resources/share~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/share~iphone.png -------------------------------------------------------------------------------- /TattleControl/Resources/stop@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/stop@2x~ipad.png -------------------------------------------------------------------------------- /TattleControl/Resources/stop~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/stop~iphone.png -------------------------------------------------------------------------------- /TattleControl/Resources/play@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/play@2x~iphone.png -------------------------------------------------------------------------------- /TattleControl/Resources/record@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/record@2x~ipad.png -------------------------------------------------------------------------------- /TattleControl/Resources/record~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/record~iphone.png -------------------------------------------------------------------------------- /TattleControl/Resources/share@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/share@2x~ipad.png -------------------------------------------------------------------------------- /TattleControl/Resources/share@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/share@2x~iphone.png -------------------------------------------------------------------------------- /TattleControl/Resources/stop@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/stop@2x~iphone.png -------------------------------------------------------------------------------- /TattleControl/Resources/record@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/HEAD/TattleControl/Resources/record@2x~iphone.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .Trashes 3 | *.swp 4 | *.lock 5 | DerivedData/ 6 | build/ 7 | *.pbxuser 8 | *.mode1v3 9 | *.mode2v3 10 | *.perspectivev3 11 | !default.pbxuser 12 | !default.mode1v3 13 | !default.mode2v3 14 | !default.perspectivev3 15 | xcuserdata 16 | *.moved-aside 17 | Pods/ 18 | *.xcworkspace 19 | -------------------------------------------------------------------------------- /Example/Classes/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // Tattle-UI 4 | // 5 | // Created by Mani on 5/26/14. 6 | // Copyright (c) 2014 Tattle. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /Example/SupportingFiles/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Tattle-UI 4 | // 5 | // Created by Mani on 5/26/14. 6 | // Copyright (c) 2014 Tattle. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Example/SupportingFiles/Tattle-UI-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_3_0 10 | #warning "This project uses features only available in iOS SDK 3.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #import "UIController+SnapShotButton.h" 17 | #endif 18 | -------------------------------------------------------------------------------- /Example-Swift/SupportingFiles/Tattle-UI-Swift-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Tattle-UI-Swift-Bridging-Header.h 3 | // ExampleProject 4 | // 5 | // Created by Mani on 6/5/14. 6 | // Copyright (c) 2014 Tattle. All rights reserved. 7 | // 8 | 9 | #import "MovableEditorView.h" 10 | #import "Scribble.h" 11 | #import "ScribbleEraseView.h" 12 | #import "ScribblePathPoint.h" 13 | #import "CommonMacro.h" 14 | #import "ScribCapControl.h" 15 | #import "SnapShotView.h" 16 | #import "TattleControl.h" 17 | #import "TAudioManager.h" 18 | #import "TConstants.h" 19 | #import "TFileManager.h" 20 | #import "TPopupView.h" 21 | #import "UIController+SnapShotButton.h" 22 | #import "UIImage+GiffAnimation.h" 23 | -------------------------------------------------------------------------------- /Example/SupportingFiles/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "29x29", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "40x40", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "76x76", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /Example-Swift/SupportingFiles/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "40x40", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "60x60", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "29x29", 21 | "scale" : "1x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "29x29", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "40x40", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "40x40", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "76x76", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "76x76", 46 | "scale" : "2x" 47 | } 48 | ], 49 | "info" : { 50 | "version" : 1, 51 | "author" : "xcode" 52 | } 53 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /Example-Swift/SupportingFiles/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | tattleUI.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | ${PRODUCT_NAME} 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | 33 | UISupportedInterfaceOrientations~ipad 34 | 35 | UIInterfaceOrientationPortrait 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Example/Classes/FirstVC/FirstVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // FirstVC.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import 26 | 27 | @interface FirstVC : UIViewController 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Example/Classes/SecondVC/SecondVC.h: -------------------------------------------------------------------------------- 1 | // 2 | // SecondVC.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | #import 25 | 26 | @interface SecondVC : UIViewController 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /TattleControl/ScribblePathPoint.m: -------------------------------------------------------------------------------- 1 | // 2 | // ScribblePathPoint.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "ScribblePathPoint.h" 26 | 27 | @implementation ScribblePathPoint 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Example/SupportingFiles/Tattle-UI-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | tattleUI.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Example/SupportingFiles/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /Example-Swift/SupportingFiles/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "orientation" : "portrait", 12 | "idiom" : "iphone", 13 | "subtype" : "retina4", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "7.0", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "orientation" : "portrait", 20 | "idiom" : "ipad", 21 | "extent" : "full-screen", 22 | "minimum-system-version" : "7.0", 23 | "scale" : "1x" 24 | }, 25 | { 26 | "orientation" : "landscape", 27 | "idiom" : "ipad", 28 | "extent" : "full-screen", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "1x" 31 | }, 32 | { 33 | "orientation" : "portrait", 34 | "idiom" : "ipad", 35 | "extent" : "full-screen", 36 | "minimum-system-version" : "7.0", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "orientation" : "landscape", 41 | "idiom" : "ipad", 42 | "extent" : "full-screen", 43 | "minimum-system-version" : "7.0", 44 | "scale" : "2x" 45 | } 46 | ], 47 | "info" : { 48 | "version" : 1, 49 | "author" : "xcode" 50 | } 51 | } -------------------------------------------------------------------------------- /TattleControl/ScribblePathPoint.h: -------------------------------------------------------------------------------- 1 | // 2 | // ScribblePathPoint.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import 26 | #import 27 | 28 | @interface ScribblePathPoint : NSObject 29 | 30 | /* List of scribble point to make one complete scribble */ 31 | @property (nonatomic) CGPoint point; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /TattleControl/TLogControlMacro.h: -------------------------------------------------------------------------------- 1 | // 2 | // TLogControlMacro.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #ifndef Tattle_UI_TLogControlMacro_h 26 | #define Tattle_UI_TLogControlMacro_h 27 | 28 | #ifndef __OPTIMIZE__ 29 | #define TLog(fmt,...) NSLog((@"%s [Line %d] " fmt),__PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__) 30 | #else 31 | #define TLog(...) {} 32 | #endif 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /TattleControl/UIImage+GiffAnimation.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+GiffAnimation.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | /* Credits goes to Rob(https://github.com/mayoff/uiimage-from-animated-gif/) */ 25 | 26 | #import 27 | 28 | @interface UIImage (animatedGIF) 29 | 30 | /* Animate gif image */ 31 | + (UIImage *)animatedImageWithAnimatedGIFURL:(NSURL *)theURL; 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /TattleControl/UIController+SnapShotButton.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIController+SnapShotButton.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import 26 | 27 | /* Swizzling method used snapbutton to get front while viewDidAppear: called. */ 28 | @interface UIViewController (SnapShotButton) 29 | 30 | @end 31 | 32 | /* Swizzling method used snapbutton to get front while addSubview: called*/ 33 | @interface UIView (SnapShotButton) 34 | 35 | @end -------------------------------------------------------------------------------- /Example-Swift/Classes/SecondVC/SecondVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecondVC.swift 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | 26 | import Foundation 27 | 28 | class SecondVC : UIViewController 29 | { 30 | @IBOutlet var secondButton : UIButton 31 | override func viewDidLoad() 32 | { 33 | super.viewDidLoad() 34 | self.title = "Second" 35 | } 36 | 37 | @IBAction func secondButtonFired (button : UIButton) 38 | { 39 | println("Third button Fired") 40 | } 41 | } -------------------------------------------------------------------------------- /TattleControl/TConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // TConstants.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import 26 | #import 27 | 28 | //Audio format configuration constants. 29 | extern NSInteger const TFORMAT_ID; 30 | extern NSInteger const TSAMPLE_RATE; 31 | extern NSInteger const TBIT_RATE_KEY; 32 | extern NSInteger const TBIT_DEPTH_KEY; 33 | extern NSInteger const TNUMBER_OF_CHANNEL; 34 | extern NSInteger const TAUDIO_QUALITY; 35 | extern NSString *const TAUDIO_EXTENSION; 36 | -------------------------------------------------------------------------------- /TattleControl/TPopupView.h: -------------------------------------------------------------------------------- 1 | // 2 | // PopupView.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import 26 | 27 | /* If press Ok ,or cancel, corresponding action triggered from this view */ 28 | typedef void(^ActionBlock)(); 29 | 30 | /* Popview used to present snap shot view with animation */ 31 | @interface TPopupView : UIView 32 | 33 | @property (nonatomic,copy) ActionBlock okActionBlock,cancelActionBlock; 34 | 35 | //It is used to show view with Popup animation 36 | -(void)showWithAnimation; 37 | //It will hide view with Popout animation 38 | -(void)hideWithAnimation; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /TattleControl/TConstants.m: -------------------------------------------------------------------------------- 1 | // 2 | // TConstants.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "TConstants.h" 26 | #import 27 | 28 | NSInteger const TFORMAT_ID = kAudioFormatMPEG4AAC; 29 | NSInteger const TSAMPLE_RATE = 16000; 30 | NSInteger const TBIT_RATE_KEY = 32000;//Various bit rates : 32k,40k,48k,56k,64k,80k,96k,128k,160k,192k,256k,320k 31 | NSString *const TAUDIO_EXTENSION = @".aac"; 32 | NSInteger const TBIT_DEPTH_KEY = 16; 33 | NSInteger const TNUMBER_OF_CHANNEL = 1; 34 | NSInteger const TAUDIO_QUALITY = AVAudioQualityMedium; 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /TattleControl/MovableEditorView.h: -------------------------------------------------------------------------------- 1 | // 2 | // MovableEditorView.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import 26 | 27 | @interface MovableEditorView : UIView 28 | 29 | //Movable Control view shareControl 30 | +(MovableEditorView*)sharedView; 31 | 32 | //Reset recording control while cancel changes or share. 33 | -(void)resetRecordingControl; 34 | 35 | //Using this following function, user can change background color of movable control, default, black color with 0.4 alpha 36 | -(void)assignBackgroundColor:(UIColor *)backgroundColor;//Default alpha 0.4 37 | -(void)assignBackgroundColor:(UIColor *)backgroundColor withAlpha:(CGFloat)alpha; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /TattleControl/ScribCapControl.h: -------------------------------------------------------------------------------- 1 | // 2 | // ScribCapControl.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import 26 | 27 | /* Delegate method to drag touch event of scrible cap control */ 28 | @protocol ScribProcessor 29 | 30 | - (void) didPassOnTouch:(UITouch *) touch withEvent:(UIEvent *) event; 31 | 32 | @end 33 | 34 | /* Scribble Cap control detect touch, this touch event pass to scribble view to make scribble */ 35 | @interface ScribCapControl : UIControl 36 | 37 | @property (nonatomic, weak) UIView *scribTarget; 38 | @property (nonatomic, weak) id controller; 39 | 40 | //Shared scribble Cap control to drag touch event. 41 | +(ScribCapControl*)sharedControl; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /Example-Swift/Classes/FirstVC/FirstVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirstVC.swift 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | import Foundation 26 | 27 | class FirstVC : UIViewController 28 | { 29 | @IBOutlet var firstButton : UIButton 30 | 31 | override func viewDidLoad() 32 | { 33 | super.viewDidLoad() 34 | self.title = "First" 35 | } 36 | 37 | @IBAction func firstButtonClicked (button : UIButton) 38 | { 39 | println("Second screen button clicked") 40 | var secondVc = SecondVC(nibName: "SecondVC", bundle: NSBundle.mainBundle()) 41 | self.navigationController.pushViewController(secondVc, animated: true) 42 | } 43 | } -------------------------------------------------------------------------------- /TattleControl/CommonMacro.h: -------------------------------------------------------------------------------- 1 | // 2 | // CommonMacro.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #ifndef SpotIt_CommonMacro_h 26 | #define SpotIt_CommonMacro_h 27 | 28 | #define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending) 29 | #define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending) 30 | #define iOS7_0 @"7.0" 31 | 32 | #define IS_IPAD ([[UIDevice currentDevice] respondsToSelector:@selector(userInterfaceIdiom)] && [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) 33 | #define resizeDependsDevice(originalSize) (CGFloat)(IS_IPAD ? originalSize*2 : originalSize) 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /Example/Classes/SecondVC/SecondVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // SecondVC.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "SecondVC.h" 26 | #import "TLogControlMacro.h" 27 | 28 | @interface SecondVC () 29 | 30 | @property (nonatomic,weak) IBOutlet UIButton *thirdBtn; 31 | 32 | -(IBAction)thirdButtonFired:(id)sender; 33 | 34 | @end 35 | 36 | @implementation SecondVC 37 | 38 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 39 | { 40 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 41 | if (self) { 42 | // Custom initialization 43 | } 44 | return self; 45 | } 46 | 47 | - (void)viewDidLoad 48 | { 49 | [super viewDidLoad]; 50 | self.title = @"Second"; 51 | // Do any additional setup after loading the view from its nib. 52 | } 53 | 54 | 55 | #pragma mark - Third Button Fired 56 | 57 | -(IBAction)thirdButtonFired:(id)sender 58 | { 59 | TLog(@"Third Button Fired"); 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /TattleControl/TFileManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // TFileManager.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import 26 | #import 27 | 28 | @interface TFileManager : NSObject 29 | 30 | //Shared manager manages file creation, deletion, rename. 31 | +(TFileManager*)sharedFileManager; 32 | 33 | //Return current file path to start record 34 | -(NSString*)getAudioFilePath; 35 | //Return last recorded file path 36 | -(NSString*)getRecentlyRecordedAudioFilePath; 37 | 38 | //Get list of screen shot file path, default .png files. 39 | -(NSArray*)getScreenShots; 40 | //Get list of recorded audio file , by default .aac files 41 | -(NSArray*)getRecordedAudios; 42 | 43 | //Screen shot image saved into file 44 | -(BOOL)saveImage:(UIImage*)image; 45 | 46 | //Clear audio files after mail send or cancelling 47 | -(void)clearAllAudios; 48 | //Clear screenshot files after mail send of cancel changes. 49 | -(void)clearAllScreenShots; 50 | 51 | //Roll back recorded audios 52 | -(void)rollbackTheRecordedAudios; 53 | @end 54 | -------------------------------------------------------------------------------- /Example-Swift/Classes/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | 26 | import Foundation 27 | import UIKit 28 | 29 | @UIApplicationMain 30 | class AppDelegate: UIResponder, UIApplicationDelegate { 31 | 32 | var window: UIWindow? 33 | 34 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool { 35 | self.window = UIWindow(frame: UIScreen.mainScreen().bounds) 36 | TattleControl.sharedControl().enableTattleToWindow(self.window) 37 | TattleControl.sharedControl().assignRecipientEmailId("yourMail@domain.com", withCCId: "yourMail@domain.com", emailSubject: "Bugs") 38 | var firstVC = FirstVC(nibName: "FirstVC", bundle: NSBundle.mainBundle()) 39 | var nav = UINavigationController(rootViewController: firstVC) 40 | self.window!.rootViewController = nav; 41 | self.window!.backgroundColor = UIColor.whiteColor() 42 | self.window!.makeKeyAndVisible() 43 | return true 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Example/Classes/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // Tattle-UI 4 | // 5 | // Created by Mani on 5/26/14. 6 | // Copyright (c) 2014 Tattle. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "TattleControl.h" 11 | #import "FirstVC.h" 12 | 13 | @implementation AppDelegate 14 | 15 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 16 | { 17 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 18 | [[TattleControl sharedControl] enableTattleToWindow:self.window]; 19 | 20 | [[TattleControl sharedControl] assignRecipientEmailId:@"yourmailid@domain.com" withCCId:@"yourmailid@domain.com" emailSubject:@"Bugs"]; 21 | /* Optional Configuration 22 | 23 | //To Add Recipients for mail 24 | [[TattleControl sharedControl] addRecipientMailId:@"yourmailid@domain.com"]; 25 | 26 | //To set CC for mail: 27 | [[TattleControl sharedControl] addCCMailId:@"yourmailid@domain.com"]; 28 | 29 | //To set Subject for mail: 30 | [[TattleControl sharedControl] assignMailSubject:@"Bugs"]; 31 | 32 | //Optionally change colors: 33 | //Change spot image color according to app theme, default is blue 34 | [[TattleControl sharedControl] changeSpotImageColor:YOUR_Color]; 35 | 36 | //If you don't like spot image, you can also set your own image too. 37 | [[TattleControl sharedControl] setSpotButtonImage:YOUR_Image]; 38 | 39 | //Change Scribble Color according to background, default is black 40 | [[TattleControl sharedControl] setScribbleColor:YOUR_Color]; 41 | 42 | //Change background color of movable control. default is black(transparent) 43 | [[TattleControl sharedControl] setMovableControlBackgroundColor:YOUR_Color]; 44 | [[TattleControl sharedControl] setMovableControlBackgroundColor:YOUR_Color withAlpha:alpha]; 45 | 46 | */ 47 | self.window.backgroundColor = [UIColor whiteColor]; 48 | FirstVC *firstVC = [[FirstVC alloc]init]; 49 | UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:firstVC]; 50 | self.window.rootViewController = nav; 51 | [self.window makeKeyAndVisible]; 52 | return YES; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /Example/Classes/FirstVC/FirstVC.m: -------------------------------------------------------------------------------- 1 | // 2 | // FirstVC.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "FirstVC.h" 26 | #import "SecondVC.h" 27 | #import "TLogControlMacro.h" 28 | 29 | @interface FirstVC () 30 | 31 | @property (nonatomic,weak) IBOutlet UIButton *secondBtn; 32 | 33 | -(IBAction)secondButtonFired:(id)sender; 34 | 35 | @end 36 | 37 | @implementation FirstVC 38 | 39 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 40 | { 41 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 42 | if (self) { 43 | // Custom initialization 44 | } 45 | return self; 46 | } 47 | 48 | - (void)viewDidLoad 49 | { 50 | [super viewDidLoad]; 51 | self.title = @"First"; 52 | } 53 | 54 | - (void)didReceiveMemoryWarning 55 | { 56 | [super didReceiveMemoryWarning]; 57 | // Dispose of any resources that can be recreated. 58 | } 59 | 60 | 61 | #pragma mark - Second Button Fired 62 | 63 | -(IBAction)secondButtonFired:(id)sender 64 | { 65 | TLog(@"Second Button Fired"); 66 | SecondVC *secondVC = [[SecondVC alloc] init]; 67 | [self.navigationController pushViewController:secondVC animated:YES]; 68 | } 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /TattleControl/Scribble.h: -------------------------------------------------------------------------------- 1 | // 2 | // Scribble.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import 26 | #import 27 | 28 | @interface Scribble : NSObject 29 | 30 | /* Storke color of scribble, default black color. Now we can change store color by [TattleControl setScribbleColor:...], We're also planning to provide dynamically change scribble color by providing extra control to movableEditorview. */ 31 | @property (nonatomic,strong) UIColor* strokeColor; 32 | /* Width of the scribble, To default, see this constant TScribbleLineWidth*/ 33 | @property (assign) float lineWidth; 34 | /* In future, we decide to provide erase control, this bool refer to erase or scribble */ 35 | @property (assign) BOOL isEraseOn; 36 | /* The end point of the scribble, top most point of the end point*/ 37 | @property (readonly) CGPoint topMostPoint; 38 | 39 | //Return path of this scribble 40 | -(CGMutablePathRef)drawingPath; 41 | //Add point to current drawing path 42 | - (CGRect) addPoint:(CGPoint)point; 43 | //To find corresponding point at index 44 | - (CGPoint) getPointAtIndex:(NSUInteger)index; 45 | 46 | //Get all points for current scribble. 47 | - (NSMutableArray*)getPoints; 48 | 49 | @end 50 | -------------------------------------------------------------------------------- /Example-Swift/Classes/SecondVC/SecondVC.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Example/Classes/SecondVC/SecondVC.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /TattleControl/SnapShotView.h: -------------------------------------------------------------------------------- 1 | // 2 | // SnapShotView.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | #import 25 | 26 | @interface SnapShotView : UIView 27 | 28 | //Snapshot view has screen shot image, all changes will be done on this view 29 | +(SnapShotView*)sharedView; 30 | 31 | //Transparent background view show title, cancel option;. 32 | -(UIView*)getBaseView; 33 | //Close snap shot view 34 | -(void)removeFromWindow; 35 | //Assign snap shot image to this view 36 | -(void)assignBackgroundColorWithImage:(UIImage*)image; 37 | 38 | //Using this following function, user can change background color of movable control, default, black color with 0.4 alpha 39 | -(void)setMovableControlBackgroundColor:(UIColor*)backgroundColor; 40 | -(void)setMovableControlBackgroundColor:(UIColor *)backgroundColor withAlpha:(CGFloat)alpha; 41 | 42 | //Add scribble view to snap shot view. 43 | -(void)addScribbleControllToSnapView; 44 | //Remove scribble view from snap shot view. 45 | -(void)removeScribbleControllFromSnapView; 46 | //Change scribble color 47 | -(void)changeScribbleColor:(UIColor*)scribbleColor; 48 | 49 | //NoSelection view is enable while either playing audio or recording audio. It will disable all other control 50 | -(void)addNoSelectionView; 51 | //Get NoSelection view to front to disable all other control 52 | -(void)getNoSelectionViewToFront; 53 | //Send NoSelectionView to back to enable all other control. 54 | -(void)sendNoSelectionViewToBack; 55 | //Add movable control to snapshot view when first scribble ended. 56 | -(void)addMovableControlToSnapView; 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /TattleControl/TattleControl.h: -------------------------------------------------------------------------------- 1 | // 2 | // TattleControl.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import 26 | 27 | @interface TattleControl : UIView 28 | 29 | //Shared view has spot button, always in top of view hierarchy 30 | +(TattleControl*)sharedControl; 31 | 32 | /*Enable spot button to application window. This should start during app launch. Example code as below 33 | ..... 34 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 35 | [[TattleControl sharedControl] enableTattleToWindow:self.window]; 36 | ....... 37 | */ 38 | -(void)enableTattleToWindow:(UIWindow*)baseWindow; 39 | 40 | //By Default, spot button image drawn by programmatically, So you can change color by this api. 41 | //Default color: blue 42 | -(void)changeSpotImageColor:(UIColor*)spotColor; 43 | 44 | //Optional method. If you want to change spot image as your own, you can also do with that. 45 | -(void)setSpotButtonImage:(UIImage*)image; 46 | 47 | //Cancel all doing and close snap view 48 | -(void)closeButtonFired:(UIButton*)closeBtn; 49 | 50 | //Common Api to take snap shot for view 51 | -(UIImage*)takeScreenShotImageForView:(UIView *)baseView; 52 | 53 | //Send screen shot and audio via MFMailComposeViewController 54 | -(void)sendScreenShotAudioFiles; 55 | 56 | //Change Scribble Color according to background, default is black 57 | -(void)setScribbleColor:(UIColor*)scribbleColor; 58 | 59 | //Change background color of movable control. default is black(transparent) 60 | -(void)setMovableControlBackgroundColor:(UIColor*)backgroundColor; 61 | -(void)setMovableControlBackgroundColor:(UIColor *)backgroundColor withAlpha:(CGFloat)alpha; 62 | 63 | //Email Configuration 64 | -(void)addRecipientMailId:(NSString*)recipientMailId; 65 | -(void)addCCMailId:(NSString*)ccMailId; 66 | -(void)assignMailSubject:(NSString*)subject; 67 | -(void)assignRecipientEmailId:(NSString*)toEmailId withCCId:(NSString*)ccId emailSubject:(NSString*)subject; 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /Example-Swift/Classes/FirstVC/FirstVC.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Example/Classes/FirstVC/FirstVC.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /TattleControl/ScribCapControl.m: -------------------------------------------------------------------------------- 1 | // 2 | // ScribCapControl.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "ScribCapControl.h" 26 | 27 | @interface ScribCapControl () 28 | 29 | @end 30 | 31 | @implementation ScribCapControl 32 | 33 | #pragma mark - Shared Control 34 | 35 | +(ScribCapControl*)sharedControl 36 | { 37 | static dispatch_once_t predicate = 0; 38 | static ScribCapControl *sharedInstance = nil; 39 | 40 | dispatch_once(&predicate, ^{ 41 | sharedInstance = [[self alloc] init]; 42 | }); 43 | return sharedInstance; 44 | } 45 | #pragma mark - Pass touch event to View 46 | 47 | -(void)sendEvent:(UIEvent*)event withTouch:(UITouch*)touch 48 | { 49 | NSSet *touches = [event allTouches]; 50 | for (UITouch *aTouch in touches) { 51 | if ((UITouchPhaseBegan == aTouch.phase) || (UITouchPhaseEnded == aTouch.phase)) { 52 | break; 53 | } 54 | } 55 | if (!self.scribTarget || !self.controller) 56 | return ; 57 | for (UITouch *touch in touches) { 58 | if (UITouchPhaseBegan == touch.phase) { 59 | CGPoint pt = [touch locationInView:self.scribTarget]; 60 | if (CGRectContainsPoint([self.scribTarget bounds], pt)) { 61 | [self.controller didPassOnTouch:touch withEvent:event]; 62 | } 63 | } else { 64 | [self.controller didPassOnTouch:touch withEvent:event]; 65 | } 66 | } 67 | } 68 | 69 | #pragma mark - Handle Touch with Event 70 | 71 | - (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event 72 | { 73 | if (!(UIEventTypeTouches == event.type)) 74 | return NO; 75 | [self sendEvent:event withTouch:touch]; 76 | return YES; 77 | } 78 | 79 | - (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event 80 | { 81 | if (!(UIEventTypeTouches == event.type)) 82 | return NO; 83 | [self sendEvent:event withTouch:touch]; 84 | return YES; 85 | } 86 | 87 | - (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event 88 | { 89 | if (!(UIEventTypeTouches == event.type)) 90 | return ; 91 | [self sendEvent:event withTouch:touch]; 92 | } 93 | 94 | @end 95 | -------------------------------------------------------------------------------- /TattleControl/ScribbleEraseView.h: -------------------------------------------------------------------------------- 1 | // 2 | // ScribbleEraseView.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | #import 25 | 26 | @class Scribble; 27 | /* Scribble Completion block called every scribble end*/ 28 | typedef void(^ScribbleCompletionBlock)(); 29 | 30 | /* Scribble delegate track scribbling state while we touch and drag.*/ 31 | @protocol ScribbleViewDelegate 32 | @optional 33 | -(void) scribbleDidStart; 34 | -(void) scribbleDidEnd; 35 | 36 | //Logging Activity 37 | -(void) logEraseStart; 38 | -(void) logScribbleStart; 39 | -(void) logEraseEnd; 40 | -(void) logScribbleEnd; 41 | 42 | @end 43 | 44 | @interface ScribbleEraseView : UIView 45 | 46 | /* Width of the scribble, To default, see this constant TScribbleLineWidth*/ 47 | @property (nonatomic) float lineWidth, eraseWidth; 48 | /* In future, we decide to provide erase control, this bool refer to erase or scribble */ 49 | @property (nonatomic) BOOL isEraseOn; 50 | /* Storke color of scribble, default black color. Now we can change store color by [TattleControl setScribbleColor:...], We're also planning to provide dynamically change scribble color by providing extra control to movableEditorview. */ 51 | @property (nonatomic,strong) UIColor *currentScribbleStrokeColor; 52 | /* Scribble delegate to show scribble state*/ 53 | @property (nonatomic, weak) id scribbleDelegate; 54 | /* this block will get call when scribble end */ 55 | @property (nonatomic,strong) ScribbleCompletionBlock scribbleEndCompletion; 56 | 57 | - (id)initWithFrame:(CGRect)frame; 58 | /* This will reset view by clearing all scribbles*/ 59 | - (void)resetView; 60 | 61 | /* Draw scribble path by using following method while touch happen with view, */ 62 | - (void) addTouchPoint:(CGPoint)point forTouch:(UITouch *)touch AndEvent:(UIEvent *) event; 63 | - (void) appendTouchPoint:(CGPoint) point forTouch:(UITouch *)touch AndEvent:(UIEvent *) event; 64 | - (void) endTouchPoint:(CGPoint) point forTouch:(UITouch *)touch AndEvent:(UIEvent *) event; 65 | - (void) cancelTouchPoint:(CGPoint) point forTouch:(UITouch *)touch AndEvent:(UIEvent *) event; 66 | -(void) outOfBounds:(CGPoint)point forTouch:(UITouch*)touch andEvent:(UIEvent*)event; 67 | 68 | //Chagne scribble color , default black color 69 | -(void) changeColorOfScribbleTo:(UIColor*)someColor; 70 | @end 71 | -------------------------------------------------------------------------------- /TattleControl/MovableEditorView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 29 | 30 | 31 | 32 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /TattleControl/MovableEditorViewiPad.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 29 | 30 | 31 | 32 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /TattleControl/TPopupView.m: -------------------------------------------------------------------------------- 1 | // 2 | // PopupView.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "TPopupView.h" 26 | #import "CommonMacro.h" 27 | 28 | @interface TPopupView () 29 | 30 | @property (nonatomic,weak) IBOutlet UILabel *message; 31 | @property (nonatomic,weak) IBOutlet UIButton *okButton,*cancelButton; 32 | @property (nonatomic,weak) IBOutlet UIView *baseVeiw; 33 | 34 | @end 35 | 36 | @implementation TPopupView 37 | 38 | #pragma mark - View Initilization 39 | 40 | -(void)awakeFromNib 41 | { 42 | self.backgroundColor = [UIColor clearColor]; 43 | self.baseVeiw.backgroundColor = [UIColor whiteColor]; 44 | } 45 | 46 | #pragma mark - Confiramtion button action 47 | 48 | -(IBAction)okButtonFired:(id)sender 49 | { 50 | if (self.okActionBlock) 51 | self.okActionBlock(); 52 | } 53 | 54 | -(IBAction)cancelButtonFired:(id)sender 55 | { 56 | if (self.cancelActionBlock) 57 | self.cancelActionBlock(); 58 | } 59 | 60 | #pragma mark - Animation Delegate - only for PopUp 61 | 62 | -(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag 63 | { 64 | [self removeFromSuperview]; 65 | [self.layer removeAnimationForKey:@"SlideAnimationUp"]; 66 | } 67 | 68 | #pragma mark - Hide View With Animation 69 | 70 | -(void)hideWithAnimation 71 | { 72 | CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath: @"opacity"]; 73 | alphaAnimation.fromValue = [NSNumber numberWithFloat:1.0]; 74 | alphaAnimation.toValue = [NSNumber numberWithFloat:0.0]; 75 | alphaAnimation.duration = 0.4f; 76 | alphaAnimation.delegate = self; 77 | [self.layer addAnimation: alphaAnimation forKey: @"fade"]; 78 | CABasicAnimation *slideAnimation = [CABasicAnimation animationWithKeyPath:@"position"]; 79 | slideAnimation.fromValue =[NSValue valueWithCGPoint:CGPointMake(self.frame.size.width/2,self.frame.size.height/2)]; 80 | slideAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(self.frame.size.width/2, self.frame.size.height/2-self.baseVeiw.frame.size.height)]; 81 | slideAnimation.duration = 0.4f; 82 | slideAnimation.fillMode = kCAFillModeForwards; 83 | slideAnimation.removedOnCompletion = NO; 84 | [self.layer addAnimation:slideAnimation forKey:@"SlideAnimationUp"]; 85 | } 86 | 87 | #pragma mark - Show View with Animation 88 | 89 | -(void)showWithAnimation 90 | { 91 | CABasicAnimation *alphaAnimation = [CABasicAnimation animationWithKeyPath: @"opacity"]; 92 | alphaAnimation.fromValue = [NSNumber numberWithFloat:0.0]; 93 | alphaAnimation.toValue = [NSNumber numberWithFloat:1.0]; 94 | alphaAnimation.duration = 0.4f; 95 | [self.layer addAnimation: alphaAnimation forKey: @"fade"]; 96 | CABasicAnimation *slideAnimation = [CABasicAnimation animationWithKeyPath:@"position"]; 97 | slideAnimation.fromValue = [NSValue valueWithCGPoint:CGPointMake(self.frame.size.width/2, self.frame.size.height/2-self.baseVeiw.frame.size.height)]; 98 | slideAnimation.toValue = [NSValue valueWithCGPoint:CGPointMake(self.frame.size.width/2,self.frame.size.height/2)]; 99 | slideAnimation.duration = 0.4f; 100 | [self.layer addAnimation:slideAnimation forKey:@"SlideAnimationDown"]; 101 | } 102 | 103 | @end 104 | -------------------------------------------------------------------------------- /TattleControl/UIController+SnapShotButton.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIController+SnapShotButton.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "UIController+SnapShotButton.h" 26 | #import "TattleControl.h" 27 | #import 28 | 29 | @implementation UIViewController (SnapShotButton) 30 | 31 | + (void)load { 32 | static dispatch_once_t onceToken; 33 | dispatch_once(&onceToken, ^{ 34 | Class class = [self class]; 35 | SEL originalSelector = @selector(viewDidAppear:); 36 | SEL swizzledSelector = @selector(snapControlViewDidAppear:); 37 | SEL addSwizzledSelector = @selector(snapControllerViewDidAppearAdd:); 38 | Method originalMethod = class_getInstanceMethod(class, originalSelector); 39 | Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); 40 | Method addSwizzledMethod = class_getInstanceMethod(class, addSwizzledSelector); 41 | BOOL didAddMethod = 42 | class_addMethod(class, 43 | originalSelector, 44 | method_getImplementation(addSwizzledMethod), 45 | method_getTypeEncoding(addSwizzledMethod)); 46 | if (!didAddMethod) 47 | { 48 | method_exchangeImplementations(originalMethod, swizzledMethod); 49 | } 50 | }); 51 | } 52 | 53 | #pragma mark - Method Swizzling 54 | -(void)snapControllerViewDidAppearAdd:(BOOL)animated 55 | { 56 | [[TattleControl sharedControl] removeFromSuperview]; 57 | [self.view.window insertSubview:[TattleControl sharedControl] aboveSubview:self.view]; 58 | } 59 | 60 | - (void)snapControlViewDidAppear:(BOOL)animated { 61 | [self snapControlViewDidAppear:animated]; 62 | [[TattleControl sharedControl] removeFromSuperview]; 63 | [self.view.window insertSubview:[TattleControl sharedControl] aboveSubview:self.view]; 64 | } 65 | 66 | @end 67 | 68 | @implementation UIView (SnapShotButton) 69 | 70 | + (void)load { 71 | static dispatch_once_t onceToken; 72 | dispatch_once(&onceToken, ^{ 73 | Class class = [self class]; 74 | SEL originalSelector = @selector(addSubview:); 75 | SEL swizzledSelector = @selector(snapControlViewAddSubview:); 76 | SEL addSwizzledSelector = @selector(snapControlViewAddSubviewAdd:); 77 | Method originalMethod = class_getInstanceMethod(class, originalSelector); 78 | Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); 79 | Method addSwizzledMethod = class_getInstanceMethod(class, addSwizzledSelector); 80 | BOOL didAddMethod = 81 | class_addMethod(class, 82 | originalSelector, 83 | method_getImplementation(addSwizzledMethod), 84 | method_getTypeEncoding(addSwizzledMethod)); 85 | if (!didAddMethod) { 86 | method_exchangeImplementations(originalMethod, swizzledMethod); 87 | } 88 | }); 89 | } 90 | 91 | #pragma mark - Method Swizzling 92 | 93 | -(void)snapControlViewAddSubviewAdd:(UIView*)animated 94 | { 95 | if (self.window) 96 | { 97 | [[TattleControl sharedControl] removeFromSuperview]; 98 | [self.window insertSubview:[TattleControl sharedControl] aboveSubview:self]; 99 | } 100 | } 101 | 102 | - (void)snapControlViewAddSubview:(UIView*)animated { 103 | 104 | [self snapControlViewAddSubview:animated]; 105 | if (self.window) 106 | { 107 | [[TattleControl sharedControl] removeFromSuperview]; 108 | [self.window insertSubview:[TattleControl sharedControl] aboveSubview:self]; 109 | } 110 | } 111 | 112 | @end 113 | -------------------------------------------------------------------------------- /TattleControl/Scribble.m: -------------------------------------------------------------------------------- 1 | // 2 | // Scribble.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "Scribble.h" 26 | #import "ScribblePathPoint.h" 27 | 28 | @interface Scribble () 29 | 30 | @property (nonatomic) CGPoint currentPoint,previousPoint1,previousPoint2; 31 | @property BOOL isEmpty; 32 | @property (nonatomic) CGMutablePathRef path; 33 | 34 | @property (nonatomic, strong) NSMutableArray *points; 35 | 36 | @end 37 | 38 | @implementation Scribble 39 | 40 | #pragma mark - Scribble Initilization 41 | 42 | - (id)init 43 | { 44 | self = [super init]; 45 | if (self) 46 | { 47 | self.points = [[NSMutableArray alloc] init]; 48 | self.strokeColor = [UIColor blackColor] ; 49 | self.isEraseOn = NO; 50 | self.path = CGPathCreateMutable(); 51 | self.isEmpty = YES; 52 | } 53 | return self; 54 | } 55 | 56 | #pragma mark - Scribble points 57 | 58 | CGPoint getMidPoint(CGPoint p1, CGPoint p2) { 59 | return CGPointMake((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5); 60 | } 61 | 62 | - (NSMutableArray*)getPoints 63 | { 64 | return self.points; 65 | } 66 | 67 | #pragma mark - Add point to scribble 68 | 69 | -(CGRect)updatePointToPath:(CGPoint)point 70 | { 71 | self.isEmpty = NO; 72 | if (self.points.count == 1) { 73 | self.previousPoint2 = self.previousPoint1 = self.currentPoint = [self getPointAtIndex:self.points.count - 1]; 74 | } 75 | else if(self.points.count == 2){ 76 | self.previousPoint2 = self.previousPoint1 =[self getPointAtIndex:self.points.count - 2]; 77 | self.currentPoint = [self getPointAtIndex:self.points.count - 1]; 78 | } 79 | else if(self.points.count > 2){ 80 | self.previousPoint2 = [self getPointAtIndex:self.points.count - 3]; 81 | self.previousPoint1 = [self getPointAtIndex:self.points.count - 2]; 82 | self.currentPoint = [self getPointAtIndex:self.points.count - 1]; 83 | } 84 | 85 | CGPoint mid1 = getMidPoint(self.previousPoint1, self.previousPoint2); 86 | CGPoint mid2 = getMidPoint(self.currentPoint,self.previousPoint1); 87 | CGMutablePathRef subpath = CGPathCreateMutable(); 88 | CGPathMoveToPoint(subpath, NULL, mid1.x, mid1.y); 89 | CGPathAddQuadCurveToPoint(subpath, NULL, self.previousPoint1.x, self.previousPoint1.y, mid2.x, mid2.y); 90 | CGRect bounds = CGPathGetBoundingBox(subpath); 91 | CGPathAddPath(self.path, NULL, subpath); 92 | CGPathRelease(subpath); 93 | CGRect drawBox = bounds; 94 | drawBox.origin.x -= self.lineWidth * 2.0; 95 | drawBox.origin.y -= self.lineWidth * 2.0; 96 | drawBox.size.width += self.lineWidth * 4.0; 97 | drawBox.size.height += self.lineWidth * 4.0; 98 | return drawBox; 99 | } 100 | 101 | - (CGRect)addPoint:(CGPoint)point 102 | { 103 | ScribblePathPoint *newPoint = [[ScribblePathPoint alloc] init]; 104 | newPoint.point=point; 105 | [self.points addObject:newPoint]; 106 | return [self updatePointToPath:point]; 107 | } 108 | 109 | #pragma mark - Get point At touch Index 110 | 111 | - (CGPoint) getPointAtIndex:(NSUInteger)index 112 | { 113 | CGPoint thePoint=CGPointZero; 114 | @try { 115 | ScribblePathPoint *point = [self.points objectAtIndex:index]; 116 | thePoint = point.point; 117 | } 118 | @catch (NSException * e) { 119 | @throw e; 120 | } 121 | return thePoint; 122 | } 123 | 124 | #pragma mark - Scribble Path 125 | 126 | - (CGMutablePathRef)drawingPath{ 127 | if (self.isEmpty || !self.path) { 128 | self.path = CGPathCreateMutable(); 129 | self.isEmpty = NO; 130 | [self recalculatePath]; 131 | } 132 | return self.path; 133 | } 134 | 135 | #pragma mark - Recalcualte Path 136 | 137 | - (void) recalculatePath{ 138 | for (int i = 0; i < self.points.count; i++) { 139 | if (i == 0) { 140 | self.previousPoint2 = self.previousPoint1 = self.currentPoint = [self getPointAtIndex:i]; 141 | } 142 | else if(i == 1){ 143 | self.previousPoint2 = self.previousPoint1 = [self getPointAtIndex:i - 1]; 144 | self.currentPoint = [self getPointAtIndex:i]; 145 | } 146 | else if(i >= 2){ 147 | self.previousPoint2 = [self getPointAtIndex:i - 2]; 148 | self.previousPoint1 = [self getPointAtIndex:i - 1]; 149 | self.currentPoint = [self getPointAtIndex:i]; 150 | } 151 | CGPoint mid1 = getMidPoint(self.previousPoint1, self.previousPoint2); 152 | CGPoint mid2 = getMidPoint(self.currentPoint, self.previousPoint1); 153 | CGMutablePathRef subpath = CGPathCreateMutable(); 154 | CGPathMoveToPoint(subpath, NULL, mid1.x, mid1.y); 155 | CGPathAddQuadCurveToPoint(subpath, NULL, self.previousPoint1.x, self.previousPoint1.y, mid2.x, mid2.y); 156 | CGPathAddPath(self.path, NULL, subpath); 157 | CGPathRelease(subpath); 158 | } 159 | } 160 | 161 | #pragma mark - Getting top most point of scribble 162 | 163 | - (CGRect) enclosingRect { 164 | return CGPathGetBoundingBox(self.path); 165 | } 166 | 167 | - (CGPoint)topMostPoint{ 168 | return [self enclosingRect].origin; 169 | } 170 | 171 | @end 172 | -------------------------------------------------------------------------------- /TattleControl/TAudioManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // TAudioManager.h 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | #import 25 | #import 26 | 27 | /* enumeration Constant for Recording status, this constant used for UI appearance 28 | @TRecordingStatusStarted : recorder already started and being in recording. 29 | @TRecordingStatusPaused : recorder in pause state. 30 | @TRecordingStatusAlreadyStopped: recorder stopped. 31 | @TRecordingStatusResumed : Recorder going to resume state from pause. 32 | @TRecordingStatusFailedToStart: Fail Due to hardware support, logging describ failed reason. 33 | @TRecordingStatusFailedToStartAudioSession: Fail due to audio session, that means, if you don't allow with microphone in your setting, audio session will fail 34 | */ 35 | 36 | typedef enum TRecordingStatus{ 37 | 38 | TRecordingStatusStarted, 39 | TRecordingStatusPaused, 40 | TRecordingStatusAlreadyStopped, 41 | TRecordingStatusResumed, 42 | TRecordingStatusFailedToStart, 43 | TRecordingStatusFailedToStartAudioSession, 44 | TRecordingStatusUnkownState 45 | }TRecordingStatus; 46 | 47 | /* enumeration for Recorder. this constant show state of recorder. 48 | @TRecorderStatusIdle: Recorder yet to be start 49 | @TRecorderStatusRecording: Recorder started and being in recording 50 | @TRecorderStatusPaused : Recorder in pause state. 51 | */ 52 | 53 | typedef enum TRecorderStatus{ 54 | 55 | TRecorderStatusIdle, 56 | TRecorderStatusRecording, 57 | TRecorderStatusPaused, 58 | TRecorderStatusUnkown 59 | }TRecorderStatus; 60 | 61 | /* enumeration Constant for playing status which used fro UI Appearance 62 | @TPlayingStatusStarted : player already started and being in playing. 63 | @TPlayingStatusPaused : player in pause state. 64 | @TPlayingStatusStopped: playing stopped. 65 | @TPlayingStatusResumed : player going to resume state from pause. 66 | @TPlayingStatusFailedToStart: Fail Due to hardware support, logging describ failed reason. 67 | @TPlayingStatusFailedToStartAudioSession: Fail due to audio session, that means, if you don't allow with microphone in your setting, audio session will fail 68 | */ 69 | 70 | typedef enum TPlayingStatus{ 71 | TPlayingStatusStarted, 72 | TPlayingStatusPaused, 73 | TPlayingStatusStopped, 74 | TPlayingStatusResumed, 75 | TPlayingStatusFailedToStart, 76 | TPlayingStatusFailedToStartAudioSession, 77 | TPlayingStatusUnknownState 78 | 79 | }TPlayingStatus; 80 | 81 | /* enumeration for player. this constant show state of player. 82 | @TPlayerStatusIdle: player yet to be start 83 | @TPlayerStatusPlaying: player started and being in playing 84 | @TPlayerStatusPaused : Player in pause state. 85 | */ 86 | 87 | typedef enum TPlayerStatus{ 88 | TPlayerStatusIdle, 89 | TPlayerStatusPlaying, 90 | TPlayerStatusPaused 91 | }TPlayerStatus; 92 | 93 | /* Recording progress block periodically return recording time and corresponding pictch lever */ 94 | typedef void (^TRecordingProgressBlock) (NSString* recordingTime, CGFloat seconds,float pitchLevel); 95 | 96 | /* Playing progress block periodically retrun playing time with respect to total duration */ 97 | typedef void (^TPlayingProgressBlock) (NSString* currentPlayingTime, CGFloat seconds, CGFloat totalDuration); 98 | 99 | /* Playing completion block once player finish playing corresponding audio file which used to make UI Changes */ 100 | typedef void (^TPlayingCompletionBlock) (); 101 | 102 | /* If error occur during playing, this block get called to make changes in UI */ 103 | typedef void (^TAudioPlayerDecodeErrorBlock) (NSError *decodeError); 104 | 105 | 106 | @interface TAudioManager : NSObject 107 | 108 | @property(nonatomic, copy) TRecordingProgressBlock recordingProgressBlock; 109 | @property(nonatomic, copy) TPlayingProgressBlock playingProgressBlock; 110 | @property(nonatomic, copy) TPlayingCompletionBlock playingCompletionBlock; 111 | @property(nonatomic, copy) TAudioPlayerDecodeErrorBlock audioPlayerErrorBlock; 112 | 113 | //Shared control for audio related methods 114 | +(TAudioManager*)sharedAudioManager; 115 | 116 | //Start recording with created file path(Document directory), if file doesn't exist at file path, recorder will fail to start 117 | -(TRecordingStatus)startRecording:(NSString*)fileName; 118 | //This will change recorder state to from pause to resume or vice versa. 119 | -(TRecordingStatus)toggleRecording; 120 | //This method going to stop recorder. 121 | -(TRecordingStatus)stopRecording; 122 | 123 | //Start playing with file path, if file doesn't exist at file path, player will fail to start 124 | -(TPlayingStatus)startPlaying:(NSString*)fileName; 125 | //This will change player state from pause to resume or vice versa. 126 | -(TPlayingStatus)togglePlaying; 127 | //This method going to stop player. 128 | -(TPlayingStatus)stopPlaying; 129 | 130 | //Getting Current status of the recoder 131 | -(TRecorderStatus)getRecorderStatus; 132 | //Getting Current status of the player 133 | -(TPlayerStatus)getPlayerStatus; 134 | 135 | //Assign file name for recording. 136 | -(void)assignTempFileName:(NSString*)fileName; 137 | @end 138 | -------------------------------------------------------------------------------- /TattleControl/UIImage+GiffAnimation.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+GiffAnimation.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | /* Credits goes to Rob(https://github.com/mayoff/uiimage-from-animated-gif/) */ 25 | 26 | #import "UIImage+GiffAnimation.h" 27 | #import 28 | 29 | #if __has_feature(objc_arc) 30 | #define toCF (__bridge CFTypeRef) 31 | #define fromCF (__bridge id) 32 | #else 33 | #define toCF (CFTypeRef) 34 | #define fromCF (id) 35 | #endif 36 | 37 | @interface GifHelperMethods : NSObject 38 | 39 | +(UIImage *)animatedImageWithAnimatedGIFReleasingImageSource:(CGImageSourceRef CF_RELEASES_ARGUMENT)source; 40 | +(UIImage *)animatedImageWithAnimatedGIFImageSource:(CGImageSourceRef )source; 41 | +(int)delayCentisecondsForImage:(CGImageSourceRef ) source atIndex:(size_t)i; 42 | 43 | @end 44 | 45 | @implementation GifHelperMethods 46 | 47 | +(UIImage *)animatedImageWithAnimatedGIFReleasingImageSource:(CGImageSourceRef CF_RELEASES_ARGUMENT)source 48 | { 49 | if (source) { 50 | UIImage *image =[GifHelperMethods animatedImageWithAnimatedGIFImageSource:source]; 51 | CFRelease(source); 52 | return image; 53 | } else { 54 | return nil; 55 | } 56 | } 57 | +(int)delayCentisecondsForImage:(CGImageSourceRef ) source atIndex:(size_t)i 58 | { 59 | int delayCentiseconds = 1; 60 | CFDictionaryRef const properties = CGImageSourceCopyPropertiesAtIndex(source, i, NULL); 61 | if (properties) { 62 | CFDictionaryRef const gifProperties = CFDictionaryGetValue(properties, kCGImagePropertyGIFDictionary); 63 | if (gifProperties) { 64 | NSNumber *number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFUnclampedDelayTime); 65 | if (number == NULL || [number doubleValue] == 0) { 66 | number = fromCF CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFDelayTime); 67 | } 68 | if ([number doubleValue] > 0) { 69 | // Even though the GIF stores the delay as an integer number of centiseconds, ImageIO “helpfully” converts that to seconds for us. 70 | delayCentiseconds = (int)lrint([number doubleValue] * 100); 71 | } 72 | } 73 | CFRelease(properties); 74 | } 75 | return delayCentiseconds; 76 | } 77 | 78 | static void createImagesAndDelays(CGImageSourceRef source, size_t count, CGImageRef imagesOut[count], int delayCentisecondsOut[count]) { 79 | for (size_t i = 0; i < count; ++i) { 80 | imagesOut[i] = CGImageSourceCreateImageAtIndex(source, i, NULL); 81 | delayCentisecondsOut[i] = [GifHelperMethods delayCentisecondsForImage:source atIndex:i]; 82 | } 83 | } 84 | 85 | +(int)sum:(size_t)count values:(int *)values 86 | { 87 | int theSum = 0; 88 | for (size_t i = 0; i < count; ++i) { 89 | theSum += values[i]; 90 | } 91 | return theSum; 92 | } 93 | 94 | int pairGCD(int a, int b) { 95 | if (a < b) 96 | return pairGCD(b, a); 97 | while (true) { 98 | int r = a % b; 99 | if (r == 0) 100 | return b; 101 | a = b; 102 | b = r; 103 | } 104 | } 105 | 106 | int vectorGCD(size_t count, int const *const values) { 107 | int gcd = values[0]; 108 | for (size_t i = 1; i < count; ++i) { 109 | gcd = pairGCD(values[i], gcd); 110 | } 111 | return gcd; 112 | } 113 | 114 | NSArray *frameArray(size_t count, CGImageRef const images[count], int const delayCentiseconds[count], int totalDurationCentiseconds) { 115 | int gcd = vectorGCD(count, delayCentiseconds); 116 | size_t frameCount = totalDurationCentiseconds / gcd; 117 | UIImage *frames[frameCount]; 118 | for (size_t i = 0, f = 0; i < count; ++i) { 119 | UIImage *frame = [UIImage imageWithCGImage:images[i]]; 120 | for (size_t j = delayCentiseconds[i] / gcd; j > 0; --j) { 121 | frames[f++] = frame; 122 | } 123 | } 124 | return [NSArray arrayWithObjects:frames count:frameCount]; 125 | } 126 | 127 | +(void)releaseImages:(CGImageRef*)images count:(size_t)count 128 | { 129 | for (size_t i = 0; i < count; ++i) { 130 | CGImageRelease(images[i]); 131 | } 132 | } 133 | 134 | +(UIImage *)animatedImageWithAnimatedGIFImageSource:(CGImageSourceRef )source { 135 | size_t count = CGImageSourceGetCount(source); 136 | CGImageRef images[count]; 137 | int delayCentiseconds[count]; // in centiseconds 138 | createImagesAndDelays(source, count, images, delayCentiseconds); 139 | int totalDurationCentiseconds = [GifHelperMethods sum:count values:delayCentiseconds]; 140 | NSArray *frames = frameArray(count, images, delayCentiseconds, totalDurationCentiseconds); 141 | UIImage *animation = [UIImage animatedImageWithImages:frames duration:(NSTimeInterval)totalDurationCentiseconds / 100.0]; 142 | [GifHelperMethods releaseImages:images count:count]; 143 | return animation; 144 | } 145 | 146 | @end 147 | 148 | @implementation UIImage (animatedGIF) 149 | 150 | + (UIImage *)animatedImageWithAnimatedGIFURL:(NSURL *)url { 151 | return [GifHelperMethods animatedImageWithAnimatedGIFReleasingImageSource:CGImageSourceCreateWithURL(toCF url, NULL)]; 152 | } 153 | 154 | @end 155 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tattle-UI-iOS 2 | ----------------- 3 | 4 | ## Why do you need Tattle-UI 5 | 6 | As a developer, we struggle to understand and reproduce few UI 7 | bugs reported by the tester. In tester's point of view, tester 8 | writes paragraphs to explain a simple UI misalignment when they test. 9 | 10 | Tattle-UI solves this problem by providing a simple mechanism to get UI feedback 11 | from testers. 12 | 13 | For Android version, please refer to https://github.com/npctech/Tattle-UI-Android 14 | 15 | ## What do you see on your app 16 | 17 | After integration, Tattle-UI library adds a floating button on every screen. 18 | Tester can click on this button whenever he sees some issue with the UI. 19 | Tattle-UI library takes the snapshot of the current screen and allow the tester 20 | to mark problematic section using scribbles. Tester may wish to add a audio note along with this. 21 | Tattle-UI provides tester to send them in Email. We use [anypic app](https://github.com/ParsePlatform/Anypic) to demo this control. 22 | 23 | [![](https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/master/Screenshot/Thumbnail/ShotImage.png)](https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/master/Screenshot/ShotImage.png) 24 | [![](https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/master/Screenshot/Thumbnail/Scribble.png)](https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/master/Screenshot/Scribble.png) 25 | [![](https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/master/Screenshot/Thumbnail/AudioRecordPlay.png)](https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/master/Screenshot/AudioRecordPlay.png) 26 | [![](https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/master/Screenshot/Thumbnail/ShareViaMail.png)](https://raw.githubusercontent.com/npctech/Tattle-UI-iOS/master/Screenshot/ShareViaMail.png) 27 | 28 | # Integration steps 29 | 30 | ## [Objective-C](https://github.com/npctech/Tattle-UI-iOS/tree/master/Example) 31 | 32 | ### From github 33 | * Download the code from github and include the files into your project. 34 | * Include following frameworks 35 | ```ruby 36 | OpenGLES.framework 37 | QuartzCore.framework 38 | AVFoundation.framework 39 | CoreGraphics.framework 40 | MessageUI.framework 41 | ImageIO.framework 42 | ``` 43 | * Import "UIController+SnapShotButton.h" file into App-Prefix.pch file. `#import "UIController+SnapShotButton.h"` 44 | * Enable Tattle-UI control by adding below line in "AppDelegate.m". `#import "TattleControl.h"` 45 | * Invoke `enableTattleToWindow:` method, after main window creation. 46 | ```ruby 47 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 48 | [[TattleControl sharedControl] enableTattleToWindow:self.window]; 49 | ``` 50 | 51 | ### From CocoaPods 52 | * Podfile 53 | ```ruby 54 | platform :ios, '6.0' 55 | pod 'Tattle-UI-iOS', '~> 1.0.2' 56 | ``` 57 | * Enable Tattle-UI control by adding below line in "AppDelegate.m". `#import "TattleControl.h"` 58 | * Invoke `enableTattleToWindow:` method, after main window creation. 59 | ```ruby 60 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 61 | [[TattleControl sharedControl] enableTattleToWindow:self.window]; 62 | ``` 63 | 64 | ## [Swift](https://github.com/npctech/Tattle-UI-iOS/tree/master/Example-Swift) 65 | 66 | ### From github 67 | * Download the code from github and include the files into your project. 68 | * Include following header files into `AppModule-Bridging-Header.h` 69 | ```ruby 70 | #import "MovableEditorView.h" 71 | #import "Scribble.h" 72 | #import "ScribbleEraseView.h" 73 | #import "ScribblePathPoint.h" 74 | #import "CommonMacro.h" 75 | #import "ScribCapControl.h" 76 | #import "SnapShotView.h" 77 | #import "TattleControl.h" 78 | #import "TAudioManager.h" 79 | #import "TConstants.h" 80 | #import "TFileManager.h" 81 | #import "TPopupView.h" 82 | #import "UIController+SnapShotButton.h" 83 | #import "UIImage+GiffAnimation.h" 84 | ``` 85 | * Invoke `enableTattleToWindow:` method, after main window creation. 86 | ```ruby 87 | self.window = UIWindow(frame: UIScreen.mainScreen().bounds) 88 | TattleControl.sharedControl().enableTattleToWindow(self.window) 89 | ``` 90 | 91 | ### From CocoaPods 92 | * Podfile 93 | ```ruby 94 | platform :ios, '6.0' 95 | pod 'Tattle-UI-iOS', '~> 1.0.2' 96 | ``` 97 | * Include following header files into `AppModule-Bridging-Header.h` 98 | ```ruby 99 | #import "MovableEditorView.h" 100 | #import "Scribble.h" 101 | #import "ScribbleEraseView.h" 102 | #import "ScribblePathPoint.h" 103 | #import "CommonMacro.h" 104 | #import "ScribCapControl.h" 105 | #import "SnapShotView.h" 106 | #import "TattleControl.h" 107 | #import "TAudioManager.h" 108 | #import "TConstants.h" 109 | #import "TFileManager.h" 110 | #import "TPopupView.h" 111 | #import "UIController+SnapShotButton.h" 112 | #import "UIImage+GiffAnimation.h" 113 | ``` 114 | * Invoke `enableTattleToWindow:` method, after main window creation. 115 | ```ruby 116 | self.window = UIWindow(frame: UIScreen.mainScreen().bounds) 117 | TattleControl.sharedControl().enableTattleToWindow(self.window) 118 | ``` 119 | 120 | **Note:** 121 | * [CocoaPods](http://cocoapods.org) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries, [Getting started here](http://guides.cocoapods.org/using/getting-started.html) 122 | 123 | * Read [Importing Objective-C into Swift](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/MixandMatch.html#//apple_ref/doc/uid/TP40014216-CH10-XID_75) topic for swift migration. 124 | 125 | ## Requirement 126 | 127 | * Minimum ios target : iOS 6 128 | * Minimum xcode : Xcode 5.0 129 | * All IOS devices. 130 | * Only compatible with ARC. 131 | 132 | ## Limitation 133 | 134 | - Only supported for **portrait** orientation. 135 | - Audio recording supports only **2 minutes**. 136 | 137 | # Optional Configuration 138 | 139 | ## Objective-C 140 | 141 | * **Change scribble color (default black)** 142 | ```ruby 143 | [[TattleControl sharedControl] setScribbleColor:YOUR_Color]; 144 | ``` 145 | * **Change color of floating button** 146 | ```ruby 147 | [[TattleControl sharedControl] changeSpotImageColor:YOUR_Color]; 148 | ``` 149 | 150 | We could also insert our own image too instead spot icon. 151 | ```ruby 152 | [[TattleControl sharedControl] setSpotButtonImage:YOUR_Image]; 153 | ``` 154 | * **Change background color of floating control** 155 | ```ruby 156 | [[TattleControl sharedControl] setMovableControlBackgroundColor:YOUR_Color]; 157 | [[TattleControl sharedControl] setMovableControlBackgroundColor:YOUR_Color withAlpha:alpha]; 158 | ``` 159 | * **Set recipients email** 160 | ```ruby 161 | [[TattleControl sharedControl] assignRecipientEmailId:@"YOUR_EMAIL_HERE" withCCId:@"YOUR_EMAIL_HERE" emailSubject:@"UI Bug using Tattle UI"]; 162 | ``` 163 | * **Add more Recipient** 164 | ```ruby 165 | [[TattleControl sharedControl] addRecipientMailId:@"YOUR_EMAIL_HERE"]; 166 | ``` 167 | * **Add more CC** 168 | ```ruby 169 | [[TattleControl sharedControl] addCCMailId:@"YOUR_EMAIL_HERE"]; 170 | ``` 171 | 172 | ## Swift 173 | * **Change scribble color (default black)** 174 | ```ruby 175 | TattleControl.sharedControl().setScribbleColor(YOUR_Color) 176 | ``` 177 | 178 | * **Change color of floating button** 179 | ```ruby 180 | TattleControl.sharedControl().changeSpotImageColor(YOUR_Color) 181 | ``` 182 | 183 | We could also insert our own image too instead spot icon. 184 | ```ruby 185 | TattleControl.sharedControl().setSpotButtonImage(YOUR_Image) 186 | ``` 187 | 188 | * **Change background color of floating control** 189 | ```ruby 190 | TattleControl.sharedControl().setMovableControlBackgroundColor(YOUR_Color) 191 | TattleControl.sharedControl().setMovableControlBackgroundColor(YOUR_Color, withAlpha: alpha) 192 | ``` 193 | 194 | * **Set recipients email** 195 | ```ruby 196 | TattleControl.sharedControl().assignRecipientEmailId("YOUR_EMAIL_HERE", withCCId: "YOUR_EMAIL_HERE", emailSubject: "Bugs") 197 | ``` 198 | 199 | * **Add more Recipient** 200 | ```ruby 201 | TattleControl.sharedControl().addRecipientMailId("YOUR_EMAIL_HERE") 202 | ``` 203 | 204 | * **Add more CC** 205 | ```ruby 206 | TattleControl.sharedControl().addCCMailId("YOUR_EMAIL_HERE") 207 | ``` 208 | 209 | ## License 210 | 211 | This code is distributed under the terms and conditions of the [MIT license](LICENSE). 212 | -------------------------------------------------------------------------------- /TattleControl/TPopupView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 30 | 42 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 82 | 94 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /TattleControl/TFileManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // TFileManager.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "TFileManager.h" 26 | #import "TConstants.h" 27 | #import "TLogControlMacro.h" 28 | 29 | const NSString *SPOT_IT_DIR = @"/SpotIt"; 30 | const NSString *SCREEN_SHOT_DIR = @"/ScreenShots"; 31 | const NSString *AUDIO_DIR = @"/Audios"; 32 | const NSString *IMAGE_EXTENSION = @".png"; 33 | const NSInteger kMaxNumberOfAudios = 3; 34 | 35 | @interface TFileManager() 36 | @property(nonatomic, strong) NSString *recentFilePathForPlaying; 37 | @end 38 | 39 | @implementation TFileManager 40 | 41 | #pragma mark API methods 42 | #pragma mark sharedFileManager 43 | +(TFileManager*)sharedFileManager 44 | { 45 | //Crearte singleton class 46 | static dispatch_once_t predicate = 0; 47 | static TFileManager *sharedInstance = nil; 48 | dispatch_once(&predicate, ^{ 49 | sharedInstance = [[self alloc] init]; 50 | }); 51 | return sharedInstance; 52 | } 53 | 54 | #pragma mark Create a directory for given name 55 | -(NSString*)documentsDirectoryAppendedWithPathComponent:(NSString*)append 56 | { 57 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 58 | NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents folder 59 | NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:(NSString*)SPOT_IT_DIR]; 60 | 61 | if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath]) 62 | { 63 | NSError *error; 64 | if(![[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:&error]) 65 | { 66 | TLog(@"Failed to create directory %@", dataPath); 67 | return nil; 68 | } 69 | } 70 | 71 | NSString *appendedPath=[dataPath stringByAppendingPathComponent:append]; 72 | return appendedPath; 73 | } 74 | 75 | #pragma mark create a directory for audio 76 | -(NSString*)getAudioDirectoryPathWithAudioName:(NSString*)append 77 | { 78 | NSString *audioDir = [self documentsDirectoryAppendedWithPathComponent:(NSString*)AUDIO_DIR]; 79 | 80 | if(!audioDir) 81 | { 82 | return nil; 83 | } 84 | if (![[NSFileManager defaultManager] fileExistsAtPath:audioDir]) 85 | { 86 | NSError *error; 87 | if(![[NSFileManager defaultManager] createDirectoryAtPath:audioDir withIntermediateDirectories:NO attributes:nil error:&error]) 88 | { 89 | TLog(@"Failed to create directory %@", audioDir); 90 | return nil; 91 | } 92 | } 93 | 94 | NSString *appendedPath=[audioDir stringByAppendingPathComponent:append]; 95 | return appendedPath; 96 | } 97 | 98 | #pragma mark create screen name in screen shot directory 99 | -(NSString*)getScreenShotDirectoryPathWithScreenName:(NSString*)append 100 | { 101 | NSString *screenDir = [self documentsDirectoryAppendedWithPathComponent:(NSString*)SCREEN_SHOT_DIR]; 102 | 103 | if(!screenDir) 104 | { 105 | return nil; 106 | } 107 | if (![[NSFileManager defaultManager] fileExistsAtPath:screenDir]) 108 | { 109 | NSError *error; 110 | [[NSFileManager defaultManager] createDirectoryAtPath:screenDir withIntermediateDirectories:NO attributes:nil error:&error]; 111 | } 112 | 113 | NSString *appendedPath=[screenDir stringByAppendingPathComponent:append]; 114 | return appendedPath; 115 | } 116 | 117 | #pragma mark Archieve filepath 118 | -(NSString*)getScreenShotFilePath 119 | { 120 | 121 | NSString *fileName = [NSString stringWithFormat:@"%@%@", [NSDate date], IMAGE_EXTENSION]; 122 | NSString *filePath = [self getScreenShotDirectoryPathWithScreenName:fileName]; 123 | 124 | TLog(@"Image File path %@", filePath); 125 | return filePath; 126 | } 127 | 128 | #pragma mark Archieve filepath 129 | -(NSString*)getAudioFilePath 130 | { 131 | NSString *fileName = [NSString stringWithFormat:@"%@%@", [NSDate date], TAUDIO_EXTENSION]; 132 | NSString *filePath = [self getAudioDirectoryPathWithAudioName:fileName]; 133 | TLog(@"Audio File path %@", filePath); 134 | 135 | [self setRecentFilePathForPlaying:filePath]; 136 | return filePath; 137 | } 138 | 139 | #pragma mark get recently recorded file path 140 | -(NSString*)getRecentlyRecordedAudioFilePath 141 | { 142 | return (self.recentFilePathForPlaying) ? self.recentFilePathForPlaying : nil; 143 | } 144 | 145 | #pragma mark clearFilefromDiskPath 146 | -(BOOL)clearFileFromDisk:(NSString*)filePath 147 | { 148 | NSFileManager *fileMgnr = [NSFileManager defaultManager]; 149 | 150 | if([fileMgnr fileExistsAtPath:filePath]) 151 | { 152 | NSError *error = nil; 153 | return ([fileMgnr removeItemAtPath:filePath error:&error]) ? YES : NO; 154 | 155 | } 156 | else 157 | { 158 | TLog(@"File doesn't exist at path: %@", filePath); 159 | return NO; 160 | } 161 | } 162 | 163 | #pragma mark (Private methods) 164 | #pragma mark Check file path exist 165 | -(BOOL)checkFileExistAtPath:(NSString*)fileName 166 | { 167 | return ([[NSFileManager defaultManager] fileExistsAtPath:fileName]) ? YES : NO; 168 | } 169 | 170 | #pragma mark Returns full filepath from directory name 171 | -(NSMutableArray*)getFilePathArrayFromDirectory:(NSString*)archieveDirectoryPath 172 | { 173 | 174 | NSError *err = nil; 175 | NSArray *docContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:archieveDirectoryPath error:&err]; 176 | 177 | if(!(docContents && docContents.count > 0)) 178 | { 179 | TLog(@"Error: Directory is empty!"); 180 | return nil; 181 | } 182 | 183 | NSMutableArray *filePathArray = [NSMutableArray arrayWithCapacity:docContents.count]; 184 | 185 | for (NSString *fileName in docContents) 186 | { 187 | NSString *filePath = [archieveDirectoryPath stringByAppendingPathComponent:fileName]; 188 | [filePathArray addObject:filePath]; 189 | } 190 | return filePathArray; 191 | } 192 | 193 | #pragma mark get file path for screen shots 194 | -(NSArray*)getScreenShots 195 | { 196 | NSString *screenShotDir = [self documentsDirectoryAppendedWithPathComponent:(NSString*)SCREEN_SHOT_DIR]; 197 | NSArray *screenShots = [self getFilePathArrayFromDirectory:screenShotDir]; 198 | return screenShots; 199 | } 200 | 201 | #pragma mark get file path for recorded audio 202 | -(NSArray*)getRecordedAudios 203 | { 204 | NSString *audioDir = [self documentsDirectoryAppendedWithPathComponent:(NSString*)AUDIO_DIR]; 205 | NSArray *audios = [self getFilePathArrayFromDirectory:audioDir]; 206 | return audios; 207 | } 208 | 209 | #pragma mark save screen shot image 210 | 211 | -(BOOL)saveImage:(UIImage *)image 212 | { 213 | NSData *imageData = UIImagePNGRepresentation(image); 214 | if(!imageData) 215 | return NO; 216 | 217 | return [imageData writeToFile:[[TFileManager sharedFileManager]getScreenShotFilePath] atomically:YES]; 218 | } 219 | 220 | #pragma mark clear all recorded 221 | -(void)clearAllAudios 222 | { 223 | NSString *audioDir = [self documentsDirectoryAppendedWithPathComponent:(NSString*)AUDIO_DIR]; 224 | NSArray *audios = [self getFilePathArrayFromDirectory:audioDir]; 225 | 226 | for (NSString *filePath in audios) 227 | [self clearFileFromDisk:filePath]; 228 | } 229 | 230 | #pragma mark clear all screen shots 231 | -(void)clearAllScreenShots 232 | { 233 | NSString *screenShotDir = [self documentsDirectoryAppendedWithPathComponent:(NSString*)SCREEN_SHOT_DIR]; 234 | NSArray *screenShots = [self getFilePathArrayFromDirectory:screenShotDir]; 235 | 236 | for (NSString *filePath in screenShots) 237 | [self clearFileFromDisk:filePath]; 238 | } 239 | 240 | 241 | #pragma mark sort file names recently created first 242 | -(NSMutableArray*)sortFilePathByDate:(NSArray*)unsortedArray{ 243 | 244 | NSMutableArray *newSortedrray = [NSMutableArray arrayWithArray:unsortedArray]; 245 | 246 | for (NSInteger i = 0; i < newSortedrray.count - 1; i++) 247 | { 248 | for (NSInteger j = i; j < newSortedrray.count ; j++) 249 | { 250 | NSString *filePath1 = [newSortedrray objectAtIndex:i]; 251 | NSString *filePath2 = [newSortedrray objectAtIndex:j]; 252 | 253 | NSDate* d1 = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath1 error:nil] objectForKey:@"NSFileCreationDate"]; 254 | NSDate* d2 = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath2 error:nil] objectForKey:@"NSFileCreationDate"]; 255 | 256 | NSComparisonResult result = [d1 compare:d2]; 257 | 258 | if (result == NSOrderedAscending) 259 | [newSortedrray exchangeObjectAtIndex:i withObjectAtIndex:j]; 260 | } 261 | } 262 | return newSortedrray; 263 | } 264 | 265 | #pragma Print the creation date of the file from file name 266 | -(void)createdDatesOfFile:(NSArray*)arr 267 | { 268 | #ifndef __OPTIMIZE__ 269 | NSInteger i = 0; 270 | for (NSString *filePath in arr) 271 | { 272 | NSDate* d1 = [[[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil] objectForKey:@"NSFileCreationDate"]; 273 | TLog(@"%ld - Created Date %@", (long)i++, d1); 274 | } 275 | #endif 276 | } 277 | 278 | #pragma Roll back recordedx audios 279 | -(void)rollbackTheRecordedAudios 280 | { 281 | NSString *audioDir = [self documentsDirectoryAppendedWithPathComponent:(NSString*)AUDIO_DIR]; 282 | NSArray *audios = [self getFilePathArrayFromDirectory:audioDir]; 283 | 284 | NSMutableArray *sortedArray = [self sortFilePathByDate:audios]; 285 | 286 | [self createdDatesOfFile:sortedArray]; 287 | if(sortedArray.count > kMaxNumberOfAudios) 288 | { 289 | do 290 | { 291 | if([self clearFileFromDisk:[sortedArray lastObject]]) 292 | [sortedArray removeLastObject]; 293 | } while (sortedArray.count > kMaxNumberOfAudios); 294 | TLog(@"After roll back"); 295 | [self createdDatesOfFile:sortedArray]; 296 | } 297 | } 298 | 299 | @end 300 | -------------------------------------------------------------------------------- /TattleControl/ScribbleEraseView.m: -------------------------------------------------------------------------------- 1 | // 2 | // ScribbleEraseView.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "ScribbleEraseView.h" 26 | #import "Scribble.h" 27 | #import "ScribCapControl.h" 28 | #import "TLogControlMacro.h" 29 | 30 | #define kEraseWidthAmplifyFactor (4.0) 31 | float const TScribbleLineWidth = 4.0; 32 | 33 | @interface ScribbleEraseView () 34 | 35 | @property (nonatomic,strong) NSMutableDictionary* scribbles; 36 | @property (nonatomic, strong) NSMutableArray *finishedScribbles; 37 | 38 | @end 39 | 40 | @implementation ScribbleEraseView 41 | 42 | #pragma mark - Scribble Erase view Initilization 43 | 44 | - (id)initWithFrame:(CGRect)frame 45 | { 46 | self = [super initWithFrame:frame]; 47 | if (self) { 48 | ScribCapControl *scribbleControl = [ScribCapControl sharedControl]; 49 | scribbleControl.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) ; 50 | scribbleControl.controller = self; 51 | scribbleControl.scribTarget = self; 52 | self.scribbleDelegate = self; 53 | [self addSubview:scribbleControl]; 54 | 55 | self.scribbles = [NSMutableDictionary dictionary]; 56 | self.lineWidth = self.eraseWidth = TScribbleLineWidth; 57 | self.finishedScribbles = [NSMutableArray array]; 58 | self.currentScribbleStrokeColor = [UIColor blackColor]; 59 | } 60 | return self; 61 | } 62 | 63 | #pragma mark - Draw Scribbles in context 64 | 65 | - (void)drawScribble:(Scribble *)scribble inContext:(CGContextRef)context{ 66 | 67 | CGColorRef colorRef = [scribble.strokeColor CGColor]; 68 | CGContextAddPath(context, scribble.drawingPath); 69 | CGContextSetLineCap(context, kCGLineCapRound); 70 | CGContextSetLineWidth(context, self.lineWidth); 71 | CGContextSetStrokeColorWithColor(context, colorRef); 72 | CGContextStrokePath(context); 73 | } 74 | 75 | - (void)drawRect:(CGRect)rect { 76 | [super drawRect:rect]; 77 | CGContextRef context = UIGraphicsGetCurrentContext(); 78 | for (NSString *key in self.scribbles){ 79 | Scribble *scribble = [self.scribbles valueForKey:key]; 80 | [self drawScribble:scribble inContext:context]; 81 | } 82 | 83 | for (Scribble *scribble in self.finishedScribbles) { 84 | if (!scribble.isEraseOn) { 85 | [self drawScribble:scribble inContext:context]; 86 | } 87 | } 88 | } 89 | 90 | #pragma mark - Clear all scribbles 91 | 92 | - (void)resetView{ 93 | [self.scribbles removeAllObjects]; 94 | [self.finishedScribbles removeAllObjects]; 95 | 96 | [self setNeedsDisplay]; 97 | } 98 | 99 | #pragma mark - Scribble manipulation - Helping methods 100 | 101 | - (void) initScribble:(Scribble*) scribble WithPoint : (CGPoint) point{ 102 | [scribble setStrokeColor:self.currentScribbleStrokeColor]; 103 | [scribble setIsEraseOn:self.isEraseOn]; 104 | 105 | if (scribble.isEraseOn) { 106 | [scribble setLineWidth:(self.eraseWidth * kEraseWidthAmplifyFactor)]; 107 | } 108 | else { 109 | [scribble setLineWidth:self.lineWidth]; 110 | [scribble addPoint:point]; 111 | } 112 | } 113 | 114 | - (void) eraseScribbleInRect:(CGRect)eraseRect{ 115 | for (Scribble *finishedScribble in self.finishedScribbles) 116 | { 117 | for (int pointIndex = 0; pointIndex < [finishedScribble getPoints].count; pointIndex++) { 118 | CGPoint currentPoint = [finishedScribble getPointAtIndex:pointIndex]; 119 | 120 | if (CGRectContainsPoint(eraseRect, currentPoint)){ 121 | finishedScribble.isEraseOn = YES; 122 | break; 123 | } 124 | } 125 | } 126 | } 127 | 128 | 129 | #pragma mark - Scribble manipulation depending on touch event 130 | 131 | - (void) addTouchPoint:(CGPoint)point forTouch:(UITouch *)touch AndEvent:(UIEvent *) event{ 132 | 133 | Scribble *scribble = [[Scribble alloc] init]; 134 | [self initScribble:scribble WithPoint:point]; 135 | if (scribble.isEraseOn) { 136 | if ([self.scribbleDelegate respondsToSelector:@selector(logEraseStart)]) 137 | [self.scribbleDelegate logEraseStart]; 138 | } else { 139 | if ([self.scribbleDelegate respondsToSelector:@selector(logScribbleStart)]) 140 | [self.scribbleDelegate logScribbleStart]; 141 | } 142 | 143 | NSValue *touchValue = [NSValue valueWithPointer:(__bridge const void *)(touch)]; 144 | NSString *key = [NSString stringWithFormat:@"%@", touchValue]; 145 | 146 | BOOL touchShouldBeCancelled; 147 | NSSet *keysOfScribblesToBeRemoved=[self.scribbles keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) { 148 | Scribble *scrib=(Scribble*)obj; 149 | CGPoint topPoint = scrib.topMostPoint; 150 | return (topPoint.y > point.y); 151 | }]; 152 | if ([keysOfScribblesToBeRemoved anyObject]) { 153 | [self.scribbles removeObjectsForKeys:[keysOfScribblesToBeRemoved allObjects]]; 154 | } 155 | touchShouldBeCancelled=([[self.scribbles allValues] count]>0)?YES:NO; 156 | if (!touchShouldBeCancelled) { 157 | [self.scribbles setValue:scribble forKey:key]; 158 | } 159 | } 160 | 161 | - (void) appendTouchPoint:(CGPoint)point forTouch:(UITouch *)touch AndEvent:(UIEvent *) event{ 162 | 163 | NSValue *touchValue = [NSValue valueWithPointer:(__bridge const void *)(touch)]; 164 | Scribble *scribble = [self.scribbles valueForKey: 165 | [NSString stringWithFormat:@"%@", touchValue]]; 166 | 167 | if (scribble.isEraseOn) { 168 | CGRect eraseRect = CGRectMake(point.x, point.y, scribble.lineWidth, scribble.lineWidth); 169 | [self eraseScribbleInRect:eraseRect]; 170 | [self setNeedsDisplay]; 171 | } 172 | else { 173 | CGRect drawRect = [scribble addPoint:point]; 174 | [self setNeedsDisplayInRect:drawRect]; 175 | } 176 | } 177 | 178 | - (void) endTouchPoint:(CGPoint)point forTouch:(UITouch *)touch AndEvent:(UIEvent *) event; { 179 | 180 | NSValue *touchValue = [NSValue valueWithPointer:(__bridge const void *)(touch)]; 181 | NSString *key = [NSString stringWithFormat:@"%@", touchValue]; 182 | 183 | Scribble *scribble = [self.scribbles valueForKey:key]; 184 | 185 | if (scribble) { 186 | if (scribble.isEraseOn) { 187 | if ([self.scribbleDelegate respondsToSelector:@selector(logEraseEnd)]) 188 | [self.scribbleDelegate logEraseEnd]; 189 | } else { 190 | if ([self.scribbleDelegate respondsToSelector:@selector(logScribbleEnd)]) 191 | [self.scribbleDelegate logScribbleEnd]; 192 | if ([self.scribbleDelegate respondsToSelector:@selector(scribbleDidEnd)]) 193 | [self.scribbleDelegate scribbleDidEnd]; 194 | } 195 | if (!scribble.isEraseOn) { 196 | [self.finishedScribbles addObject:scribble]; 197 | } 198 | [self.scribbles removeObjectForKey:key]; 199 | 200 | if ([self.finishedScribbles count] == 1) { 201 | if ([self.scribbleDelegate respondsToSelector:@selector(scribbleDidStart)]) 202 | [self.scribbleDelegate scribbleDidStart]; 203 | } 204 | } 205 | else { 206 | TLog(@"ended scribb, object not found in scrib show view!\n"); 207 | } 208 | [self setNeedsDisplay]; 209 | } 210 | 211 | - (void) cancelTouchPoint:(CGPoint)point forTouch:(UITouch *)touch AndEvent:(UIEvent *) event { 212 | TLog(@"Cancelled touch with point X=%.2f and y=%.2f \n", point.x, point.y); 213 | } 214 | 215 | -(void) outOfBounds:(CGPoint)point forTouch:(UITouch*)touch andEvent:(UIEvent*)event 216 | { 217 | NSValue *touchValue = [NSValue valueWithPointer:(__bridge const void *)(touch)]; 218 | NSString *key = [NSString stringWithFormat:@"%@", touchValue]; 219 | 220 | Scribble *scribble = [self.scribbles valueForKey:key]; 221 | if (scribble) 222 | { 223 | if (scribble.isEraseOn) { 224 | if ([self.scribbleDelegate respondsToSelector:@selector(logEraseEnd)]) 225 | [self.scribbleDelegate logEraseEnd]; 226 | } else { 227 | if ([self.scribbleDelegate respondsToSelector:@selector(logScribbleEnd)]) 228 | [self.scribbleDelegate logScribbleEnd]; 229 | if ([self.scribbleDelegate respondsToSelector:@selector(scribbleDidEnd)]) 230 | [self.scribbleDelegate scribbleDidEnd]; 231 | } 232 | if (!scribble.isEraseOn) { 233 | [self.finishedScribbles addObject:scribble]; 234 | } 235 | [self.scribbles removeObjectForKey:key]; 236 | } 237 | [self setNeedsDisplay]; 238 | } 239 | 240 | #pragma mark - Get Finished Scribbles 241 | 242 | -(NSArray*) getFinishedScribbles { 243 | return [NSArray arrayWithArray:self.finishedScribbles]; 244 | } 245 | 246 | -(void) changeColorOfScribbleTo:(UIColor*)changedColor { 247 | if (changedColor) { 248 | self.currentScribbleStrokeColor =changedColor; 249 | } 250 | [self setNeedsDisplay]; 251 | } 252 | 253 | #pragma mark - Scribble Processor Delegate 254 | 255 | - (void) didPassOnTouch:(UITouch *) touch withEvent:(UIEvent *) event 256 | { 257 | if(!self) return; 258 | CGPoint tPt = [touch locationInView: self]; 259 | if (!(CGRectContainsPoint(self.bounds, tPt))) 260 | { 261 | TLog(@"Out of bounds"); 262 | [self outOfBounds:tPt forTouch:touch andEvent:event]; 263 | return; 264 | } 265 | 266 | if (UITouchPhaseBegan == touch.phase) { 267 | [self addTouchPoint:tPt forTouch:touch AndEvent:event]; 268 | } 269 | if (UITouchPhaseMoved == touch.phase) { 270 | [self appendTouchPoint:tPt forTouch:touch AndEvent:event]; 271 | } 272 | if (UITouchPhaseEnded == touch.phase) { 273 | [self endTouchPoint:tPt forTouch:touch AndEvent:event]; 274 | } 275 | if (UITouchPhaseCancelled == touch.phase) { 276 | [self cancelTouchPoint:tPt forTouch:touch AndEvent:event]; 277 | } 278 | } 279 | 280 | -(void) scribbleDidEnd 281 | { 282 | if (self.scribbleEndCompletion) 283 | self.scribbleEndCompletion(); 284 | } 285 | 286 | @end 287 | -------------------------------------------------------------------------------- /TattleControl/SnapShotView.m: -------------------------------------------------------------------------------- 1 | // 2 | // SnapShotView.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "SnapShotView.h" 26 | #import "TattleControl.h" 27 | #import "ScribbleEraseView.h" 28 | #import "MovableEditorView.h" 29 | #import "CommonMacro.h" 30 | #import "UIImage+GiffAnimation.h" 31 | #import "TLogControlMacro.h" 32 | 33 | enum{ 34 | eBorderEdgeInsetTop = 40, 35 | eBorderEdgeInsetBottom = 10, 36 | eBorderEdgeInsetLeftRight = 20 37 | }BorderEdgeInset; 38 | 39 | CGFloat const CloseButtonSize = 80; 40 | CGFloat const TitleLabelOriginY = 20; 41 | CGFloat const TitleLabelHeight = 20; 42 | 43 | CGFloat const kBorderWidth = 0.0; 44 | CGFloat const GifAnimationDuration = 3.4; 45 | 46 | @interface SnapShotView () 47 | 48 | @property (nonatomic,strong) ScribbleEraseView *scribbleView; 49 | @property (nonatomic,strong) UIColor *scribbleColor; 50 | @property (nonatomic, strong) UIView *baseView; 51 | @property (nonatomic, strong) UIButton *closeBtn; 52 | @property (nonatomic, strong) UIView *noSelectionView; 53 | @property (nonatomic, strong) UIImageView *screenShotImageView; 54 | 55 | @end 56 | 57 | @implementation SnapShotView 58 | 59 | - (id)initWithFrame:(CGRect)frame 60 | { 61 | self = [super initWithFrame:frame]; 62 | if (self) { 63 | // Initialization code 64 | } 65 | return self; 66 | } 67 | 68 | #pragma mark - Shared View & Close button 69 | 70 | -(void)designBaseViewFrame 71 | { 72 | self.layer.borderWidth = kBorderWidth; 73 | self.layer.borderColor = [UIColor blackColor].CGColor; 74 | self.clipsToBounds = YES; 75 | self.baseView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.7]; 76 | UILabel *titleLbl = [[UILabel alloc] initWithFrame:CGRectMake(0, TitleLabelOriginY, self.baseView.frame.size.width, TitleLabelHeight)]; 77 | titleLbl.text = @"Tattle-UI"; 78 | titleLbl.textColor = [UIColor whiteColor]; 79 | titleLbl.backgroundColor = [UIColor clearColor]; 80 | titleLbl.textAlignment = NSTextAlignmentCenter; 81 | [self.baseView addSubview:titleLbl]; 82 | } 83 | 84 | -(void)addCloseButton 85 | { 86 | self.closeBtn = [UIButton buttonWithType:UIButtonTypeCustom]; 87 | CGSize buttonSize = CGSizeMake(resizeDependsDevice(CloseButtonSize),resizeDependsDevice(TitleLabelHeight)); 88 | self.closeBtn.frame = CGRectMake(self.baseView.frame.origin.x + self.baseView.frame.size.width - buttonSize.width -10, TitleLabelOriginY,buttonSize.width,buttonSize.height); 89 | [self.closeBtn addTarget:[TattleControl sharedControl] action:@selector(closeButtonFired:) forControlEvents:UIControlEventTouchUpInside]; 90 | [self.closeBtn setBackgroundColor:[UIColor clearColor]]; 91 | [self.closeBtn setTitleColor:[UIColor colorWithRed:0.0 green:122.0/255.0 blue:1.0 alpha:1.0] forState:UIControlStateNormal]; 92 | if (IS_IPAD) 93 | self.closeBtn.titleLabel.font = [UIFont systemFontOfSize:22.0]; 94 | else 95 | self.closeBtn.titleLabel.font = [UIFont systemFontOfSize:16.0]; 96 | self.closeBtn.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight; 97 | self.closeBtn.contentVerticalAlignment = UIControlContentVerticalAlignmentTop; 98 | [self.closeBtn setTitle:@"Cancel" forState:UIControlStateNormal]; 99 | [self.closeBtn setNeedsDisplay]; 100 | [self.baseView addSubview:self.closeBtn]; 101 | } 102 | 103 | +(SnapShotView*)sharedView 104 | { 105 | static dispatch_once_t predicate = 0; 106 | static SnapShotView *sharedInstance = nil; 107 | 108 | dispatch_once(&predicate, ^{ 109 | sharedInstance = [[self alloc] init]; 110 | sharedInstance.baseView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 111 | [sharedInstance setFrame:[sharedInstance getSnapShotViewFrameFromSize:[sharedInstance getScreenShotSize]]]; 112 | sharedInstance.screenShotImageView = [[UIImageView alloc]initWithFrame:CGRectMake(0.0, 0.0, sharedInstance.frame.size.width, sharedInstance.frame.size.height)]; 113 | sharedInstance.screenShotImageView.contentMode = UIViewContentModeScaleAspectFit; 114 | [sharedInstance addSubview:sharedInstance.screenShotImageView]; 115 | [sharedInstance.baseView addSubview:sharedInstance]; 116 | [sharedInstance designBaseViewFrame]; 117 | [sharedInstance addCloseButton]; 118 | 119 | }); 120 | return sharedInstance; 121 | } 122 | 123 | #pragma mark - Screen Sizes 124 | 125 | -(CGSize)getScreenShotSize 126 | { 127 | CGFloat width = [[UIScreen mainScreen]bounds].size.width; 128 | CGFloat height = [[UIScreen mainScreen]bounds].size.height - eBorderEdgeInsetTop; 129 | return CGSizeMake(width, height); 130 | } 131 | 132 | -(CGRect)getSnapShotViewFrameFromSize:(CGSize)size 133 | { 134 | CGFloat aspectRatio = size.height/size.width; 135 | CGFloat height = size.height - eBorderEdgeInsetBottom; 136 | CGFloat width = height/aspectRatio; 137 | CGFloat border = size.width - width; 138 | CGFloat x = border/2; 139 | return CGRectMake(x, eBorderEdgeInsetTop, width, height); 140 | } 141 | 142 | -(BOOL)isScribbleViewCreated 143 | { 144 | if (!self.scribbleView) 145 | { 146 | self.scribbleView = [[ScribbleEraseView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; 147 | self.scribbleView.backgroundColor = [UIColor clearColor]; 148 | __weak typeof(self) weakSelf = self; 149 | self.scribbleView.scribbleEndCompletion = ^{ 150 | [weakSelf addMovableControlToSnapView]; 151 | }; 152 | } 153 | return YES; 154 | } 155 | 156 | #pragma mark - Background View 157 | 158 | -(UIView*)getBaseView 159 | { 160 | return self.baseView; 161 | } 162 | 163 | #pragma mark - Close Button Handle - Front & Back 164 | 165 | -(void)getCloseButtonToFront 166 | { 167 | [self bringSubviewToFront:self.closeBtn]; 168 | } 169 | 170 | #pragma mark - Giff image Setting 171 | 172 | -(BOOL)isControlPresentFirstTime 173 | { 174 | NSNumber *isFirst = [[NSUserDefaults standardUserDefaults] 175 | objectForKey:@"IsFirstTimeToGif"]; 176 | return (isFirst) ? NO : YES; 177 | } 178 | 179 | #pragma mark - Giff Animation for demo 180 | 181 | -(void)gifAnimationEnd:(UIImageView*)imageView 182 | { 183 | [imageView removeFromSuperview]; 184 | [[NSUserDefaults standardUserDefaults] 185 | setObject:[NSNumber numberWithBool:YES] forKey:@"IsFirstTimeToGif"]; 186 | [[NSUserDefaults standardUserDefaults] synchronize]; 187 | [self addScribbleView]; 188 | } 189 | 190 | -(void)animateDemoGiff 191 | { 192 | UIView *baseView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)]; 193 | baseView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.4]; 194 | UIImageView *imageview = [[UIImageView alloc] initWithFrame:CGRectMake(0,0 , 320, 320)]; 195 | imageview.center = CGPointMake(baseView.frame.size.width/2, baseView.frame.size.height/2); 196 | [baseView addSubview:imageview]; 197 | [self addSubview:baseView]; 198 | NSURL *url = [[NSBundle mainBundle] URLForResource:@"gif_3D" withExtension:@"gif"]; 199 | imageview.image = [UIImage animatedImageWithAnimatedGIFURL:url]; 200 | [self performSelector:@selector(gifAnimationEnd:) withObject:baseView afterDelay:GifAnimationDuration]; 201 | } 202 | 203 | #pragma mark - Add scribble view 204 | 205 | -(void)addScribbleView 206 | { 207 | if (![self isScribbleViewCreated]) 208 | return; 209 | [self.scribbleView changeColorOfScribbleTo:self.scribbleColor]; 210 | self.scribbleView.userInteractionEnabled = YES; 211 | self.scribbleView.isEraseOn = NO; 212 | [self addSubview:self.scribbleView]; 213 | [self getCloseButtonToFront]; 214 | [self setNeedsDisplay]; 215 | } 216 | 217 | -(void)changeScribbleColor:(UIColor*)scribbleColor 218 | { 219 | if (self.scribbleView) 220 | { 221 | [self.scribbleView changeColorOfScribbleTo:scribbleColor]; 222 | } 223 | self.scribbleColor = scribbleColor; 224 | } 225 | 226 | -(void)addScribbleControllToSnapView 227 | { 228 | if ([self isControlPresentFirstTime]) 229 | { 230 | [self animateDemoGiff]; 231 | return; 232 | } 233 | [self addScribbleView]; 234 | } 235 | 236 | #pragma mark - Remove Scribble view 237 | 238 | -(void)removeScribbleControllFromSnapView 239 | { 240 | [self.scribbleView removeFromSuperview]; 241 | } 242 | 243 | -(CGFloat)getScale 244 | { 245 | CGFloat scale = 1.0; 246 | 247 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 248 | if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]){ 249 | scale = [[UIScreen mainScreen] scale]; 250 | } 251 | #endif 252 | TLog(@"Screen scale %f", scale); 253 | return scale; 254 | } 255 | 256 | -(void)assignBackgroundColorWithImage:(UIImage*)fillingImage 257 | { 258 | CGFloat scale = [self getScale]; 259 | CGSize size = CGSizeMake(self.frame.size.width * scale, self.frame.size.height * scale); 260 | UIImage *resizedImage = [self imageWithImage:fillingImage scaledToSize:size]; 261 | [self.screenShotImageView setImage:resizedImage]; 262 | [[MovableEditorView sharedView] resetRecordingControl]; 263 | } 264 | 265 | #pragma mark - Change movable background color 266 | 267 | -(void)setMovableControlBackgroundColor:(UIColor*)backgroundColor 268 | { 269 | [[MovableEditorView sharedView] assignBackgroundColor:backgroundColor]; 270 | } 271 | 272 | -(void)setMovableControlBackgroundColor:(UIColor *)backgroundColor withAlpha:(CGFloat)alpha 273 | { 274 | [[MovableEditorView sharedView] assignBackgroundColor:backgroundColor withAlpha:alpha]; 275 | } 276 | 277 | #pragma mark add no selection view 278 | -(UIView*)noSelectionView 279 | { 280 | if(!_noSelectionView) 281 | { 282 | _noSelectionView = [[UIView alloc]initWithFrame:[[UIScreen mainScreen] bounds]]; 283 | [_noSelectionView setBackgroundColor:[UIColor clearColor]]; 284 | } 285 | return _noSelectionView; 286 | } 287 | 288 | -(void)getNoSelectionViewToFront 289 | { 290 | [self.noSelectionView removeFromSuperview]; 291 | [self addNoSelectionView]; 292 | } 293 | 294 | -(void)sendNoSelectionViewToBack 295 | { 296 | [self sendSubviewToBack:self.noSelectionView]; 297 | } 298 | 299 | -(void)addNoSelectionView 300 | { 301 | [self insertSubview:self.noSelectionView belowSubview:[MovableEditorView sharedView]]; 302 | [self getCloseButtonToFront]; 303 | } 304 | 305 | #pragma mark - Movable Control Actions 306 | -(void)addMovableControlToSnapView 307 | { 308 | [self removeMovableControlFromSnapView]; 309 | [self addSubview:[MovableEditorView sharedView]]; 310 | } 311 | 312 | -(void)removeMovableControlFromSnapView 313 | { 314 | [[MovableEditorView sharedView] removeFromSuperview]; 315 | } 316 | 317 | #pragma mark - Remove Snap View from window 318 | -(void)removeFromWindow 319 | { 320 | [self.baseView removeFromSuperview]; 321 | [self.scribbleView resetView]; 322 | [self removeMovableControlFromSnapView]; 323 | } 324 | 325 | #pragma mark - Assign Image to snap button 326 | 327 | -(UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize 328 | { 329 | UIGraphicsBeginImageContextWithOptions(newSize, NO, 0.0); 330 | [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)]; 331 | UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); 332 | UIGraphicsEndImageContext(); 333 | return newImage; 334 | } 335 | 336 | @end 337 | -------------------------------------------------------------------------------- /TattleControl/TAudioManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // TAudioManager.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "TAudioManager.h" 26 | #import "TFileManager.h" 27 | #import "TConstants.h" 28 | #import "TLogControlMacro.h" 29 | 30 | @interface TAudioManager() 31 | { 32 | float pitch; 33 | } 34 | 35 | @end 36 | 37 | @interface TAudioManager() 38 | 39 | @property(nonatomic, strong) NSMutableDictionary *recordingParamsDicionary; 40 | @property(nonatomic, strong) NSString *recordingFormat; 41 | @property(nonatomic, strong) AVAudioRecorder *recorder; 42 | @property(nonatomic, strong) AVAudioPlayer *player; 43 | @property(nonatomic, strong) NSString *fileName; 44 | 45 | @property(nonatomic, strong) NSTimer *playerTimer; 46 | @property(nonatomic, strong) NSTimer *pitchTimer; 47 | @property(nonatomic) BOOL isRecorderPaused, isPlayerPaused; 48 | 49 | @end 50 | 51 | @implementation TAudioManager 52 | 53 | +(TAudioManager*)sharedAudioManager{ 54 | 55 | static dispatch_once_t predicate = 0; 56 | static TAudioManager *sharedInstance = nil; 57 | dispatch_once(&predicate, ^{ 58 | sharedInstance = [[self alloc] init]; 59 | }); 60 | return sharedInstance; 61 | } 62 | 63 | -(void)assignTempFileName:(NSString*)fileName 64 | { 65 | self.fileName = fileName; 66 | } 67 | 68 | #pragma amrk Recorder related functions 69 | #pragma mark getRecorderStatus 70 | -(TRecorderStatus)getRecorderStatus 71 | { 72 | if (self.recorder.isRecording) 73 | return TRecorderStatusRecording; 74 | 75 | else if (self.isRecorderPaused) 76 | return TRecorderStatusPaused; 77 | else 78 | return TRecorderStatusIdle; 79 | } 80 | 81 | -(NSMutableDictionary*)recordingParamsDicionary 82 | { 83 | if(!_recordingParamsDicionary) 84 | { 85 | _recordingParamsDicionary = [[NSMutableDictionary alloc]init]; 86 | 87 | [_recordingParamsDicionary setObject:[NSNumber numberWithInteger:TFORMAT_ID] forKey: AVFormatIDKey]; 88 | [_recordingParamsDicionary setObject:[NSNumber numberWithInteger:TSAMPLE_RATE] forKey: AVSampleRateKey]; 89 | [_recordingParamsDicionary setObject:[NSNumber numberWithInteger:TNUMBER_OF_CHANNEL] forKey:AVNumberOfChannelsKey]; 90 | 91 | [_recordingParamsDicionary setObject:[NSNumber numberWithInteger:TBIT_RATE_KEY] forKey:AVEncoderBitRateKey]; 92 | [_recordingParamsDicionary setObject:[NSNumber numberWithInteger:TBIT_DEPTH_KEY] forKey:AVLinearPCMBitDepthKey]; 93 | [_recordingParamsDicionary setObject:[NSNumber numberWithInteger:TAUDIO_QUALITY] forKey: AVEncoderAudioQualityKey]; 94 | 95 | } 96 | return _recordingParamsDicionary; 97 | } 98 | 99 | -(AVAudioRecorder*)recorder{ 100 | 101 | if(!_recorder) 102 | { 103 | self.fileName = (self.fileName) ? self.fileName : [[TFileManager sharedFileManager] getAudioFilePath]; 104 | 105 | NSError *recordingError = nil; 106 | _recorder = [[ AVAudioRecorder alloc] initWithURL:[NSURL fileURLWithPath:self.fileName] settings:self.recordingParamsDicionary error:&recordingError]; 107 | _recorder.meteringEnabled = YES; 108 | 109 | if (recordingError) 110 | { 111 | TLog(@"Recodring error: %@", recordingError.description); 112 | } 113 | } 114 | return _recorder; 115 | } 116 | 117 | #pragma mark StartRecorder and timer 118 | -(void)startRecorderAndTimer 119 | { 120 | self.recorder.meteringEnabled = YES; 121 | [self.recorder record]; 122 | self.isRecorderPaused = NO; 123 | [self enableRecordingPitchTimer]; 124 | } 125 | 126 | #pragma mark StopRecorder and timer 127 | -(void)stopRecorderAndInvalidateTimer 128 | { 129 | self.isRecorderPaused = NO; 130 | [self.recorder stop]; 131 | [self invalidateRecorderAndTimer]; 132 | } 133 | 134 | #pragma mark EnableRecordingPitchTimer 135 | -(void)enableRecordingPitchTimer 136 | { 137 | self.pitchTimer = [NSTimer scheduledTimerWithTimeInterval:0.01 target: self selector: @selector(pitchTimerCallBack:) userInfo: nil repeats: YES]; 138 | } 139 | 140 | #pragma mark DisableRecorderAndPitchTimer 141 | -(void)invalidateRecorderAndTimer 142 | { 143 | self.recorder = nil; 144 | [self.pitchTimer invalidate]; 145 | } 146 | 147 | #pragma mark Start Recording 148 | -(TRecordingStatus)startRecording:(NSString*)fileName 149 | { 150 | if(![self.fileName isEqualToString:fileName]) 151 | { 152 | self.fileName = fileName; 153 | self.recorder = nil; 154 | } 155 | 156 | if(self.recorder.isRecording || self.isRecorderPaused) 157 | { 158 | return [self toggleRecording]; 159 | } 160 | else 161 | { 162 | //Activate audio session 163 | if(![self activateAudioSession]) 164 | return TRecordingStatusFailedToStartAudioSession; 165 | 166 | if ([self.recorder prepareToRecord] == YES) 167 | { 168 | [self startRecorderAndTimer]; 169 | [[TFileManager sharedFileManager]rollbackTheRecordedAudios]; 170 | return TRecordingStatusStarted; 171 | } 172 | else 173 | { 174 | TLog(@"Error: Recorder not ready:"); 175 | return TRecordingStatusFailedToStart; 176 | } 177 | } 178 | } 179 | 180 | #pragma mark Toggle recording 181 | -(TRecordingStatus)toggleRecording 182 | { 183 | if(self.recorder.recording) 184 | { 185 | [self pauseRecording]; 186 | return TRecordingStatusPaused; 187 | } 188 | else 189 | { 190 | [self resumeRecording]; 191 | return TRecordingStatusResumed; 192 | } 193 | } 194 | 195 | #pragma mark PauseRecording 196 | -(TRecordingStatus)pauseRecording 197 | { 198 | [self.recorder pause]; 199 | self.isRecorderPaused = YES; 200 | [self.pitchTimer invalidate]; 201 | return TRecordingStatusPaused; 202 | } 203 | 204 | #pragma mark resumeRecording 205 | -(TRecordingStatus)resumeRecording 206 | { 207 | [self startRecorderAndTimer]; 208 | return TRecordingStatusResumed; 209 | } 210 | 211 | #pragma mark PauseRecording 212 | -(TRecordingStatus)stopRecording 213 | { 214 | TRecorderStatus recorderStatus = [self getRecorderStatus]; 215 | 216 | if(recorderStatus == TRecorderStatusPaused || recorderStatus == TRecorderStatusRecording) 217 | { 218 | [self stopRecorderAndInvalidateTimer]; 219 | return TRecordingStatusAlreadyStopped; 220 | } 221 | return TRecordingStatusUnkownState; 222 | } 223 | 224 | #pragma mark PitchTimerCallback 225 | -(void)pitchTimerCallBack:(NSTimer*)timer 226 | { 227 | [self.recorder updateMeters]; 228 | float linear1 = pow (10, [self.recorder averagePowerForChannel:0] / 20); 229 | 230 | if (linear1>0.03) 231 | pitch = linear1+.20; 232 | else 233 | pitch = 0.0; 234 | 235 | pitch =linear1; 236 | 237 | float minutes = floor(self.recorder.currentTime/60); 238 | float seconds = self.recorder.currentTime - (minutes * 60); 239 | 240 | NSString *time = [NSString stringWithFormat:@"%0.0f.%0.0f",minutes, seconds]; 241 | if(self.recordingProgressBlock) 242 | self.recordingProgressBlock(time, self.recorder.currentTime,pitch); 243 | } 244 | 245 | #pragma mark audioplayer related functions 246 | #pragma mark Enable Player Timer 247 | -(void)enablePlayerTimer 248 | { 249 | self.playerTimer = [NSTimer scheduledTimerWithTimeInterval: 0.01 target: self selector: @selector(playerTimerCallBack:) userInfo: nil repeats: YES]; 250 | } 251 | 252 | #pragma mark InvalidatePlayeranTimer 253 | -(void)invalidatePlayerAndTimer 254 | { 255 | self.player = nil; 256 | [self.playerTimer invalidate]; 257 | } 258 | 259 | #pragma mark startPlayerAndTimer 260 | -(void)startplayerAndTimer 261 | { 262 | [self.player play]; 263 | self.isPlayerPaused = NO; 264 | [self enablePlayerTimer]; 265 | } 266 | 267 | #pragma mark stopPayerAndInvalidateTimer 268 | -(void)stopPlayerAndInvalidateTimer 269 | { 270 | self.isPlayerPaused = NO; 271 | [self.player stop]; 272 | [self invalidatePlayerAndTimer]; 273 | } 274 | 275 | #pragma mark getPlayerStatus 276 | -(TPlayerStatus)getPlayerStatus 277 | { 278 | if(self.player.isPlaying) 279 | return TPlayerStatusPlaying; 280 | 281 | else if(self.isPlayerPaused) 282 | return TPlayerStatusPaused; 283 | 284 | else 285 | return TPlayerStatusIdle; 286 | } 287 | 288 | #pragma mark 289 | #pragma mark playerTimerCallback 290 | -(void)playerTimerCallBack:(NSTimer*)timer{ 291 | 292 | if(self.playingProgressBlock) 293 | { 294 | float minutes = floor(self.player.currentTime/60); 295 | float seconds = self.player.currentTime - (minutes * 60); 296 | 297 | NSString *time = [NSString stringWithFormat:@"%0.0f.%0.0f",minutes, seconds]; 298 | self.playingProgressBlock(time, self.player.currentTime, self.player.duration); 299 | } 300 | } 301 | 302 | #pragma mark activateAudioSession 303 | -(BOOL)activateAudioSession{ 304 | 305 | AVAudioSession *audioSession = [AVAudioSession sharedInstance]; 306 | NSError *err = nil; 307 | 308 | if(![audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:&err]) 309 | { 310 | TLog(@"Error: failed to set category for audio session+++ description %@", err.description); 311 | return NO; 312 | } 313 | 314 | if(![audioSession setCategory:AVAudioSessionCategoryMultiRoute error:&err]) 315 | { 316 | TLog(@"Error: failed to set category for audio session+++ description %@", err.description); 317 | return NO; 318 | } 319 | 320 | BOOL active = [audioSession setActive: YES error: &err]; 321 | if (!active) 322 | TLog(@"Failed to set category on AVAudioSession+++ Description %@", err.description); 323 | 324 | return YES; 325 | } 326 | 327 | #pragma mark AVAudiopayer delegates 328 | - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{ 329 | 330 | if(self.playingCompletionBlock) 331 | self.playingCompletionBlock(); 332 | 333 | [self stopPlaying]; 334 | } 335 | 336 | - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error{ 337 | 338 | TLog(@"Error: AudioPlayer DecodeError Occured %@", error.description); 339 | if(self.audioPlayerErrorBlock) 340 | self.audioPlayerErrorBlock(error); 341 | } 342 | 343 | #pragma mark startplaying 344 | -(TPlayingStatus)startPlaying:(NSString*)fileName{ 345 | 346 | if(self.player.isPlaying || self.isPlayerPaused) 347 | { 348 | TPlayingStatus status = [self togglePlaying]; 349 | return status; 350 | } 351 | else 352 | { 353 | //Activate audio session 354 | if(![self activateAudioSession]) 355 | return TPlayingStatusFailedToStartAudioSession; 356 | 357 | //Load audio url 358 | NSURL *url = [NSURL fileURLWithPath:fileName]; 359 | NSError *error; 360 | 361 | //create audio player with url 362 | self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error]; 363 | self.player.numberOfLoops = 0; 364 | [self.player setDelegate: self]; 365 | 366 | //Check the player is ready for play 367 | if(![self.player prepareToPlay]){ 368 | TLog(@"Failed to start player %@", error.description); 369 | return TPlayingStatusFailedToStart; 370 | } 371 | 372 | [self startplayerAndTimer]; 373 | return TPlayingStatusStarted; 374 | } 375 | } 376 | 377 | #pragma mark togglePlaying 378 | -(TPlayingStatus)togglePlaying 379 | { 380 | if(self.player.isPlaying) 381 | { 382 | return [self pausePlayer]; 383 | } 384 | 385 | else if(self.isPlayerPaused) 386 | { 387 | return [self resumePlayer]; 388 | } 389 | 390 | return TPlayingStatusUnknownState; 391 | } 392 | 393 | #pragma mark resumePlaying 394 | -(TPlayingStatus)resumePlayer 395 | { 396 | [self.player play]; 397 | [self enablePlayerTimer]; 398 | return TPlayingStatusResumed; 399 | } 400 | 401 | #pragma mark pausePlaying 402 | -(TPlayingStatus)pausePlayer 403 | { 404 | [self.player pause]; 405 | self.isPlayerPaused = YES; 406 | [self.playerTimer invalidate]; 407 | return TPlayingStatusPaused; 408 | } 409 | 410 | #pragma mark setCurrentPlaying time 411 | -(void)setCurrentPlayingTime:(double)secs 412 | { 413 | if (self.player.isPlaying || self.isPlayerPaused) 414 | { 415 | [self.player setCurrentTime:secs]; 416 | } 417 | else 418 | { 419 | TLog(@"Error: Can't set current time. Audio player not started yet"); 420 | } 421 | } 422 | 423 | #pragma invalidateCompletionBlock 424 | -(void)invalidateAllProgressBlock 425 | { 426 | self.playingProgressBlock = nil; 427 | self.playingCompletionBlock = nil; 428 | 429 | self.recordingProgressBlock = nil; 430 | self.audioPlayerErrorBlock = nil; 431 | } 432 | 433 | #pragma mark stopPlaying 434 | -(TPlayingStatus)stopPlaying 435 | { 436 | [self.player stop]; 437 | [self invalidatePlayerAndTimer]; 438 | self.isPlayerPaused = NO; 439 | return TPlayingStatusStopped; 440 | } 441 | 442 | @end 443 | -------------------------------------------------------------------------------- /TattleControl/MovableEditorView.m: -------------------------------------------------------------------------------- 1 | // 2 | // MovableEditorView.m 3 | /* 4 | Copyright (c) 2014 TattleUI (http://www.npcompete.com/) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | #import "MovableEditorView.h" 26 | #import "SnapShotView.h" 27 | #import "TAudioManager.h" 28 | #import "TFileManager.h" 29 | #import "TattleControl.h" 30 | #import "CommonMacro.h" 31 | #import 32 | #import "TConstants.h" 33 | #import "TLogControlMacro.h" 34 | 35 | NSString *const AUDIO_SELECTED_IMAGE = @"audio_selected.png"; 36 | NSString *const AUDIO_UNSELECTED_IMAGE = @"audio_unselected.png"; 37 | 38 | NSString *const EMAIL_SELECTED_IMAGE = @"email_selected.png"; 39 | NSString *const EMAIL_UNSELECTED_IMAGE = @"email_Unselected.png"; 40 | 41 | NSString *const RECORD_IMAGE = @"record.png"; 42 | NSString *const STOP_IMAGE = @"stop.png"; 43 | NSString *const PLAY_IMAGE = @"play.png"; 44 | 45 | NSInteger const kMaxRecordingTime = 2.0; //in minutes 46 | NSInteger const kSecondsPerMinutes = 60.0; 47 | CGFloat const MovableControlBackgroundAlpha = 0.4; 48 | 49 | enum{ 50 | AUDIO_BTN_ID = 100, 51 | EMAIL_BTN_ID 52 | }; 53 | typedef enum NSInteger MovableControlButtonId; 54 | 55 | @interface PlayRecordProgressView : UIView 56 | 57 | @property (nonatomic) CGFloat value; 58 | @property CGFloat minimumValue; 59 | @property CGFloat maximumValue; 60 | @property CGFloat arcStartAngle; 61 | @property CGFloat cutoutSize; 62 | @property CGFloat valueArcWidth; 63 | @property (nonatomic,strong) UIColor *trackingColor; 64 | @property (nonatomic,strong) CAShapeLayer *unTrackedLayer; 65 | 66 | -(void)resetProgressView; 67 | 68 | @end 69 | 70 | @interface MovableEditorView() 71 | 72 | @property(nonatomic, weak) IBOutlet UIButton *audioBtn, *emailBtn; 73 | 74 | @property(nonatomic) CGFloat recentlyRecordedAudioPlayBackTime; 75 | @property(nonatomic) NSInteger selectedBtnId, previouslySelectedBtnId; 76 | @property(nonatomic) BOOL isRecordedAudioAvailable; 77 | 78 | @property (nonatomic,weak) IBOutlet PlayRecordProgressView *progressView; 79 | 80 | -(IBAction)audioPressed:(id)sender; 81 | -(IBAction)emailPressed:(id)sender; 82 | 83 | @end 84 | 85 | @implementation PlayRecordProgressView 86 | #define kDCControlDegreesToRadians(x) (M_PI * (x) / 180.0) 87 | #define kDCControlRadiansToDegrees(x) ((x) * 180.0 / M_PI) 88 | 89 | -(void)awakeFromNib 90 | { 91 | self.clipsToBounds = NO; 92 | self.opaque = YES; 93 | self.cutoutSize = 0.0; 94 | self.arcStartAngle = 270.0; 95 | self.minimumValue = 10.0; 96 | self.maximumValue = 11.0; 97 | self.valueArcWidth = 1.5; 98 | self.trackingColor = [UIColor colorWithRed:215.0/255.0 green:215.0/255.0 blue:150.0/255.0 alpha:1]; 99 | } 100 | 101 | - (void)setValue:(CGFloat)newValue 102 | { 103 | if (newValue > self.maximumValue) 104 | _value = self.maximumValue; 105 | else if (newValue < self.minimumValue) 106 | _value = self.minimumValue; 107 | else 108 | _value = newValue; 109 | [self setNeedsDisplay]; 110 | } 111 | -(void)fillLayerCommonFields:(CAShapeLayer*)subLayer 112 | { 113 | subLayer.fillColor = [UIColor clearColor].CGColor; 114 | subLayer.lineWidth = self.frame.size.width/12; 115 | subLayer.shadowColor = subLayer.fillColor; 116 | subLayer.shadowRadius = 50.0; 117 | subLayer.masksToBounds = NO; 118 | subLayer.shadowOffset = CGSizeMake(5.0f, 5.0f); 119 | subLayer.shadowOpacity = 0.5f; 120 | subLayer.shadowPath = subLayer.path; 121 | } 122 | 123 | -(void)showUnTrackCircle 124 | { 125 | CGPoint centerPoint = CGPointMake(self.frame.size.width/2, self.frame.size.height/2); 126 | if (!self.unTrackedLayer) 127 | { 128 | CAShapeLayer *unTrackedCircle = [CAShapeLayer layer]; 129 | CGMutablePathRef unTrackedPath = CGPathCreateMutable(); 130 | CGPathAddArc(unTrackedPath, nil, centerPoint.x , centerPoint.y, self.frame.size.width/2 -resizeDependsDevice(2) , kDCControlDegreesToRadians(self.arcStartAngle + self.cutoutSize / 2), kDCControlDegreesToRadians(self.arcStartAngle + 360 - self.cutoutSize / 2), NO); 131 | unTrackedCircle.path = unTrackedPath; 132 | 133 | [self fillLayerCommonFields:unTrackedCircle]; 134 | unTrackedCircle.strokeColor = [[UIColor lightGrayColor] colorWithAlphaComponent:0.3].CGColor; 135 | self.unTrackedLayer = unTrackedCircle; 136 | CGPathRelease(unTrackedPath); 137 | } 138 | [self.layer addSublayer:self.unTrackedLayer]; 139 | } 140 | 141 | - (void)drawRect:(CGRect)rect 142 | { 143 | CGContextRef context = UIGraphicsGetCurrentContext(); 144 | 145 | CGRect boundsRect = self.bounds; 146 | float x = boundsRect.size.width / 2; 147 | float y = boundsRect.size.height / 2; 148 | CGContextSaveGState(context); 149 | CGContextSetLineWidth(context, self.valueArcWidth); 150 | [self.trackingColor set]; 151 | // Drawing code 152 | CGFloat valueAdjusted = (self.value - self.minimumValue) / (self.maximumValue - self.minimumValue); 153 | CGContextAddArc(context, 154 | x, 155 | y, 156 | boundsRect.size.width / 2 -resizeDependsDevice(2), 157 | kDCControlDegreesToRadians(self.arcStartAngle + self.cutoutSize / 2), 158 | kDCControlDegreesToRadians(self.arcStartAngle + self.cutoutSize / 2 + (360 - self.cutoutSize) * valueAdjusted), 159 | 0); 160 | CGContextStrokePath(context); 161 | CGContextRestoreGState(context); 162 | } 163 | 164 | -(void)resetProgressView 165 | { 166 | self.value = 10.0; 167 | [self.unTrackedLayer removeFromSuperlayer]; 168 | } 169 | 170 | @end 171 | 172 | @implementation MovableEditorView 173 | 174 | #pragma mark - setBackground Color for this 175 | 176 | -(void)assignBackgroundColor:(UIColor *)backgroundColor withAlpha:(CGFloat)alpha 177 | { 178 | self.backgroundColor = [backgroundColor colorWithAlphaComponent:alpha]; 179 | } 180 | 181 | -(void)assignBackgroundColor:(UIColor *)backgroundColor 182 | { 183 | [self assignBackgroundColor:backgroundColor withAlpha:MovableControlBackgroundAlpha]; 184 | } 185 | 186 | #pragma mark - Shared Control 187 | 188 | +(MovableEditorView*)sharedView 189 | { 190 | static dispatch_once_t predicate = 0; 191 | static MovableEditorView *sharedInstance = nil; 192 | 193 | dispatch_once(&predicate, ^{ 194 | NSString *nibName = IS_IPAD ? @"MovableEditorViewiPad" : @"MovableEditorView"; 195 | sharedInstance = [[[NSBundle mainBundle] loadNibNamed:nibName owner:nil options:nil] lastObject]; 196 | CGRect frame = [[UIScreen mainScreen] bounds]; 197 | [sharedInstance setFrame:CGRectMake(frame.origin.x, frame.size.height/2, sharedInstance.frame.size.width, sharedInstance.frame.size.height)]; 198 | [sharedInstance assignBackgroundColor:[UIColor blackColor]];//Default 199 | sharedInstance.layer.cornerRadius = resizeDependsDevice(4.0); 200 | sharedInstance.layer.shadowColor = [[[UIColor clearColor]colorWithAlphaComponent:0.4 ]CGColor]; 201 | sharedInstance.layer.shadowOpacity = 1.0; 202 | [sharedInstance addPanGestureRecognizerToContainerView]; 203 | 204 | [[TAudioManager sharedAudioManager] assignTempFileName:[[TFileManager sharedFileManager] getAudioFilePath]]; 205 | [sharedInstance assignCompletionBlocksForAudioManager]; 206 | 207 | [sharedInstance.progressView setBackgroundColor:[UIColor clearColor]]; 208 | sharedInstance.progressView.trackingColor = [UIColor colorWithRed:0.0 green:122.0/255.0 blue:1.0 alpha:1.0]; 209 | sharedInstance.progressView.valueArcWidth = sharedInstance.progressView.frame.size.width / 12; 210 | }); 211 | return sharedInstance; 212 | } 213 | 214 | #pragma mark reset recording and buttons 215 | -(void)resetRecordingControl 216 | { 217 | //Change reset recorder image if it is ready to play state 218 | [self resetRecorderImage]; 219 | [self.progressView resetProgressView]; 220 | [self stopPlaying]; 221 | [self stopRecording]; 222 | } 223 | 224 | - (id)initWithFrame:(CGRect)frame 225 | { 226 | self = [super initWithFrame:frame]; 227 | if (self) { 228 | // Initialization code 229 | } 230 | return self; 231 | } 232 | 233 | #pragma Pan Gesture related function 234 | -(void)addPanGestureRecognizerToContainerView 235 | { 236 | UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panningContainerView:)]; 237 | [self addGestureRecognizer:panGesture]; 238 | } 239 | 240 | -(CGPoint)checkViewOutOfBounds:(UIView*)snapView withBaseView:(UIView*)baseView 241 | { 242 | TLog(@"Snap view Frame %f %f",snapView.center.x,snapView.center.y); 243 | CGPoint leftTopCorner = CGPointMake( snapView.frame.size.width/2, snapView.frame.size.height/2); 244 | CGPoint rightTopCorner = CGPointMake( baseView.frame.size.width - snapView.frame.size.width/2, snapView.frame.size.height/2); 245 | CGPoint leftBottomCornor = CGPointMake( snapView.frame.size.width/2, baseView.frame.size.height - snapView.frame.size.height/2); 246 | CGPoint rightBottomCornor = CGPointMake( baseView.frame.size.width - snapView.frame.size.width/2, baseView.frame.size.height - snapView.frame.size.height/2); 247 | if ( snapView.center.x < leftTopCorner.x && snapView.center.y < leftTopCorner.y) 248 | return leftTopCorner; 249 | else if ( snapView.center.x > rightTopCorner.x && snapView.center.y < rightTopCorner.y) 250 | return rightTopCorner; 251 | else if ( snapView.center.x < leftBottomCornor.x && snapView.center.y > leftBottomCornor.y) 252 | return leftBottomCornor; 253 | else if (snapView.center.x > rightBottomCornor.x && snapView.center.y > rightBottomCornor.y ) 254 | return rightBottomCornor; 255 | else if ( snapView.center.x < leftTopCorner.x) 256 | return CGPointMake(leftTopCorner.x, snapView.center.y); 257 | else if (snapView.center.y < leftTopCorner.y) 258 | return CGPointMake(snapView.center.x,leftTopCorner.y); 259 | else if (snapView.center.x > rightTopCorner.x ) 260 | return CGPointMake(rightTopCorner.x, snapView.center.y); 261 | else if (snapView.center.y < rightTopCorner.y) 262 | return CGPointMake(snapView.center.x,rightTopCorner.y); 263 | else if ( snapView.center.x < leftBottomCornor.x ) 264 | return CGPointMake(leftBottomCornor.x, snapView.center.y); 265 | else if ( snapView.center.y > leftBottomCornor.y) 266 | return CGPointMake(snapView.center.x, leftBottomCornor.y); 267 | else if ( snapView.center.x > rightBottomCornor.x) 268 | return CGPointMake(rightBottomCornor.x, snapView.center.y); 269 | else if ( snapView.center.y > rightBottomCornor.y) 270 | return CGPointMake(snapView.center.x, rightBottomCornor.y); 271 | else 272 | return CGPointZero; 273 | } 274 | 275 | -(void)panningContainerView:(UIPanGestureRecognizer*)gesture 276 | { 277 | CGPoint translation = [gesture translationInView:self.superview]; 278 | CGPoint finalTranslation = CGPointZero; 279 | gesture.view.center = CGPointMake(gesture.view.center.x + translation.x, 280 | gesture.view.center.y + translation.y); 281 | if (gesture.state == UIGestureRecognizerStateCancelled || 282 | gesture.state == UIGestureRecognizerStateFailed || 283 | gesture.state == UIGestureRecognizerStateEnded) 284 | { 285 | if (CGPointEqualToPoint((finalTranslation =[self checkViewOutOfBounds:gesture.view withBaseView:self.superview]),CGPointZero)) 286 | return; 287 | else 288 | gesture.view.center = finalTranslation; 289 | } 290 | [gesture setTranslation:CGPointMake(0, 0) inView:self.window]; 291 | } 292 | 293 | #pragma Save currently selected btn id to maintain state 294 | -(void)setSelectedBtnId:(NSInteger)selectedBtnId 295 | { 296 | _selectedBtnId = selectedBtnId; 297 | if(selectedBtnId == AUDIO_BTN_ID) 298 | { 299 | [self addNoButtonSelectedView]; 300 | } 301 | else 302 | { 303 | [self stopRecording]; 304 | [self stopPlaying]; 305 | if(selectedBtnId == EMAIL_BTN_ID) 306 | { 307 | [self setHidden:YES]; 308 | UIImage *image = [[TattleControl sharedControl] takeScreenShotImageForView:[SnapShotView sharedView]]; 309 | [self setHidden:NO]; 310 | if(image) 311 | { 312 | if([[TFileManager sharedFileManager] saveImage:image]) 313 | [self sendScreenShotAndAudio]; 314 | } 315 | else 316 | { 317 | TLog(@"Error: Failed to get screen shot "); 318 | } 319 | } 320 | } 321 | } 322 | 323 | #pragma send screen shot 324 | -(void)sendScreenShotAndAudio 325 | { 326 | [[TattleControl sharedControl] sendScreenShotAudioFiles]; 327 | } 328 | 329 | #pragma mark - No button seleted view to base view 330 | -(void)addNoButtonSelectedView 331 | { 332 | [[SnapShotView sharedView] addNoSelectionView]; 333 | } 334 | 335 | #pragma mark assigning completion and progress block 336 | -(void)assignCompletionBlocksForAudioManager 337 | { 338 | [[TAudioManager sharedAudioManager] setPlayingCompletionBlock:^{ 339 | self.isRecordedAudioAvailable = NO; 340 | [self.progressView resetProgressView]; 341 | [self changeAudioButtonImageToStartRecodring]; 342 | }]; 343 | [[TAudioManager sharedAudioManager] setRecordingProgressBlock:^(NSString* recordingTime, CGFloat seconds, float pitchLevel){ 344 | CGFloat percent = (CGFloat)(seconds/(kSecondsPerMinutes * kMaxRecordingTime)); 345 | self.progressView.value = self.progressView.minimumValue + percent; 346 | if(seconds > (kSecondsPerMinutes * kMaxRecordingTime)) 347 | { 348 | [self audioPressed:self.audioBtn]; 349 | [self.progressView resetProgressView]; 350 | } 351 | }]; 352 | [[TAudioManager sharedAudioManager] setPlayingProgressBlock:^(NSString *timeString, CGFloat seconds, CGFloat totalDuaration){ 353 | CGFloat percent = (CGFloat)(seconds/totalDuaration); 354 | self.progressView.value = self.progressView.minimumValue + percent; 355 | if(seconds >= totalDuaration) 356 | { 357 | [self audioPressed:self.audioBtn]; 358 | [self.progressView resetProgressView]; 359 | } 360 | }]; 361 | } 362 | 363 | #pragma mark change audio button image to start recording 364 | -(void)changeAudioButtonImageToStartRecodring 365 | { 366 | [[SnapShotView sharedView] sendNoSelectionViewToBack]; 367 | [self.audioBtn setBackgroundImage:[UIImage imageNamed:RECORD_IMAGE] forState:UIControlStateNormal]; 368 | } 369 | 370 | #pragma mark change audio button image to stop 371 | -(void)changeAudioButtonImageToStop 372 | { 373 | [self.progressView showUnTrackCircle]; 374 | [[SnapShotView sharedView] getNoSelectionViewToFront]; 375 | [self.audioBtn setBackgroundImage:[UIImage imageNamed:STOP_IMAGE] forState:UIControlStateNormal]; 376 | } 377 | 378 | -(void)changeAudioButtonImageToStartPlaying 379 | { 380 | [self.progressView resetProgressView]; 381 | [[SnapShotView sharedView] sendNoSelectionViewToBack]; 382 | [self.audioBtn setBackgroundImage:[UIImage imageNamed:PLAY_IMAGE] forState:UIControlStateNormal]; 383 | } 384 | 385 | #pragma mark change audio button image to start playing 386 | -(void)stopRecording 387 | { 388 | if([[TAudioManager sharedAudioManager] getRecorderStatus] == TRecorderStatusRecording) 389 | { 390 | [[TAudioManager sharedAudioManager] stopRecording]; 391 | [self.progressView resetProgressView]; 392 | [self changeAudioButtonImageToStartRecodring]; 393 | } 394 | } 395 | 396 | -(void)stopPlaying 397 | { 398 | if([[TAudioManager sharedAudioManager] getPlayerStatus] == TPlayerStatusPlaying) 399 | { 400 | [[TAudioManager sharedAudioManager] stopPlaying]; 401 | [self.progressView resetProgressView]; 402 | [self changeAudioButtonImageToStartRecodring]; 403 | } 404 | } 405 | 406 | -(IBAction)audioPressed:(id)sender 407 | { 408 | [self controlRecorderAndPlayer]; 409 | } 410 | 411 | -(IBAction)emailPressed:(id)sender 412 | { 413 | [self setSelectedBtnId:EMAIL_BTN_ID]; 414 | } 415 | 416 | #pragma mark audio button fuctionality 417 | -(void)controlRecorderAndPlayer{ 418 | if([[TAudioManager sharedAudioManager] getRecorderStatus] == TRecorderStatusIdle || [[TAudioManager sharedAudioManager] getRecorderStatus] == TRecorderStatusUnkown)//Recorder status idle, unknown 419 | { 420 | if(self.isRecordedAudioAvailable == YES) 421 | { 422 | if([[TAudioManager sharedAudioManager] getPlayerStatus] == TPlayerStatusIdle) 423 | { 424 | NSString *recentlyRecordedFilePath = [[TFileManager sharedFileManager] getRecentlyRecordedAudioFilePath]; 425 | if(recentlyRecordedFilePath) 426 | { 427 | if([[TAudioManager sharedAudioManager] startPlaying:recentlyRecordedFilePath] != TPlayingStatusStarted) 428 | { 429 | [self changeAudioButtonImageToStartRecodring]; 430 | return; 431 | } 432 | [self changeAudioButtonImageToStop]; 433 | } 434 | else 435 | { 436 | TLog(@"Error: No recorded audio file found"); 437 | } 438 | } 439 | else if([[TAudioManager sharedAudioManager] getPlayerStatus] == TPlayerStatusPlaying) 440 | { 441 | [self stopPlaying]; 442 | [self resetRecorderImage]; 443 | } 444 | } 445 | else 446 | { 447 | if([[TAudioManager sharedAudioManager] startRecording:[[TFileManager sharedFileManager] getAudioFilePath]] != TRecordingStatusStarted) 448 | { 449 | return; 450 | } 451 | self.previouslySelectedBtnId = self.selectedBtnId; 452 | [self setSelectedBtnId:AUDIO_BTN_ID]; 453 | //Change to stop button image 454 | [self changeAudioButtonImageToStop]; 455 | } 456 | } 457 | else if([[TAudioManager sharedAudioManager] getRecorderStatus] == TRecorderStatusRecording)//Recorder status recording 458 | { 459 | [[TAudioManager sharedAudioManager] stopRecording]; 460 | [self.progressView resetProgressView]; 461 | 462 | //Change to start playing 463 | [self changeAudioButtonImageToStartPlaying]; 464 | self.isRecordedAudioAvailable = YES; 465 | } 466 | } 467 | 468 | -(void)resetRecorderImage 469 | { 470 | [self.audioBtn setBackgroundImage:[UIImage imageNamed:RECORD_IMAGE] forState:UIControlStateNormal]; 471 | self.isRecordedAudioAvailable = NO; 472 | } 473 | 474 | @end 475 | --------------------------------------------------------------------------------